Compare commits

...

6 commits

Author SHA1 Message Date
WJQSERVER
37917363e2
Merge pull request #18 from infinite-iroha/dev
update deps
2025-06-10 21:54:22 +08:00
wjqserver
96154fff78 update deps 2025-06-10 21:52:32 +08:00
WJQSERVER
76bf441ce8
Merge pull request #17 from infinite-iroha/dev
0.2.0
2025-06-10 21:42:57 +08:00
wjqserver
5ae9e9c12e updaten license 2025-06-10 21:39:37 +08:00
wjqserver
ce5efae287 update 2025-06-10 21:37:53 +08:00
wjqserver
e6b54eedbf remove tgzip 2025-06-10 00:04:15 +08:00
9 changed files with 470 additions and 632 deletions

518
LICENSE
View file

@ -1,201 +1,373 @@
WJQserver Studio 开源许可证
版本 v2.1
Mozilla Public License Version 2.0
==================================
版权所有 © WJQserver Studio 2025
版权所有 © Infinite Iroha 2025
版权所有 © WJQserver 2025
1. Definitions
--------------
定义
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
* 许可 (License): 指的是在本许可证内定义的使用、复制、分发与修改软件的条款与要求。
* 授权方 (Licensor): 指的是拥有版权的个人或组织,亦或是拥有版权的个人或组织所指派的实体,在本许可证中特指 WJQserver Studio。
* 贡献者 (Contributor): 指的是授权方以及根据本许可证授予贡献代码或软件的个人或实体。
* 您 (You): 指的是行使本许可授予的权限的个人或法律实体。
* 衍生作品 (Derivative Works): 指的是基于本软件或本软件任何部分的修改作品,无论修改程度如何。这包括但不限于基于本软件或其任何部分的修改、修订、改编、翻译或其他形式的创作,以及包含本软件或其部分的集合作品。
* 非营利性使用 (Non-profit Use): 指的是不以直接商业盈利为主要目的的使用方式,包括但不限于:
* 个人用途: 由个人为了个人学习、研究、实验、非商业项目、个人网站搭建、毕业设计、家庭内部娱乐等非直接商业目的使用软件。
* 教育用途: 在教育机构(如学校、大学、培训机构)内部用于教学、研究、学术交流等活动。
* 科研用途: 在科研院所、实验室等机构内部用于科学研究、实验开发等活动。
* 慈善与公益用途: 由慈善机构、公益组织等非营利性组织为了其公益使命或慈善事业内部运营使用,或对外提供不直接产生商业利润的公益服务。
* 内部运营用途 (非营利组织) 非营利性组织在其内部运营中使用软件,例如用于行政管理、会员管理、内部沟通、项目管理等非直接营利性活动。
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
开源与自由软件
1.3. "Contribution"
means Covered Software of a particular Contributor.
本项目为开源软件,允许用户在遵循本许可证的前提下访问和使用源代码。
本项目旨在向用户提供尽可能广泛的非商业使用自由,同时保障社区的共同发展和良性生态,并为商业创新提供清晰的路径。
强调版权所有,所有权利由 WJQserver Studio 及贡献者共同保留。
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
许可证条款
1.5. "Incompatible With Secondary Licenses"
means
1. 使用权限
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
* 1.1 非营利性使用: 您被授予在非营利性使用场景下,为了任何目的,自由使用本软件的权限。 非营利性使用的具体场景包括但不限于定义部分所列举的各种情况。
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
* 1.2 商业使用: 您可以在商业环境中使用本软件,无需获得额外授权,但您的商业使用行为必须遵守以下条款:
1.6. "Executable Form"
means any form of the work other than Source Code Form.
* 1.2.1 开源继承 (Copyleft) 与互惠共享: 如果您或您的组织希望将本软件或其衍生作品用于任何商业用途,包括但不限于:
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
* 盈利性分发: 销售、出租、许可分发本软件或其衍生作品。
* 盈利性服务: 基于本软件或其衍生作品提供商业服务,例如 SaaS 服务、咨询服务、定制开发服务、收费技术支持服务等。
* 嵌入式商业应用: 将本软件或其衍生作品嵌入到商业产品或解决方案中进行销售。
* 组织内部商业运营: 在营利性组织的内部运营中使用修改后的版本以直接支持其商业活动,例如定制化内部系统,通过例如但不限于在软件或相关服务中投放广告 (例如 Google Ads 等),应用内购买 (内购), 会员订阅, 增值功能收费等方式直接或间接产生商业收入。
1.8. "License"
means this document.
您必须选择以下两种方式之一:
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
* i) 继承本许可证并开源: 您必须以本许可证或兼容的开源许可证分发您的衍生作品,并公开您的衍生作品的全部源代码,使得您的衍生作品的接收者也享有与您相同的权利,包括进一步修改和商业使用的权利。 本选项旨在促进社区的共同发展和知识共享,确保基于本软件的商业创新成果也能回馈社区。
* ii) 获得授权方明确授权: 如果您不希望以开源方式发布您的衍生作品,或者希望使用其他许可证进行分发,或者您希望在商业运营中使用修改后的版本但不开源,您必须事先获得 WJQserver Studio 的明确书面授权。 授权的具体条款和条件将由 WJQserver Studio 另行协商确定。
1.10. "Modifications"
means any of the following:
* 1.3 保持声明: 公开发布服务时,不得移除或修改软件中包含的原始版权声明、许可证声明以及来源声明。
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
2. 复制与分发
(b) any new file in Source Code Form that contains any Covered
Software.
* 2.1 原始版本复制与分发: 您可以复制和分发本软件的原始版本,前提是必须满足以下条件:
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
* 保留所有声明: 完整保留所有原始版权声明、许可证声明、来源声明以及其他所有权声明。
* 附带许可证: 在分发软件时,必须同时附带本许可证的完整文本,确保接收者知悉并理解本许可证的全部条款。
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
* 2.2 衍生作品复制与分发: 您可以复制和分发基于本软件的衍生作品,您对衍生作品的分发行为将受到本许可证第 1.3 条(开源继承与互惠共享)的约束。
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
3. 修改权限
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
* 3.1 自由修改: 您被授予自由修改本软件的权限,无论修改目的是非营利性使用还是商业用途。
2. License Grants and Conditions
--------------------------------
* 3.2 修改后使用与分发约束: 当您将修改后的版本用于商业用途或分发修改后的版本时,您需要遵守本许可证第 1.3 条(开源继承与互惠共享)以及第 2 条(复制与分发)的规定。 即使您不分发修改后的版本,只要您将其用于商业目的,也需要遵守开源继承条款或获得授权。
2.1. Grants
* 3.3 贡献接受: WJQserver Studio 鼓励社区贡献代码。如果您向本项目贡献代码,您需要同意您的贡献代码按照本许可证条款进行许可。
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
4. 专利权
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
* 4.1 无专利担保,风险自担: 本软件以“现状”提供,授权方及贡献者明确声明,不对本软件的专利侵权问题做任何形式的担保,亦不承担任何因专利侵权可能产生的责任与后果。 用户理解并同意,使用本软件的专利风险完全由用户自行承担。
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
* 4.2 专利纠纷应对: 如因用户使用本软件而引发任何专利侵权指控、诉讼或索赔,用户应自行负责处理并承担全部法律责任。 授权方及贡献者无义务参与任何相关法律程序,亦不承担任何由此产生的费用或赔偿。
2.2. Effective Date
5. 免责声明
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
* 5.1 “现状”提供,无任何保证: 本软件按“现状”提供,不提供任何明示或暗示的保证,包括但不限于适销性、特定用途适用性及非侵权性。
2.3. Limitations on Grant Scope
* 5.2 责任限制: 在适用法律允许的最大范围内,在任何情况下,授权方或任何贡献者均不对因使用或无法使用本软件而产生的任何直接、间接、偶然、特殊、惩罚性或后果性损害(包括但不限于采购替代商品或服务;损失使用、数据或利润;或业务中断)负责,无论其是如何造成的,也无论依据何种责任理论,即使已被告知可能发生此类损害。
* 5.3 用户法律责任: 用户需根据当地法律对待本项目,确保遵守所有适用法规。
6. 许可证期限与终止
* 6.1 许可证期限: 除版权所有人主动宣布放弃本软件版权外,本许可证无限期生效。
* 6.2 许可证终止: 如果您未能遵守本许可证的任何条款或条件,授权方有权终止本许可证。 您的许可证将在您违反本许可证条款时自动终止。
* 6.3 终止后的效力: 许可证终止后,您根据本许可证所享有的所有权利将立即终止,但您在许可证终止前已合法分发的软件副本,其接收者所获得的许可及权利将不受影响,继续有效。 免责声明(第 5 条)和责任限制(第 5.2 条)在本许可证终止后仍然有效。
7. 条款修订
* 7.1 修订权利保留: 授权方保留随时修改本许可证条款的权利,以便更好地适应法律、技术发展以及社区需求。
* 7.2 修订生效与接受: 修订后的条款将在发布时生效,除非另行声明,否则继续使用、复制、分发或修改本软件即表示您接受修订后的条款。授权方鼓励用户定期查阅本许可证的最新版本。
8. 其他
* 8.1 法定权利: 本许可证不影响您作为最终用户在适用法律下的法定权利。
* 8.2 条款可分割性: 若本许可证的某些条款被认定为不可执行,其余条款仍然完全有效。
* 8.3 版本更新: 授权方可能会发布本许可证的修订版本或新版本。您可以选择是继续使用本许可证的旧版本还是选择适用新版本。
WJQserver Studio Open Source License
Version v2.0
Copyright © WJQserver Studio 2024
Definitions
* License: Refers to the terms and requirements for use, reproduction, distribution, and modification defined within this license.
* Licensor: Refers to the individual or organization that holds the copyright, or the entity designated by the copyright holder, specifically WJQserver Studio in this license.
* Contributor: Refers to the Licensor and individuals or entities who contribute code or software under this License.
* You: Refers to the individual or legal entity exercising permissions granted by this License.
* Derivative Works: Refers to works modified based on the Software or any part thereof, regardless of the extent of modification. This includes but is not limited to modifications, revisions, adaptations, translations, or other forms of creation based on the Software or any part thereof, as well as collective works containing the Software or parts thereof.
* Non-profit Use: Refers to uses not primarily intended for direct commercial profit, including but not limited to:
* Personal Use: Use by an individual for personal learning, research, experimentation, non-commercial projects, personal website development, graduation projects, home entertainment, and other non-directly commercial purposes.
* Educational Use: Use within educational institutions (such as schools, universities, training organizations) for activities such as teaching, research, and academic exchange.
* Scientific Research Use: Use within scientific research institutions, laboratories, and similar organizations for activities such as scientific research and experimental development.
* Charitable and Public Welfare Use: Use by charitable organizations, public welfare organizations, and similar non-profit entities for their public missions or internal operation of charitable activities, or to provide public services that do not directly generate commercial profit.
* Internal Operational Use (Non-profit Organizations): Use within the internal operations of non-profit organizations, such as for administrative management, membership management, internal communication, project management, and other non-directly profit-generating activities.
Open Source and Free Software
This project is open-source software, allowing users to access and use the source code under the premise of complying with this License.
This project aims to provide users with the broadest possible freedom for non-commercial use while ensuring the common development and healthy ecosystem of the community, and providing a clear path for commercial innovation.
Copyright is emphasized; all rights are jointly reserved by WJQserver Studio and Contributors.
License Terms
1. Permissions for Use
* 1.1 Non-profit Use: You are granted permission to freely use the Software for any purpose in non-profit use scenarios. Specific non-profit use scenarios include but are not limited to the various situations listed in the Definition section.
* 1.2 Commercial Use: You may use the Software in a commercial environment without additional authorization, but your commercial use must comply with the following terms:
* 1.2.1 Open Source Inheritance (Copyleft) and Reciprocal Sharing: If you or your organization wish to use the Software or its Derivative Works for any commercial purpose, including but not limited to:
* Profit-generating Distribution: Selling, renting, licensing, or distributing the Software or its Derivative Works.
* Profit-generating Services: Providing commercial services based on the Software or its Derivative Works, such as SaaS services, consulting services, custom development services, and paid technical support services.
* Embedded Commercial Applications: Embedding the Software or its Derivative Works into commercial products or solutions for sale.
* Internal Commercial Operations: Using modified versions within the internal operations of for-profit organizations to directly support their commercial activities, such as customized internal systems, generating commercial revenue directly or indirectly through means including but not limited to placing advertisements in the software or related services (e.g., Google Ads), in-app purchases, membership subscriptions, and charging for value-added features.
You must choose one of the following two options:
* i) Inherit this License and Open Source: You must distribute your Derivative Works under this License or a compatible open-source license and publicly disclose the entire source code of your Derivative Works, so that recipients of your Derivative Works also enjoy the same rights as you, including the right to further modify and use commercially. This option aims to promote the common development and knowledge sharing of the community, ensuring that commercial innovation achievements based on this Software can also contribute back to the community.
* ii) Obtain Explicit Authorization from the Licensor: If you do not wish to release your Derivative Works in an open-source manner, or wish to distribute them under another license, or you wish to use a modified version in commercial operations without open-sourcing it, you must obtain explicit written authorization from WJQserver Studio in advance. The specific terms and conditions of authorization will be determined separately by WJQserver Studio through negotiation.
* 1.3 Maintain Statements: When publish services to public, you must not remove or modify the original copyright notices, license notices, and source statements contained in the Software.
2. Reproduction and Distribution
* 2.1 Reproduction and Distribution of Original Version: You may reproduce and distribute the original version of the Software, provided that the following conditions are met:
* Retain All Statements: Completely retain all original copyright notices, license notices, source statements, and other proprietary notices.
* Accompany with License: When distributing the Software, you must also include the full text of this License to ensure that recipients are aware of and understand all terms of this License.
* 2.2 Reproduction and Distribution of Derivative Works: You may reproduce and distribute Derivative Works based on the Software. Your distribution of Derivative Works will be subject to the constraints of Clause 1.3 of this License (Open Source Inheritance and Reciprocal Sharing).
3. Modification Permissions
* 3.1 Free Modification: You are granted permission to freely modify the Software, regardless of whether the purpose of modification is for non-profit use or commercial use.
* 3.2 Constraints on Use and Distribution after Modification: When you use a modified version for commercial purposes or distribute a modified version, you need to comply with the provisions of Clause 1.3 of this License (Open Source Inheritance and Reciprocal Sharing) and Clause 2 (Reproduction and Distribution). Even if you do not distribute the modified version, as long as you use it for commercial purposes, you also need to comply with the open-source inheritance clause or obtain authorization.
* 3.3 Contribution Acceptance: WJQserver Studio encourages community contribution of code. If you contribute code to this project, you need to agree that your contributed code is licensed under the terms of this License.
4. Patent Rights
* 4.1 No Patent Warranty, Risk Self-Bearing: The software is provided “AS IS”, and the Licensor and Contributors explicitly declare that they do not provide any form of warranty regarding patent infringement issues of this software, nor do they assume any responsibility and consequences arising from patent infringement. Users understand and agree that the patent risk of using this software is entirely borne by the users themselves.
* 4.2 Handling of Patent Disputes: If any patent infringement allegations, lawsuits, or claims arise due to the user's use of this Software, the user shall be solely responsible for handling and bear all legal liabilities. The Licensor and Contributors are under no obligation to participate in any related legal proceedings, nor do they bear any costs or compensation arising therefrom.
5. Disclaimer of Warranty
* 5.1 “AS IS” Provision, No Warranty: The software is provided “AS IS” without any express or implied warranties, including but not limited to warranties of merchantability, fitness for a particular purpose, and non-infringement.
* 5.2 Limitation of Liability: To the maximum extent permitted by applicable law, in no event shall the Licensor or any Contributor be liable for any direct, indirect, incidental, special, punitive, or consequential damages (including but not limited to procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
* 5.3 User Legal Responsibility: Users shall treat this project in accordance with local laws and regulations to ensure compliance with all applicable laws and regulations.
6. License Term and Termination
* 6.1 License Term: Unless the copyright holder proactively announces the abandonment of the copyright of this software, this License shall be effective indefinitely from the date of your acceptance.
* 6.2 License Termination: If you fail to comply with any terms or conditions of this License, the Licensor has the right to terminate this License. Your License will automatically terminate upon your violation of the terms of this License.
* 6.3 Effect after Termination: Upon termination of the License, all rights granted to you under this License will terminate immediately, but the licenses and rights obtained by recipients of software copies you have legally distributed before the termination of the License will not be affected and will remain valid. The Disclaimer of Warranty (Clause 5) and Limitation of Liability (Clause 5.2) shall remain in effect after the termination of this License.
7. Revision of Terms
* 7.1 Reservation of Revision Rights: The Licensor reserves the right to modify the terms of this License at any time to better adapt to legal, technological developments, and community needs.
* 7.2 Effectiveness and Acceptance of Revisions: Revised terms will take effect upon publication, and unless otherwise stated, continued use, reproduction, distribution, or modification of the Software indicates your acceptance of the revised terms. The Licensor encourages users to periodically review the latest version of this License.
8. Other
* 8.1 Statutory Rights: This License does not affect your statutory rights as an end-user under applicable laws.
* 8.2 Severability of Terms: If certain terms of this License are deemed unenforceable, the remaining terms shall remain in full force and effect.
* 8.3 Version Updates: The Licensor may publish revised versions or new versions of this License. You may choose to continue using the old version of this License or choose to apply the new version.
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View file

@ -171,9 +171,7 @@ func min(a, b int) int {
### 内置
Recovery `r.Use(Recovery())`
Touka Gzip `r.Use(Gzip(-1))`
Recovery `r.Use(touka.Recovery())`
### fenthope
@ -206,7 +204,7 @@ Touka Gzip `r.Use(Gzip(-1))`
## 许可证
本项目在v0阶段使用WJQSERVER STUDIO LICENSE许可证, 后续进行调整
本项目使用MPL许可证
tree部分来自[gin](https://github.com/gin-gonic/gin)与[httprouter](https://github.com/julienschmidt/httprouter)

View file

@ -2,6 +2,7 @@ package touka
import (
"context"
"encoding/gob"
"errors"
"fmt"
"html/template"
@ -44,8 +45,8 @@ type Context struct {
// 携带ctx以实现关闭逻辑
ctx context.Context
// HTTPClient 用于在此上下文中执行出站 HTTP 请求
// 它由 Engine 提供
// HTTPClient 用于在此上下文中执行出站 HTTP 请求
// 它由 Engine 提供
HTTPClient *httpc.Client
// 引用所属的 Engine 实例,方便访问 Engine 的配置(如 HTMLRender
@ -56,8 +57,8 @@ type Context struct {
// --- Context 相关方法实现 ---
// reset 重置 Context 对象以供复用
// 每次从 sync.Pool 中获取 Context 后,都需要调用此方法进行初始化
// reset 重置 Context 对象以供复用
// 每次从 sync.Pool 中获取 Context 后,都需要调用此方法进行初始化
func (c *Context) reset(w http.ResponseWriter, req *http.Request) {
if rw, ok := c.Writer.(*responseWriterImpl); ok && !rw.IsHijacked() {
@ -80,8 +81,8 @@ func (c *Context) reset(w http.ResponseWriter, req *http.Request) {
// c.HTTPClient 和 c.engine 保持不变,它们引用 Engine 实例的成员
}
// Next 在处理链中执行下一个处理函数
// 这是中间件模式的核心,允许请求依次经过多个处理函数
// Next 在处理链中执行下一个处理函数
// 这是中间件模式的核心,允许请求依次经过多个处理函数
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
@ -90,25 +91,25 @@ func (c *Context) Next() {
}
}
// Abort 停止处理链的后续执行
// 通常在中间件中,当遇到错误或需要提前终止请求时调用
// Abort 停止处理链的后续执行
// 通常在中间件中,当遇到错误或需要提前终止请求时调用
func (c *Context) Abort() {
c.index = abortIndex // 将 index 设置为一个很大的值,使后续 Next() 调用跳过所有处理函数
}
// IsAborted 返回处理链是否已被中止
// IsAborted 返回处理链是否已被中止
func (c *Context) IsAborted() bool {
return c.index >= abortIndex
}
// AbortWithStatus 中止处理链并设置 HTTP 状态码
// AbortWithStatus 中止处理链并设置 HTTP 状态码
func (c *Context) AbortWithStatus(code int) {
c.Writer.WriteHeader(code) // 设置响应状态码
c.Abort() // 中止处理链
}
// Set 将一个键值对存储到 Context 中
// 这是一个线程安全的操作,用于在中间件之间传递数据
// Set 将一个键值对存储到 Context 中
// 这是一个线程安全的操作,用于在中间件之间传递数据
func (c *Context) Set(key string, value interface{}) {
c.mu.Lock() // 加写锁
if c.Keys == nil {
@ -118,8 +119,8 @@ func (c *Context) Set(key string, value interface{}) {
c.mu.Unlock() // 解写锁
}
// Get 从 Context 中获取一个值
// 这是一个线程安全的操作
// Get 从 Context 中获取一个值
// 这是一个线程安全的操作
func (c *Context) Get(key string) (value interface{}, exists bool) {
c.mu.RLock() // 加读锁
value, exists = c.Keys[key]
@ -127,8 +128,8 @@ func (c *Context) Get(key string) (value interface{}, exists bool) {
return
}
// MustGet 从 Context 中获取一个值,如果不存在则 panic
// 适用于确定值一定存在的场景
// MustGet 从 Context 中获取一个值,如果不存在则 panic
// 适用于确定值一定存在的场景
func (c *Context) MustGet(key string) interface{} {
if value, exists := c.Get(key); exists {
return value
@ -136,8 +137,8 @@ func (c *Context) MustGet(key string) interface{} {
panic("Key \"" + key + "\" does not exist in context.")
}
// Query 从 URL 查询参数中获取值
// 懒加载解析查询参数,并进行缓存
// Query 从 URL 查询参数中获取值
// 懒加载解析查询参数,并进行缓存
func (c *Context) Query(key string) string {
if c.queryCache == nil {
c.queryCache = c.Request.URL.Query() // 首次访问时解析并缓存
@ -145,7 +146,7 @@ func (c *Context) Query(key string) string {
return c.queryCache.Get(key)
}
// DefaultQuery 从 URL 查询参数中获取值,如果不存在则返回默认值
// DefaultQuery 从 URL 查询参数中获取值,如果不存在则返回默认值
func (c *Context) DefaultQuery(key, defaultValue string) string {
if value := c.Query(key); value != "" {
return value
@ -153,8 +154,8 @@ func (c *Context) DefaultQuery(key, defaultValue string) string {
return defaultValue
}
// PostForm 从 POST 请求体中获取表单值
// 懒加载解析表单数据,并进行缓存
// PostForm 从 POST 请求体中获取表单值
// 懒加载解析表单数据,并进行缓存
func (c *Context) PostForm(key string) string {
if c.formCache == nil {
c.Request.ParseMultipartForm(defaultMemory) // 解析 multipart/form-data 或 application/x-www-form-urlencoded
@ -163,7 +164,7 @@ func (c *Context) PostForm(key string) string {
return c.formCache.Get(key)
}
// DefaultPostForm 从 POST 请求体中获取表单值,如果不存在则返回默认值
// DefaultPostForm 从 POST 请求体中获取表单值,如果不存在则返回默认值
func (c *Context) DefaultPostForm(key, defaultValue string) string {
if value := c.PostForm(key); value != "" {
return value
@ -171,8 +172,8 @@ func (c *Context) DefaultPostForm(key, defaultValue string) string {
return defaultValue
}
// Param 从 URL 路径参数中获取值
// 例如,对于路由 /users/:idc.Param("id") 可以获取 id 的值
// Param 从 URL 路径参数中获取值
// 例如,对于路由 /users/:idc.Param("id") 可以获取 id 的值
func (c *Context) Param(key string) string {
return c.Params.ByName(key)
}
@ -184,31 +185,47 @@ func (c *Context) Raw(code int, contentType string, data []byte) {
c.Writer.Write(data)
}
// String 向响应写入格式化的字符串
// String 向响应写入格式化的字符串
func (c *Context) String(code int, format string, values ...interface{}) {
c.Writer.WriteHeader(code)
c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}
// JSON 向响应写入 JSON 数据
// 设置 Content-Type 为 application/json
// JSON 向响应写入 JSON 数据
// 设置 Content-Type 为 application/json
func (c *Context) JSON(code int, obj interface{}) {
c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
c.Writer.WriteHeader(code)
// 实际 JSON 编码
// JSON 编码
jsonBytes, err := json.Marshal(obj)
if err != nil {
c.AddError(fmt.Errorf("failed to marshal JSON: %w", err))
c.String(http.StatusInternalServerError, "Internal Server Error: Failed to marshal JSON")
//c.String(http.StatusInternalServerError, "Internal Server Error: Failed to marshal JSON")
c.ErrorUseHandle(http.StatusInternalServerError, fmt.Errorf("failed to marshal JSON: %w", err))
return
}
c.Writer.Write(jsonBytes)
}
// HTML 渲染 HTML 模板。
// 如果 Engine 配置了 HTMLRender则使用它进行渲染。
// 否则,会进行简单的字符串输出。
// 预留接口,可以扩展为支持多种模板引擎。
// GOB 向响应写入GOB数据
// 设置 Content-Type 为 application/octet-stream
func (c *Context) GOB(code int, obj interface{}) {
c.Writer.Header().Set("Content-Type", "application/octet-stream") // 设置合适的 Content-Type
c.Writer.WriteHeader(code)
// GOB 编码
encoder := gob.NewEncoder(c.Writer)
if err := encoder.Encode(obj); err != nil {
c.AddError(fmt.Errorf("failed to encode GOB: %w", err))
//c.String(http.StatusInternalServerError, "Internal Server Error: Failed to encode GOB")
c.ErrorUseHandle(http.StatusInternalServerError, fmt.Errorf("failed to encode GOB: %w", err))
return
}
}
// HTML 渲染 HTML 模板
// 如果 Engine 配置了 HTMLRender则使用它进行渲染
// 否则,会进行简单的字符串输出
// 预留接口,可以扩展为支持多种模板引擎
func (c *Context) HTML(code int, name string, obj interface{}) {
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
c.Writer.WriteHeader(code)
@ -219,7 +236,8 @@ func (c *Context) HTML(code int, name string, obj interface{}) {
err := tpl.ExecuteTemplate(c.Writer, name, obj)
if err != nil {
c.AddError(fmt.Errorf("failed to render HTML template '%s': %w", name, err))
c.String(http.StatusInternalServerError, "Internal Server Error: Failed to render HTML template")
//c.String(http.StatusInternalServerError, "Internal Server Error: Failed to render HTML template")
c.ErrorUseHandle(http.StatusInternalServerError, fmt.Errorf("failed to render HTML template '%s': %w", name, err))
}
return
}
@ -229,8 +247,8 @@ func (c *Context) HTML(code int, name string, obj interface{}) {
c.Writer.Write([]byte(fmt.Sprintf("<!-- HTML rendered for %s -->\n<pre>%v</pre>", name, obj)))
}
// Redirect 执行 HTTP 重定向
// code 应为 3xx 状态码 (如 http.StatusMovedPermanently, http.StatusFound)
// Redirect 执行 HTTP 重定向
// code 应为 3xx 状态码 (如 http.StatusMovedPermanently, http.StatusFound)
func (c *Context) Redirect(code int, location string) {
http.Redirect(c.Writer, c.Request, location, code)
c.Abort()
@ -239,7 +257,7 @@ func (c *Context) Redirect(code int, location string) {
}
}
// ShouldBindJSON 尝试将请求体绑定到 JSON 对象
// ShouldBindJSON 尝试将请求体绑定到 JSON 对象
func (c *Context) ShouldBindJSON(obj interface{}) error {
if c.Request.Body == nil {
return errors.New("request body is empty")
@ -257,9 +275,9 @@ func (c *Context) ShouldBindJSON(obj interface{}) error {
return nil
}
// ShouldBind 尝试将请求体绑定到各种类型JSON, Form, XML 等)
// 这是一个复杂的通用绑定接口,通常根据 Content-Type 或其他头部来判断绑定方式
// 预留接口,可根据项目需求进行扩展
// ShouldBind 尝试将请求体绑定到各种类型JSON, Form, XML 等)
// 这是一个复杂的通用绑定接口,通常根据 Content-Type 或其他头部来判断绑定方式
// 预留接口,可根据项目需求进行扩展
func (c *Context) ShouldBind(obj interface{}) error {
// TODO: 完整的通用绑定逻辑
// 可以根据 c.Request.Header.Get("Content-Type") 来判断是 JSON, Form, XML 等
@ -274,45 +292,45 @@ func (c *Context) ShouldBind(obj interface{}) error {
return errors.New("generic binding not fully implemented yet, implement based on Content-Type")
}
// AddError 添加一个错误到 Context
// 允许在处理请求过程中收集多个错误
// AddError 添加一个错误到 Context
// 允许在处理请求过程中收集多个错误
func (c *Context) AddError(err error) {
c.Errors = append(c.Errors, err)
}
// Errors 返回 Context 中收集的所有错误
// Errors 返回 Context 中收集的所有错误
func (c *Context) GetErrors() []error {
return c.Errors
}
// Client 返回 Engine 提供的 HTTPClient
// 方便在请求处理函数中进行出站 HTTP 请求
// Client 返回 Engine 提供的 HTTPClient
// 方便在请求处理函数中进行出站 HTTP 请求
func (c *Context) Client() *httpc.Client {
return c.HTTPClient
}
// Context() 返回请求的上下文,用于取消操作
// 这是 Go 标准库的 `context.Context`,用于请求的取消和超时管理
// Context() 返回请求的上下文,用于取消操作
// 这是 Go 标准库的 `context.Context`,用于请求的取消和超时管理
func (c *Context) Context() context.Context {
return c.ctx
}
// Done returns a channel that is closed when the request context is cancelled or times out.
// 继承自 `context.Context`
// 继承自 `context.Context`
func (c *Context) Done() <-chan struct{} {
return c.ctx.Done()
}
// Err returns the error, if any, that caused the context to be canceled or to
// time out.
// 继承自 `context.Context`
// 继承自 `context.Context`
func (c *Context) Err() error {
return c.ctx.Err()
}
// Value returns the value associated with this context for key, or nil if no
// value is associated with key.
// 可以用于从 Context 中获取与特定键关联的值,包括 Go 原生 Context 的值和 Touka Context 的 Keys
// 可以用于从 Context 中获取与特定键关联的值,包括 Go 原生 Context 的值和 Touka Context 的 Keys
func (c *Context) Value(key interface{}) interface{} {
if keyAsString, ok := key.(string); ok {
if val, exists := c.Get(keyAsString); exists {
@ -322,18 +340,18 @@ func (c *Context) Value(key interface{}) interface{} {
return c.ctx.Value(key) // 尝试从 Go 原生 Context 中获取值
}
// GetWriter 获得一个 io.Writer 接口,可以直接向响应体写入数据
// 这对于需要自定义流式写入或与其他需要 io.Writer 的库集成非常有用
// GetWriter 获得一个 io.Writer 接口,可以直接向响应体写入数据
// 这对于需要自定义流式写入或与其他需要 io.Writer 的库集成非常有用
func (c *Context) GetWriter() io.Writer {
return c.Writer // ResponseWriter 接口嵌入了 http.ResponseWriter而 http.ResponseWriter 实现了 io.Writer
}
// WriteStream 接受一个 io.Reader 并将其内容流式传输到响应体
// 返回写入的字节数和可能遇到的错误
// 该方法在开始写入之前,会确保设置 HTTP 状态码为 200 OK
// WriteStream 接受一个 io.Reader 并将其内容流式传输到响应体
// 返回写入的字节数和可能遇到的错误
// 该方法在开始写入之前,会确保设置 HTTP 状态码为 200 OK
func (c *Context) WriteStream(reader io.Reader) (written int64, err error) {
// 确保在写入数据前设置状态码
// WriteHeader 会在第一次写入时被 Write 方法隐式调用,但显式调用可以确保状态码的预期
// 确保在写入数据前设置状态码
// WriteHeader 会在第一次写入时被 Write 方法隐式调用,但显式调用可以确保状态码的预期
if !c.Writer.Written() {
c.Writer.WriteHeader(http.StatusOK) // 默认 200 OK
}
@ -346,14 +364,14 @@ func (c *Context) WriteStream(reader io.Reader) (written int64, err error) {
}
// GetReqBody 以获取一个 io.ReadCloser 接口,用于读取请求体
// 注意:请求体只能读取一次
// 注意:请求体只能读取一次
func (c *Context) GetReqBody() io.ReadCloser {
return c.Request.Body
}
// GetReqBodyFull
// GetReqBodyFull 读取并返回请求体的所有内容
// 注意:请求体只能读取一次
// GetReqBodyFull 读取并返回请求体的所有内容
// 注意:请求体只能读取一次
func (c *Context) GetReqBodyFull() ([]byte, error) {
if c.Request.Body == nil {
return nil, nil
@ -367,9 +385,9 @@ func (c *Context) GetReqBodyFull() ([]byte, error) {
return data, nil
}
// RequestIP 返回客户端的 IP 地址
// RequestIP 返回客户端的 IP 地址
// 它会根据 Engine 的配置 (ForwardByClientIP) 尝试从 X-Forwarded-For 或 X-Real-IP 等头部获取,
// 否则回退到 Request.RemoteAddr
// 否则回退到 Request.RemoteAddr
func (c *Context) RequestIP() string {
if c.engine.ForwardByClientIP {
for _, headerName := range c.engine.RemoteIPHeaders {
@ -409,63 +427,68 @@ func (c *Context) RequestIP() string {
return ""
}
// ClientIP 返回客户端的 IP 地址
// 这是一个别名,与 RequestIP 功能相同
// ClientIP 返回客户端的 IP 地址
// 这是一个别名,与 RequestIP 功能相同
func (c *Context) ClientIP() string {
return c.RequestIP()
}
// ContentType 返回请求的 Content-Type 头部
// ContentType 返回请求的 Content-Type 头部
func (c *Context) ContentType() string {
return c.GetReqHeader("Content-Type")
}
// UserAgent 返回请求的 User-Agent 头部
// UserAgent 返回请求的 User-Agent 头部
func (c *Context) UserAgent() string {
return c.GetReqHeader("User-Agent")
}
// Status 设置响应状态码
// Status 设置响应状态码
func (c *Context) Status(code int) {
c.Writer.WriteHeader(code)
}
// File 将指定路径的文件作为响应发送
// 它会设置 Content-Type 和 Content-Disposition 头部
// File 将指定路径的文件作为响应发送
// 它会设置 Content-Type 和 Content-Disposition 头部
func (c *Context) File(filepath string) {
http.ServeFile(c.Writer, c.Request, filepath)
c.Abort() // 发送文件后中止后续处理
}
// SetHeader 设置响应头部
// SetHeader 设置响应头部
func (c *Context) SetHeader(key, value string) {
c.Writer.Header().Set(key, value)
}
// AddHeader 添加响应头部
// AddHeader 添加响应头部
func (c *Context) AddHeader(key, value string) {
c.Writer.Header().Add(key, value)
}
// DelHeader 删除响应头部。
// Header 作为SetHeader的别名
func (c *Context) Header(key, value string) {
c.SetHeader(key, value)
}
// DelHeader 删除响应头部
func (c *Context) DelHeader(key string) {
c.Writer.Header().Del(key)
}
// GetReqHeader 获取请求头部的值。
// GetReqHeader 获取请求头部的值
func (c *Context) GetReqHeader(key string) string {
return c.Request.Header.Get(key)
}
// GetAllReqHeader 获取所有请求头部
// GetAllReqHeader 获取所有请求头部
func (c *Context) GetAllReqHeader() http.Header {
return c.Request.Header
}
// 使用定义的errorHandle来处理error并结束当前handle
func (c *Context) ErrorUseHandle(code int) {
func (c *Context) ErrorUseHandle(code int, err error) {
if c.engine != nil && c.engine.errorHandle.handler != nil {
c.engine.errorHandle.handler(c, code)
c.engine.errorHandle.handler(c, code, err)
c.Abort()
return
} else {
@ -489,12 +512,12 @@ func (c *Context) GetLogger() *reco.Logger {
return c.engine.LogReco
}
// SetSameSite 设置响应的 SameSite cookie 属性
// SetSameSite 设置响应的 SameSite cookie 属性
func (c *Context) SetSameSite(samesite http.SameSite) {
c.sameSite = samesite
}
// SetCookie 设置一个 HTTP cookie
// SetCookie 设置一个 HTTP cookie
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
if path == "" {
path = "/"
@ -521,7 +544,7 @@ func (c *Context) SetCookieData(cookie *http.Cookie) {
http.SetCookie(c.Writer, cookie)
}
// GetCookie 获取指定名称的 cookie 值
// GetCookie 获取指定名称的 cookie 值
func (c *Context) GetCookie(name string) (string, error) {
cookie, err := c.Request.Cookie(name)
if err != nil {
@ -535,8 +558,8 @@ func (c *Context) GetCookie(name string) (string, error) {
return value, nil
}
// DeleteCookie 删除指定名称的 cookie
// 通过设置 MaxAge 为 -1 来删除 cookie
// DeleteCookie 删除指定名称的 cookie
// 通过设置 MaxAge 为 -1 来删除 cookie
func (c *Context) DeleteCookie(name string) {
c.SetCookie(name, "", -1, "/", "", false, false) // 设置 MaxAge 为 -1 删除 cookie
}

2
ecw.go
View file

@ -136,7 +136,7 @@ func (ecw *errorCapturingResponseWriter) processAfterFileServer() {
ecw.ctx.Next()
} else {
// 调用用户自定义的 ErrorHandlerFunc, 由它负责完整的错误响应
ecw.errorHandlerFunc(ecw.ctx, ecw.Status())
ecw.errorHandlerFunc(ecw.ctx, ecw.Status(), errors.New("file server error"))
ecw.ctx.Abort()
}
}

View file

@ -2,6 +2,7 @@ package touka
import (
"context"
"errors"
"log"
"reflect"
"runtime"
@ -70,10 +71,10 @@ type ErrorHandle struct {
handler ErrorHandler
}
type ErrorHandler func(c *Context, code int)
type ErrorHandler func(c *Context, code int, err error)
// defaultErrorHandle 默认错误处理
func defaultErrorHandle(c *Context, code int) { // 检查客户端是否已断开连接
func defaultErrorHandle(c *Context, code int, err error) { // 检查客户端是否已断开连接
select {
case <-c.Request.Context().Done():
@ -86,6 +87,7 @@ func defaultErrorHandle(c *Context, code int) { // 检查客户端是否已断
c.JSON(code, H{
"code": code,
"message": http.StatusText(code),
"error": err,
})
c.Writer.Flush()
c.Abort()
@ -95,17 +97,17 @@ func defaultErrorHandle(c *Context, code int) { // 检查客户端是否已断
// 默认errorhandle包装 避免竞争意外问题, 保证稳定性
func defaultErrorWarp(handler ErrorHandler) ErrorHandler {
return func(c *Context, code int) {
return func(c *Context, code int, err error) {
select {
case <-c.Request.Context().Done():
return
default:
if c.Writer.Written() {
log.Printf("errpage: response already started for status %d, skipping error page rendering", code)
log.Printf("errpage: response already started for status %d, skipping error page rendering, err: %v", code, err)
return
}
}
handler(c, code)
handler(c, code, err)
}
}
@ -431,7 +433,7 @@ func unMatchFSHandle() HandlerFunc {
c.Abort()
return
} else {
engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
return
}
} else {
@ -476,7 +478,7 @@ func MethodNotAllowed() HandlerFunc {
value := treeIter.root.getValue(requestPath, nil, &tempSkippedNodes, false) // 只查找是否存在,不需要参数
if value.handlers != nil {
// 使用定义的ErrorHandle处理
engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
return
}
}
@ -487,7 +489,7 @@ func MethodNotAllowed() HandlerFunc {
func NotFound() HandlerFunc {
return func(c *Context) {
engine := c.engine
engine.errorHandle.handler(c, http.StatusNotFound)
engine.errorHandle.handler(c, http.StatusNotFound, errors.New("not found"))
return
}
}
@ -682,7 +684,7 @@ func (engine *Engine) Static(relativePath, rootPath string) {
c.Next()
} else {
// 否则,返回 405 Method Not Allowed
engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
}
return
}
@ -746,7 +748,7 @@ func (group *RouterGroup) Static(relativePath, rootPath string) {
c.Next()
} else {
// 否则,返回 405 Method Not Allowed
group.engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
group.engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
}
return
}
@ -805,7 +807,7 @@ func (engine *Engine) StaticFile(relativePath, filePath string) {
c.Next()
} else {
// 否则,返回 405 Method Not Allowed
engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
}
return
}
@ -861,7 +863,7 @@ func (group *RouterGroup) StaticFile(relativePath, filePath string) {
c.Next()
} else {
// 否则,返回 405 Method Not Allowed
group.engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
group.engine.errorHandle.handler(c, http.StatusMethodNotAllowed, errors.New("method not allowed"))
}
return
}

2
go.mod
View file

@ -4,7 +4,7 @@ go 1.24.4
require (
github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4
github.com/WJQSERVER-STUDIO/httpc v0.6.0
github.com/WJQSERVER-STUDIO/httpc v0.7.0
github.com/fenthope/reco v0.0.1
github.com/go-json-experiment/json v0.0.0-20250517221953-25912455fbc8
)

4
go.sum
View file

@ -1,7 +1,7 @@
github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4 h1:JLtFd00AdFg/TP+dtvIzLkdHwKUGPOAijN1sMtEYoFg=
github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4/go.mod h1:FZ6XE+4TKy4MOfX1xWKe6Rwsg0ucYFCdNh1KLvyKTfc=
github.com/WJQSERVER-STUDIO/httpc v0.6.0 h1:GXKc6BNGn5ALdDLkCsM+mP24jyi1S9QBLL2XQ1a2sPM=
github.com/WJQSERVER-STUDIO/httpc v0.6.0/go.mod h1:M7KNUZjjhCkzzcg9lBPs9YfkImI+7vqjAyjdA19+joE=
github.com/WJQSERVER-STUDIO/httpc v0.7.0 h1:iHhqlxppJBjlmvsIjvLZKRbWXqSdbeSGGofjHGmqGJc=
github.com/WJQSERVER-STUDIO/httpc v0.7.0/go.mod h1:M7KNUZjjhCkzzcg9lBPs9YfkImI+7vqjAyjdA19+joE=
github.com/fenthope/reco v0.0.1 h1:GYcuXCEKYoctD0dFkiBC+t0RMTOyOiujBCin8bbLR3Y=
github.com/fenthope/reco v0.0.1/go.mod h1:mDkGLHte5udWTIcjQTxrABRcf56SSdxBOCLgrRDwI/Y=
github.com/go-json-experiment/json v0.0.0-20250517221953-25912455fbc8 h1:o8UqXPI6SVwQt04RGsqKp3qqmbOfTNMqDrWsc4O47kk=

View file

@ -91,7 +91,7 @@ func defaultPanicHandler(c *Context, r interface{}) {
// 尝试发送 500 Internal Server Error 响应
// 使用框架提供的统一错误处理器(如果可用)
if c.engine != nil && c.engine.errorHandle.handler != nil {
c.engine.errorHandle.handler(c, http.StatusInternalServerError)
c.engine.errorHandle.handler(c, http.StatusInternalServerError, errors.New("Internal Panic Error"))
} else {
// 如果框架错误处理器不可用,提供一个备用的简单响应
// 返回英文错误信息

357
tgzip.go
View file

@ -1,357 +0,0 @@
package touka
import (
"bufio"
"compress/gzip"
"errors"
"io"
"net"
"net/http"
"strconv"
"strings"
"sync"
)
const (
headerAcceptEncoding = "Accept-Encoding" // 请求头部,客户端声明接受的编码
headerContentEncoding = "Content-Encoding" // 响应头部,服务器声明使用的编码
headerContentLength = "Content-Length" // 响应头部,内容长度
headerContentType = "Content-Type" // 响应头部,内容类型
headerVary = "Vary" // 响应头部,指示缓存行为
encodingGzip = "gzip" // Gzip 编码名称
)
var (
// 默认可压缩的 MIME 类型
defaultCompressibleTypes = []string{
"text/html", "text/css", "text/plain", "text/javascript",
"application/javascript", "application/x-javascript", "application/json",
"application/xml", "image/svg+xml",
}
)
// GzipOptions 用于配置 Gzip 中间件。
type GzipOptions struct {
// Level 设置 Gzip 压缩级别。
// 例如: gzip.DefaultCompression, gzip.BestSpeed, gzip.BestCompression。
Level int
// MinContentLength 是应用 Gzip 的最小内容长度。
// 如果响应的 Content-Length 小于此值,则不应用 Gzip。
// 默认为 0 (无最小长度限制)。
MinContentLength int64
// CompressibleTypes 是要压缩的 MIME 类型列表。
// 如果为空,将使用 defaultCompressibleTypes。
CompressibleTypes []string
// DecompressFn 是一个可选函数,用于解压缩请求体 (如果请求体是 gzipped)。
// 如果为 nil则禁用请求体解压缩。
// 注意: 本次实现主要关注响应压缩,请求解压可以作为扩展。
// DecompressFn func(c *Context)
}
// gzipResponseWriter 包装了 touka.ResponseWriter 以提供 Gzip 压缩功能。
type gzipResponseWriter struct {
ResponseWriter // 底层的 ResponseWriter (可能是 ecw 或 responseWriterImpl)
gzWriter *gzip.Writer // compress/gzip 的 writer
options *GzipOptions // Gzip 配置
wroteHeader bool // 标记 Header 是否已写入
doCompression bool // 标记是否执行压缩
statusCode int // 存储状态码,在实际写入底层 Writer 前使用
}
// --- 对象池 ---
var gzipResponseWriterPool = sync.Pool{
New: func() interface{} {
return &gzipResponseWriter{}
},
}
// gzip.Writer 实例的对象池。
// 注意: gzip.Writer.Reset() 不会改变压缩级别,所以对象池需要提供已正确初始化级别的 writer。
// 我们为每个可能的级别创建一个池。
var gzipWriterPools [gzip.BestCompression - gzip.BestSpeed + 2]*sync.Pool // 覆盖 -1 (Default) 到 9 (BestCompression)
func init() {
for i := gzip.BestSpeed; i <= gzip.BestCompression; i++ {
level := i // 捕获循环变量用于闭包
gzipWriterPools[level-gzip.BestSpeed] = &sync.Pool{
New: func() interface{} {
// 初始化时 writer 为 nil在 Reset 时设置
w, _ := gzip.NewWriterLevel(nil, level)
return w
},
}
}
// 为 gzip.DefaultCompression (-1) 映射一个索引
defaultLevelIndex := gzip.BestCompression - gzip.BestSpeed + 1
gzipWriterPools[defaultLevelIndex] = &sync.Pool{
New: func() interface{} {
w, _ := gzip.NewWriterLevel(nil, gzip.DefaultCompression)
return w
},
}
}
// 从对象池获取一个 gzip.Writer
func getGzipWriterFromPool(level int, underlyingWriter io.Writer) *gzip.Writer {
var poolIndex int
if level == gzip.DefaultCompression {
poolIndex = gzip.BestCompression - gzip.BestSpeed + 1
} else if level >= gzip.BestSpeed && level <= gzip.BestCompression {
poolIndex = level - gzip.BestSpeed
} else { // 无效级别,使用默认级别
poolIndex = gzip.BestCompression - gzip.BestSpeed + 1
level = gzip.DefaultCompression // 保证一致性
}
gz := gzipWriterPools[poolIndex].Get().(*gzip.Writer)
gz.Reset(underlyingWriter) // 重置并关联到底层的 io.Writer
return gz
}
// 将 gzip.Writer 返还给对象池
func putGzipWriterToPool(gz *gzip.Writer, level int) {
var poolIndex int
if level == gzip.DefaultCompression {
poolIndex = gzip.BestCompression - gzip.BestSpeed + 1
} else if level >= gzip.BestSpeed && level <= gzip.BestCompression {
poolIndex = level - gzip.BestSpeed
} else { // 不应该发生,如果 getGzipWriterFromPool 进行了标准化
poolIndex = gzip.BestCompression - gzip.BestSpeed + 1
}
gzipWriterPools[poolIndex].Put(gz)
}
// 从对象池获取一个 gzipResponseWriter
func acquireGzipResponseWriter(underlying ResponseWriter, opts *GzipOptions) *gzipResponseWriter {
gzw := gzipResponseWriterPool.Get().(*gzipResponseWriter)
gzw.ResponseWriter = underlying
gzw.options = opts
gzw.wroteHeader = false
gzw.doCompression = false
gzw.statusCode = 0 // 重置状态码
// gzWriter 将在 WriteHeader 中如果需要时获取
return gzw
}
// 将 gzipResponseWriter 返还给对象池
func releaseGzipResponseWriter(gzw *gzipResponseWriter) {
if gzw.gzWriter != nil {
// 确保它被关闭并返回到池中
_ = gzw.gzWriter.Close() // 关闭会 flush
putGzipWriterToPool(gzw.gzWriter, gzw.options.Level)
gzw.gzWriter = nil
}
gzw.ResponseWriter = nil // 断开引用
gzw.options = nil
gzipResponseWriterPool.Put(gzw)
}
// --- gzipResponseWriter 方法实现 ---
// Header 返回底层 ResponseWriter 的头部 map。
func (gzw *gzipResponseWriter) Header() http.Header {
return gzw.ResponseWriter.Header()
}
// WriteHeader 发送 HTTP 响应头部和指定的状态码。
// 在这里决定是否进行压缩。
func (gzw *gzipResponseWriter) WriteHeader(statusCode int) {
if gzw.wroteHeader {
return
}
gzw.wroteHeader = true
gzw.statusCode = statusCode // 存储状态码
// 在修改头部以进行压缩之前进行条件检查
// 1. 如果状态码是信息性(1xx)、重定向(3xx)、无内容(204)、重置内容(205)或未修改(304),则不压缩
if statusCode < http.StatusOK || statusCode == http.StatusNoContent || statusCode == http.StatusResetContent || statusCode == http.StatusNotModified {
gzw.ResponseWriter.WriteHeader(statusCode)
return
}
// 2. 如果响应已经被编码,则不压缩
if gzw.Header().Get(headerContentEncoding) != "" {
gzw.ResponseWriter.WriteHeader(statusCode)
return
}
// 3. 检查 Content-Type
contentType := strings.ToLower(strings.TrimSpace(strings.Split(gzw.Header().Get(headerContentType), ";")[0]))
compressibleTypes := gzw.options.CompressibleTypes
if len(compressibleTypes) == 0 {
compressibleTypes = defaultCompressibleTypes
}
isCompressible := false
for _, t := range compressibleTypes {
if strings.HasPrefix(contentType, t) { // 使用 HasPrefix 以匹配如 "text/html; charset=utf-8"
isCompressible = true
break
}
}
if !isCompressible {
gzw.ResponseWriter.WriteHeader(statusCode)
return
}
// 4. 检查 MinContentLength
if gzw.options.MinContentLength > 0 {
if clStr := gzw.Header().Get(headerContentLength); clStr != "" {
if cl, err := strconv.ParseInt(clStr, 10, 64); err == nil && cl < gzw.options.MinContentLength {
gzw.ResponseWriter.WriteHeader(statusCode)
return
}
}
// 如果未设置 Content-Length但设置了 MinContentLength我们可能仍会压缩。
// 这是一个权衡:可能会压缩小的动态内容。
}
// 所有检查通过,进行压缩
gzw.doCompression = true
gzw.Header().Set(headerContentEncoding, encodingGzip)
gzw.Header().Add(headerVary, headerAcceptEncoding) // 使用 Add 以避免覆盖其他 Vary 值
gzw.Header().Del(headerContentLength) // Gzip 会改变内容长度,所以删除它
// 从池中获取 gzWriter并将其 Reset 指向实际的底层 ResponseWriter
// 注意gzw.ResponseWriter 是被 Gzip 包装的 writer (例如,原始的 responseWriterImpl 或 ecw)
gzw.gzWriter = getGzipWriterFromPool(gzw.options.Level, gzw.ResponseWriter)
gzw.ResponseWriter.WriteHeader(statusCode) // 调用原始的 WriteHeader
}
// Write 将数据写入连接作为 HTTP 回复的一部分。
func (gzw *gzipResponseWriter) Write(data []byte) (int, error) {
if !gzw.wroteHeader {
// 如果在 WriteHeader 之前调用 Write根据 http.ResponseWriter 规范,
// 应写入 200 OK 头部。
gzw.WriteHeader(http.StatusOK)
}
if gzw.doCompression {
return gzw.gzWriter.Write(data)
}
return gzw.ResponseWriter.Write(data)
}
// Close 确保 gzip writer 被关闭并释放资源。
// 中间件应该在 c.Next() 之后调用它(通常在 defer 中)。
func (gzw *gzipResponseWriter) Close() error {
if gzw.gzWriter != nil {
err := gzw.gzWriter.Close() // Close 会 Flush
putGzipWriterToPool(gzw.gzWriter, gzw.options.Level)
gzw.gzWriter = nil // 标记为已返回
return err
}
return nil
}
// Flush 将所有缓冲数据发送到客户端。
// 实现 http.Flusher。
func (gzw *gzipResponseWriter) Flush() {
if gzw.doCompression && gzw.gzWriter != nil {
_ = gzw.gzWriter.Flush() // 确保 gzip writer 的缓冲被刷新
}
// 然后刷新底层的 writer (如果它支持)
if fl, ok := gzw.ResponseWriter.(http.Flusher); ok {
fl.Flush()
}
}
// Hijack 允许调用者接管连接。
// 实现 http.Hijacker。
func (gzw *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
// 如果正在压缩hijack 的意义不大或不安全。
// 然而WriteHeader 应该会阻止对 101 状态码的压缩。
// 此调用必须转到实际的底层 ResponseWriter。
if hj, ok := gzw.ResponseWriter.(http.Hijacker); ok {
return hj.Hijack()
}
// 返回英文错误
return nil, nil, errors.New("touka.gzipResponseWriter: underlying ResponseWriter does not implement http.Hijacker")
}
// Status 返回已写入的 HTTP 状态码。委托给底层的 ResponseWriter。
// 这确保了与 ecw 或其他可能跟踪状态的包装器的兼容性。
func (gzw *gzipResponseWriter) Status() int {
if gzw.statusCode != 0 { // 如果我们在 WriteHeader 期间存储了它
return gzw.statusCode
}
return gzw.ResponseWriter.Status() // 委托
}
// Size 返回已写入的字节数。委托给底层的 ResponseWriter。
// 如果已压缩,这将是压缩后的大小 (由底层 writer 记录)。
func (gzw *gzipResponseWriter) Size() int {
return gzw.ResponseWriter.Size() // GzipResponseWriter 本身不直接跟踪大小,依赖底层
}
// Written 返回 WriteHeader 是否已被调用。委托给底层的 ResponseWriter。
func (gzw *gzipResponseWriter) Written() bool {
// 如果 gzw.wroteHeader 为 true说明 WriteHeader 至少被 gzw 处理过。
// 但最终是否写入底层取决于 gzw 的逻辑。
// 更可靠的是询问底层 writer。
return gzw.ResponseWriter.Written() // 委托
}
// --- Gzip 中间件 ---
// Gzip 返回一个使用 Gzip 压缩 HTTP 响应的中间件。
// 它会检查客户端的 "Accept-Encoding" 头部和响应的 "Content-Type"
// 来决定是否应用压缩。
// level 参数指定压缩级别 (例如 gzip.DefaultCompression)。
// opts 参数是可选的 GzipOptions。
func Gzip(level int, opts ...GzipOptions) HandlerFunc {
config := GzipOptions{ // 初始化默认配置
Level: level,
MinContentLength: 0, // 默认:无最小长度
CompressibleTypes: defaultCompressibleTypes,
}
if len(opts) > 0 { // 如果传入了 GzipOptions则覆盖默认值
opt := opts[0]
config.Level = opt.Level // 允许通过结构体覆盖级别
if opt.MinContentLength > 0 {
config.MinContentLength = opt.MinContentLength
}
if len(opt.CompressibleTypes) > 0 {
config.CompressibleTypes = opt.CompressibleTypes
}
}
// 验证级别
if config.Level < gzip.DefaultCompression || config.Level > gzip.BestCompression {
config.Level = gzip.DefaultCompression
}
return func(c *Context) {
// 1. 检查客户端是否接受 gzip
if !strings.Contains(c.Request.Header.Get(headerAcceptEncoding), encodingGzip) {
c.Next()
return
}
// 2. 包装 ResponseWriter
originalWriter := c.Writer
gzw := acquireGzipResponseWriter(originalWriter, &config)
c.Writer = gzw // 替换上下文的 writer
// defer 确保即使后续处理函数发生 panic也能进行清理
// 尽管恢复中间件应该自己处理 panic 响应。
defer func() {
// 必须关闭 gzip writer 以刷新其缓冲区。
// 这也会将 gzip.Writer 返回到其对象池。
if err := gzw.Close(); err != nil {
// 记录关闭 gzip writer 时的错误,但不应覆盖已发送的响应
// 通常这个错误不严重,因为数据可能已经大部分发送
// 使用英文记录日志
// log.Printf("Error closing gzip writer: %v", err)
c.AddError(err) // 可以选择将错误添加到 Context 中
}
// 恢复原始 writer 并将 gzipResponseWriter 返回到其对象池
c.Writer = originalWriter
releaseGzipResponseWriter(gzw)
}()
// 3. 调用链中的下一个处理函数
c.Next()
// c.Next() 执行完毕后,响应头部应该已经设置。
// gzw.WriteHeader 会被显式调用或通过第一次 Write 隐式调用。
// 如果 gzw.doCompression 为 true响应体已写入 gzw.gzWriter。
// defer 中的 gzw.Close() 会刷新最终的压缩字节。
}
}