[
  {
    "path": ".codecov.yml",
    "content": "github_checks:\n  annotations: false\n\nignore:\n  - \"images/.*\"\n  - \"examples/.*\"\n  - \"cmd/.*\"\n\ncoverage:\n  status:\n    project:\n      default:\n        # pass if coverage drops by no more than 0.05%\n        # this is possibly caused by unstable coverage.\n        threshold: 0.05%\n    patch:\n      default:\n        target: 80%\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Handle line endings automatically for files detected as text\n# and leave all files detected as binary untouched.\n* text=auto eol=lf\n\n# Force bash scripts to always use LF line endings so that if a repo is accessed\n# in Unix via a file share from Windows, the scripts will work.\n*.sh text eol=lf\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# For more information, please refer to https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners\n\n*   @cloudwego/hertz-reviewers @cloudwego/hertz-approvers @cloudwego/hertz-maintainers\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\n\nA clear and concise description of what the bug is.\n\n**To Reproduce**\n\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\n\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\n\nIf applicable, add screenshots to help explain your problem.\n\n**Hertz version:**\n\nPlease provide the version of Hertz you are using.\n\n**Environment:**\n\nThe output of `go env`.\n\n**Additional context**\n\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\n\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\n\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\n\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\n\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: Question\nabout: Ask a question, so we can help you easily\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the Question**\n\nA clear and concise description of what the question is.\n\n**Reproducible Code**\n\nPlease construct a minimum complete and reproducible example for us to get the same error. And tell us how to reproduce it like how you send a request or send what request.\n\n**Expected behavior**\n\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\n\nIf applicable, add screenshots to help explain your question.\n\n**Hertz version:**\n\nPlease provide the version of Hertz you are using.\n\n**Environment:**\n\nThe output of `go env`.\n\n**Additional context**\n\nAdd any other context about the question here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "#### What type of PR is this?\n<!--\nAdd one of the following kinds:\n\nbuild: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)\nci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)\ndocs: Documentation only changes\nfeat: A new feature\noptimize: A new optimization\nfix: A bug fix\nperf: A code change that improves performance\nrefactor: A code change that neither fixes a bug nor adds a feature\nstyle: Changes that do not affect the meaning of the code (white space, formatting, missing semi-colons, etc)\ntest: Adding missing tests or correcting existing tests\nchore: Changes to the build process or auxiliary tools and libraries such as documentation generation\n-->\n\n#### Check the PR title.\n<!--\nThe description of the title will be attached in Release Notes, \nso please describe it from user-oriented, what this PR does / why we need it.\nPlease check your PR title with the below requirements:\n-->\n- [ ] This PR title match the format: \\<type\\>(optional scope): \\<description\\>\n- [ ] The description of this PR title is user-oriented and clear enough for others to understand.\n- [ ] Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. [User docs repo](https://github.com/cloudwego/cloudwego.github.io)\n\n\n#### (Optional) Translate the PR title into Chinese.\n\n\n#### (Optional) More detailed description for this PR(en: English/zh: Chinese).\n<!--\nProvide more detailed info for review(e.g., it's recommended to provide perf data if this is a perf type PR).\n-->\nen:\nzh(optional):\n\n#### (Optional) Which issue(s) this PR fixes:\n<!--\nAutomatically closes linked issue when PR is merged.\nEg: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.\n-->\n\n#### (Optional) The PR that updates user documentation:\n<!--\nIf the current PR requires user awareness at the usage level, please submit a PR to update user docs. [User docs repo](https://github.com/cloudwego/cloudwego.github.io)\n-->"
  },
  {
    "path": ".github/labels.json",
    "content": "{\n  \"labels\": {\n    \"invalid_issue\": {\n      \"name\": \"invalid issue\",\n      \"colour\": \"#CF2E1F\",\n      \"description\": \"invalid issue (not related to Hertz or described in document or not enough information provided)\"\n    }\n  },\n  \"issue\": {\n    \"invalid_issue\": {\n      \"requires\": 3,\n      \"conditions\": [\n        {\n          \"type\": \"descriptionMatches\",\n          \"pattern\": \"/^((?!Describe the bug).)*$/is\"\n        },\n        {\n          \"type\": \"descriptionMatches\",\n          \"pattern\": \"/^((?!Is your feature request related to a problem\\\\? Please describe).)*$/is\"\n        },\n        {\n          \"type\": \"descriptionMatches\",\n          \"pattern\": \"/^((?!Describe the Question).)*$/is\"\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": ".github/workflows/cmd-tests.yml",
    "content": "name: Cmd Tests\n\non:\n  push:\n    paths:\n      - 'cmd/**'\n  pull_request:\n    paths:\n      - 'cmd/**'\n\njobs:\n  hz-test-unix:\n    runs-on: [ self-hosted, Linux, X64 ]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: stable\n          cache: false # don't use cache for self-hosted runners\n\n      - name: Setup Environment\n        run: |\n          echo \"GOPATH=$(go env GOPATH)\" >> $GITHUB_ENV\n          echo \"$(go env GOPATH)/bin\" >> $GITHUB_PATH\n\n      - name: Hz Test\n        run: |\n          cd cmd/hz\n          sh test_hz_unix.sh\n\n\n  hz-test-windows:\n    runs-on: windows-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: stable\n\n      - name: Install Protobuf\n        shell: pwsh\n        run: |\n          Invoke-WebRequest https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win64.zip -OutFile protoc-3.19.4-win64.zip\n          Expand-Archive protoc-3.19.4-win64.zip -DestinationPath protoc-3.19.4-win64\n          $GOPATH=go env GOPATH\n          Copy-Item -Path protoc-3.19.4-win64\\bin\\protoc.exe -Destination $GOPATH\\bin\n          Copy-Item -Path protoc-3.19.4-win64\\include\\* -Destination cmd\\hz\\testdata\\include\\google -Recurse\n          protoc --version\n\n      - name: Hz Test\n        run: |\n          cd cmd/hz\n          sh test_hz_windows.sh\n"
  },
  {
    "path": ".github/workflows/invalid_question.yml",
    "content": "name: \"Close Invalid Issue\"\non:\n  schedule:\n    - cron: \"0 0,8,16 * * *\"\n\npermissions:\n  contents: read\n\njobs:\n  stale:\n    permissions:\n      issues: write\n    runs-on: ubuntu-latest\n    env:\n      ACTIONS_STEP_DEBUG: true\n    steps:\n      - name: Close Stale Issues\n        uses: actions/stale@v6\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          stale-issue-message: \"This issue has been marked as invalid question, please give more information by following the `issue` template. The issue will be closed in 1 days if no further activity occurs.\"\n          stale-issue-label: \"stale\"\n          days-before-stale: 0\n          days-before-close: 1\n          remove-stale-when-updated: true\n          only-labels: \"invalid issue\"\n"
  },
  {
    "path": ".github/workflows/labeler.yml",
    "content": "name: \"Labeler\"\non:\n  issues:\n    types: [ opened, edited, reopened ]\n\njobs:\n  triage:\n    if: contains(github.event.issue.labels.*.name, 'invalid issue') || join(github.event.issue.labels) == ''\n    runs-on: ubuntu-latest\n    name: Label issues\n    steps:\n      - name: check out\n        uses: actions/checkout@v3\n\n      - name: labeler\n        uses: jbinda/super-labeler-action@develop\n        with:\n          GITHUB_TOKEN: \"${{ secrets.GITHUB_TOKEN }}\"\n"
  },
  {
    "path": ".github/workflows/pr-check.yml",
    "content": "name: Pull Request Check\n\non: [ pull_request ]\n\njobs:\n  compliant:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Check License Header\n        uses: apache/skywalking-eyes/header@v0.4.0\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Check Spell\n        uses: crate-ci/typos@master\n\n  lint:\n    runs-on: [ self-hosted, Linux, X64 ]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: stable\n          cache: false # don't use cache for self-hosted runners\n\n      - name: Golangci Lint\n        # https://golangci-lint.run/\n        uses: golangci/golangci-lint-action@v8\n        with:\n          version: latest\n          only-new-issues: true\n"
  },
  {
    "path": ".github/workflows/unit-tests.yml",
    "content": "name: Unit Tests\n\non: [push, pull_request]\n\njobs:\n  unit-test-x64:\n    strategy:\n      matrix:\n        version: [\"1.19\", \"1.20\", oldstable, stable]\n    runs-on: [self-hosted, Linux, X64]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.version }}\n          cache: false # don't use cache for self-hosted runners\n\n      - name: Unit Test\n        run: go test -race ./...\n\n  unit-test-arm64:\n    strategy:\n      matrix:\n        version: [\"1.19\", \"1.20\", oldstable, stable]\n    runs-on: [self-hosted, ARM64]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.version }}\n          cache: false # don't use cache for self-hosted runners\n\n      - name: Unit Test\n        run: go test -race ./...\n\n\n  unit-test-no-netpoll:\n    runs-on: [self-hosted, Linux]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: stable\n          cache: false # don't use cache for self-hosted runners\n\n      - name: Unit Test\n        run: HERTZ_NO_NETPOLL=1 go test -race ./...\n\n  ut-windows:\n    runs-on: [self-hosted, Windows]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: stable\n          cache: false # don't use cache for self-hosted runners\n\n      - name: Unit Test\n        run: go test -race ./...\n\n  code-cov:\n    runs-on: [self-hosted, Linux, X64]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: stable\n          cache: false # don't use cache for self-hosted runners\n\n      - name: Unit Test\n        run: go test -coverpkg=./... -coverprofile=coverage.txt ./...\n\n      - uses: codecov/codecov-action@v5\n        with:\n          fail_ci_if_error: true\n"
  },
  {
    "path": ".github/workflows/vulncheck.yml",
    "content": "name: Run govulncheck\n\non:\n  push:\n    branches:\n      - develop\n    paths:\n      - \"**\"\n      - \"!**.md\"\n  pull_request:\n    paths:\n      - \"**\"\n      - \"!**.md\"\n\njobs:\n  vulncheck-check:\n    runs-on: ubuntu-latest\n    env:\n      GO111MODULE: on\n    steps:\n      - name: Fetch Repository\n        uses: actions/checkout@v4\n\n      - name: Install Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: stable\n          check-latest: true\n          cache: false\n\n      - name: Install Govulncheck\n        run: go install golang.org/x/vuln/cmd/govulncheck@latest\n\n      - name: Run Govulncheck\n        run: govulncheck ./...\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n.vscode\npkg/app/fs.go.hertz.gz\ncoverage.txt\ncoverage.out\n# OSX trash\n.DS_Store\n\n# test benchmark tmp output\ncpu.out\nmem.out\n*.test\n"
  },
  {
    "path": ".golangci.yaml",
    "content": "version: \"2\"\nlinters:\n  default: none\n  enable:\n    - govet\n    - ineffassign\n    - staticcheck\n    - unconvert\n    - unused\n  settings:\n    staticcheck:\n      checks:\n        - all\n        - -SA5008\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": ".licenserc.yaml",
    "content": "header:\n  license:\n    spdx-id: Apache-2.0\n    copyright-owner: CloudWeGo Authors\n\n  paths:\n    - '**/*.go'\n    - '**/*.s'\n\n  paths-ignore:\n    - pkg/common/testdata/**\n    - cmd/hz/protobuf/api\n\n  comment: on-failure\n"
  },
  {
    "path": ".typos.toml",
    "content": "# Typo check: https://github.com/crate-ci/typos\n\n[files]\nextend-exclude = [\"go.mod\", \"go.sum\"]\n\n[default.extend-identifiers]\npn = \"pn\" # packageName in cmd/hz\nConnTLSer = \"ConnTLSer\"\nOPTIO = \"OPTIO\" # b[:5] for OPTION\n\n[default.extend-words]\ntyp = \"typ\"   # type\nFlate = \"Flate\" # flate algorithm\n\n[default]\n# Only ignore these exact \"weird-case\" examples used to demonstrate case-insensitive matching.\nextend-ignore-re = [\n  # Comments showing header normalization examples:\n  \"\\\\bcONTENT-lenGTH\\\\b\",\n  # Test intentionally using odd case:\n  \"\\\\bconnecTION\\\\b\",\n]\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nconduct@cloudwego.io.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to Contribute\n\n## Your First Pull Request\nWe use github for our codebase. You can start by reading [How To Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests).\n\n## Without Semantic Versioning\nWe keep the stable code in branch `main` like `golang.org/x`. Development base on branch `develop`. And we promise the **Forward Compatibility** by adding new package directory with suffix `v2/v3` when code has break changes.\n\n## Branch Organization\nWe use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) as our branch organization, as known as [FDD](https://en.wikipedia.org/wiki/Feature-driven_development)\n\n## Bugs\n### 1. How to Find Known Issues\nWe are using [Github Issues](https://github.com/cloudwego/hertz/issues) for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist.\n\n### 2. Reporting New Issues\nProviding a reduced test code is a recommended way for reporting issues. Then can placed in:\n- Just in issues\n- [Golang Playground](https://play.golang.org/)\n\n### 3. Security Bugs\nPlease do not report the safe disclosure of bugs to public issues. Contact us by [Support Email](mailto:conduct@cloudwego.io)\n\n## How to Get in Touch\n- [Email](mailto:conduct@cloudwego.io)\n\n## Submit a Pull Request\nBefore you submit your Pull Request (PR) consider the following guidelines:\n1. Search [GitHub](https://github.com/cloudwego/hertz/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate existing efforts.\n2. Please submit an issue instead of PR if you have a better suggestion for format tools. We won't accept a lot of file changes directly without issue statement and assignment.\n3. Be sure that the issue describes the problem you're fixing, or documents the design for the feature you'd like to add. Before we accepting your work, we need to conduct some checks and evaluations. So, It will be better if you can discuss the design with us.\n4. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the cloudwego/hertz repo.\n5. In your forked repository, make your changes in a new git branch:\n    ```\n    git checkout -b my-fix-branch develop\n    ```\n6. Create your patch, including appropriate test cases.\n7. Follow our [Style Guides](#code-style-guides).\n8. Commit your changes using a descriptive commit message that follows [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit).\n   Adherence to these conventions is necessary because release notes are automatically generated from these messages.\n9. Push your branch to GitHub:\n    ```\n    git push origin my-fix-branch\n    ```\n10. In GitHub, send a pull request to `hertz:develop` with a clear and unambiguous title.\n\n## Contribution Prerequisites\n- Our development environment keeps up with [Go Official](https://golang.org/project/).\n- You need fully checking with lint tools before submit your pull request. [gofmt](https://golang.org/pkg/cmd/gofmt/) and [golangci-lint](https://github.com/golangci/golangci-lint)\n- You are familiar with [Github](https://github.com)\n- Maybe you need familiar with [Actions](https://github.com/features/actions)(our default workflow tool).\n\n## Code Style Guides\n\nSee [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).\n\nGood resources:\n- [Effective Go](https://golang.org/doc/effective_go)\n- [Pingcap General advice](https://pingcap.github.io/style-guide/general.html)\n- [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md)\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "SHELL := /bin/bash\n\n.PHONY: \\\n\thelp \\\n\tcoverage \\\n\tvet \\\n\tlint \\\n\tfmt \\\n\tversion\n\nall: imports fmt lint vet errors build\n\nhelp:\n\t@echo 'Usage: make <OPTIONS> ... <TARGETS>'\n\t@echo ''\n\t@echo 'Available targets are:'\n\t@echo ''\n\t@echo '    help               Show this help screen.'\n\t@echo '    coverage           Report code tests coverage.'\n\t@echo '    vet                Run go vet.'\n\t@echo '    lint               Run golint.'\n\t@echo '    fmt                Run go fmt.'\n\t@echo '    version            Display Go version.'\n\t@echo ''\n\t@echo 'Targets run by default are: lint, vet.'\n\t@echo ''\n\nprint-%:\n\t@echo $* = $($*)\n\ndeps:\n\tgo get golang.org/x/lint/golint\n\ncoverage:\n\tgo test $(go list ./... | grep -v examples) -coverprofile coverage.txt ./...\n\nvet:\n\tgo vet ./...\n\nlint: deps\n\tgolint ./...\n\nfmt:\n\tgo install mvdan.cc/gofumpt@latest\n\tgofumpt -l -w -extra .\n\npre-dev:\n\tmake pre-commit\n\npre-commit:\n\tbash script/pre-commit-hook\n\nrelease: package-release sign-release\n\nversion:\n\t@go version"
  },
  {
    "path": "NOTICE",
    "content": "CloudWeGo\nCopyright 2022 CloudWeGo Authors\n\nApache Thrift\nCopyright (C) 2006 - 2019, The Apache Software Foundation\n\nThis product includes software developed at\nThe Apache Software Foundation (http://www.apache.org/)."
  },
  {
    "path": "README.md",
    "content": "# Hertz\n\nEnglish | [中文](README_cn.md)\n\n[![Release](https://img.shields.io/github/v/release/cloudwego/hertz)](https://github.com/cloudwego/hertz/releases)\n[![WebSite](https://img.shields.io/website?up_message=cloudwego&url=https%3A%2F%2Fwww.cloudwego.io%2F)](https://www.cloudwego.io/)\n[![License](https://img.shields.io/github/license/cloudwego/hertz)](https://github.com/cloudwego/hertz/blob/main/LICENSE)\n[![Go Report Card](https://goreportcard.com/badge/github.com/cloudwego/hertz)](https://goreportcard.com/report/github.com/cloudwego/hertz)\n[![OpenIssue](https://img.shields.io/github/issues/cloudwego/hertz)](https://github.com/cloudwego/hertz/issues)\n[![ClosedIssue](https://img.shields.io/github/issues-closed/cloudwego/hertz)](https://github.com/cloudwego/hertz/issues?q=is%3Aissue+is%3Aclosed)\n![Stars](https://img.shields.io/github/stars/cloudwego/hertz)\n![Forks](https://img.shields.io/github/forks/cloudwego/hertz)\n\n\nHertz [həːts] is a high-usability, high-performance and high-extensibility Golang HTTP framework that helps developers build microservices. It was originally a fork of [fasthttp](https://github.com/valyala/fasthttp) and inspired by [gin](https://github.com/gin-gonic/gin), [echo](https://github.com/labstack/echo) and combined with the internal requirements in ByteDance. At present, it has been widely used inside ByteDance. Nowadays, more and more microservices use Golang. If you have requirements for microservice performance and hope that the framework can fully meet the internal customizable requirements, Hertz will be a good choice.\n## Basic Features\n- High usability\n\n  During the development process, it is often more important to write the correct code quickly. Therefore, in the iterative process of Hertz, we actively listen to users' opinions and continue to polish the framework, hoping to provide users with a better user experience and help users write correct code faster.\n- High performance\n\n  Hertz uses the self-developed high-performance network library Netpoll by default. In some special scenarios, compared to Go Net, Hertz has certain advantages in QPS and time delay. For performance data, please refer to the Echo data in the figure below.\n\n  Comparison of four frameworks:\n  ![Performance](images/performance-4.png)\n  Comparison of three frameworks:\n  ![Performance](images/performance-3.png)\n  For detailed performance data, please refer to [hertz-benchmark](https://github.com/cloudwego/hertz-benchmark).\n- High extensibility\n\n  Hertz adopts a layered design, providing more interfaces and default extension implementations. Users can also extend by themselves. At the same time, thanks to the layered design of the framework, the extensibility of the framework will be much greater. At present, only stable capabilities are open-sourced to the community. More planning refers to [RoadMap](ROADMAP.md).\n- Multi-protocol support\n\n  The Hertz framework provides HTTP/1.1 and ALPN protocol support natively. In addition, due to the layered design, Hertz even supports custom build protocol resolution logic to meet any needs of protocol layer extensions.\n- Network layer switching capability\n\n  Hertz implements the function to switch between Netpoll and Go Net on demand. Users can choose the appropriate network library for different scenarios. And Hertz also supports the extension of network library in the form of plug-ins.\n## Documentation\n### [Getting Started](https://www.cloudwego.io/docs/hertz/getting-started/)\n### Example\n  The Hertz-Examples repository provides code out of the box. [more](https://www.cloudwego.io/zh/docs/hertz/tutorials/example/)\n### Basic Features\n  Contains introduction and use of general middleware, context selection, data binding, data rendering, direct access, logging, error handling. [more](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/)\n### Observability\n  Contains instrumentation, logging, tracing. [more](https://www.cloudwego.io/docs/hertz/tutorials/observability/)\n### Framework Extension\n  Contains network library extensions, protocol extensions, logger extensions, monitoring extensions. [more](https://www.cloudwego.io/zh/docs/hertz/tutorials/framework-exten/)\n### Reference\n  Framework configurable items list. [more](https://www.cloudwego.io/zh/docs/hertz/reference/)\n### FAQ\n  Frequently Asked Questions. [more](https://www.cloudwego.io/zh/docs/hertz/faq/)\n## Performance\n  Performance testing can only provide a relative reference. In production, there are many factors that can affect actual performance.\n  We provide the [hertz-benchmark](https://github.com/cloudwego/hertz-benchmark) project to track and compare the performance of Hertz and other frameworks in different situations for reference.\n## Related Projects\n- [Netpoll](https://github.com/cloudwego/netpoll): A high-performance network library. Hertz integrated by default.\n- [Example](https://github.com/cloudwego/hertz-examples): Use examples of Hertz.\n## Extensions\n[Hertz-contrib](https://github.com/hertz-contrib) is a partial extension library of Hertz, which users can integrate into Hertz through options according to their needs, built and maintained by the community.\n\n| Extensions                                                                                         | Description                                                                                                                                                                    |\n|----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| [Autotls](https://github.com/hertz-contrib/autotls)                                                | Make Hertz support Let's Encrypt.                                                                                                                                              |\n| [Http2](https://github.com/hertz-contrib/http2)                                                    | HTTP2 support for Hertz.                                                                                                                                                       |\n| [Websocket](https://github.com/hertz-contrib/websocket)                                            | Enable Hertz to support the Websocket protocol.                                                                                                                                |\n| [Etag](https://github.com/hertz-contrib/etag)                                                      | Support ETag (or entity tag) HTTP response header for Hertz.                                                                                                                   |\n| [Limiter](https://github.com/hertz-contrib/limiter)                                                | Provides a current limiter based on the bbr algorithm.                                                                                                                         |\n| [Monitor-prometheus](https://github.com/hertz-contrib/monitor-prometheus)                          | Provides service monitoring based on Prometheus.                                                                                                                               |\n| [Obs-opentelemetry](https://github.com/hertz-contrib/obs-opentelemetry)                            | Hertz's Opentelemetry extension that supports Metric, Logger, Tracing and works out of the box.                                                                                |\n| [Opensergo](https://github.com/hertz-contrib/opensergo)                                            | The Opensergo extension.                                                                                                                                                       |\n| [Pprof](https://github.com/hertz-contrib/pprof)                                                    | Extension for Hertz integration with Pprof.                                                                                                                                    |\n| [Registry](https://github.com/hertz-contrib/registry)                                              | Provides service registry and discovery functions. So far, the supported service discovery extensions are nacos, consul, etcd, eureka, polaris, servicecomb, zookeeper, redis. |\n| [Sentry](https://github.com/hertz-contrib/hertzsentry)                                             | Sentry extension provides some unified interfaces to help users perform real-time error monitoring.                                                                            |\n| [Tracer](https://github.com/hertz-contrib/tracer)                                                  | Link tracing based on Opentracing.                                                                                                                                             |\n| [Basicauth](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/basic_auth) | Basicauth middleware can provide HTTP basic authentication.                                                                                                                    |\n| [Jwt](https://github.com/hertz-contrib/jwt)                                                        | Jwt extension.                                                                                                                                                                 |\n| [Keyauth](https://github.com/hertz-contrib/keyauth)                                                | Provides token-based authentication.                                                                                                                                           |\n| [Requestid](https://github.com/hertz-contrib/requestid)                                            | Add request id in response.                                                                                                                                                    |\n| [Sessions](https://github.com/hertz-contrib/sessions)                                              | Session middleware with multi-state store support.                                                                                                                             |\n| [Casbin](https://github.com/hertz-contrib/casbin)                                                  | Supports various access control models by Casbin.                                                                                                                              |\n| [Cors](https://github.com/hertz-contrib/cors)                                                      | Provides cross-domain resource sharing support.                                                                                                                                |\n| [Csrf](https://github.com/hertz-contrib/csrf)                                                      | Csrf middleware is used to prevent cross-site request forgery attacks.                                                                                                         |\n| [Secure](https://github.com/hertz-contrib/secure)                                                  | Secure middleware with multiple configuration items.                                                                                                                           |\n| [Gzip](https://github.com/hertz-contrib/gzip)                                                      | A Gzip extension with multiple options.                                                                                                                                        |\n| [I18n](https://github.com/hertz-contrib/i18n)                                                      | Helps translate Hertz programs into multi programming languages.                                                                                                               |\n| [Lark](https://github.com/hertz-contrib/lark-hertz)                                                | Use hertz handle Lark/Feishu card message and event callback.                                                                                                                  |\n| [Loadbalance](https://github.com/hertz-contrib/loadbalance)                                        | Provides load balancing algorithms for Hertz.                                                                                                                                  |\n| [Logger](https://github.com/hertz-contrib/logger)                                                  | Logger extension for Hertz, which provides support for zap, logrus, zerologs logging frameworks.                                                                               |\n| [Recovery](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/recovery)    | Recovery middleware for Hertz.                                                                                                                                                 |\n| [Reverseproxy](https://github.com/hertz-contrib/reverseproxy)                                      | Implement a reverse proxy.                                                                                                                                                     |\n| [Swagger](https://github.com/hertz-contrib/swagger)                                                | Automatically generate RESTful API documentation with Swagger 2.0.                                                                                                             |\n| [Cache](https://github.com/hertz-contrib/cache)                                                    | Hertz middleware for cache HTTP response with multi-backend support                                                                                                            |\n\n## Blogs\n- [ByteDance Practice on Go Network Library](https://www.cloudwego.io/blog/2020/05/24/bytedance-practices-on-go-network-library/)\n- [Ultra-large-scale Enterprise-level Microservice HTTP Framework — Hertz is Officially Open Source!](https://www.cloudwego.io/zh/blog/2022/06/21/%E8%B6%85%E5%A4%A7%E8%A7%84%E6%A8%A1%E7%9A%84%E4%BC%81%E4%B8%9A%E7%BA%A7%E5%BE%AE%E6%9C%8D%E5%8A%A1-http-%E6%A1%86%E6%9E%B6-hertz-%E6%AD%A3%E5%BC%8F%E5%BC%80%E6%BA%90/)\n- [ByteDance Open Source Go HTTP Framework Hertz Design Practice](https://www.cloudwego.io/zh/blog/2022/06/21/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8%E5%BC%80%E6%BA%90-go-http-%E6%A1%86%E6%9E%B6-hertz-%E8%AE%BE%E8%AE%A1%E5%AE%9E%E8%B7%B5/)\n- [Help ByteDance Reduce Costs and Increase Efficiency, the Design Practice for Large-scale Enterprise-level HTTP Framework Hertz](https://www.cloudwego.io/zh/blog/2022/09/27/%E5%8A%A9%E5%8A%9B%E5%AD%97%E8%8A%82%E9%99%8D%E6%9C%AC%E5%A2%9E%E6%95%88%E5%A4%A7%E8%A7%84%E6%A8%A1%E4%BC%81%E4%B8%9A%E7%BA%A7-http-%E6%A1%86%E6%9E%B6-hertz-%E8%AE%BE%E8%AE%A1%E5%AE%9E%E8%B7%B5/)\n- [Getting Started with Hertz: Performance Testing Guide](https://www.cloudwego.io/blog/2023/02/24/getting-started-with-hertz-performance-testing-guide/)\n\n## Contributing\n\n[Contributing](https://github.com/cloudwego/hertz/blob/main/CONTRIBUTING.md)\n## RoadMap\n[Hertz RoadMap](ROADMAP.md)\n## License\nHertz is distributed under the [Apache License, version 2.0](https://github.com/cloudwego/hertz/blob/main/LICENSE). The licenses of third party dependencies of Hertz are explained [here](https://github.com/cloudwego/hertz/blob/main/licenses).\n## Community\n- Email: [conduct@cloudwego.io](conduct@cloudwego.io)\n- How to become a member: [COMMUNITY MEMBERSHIP](https://github.com/cloudwego/community/blob/main/COMMUNITY_MEMBERSHIP.md)\n- Issues: [Issues](https://github.com/cloudwego/hertz/issues)\n- Slack: Join our CloudWeGo community [Slack Channel](https://join.slack.com/t/cloudwego/shared_invite/zt-tmcbzewn-UjXMF3ZQsPhl7W3tEDZboA).\n- Lark: Scan the QR code below with [Lark](https://www.larksuite.com/zh_cn/download) to join our CloudWeGo/hertz user group.\n\n![LarkGroup](images/lark_group.png)\n\n## Contributors\nThank you for your contribution to Hertz!\n\n[![Contributors](https://contrib.rocks/image?repo=cloudwego/hertz)](https://github.com/cloudwego/hertz/graphs/contributors)\n## Landscapes\n\n<p align=\"center\">\n<img src=\"https://landscape.cncf.io/images/cncf-landscape-horizontal-color.svg\" width=\"150\"/>&nbsp;&nbsp;<img src=\"https://www.cncf.io/wp-content/uploads/2023/04/cncf-main-site-logo.svg\" width=\"200\"/>\n<br/><br/>\nCloudWeGo enriches the <a href=\"https://landscape.cncf.io/\">CNCF CLOUD NATIVE Landscape</a>.\n</p>\n"
  },
  {
    "path": "README_cn.md",
    "content": "# Hertz\n\n[English](README.md) | 中文\n\n[![Release](https://img.shields.io/github/v/release/cloudwego/hertz)](https://github.com/cloudwego/hertz/releases)\n[![WebSite](https://img.shields.io/website?up_message=cloudwego&url=https%3A%2F%2Fwww.cloudwego.io%2F)](https://www.cloudwego.io/)\n[![License](https://img.shields.io/github/license/cloudwego/hertz)](https://github.com/cloudwego/hertz/blob/main/LICENSE)\n[![Go Report Card](https://goreportcard.com/badge/github.com/cloudwego/hertz)](https://goreportcard.com/report/github.com/cloudwego/hertz)\n[![OpenIssue](https://img.shields.io/github/issues/cloudwego/hertz)](https://github.com/cloudwego/hertz/issues)\n[![ClosedIssue](https://img.shields.io/github/issues-closed/cloudwego/hertz)](https://github.com/cloudwego/hertz/issues?q=is%3Aissue+is%3Aclosed)\n![Stars](https://img.shields.io/github/stars/cloudwego/hertz)\n![Forks](https://img.shields.io/github/forks/cloudwego/hertz)\n\nHertz[həːts] 是一个 Golang 微服务 HTTP 框架，最初fork自[fasthttp](https://github.com/valyala/fasthttp)，并在设计时参考了其他开源框架[gin](https://github.com/gin-gonic/gin)、[echo](https://github.com/labstack/echo) 的优势，并结合字节跳动内部的需求，使其具有高易用性、高\n性能、高扩展性等特点，目前在字节跳动内部已广泛使用。如今越来越多的微服务选择使用 Golang，如果对微服务性能有要求，又希望框架能够充分满足内部的可定制化需求，Hertz 会是一个不错的选择。\n\n## 框架特点\n- 高易用性\n\n  在开发过程中，快速写出来正确的代码往往是更重要的。因此，在 Hertz 在迭代过程中，积极听取用户意见，持续打磨框架，希望为用户提供一个更好的使用体验，帮助用户更快的写出正确的代码。\n- 高性能\n\n  Hertz 默认使用自研的高性能网络库 Netpoll，在一些特殊场景相较于 go net，Hertz 在 QPS、时延上均具有一定优势。关于性能数据，可参考下图 Echo 数据。\n  \n  四个框架的对比:\n  ![Performance](images/performance-4.png)\n  三个框架的对比:\n  ![Performance](images/performance-3.png)\n  关于详细的性能数据，可参考 [hertz-benchmark](https://github.com/cloudwego/hertz-benchmark)。\n- 高扩展性\n\n  Hertz 采用了分层设计，提供了较多的接口以及默认的扩展实现，用户也可以自行扩展。同时得益于框架的分层设计，框架的扩展性也会大很多。目前仅将稳定的能力开源给社区，更多的规划参考 [RoadMap](ROADMAP.md)。\n- 多协议支持\n\n  Hertz 框架原生提供 HTTP/1.1 及 ALPN 协议支持。除此之外，由于分层设计，Hertz 甚至支持自定义构建协议解析逻辑，以满足协议层扩展的任意需求。\n- 网络层切换能力\n\n  Hertz 实现了 Netpoll 和 Golang 原生网络库 间按需切换能力，用户可以针对不同的场景选择合适的网络库，同时也支持以插件的方式为 Hertz 扩展网络库实现。\n## 详细文档\n### [快速开始](https://www.cloudwego.io/zh/docs/hertz/getting-started/)\n### Example\n  Hertz-Examples 仓库提供了开箱即用的代码，[详见](https://www.cloudwego.io/zh/docs/hertz/tutorials/example/)。\n### 用户指南\n### 基本特性\n  包含通用中间件的介绍和使用，上下文选择，数据绑定，数据渲染，直连访问，日志，错误处理，[详见文档](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/)\n### 可观测性\n  包含日志，链路追踪，埋点，[详见文档](https://www.cloudwego.io/zh/docs/hertz/tutorials/observability/)\n### 框架扩展\n  包含网络库扩展，协议扩展，日志扩展，监控扩展，服务注册与发现扩展，[详见文档](https://www.cloudwego.io/zh/docs/hertz/tutorials/framework-exten/)\n### 参考\n  框架可配置项一览，[详见文档](https://www.cloudwego.io/zh/docs/hertz/reference/)\n### FAQ\n  常见问题排查，[详见文档](https://www.cloudwego.io/zh/docs/hertz/faq/)\n## 框架性能\n  性能测试只能提供相对参考，工业场景下，有诸多因素可以影响实际的性能表现\n  我们提供了 [hertz-benchmark](https://github.com/cloudwego/hertz-benchmark) 项目用来长期追踪和比较 Hertz 与其他框架在不同情况下的性能数据以供参考\n## 相关项目\n- [Netpoll](https://github.com/cloudwego/netpoll): 自研高性能网络库，Hertz 默认集成\n- [Example](https://github.com/cloudwego/hertz-examples): Hertz 使用例子\n\n## 相关拓展\n[hertz-Contrib](https://github.com/hertz-contrib) 是 Hertz 扩展生态所在组织，提供服务注册发现、可观测、安全、流量治理、协议、HTTP 通用能力等扩展，由社区共建与维护\n\n| 拓展                                                                                                 | 描述                                                                                                |\n|----------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|\n| [Autotls](https://github.com/hertz-contrib/autotls)                                                | 为 Hertz 支持 Let's Encrypt 。                                                                        |\n| [Http2](https://github.com/hertz-contrib/http2)                                                    | 提供对 HTTP2 的支持。                                                                                    |\n| [Websocket](https://github.com/hertz-contrib/websocket)                                            | 使 Hertz 支持 Websocket 协议。                                                                          |\n| [Etag](https://github.com/hertz-contrib/etag)                                                      | 提供 ETag HTTP 响应标头。                                                                                |\n| [Limiter](https://github.com/hertz-contrib/limiter)                                                | 提供了基于 bbr 算法的限流器。                                                                                 |\n| [Monitor-prometheus](https://github.com/hertz-contrib/monitor-prometheus)                          | 提供基于 Prometheus 服务监控功能。                                                                           |\n| [Obs-opentelemetry](https://github.com/hertz-contrib/obs-opentelemetry)                            | Hertz 的 Opentelemetry 扩展，支持 Metric、Logger、Tracing 并且达到开箱即用。                                       |\n| [Opensergo](https://github.com/hertz-contrib/opensergo)                                            | Opensergo 扩展。                                                                                     |\n| [Pprof](https://github.com/hertz-contrib/pprof)                                                    | Hertz 集成 Pprof 的扩展。                                                                               |\n| [Registry](https://github.com/hertz-contrib/registry)                                              | 提供服务注册与发现功能。到现在为止，支持的服务发现拓展有 nacos， consul， etcd， eureka， polaris， servicecomb， zookeeper， redis。 |\n| [Sentry](https://github.com/hertz-contrib/hertzsentry)                                             | Sentry 拓展提供了一些统一的接口来帮助用户进行实时的错误监控。                                                                |\n| [Tracer](https://github.com/hertz-contrib/tracer)                                                  | 基于 Opentracing 的链路追踪。                                                                             |\n| [Basicauth](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/basic_auth) | Basicauth 中间件能够提供 HTTP 基本身份验证。                                                                    |\n| [Jwt](https://github.com/hertz-contrib/jwt)                                                        | Jwt 拓展。                                                                                           |\n| [Keyauth](https://github.com/hertz-contrib/keyauth)                                                | 提供基于 token 的身份验证。                                                                                 |\n| [Requestid](https://github.com/hertz-contrib/requestid)                                            | 在 response 中添加 request id。                                                                        |\n| [Sessions](https://github.com/hertz-contrib/sessions)                                              | 具有多状态存储支持的 Session 中间件。                                                                           |\n| [Casbin](https://github.com/hertz-contrib/casbin)                                                  | 通过 Casbin 支持各种访问控制模型。                                                                             |\n| [Cors](https://github.com/hertz-contrib/cors)                                                      | 提供跨域资源共享支持。                                                                                       |\n| [Csrf](https://github.com/hertz-contrib/csrf)                                                      | Csrf 中间件用于防止跨站点请求伪造攻击。                                                                            |\n| [Secure](https://github.com/hertz-contrib/secure)                                                  | 具有多配置项的 Secure 中间件。                                                                               |\n| [Gzip](https://github.com/hertz-contrib/gzip)                                                      | 含多个可选项的 Gzip 拓展。                                                                                  |\n| [I18n](https://github.com/hertz-contrib/i18n)                                                      | 可帮助将 Hertz 程序翻译成多种语言。                                                                             |\n| [Lark](https://github.com/hertz-contrib/lark-hertz)                                                | 在 Hertz 中处理 Lark/飞书的卡片消息和事件的回调。                                                                   |\n| [Loadbalance](https://github.com/hertz-contrib/loadbalance)                                        | 提供适用于 Hertz 的负载均衡算法。                                                                              |\n| [Logger](https://github.com/hertz-contrib/logger)                                                  | Hertz 的日志拓展，提供了对 zap、logrus、zerologs 日志框架的支持。                                                     |\n| [Recovery](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/recovery)    | Hertz 的异常恢复中间件。                                                                                   |\n| [Reverseproxy](https://github.com/hertz-contrib/reverseproxy)                                      | 实现反向代理。                                                                                           |\n| [Swagger](https://github.com/hertz-contrib/swagger)                                                | 使用 Swagger 2.0 自动生成 RESTful API 文档。                                                               |\n| [Cache](https://github.com/hertz-contrib/cache)                                                    | 用于缓存 HTTP 接口内容的 Hertz 中间件，支持多种客户端。                                                                |\n\n## 相关文章\n- [字节跳动在 Go 网络库上的实践](https://www.cloudwego.io/zh/blog/2020/05/24/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8%E5%9C%A8-go-%E7%BD%91%E7%BB%9C%E5%BA%93%E4%B8%8A%E7%9A%84%E5%AE%9E%E8%B7%B5/)\n- [超大规模的企业级微服务 HTTP 框架 — Hertz 正式开源！](https://www.cloudwego.io/zh/blog/2022/06/21/%E8%B6%85%E5%A4%A7%E8%A7%84%E6%A8%A1%E7%9A%84%E4%BC%81%E4%B8%9A%E7%BA%A7%E5%BE%AE%E6%9C%8D%E5%8A%A1-http-%E6%A1%86%E6%9E%B6-hertz-%E6%AD%A3%E5%BC%8F%E5%BC%80%E6%BA%90/)\n- [字节跳动开源 Go HTTP 框架 Hertz 设计实践](https://www.cloudwego.io/zh/blog/2022/06/21/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8%E5%BC%80%E6%BA%90-go-http-%E6%A1%86%E6%9E%B6-hertz-%E8%AE%BE%E8%AE%A1%E5%AE%9E%E8%B7%B5/)\n- [助力字节降本增效，大规模企业级 HTTP 框架 Hertz 设计实践](https://www.cloudwego.io/zh/blog/2022/09/27/%E5%8A%A9%E5%8A%9B%E5%AD%97%E8%8A%82%E9%99%8D%E6%9C%AC%E5%A2%9E%E6%95%88%E5%A4%A7%E8%A7%84%E6%A8%A1%E4%BC%81%E4%B8%9A%E7%BA%A7-http-%E6%A1%86%E6%9E%B6-hertz-%E8%AE%BE%E8%AE%A1%E5%AE%9E%E8%B7%B5/)\n- [HTTP 框架 Hertz 实践入门：性能测试指南](https://www.cloudwego.io/zh/blog/2023/02/24/http-%E6%A1%86%E6%9E%B6-hertz-%E5%AE%9E%E8%B7%B5%E5%85%A5%E9%97%A8%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E6%8C%87%E5%8D%97/)\n\n## 贡献代码\n  [Contributing](https://github.com/cloudwego/hertz/blob/main/CONTRIBUTING.md)\n## RoadMap\n  [Hertz RoadMap](ROADMAP.md)\n## 开源许可\n\nHertz 基于[Apache License 2.0](https://github.com/cloudwego/hertz/blob/main/LICENSE) 许可证，其依赖的三方组件的开源许可见 [Licenses](https://github.com/cloudwego/hertz/blob/main/licenses)。\n\n## 联系我们\n- Email: conduct@cloudwego.io\n- 如何成为 member: [COMMUNITY MEMBERSHIP](https://github.com/cloudwego/community/blob/main/COMMUNITY_MEMBERSHIP.md)\n- Issues: [Issues](https://github.com/cloudwego/hertz/issues)\n- Slack: 加入我们的 [Slack 频道](https://join.slack.com/t/cloudwego/shared_invite/zt-tmcbzewn-UjXMF3ZQsPhl7W3tEDZboA)\n- 飞书用户群（[注册飞书](https://www.larksuite.com/zh_cn/download)进群）\n\n  ![LarkGroup](images/lark_group_cn.png)\n\n## 贡献者\n感谢您对 Hertz 作出的贡献！\n\n[![Contributors](https://contrib.rocks/image?repo=cloudwego/hertz)](https://github.com/cloudwego/hertz/graphs/contributors)\n## Landscapes\n\n<p align=\"center\">\n<img src=\"https://landscape.cncf.io/images/cncf-landscape-horizontal-color.svg\" width=\"150\"/>&nbsp;&nbsp;<img src=\"https://www.cncf.io/wp-content/uploads/2023/04/cncf-main-site-logo.svg\" width=\"200\"/>\n<br/><br/>\nCloudWeGo 丰富了 <a href=\"https://landscape.cncf.io/\">CNCF 云原生生态</a>。\n</p>\n"
  },
  {
    "path": "ROADMAP.md",
    "content": "# Hertz RoadMap\n\nFrom 2025, instead of developing new features we will focus on optimizing core functionalities and user experience.\n\nThe following is a list of planned projects.\n\n- [ ] Optimize `RequestContext` issues under concurrency.\n- [ ] Refactor binding and validator for better extensibility, and deprecate built-in implementations.\n- [ ] Make `netpoll` optional, and `pkg/network/standard` the default.\n- [ ] Enhance Content-Encoding extension interface for better extensibility.\n- [ ] Optimize `pkg/common/adaptor`, and deprecate implementations with `net/http` alternatives.\n- [ ] Deprecate the built-in protobuf code generator, use [cloudwego/prutal](https://github.com/cloudwego/prutal)\n\nAll users are encouraged to provide suggestions on the projects listed above, or to submit proposals for enhancing current features.\n"
  },
  {
    "path": "cmd/hz/app/app.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage app\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/config\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator\"\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/protobuf\"\n\t\"github.com/cloudwego/hertz/cmd/hz/thrift\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n\t\"github.com/urfave/cli/v2\"\n)\n\n// global args. MUST fork it when use\nvar globalArgs = config.NewArgument()\n\nfunc New(c *cli.Context) error {\n\targs, err := globalArgs.Parse(c, meta.CmdNew)\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.LoadError)\n\t}\n\tsetLogVerbose(args.Verbose)\n\tlogs.Debugf(\"args: %#v\\n\", args)\n\n\texist, err := util.PathExist(filepath.Join(args.OutDir, meta.ManifestFile))\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.LoadError)\n\t}\n\n\tif exist && !args.ForceNew {\n\t\treturn cli.Exit(fmt.Errorf(\"the current is already a hertz project, if you want to regenerate it you can specify \\\"-force\\\"\"), meta.LoadError)\n\t}\n\n\terr = GenerateLayout(args)\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.GenerateLayoutError)\n\t}\n\n\terr = TriggerPlugin(args)\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.PluginError)\n\t}\n\t// \".hz\" file converges to the hz tool\n\tmanifest := new(meta.Manifest)\n\targs.InitManifest(manifest)\n\terr = manifest.Persist(args.OutDir)\n\tif err != nil {\n\t\treturn cli.Exit(fmt.Errorf(\"persist manifest failed: %v\", err), meta.PersistError)\n\t}\n\tif !args.NeedGoMod && args.IdlType == meta.IdlThrift {\n\t\tlogs.Warn(meta.AddThriftReplace)\n\t}\n\n\treturn nil\n}\n\nfunc Update(c *cli.Context) error {\n\t// begin to update\n\targs, err := globalArgs.Parse(c, meta.CmdUpdate)\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.LoadError)\n\t}\n\tsetLogVerbose(args.Verbose)\n\tlogs.Debugf(\"Args: %#v\\n\", args)\n\n\tmanifest := new(meta.Manifest)\n\terr = manifest.InitAndValidate(args.OutDir)\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.LoadError)\n\t}\n\t// update argument by \".hz\", can automatically get \"handler_dir\"/\"model_dir\"/\"router_dir\"\n\targs.UpdateByManifest(manifest)\n\n\terr = TriggerPlugin(args)\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.PluginError)\n\t}\n\t// If the \"handler_dir\"/\"model_dir\" is updated, write it back to \".hz\"\n\targs.UpdateManifest(manifest)\n\terr = manifest.Persist(args.OutDir)\n\tif err != nil {\n\t\treturn cli.Exit(fmt.Errorf(\"persist manifest failed: %v\", err), meta.PersistError)\n\t}\n\n\treturn nil\n}\n\nfunc Model(c *cli.Context) error {\n\targs, err := globalArgs.Parse(c, meta.CmdModel)\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.LoadError)\n\t}\n\tsetLogVerbose(args.Verbose)\n\tlogs.Debugf(\"Args: %#v\\n\", args)\n\n\terr = TriggerPlugin(args)\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.PluginError)\n\t}\n\n\treturn nil\n}\n\nfunc Client(c *cli.Context) error {\n\targs, err := globalArgs.Parse(c, meta.CmdClient)\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.LoadError)\n\t}\n\tsetLogVerbose(args.Verbose)\n\tlogs.Debugf(\"Args: %#v\\n\", args)\n\n\terr = TriggerPlugin(args)\n\tif err != nil {\n\t\treturn cli.Exit(err, meta.PluginError)\n\t}\n\n\treturn nil\n}\n\nfunc PluginMode() {\n\tmode := os.Getenv(meta.EnvPluginMode)\n\tif len(os.Args) <= 1 && mode != \"\" {\n\t\tswitch mode {\n\t\tcase meta.ThriftPluginName:\n\t\t\tplugin := new(thrift.Plugin)\n\t\t\tos.Exit(plugin.Run())\n\t\tcase meta.ProtocPluginName:\n\t\t\tplugin := new(protobuf.Plugin)\n\t\t\tos.Exit(plugin.Run())\n\t\t}\n\t}\n}\n\nfunc Init() *cli.App {\n\t// flags\n\tverboseFlag := cli.BoolFlag{Name: \"verbose,vv\", Usage: \"turn on verbose mode\", Destination: &globalArgs.Verbose}\n\n\tidlFlag := cli.StringSliceFlag{Name: \"idl\", Usage: \"Specify the IDL file path. (.thrift or .proto)\"}\n\tmoduleFlag := cli.StringFlag{Name: \"module\", Aliases: []string{\"mod\"}, Usage: \"Specify the Go module name.\", Destination: &globalArgs.Gomod}\n\tserviceNameFlag := cli.StringFlag{Name: \"service\", Usage: \"Specify the service name.\", Destination: &globalArgs.ServiceName}\n\toutDirFlag := cli.StringFlag{Name: \"out_dir\", Usage: \"Specify the project path.\", Destination: &globalArgs.OutDir}\n\thandlerDirFlag := cli.StringFlag{Name: \"handler_dir\", Usage: \"Specify the handler relative path (based on \\\"out_dir\\\").\", Destination: &globalArgs.HandlerDir}\n\tmodelDirFlag := cli.StringFlag{Name: \"model_dir\", Usage: \"Specify the model relative path (based on \\\"out_dir\\\").\", Destination: &globalArgs.ModelDir}\n\trouterDirFlag := cli.StringFlag{Name: \"router_dir\", Usage: \"Specify the router relative path (based on \\\"out_dir\\\").\", Destination: &globalArgs.RouterDir}\n\tuseFlag := cli.StringFlag{Name: \"use\", Usage: \"Specify the model package to import for handler.\", Destination: &globalArgs.Use}\n\tbaseDomainFlag := cli.StringFlag{Name: \"base_domain\", Usage: \"Specify the request domain.\", Destination: &globalArgs.BaseDomain}\n\tclientDirFlag := cli.StringFlag{Name: \"client_dir\", Usage: \"Specify the client path. If not specified, IDL generated path is used for 'client' command; no client code is generated for 'new' command\", Destination: &globalArgs.ClientDir}\n\tforceClientDirFlag := cli.StringFlag{Name: \"force_client_dir\", Usage: \"Specify the client path, and won't use namespaces as subpaths\", Destination: &globalArgs.ForceClientDir}\n\n\toptPkgFlag := cli.StringSliceFlag{Name: \"option_package\", Aliases: []string{\"P\"}, Usage: \"Specify the package path. ({include_path}={import_path})\"}\n\tincludesFlag := cli.StringSliceFlag{Name: \"proto_path\", Aliases: []string{\"I\"}, Usage: \"Add an IDL search path for includes. (Valid only if idl is protobuf)\"}\n\texcludeFilesFlag := cli.StringSliceFlag{Name: \"exclude_file\", Aliases: []string{\"E\"}, Usage: \"Specify the files that do not need to be updated.\"}\n\tthriftOptionsFlag := cli.StringSliceFlag{Name: \"thriftgo\", Aliases: []string{\"t\"}, Usage: \"Specify arguments for the thriftgo. ({flag}={value})\"}\n\tprotoOptionsFlag := cli.StringSliceFlag{Name: \"protoc\", Aliases: []string{\"p\"}, Usage: \"Specify arguments for the protoc. ({flag}={value})\"}\n\tthriftPluginsFlag := cli.StringSliceFlag{Name: \"thrift-plugins\", Usage: \"Specify plugins for the thriftgo. ({plugin_name}:{options})\"}\n\tprotoPluginsFlag := cli.StringSliceFlag{Name: \"protoc-plugins\", Usage: \"Specify plugins for the protoc. ({plugin_name}:{options}:{out_dir})\"}\n\tnoRecurseFlag := cli.BoolFlag{Name: \"no_recurse\", Usage: \"Generate master model only.\", Destination: &globalArgs.NoRecurse}\n\tforceNewFlag := cli.BoolFlag{Name: \"force\", Aliases: []string{\"f\"}, Usage: \"Force new a project, which will overwrite the generated files\", Destination: &globalArgs.ForceNew}\n\tforceUpdateClientFlag := cli.BoolFlag{Name: \"force_client\", Usage: \"Force update 'hertz_client.go'\", Destination: &globalArgs.ForceUpdateClient}\n\tenableExtendsFlag := cli.BoolFlag{Name: \"enable_extends\", Usage: \"Parse 'extends' for thrift IDL\", Destination: &globalArgs.EnableExtends}\n\tsortRouterFlag := cli.BoolFlag{Name: \"sort_router\", Usage: \"Sort router register code, to avoid code difference\", Destination: &globalArgs.SortRouter}\n\n\tjsonEnumStrFlag := cli.BoolFlag{Name: \"json_enumstr\", Usage: \"Use string instead of num for json enums when idl is thrift.\", Destination: &globalArgs.JSONEnumStr}\n\tqueryEnumIntFlag := cli.BoolFlag{Name: \"query_enumint\", Usage: \"Use num instead of string for query enum parameter.\", Destination: &globalArgs.QueryEnumAsInt}\n\tunsetOmitemptyFlag := cli.BoolFlag{Name: \"unset_omitempty\", Usage: \"Remove 'omitempty' tag for generated struct.\", Destination: &globalArgs.UnsetOmitempty}\n\tprotoCamelJSONTag := cli.BoolFlag{Name: \"pb_camel_json_tag\", Usage: \"Convert Name style for json tag to camel(Only works protobuf).\", Destination: &globalArgs.ProtobufCamelJSONTag}\n\tsnakeNameFlag := cli.BoolFlag{Name: \"snake_tag\", Usage: \"Use snake_case style naming for tags. (Only works for 'form', 'query', 'json')\", Destination: &globalArgs.SnakeName}\n\trmTagFlag := cli.StringSliceFlag{Name: \"rm_tag\", Usage: \"Remove the default tag(json/query/form). If the annotation tag is set explicitly, it will not be removed.\"}\n\tcustomLayout := cli.StringFlag{Name: \"customize_layout\", Usage: \"Specify the path for layout template.\", Destination: &globalArgs.CustomizeLayout}\n\tcustomLayoutData := cli.StringFlag{Name: \"customize_layout_data_path\", Usage: \"Specify the path for layout template render data.\", Destination: &globalArgs.CustomizeLayoutData}\n\tcustomPackage := cli.StringFlag{Name: \"customize_package\", Usage: \"Specify the path for package template.\", Destination: &globalArgs.CustomizePackage}\n\thandlerByMethod := cli.BoolFlag{Name: \"handler_by_method\", Usage: \"Generate a separate handler file for each method.\", Destination: &globalArgs.HandlerByMethod}\n\ttrimGoPackage := cli.StringFlag{Name: \"trim_gopackage\", Aliases: []string{\"trim_pkg\"}, Usage: \"Trim the prefix of go_package for protobuf.\", Destination: &globalArgs.TrimGoPackage}\n\n\t// client flag\n\tenableClientOptionalFlag := cli.BoolFlag{Name: \"enable_optional\", Usage: \"Optional field do not transfer for thrift if not set.(Only works for query tag)\", Destination: &globalArgs.EnableClientOptional}\n\n\t// app\n\tapp := cli.NewApp()\n\tapp.Name = \"hz\"\n\tapp.Usage = \"A idl parser and code generator for Hertz projects\"\n\tapp.Version = meta.Version\n\t// The default separator for multiple parameters is modified to \";\"\n\tapp.SliceFlagSeparator = \";\"\n\n\t// global flags\n\tapp.Flags = []cli.Flag{\n\t\t&verboseFlag,\n\t}\n\n\t// Commands\n\tapp.Commands = []*cli.Command{\n\t\t{\n\t\t\tName:  meta.CmdNew,\n\t\t\tUsage: \"Generate a new Hertz project\",\n\t\t\tFlags: []cli.Flag{\n\t\t\t\t&idlFlag,\n\t\t\t\t&serviceNameFlag,\n\t\t\t\t&moduleFlag,\n\t\t\t\t&outDirFlag,\n\t\t\t\t&handlerDirFlag,\n\t\t\t\t&modelDirFlag,\n\t\t\t\t&routerDirFlag,\n\t\t\t\t&clientDirFlag,\n\t\t\t\t&useFlag,\n\n\t\t\t\t&includesFlag,\n\t\t\t\t&thriftOptionsFlag,\n\t\t\t\t&protoOptionsFlag,\n\t\t\t\t&optPkgFlag,\n\t\t\t\t&trimGoPackage,\n\t\t\t\t&noRecurseFlag,\n\t\t\t\t&forceNewFlag,\n\t\t\t\t&enableExtendsFlag,\n\t\t\t\t&sortRouterFlag,\n\n\t\t\t\t&jsonEnumStrFlag,\n\t\t\t\t&unsetOmitemptyFlag,\n\t\t\t\t&protoCamelJSONTag,\n\t\t\t\t&snakeNameFlag,\n\t\t\t\t&rmTagFlag,\n\t\t\t\t&excludeFilesFlag,\n\t\t\t\t&customLayout,\n\t\t\t\t&customLayoutData,\n\t\t\t\t&customPackage,\n\t\t\t\t&handlerByMethod,\n\t\t\t\t&protoPluginsFlag,\n\t\t\t\t&thriftPluginsFlag,\n\t\t\t},\n\t\t\tAction: New,\n\t\t},\n\t\t{\n\t\t\tName:  meta.CmdUpdate,\n\t\t\tUsage: \"Update an existing Hertz project\",\n\t\t\tFlags: []cli.Flag{\n\t\t\t\t&idlFlag,\n\t\t\t\t&moduleFlag,\n\t\t\t\t&outDirFlag,\n\t\t\t\t&handlerDirFlag,\n\t\t\t\t&modelDirFlag,\n\t\t\t\t&clientDirFlag,\n\t\t\t\t&useFlag,\n\n\t\t\t\t&includesFlag,\n\t\t\t\t&thriftOptionsFlag,\n\t\t\t\t&protoOptionsFlag,\n\t\t\t\t&optPkgFlag,\n\t\t\t\t&trimGoPackage,\n\t\t\t\t&noRecurseFlag,\n\t\t\t\t&enableExtendsFlag,\n\t\t\t\t&sortRouterFlag,\n\n\t\t\t\t&jsonEnumStrFlag,\n\t\t\t\t&unsetOmitemptyFlag,\n\t\t\t\t&protoCamelJSONTag,\n\t\t\t\t&snakeNameFlag,\n\t\t\t\t&rmTagFlag,\n\t\t\t\t&excludeFilesFlag,\n\t\t\t\t&customPackage,\n\t\t\t\t&handlerByMethod,\n\t\t\t\t&protoPluginsFlag,\n\t\t\t\t&thriftPluginsFlag,\n\t\t\t},\n\t\t\tAction: Update,\n\t\t},\n\t\t{\n\t\t\tName:  meta.CmdModel,\n\t\t\tUsage: \"Generate model code only\",\n\t\t\tFlags: []cli.Flag{\n\t\t\t\t&idlFlag,\n\t\t\t\t&moduleFlag,\n\t\t\t\t&outDirFlag,\n\t\t\t\t&modelDirFlag,\n\n\t\t\t\t&includesFlag,\n\t\t\t\t&thriftOptionsFlag,\n\t\t\t\t&protoOptionsFlag,\n\t\t\t\t&noRecurseFlag,\n\t\t\t\t&trimGoPackage,\n\n\t\t\t\t&jsonEnumStrFlag,\n\t\t\t\t&unsetOmitemptyFlag,\n\t\t\t\t&protoCamelJSONTag,\n\t\t\t\t&snakeNameFlag,\n\t\t\t\t&rmTagFlag,\n\t\t\t\t&excludeFilesFlag,\n\t\t\t},\n\t\t\tAction: Model,\n\t\t},\n\t\t{\n\t\t\tName:  meta.CmdClient,\n\t\t\tUsage: \"Generate hertz client based on IDL\",\n\t\t\tFlags: []cli.Flag{\n\t\t\t\t&idlFlag,\n\t\t\t\t&moduleFlag,\n\t\t\t\t&baseDomainFlag,\n\t\t\t\t&modelDirFlag,\n\t\t\t\t&clientDirFlag,\n\t\t\t\t&useFlag,\n\t\t\t\t&forceClientDirFlag,\n\t\t\t\t&forceUpdateClientFlag,\n\n\t\t\t\t&includesFlag,\n\t\t\t\t&thriftOptionsFlag,\n\t\t\t\t&protoOptionsFlag,\n\t\t\t\t&noRecurseFlag,\n\t\t\t\t&enableExtendsFlag,\n\t\t\t\t&trimGoPackage,\n\n\t\t\t\t&jsonEnumStrFlag,\n\t\t\t\t&enableClientOptionalFlag,\n\t\t\t\t&queryEnumIntFlag,\n\t\t\t\t&unsetOmitemptyFlag,\n\t\t\t\t&protoCamelJSONTag,\n\t\t\t\t&snakeNameFlag,\n\t\t\t\t&rmTagFlag,\n\t\t\t\t&excludeFilesFlag,\n\t\t\t\t&customPackage,\n\t\t\t\t&protoPluginsFlag,\n\t\t\t\t&thriftPluginsFlag,\n\t\t\t},\n\t\t\tAction: Client,\n\t\t},\n\t}\n\treturn app\n}\n\nfunc setLogVerbose(verbose bool) {\n\tif verbose {\n\t\tlogs.SetLevel(logs.LevelDebug)\n\t} else {\n\t\tlogs.SetLevel(logs.LevelWarn)\n\t}\n}\n\nfunc GenerateLayout(args *config.Argument) error {\n\tlg := &generator.LayoutGenerator{\n\t\tTemplateGenerator: generator.TemplateGenerator{\n\t\t\tOutputDir: args.OutDir,\n\t\t\tExcludes:  args.Excludes,\n\t\t},\n\t}\n\n\tlayout := generator.Layout{\n\t\tGoModule:        args.Gomod,\n\t\tServiceName:     args.ServiceName,\n\t\tUseApacheThrift: args.IdlType == meta.IdlThrift,\n\t\tHasIdl:          0 != len(args.IdlPaths),\n\t\tModelDir:        args.ModelDir,\n\t\tHandlerDir:      args.HandlerDir,\n\t\tRouterDir:       args.RouterDir,\n\t\tNeedGoMod:       args.NeedGoMod,\n\t}\n\n\tif args.CustomizeLayout == \"\" {\n\t\t// generate by default\n\t\terr := lg.GenerateByService(layout)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"generating layout failed: %v\", err)\n\t\t}\n\t} else {\n\t\t// generate by customized layout\n\t\tconfigPath, dataPath := args.CustomizeLayout, args.CustomizeLayoutData\n\t\tlogs.Infof(\"get customized layout info, layout_config_path: %s, template_data_path: %s\", configPath, dataPath)\n\t\texist, err := util.PathExist(configPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"check customized layout config file exist failed: %v\", err)\n\t\t}\n\t\tif !exist {\n\t\t\treturn errors.New(\"layout_config_path doesn't exist\")\n\t\t}\n\t\tlg.ConfigPath = configPath\n\t\t// generate by service info\n\t\tif dataPath == \"\" {\n\t\t\terr := lg.GenerateByService(layout)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"generating layout failed: %v\", err)\n\t\t\t}\n\t\t} else {\n\t\t\t// generate by customized data\n\t\t\terr := lg.GenerateByConfig(dataPath)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"generating layout failed: %v\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\terr := lg.Persist()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"generating layout failed: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc TriggerPlugin(args *config.Argument) error {\n\tif len(args.IdlPaths) == 0 {\n\t\treturn nil\n\t}\n\tcmd, err := config.BuildPluginCmd(args)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"build plugin command failed: %v\", err)\n\t}\n\n\tcompiler, err := config.IdlTypeToCompiler(args.IdlType)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get compiler failed: %v\", err)\n\t}\n\n\tlogs.Debugf(\"begin to trigger plugin, compiler: %s, idl_paths: %v\", compiler, args.IdlPaths)\n\tbuf, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\tout := strings.TrimSpace(string(buf))\n\t\tif !strings.HasSuffix(out, meta.TheUseOptionMessage) {\n\t\t\treturn fmt.Errorf(\"plugin %s_gen_hertz returns error: %v, cause:\\n%v\", compiler, err, string(buf))\n\t\t}\n\t}\n\n\t// If len(buf) != 0, the plugin returned the log.\n\tif len(buf) != 0 {\n\t\tfmt.Println(string(buf))\n\t}\n\tlogs.Debugf(\"end run plugin %s_gen_hertz\", compiler)\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/hz/config/argument.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage config\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n\t\"github.com/urfave/cli/v2\"\n)\n\ntype Argument struct {\n\t// Mode              meta.Mode // operating mode（0-compiler, 1-plugin)\n\tCmdType        string // command type\n\tVerbose        bool   // print verbose log\n\tCwd            string // execution path\n\tOutDir         string // output path\n\tHandlerDir     string // handler path\n\tModelDir       string // model path\n\tRouterDir      string // router path\n\tClientDir      string // client path\n\tBaseDomain     string // request domain\n\tForceClientDir string // client dir (not use namespace as a subpath)\n\n\tIdlType       string   // idl type\n\tIdlPaths      []string // master idl path\n\tRawOptPkg     []string // user-specified package import path\n\tOptPkgMap     map[string]string\n\tIncludes      []string\n\tPkgPrefix     string\n\tTrimGoPackage string // trim go_package for protobuf, avoid to generate multiple directory\n\n\tGopath      string // $GOPATH\n\tGosrc       string // $GOPATH/src\n\tGomod       string\n\tGopkg       string // $GOPATH/src/{{gopkg}}\n\tServiceName string // service name\n\tUse         string\n\tNeedGoMod   bool\n\n\tJSONEnumStr          bool\n\tQueryEnumAsInt       bool\n\tUnsetOmitempty       bool\n\tProtobufCamelJSONTag bool\n\tProtocOptions        []string // options to pass through to protoc\n\tThriftOptions        []string // options to pass through to thriftgo for go flag\n\tProtobufPlugins      []string\n\tThriftPlugins        []string\n\tSnakeName            bool\n\tRmTags               []string\n\tExcludes             []string\n\tNoRecurse            bool\n\tHandlerByMethod      bool\n\tForceNew             bool\n\tForceUpdateClient    bool\n\tSnakeStyleMiddleware bool\n\tEnableExtends        bool\n\tSortRouter           bool\n\n\t// client flag\n\tEnableClientOptional bool\n\n\tCustomizeLayout     string\n\tCustomizeLayoutData string\n\tCustomizePackage    string\n\tModelBackend        string\n}\n\nfunc NewArgument() *Argument {\n\treturn &Argument{\n\t\tOptPkgMap:     make(map[string]string),\n\t\tIncludes:      make([]string, 0, 4),\n\t\tExcludes:      make([]string, 0, 4),\n\t\tProtocOptions: make([]string, 0, 4),\n\t\tThriftOptions: make([]string, 0, 4),\n\t}\n}\n\n// Parse initializes a new argument based on its own information\nfunc (arg *Argument) Parse(c *cli.Context, cmd string) (*Argument, error) {\n\t// v2 cli cannot put the StringSlice flag to struct, so we need to parse it here\n\targ.parseStringSlice(c)\n\targs := arg.Fork()\n\targs.CmdType = cmd\n\n\terr := args.checkPath()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = args.checkIDL()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = args.checkPackage()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn args, nil\n}\n\nfunc (arg *Argument) parseStringSlice(c *cli.Context) {\n\targ.IdlPaths = c.StringSlice(\"idl\")\n\targ.Includes = c.StringSlice(\"proto_path\")\n\targ.Excludes = c.StringSlice(\"exclude_file\")\n\targ.RawOptPkg = c.StringSlice(\"option_package\")\n\targ.ThriftOptions = c.StringSlice(\"thriftgo\")\n\targ.ProtocOptions = c.StringSlice(\"protoc\")\n\targ.ThriftPlugins = c.StringSlice(\"thrift-plugins\")\n\targ.ProtobufPlugins = c.StringSlice(\"protoc-plugins\")\n\targ.RmTags = c.StringSlice(\"rm_tag\")\n}\n\nfunc (arg *Argument) UpdateByManifest(m *meta.Manifest) {\n\tif arg.HandlerDir == \"\" && m.HandlerDir != \"\" {\n\t\tlogs.Infof(\"use \\\"handler_dir\\\" in \\\".hz\\\" as the handler generated dir\\n\")\n\t\targ.HandlerDir = m.HandlerDir\n\t}\n\tif arg.ModelDir == \"\" && m.ModelDir != \"\" {\n\t\tlogs.Infof(\"use \\\"model_dir\\\" in \\\".hz\\\" as the model generated dir\\n\")\n\t\targ.ModelDir = m.ModelDir\n\t}\n\tif len(m.RouterDir) != 0 {\n\t\tlogs.Infof(\"use \\\"router_dir\\\" in \\\".hz\\\" as the router generated dir\\n\")\n\t\targ.RouterDir = m.RouterDir\n\t}\n}\n\n// checkPath sets the project path and verifies that the model、handler、router and client path is compliant\nfunc (arg *Argument) checkPath() error {\n\tdir, err := os.Getwd()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get current path failed: %s\", err)\n\t}\n\targ.Cwd = dir\n\tif arg.OutDir == \"\" {\n\t\targ.OutDir = dir\n\t}\n\tif !filepath.IsAbs(arg.OutDir) {\n\t\tap := filepath.Join(arg.Cwd, arg.OutDir)\n\t\targ.OutDir = ap\n\t}\n\tif arg.ModelDir != \"\" && filepath.IsAbs(arg.ModelDir) {\n\t\treturn fmt.Errorf(\"model path %s must be relative to out_dir\", arg.ModelDir)\n\t}\n\tif arg.HandlerDir != \"\" && filepath.IsAbs(arg.HandlerDir) {\n\t\treturn fmt.Errorf(\"handler path %s must be relative to out_dir\", arg.HandlerDir)\n\t}\n\tif arg.RouterDir != \"\" && filepath.IsAbs(arg.RouterDir) {\n\t\treturn fmt.Errorf(\"router path %s must be relative to out_dir\", arg.RouterDir)\n\t}\n\tif arg.ClientDir != \"\" && filepath.IsAbs(arg.ClientDir) {\n\t\treturn fmt.Errorf(\"router path %s must be relative to out_dir\", arg.ClientDir)\n\t}\n\treturn nil\n}\n\n// checkIDL check if the idl path exists, set and check the idl type\nfunc (arg *Argument) checkIDL() error {\n\tfor i, path := range arg.IdlPaths {\n\t\tabPath, err := filepath.Abs(path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"idl path %s is not absolute\", path)\n\t\t}\n\t\text := filepath.Ext(abPath)\n\t\tif ext == \"\" || ext[0] != '.' {\n\t\t\treturn fmt.Errorf(\"idl path %s is not a valid file\", path)\n\t\t}\n\t\text = ext[1:]\n\t\tswitch ext {\n\t\tcase meta.IdlThrift:\n\t\t\targ.IdlType = meta.IdlThrift\n\t\tcase meta.IdlProto:\n\t\t\targ.IdlType = meta.IdlProto\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"IDL type %s is not supported\", ext)\n\t\t}\n\t\targ.IdlPaths[i] = abPath\n\t}\n\treturn nil\n}\n\nfunc (arg *Argument) IsUpdate() bool {\n\treturn arg.CmdType == meta.CmdUpdate\n}\n\nfunc (arg *Argument) IsNew() bool {\n\treturn arg.CmdType == meta.CmdNew\n}\n\n// checkPackage check and set the gopath、 module and package name\nfunc (arg *Argument) checkPackage() error {\n\tgopath, err := util.GetGOPATH()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get gopath failed: %s\", err)\n\t}\n\tif gopath == \"\" {\n\t\treturn fmt.Errorf(\"GOPATH is not set\")\n\t}\n\n\targ.Gopath = gopath\n\targ.Gosrc = filepath.Join(gopath, \"src\")\n\n\t// Generate the project under gopath, use the relative path as the package name\n\tif strings.HasPrefix(arg.Cwd, arg.Gosrc) {\n\t\tif gopkg, err := filepath.Rel(arg.Gosrc, arg.Cwd); err != nil {\n\t\t\treturn fmt.Errorf(\"get relative path to GOPATH/src failed: %s\", err)\n\t\t} else {\n\t\t\targ.Gopkg = gopkg\n\t\t}\n\t}\n\tif len(arg.Gomod) == 0 { // not specified \"go module\"\n\t\t// search go.mod recursively\n\t\tmodule, path, ok := util.SearchGoMod(arg.Cwd, true)\n\t\tif ok { // find go.mod in upper level, use it as project module, don't generate go.mod\n\t\t\trel, err := filepath.Rel(path, arg.Cwd)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"can not get relative path, err :%v\", err)\n\t\t\t}\n\t\t\targ.Gomod = filepath.Join(module, rel)\n\t\t\tlogs.Debugf(\"find module '%s' from '%s/go.mod', so use it as module name\", module, path)\n\t\t}\n\t\tif len(arg.Gomod) == 0 { // don't find go.mod in upper level, use relative path as module name, generate go.mod\n\t\t\tlogs.Debugf(\"use gopath's relative path '%s' as the module name\", arg.Gopkg)\n\t\t\t// gopkg will be \"\" under non-gopath\n\t\t\targ.Gomod = arg.Gopkg\n\t\t\targ.NeedGoMod = true\n\t\t}\n\t\targ.Gomod = filepath.ToSlash(arg.Gomod)\n\t} else { // specified \"go module\"\n\t\t// search go.mod in current path\n\t\tmodule, path, ok := util.SearchGoMod(arg.Cwd, false)\n\t\tif ok { // go.mod exists in current path, check module name, don't generate go.mod\n\t\t\tif module != arg.Gomod {\n\t\t\t\treturn fmt.Errorf(\"module name given by the '-module/mod' option ('%s') is not consist with the name defined in go.mod ('%s' from %s), try to remove '-module/mod' option in your command\\n\", arg.Gomod, module, path)\n\t\t\t}\n\t\t} else { // go.mod don't exist in current path, generate go.mod\n\t\t\targ.NeedGoMod = true\n\t\t}\n\t}\n\n\tif len(arg.Gomod) == 0 {\n\t\treturn fmt.Errorf(\"can not get go module, please specify a module name with the '-module/mod' flag\")\n\t}\n\n\tif len(arg.RawOptPkg) > 0 {\n\t\targ.OptPkgMap = make(map[string]string, len(arg.RawOptPkg))\n\t\tfor _, op := range arg.RawOptPkg {\n\t\t\tps := strings.SplitN(op, \"=\", 2)\n\t\t\tif len(ps) != 2 {\n\t\t\t\treturn fmt.Errorf(\"invalid option package: %s\", op)\n\t\t\t}\n\t\t\targ.OptPkgMap[ps[0]] = ps[1]\n\t\t}\n\t\targ.RawOptPkg = nil\n\t}\n\treturn nil\n}\n\nfunc (arg *Argument) Pack() ([]string, error) {\n\tdata, err := util.PackArgs(arg)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"pack argument failed: %s\", err)\n\t}\n\treturn data, nil\n}\n\nfunc (arg *Argument) Unpack(data []string) error {\n\terr := util.UnpackArgs(data, arg)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unpack argument failed: %s\", err)\n\t}\n\treturn nil\n}\n\n// Fork can copy its own parameters to a new argument\nfunc (arg *Argument) Fork() *Argument {\n\targs := NewArgument()\n\t*args = *arg\n\tutil.CopyString2StringMap(arg.OptPkgMap, args.OptPkgMap)\n\tutil.CopyStringSlice(&arg.Includes, &args.Includes)\n\tutil.CopyStringSlice(&arg.Excludes, &args.Excludes)\n\tutil.CopyStringSlice(&arg.ProtocOptions, &args.ProtocOptions)\n\tutil.CopyStringSlice(&arg.ThriftOptions, &args.ThriftOptions)\n\treturn args\n}\n\nfunc (arg *Argument) GetGoPackage() (string, error) {\n\tif arg.Gomod != \"\" {\n\t\treturn arg.Gomod, nil\n\t} else if arg.Gopkg != \"\" {\n\t\treturn arg.Gopkg, nil\n\t}\n\treturn \"\", fmt.Errorf(\"project package name is not set\")\n}\n\nfunc IdlTypeToCompiler(idlType string) (string, error) {\n\tswitch idlType {\n\tcase meta.IdlProto:\n\t\treturn meta.TpCompilerProto, nil\n\tcase meta.IdlThrift:\n\t\treturn meta.TpCompilerThrift, nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"IDL type %s is not supported\", idlType)\n\t}\n}\n\nfunc (arg *Argument) ModelPackagePrefix() (string, error) {\n\tret := arg.Gomod\n\tif arg.ModelDir == \"\" {\n\t\tpath, err := util.RelativePath(meta.ModelDir)\n\t\tif !strings.HasPrefix(path, \"/\") {\n\t\t\tpath = \"/\" + path\n\t\t}\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tret += path\n\t} else {\n\t\tpath, err := util.RelativePath(arg.ModelDir)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tret += \"/\" + path\n\t}\n\treturn filepath.ToSlash(ret), nil\n}\n\nfunc (arg *Argument) ModelOutDir() string {\n\tret := arg.OutDir\n\tif arg.ModelDir == \"\" {\n\t\tret = filepath.Join(ret, meta.ModelDir)\n\t} else {\n\t\tret = filepath.Join(ret, arg.ModelDir)\n\t}\n\treturn ret\n}\n\nfunc (arg *Argument) GetHandlerDir() (string, error) {\n\tif arg.HandlerDir == \"\" {\n\t\treturn util.RelativePath(meta.HandlerDir)\n\t}\n\treturn util.RelativePath(arg.HandlerDir)\n}\n\nfunc (arg *Argument) GetModelDir() (string, error) {\n\tif arg.ModelDir == \"\" {\n\t\treturn util.RelativePath(meta.ModelDir)\n\t}\n\treturn util.RelativePath(arg.ModelDir)\n}\n\nfunc (arg *Argument) GetRouterDir() (string, error) {\n\tif arg.RouterDir == \"\" {\n\t\treturn util.RelativePath(meta.RouterDir)\n\t}\n\treturn util.RelativePath(arg.RouterDir)\n}\n\nfunc (arg *Argument) GetClientDir() (string, error) {\n\tif arg.ClientDir == \"\" {\n\t\treturn \"\", nil\n\t}\n\treturn util.RelativePath(arg.ClientDir)\n}\n\nfunc (arg *Argument) InitManifest(m *meta.Manifest) {\n\tm.Version = meta.Version\n\tm.HandlerDir = arg.HandlerDir\n\tm.ModelDir = arg.ModelDir\n\tm.RouterDir = arg.RouterDir\n}\n\nfunc (arg *Argument) UpdateManifest(m *meta.Manifest) {\n\tm.Version = meta.Version\n\tif arg.HandlerDir != m.HandlerDir {\n\t\tm.HandlerDir = arg.HandlerDir\n\t}\n\tif arg.HandlerDir != m.ModelDir {\n\t\tm.ModelDir = arg.ModelDir\n\t}\n\t// \"router_dir\" must not be defined by \"update\" command\n}\n"
  },
  {
    "path": "cmd/hz/config/cmd.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage config\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n)\n\nfunc lookupTool(idlType string) (string, error) {\n\ttool := meta.TpCompilerThrift\n\tif idlType == meta.IdlProto {\n\t\ttool = meta.TpCompilerProto\n\t}\n\n\tpath, err := exec.LookPath(tool)\n\tlogs.Debugf(\"[DEBUG]path:%v\", path)\n\tif err != nil {\n\t\tgoPath, err := util.GetGOPATH()\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"get 'GOPATH' failed for find %s : %v\", tool, path)\n\t\t}\n\t\tpath = filepath.Join(goPath, \"bin\", tool)\n\t}\n\n\tisExist, err := util.PathExist(path)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"check '%s' path error: %v\", path, err)\n\t}\n\n\tif !isExist {\n\t\tif tool == meta.TpCompilerThrift {\n\t\t\t// If thriftgo does not exist, the latest version will be installed automatically.\n\t\t\terr := util.InstallAndCheckThriftgo()\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"can't install '%s' automatically, please install it manually for https://github.com/cloudwego/thriftgo, err : %v\", tool, err)\n\t\t\t}\n\t\t} else {\n\t\t\t// todo: protoc automatic installation\n\t\t\treturn \"\", fmt.Errorf(\"%s is not installed, please install it first\", tool)\n\t\t}\n\t}\n\n\tif tool == meta.TpCompilerThrift {\n\t\t// If thriftgo exists, the version is detected; if the version is lower than v0.2.0 then the latest version of thriftgo is automatically installed.\n\t\terr := util.CheckAndUpdateThriftgo()\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"update thriftgo version failed, please install it manually for https://github.com/cloudwego/thriftgo, err: %v\", err)\n\t\t}\n\t}\n\n\treturn path, nil\n}\n\n// link removes the previous symbol link and rebuilds a new one.\nfunc link(src, dst string) error {\n\terr := syscall.Unlink(dst)\n\tif err != nil && !os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"unlink %q: %s\", dst, err)\n\t}\n\terr = os.Symlink(src, dst)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"symlink %q: %s\", dst, err)\n\t}\n\treturn nil\n}\n\nfunc BuildPluginCmd(args *Argument) (*exec.Cmd, error) {\n\texe, err := os.Executable()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to detect current executable, err: %v\", err)\n\t}\n\n\targPacks, err := args.Pack()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkas := strings.Join(argPacks, \",\")\n\n\tpath, err := lookupTool(args.IdlType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcmd := &exec.Cmd{\n\t\tPath: path,\n\t}\n\n\tif args.IdlType == meta.IdlThrift {\n\t\t// thriftgo\n\t\tos.Setenv(meta.EnvPluginMode, meta.ThriftPluginName)\n\t\tcmd.Args = append(cmd.Args, meta.TpCompilerThrift)\n\t\tfor _, inc := range args.Includes {\n\t\t\tcmd.Args = append(cmd.Args, \"-i\", inc)\n\t\t}\n\n\t\tif args.Verbose {\n\t\t\tcmd.Args = append(cmd.Args, \"-v\")\n\t\t}\n\t\tthriftOpt, err := args.GetThriftgoOptions()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcmd.Args = append(cmd.Args,\n\t\t\t\"-o\", args.ModelOutDir(),\n\t\t\t\"-g\", thriftOpt,\n\t\t\t\"-p\", \"hertz=\"+exe+\":\"+kas,\n\t\t)\n\t\tfor _, p := range args.ThriftPlugins {\n\t\t\tcmd.Args = append(cmd.Args, \"-p\", p)\n\t\t}\n\t\tif !args.NoRecurse {\n\t\t\tcmd.Args = append(cmd.Args, \"-r\")\n\t\t}\n\t} else {\n\t\t// protoc\n\t\tos.Setenv(meta.EnvPluginMode, meta.ProtocPluginName)\n\t\tcmd.Args = append(cmd.Args, meta.TpCompilerProto)\n\t\tfor _, inc := range args.Includes {\n\t\t\tcmd.Args = append(cmd.Args, \"-I\", inc)\n\t\t}\n\t\tfor _, inc := range args.IdlPaths {\n\t\t\tcmd.Args = append(cmd.Args, \"-I\", filepath.Dir(inc))\n\t\t}\n\t\tcmd.Args = append(cmd.Args,\n\t\t\t\"--plugin=protoc-gen-hertz=\"+exe,\n\t\t\t\"--hertz_out=\"+args.OutDir,\n\t\t\t\"--hertz_opt=\"+kas,\n\t\t)\n\t\tfor _, p := range args.ProtobufPlugins {\n\t\t\tpluginParams := strings.Split(p, \":\")\n\t\t\tif len(pluginParams) != 3 {\n\t\t\t\tlogs.Warnf(\"Failed to get the correct protoc plugin parameters for %. \"+\n\t\t\t\t\t\"Please specify the protoc plugin in the form of \\\"plugin_name:options:out_dir\\\"\", p)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\t// pluginParams[0] -> plugin name, pluginParams[1] -> plugin options, pluginParams[2] -> out_dir\n\t\t\tcmd.Args = append(cmd.Args,\n\t\t\t\tfmt.Sprintf(\"--%s_out=%s\", pluginParams[0], pluginParams[2]),\n\t\t\t\tfmt.Sprintf(\"--%s_opt=%s\", pluginParams[0], pluginParams[1]),\n\t\t\t)\n\t\t}\n\t\tfor _, kv := range args.ProtocOptions {\n\t\t\tcmd.Args = append(cmd.Args, \"--\"+kv)\n\t\t}\n\t}\n\n\tcmd.Args = append(cmd.Args, args.IdlPaths...)\n\tlogs.Infof(strings.Join(cmd.Args, \" \"))\n\tlogs.Flush()\n\treturn cmd, nil\n}\n\nfunc (arg *Argument) GetThriftgoOptions() (string, error) {\n\tdefaultOpt := \"reserve_comments,gen_json_tag=false,\"\n\tprefix, err := arg.ModelPackagePrefix()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\targ.ThriftOptions = append(arg.ThriftOptions, \"package_prefix=\"+prefix)\n\tif arg.JSONEnumStr {\n\t\targ.ThriftOptions = append(arg.ThriftOptions, \"json_enum_as_text\")\n\t}\n\tgas := \"go:\" + defaultOpt + strings.Join(arg.ThriftOptions, \",\")\n\treturn gas, nil\n}\n"
  },
  {
    "path": "cmd/hz/doc.go",
    "content": "// Copyright 2022 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n// Package \"github.com/cloudwego/hertz/cmd/hz\" contains packages for building the hz command line tool.\n// APIs exported by packages under this directory do not promise any backward\n// compatibility, so please do not rely on them.\n\npackage main\n"
  },
  {
    "path": "cmd/hz/generator/client.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n)\n\ntype ClientMethod struct {\n\t*HttpMethod\n\tBodyParamsCode   string\n\tQueryParamsCode  string\n\tPathParamsCode   string\n\tHeaderParamsCode string\n\tFormValueCode    string\n\tFormFileCode     string\n}\n\ntype ClientConfig struct {\n\tQueryEnumAsInt bool\n}\n\ntype ClientFile struct {\n\tConfig        ClientConfig\n\tFilePath      string\n\tPackageName   string\n\tServiceName   string\n\tBaseDomain    string\n\tImports       map[string]*model.Model\n\tClientMethods []*ClientMethod\n}\n\nfunc (pkgGen *HttpPackageGenerator) genClient(pkg *HttpPackage, clientDir string) error {\n\tfor _, s := range pkg.Services {\n\t\tcliDir := util.SubDir(clientDir, util.ToSnakeCase(s.Name))\n\t\tif len(pkgGen.ForceClientDir) != 0 {\n\t\t\tcliDir = pkgGen.ForceClientDir\n\t\t}\n\t\thertzClientPath := filepath.Join(cliDir, hertzClientTplName)\n\t\tisExist, err := util.PathExist(hertzClientPath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbaseDomain := s.BaseDomain\n\t\tif len(pkgGen.BaseDomain) != 0 {\n\t\t\tbaseDomain = pkgGen.BaseDomain\n\t\t}\n\t\tclient := ClientFile{\n\t\t\tFilePath:      filepath.Join(cliDir, util.ToSnakeCase(s.Name)+\".go\"),\n\t\t\tPackageName:   util.ToSnakeCase(filepath.Base(cliDir)),\n\t\t\tServiceName:   util.ToCamelCase(s.Name),\n\t\t\tClientMethods: s.ClientMethods,\n\t\t\tBaseDomain:    baseDomain,\n\t\t\tConfig:        ClientConfig{QueryEnumAsInt: pkgGen.QueryEnumAsInt},\n\t\t}\n\t\tif !isExist || pkgGen.ForceUpdateClient {\n\t\t\terr := pkgGen.TemplateGenerator.Generate(client, hertzClientTplName, hertzClientPath, false)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tclient.Imports = make(map[string]*model.Model, len(client.ClientMethods))\n\t\tfor _, m := range client.ClientMethods {\n\t\t\t// Iterate over the request and return parameters of the method to get import path.\n\t\t\tfor key, mm := range m.Models {\n\t\t\t\tif v, ok := client.Imports[mm.PackageName]; ok && v.Package != mm.Package {\n\t\t\t\t\tclient.Imports[key] = mm\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tclient.Imports[mm.PackageName] = mm\n\t\t\t}\n\t\t}\n\t\tif len(pkgGen.UseDir) != 0 {\n\t\t\toldModelPkg := util.SubPackage(pkgGen.ProjPackage, filepath.Clean(pkgGen.ModelDir))\n\t\t\tnewModelPkg := path.Clean(pkgGen.UseDir)\n\t\t\tfor _, m := range client.ClientMethods {\n\t\t\t\tfor _, mm := range m.Models {\n\t\t\t\t\tmm.Package = strings.Replace(mm.Package, oldModelPkg, newModelPkg, 1)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\terr = pkgGen.TemplateGenerator.Generate(client, idlClientName, client.FilePath, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/hz/generator/custom_files.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n)\n\ntype FilePathRenderInfo struct {\n\tMasterIDLName  string // master IDL name\n\tGenPackage     string // master IDL generate code package\n\tHandlerDir     string // handler generate dir\n\tModelDir       string // model generate dir\n\tRouterDir      string // router generate dir\n\tProjectDir     string // projectDir\n\tGoModule       string // go module\n\tServiceName    string // service name, changed as services are traversed\n\tMethodName     string // method name, changed as methods are traversed\n\tHandlerGenPath string // \"api.gen_path\" value\n}\n\ntype IDLPackageRenderInfo struct {\n\tFilePathRenderInfo\n\tServiceInfos *HttpPackage\n}\n\ntype CustomizedFileForMethod struct {\n\t*HttpMethod\n\tFilePath       string\n\tFilePackage    string\n\tServiceInfo    *Service              // service info for this method\n\tIDLPackageInfo *IDLPackageRenderInfo // IDL info for this service\n}\n\ntype CustomizedFileForService struct {\n\t*Service\n\tFilePath       string\n\tFilePackage    string\n\tIDLPackageInfo *IDLPackageRenderInfo // IDL info for this service\n}\n\ntype CustomizedFileForIDL struct {\n\t*IDLPackageRenderInfo\n\tFilePath    string\n\tFilePackage string\n}\n\n// todo: 1. how to import other file, if the other file name is a template\n\n// genCustomizedFile generate customized file template\nfunc (pkgGen *HttpPackageGenerator) genCustomizedFile(pkg *HttpPackage) error {\n\tfilePathRenderInfo := FilePathRenderInfo{\n\t\tMasterIDLName: pkg.IdlName,\n\t\tGenPackage:    pkg.Package,\n\t\tHandlerDir:    pkgGen.HandlerDir,\n\t\tModelDir:      pkgGen.ModelDir,\n\t\tRouterDir:     pkgGen.RouterDir,\n\t\tProjectDir:    pkgGen.OutputDir,\n\t\tGoModule:      pkgGen.ProjPackage,\n\t\t// methodName & serviceName will change as traverse\n\t}\n\n\tidlPackageRenderInfo := IDLPackageRenderInfo{\n\t\tFilePathRenderInfo: filePathRenderInfo,\n\t\tServiceInfos:       pkg,\n\t}\n\n\tfor _, tplInfo := range pkgGen.tplsInfo {\n\t\t// the default template has been automatically generated by the tool, so skip\n\t\tif tplInfo.Default {\n\t\t\tcontinue\n\t\t}\n\n\t\t// loop generate file\n\t\tif tplInfo.LoopService || tplInfo.LoopMethod {\n\t\t\tloopMethod := tplInfo.LoopMethod\n\t\t\tloopService := tplInfo.LoopService\n\t\t\tif loopService && !loopMethod { // only loop service\n\t\t\t\tfor _, service := range idlPackageRenderInfo.ServiceInfos.Services {\n\t\t\t\t\tfilePathRenderInfo.ServiceName = service.Name\n\t\t\t\t\terr := pkgGen.genLoopService(tplInfo, filePathRenderInfo, service, &idlPackageRenderInfo)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else { // loop service & method, because if loop method, the service must be looped\n\t\t\t\tfor _, service := range idlPackageRenderInfo.ServiceInfos.Services {\n\t\t\t\t\tfor _, method := range service.Methods {\n\t\t\t\t\t\tfilePathRenderInfo.ServiceName = service.Name\n\t\t\t\t\t\tfilePathRenderInfo.MethodName = method.Name\n\t\t\t\t\t\tfilePathRenderInfo.HandlerGenPath = method.OutputDir\n\t\t\t\t\t\terr := pkgGen.genLoopMethod(tplInfo, filePathRenderInfo, method, service, &idlPackageRenderInfo)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else { // generate customized file single\n\t\t\terr := pkgGen.genSingleCustomizedFile(tplInfo, filePathRenderInfo, idlPackageRenderInfo)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// renderFilePath used to render file path template to get real file path\nfunc renderFilePath(tplInfo *Template, filePathRenderInfo FilePathRenderInfo) (string, error) {\n\ttpl, err := template.New(tplInfo.Path).Funcs(funcMap).Parse(tplInfo.Path)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"parse file path template(%s) failed, err: %v\", tplInfo.Path, err)\n\t}\n\tfilePath := bytes.NewBuffer(nil)\n\terr = tpl.Execute(filePath, filePathRenderInfo)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"render file path template(%s) failed, err: %v\", tplInfo.Path, err)\n\t}\n\n\treturn filePath.String(), nil\n}\n\nfunc renderInsertKey(tplInfo *Template, data interface{}) (string, error) {\n\ttpl, err := template.New(tplInfo.UpdateBehavior.InsertKey).Funcs(funcMap).Parse(tplInfo.UpdateBehavior.InsertKey)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"parse insert key template(%s) failed, err: %v\", tplInfo.UpdateBehavior.InsertKey, err)\n\t}\n\tinsertKey := bytes.NewBuffer(nil)\n\terr = tpl.Execute(insertKey, data)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"render insert key template(%s) failed, err: %v\", tplInfo.UpdateBehavior.InsertKey, err)\n\t}\n\n\treturn insertKey.String(), nil\n}\n\n// renderImportTpl will render import template\n// it will return the []string, like blow:\n// [\"import\", alias \"import\", import]\n// other format will be error\nfunc renderImportTpl(tplInfo *Template, data interface{}) ([]string, error) {\n\tvar importList []string\n\tfor _, impt := range tplInfo.UpdateBehavior.ImportTpl {\n\t\ttpl, err := template.New(impt).Funcs(funcMap).Parse(impt)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parse import template(%s) failed, err: %v\", impt, err)\n\t\t}\n\t\timptContent := bytes.NewBuffer(nil)\n\t\terr = tpl.Execute(imptContent, data)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"render import template(%s) failed, err: %v\", impt, err)\n\t\t}\n\t\timportList = append(importList, imptContent.String())\n\t}\n\tvar ret []string\n\tfor _, impts := range importList {\n\t\t// 'import render result' may have multiple imports\n\t\tif strings.Contains(impts, \"\\n\") {\n\t\t\tfor _, impt := range strings.Split(impts, \"\\n\") {\n\t\t\t\tret = append(ret, impt)\n\t\t\t}\n\t\t} else {\n\t\t\tret = append(ret, impts)\n\t\t}\n\t}\n\n\treturn ret, nil\n}\n\n// renderAppendContent used to render append content for 'update' command\nfunc renderAppendContent(tplInfo *Template, renderInfo interface{}) (string, error) {\n\ttpl, err := template.New(tplInfo.Path).Funcs(funcMap).Parse(tplInfo.UpdateBehavior.AppendTpl)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"parse append content template(%s) failed, err: %v\", tplInfo.Path, err)\n\t}\n\tappendContent := bytes.NewBuffer(nil)\n\terr = tpl.Execute(appendContent, renderInfo)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"render append content template(%s) failed, err: %v\", tplInfo.Path, err)\n\t}\n\n\treturn appendContent.String(), nil\n}\n\n// appendUpdateFile used to append content to file for 'update' command\nfunc appendUpdateFile(tplInfo *Template, renderInfo interface{}, fileContent []byte) ([]byte, error) {\n\t// render insert content\n\tappendContent, err := renderAppendContent(tplInfo, renderInfo)\n\tif err != nil {\n\t\treturn []byte(\"\"), err\n\t}\n\tbuf := bytes.NewBuffer(nil)\n\t_, err = buf.Write(fileContent)\n\tif err != nil {\n\t\treturn []byte(\"\"), fmt.Errorf(\"write file(%s) failed, err: %v\", tplInfo.Path, err)\n\t}\n\t// \"\\r\\n\" && \"\\n\" has the same suffix\n\tif !bytes.HasSuffix(buf.Bytes(), []byte(\"\\n\")) {\n\t\t_, err = buf.WriteString(\"\\n\")\n\t\tif err != nil {\n\t\t\treturn []byte(\"\"), fmt.Errorf(\"write file(%s) line break failed, err: %v\", tplInfo.Path, err)\n\t\t}\n\t}\n\t_, err = buf.WriteString(appendContent)\n\tif err != nil {\n\t\treturn []byte(\"\"), fmt.Errorf(\"append file(%s) failed, err: %v\", tplInfo.Path, err)\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\nfunc getInsertImportContent(tplInfo *Template, renderInfo interface{}, fileContent []byte) ([][2]string, error) {\n\timportContent, err := renderImportTpl(tplInfo, renderInfo)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar imptSlice [][2]string\n\tfor _, impt := range importContent {\n\t\t// import has to format\n\t\t// 1. alias \"import\"\n\t\t// 2. \"import\"\n\t\t// 3. import (can not contain '\"')\n\t\timpt = strings.TrimSpace(impt)\n\t\tif !strings.Contains(impt, \"\\\"\") { // 3. import (can not contain '\"')\n\t\t\tif bytes.Contains(fileContent, []byte(impt)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\timptSlice = append(imptSlice, [2]string{\"\", impt})\n\t\t} else {\n\t\t\tif !strings.HasSuffix(impt, \"\\\"\") {\n\t\t\t\treturn nil, fmt.Errorf(\"import can not has suffix \\\"\\\"\\\", for file: %s\", tplInfo.Path)\n\t\t\t}\n\t\t\tif strings.HasPrefix(impt, \"\\\"\") { // 2. \"import\"\n\t\t\t\tif bytes.Contains(fileContent, []byte(impt[1:len(impt)-1])) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\timptSlice = append(imptSlice, [2]string{\"\", impt[1 : len(impt)-1]})\n\t\t\t} else { // 3. alias \"import\"\n\t\t\t\tidx := strings.Index(impt, \"\\\"\")\n\t\t\t\tif idx == -1 {\n\t\t\t\t\treturn nil, fmt.Errorf(\"error import format for file: %s\", tplInfo.Path)\n\t\t\t\t}\n\t\t\t\tif bytes.Contains(fileContent, []byte(impt[idx+1:len(impt)-1])) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\timptSlice = append(imptSlice, [2]string{impt[:idx], impt[idx+1 : len(impt)-1]})\n\t\t\t}\n\t\t}\n\t}\n\n\treturn imptSlice, nil\n}\n\n// genLoopService used to generate files by 'service'\nfunc (pkgGen *HttpPackageGenerator) genLoopService(tplInfo *Template, filePathRenderInfo FilePathRenderInfo, service *Service, idlPackageRenderInfo *IDLPackageRenderInfo) error {\n\tfilePath, err := renderFilePath(tplInfo, filePathRenderInfo)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// determine if a custom file exists\n\texist, err := util.PathExist(filePath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"judge file(%s) exists failed, err: %v\", filePath, err)\n\t}\n\tif !exist { // create file\n\t\tdata := CustomizedFileForService{\n\t\t\tService:        service,\n\t\t\tFilePath:       filePath,\n\t\t\tFilePackage:    util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t\tIDLPackageInfo: idlPackageRenderInfo,\n\t\t}\n\t\terr = pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tswitch tplInfo.UpdateBehavior.Type {\n\t\tcase Skip:\n\t\t\t// do nothing\n\t\t\tlogs.Infof(\"do not update file '%s', because the update behavior is 'Unchanged'\", filePath)\n\t\tcase Cover:\n\t\t\t// re-generate\n\t\t\tlogs.Infof(\"re-generate file '%s', because the update behavior is 'Regenerate'\", filePath)\n\t\t\tdata := CustomizedFileForService{\n\t\t\t\tService:        service,\n\t\t\t\tFilePath:       filePath,\n\t\t\t\tFilePackage:    util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t\t\tIDLPackageInfo: idlPackageRenderInfo,\n\t\t\t}\n\t\t\terr := pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase Append: // todo: append logic need to be optimized for method\n\t\t\tfileContent, err := ioutil.ReadFile(filePath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvar appendContent []byte\n\t\t\t// get insert content\n\t\t\tif tplInfo.UpdateBehavior.AppendKey == \"method\" {\n\t\t\t\tfor _, method := range service.Methods {\n\t\t\t\t\tdata := CustomizedFileForMethod{\n\t\t\t\t\t\tHttpMethod:     method,\n\t\t\t\t\t\tFilePath:       filePath,\n\t\t\t\t\t\tFilePackage:    util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t\t\t\t\tServiceInfo:    service,\n\t\t\t\t\t\tIDLPackageInfo: idlPackageRenderInfo,\n\t\t\t\t\t}\n\t\t\t\t\tinsertKey, err := renderInsertKey(tplInfo, data)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tif bytes.Contains(fileContent, []byte(insertKey)) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\timptSlice, err := getInsertImportContent(tplInfo, data, fileContent)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\t// insert new import to the fileContent\n\t\t\t\t\tfor _, impt := range imptSlice {\n\t\t\t\t\t\tif bytes.Contains(fileContent, []byte(impt[1])) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfileContent, err = util.AddImportForContent(fileContent, impt[0], impt[1])\n\t\t\t\t\t\t// insert import error do not influence the generated file\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlogs.Warnf(\"can not add import(%s) for file(%s), err: %v\\n\", impt[1], filePath, err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tappendContent, err = appendUpdateFile(tplInfo, data, appendContent)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(tplInfo.UpdateBehavior.AppendLocation) == 0 { // default, append to end of file\n\t\t\t\t\tbuf := bytes.NewBuffer(nil)\n\t\t\t\t\t_, err = buf.Write(fileContent)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"write file(%s) failed, err: %v\", tplInfo.Path, err)\n\t\t\t\t\t}\n\t\t\t\t\t_, err = buf.Write(appendContent)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"append file(%s) failed, err: %v\", tplInfo.Path, err)\n\t\t\t\t\t}\n\t\t\t\t\tlogs.Infof(\"append content for file '%s', because the update behavior is 'Append' and appendKey is 'method'\", filePath)\n\t\t\t\t\tpkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, \"\"})\n\t\t\t\t} else { // 'append location', append new content after 'append location'\n\t\t\t\t\tpart := bytes.Split(fileContent, []byte(tplInfo.UpdateBehavior.AppendLocation))\n\t\t\t\t\tif len(part) == 0 {\n\t\t\t\t\t\treturn fmt.Errorf(\"can not find append location '%s' for file '%s'\\n\", tplInfo.UpdateBehavior.AppendLocation, filePath)\n\t\t\t\t\t}\n\t\t\t\t\tif len(part) != 2 {\n\t\t\t\t\t\treturn fmt.Errorf(\"do not support multiple append location '%s' for file '%s'\\n\", tplInfo.UpdateBehavior.AppendLocation, filePath)\n\t\t\t\t\t}\n\t\t\t\t\tbuf := bytes.NewBuffer(nil)\n\t\t\t\t\terr = writeBytes(buf, part[0], []byte(tplInfo.UpdateBehavior.AppendLocation), appendContent, part[1])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"write file(%s) failed, err: %v\", tplInfo.Path, err)\n\t\t\t\t\t}\n\t\t\t\t\tlogs.Infof(\"append content for file '%s', because the update behavior is 'Append' and appendKey is 'method'\", filePath)\n\t\t\t\t\tpkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, \"\"})\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlogs.Warnf(\"Loop 'service' field for '%s' only append content by appendKey for 'method', so cannot append content\", filePath)\n\t\t\t}\n\t\tdefault:\n\t\t\t// do nothing\n\t\t\tlogs.Warnf(\"unknown update behavior, do nothing\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// genLoopMethod used to generate files by 'method'\nfunc (pkgGen *HttpPackageGenerator) genLoopMethod(tplInfo *Template, filePathRenderInfo FilePathRenderInfo, method *HttpMethod, service *Service, idlPackageRenderInfo *IDLPackageRenderInfo) error {\n\tfilePath, err := renderFilePath(tplInfo, filePathRenderInfo)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// determine if a custom file exists\n\texist, err := util.PathExist(filePath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"judge file(%s) exists failed, err: %v\", filePath, err)\n\t}\n\n\tif !exist { // create file\n\t\tdata := CustomizedFileForMethod{\n\t\t\tHttpMethod:     method,\n\t\t\tFilePath:       filePath,\n\t\t\tFilePackage:    util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t\tServiceInfo:    service,\n\t\t\tIDLPackageInfo: idlPackageRenderInfo,\n\t\t}\n\t\terr := pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tswitch tplInfo.UpdateBehavior.Type {\n\t\tcase Skip:\n\t\t\t// do nothing\n\t\t\tlogs.Infof(\"do not update file '%s', because the update behavior is 'Unchanged'\", filePath)\n\t\tcase Cover:\n\t\t\t// re-generate\n\t\t\tlogs.Infof(\"re-generate file '%s', because the update behavior is 'Regenerate'\", filePath)\n\t\t\tdata := CustomizedFileForMethod{\n\t\t\t\tHttpMethod:     method,\n\t\t\t\tFilePath:       filePath,\n\t\t\t\tFilePackage:    util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t\t\tServiceInfo:    service,\n\t\t\t\tIDLPackageInfo: idlPackageRenderInfo,\n\t\t\t}\n\t\t\terr := pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase Append:\n\t\t\t// for loop method, no need to append something; so do nothing\n\t\t\tlogs.Warnf(\"do not append content for file '%s', because the update behavior is 'Append' and loop 'method' have no need to append content\", filePath)\n\t\tdefault:\n\t\t\t// do nothing\n\t\t\tlogs.Warnf(\"unknown update behavior, do nothing\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// genSingleCustomizedFile used to generate file by 'master IDL'\nfunc (pkgGen *HttpPackageGenerator) genSingleCustomizedFile(tplInfo *Template, filePathRenderInfo FilePathRenderInfo, idlPackageRenderInfo IDLPackageRenderInfo) error {\n\t// generate file single\n\tfilePath, err := renderFilePath(tplInfo, filePathRenderInfo)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// determine if a custom file exists\n\texist, err := util.PathExist(filePath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"judge file(%s) exists failed, err: %v\", filePath, err)\n\t}\n\n\tif !exist { // create file\n\t\tdata := CustomizedFileForIDL{\n\t\t\tIDLPackageRenderInfo: &idlPackageRenderInfo,\n\t\t\tFilePath:             filePath,\n\t\t\tFilePackage:          util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t}\n\t\terr := pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tswitch tplInfo.UpdateBehavior.Type {\n\t\tcase Skip:\n\t\t\t// do nothing\n\t\t\tlogs.Infof(\"do not update file '%s', because the update behavior is 'Unchanged'\", filePath)\n\t\tcase Cover:\n\t\t\t// re-generate\n\t\t\tlogs.Infof(\"re-generate file '%s', because the update behavior is 'Regenerate'\", filePath)\n\t\t\tdata := CustomizedFileForIDL{\n\t\t\t\tIDLPackageRenderInfo: &idlPackageRenderInfo,\n\t\t\t\tFilePath:             filePath,\n\t\t\t\tFilePackage:          util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t\t}\n\t\t\terr := pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase Append: // todo: append logic need to be optimized for single file\n\t\t\tfileContent, err := ioutil.ReadFile(filePath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif tplInfo.UpdateBehavior.AppendKey == \"method\" {\n\t\t\t\tvar appendContent []byte\n\t\t\t\tfor _, service := range idlPackageRenderInfo.ServiceInfos.Services {\n\t\t\t\t\tfor _, method := range service.Methods {\n\t\t\t\t\t\tdata := CustomizedFileForMethod{\n\t\t\t\t\t\t\tHttpMethod:     method,\n\t\t\t\t\t\t\tFilePath:       filePath,\n\t\t\t\t\t\t\tFilePackage:    util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t\t\t\t\t\tServiceInfo:    service,\n\t\t\t\t\t\t\tIDLPackageInfo: &idlPackageRenderInfo,\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinsertKey, err := renderInsertKey(tplInfo, data)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif bytes.Contains(fileContent, []byte(insertKey)) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\timptSlice, err := getInsertImportContent(tplInfo, data, fileContent)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, impt := range imptSlice {\n\t\t\t\t\t\t\tif bytes.Contains(fileContent, []byte(impt[1])) {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfileContent, err = util.AddImportForContent(fileContent, impt[0], impt[1])\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tlogs.Warnf(\"can not add import(%s) for file(%s)\\n\", impt[1], filePath)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tappendContent, err = appendUpdateFile(tplInfo, data, appendContent)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(tplInfo.UpdateBehavior.AppendLocation) == 0 { // default, append to end of file\n\t\t\t\t\tbuf := bytes.NewBuffer(nil)\n\t\t\t\t\t_, err = buf.Write(fileContent)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"write file(%s) failed, err: %v\", tplInfo.Path, err)\n\t\t\t\t\t}\n\t\t\t\t\t_, err = buf.Write(appendContent)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"append file(%s) failed, err: %v\", tplInfo.Path, err)\n\t\t\t\t\t}\n\t\t\t\t\tlogs.Infof(\"append content for file '%s', because the update behavior is 'Append' and appendKey is 'method'\", filePath)\n\t\t\t\t\tpkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, \"\"})\n\t\t\t\t} else { // 'append location', append new content after 'append location'\n\t\t\t\t\tpart := bytes.Split(fileContent, []byte(tplInfo.UpdateBehavior.AppendLocation))\n\t\t\t\t\tif len(part) == 0 {\n\t\t\t\t\t\treturn fmt.Errorf(\"can not find append location '%s' for file '%s'\\n\", tplInfo.UpdateBehavior.AppendLocation, filePath)\n\t\t\t\t\t}\n\t\t\t\t\tif len(part) != 2 {\n\t\t\t\t\t\treturn fmt.Errorf(\"do not support multiple append location '%s' for file '%s'\\n\", tplInfo.UpdateBehavior.AppendLocation, filePath)\n\t\t\t\t\t}\n\t\t\t\t\tbuf := bytes.NewBuffer(nil)\n\t\t\t\t\terr = writeBytes(buf, part[0], []byte(tplInfo.UpdateBehavior.AppendLocation), appendContent, part[1])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"write file(%s) failed, err: %v\", tplInfo.Path, err)\n\t\t\t\t\t}\n\t\t\t\t\tlogs.Infof(\"append content for file '%s', because the update behavior is 'Append' and appendKey is 'method'\", filePath)\n\t\t\t\t\tpkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, \"\"})\n\t\t\t\t}\n\t\t\t} else if tplInfo.UpdateBehavior.AppendKey == \"service\" {\n\t\t\t\tvar appendContent []byte\n\t\t\t\tfor _, service := range idlPackageRenderInfo.ServiceInfos.Services {\n\t\t\t\t\tdata := CustomizedFileForService{\n\t\t\t\t\t\tService:        service,\n\t\t\t\t\t\tFilePath:       filePath,\n\t\t\t\t\t\tFilePackage:    util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t\t\t\t\tIDLPackageInfo: &idlPackageRenderInfo,\n\t\t\t\t\t}\n\t\t\t\t\tinsertKey, err := renderInsertKey(tplInfo, data)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tif bytes.Contains(fileContent, []byte(insertKey)) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\timptSlice, err := getInsertImportContent(tplInfo, data, fileContent)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tfor _, impt := range imptSlice {\n\t\t\t\t\t\tif bytes.Contains(fileContent, []byte(impt[1])) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfileContent, err = util.AddImportForContent(fileContent, impt[0], impt[1])\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlogs.Warnf(\"can not add import(%s) for file(%s)\\n\", impt[1], filePath)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tappendContent, err = appendUpdateFile(tplInfo, data, appendContent)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(tplInfo.UpdateBehavior.AppendLocation) == 0 { // default, append to end of file\n\t\t\t\t\tbuf := bytes.NewBuffer(nil)\n\t\t\t\t\t_, err = buf.Write(fileContent)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"write file(%s) failed, err: %v\", tplInfo.Path, err)\n\t\t\t\t\t}\n\t\t\t\t\t_, err = buf.Write(appendContent)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"append file(%s) failed, err: %v\", tplInfo.Path, err)\n\t\t\t\t\t}\n\t\t\t\t\tlogs.Infof(\"append content for file '%s', because the update behavior is 'Append' and appendKey is 'service'\", filePath)\n\t\t\t\t\tpkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, \"\"})\n\t\t\t\t} else { // 'append location', append new content after 'append location'\n\t\t\t\t\tpart := bytes.Split(fileContent, []byte(tplInfo.UpdateBehavior.AppendLocation))\n\t\t\t\t\tif len(part) == 0 {\n\t\t\t\t\t\treturn fmt.Errorf(\"can not find append location '%s' for file '%s'\\n\", tplInfo.UpdateBehavior.AppendLocation, filePath)\n\t\t\t\t\t}\n\t\t\t\t\tif len(part) != 2 {\n\t\t\t\t\t\treturn fmt.Errorf(\"do not support multiple append location '%s' for file '%s'\\n\", tplInfo.UpdateBehavior.AppendLocation, filePath)\n\t\t\t\t\t}\n\t\t\t\t\tbuf := bytes.NewBuffer(nil)\n\t\t\t\t\terr = writeBytes(buf, part[0], []byte(tplInfo.UpdateBehavior.AppendLocation), appendContent, part[1])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"write file(%s) failed, err: %v\", tplInfo.Path, err)\n\t\t\t\t\t}\n\t\t\t\t\tlogs.Infof(\"append content for file '%s', because the update behavior is 'Append' and appendKey is 'service'\", filePath)\n\t\t\t\t\tpkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, \"\"})\n\t\t\t\t}\n\t\t\t} else { // add append content to the file directly\n\t\t\t\tdata := CustomizedFileForIDL{\n\t\t\t\t\tIDLPackageRenderInfo: &idlPackageRenderInfo,\n\t\t\t\t\tFilePath:             filePath,\n\t\t\t\t\tFilePackage:          util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t\t\t}\n\t\t\t\tfile, err := appendUpdateFile(tplInfo, data, fileContent)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tpkgGen.files = append(pkgGen.files, File{filePath, string(file), false, \"\"})\n\t\t\t}\n\t\tdefault:\n\t\t\t// do nothing\n\t\t\tlogs.Warnf(\"unknown update behavior, do nothing\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc writeBytes(buf *bytes.Buffer, bytes ...[]byte) error {\n\tfor _, b := range bytes {\n\t\t_, err := buf.Write(b)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/hz/generator/file.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"fmt\"\n\t\"go/format\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n)\n\ntype File struct {\n\tPath        string\n\tContent     string\n\tNoRepeat    bool\n\tFileTplName string\n}\n\n// Lint is used to statically analyze and format go code\nfunc (file *File) Lint() error {\n\tname := filepath.Base(file.Path)\n\tif strings.HasSuffix(name, \".go\") {\n\t\tout, err := format.Source(util.Str2Bytes(file.Content))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"lint file '%s' failed, err: %v\", name, err.Error())\n\t\t}\n\t\tfile.Content = util.Bytes2Str(out)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/hz/generator/handler.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n)\n\ntype HttpMethod struct {\n\tName               string\n\tHTTPMethod         string\n\tComment            string\n\tRequestTypeName    string\n\tRequestTypePackage string\n\tRequestTypeRawName string\n\tReturnTypeName     string\n\tReturnTypePackage  string\n\tReturnTypeRawName  string\n\tPath               string\n\tSerializer         string\n\tOutputDir          string\n\tRefPackage         string // handler import dir\n\tRefPackageAlias    string // handler import alias\n\tModelPackage       map[string]string\n\tGenHandler         bool // Whether to generate one handler, when an idl interface corresponds to multiple http method\n\t// Annotations     map[string]string\n\tModels map[string]*model.Model\n}\n\ntype Handler struct {\n\tFilePath    string\n\tPackageName string\n\tProjPackage string\n\tImports     map[string]*model.Model\n\tMethods     []*HttpMethod\n}\n\ntype SingleHandler struct {\n\t*HttpMethod\n\tFilePath    string\n\tPackageName string\n\tProjPackage string\n}\n\ntype Client struct {\n\tHandler\n\tServiceName string\n}\n\nfunc (pkgGen *HttpPackageGenerator) genHandler(pkg *HttpPackage, handlerDir, handlerPackage string, root *RouterNode) error {\n\tfor _, s := range pkg.Services {\n\t\tvar handler Handler\n\t\tif pkgGen.HandlerByMethod { // generate handler by method\n\t\t\tfor _, m := range s.Methods {\n\t\t\t\tfilePath := filepath.Join(handlerDir, m.OutputDir, util.ToSnakeCase(m.Name)+\".go\")\n\t\t\t\thandler = Handler{\n\t\t\t\t\tFilePath:    filePath,\n\t\t\t\t\tPackageName: util.SplitPackage(filepath.Dir(filePath), \"\"),\n\t\t\t\t\tMethods:     []*HttpMethod{m},\n\t\t\t\t\tProjPackage: pkgGen.ProjPackage,\n\t\t\t\t}\n\n\t\t\t\tif err := pkgGen.processHandler(&handler, root, handlerDir, m.OutputDir, true); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"generate handler %s failed, err: %v\", handler.FilePath, err.Error())\n\t\t\t\t}\n\n\t\t\t\tif m.GenHandler {\n\t\t\t\t\tif err := pkgGen.updateHandler(handler, handlerTplName, handler.FilePath, false); err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"generate handler %s failed, err: %v\", handler.FilePath, err.Error())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else { // generate handler service\n\t\t\ttmpHandlerDir := handlerDir\n\t\t\ttmpHandlerPackage := handlerPackage\n\t\t\tif len(s.ServiceGenDir) != 0 {\n\t\t\t\ttmpHandlerDir = s.ServiceGenDir\n\t\t\t\ttmpHandlerPackage = util.SubPackage(pkgGen.ProjPackage, strings.TrimPrefix(tmpHandlerDir, \"/\"))\n\t\t\t}\n\t\t\thandler = Handler{\n\t\t\t\tFilePath:    filepath.Join(tmpHandlerDir, util.ToSnakeCase(s.Name)+\".go\"),\n\t\t\t\tPackageName: util.SplitPackage(tmpHandlerPackage, \"\"),\n\t\t\t\tMethods:     s.Methods,\n\t\t\t\tProjPackage: pkgGen.ProjPackage,\n\t\t\t}\n\n\t\t\tfor _, m := range s.Methods {\n\t\t\t\tm.RefPackage = tmpHandlerPackage\n\t\t\t\tm.RefPackageAlias = util.BaseName(tmpHandlerPackage, \"\")\n\t\t\t}\n\n\t\t\tif err := pkgGen.processHandler(&handler, root, \"\", \"\", false); err != nil {\n\t\t\t\treturn fmt.Errorf(\"generate handler %s failed, err: %v\", handler.FilePath, err.Error())\n\t\t\t}\n\n\t\t\t// Avoid generating duplicate handlers when IDL interface corresponds to multiple http methods\n\t\t\tmethods := handler.Methods\n\t\t\thandler.Methods = []*HttpMethod{}\n\t\t\tfor _, m := range methods {\n\t\t\t\tif m.GenHandler {\n\t\t\t\t\thandler.Methods = append(handler.Methods, m)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err := pkgGen.updateHandler(handler, handlerTplName, handler.FilePath, false); err != nil {\n\t\t\t\treturn fmt.Errorf(\"generate handler %s failed, err: %v\", handler.FilePath, err.Error())\n\t\t\t}\n\t\t}\n\n\t\tif len(pkgGen.ClientDir) != 0 {\n\t\t\tclientDir := util.SubDir(pkgGen.ClientDir, pkg.Package)\n\t\t\tclientPackage := util.SubPackage(pkgGen.ProjPackage, clientDir)\n\t\t\tclient := Client{}\n\t\t\tclient.Handler = handler\n\t\t\tclient.ServiceName = s.Name\n\t\t\tclient.PackageName = util.SplitPackage(clientPackage, \"\")\n\t\t\tclient.FilePath = filepath.Join(clientDir, util.ToSnakeCase(s.Name)+\".go\")\n\t\t\tif err := pkgGen.updateClient(client, clientTplName, client.FilePath, false); err != nil {\n\t\t\t\treturn fmt.Errorf(\"generate client %s failed, err: %v\", client.FilePath, err.Error())\n\t\t\t}\n\t\t}\n\n\t}\n\treturn nil\n}\n\nfunc (pkgGen *HttpPackageGenerator) processHandler(handler *Handler, root *RouterNode, handlerDir, projectOutDir string, handlerByMethod bool) error {\n\tsingleHandlerPackage := \"\"\n\tif handlerByMethod {\n\t\tsingleHandlerPackage = util.SubPackage(pkgGen.ProjPackage, filepath.Join(handlerDir, projectOutDir))\n\t}\n\thandler.Imports = make(map[string]*model.Model, len(handler.Methods))\n\tfor _, m := range handler.Methods {\n\t\t// Iterate over the request and return parameters of the method to get import path.\n\t\tfor key, mm := range m.Models {\n\t\t\tif v, ok := handler.Imports[mm.PackageName]; ok && v.Package != mm.Package {\n\t\t\t\thandler.Imports[key] = mm\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\thandler.Imports[mm.PackageName] = mm\n\t\t}\n\t\terr := root.Update(m, handler.PackageName, singleHandlerPackage, pkgGen.SortRouter)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif len(pkgGen.UseDir) != 0 {\n\t\toldModelPkg := util.SubPackage(pkgGen.ProjPackage, filepath.Clean(pkgGen.ModelDir))\n\t\tnewModelPkg := path.Clean(pkgGen.UseDir)\n\t\tfor _, m := range handler.Methods {\n\t\t\tfor _, mm := range m.Models {\n\t\t\t\tmm.Package = strings.Replace(mm.Package, oldModelPkg, newModelPkg, 1)\n\t\t\t}\n\t\t}\n\t}\n\n\thandler.Format()\n\treturn nil\n}\n\nfunc (pkgGen *HttpPackageGenerator) updateHandler(handler interface{}, handlerTpl, filePath string, noRepeat bool) error {\n\tif pkgGen.tplsInfo[handlerTpl].Disable {\n\t\treturn nil\n\t}\n\tisExist, err := util.PathExist(filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !isExist {\n\t\treturn pkgGen.TemplateGenerator.Generate(handler, handlerTpl, filePath, noRepeat)\n\t}\n\tif pkgGen.HandlerByMethod { // method by handler, do not need to insert new content\n\t\treturn nil\n\t}\n\n\tfile, err := ioutil.ReadFile(filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// insert new model imports\n\tfor alias, model := range handler.(Handler).Imports {\n\t\tif bytes.Contains(file, []byte(model.Package)) {\n\t\t\tcontinue\n\t\t}\n\t\tfile, err = util.AddImportForContent(file, alias, model.Package)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t// insert customized imports\n\tif tplInfo, exist := pkgGen.TemplateGenerator.tplsInfo[handlerTpl]; exist {\n\t\tif len(tplInfo.UpdateBehavior.ImportTpl) != 0 {\n\t\t\timptSlice, err := getInsertImportContent(tplInfo, handler, file)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor _, impt := range imptSlice {\n\t\t\t\tif bytes.Contains(file, []byte(impt[1])) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfile, err = util.AddImportForContent(file, impt[0], impt[1])\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogs.Warnf(\"can not add import(%s) for file(%s), err: %v\\n\", impt[1], filePath, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// insert new handler\n\tfor _, method := range handler.(Handler).Methods {\n\t\tif bytes.Contains(file, []byte(fmt.Sprintf(\"func %s(\", method.Name))) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Generate additional handlers using templates\n\t\thandlerSingleTpl := pkgGen.tpls[handlerSingleTplName]\n\t\tif handlerSingleTpl == nil {\n\t\t\treturn fmt.Errorf(\"tpl %s not found\", handlerSingleTplName)\n\t\t}\n\t\tdata := SingleHandler{\n\t\t\tHttpMethod:  method,\n\t\t\tFilePath:    handler.(Handler).FilePath,\n\t\t\tPackageName: handler.(Handler).PackageName,\n\t\t\tProjPackage: handler.(Handler).ProjPackage,\n\t\t}\n\t\thandlerFunc := bytes.NewBuffer(nil)\n\t\terr = handlerSingleTpl.Execute(handlerFunc, data)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"execute template \\\"%s\\\" failed, %v\", handlerSingleTplName, err)\n\t\t}\n\n\t\tbuf := bytes.NewBuffer(nil)\n\t\t_, err = buf.Write(file)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"write handler \\\"%s\\\" failed, %v\", method.Name, err)\n\t\t}\n\t\t_, err = buf.Write(handlerFunc.Bytes())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"write handler \\\"%s\\\" failed, %v\", method.Name, err)\n\t\t}\n\t\tfile = buf.Bytes()\n\t}\n\n\tpkgGen.files = append(pkgGen.files, File{filePath, string(file), false, \"\"})\n\n\treturn nil\n}\n\nfunc (pkgGen *HttpPackageGenerator) updateClient(client interface{}, clientTpl, filePath string, noRepeat bool) error {\n\tisExist, err := util.PathExist(filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !isExist {\n\t\treturn pkgGen.TemplateGenerator.Generate(client, clientTpl, filePath, noRepeat)\n\t}\n\tlogs.Infof(\"Client file:%s has been generated, so don't update it\", filePath)\n\n\treturn nil\n}\n\nfunc (m *HttpMethod) InitComment() {\n\ttext := strings.TrimLeft(strings.TrimSpace(m.Comment), \"/\")\n\tif text == \"\" {\n\t\ttext = \"// \" + m.Name + \" .\"\n\t} else if strings.HasPrefix(text, m.Name) {\n\t\ttext = \"// \" + text\n\t} else {\n\t\ttext = \"// \" + m.Name + \" \" + text\n\t}\n\ttext = strings.Replace(text, \"\\n\", \"\\n// \", -1)\n\tif !strings.Contains(text, \"@router \") {\n\t\ttext += \"\\n// @router \" + m.Path\n\t}\n\tm.Comment = text + \" [\" + m.HTTPMethod + \"]\"\n}\n\nfunc MapSerializer(serializer string) string {\n\tswitch serializer {\n\tcase \"json\":\n\t\treturn \"JSON\"\n\tcase \"thrift\":\n\t\treturn \"Thrift\"\n\tcase \"pb\":\n\t\treturn \"ProtoBuf\"\n\tdefault:\n\t\treturn \"JSON\"\n\t}\n}\n\nfunc (h *Handler) Format() {\n\tfor _, m := range h.Methods {\n\t\tm.Serializer = MapSerializer(m.Serializer)\n\t\tm.InitComment()\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/generator/layout.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"gopkg.in/yaml.v2\"\n)\n\n// Layout contains the basic information of idl\ntype Layout struct {\n\tOutDir          string\n\tGoModule        string\n\tServiceName     string\n\tUseApacheThrift bool\n\tHasIdl          bool\n\tNeedGoMod       bool\n\tModelDir        string\n\tHandlerDir      string\n\tRouterDir       string\n}\n\n// LayoutGenerator contains the information generated by generating the layout template\ntype LayoutGenerator struct {\n\tConfigPath string\n\tTemplateGenerator\n}\n\nvar (\n\tlayoutConfig  = defaultLayoutConfig\n\tpackageConfig = defaultPkgConfig\n)\n\nfunc SetDefaultTemplateConfig() {\n\tlayoutConfig = defaultLayoutConfig\n\tpackageConfig = defaultPkgConfig\n}\n\nfunc (lg *LayoutGenerator) Init() error {\n\tconfig := layoutConfig\n\t// unmarshal from user-defined config file if it exists\n\tif lg.ConfigPath != \"\" {\n\t\tcdata, err := ioutil.ReadFile(lg.ConfigPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"read layout config from  %s failed, err: %v\", lg.ConfigPath, err.Error())\n\t\t}\n\t\tconfig = TemplateConfig{}\n\t\tif err = yaml.Unmarshal(cdata, &config); err != nil {\n\t\t\treturn fmt.Errorf(\"unmarshal layout config failed, err: %v\", err.Error())\n\t\t}\n\t}\n\n\tif reflect.DeepEqual(config, TemplateConfig{}) {\n\t\treturn errors.New(\"empty config\")\n\t}\n\tlg.Config = &config\n\n\treturn lg.TemplateGenerator.Init()\n}\n\n// checkInited initialize template definition\nfunc (lg *LayoutGenerator) checkInited() error {\n\tif lg.tpls == nil || lg.dirs == nil {\n\t\tif err := lg.Init(); err != nil {\n\t\t\treturn fmt.Errorf(\"init layout config failed, err: %v\", err.Error())\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (lg *LayoutGenerator) Generate(data map[string]interface{}) error {\n\tif err := lg.checkInited(); err != nil {\n\t\treturn err\n\t}\n\treturn lg.TemplateGenerator.Generate(data, \"\", \"\", false)\n}\n\nfunc (lg *LayoutGenerator) GenerateByService(service Layout) error {\n\tif err := lg.checkInited(); err != nil {\n\t\treturn err\n\t}\n\n\tif len(service.HandlerDir) != 0 {\n\t\t// override the default \"biz/handler/ping.go\" to \"HANDLER_DIR/ping.go\"\n\t\tdefaultPingDir := defaultHandlerDir + sp + \"ping.go\"\n\t\tif tpl, exist := lg.tpls[defaultPingDir]; exist {\n\t\t\tdelete(lg.tpls, defaultPingDir)\n\t\t\tnewPingDir := filepath.Clean(service.HandlerDir + sp + \"ping.go\")\n\t\t\tlg.tpls[newPingDir] = tpl\n\t\t}\n\t}\n\n\tif len(service.RouterDir) != 0 {\n\t\tdefaultRegisterDir := defaultRouterDir + sp + registerTplName\n\t\tif tpl, exist := lg.tpls[defaultRegisterDir]; exist {\n\t\t\tdelete(lg.tpls, defaultRegisterDir)\n\t\t\tnewRegisterDir := filepath.Clean(service.RouterDir + sp + registerTplName)\n\t\t\tlg.tpls[newRegisterDir] = tpl\n\t\t}\n\t}\n\n\tif !service.NeedGoMod {\n\t\tgomodFile := \"go.mod\"\n\t\tif _, exist := lg.tpls[gomodFile]; exist {\n\t\t\tdelete(lg.tpls, gomodFile)\n\t\t}\n\t}\n\n\tif util.IsWindows() {\n\t\tbuildSh := \"build.sh\"\n\t\tbootstrapSh := defaultScriptDir + sp + \"bootstrap.sh\"\n\t\tif _, exist := lg.tpls[buildSh]; exist {\n\t\t\tdelete(lg.tpls, buildSh)\n\t\t}\n\t\tif _, exist := lg.tpls[bootstrapSh]; exist {\n\t\t\tdelete(lg.tpls, bootstrapSh)\n\t\t}\n\t}\n\n\tsd, err := serviceToLayoutData(service)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trd, err := serviceToRouterData(service)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif service.HasIdl {\n\t\tfor k := range lg.tpls {\n\t\t\tif strings.Contains(k, registerTplName) {\n\t\t\t\tdelete(lg.tpls, k)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tdata := map[string]interface{}{\n\t\t\"*\":                                    sd,\n\t\tlayoutConfig.Layouts[routerIndex].Path: rd, // router.go\n\t\tlayoutConfig.Layouts[routerGenIndex].Path: rd, // router_gen.go\n\t}\n\n\treturn lg.Generate(data)\n}\n\n// serviceToLayoutData stores go mod, serviceName, UseApacheThrift mapping\nfunc serviceToLayoutData(service Layout) (map[string]interface{}, error) {\n\tgoMod := service.GoModule\n\tif goMod == \"\" {\n\t\treturn nil, errors.New(\"please specify go-module\")\n\t}\n\thandlerPkg := filepath.Base(defaultHandlerDir)\n\tif len(service.HandlerDir) != 0 {\n\t\thandlerPkg = filepath.Base(service.HandlerDir)\n\t}\n\trouterPkg := filepath.Base(defaultRouterDir)\n\tif len(service.RouterDir) != 0 {\n\t\trouterPkg = filepath.Base(service.RouterDir)\n\t}\n\tserviceName := service.ServiceName\n\tif len(serviceName) == 0 {\n\t\tserviceName = meta.DefaultServiceName\n\t}\n\n\treturn map[string]interface{}{\n\t\t\"GoModule\":        goMod,\n\t\t\"ServiceName\":     serviceName,\n\t\t\"UseApacheThrift\": service.UseApacheThrift,\n\t\t\"HandlerPkg\":      handlerPkg,\n\t\t\"RouterPkg\":       routerPkg,\n\t}, nil\n}\n\n// serviceToRouterData stores the registers function, router import path, handler import path\nfunc serviceToRouterData(service Layout) (map[string]interface{}, error) {\n\trouterDir := sp + defaultRouterDir\n\thandlerDir := sp + defaultHandlerDir\n\tif len(service.RouterDir) != 0 {\n\t\trouterDir = sp + service.RouterDir\n\t}\n\tif len(service.HandlerDir) != 0 {\n\t\thandlerDir = sp + service.HandlerDir\n\t}\n\treturn map[string]interface{}{\n\t\t\"Registers\":      []string{},\n\t\t\"RouterPkgPath\":  service.GoModule + filepath.ToSlash(routerDir),\n\t\t\"HandlerPkgPath\": service.GoModule + filepath.ToSlash(handlerDir),\n\t}, nil\n}\n\nfunc (lg *LayoutGenerator) GenerateByConfig(configPath string) error {\n\tif err := lg.checkInited(); err != nil {\n\t\treturn err\n\t}\n\tbuf, err := ioutil.ReadFile(configPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"read data file '%s' failed, err: %v\", configPath, err.Error())\n\t}\n\tvar data map[string]interface{}\n\tif err := json.Unmarshal(buf, &data); err != nil {\n\t\treturn fmt.Errorf(\"unmarshal json data failed, err: %v\", err.Error())\n\t}\n\treturn lg.Generate(data)\n}\n\nfunc (lg *LayoutGenerator) Degenerate() error {\n\treturn lg.TemplateGenerator.Degenerate()\n}\n"
  },
  {
    "path": "cmd/hz/generator/layout_tpl.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport \"path/filepath\"\n\n//-----------------------------------Default Layout-----------------------------------------\n\nconst (\n\tsp = string(filepath.Separator)\n\n\tdefaultBizDir     = \"biz\"\n\tdefaultModelDir   = \"biz\" + sp + \"model\"\n\tdefaultHandlerDir = \"biz\" + sp + \"handler\"\n\tdefaultServiceDir = \"biz\" + sp + \"service\"\n\tdefaultDalDir     = \"biz\" + sp + \"dal\"\n\tdefaultScriptDir  = \"script\"\n\tdefaultConfDir    = \"conf\"\n\tdefaultRouterDir  = \"biz\" + sp + \"router\"\n\tdefaultClientDir  = \"biz\" + sp + \"client\"\n)\n\nconst (\n\trouterGenIndex = 8\n\trouterIndex    = 9\n\n\tRegisterFile = \"router_gen.go\"\n)\n\nvar defaultLayoutConfig = TemplateConfig{\n\tLayouts: []Template{\n\t\t{\n\t\t\tPath: defaultDalDir + sp,\n\t\t},\n\t\t{\n\t\t\tPath: defaultHandlerDir + sp,\n\t\t},\n\t\t{\n\t\t\tPath: defaultModelDir + sp,\n\t\t},\n\t\t{\n\t\t\tPath: defaultServiceDir + sp,\n\t\t},\n\t\t{\n\t\t\tPath: \"main.go\",\n\t\t\tBody: `// Code generated by hertz generator.\n\npackage main\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/app/server\"\n)\n\nfunc main() {\n\th := server.Default()\n\n\tregister(h)\n\th.Spin()\n}\n\t\t\t`,\n\t\t},\n\t\t{\n\t\t\tPath:   \"go.mod\",\n\t\t\tDelims: [2]string{\"{{\", \"}}\"},\n\t\t\tBody: `module {{.GoModule}}\n{{- if .UseApacheThrift}}\nreplace github.com/apache/thrift => github.com/apache/thrift v0.13.0\n{{- end}}\n\t\t\t`,\n\t\t},\n\t\t{\n\t\t\tPath: \".gitignore\",\n\t\t\tBody: `*.o\n*.a\n*.so\n_obj\n_test\n*.[568vq]\n[568vq].out\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n_testmain.go\n*.exe\n*.exe~\n*.test\n*.prof\n*.rar\n*.zip\n*.gz\n*.psd\n*.bmd\n*.cfg\n*.pptx\n*.log\n*nohup.out\n*settings.pyc\n*.sublime-project\n*.sublime-workspace\n!.gitkeep\n.DS_Store\n/.idea\n/.vscode\n/output\n*.local.yml\ndumped_hertz_remote_config.json\n\t\t  `,\n\t\t},\n\t\t{\n\t\t\tPath: defaultHandlerDir + sp + \"ping.go\",\n\t\t\tBody: `// Code generated by hertz generator.\n\npackage {{.HandlerPkg}}\n\nimport (\n\t\"context\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\n// Ping .\nfunc Ping(ctx context.Context, c *app.RequestContext) {\n\tc.JSON(consts.StatusOK, utils.H{\n\t\t\"message\": \"pong\",\n\t})\n}\n`,\n\t\t},\n\t\t{\n\t\t\tPath: RegisterFile,\n\t\t\tBody: `// Code generated by hertz generator. DO NOT EDIT.\n\npackage main\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/app/server\"\n\trouter \"{{.RouterPkgPath}}\"\n)\n\n// register registers all routers.\nfunc register(r *server.Hertz) {\n\n\trouter.GeneratedRegister(r)\n\n\tcustomizedRegister(r)\n}\n`,\n\t\t},\n\t\t{\n\t\t\tPath: \"router.go\",\n\t\t\tBody: `// Code generated by hertz generator.\n\npackage main\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/app/server\"\n\thandler \"{{.HandlerPkgPath}}\"\n)\n\n// customizeRegister registers customize routers.\nfunc customizedRegister(r *server.Hertz){\n\tr.GET(\"/ping\", handler.Ping)\n\n\t// your code ...\n}\n`,\n\t\t},\n\t\t{\n\t\t\tPath: defaultRouterDir + sp + registerTplName,\n\t\t\tBody: `// Code generated by hertz generator. DO NOT EDIT.\n\npackage {{.RouterPkg}}\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/app/server\"\n)\n\n// GeneratedRegister registers routers generated by IDL.\nfunc GeneratedRegister(r *server.Hertz){\n\t` + insertPointNew + `\n}\n`,\n\t\t},\n\t\t{\n\t\t\tPath: \"build.sh\",\n\t\t\tBody: `#!/bin/bash\nRUN_NAME={{.ServiceName}}\nmkdir -p output/bin\ncp script/* output 2>/dev/null\nchmod +x output/bootstrap.sh\ngo build -o output/bin/${RUN_NAME}`,\n\t\t},\n\t\t{\n\t\t\tPath: defaultScriptDir + sp + \"bootstrap.sh\",\n\t\t\tBody: `#!/bin/bash\nCURDIR=$(cd $(dirname $0); pwd)\nBinaryName={{.ServiceName}}\necho \"$CURDIR/bin/${BinaryName}\"\nexec $CURDIR/bin/${BinaryName}`,\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "cmd/hz/generator/model/define.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nvar (\n\tBaseTypes      = []*Type{TypeBool, TypeByte, TypeInt8, TypeInt16, TypeInt32, TypeInt64, TypeUint8, TypeUint16, TypeUint32, TypeUint64, TypeFloat64, TypeString, TypeBinary}\n\tContainerTypes = []*Type{TypeBaseList, TypeBaseMap, TypeBaseSet}\n\tBaseModel      = Model{}\n)\n\nvar (\n\tTypeBool = &Type{\n\t\tName:  \"bool\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindBool,\n\t}\n\tTypeByte = &Type{\n\t\tName:  \"int8\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt8,\n\t}\n\tTypePbByte = &Type{\n\t\tName:  \"byte\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt8,\n\t}\n\tTypeUint8 = &Type{\n\t\tName:  \"uint8\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt8,\n\t}\n\tTypeUint16 = &Type{\n\t\tName:  \"uint16\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt16,\n\t}\n\tTypeUint32 = &Type{\n\t\tName:  \"uint32\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt32,\n\t}\n\tTypeUint64 = &Type{\n\t\tName:  \"uint64\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt64,\n\t}\n\tTypeUint = &Type{\n\t\tName:  \"uint\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt,\n\t}\n\tTypeInt8 = &Type{\n\t\tName:  \"int8\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt8,\n\t}\n\tTypeInt16 = &Type{\n\t\tName:  \"int16\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt16,\n\t}\n\tTypeInt32 = &Type{\n\t\tName:  \"int32\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt32,\n\t}\n\tTypeInt64 = &Type{\n\t\tName:  \"int64\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt64,\n\t}\n\tTypeInt = &Type{\n\t\tName:  \"int\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindInt,\n\t}\n\tTypeFloat32 = &Type{\n\t\tName:  \"float32\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindFloat64,\n\t}\n\tTypeFloat64 = &Type{\n\t\tName:  \"float64\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindFloat64,\n\t}\n\tTypeString = &Type{\n\t\tName:  \"string\",\n\t\tScope: &BaseModel,\n\t\tKind:  KindString,\n\t}\n\tTypeBinary = &Type{\n\t\tName:     \"binary\",\n\t\tScope:    &BaseModel,\n\t\tKind:     KindSlice,\n\t\tCategory: CategoryBinary,\n\t\tExtra:    []*Type{TypePbByte},\n\t}\n\n\tTypeBaseMap = &Type{\n\t\tName:     \"map\",\n\t\tScope:    &BaseModel,\n\t\tKind:     KindMap,\n\t\tCategory: CategoryMap,\n\t}\n\tTypeBaseSet = &Type{\n\t\tName:     \"set\",\n\t\tScope:    &BaseModel,\n\t\tKind:     KindSlice,\n\t\tCategory: CategorySet,\n\t}\n\tTypeBaseList = &Type{\n\t\tName:     \"list\",\n\t\tScope:    &BaseModel,\n\t\tKind:     KindSlice,\n\t\tCategory: CategoryList,\n\t}\n)\n\nfunc NewCategoryType(typ *Type, cg Category) *Type {\n\tcyp := *typ\n\tcyp.Category = cg\n\treturn &cyp\n}\n\nfunc NewStructType(name string, cg Category) *Type {\n\treturn &Type{\n\t\tName:     name,\n\t\tScope:    nil,\n\t\tKind:     KindStruct,\n\t\tCategory: cg,\n\t\tIndirect: false,\n\t\tExtra:    nil,\n\t\tHasNew:   true,\n\t}\n}\n\nfunc NewFuncType(name string, cg Category) *Type {\n\treturn &Type{\n\t\tName:     name,\n\t\tScope:    nil,\n\t\tKind:     KindFunc,\n\t\tCategory: cg,\n\t\tIndirect: false,\n\t\tExtra:    nil,\n\t\tHasNew:   false,\n\t}\n}\n\nfunc IsBaseType(typ *Type) bool {\n\tfor _, t := range BaseTypes {\n\t\tif typ == t {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc NewEnumType(name string, cg Category) *Type {\n\treturn &Type{\n\t\tName:     name,\n\t\tScope:    &BaseModel,\n\t\tKind:     KindInt,\n\t\tCategory: cg,\n\t\tIndirect: false,\n\t\tExtra:    nil,\n\t\tHasNew:   true,\n\t}\n}\n\nfunc NewOneofType(name string) *Type {\n\treturn &Type{\n\t\tName:     name,\n\t\tScope:    &BaseModel,\n\t\tKind:     KindInterface,\n\t\tIndirect: false,\n\t\tExtra:    nil,\n\t\tHasNew:   true,\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/generator/model/expr.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n)\n\ntype BoolExpression struct {\n\tSrc bool\n}\n\nfunc (boolExpr BoolExpression) Expression() string {\n\tif boolExpr.Src {\n\t\treturn \"true\"\n\t} else {\n\t\treturn \"false\"\n\t}\n}\n\ntype StringExpression struct {\n\tSrc string\n}\n\nfunc (stringExpr StringExpression) Expression() string {\n\treturn fmt.Sprintf(\"%q\", stringExpr.Src)\n}\n\ntype NumberExpression struct {\n\tSrc string\n}\n\nfunc (numExpr NumberExpression) Expression() string {\n\treturn numExpr.Src\n}\n\ntype ListExpression struct {\n\tElementType *Type\n\tElements    []Literal\n}\n\ntype IntExpression struct {\n\tSrc int\n}\n\nfunc (intExpr IntExpression) Expression() string {\n\treturn strconv.Itoa(intExpr.Src)\n}\n\ntype DoubleExpression struct {\n\tSrc float64\n}\n\nfunc (doubleExpr DoubleExpression) Expression() string {\n\treturn strconv.FormatFloat(doubleExpr.Src, 'f', -1, 64)\n}\n\nfunc (listExpr ListExpression) Expression() string {\n\tret := \"[]\" + listExpr.ElementType.Name + \"{\\n\"\n\tfor _, e := range listExpr.Elements {\n\t\tret += e.Expression() + \",\\n\"\n\t}\n\tret += \"\\n}\"\n\treturn ret\n}\n\ntype MapExpression struct {\n\tKeyType   *Type\n\tValueType *Type\n\tElements  map[string]Literal\n}\n\nfunc (mapExpr MapExpression) Expression() string {\n\tret := \"map[\" + mapExpr.KeyType.Name + \"]\" + mapExpr.ValueType.Name + \"{\\n\"\n\tfor k, e := range mapExpr.Elements {\n\t\tret += fmt.Sprintf(\"%q: %s,\\n\", k, e.Expression())\n\t}\n\tret += \"\\n}\"\n\treturn ret\n}\n"
  },
  {
    "path": "cmd/hz/generator/model/golang/constant.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage golang\n\nvar constants = `\n{{define \"Constants\"}}\nconst {{.Name}} {{.Type.ResolveName ROOT}} = {{.Value.Expression}}\n{{end}}\n`\n"
  },
  {
    "path": "cmd/hz/generator/model/golang/enum.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage golang\n\n// Enum .\nvar enum = `\n{{define \"Enum\"}}\n{{- $EnumType := (Identify .Name)}}\ntype {{$EnumType}} {{.GoType}}\n\nconst (\n\t{{- range $i, $e := .Values}}\n\t{{$EnumType}}_{{$e.Name}} {{$EnumType}} = {{$e.Value.Expression}}\n\t{{- end}}\n)\n\nfunc (p {{$EnumType}}) String() string {\n\tswitch p {\n\t{{- range $i, $e := .Values}}\n\tcase {{$EnumType}}_{{$e.Name}}:\n\t\treturn \"{{printf \"%s%s\" $EnumType $e.Name | SnakeCase}}\"\n\t{{- end}}\n\t}\n\treturn \"<UNSET>\"\n}\n\nfunc {{$EnumType}}FromString(s string) ({{$EnumType}}, error) {\n\tswitch s {\n\t{{- range $i, $e := .Values}}\n\tcase \"{{printf \"%s%s\" $EnumType $e.Name | SnakeCase}}\":\n\t\treturn {{$EnumType}}_{{$e.Name}}, nil\n\t{{- end}}\n\t}\n\treturn {{$EnumType}}(0), fmt.Errorf(\"not a valid {{$EnumType}} string\")\n}\n\n{{- if Features.MarshalEnumToText}}\n\nfunc (p {{$EnumType}}) MarshalText() ([]byte, error) {\n\treturn []byte(p.String()), nil\n}\n\nfunc (p *{{$EnumType}}) UnmarshalText(text []byte) error {\n\tq, err := {{$EnumType}}FromString(string(text))\n\tif err != nil {\n\t\treturn err\n\t}\n\t*p = q\n\treturn nil\n}\n{{- end}}\n{{end}}\n`\n"
  },
  {
    "path": "cmd/hz/generator/model/golang/file.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage golang\n\nvar file = `{{$ROOT := . -}}\n// Code generated by hz.\n\npackage {{.PackageName}}\n\nimport (\n\t\"fmt\"\n{{- range $alias, $model := .Imports}}\n\t{{$model.PackageName}} \"{{$model.Package}}\"\n{{- end}}\n)\n\n{{- range .Typedefs}}\n{{template \"Typedef\" .}}\n{{- end}}\n\n{{- range .Constants}}\n{{template \"Constants\" .}}\n{{- end}}\n\n{{- range .Variables}}\n{{template \"Variables\" .}}\n{{- end}}\n\n{{- range .Functions}}\n{{template \"Function\" .}}\n{{- end}}\n\n{{- range .Enums}}\n{{template \"Enum\" .}}\n{{- end}}\n\n{{- range .Oneofs}}\n{{template \"Oneof\" .}}\n{{- end}}\n\n{{- range .Structs}}\n{{template \"Struct\" .}}\n{{- end}}\n\n{{- range .Methods}}\n{{template \"Method\" .}}\n{{- end}}\n`\n"
  },
  {
    "path": "cmd/hz/generator/model/golang/function.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage golang\n\nvar function = `\n{{define \"Function\"}}\nfunc {{template \"FuncBody\" . -}}\n{{end}}{{/* define \"Function\" */}}\n\n{{define \"FuncBody\"}}\n{{- .Name -}}(\n{{- range $i, $arg := .Args -}}\n{{- if gt $i 0}}, {{end -}}\n{{$arg.Name}} {{$arg.Type.ResolveName ROOT}}\n{{- end -}}{{/* range */}})\n{{- if gt (len .Rets) 0}} ({{end -}}\n{{- range $i, $ret := .Rets -}}\n{{- if gt $i 0}}, {{end -}}\n{{$ret.Type.ResolveName ROOT}}\n{{- end -}}{{/* range */}}\n{{- if gt (len .Rets) 0}}) {{end -}}{\n{{.Code}}\n}\n{{end}}{{/* define \"FuncBody\" */}}\n`\n\nvar method = `\n{{define \"Method\"}}\nfunc ({{.ReceiverName}} {{.ReceiverType.ResolveName ROOT}}) \n{{- template \"FuncBody\" .Function -}}\n{{end}}\n`\n"
  },
  {
    "path": "cmd/hz/generator/model/golang/init.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage golang\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"text/template\"\n)\n\nvar tpls *template.Template\n\nvar list = map[string]string{\n\t\"file\":      file,\n\t\"typedef\":   typedef,\n\t\"constants\": constants,\n\t\"variables\": variables,\n\t\"function\":  function,\n\t\"enum\":      enum,\n\t\"struct\":    structLike,\n\t\"method\":    method,\n\t\"oneof\":     oneof,\n}\n\n/***********************Export API*******************************/\n\nfunc Template() (*template.Template, error) {\n\tif tpls != nil {\n\t\treturn tpls, nil\n\t}\n\ttpls = new(template.Template)\n\n\ttpls = tpls.Funcs(funcMap)\n\n\tvar err error\n\tfor k, li := range list {\n\t\ttpls, err = tpls.Parse(li)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parse template '%s' failed, err: %v\", k, err.Error())\n\t\t}\n\t}\n\treturn tpls, nil\n}\n\nfunc List() map[string]string {\n\treturn list\n}\n\n/***********************Template Funcs**************************/\n\nvar funcMap = template.FuncMap{\n\t\"Features\":            getFeatures,\n\t\"Identify\":            identify,\n\t\"CamelCase\":           camelCase,\n\t\"SnakeCase\":           snakeCase,\n\t\"GetTypedefReturnStr\": getTypedefReturnStr,\n}\n\nfunc Funcs(name string, fn interface{}) error {\n\tif _, ok := funcMap[name]; ok {\n\t\treturn fmt.Errorf(\"duplicate function: %s has been registered\", name)\n\t}\n\tfuncMap[name] = fn\n\treturn nil\n}\n\nfunc identify(name string) string {\n\treturn name\n}\n\nfunc camelCase(name string) string {\n\treturn name\n}\n\nfunc snakeCase(name string) string {\n\treturn name\n}\n\nfunc getTypedefReturnStr(name string) string {\n\tif strings.Contains(name, \".\") {\n\t\tidx := strings.LastIndex(name, \".\")\n\t\treturn name[:idx] + \".\" + \"New\" + name[idx+1:] + \"()\"\n\n\t}\n\treturn \"New\" + name + \"()\"\n}\n\n/***********************Template Options**************************/\n\ntype feature struct {\n\tMarshalEnumToText  bool\n\tTypedefAsTypeAlias bool\n}\n\nvar features = feature{}\n\nfunc getFeatures() feature {\n\treturn features\n}\n\nfunc SetOption(opt string) error {\n\tswitch opt {\n\tcase \"MarshalEnumToText\":\n\t\tfeatures.MarshalEnumToText = true\n\tcase \"TypedefAsTypeAlias\":\n\t\tfeatures.TypedefAsTypeAlias = true\n\t}\n\treturn nil\n}\n\nvar Options = []string{\n\t\"MarshalEnumToText\",\n\t\"TypedefAsTypeAlias\",\n}\n\nfunc GetOptions() []string {\n\treturn Options\n}\n"
  },
  {
    "path": "cmd/hz/generator/model/golang/oneof.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage golang\n\nvar oneof = `\n{{define \"Oneof\"}}\ntype {{$.InterfaceName}} interface {\n\t{{$.InterfaceName}}()\n}\n\n{{range $i, $f := .Choices}}\ntype {{$f.MessageName}}_{{$f.ChoiceName}} struct {\n\t{{$f.ChoiceName}} {{$f.Type.ResolveName ROOT}}\n}\n{{end}}\n\n{{range $i, $f := .Choices}}\nfunc (*{{$f.MessageName}}_{{$f.ChoiceName}}) {{$.InterfaceName}}() {}\n{{end}}\n\n{{range $i, $f := .Choices}}\nfunc (p *{{$f.MessageName}}) Get{{$f.ChoiceName}}() {{$f.Type.ResolveName ROOT}} {\n\tif p, ok := p.Get{{$.OneofName}}().(*{{$f.MessageName}}_{{$f.ChoiceName}}); ok {\n\t\treturn p.{{$f.ChoiceName}}\n\t}\n\treturn {{$f.Type.ResolveDefaultValue}}\n}\n{{end}}\n\n{{end}}\n`\n"
  },
  {
    "path": "cmd/hz/generator/model/golang/struct.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage golang\n\n// StructLike is the code template for struct, union, and exception.\nvar structLike = `\n{{define \"Struct\"}}\n{{- $TypeName := (Identify .Name) -}}\n{{$MessageLeadingComments := .LeadingComments}}\n{{if ne (len $MessageLeadingComments) 0}}\n//{{$MessageLeadingComments}}\n{{end -}}\ntype {{$TypeName}} struct {\n{{- range $i, $f := .Fields}}\n{{- $FieldLeadingComments := $f.LeadingComments}}\n{{$FieldTrailingComments := $f.TrailingComments -}}\n{{- if ne (len $FieldLeadingComments) 0 -}}\n    //{{$FieldLeadingComments}}\n{{end -}}\n{{- if $f.IsPointer -}}\n\t{{$f.Name}} *{{$f.Type.ResolveName ROOT}} {{$f.GenGoTags}}{{if ne (len $FieldTrailingComments) 0}} //{{$FieldTrailingComments}}{{end -}}\n{{- else -}}\n\t{{$f.Name}} {{$f.Type.ResolveName ROOT}} {{$f.GenGoTags}}{{if ne (len $FieldTrailingComments) 0}} //{{$FieldTrailingComments}}{{end -}}\n{{- end -}}\n{{- end}}\n}\n\nfunc New{{$TypeName}}() *{{$TypeName}} {\n\treturn &{{$TypeName}}{\n\t\t{{template \"StructLikeDefault\" .}}\n\t}\n}\n\n{{template \"FieldGetOrSet\" .}}\n\n{{if eq .Category 14}}\nfunc (p *{{$TypeName}}) CountSetFields{{$TypeName}}() int {\n\tcount := 0\n\t{{- range $i, $f := .Fields}}\n\t{{- if $f.Type.IsSettable}}\n\tif p.IsSet{{$f.Name}}() {\n\t\tcount++\n\t}\n\t{{- end}}\n\t{{- end}}\n\treturn count\n}\n{{- end}}\n\nfunc (p *{{$TypeName}}) String() string {\n\tif p == nil {\n\t\treturn \"<nil>\"\n\t}\n\treturn fmt.Sprintf(\"{{$TypeName}}(%+v)\", *p)\n}\n\n{{- if eq .Category 15}}\nfunc (p *{{$TypeName}}) Error() string {\n\treturn p.String()\n}\n{{- end}}\n{{- end}}{{/* define \"StructLike\" */}}\n\n{{- define \"StructLikeDefault\"}}\n{{- range $i, $f := .Fields}}\n\t{{- if $f.IsSetDefault}}\n\t\t{{$f.Name}}: {{$f.DefaultValue.Expression}},\n\t{{- end}}\n{{- end}}\n{{- end -}}{{/* define \"StructLikeDefault\" */}}\n\n{{- define \"FieldGetOrSet\"}}\n{{- $TypeName := (Identify .Name)}}\n{{- range $i, $f := .Fields}}\n{{$FieldName := $f.Name}}\n{{$FieldTypeName := $f.Type.ResolveName ROOT}}\n\n{{- if $f.Type.IsSettable}}\nfunc (p *{{$TypeName}}) IsSet{{$FieldName}}() bool {\n\treturn p.{{$FieldName}} != nil\n}\n{{- end}}{{/* IsSettable . */}}\n\nfunc (p *{{$TypeName}}) Get{{$FieldName}}() {{$FieldTypeName}} {\n\t{{- if $f.Type.IsSettable}}\n\tif !p.IsSet{{$FieldName}}() {\n\t\treturn {{with $f.DefaultValue}}{{$f.DefaultValue.Expression}}{{else}}nil{{end}}\n\t}\n\t{{- end}}\n{{- if $f.IsPointer}}\n\treturn *p.{{$FieldName}}\n{{else}}\n\treturn p.{{$FieldName}}\n{{- end -}}\n}\n\nfunc (p *{{$TypeName}}) Set{{$FieldName}}(val {{$FieldTypeName}}) {\n{{- if $f.IsPointer}}\n\t*p.{{$FieldName}} = val\n{{else}}\n\tp.{{$FieldName}} = val\n{{- end -}}\n}\n{{- end}}{{/* range .Fields */}}\n{{- end}}{{/* define \"FieldGetOrSet\" */}}\n`\n"
  },
  {
    "path": "cmd/hz/generator/model/golang/typedef.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage golang\n\n// Typedef .\nvar typedef = `\n{{define \"Typedef\"}}\n{{- $NewTypeName := (Identify .Alias)}}\n{{- $OldTypeName := .Type.ResolveNameForTypedef ROOT}}\ntype {{$NewTypeName}} = {{$OldTypeName}}\n\n{{if eq .Type.Kind 25}}{{if .Type.HasNew}}\nfunc New{{$NewTypeName}}() *{{$NewTypeName}} {\n\treturn {{(GetTypedefReturnStr $OldTypeName)}}\n}\n{{- end}}{{- end}}\n{{- end}}\n`\n"
  },
  {
    "path": "cmd/hz/generator/model/golang/variable.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage golang\n\nvar variables = `\n{{- define \"Variables\"}}\nvar {{.Name}} {{.Type.ResolveName ROOT}} = {{.Value.Expression}}\n{{end}}\n`\n"
  },
  {
    "path": "cmd/hz/generator/model/model.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype Kind uint\n\nconst (\n\tKindInvalid Kind = iota\n\tKindBool\n\tKindInt\n\tKindInt8\n\tKindInt16\n\tKindInt32\n\tKindInt64\n\tKindUint\n\tKindUint8\n\tKindUint16\n\tKindUint32\n\tKindUint64\n\tKindUintptr\n\tKindFloat32\n\tKindFloat64\n\tKindComplex64\n\tKindComplex128\n\tKindArray\n\tKindChan\n\tKindFunc\n\tKindInterface\n\tKindMap\n\tKindPtr\n\tKindSlice\n\tKindString\n\tKindStruct\n\tKindUnsafePointer\n)\n\ntype Category int64\n\nconst (\n\tCategoryConstant  Category = 1\n\tCategoryBinary    Category = 8\n\tCategoryMap       Category = 9\n\tCategoryList      Category = 10\n\tCategorySet       Category = 11\n\tCategoryEnum      Category = 12\n\tCategoryStruct    Category = 13\n\tCategoryUnion     Category = 14\n\tCategoryException Category = 15\n\tCategoryTypedef   Category = 16\n\tCategoryService   Category = 17\n)\n\ntype Model struct {\n\tFilePath string\n\tPackage  string\n\tImports  map[string]*Model //{{import}}:Model\n\n\t// rendering data\n\tPackageName string\n\t// Imports     map[string]string //{{alias}}:{{import}}\n\tTypedefs  []TypeDef\n\tConstants []Constant\n\tVariables []Variable\n\tFunctions []Function\n\tEnums     []Enum\n\tStructs   []Struct\n\tMethods   []Method\n\tOneofs    []Oneof\n}\n\nfunc (m Model) IsEmpty() bool {\n\treturn len(m.Typedefs) == 0 && len(m.Constants) == 0 && len(m.Variables) == 0 &&\n\t\tlen(m.Functions) == 0 && len(m.Enums) == 0 && len(m.Structs) == 0 && len(m.Methods) == 0\n}\n\ntype Models []*Model\n\nfunc (a *Models) MergeMap(b map[string]*Model) {\n\tfor _, v := range b {\n\t\tinsert := true\n\t\tfor _, p := range *a {\n\t\t\tif p == v {\n\t\t\t\tinsert = false\n\t\t\t}\n\t\t}\n\t\tif insert {\n\t\t\t*a = append(*a, v)\n\t\t}\n\t}\n\treturn\n}\n\nfunc (a *Models) MergeArray(b []*Model) {\n\tfor _, v := range b {\n\t\tinsert := true\n\t\tfor _, p := range *a {\n\t\t\tif p == v {\n\t\t\t\tinsert = false\n\t\t\t}\n\t\t}\n\t\tif insert {\n\t\t\t*a = append(*a, v)\n\t\t}\n\t}\n\treturn\n}\n\ntype RequiredNess int\n\nconst (\n\tRequiredNess_Default  RequiredNess = 0\n\tRequiredNess_Required RequiredNess = 1\n\tRequiredNess_Optional RequiredNess = 2\n)\n\ntype Type struct {\n\tName     string\n\tScope    *Model\n\tKind     Kind\n\tIndirect bool\n\tCategory Category\n\tExtra    []*Type // [{key_type},{value_type}] for map, [{element_type}] for list or set\n\tHasNew   bool\n}\n\nfunc (rt *Type) ResolveDefaultValue() string {\n\tif rt == nil {\n\t\treturn \"\"\n\t}\n\tswitch rt.Kind {\n\tcase KindInt, KindInt8, KindInt16, KindInt32, KindInt64, KindUint, KindUint16, KindUint32, KindUint64,\n\t\tKindFloat32, KindFloat64, KindComplex64, KindComplex128:\n\t\treturn \"0\"\n\tcase KindBool:\n\t\treturn \"false\"\n\tcase KindString:\n\t\treturn \"\\\"\\\"\"\n\tdefault:\n\t\treturn \"nil\"\n\t}\n}\n\nfunc (rt *Type) ResolveNameForTypedef(scope *Model) (string, error) {\n\tif rt == nil {\n\t\treturn \"\", errors.New(\"type is nil\")\n\t}\n\tname := rt.Name\n\tif rt.Scope == nil {\n\t\treturn rt.Name, nil\n\t}\n\n\tswitch rt.Kind {\n\tcase KindArray, KindSlice:\n\t\tif len(rt.Extra) != 1 {\n\t\t\treturn \"\", fmt.Errorf(\"the type: %s should have 1 extra type, but has %d\", rt.Name, len(rt.Extra))\n\t\t}\n\t\tresolveName, err := rt.Extra[0].ResolveName(scope)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tname = fmt.Sprintf(\"[]%s\", resolveName)\n\tcase KindMap:\n\t\tif len(rt.Extra) != 2 {\n\t\t\treturn \"\", fmt.Errorf(\"the type: %s should have 2 extra types, but has %d\", rt.Name, len(rt.Extra))\n\t\t}\n\t\tresolveKey, err := rt.Extra[0].ResolveName(scope)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tresolveValue, err := rt.Extra[1].ResolveName(scope)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tname = fmt.Sprintf(\"map[%s]%s\", resolveKey, resolveValue)\n\tcase KindChan:\n\t\tif len(rt.Extra) != 1 {\n\t\t\treturn \"\", fmt.Errorf(\"the type: %s should have 1 extra type, but has %d\", rt.Name, len(rt.Extra))\n\t\t}\n\t\tresolveName, err := rt.Extra[0].ResolveName(scope)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tname = fmt.Sprintf(\"chan %s\", resolveName)\n\t}\n\n\tif scope != nil && rt.Scope != &BaseModel && rt.Scope.Package != scope.Package {\n\t\tname = rt.Scope.PackageName + \".\" + name\n\t}\n\treturn name, nil\n}\n\nfunc (rt *Type) ResolveName(scope *Model) (string, error) {\n\tif rt == nil {\n\t\treturn \"\", fmt.Errorf(\"type is nil\")\n\t}\n\tname := rt.Name\n\tif rt.Scope == nil {\n\t\tif rt.Kind == KindStruct {\n\t\t\treturn \"*\" + rt.Name, nil\n\t\t}\n\t\treturn rt.Name, nil\n\t}\n\n\tif rt.Category == CategoryTypedef {\n\t\tif scope != nil && rt.Scope != &BaseModel && rt.Scope.Package != scope.Package {\n\t\t\tname = rt.Scope.PackageName + \".\" + name\n\t\t}\n\n\t\tif rt.Kind == KindStruct {\n\t\t\tname = \"*\" + name\n\t\t}\n\n\t\treturn name, nil\n\t}\n\n\tswitch rt.Kind {\n\tcase KindArray, KindSlice:\n\t\tif len(rt.Extra) != 1 {\n\t\t\treturn \"\", fmt.Errorf(\"The type: %s should have 1 extra type, but has %d\", rt.Name, len(rt.Extra))\n\t\t}\n\t\tresolveName, err := rt.Extra[0].ResolveName(scope)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tname = fmt.Sprintf(\"[]%s\", resolveName)\n\tcase KindMap:\n\t\tif len(rt.Extra) != 2 {\n\t\t\treturn \"\", fmt.Errorf(\"The type: %s should have 2 extra type, but has %d\", rt.Name, len(rt.Extra))\n\t\t}\n\t\tresolveKey, err := rt.Extra[0].ResolveName(scope)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tresolveValue, err := rt.Extra[1].ResolveName(scope)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tname = fmt.Sprintf(\"map[%s]%s\", resolveKey, resolveValue)\n\tcase KindChan:\n\t\tif len(rt.Extra) != 1 {\n\t\t\treturn \"\", fmt.Errorf(\"The type: %s should have 1 extra type, but has %d\", rt.Name, len(rt.Extra))\n\t\t}\n\t\tresolveName, err := rt.Extra[0].ResolveName(scope)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tname = fmt.Sprintf(\"chan %s\", resolveName)\n\t}\n\n\tif scope != nil && rt.Scope != &BaseModel && rt.Scope.Package != scope.Package {\n\t\tname = rt.Scope.PackageName + \".\" + name\n\t}\n\n\tif rt.Kind == KindStruct {\n\t\tname = \"*\" + name\n\t}\n\treturn name, nil\n}\n\nfunc (rt *Type) IsBinary() bool {\n\treturn rt.Category == CategoryBinary && (rt.Kind == KindSlice || rt.Kind == KindArray)\n}\n\nfunc (rt *Type) IsBaseType() bool {\n\treturn rt.Kind < KindComplex64\n}\n\nfunc (rt *Type) IsSettable() bool {\n\tswitch rt.Kind {\n\tcase KindArray, KindChan, KindFunc, KindInterface, KindMap, KindPtr, KindSlice, KindUnsafePointer:\n\t\treturn true\n\t}\n\treturn false\n}\n\ntype TypeDef struct {\n\tScope *Model\n\tAlias string\n\tType  *Type\n}\n\ntype Constant struct {\n\tScope *Model\n\tName  string\n\tType  *Type\n\tValue Literal\n}\n\ntype Literal interface {\n\tExpression() string\n}\n\ntype Variable struct {\n\tScope *Model\n\tName  string\n\tType  *Type\n\tValue Literal\n}\n\ntype Function struct {\n\tScope *Model\n\tName  string\n\tArgs  []Variable\n\tRets  []Variable\n\tCode  string\n}\n\ntype Method struct {\n\tScope        *Model\n\tReceiverName string\n\tReceiverType *Type\n\tByPtr        bool\n\tFunction\n}\n\ntype Enum struct {\n\tScope  *Model\n\tName   string\n\tGoType string\n\tValues []Constant\n}\n\ntype Struct struct {\n\tScope           *Model\n\tName            string\n\tFields          []Field\n\tCategory        Category\n\tLeadingComments string\n}\n\ntype Field struct {\n\tScope            *Struct\n\tName             string\n\tType             *Type\n\tIsSetDefault     bool\n\tDefaultValue     Literal\n\tRequired         RequiredNess\n\tTags             Tags\n\tLeadingComments  string\n\tTrailingComments string\n\tIsPointer        bool\n}\n\ntype Oneof struct {\n\tMessageName   string\n\tOneofName     string\n\tInterfaceName string\n\tChoices       []Choice\n}\n\ntype Choice struct {\n\tMessageName string\n\tChoiceName  string\n\tType        *Type\n}\n\ntype Tags []Tag\n\ntype Tag struct {\n\tKey       string\n\tValue     string\n\tIsDefault bool // default tag\n}\n\nfunc (ts Tags) String() string {\n\tret := make([]string, 0, len(ts))\n\tfor _, t := range ts {\n\t\tret = append(ret, fmt.Sprintf(\"%v:%q\", t.Key, t.Value))\n\t}\n\treturn strings.Join(ret, \" \")\n}\n\nfunc (ts *Tags) Remove(name string) {\n\tret := make([]Tag, 0, len(*ts))\n\tfor _, t := range *ts {\n\t\tif t.Key != name {\n\t\t\tret = append(ret, t)\n\t\t}\n\t}\n\t*ts = ret\n}\n\nfunc (ts Tags) Len() int { return len(ts) }\n\nfunc (ts Tags) Less(i, j int) bool {\n\treturn ts[i].Key < ts[j].Key\n}\n\nfunc (ts Tags) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] }\n\nfunc (f Field) GenGoTags() string {\n\tif len(f.Tags) == 0 {\n\t\treturn \"\"\n\t}\n\n\treturn fmt.Sprintf(\"`%s`\", f.Tags.String())\n}\n"
  },
  {
    "path": "cmd/hz/generator/model.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model/golang\"\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n)\n\n//---------------------------------Backend----------------------------------\n\ntype Option string\n\nconst (\n\tOptionMarshalEnumToText  Option = \"MarshalEnumToText\"\n\tOptionTypedefAsTypeAlias Option = \"TypedefAsTypeAlias\"\n)\n\ntype Backend interface {\n\tTemplate() (*template.Template, error)\n\tList() map[string]string\n\tSetOption(opts string) error\n\tGetOptions() []string\n\tFuncs(name string, fn interface{}) error\n}\n\ntype GolangBackend struct{}\n\nfunc (gb *GolangBackend) Template() (*template.Template, error) {\n\treturn golang.Template()\n}\n\nfunc (gb *GolangBackend) List() map[string]string {\n\treturn golang.List()\n}\n\nfunc (gb *GolangBackend) SetOption(opts string) error {\n\treturn golang.SetOption(opts)\n}\n\nfunc (gb *GolangBackend) GetOptions() []string {\n\treturn golang.GetOptions()\n}\n\nfunc (gb *GolangBackend) Funcs(name string, fn interface{}) error {\n\treturn golang.Funcs(name, fn)\n}\n\nfunc switchBackend(backend meta.Backend) Backend {\n\tswitch backend {\n\tcase meta.BackendGolang:\n\t\treturn &GolangBackend{}\n\t}\n\treturn loadThirdPartyBackend(string(backend))\n}\n\nfunc loadThirdPartyBackend(plugin string) Backend {\n\tpanic(\"no implement yet!\")\n}\n\n/**********************Generating*************************/\n\nfunc (pkgGen *HttpPackageGenerator) LoadBackend(backend meta.Backend) error {\n\tbd := switchBackend(backend)\n\tif bd == nil {\n\t\treturn fmt.Errorf(\"no found backend '%s'\", backend)\n\t}\n\tfor _, opt := range pkgGen.Options {\n\t\tif err := bd.SetOption(string(opt)); err != nil {\n\t\t\treturn fmt.Errorf(\"set option %s error, err: %v\", opt, err.Error())\n\t\t}\n\t}\n\n\terr := bd.Funcs(\"ROOT\", func() *model.Model {\n\t\treturn pkgGen.curModel\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"register global function in model template failed, err: %v\", err.Error())\n\t}\n\n\ttpl, err := bd.Template()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"load backend %s failed, err: %v\", backend, err.Error())\n\t}\n\n\tif pkgGen.tpls == nil {\n\t\tpkgGen.tpls = map[string]*template.Template{}\n\t}\n\tpkgGen.tpls[modelTplName] = tpl\n\tpkgGen.loadedBackend = bd\n\treturn nil\n}\n\nfunc (pkgGen *HttpPackageGenerator) GenModel(data *model.Model, gen bool) error {\n\tif pkgGen.processedModels == nil {\n\t\tpkgGen.processedModels = map[*model.Model]bool{}\n\t}\n\n\tif _, ok := pkgGen.processedModels[data]; !ok {\n\t\tvar path string\n\t\tvar updatePackage bool\n\t\tif strings.HasPrefix(data.Package, pkgGen.ProjPackage) && data.PackageName != pkgGen.ProjPackage {\n\t\t\tpath = data.Package[len(pkgGen.ProjPackage):]\n\t\t} else {\n\t\t\tpath = data.Package\n\t\t\tupdatePackage = true\n\t\t}\n\t\tmodelDir := util.SubDir(pkgGen.ModelDir, path)\n\t\tif updatePackage {\n\t\t\tdata.Package = util.SubPackage(pkgGen.ProjPackage, modelDir)\n\t\t}\n\t\tdata.FilePath = filepath.Join(modelDir, util.BaseNameAndTrim(data.FilePath)+\".go\")\n\n\t\tpkgGen.processedModels[data] = true\n\t}\n\n\tfor _, dep := range data.Imports {\n\t\tif err := pkgGen.GenModel(dep, false); err != nil {\n\t\t\treturn fmt.Errorf(\"generate model %s failed, err: %v\", dep.FilePath, err.Error())\n\t\t}\n\t}\n\n\tif gen && !data.IsEmpty() {\n\t\tpkgGen.curModel = data\n\t\tremoveDuplicateImport(data)\n\t\terr := pkgGen.TemplateGenerator.Generate(data, modelTplName, data.FilePath, false)\n\t\tpkgGen.curModel = nil\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// Idls with the same Package do not need to refer to each other\nfunc removeDuplicateImport(data *model.Model) {\n\tfor k, v := range data.Imports {\n\t\tif data.Package == v.Package {\n\t\t\tdelete(data.Imports, k)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/generator/model_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"testing\"\n\t\"text/template\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n)\n\ntype StringValue struct {\n\tsrc string\n}\n\nfunc (sv *StringValue) Expression() string {\n\treturn sv.src\n}\n\nfunc TestIdlGenerator_GenModel(t *testing.T) {\n\ttypeModel := &model.Type{\n\t\tName:     \"Model\",\n\t\tKind:     model.KindStruct,\n\t\tIndirect: true,\n\t}\n\ttypeErr := &model.Type{\n\t\tName:     \"error\",\n\t\tKind:     model.KindInterface,\n\t\tIndirect: false,\n\t}\n\n\ttype fields struct {\n\t\tConfigPath  string\n\t\tOutputDir   string\n\t\tBackend     meta.Backend\n\t\thandlerDir  string\n\t\trouterDir   string\n\t\tmodelDir    string\n\t\tProjPackage string\n\t\tConfig      *TemplateConfig\n\t\ttpls        map[string]*template.Template\n\t}\n\ttype args struct {\n\t\tdata *model.Model\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\targs    args\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"\",\n\t\t\tfields: fields{\n\t\t\t\tOutputDir: \"./testdata\",\n\t\t\t\tBackend:   meta.BackendGolang,\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tdata: &model.Model{\n\t\t\t\t\tFilePath:    \"idl/main.thrift\",\n\t\t\t\t\tPackage:     \"model/psm\",\n\t\t\t\t\tPackageName: \"psm\",\n\t\t\t\t\tImports: map[string]*model.Model{\n\t\t\t\t\t\t\"base\": {\n\t\t\t\t\t\t\tPackage:     \"model/base\",\n\t\t\t\t\t\t\tPackageName: \"base\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTypedefs: []model.TypeDef{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAlias: \"HerztModel\",\n\t\t\t\t\t\t\tType:  typeModel,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tConstants: []model.Constant{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  \"OBJ\",\n\t\t\t\t\t\t\tType:  typeErr,\n\t\t\t\t\t\t\tValue: &StringValue{\"fmt.Errorf(\\\"EOF\\\")\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tVariables: []model.Variable{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  \"Object\",\n\t\t\t\t\t\t\tType:  typeModel,\n\t\t\t\t\t\t\tValue: &StringValue{\"&Model{}\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tFunctions: []model.Function{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"Init\",\n\t\t\t\t\t\t\tArgs: nil,\n\t\t\t\t\t\t\tRets: []model.Variable{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName: \"err\",\n\t\t\t\t\t\t\t\t\tType: typeErr,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tCode: \"return nil\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tEnums: []model.Enum{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"Sex\",\n\t\t\t\t\t\t\tValues: []model.Constant{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName: \"Male\",\n\t\t\t\t\t\t\t\t\tType: &model.Type{\n\t\t\t\t\t\t\t\t\t\tName:     \"int\",\n\t\t\t\t\t\t\t\t\t\tKind:     model.KindInt,\n\t\t\t\t\t\t\t\t\t\tIndirect: false,\n\t\t\t\t\t\t\t\t\t\tCategory: 1,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tValue: &StringValue{\"1\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName: \"Femal\",\n\t\t\t\t\t\t\t\t\tType: &model.Type{\n\t\t\t\t\t\t\t\t\t\tName:     \"int\",\n\t\t\t\t\t\t\t\t\t\tKind:     model.KindInt,\n\t\t\t\t\t\t\t\t\t\tIndirect: false,\n\t\t\t\t\t\t\t\t\t\tCategory: 1,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tValue: &StringValue{\"2\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStructs: []model.Struct{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"Model\",\n\t\t\t\t\t\t\tFields: []model.Field{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName: \"A\",\n\t\t\t\t\t\t\t\t\tType: &model.Type{\n\t\t\t\t\t\t\t\t\t\tName:     \"[]byte\",\n\t\t\t\t\t\t\t\t\t\tKind:     model.KindSlice,\n\t\t\t\t\t\t\t\t\t\tIndirect: false,\n\t\t\t\t\t\t\t\t\t\tCategory: model.CategoryBinary,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tIsSetDefault: true,\n\t\t\t\t\t\t\t\t\tDefaultValue: &StringValue{\"[]byte(\\\"\\\")\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName: \"B\",\n\t\t\t\t\t\t\t\t\tType: &model.Type{\n\t\t\t\t\t\t\t\t\t\tName:     \"Base\",\n\t\t\t\t\t\t\t\t\t\tKind:     model.KindStruct,\n\t\t\t\t\t\t\t\t\t\tIndirect: false,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tCategory: model.CategoryUnion,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tMethods: []model.Method{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tReceiverName: \"self\",\n\t\t\t\t\t\t\tReceiverType: typeModel,\n\t\t\t\t\t\t\tByPtr:        true,\n\t\t\t\t\t\t\tFunction: model.Function{\n\t\t\t\t\t\t\t\tName: \"Bind\",\n\t\t\t\t\t\t\t\tArgs: []model.Variable{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tName: \"c\",\n\t\t\t\t\t\t\t\t\t\tType: &model.Type{\n\t\t\t\t\t\t\t\t\t\t\tName: \"RequestContext\",\n\t\t\t\t\t\t\t\t\t\t\tScope: &model.Model{\n\t\t\t\t\t\t\t\t\t\t\t\tPackageName: \"hertz\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tKind:     model.KindStruct,\n\t\t\t\t\t\t\t\t\t\t\tIndirect: true,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tRets: []model.Variable{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tName: \"error\",\n\t\t\t\t\t\t\t\t\t\tType: typeErr,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tCode: \"return nil\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tself := &HttpPackageGenerator{\n\t\t\t\tConfigPath:  tt.fields.ConfigPath,\n\t\t\t\tBackend:     tt.fields.Backend,\n\t\t\t\tHandlerDir:  tt.fields.handlerDir,\n\t\t\t\tRouterDir:   tt.fields.routerDir,\n\t\t\t\tModelDir:    tt.fields.modelDir,\n\t\t\t\tProjPackage: tt.fields.ProjPackage,\n\t\t\t\tTemplateGenerator: TemplateGenerator{\n\t\t\t\t\tOutputDir: tt.fields.OutputDir,\n\t\t\t\t\tConfig:    tt.fields.Config,\n\t\t\t\t\ttpls:      tt.fields.tpls,\n\t\t\t\t},\n\t\t\t\tOptions: []Option{\n\t\t\t\t\tOptionTypedefAsTypeAlias,\n\t\t\t\t\tOptionMarshalEnumToText,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\terr := self.LoadBackend(meta.BackendGolang)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif err := self.GenModel(tt.args.data, true); (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"IdlGenerator.GenModel() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\tif err := self.Persist(); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/generator/package.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"text/template\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"gopkg.in/yaml.v2\"\n)\n\ntype HttpPackage struct {\n\tIdlName    string\n\tPackage    string\n\tServices   []*Service\n\tModels     []*model.Model\n\tRouterInfo *Router\n}\n\ntype Service struct {\n\tName          string\n\tMethods       []*HttpMethod\n\tClientMethods []*ClientMethod\n\tModels        []*model.Model // all dependency models\n\tBaseDomain    string         // base domain for client code\n\tServiceGroup  string         // service level router group\n\tServiceGenDir string         // handler_dir for handler_by_service\n}\n\n// HttpPackageGenerator is used to record the configuration related to generating hertz http code.\ntype HttpPackageGenerator struct {\n\tConfigPath     string       // package template path\n\tBackend        meta.Backend // model template\n\tOptions        []Option\n\tCmdType        string\n\tProjPackage    string // go module for project\n\tHandlerDir     string\n\tRouterDir      string\n\tModelDir       string // like: biz/model or biz\\model (Windows)\n\tUseDir         string // XXX: should be UsePkg, not a filepath?\n\tClientDir      string // client dir for \"new\"/\"update\" command\n\tIdlClientDir   string // client dir for \"client\" command\n\tForceClientDir string // client dir without namespace for \"client\" command\n\tBaseDomain     string // request domain for \"client\" command\n\tQueryEnumAsInt bool   // client code use number for query parameter\n\tServiceGenDir  string\n\n\tNeedModel            bool\n\tHandlerByMethod      bool // generate handler files with method dimension\n\tSnakeStyleMiddleware bool // use snake name style for middleware\n\tSortRouter           bool\n\tForceUpdateClient    bool // force update 'hertz_client.go'\n\n\tloadedBackend   Backend\n\tcurModel        *model.Model\n\tprocessedModels map[*model.Model]bool\n\n\tTemplateGenerator\n}\n\nfunc (pkgGen *HttpPackageGenerator) Init() error {\n\tdefaultConfig := packageConfig\n\tcustomConfig := TemplateConfig{}\n\t// unmarshal from user-defined config file if it exists\n\tif pkgGen.ConfigPath != \"\" {\n\t\tcdata, err := ioutil.ReadFile(pkgGen.ConfigPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"read layout config from  %s failed, err: %v\", pkgGen.ConfigPath, err.Error())\n\t\t}\n\t\tif err = yaml.Unmarshal(cdata, &customConfig); err != nil {\n\t\t\treturn fmt.Errorf(\"unmarshal layout config failed, err: %v\", err.Error())\n\t\t}\n\t\tif reflect.DeepEqual(customConfig, TemplateConfig{}) {\n\t\t\treturn errors.New(\"empty config\")\n\t\t}\n\t}\n\n\tif pkgGen.tpls == nil {\n\t\tpkgGen.tpls = make(map[string]*template.Template, len(defaultConfig.Layouts))\n\t}\n\tif pkgGen.tplsInfo == nil {\n\t\tpkgGen.tplsInfo = make(map[string]*Template, len(defaultConfig.Layouts))\n\t}\n\n\t// extract routerTplName/middlewareTplName/handlerTplName/registerTplName/modelTplName/clientTplName directories\n\t// load default template\n\tfor _, layout := range defaultConfig.Layouts {\n\t\t// default template use \"fileName\" as template name\n\t\tpath := filepath.Base(layout.Path)\n\t\terr := pkgGen.loadLayout(layout, path, true)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// override the default template, other customized file template will be loaded by \"TemplateGenerator.Init\"\n\tfor _, layout := range customConfig.Layouts {\n\t\tif !IsDefaultPackageTpl(layout.Path) {\n\t\t\tcontinue\n\t\t}\n\t\terr := pkgGen.loadLayout(layout, layout.Path, true)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpkgGen.Config = &customConfig\n\t// load Model tpl if need\n\tif pkgGen.Backend != \"\" {\n\t\tif err := pkgGen.LoadBackend(pkgGen.Backend); err != nil {\n\t\t\treturn fmt.Errorf(\"load model template failed, err: %v\", err.Error())\n\t\t}\n\t}\n\n\tpkgGen.processedModels = make(map[*model.Model]bool)\n\tpkgGen.TemplateGenerator.isPackageTpl = true\n\n\treturn pkgGen.TemplateGenerator.Init()\n}\n\nfunc (pkgGen *HttpPackageGenerator) checkInited() (bool, error) {\n\tif pkgGen.tpls == nil {\n\t\tif err := pkgGen.Init(); err != nil {\n\t\t\treturn false, fmt.Errorf(\"init layout config failed, err: %v\", err.Error())\n\t\t}\n\t}\n\treturn pkgGen.ConfigPath == \"\", nil\n}\n\nfunc (pkgGen *HttpPackageGenerator) Generate(pkg *HttpPackage) error {\n\tif _, err := pkgGen.checkInited(); err != nil {\n\t\treturn err\n\t}\n\tif len(pkg.Models) != 0 {\n\t\tfor _, m := range pkg.Models {\n\t\t\tif err := pkgGen.GenModel(m, pkgGen.NeedModel); err != nil {\n\t\t\t\treturn fmt.Errorf(\"generate model %s failed, err: %v\", m.FilePath, err.Error())\n\t\t\t}\n\t\t}\n\t}\n\n\tif pkgGen.CmdType == meta.CmdClient {\n\t\t// default client dir\n\t\tclientDir := pkgGen.IdlClientDir\n\t\t// user specify client dir\n\t\tif len(pkgGen.ClientDir) != 0 {\n\t\t\tclientDir = pkgGen.ClientDir\n\t\t}\n\t\tif err := pkgGen.genClient(pkg, clientDir); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pkgGen.genCustomizedFile(pkg); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\t// this is for handler_by_service, the handler_dir is {$HANDLER_DIR}/{$PKG}\n\thandlerDir := util.SubDir(pkgGen.HandlerDir, pkg.Package)\n\tif pkgGen.HandlerByMethod {\n\t\thandlerDir = pkgGen.HandlerDir\n\t}\n\thandlerPackage := util.SubPackage(pkgGen.ProjPackage, handlerDir)\n\trouterDir := util.SubDir(pkgGen.RouterDir, pkg.Package)\n\trouterPackage := util.SubPackage(pkgGen.ProjPackage, routerDir)\n\n\troot := NewRouterTree()\n\tif err := pkgGen.genHandler(pkg, handlerDir, handlerPackage, root); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pkgGen.genRouter(pkg, root, handlerPackage, routerDir, routerPackage); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pkgGen.genCustomizedFile(pkg); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/hz/generator/package_tpl.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nvar (\n\trouterTplName           = \"router.go\"\n\tmiddlewareTplName       = \"middleware.go\"\n\tmiddlewareSingleTplName = \"middleware_single.go\"\n\thandlerTplName          = \"handler.go\"\n\thandlerSingleTplName    = \"handler_single.go\"\n\tmodelTplName            = \"model.go\"\n\tregisterTplName         = \"register.go\"\n\tclientTplName           = \"client.go\"       // generate a default client for server\n\thertzClientTplName      = \"hertz_client.go\" // underlying client for client command\n\tidlClientName           = \"idl_client.go\"   // client of service for quick call\n\n\tinsertPointNew        = \"//INSERT_POINT: DO NOT DELETE THIS LINE!\"\n\tinsertPointPatternNew = `//INSERT_POINT\\: DO NOT DELETE THIS LINE\\!`\n)\n\nvar templateNameSet = map[string]string{\n\trouterTplName:           routerTplName,\n\tmiddlewareTplName:       middlewareTplName,\n\tmiddlewareSingleTplName: middlewareSingleTplName,\n\thandlerTplName:          handlerTplName,\n\thandlerSingleTplName:    handlerSingleTplName,\n\tmodelTplName:            modelTplName,\n\tregisterTplName:         registerTplName,\n\tclientTplName:           clientTplName,\n\thertzClientTplName:      hertzClientTplName,\n\tidlClientName:           idlClientName,\n}\n\nfunc IsDefaultPackageTpl(name string) bool {\n\tif _, exist := templateNameSet[name]; exist {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nvar defaultPkgConfig = TemplateConfig{\n\tLayouts: []Template{\n\t\t{\n\t\t\tPath:   defaultHandlerDir + sp + handlerTplName,\n\t\t\tDelims: [2]string{\"{{\", \"}}\"},\n\t\t\tBody: `// Code generated by hertz generator.\n\npackage {{.PackageName}}\n\nimport (\n\t\"context\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\n{{- range $k, $v := .Imports}}\n\t{{$k}} \"{{$v.Package}}\"\n{{- end}}\n)\n\n{{range $_, $MethodInfo := .Methods}}\n{{$MethodInfo.Comment}}\nfunc {{$MethodInfo.Name}}(ctx context.Context, c *app.RequestContext) { \n\tvar err error\n\t{{if ne $MethodInfo.RequestTypeName \"\" -}}\n\tvar req {{$MethodInfo.RequestTypeName}}\n\terr = c.BindAndValidate(&req)\n\tif err != nil {\n\t\tc.String(consts.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\t{{end}}\n\tresp := new({{$MethodInfo.ReturnTypeName}})\n\n\tc.{{.Serializer}}(consts.StatusOK, resp)\n}\n{{end}}\n\t\t\t`,\n\t\t},\n\t\t{\n\t\t\tPath:   defaultRouterDir + sp + routerTplName,\n\t\t\tDelims: [2]string{\"{{\", \"}}\"},\n\t\t\tBody: `// Code generated by hertz generator. DO NOT EDIT.\n\npackage {{$.PackageName}}\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/app/server\"\n\n    {{- range $k, $v := .HandlerPackages}}\n        {{$k}} \"{{$v}}\"\n    {{- end}}\n)\n\n/*\n This file will register all the routes of the services in the master idl.\n And it will update automatically when you use the \"update\" command for the idl.\n So don't modify the contents of the file, or your code will be deleted when it is updated.\n */\n\n{{define \"g\"}}\n{{- if eq .Path \"/\"}}r\n{{- else}}{{.GroupName}}{{end}}\n{{- end}}\n\n{{define \"G\"}}\n{{- if ne .Handler \"\"}}\n\t{{- .GroupName}}.{{.HttpMethod}}(\"{{.Path}}\", append({{.HandlerMiddleware}}Mw(), {{.Handler}})...)\n{{- end}}\n{{- if ne (len .Children) 0}}\n{{.MiddleWare}} := {{template \"g\" .}}.Group(\"{{.Path}}\", {{.GroupMiddleware}}Mw()...)\n{{- end}}\n{{- range $_, $router := .Children}}\n{{- if ne .Handler \"\"}}\n\t{{template \"G\" $router}}\n{{- else}}\n\t{\t{{template \"G\" $router}}\n\t}\n{{- end}}\n{{- end}}\n{{- end}}\n\n// Register register routes based on the IDL 'api.${HTTP Method}' annotation.\nfunc Register(r *server.Hertz) {\n{{template \"G\" .Router}}\n}\n\n\t\t`,\n\t\t},\n\t\t{\n\t\t\tPath: defaultRouterDir + sp + registerTplName,\n\t\t\tBody: `// Code generated by hertz generator. DO NOT EDIT.\n\npackage {{.PackageName}}\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/app/server\"\n\t{{$.DepPkgAlias}} \"{{$.DepPkg}}\"\n)\n\n// GeneratedRegister registers routers generated by IDL.\nfunc GeneratedRegister(r *server.Hertz){\n\t` + insertPointNew + `\n\t{{$.DepPkgAlias}}.Register(r)\n}\n`,\n\t\t},\n\t\t// Model tpl is imported by model generator. Here only decides model directory.\n\t\t{\n\t\t\tPath: defaultModelDir + sp + modelTplName,\n\t\t\tBody: ``,\n\t\t},\n\t\t{\n\t\t\tPath:   defaultRouterDir + sp + middlewareTplName,\n\t\t\tDelims: [2]string{\"{{\", \"}}\"},\n\t\t\tBody: `// Code generated by hertz generator.\n\npackage {{$.PackageName}}\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/app\"\n)\n\n{{define \"M\"}}\n{{- if ne .Children.Len 0}}\nfunc {{.GroupMiddleware}}Mw() []app.HandlerFunc {\n\t// your code...\n\treturn nil\n}\n{{end}}\n{{- if ne .Handler \"\"}}\nfunc {{.HandlerMiddleware}}Mw() []app.HandlerFunc {\n\t// your code...\n\treturn nil\n}\n{{end}}\n{{range $_, $router := $.Children}}{{template \"M\" $router}}{{end}}\n{{- end}}\n\n{{template \"M\" .Router}}\n\n\t\t`,\n\t\t},\n\t\t{\n\t\t\tPath:   defaultClientDir + sp + clientTplName,\n\t\t\tDelims: [2]string{\"{{\", \"}}\"},\n\t\t\tBody: `// Code generated by hertz generator.\n\npackage {{$.PackageName}}\n\nimport (\n    \"github.com/cloudwego/hertz/pkg/app/client\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n)\n\ntype {{.ServiceName}}Client struct {\n\tclient * client.Client\n}\n\nfunc New{{.ServiceName}}Client(opt ...config.ClientOption) (*{{.ServiceName}}Client, error) {\n\tc, err := client.NewClient(opt...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &{{.ServiceName}}Client{\n\t\tclient: c,\n\t}, nil\n}\n\t\t`,\n\t\t},\n\t\t{\n\t\t\tPath:   defaultHandlerDir + sp + handlerSingleTplName,\n\t\t\tDelims: [2]string{\"{{\", \"}}\"},\n\t\t\tBody: `\n{{.Comment}}\nfunc {{.Name}}(ctx context.Context, c *app.RequestContext) { \n\tvar err error\n\t{{if ne .RequestTypeName \"\" -}}\n\tvar req {{.RequestTypeName}}\n\terr = c.BindAndValidate(&req)\n\tif err != nil {\n\t\tc.String(consts.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\t{{end}}\n\tresp := new({{.ReturnTypeName}})\n\n\tc.{{.Serializer}}(consts.StatusOK, resp)\n}\n`,\n\t\t},\n\t\t{\n\t\t\tPath:   defaultRouterDir + sp + middlewareSingleTplName,\n\t\t\tDelims: [2]string{\"{{\", \"}}\"},\n\t\t\tBody: `\nfunc {{.MiddleWare}}Mw() []app.HandlerFunc {\n\t// your code...\n\treturn nil\n}\n`,\n\t\t},\n\t\t{\n\t\t\tPath:   defaultRouterDir + sp + hertzClientTplName,\n\t\t\tDelims: [2]string{\"{{\", \"}}\"},\n\t\t\tBody:   hertzClientTpl,\n\t\t},\n\t\t{\n\t\t\tPath:   defaultRouterDir + sp + idlClientName,\n\t\t\tDelims: [2]string{\"{{\", \"}}\"},\n\t\t\tBody:   idlClientTpl,\n\t\t},\n\t},\n}\n\nvar hertzClientTpl = `// Code generated by hz.\n\npackage {{.PackageName}}\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\n\thertz_client \"github.com/cloudwego/hertz/pkg/app/client\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/client\"\n)\n\ntype use interface {\n\tUse(mws ...hertz_client.Middleware)\n}\n\n// Definition of global data and types.\ntype ResponseResultDecider func(statusCode int, rawResponse *protocol.Response) (isError bool)\n\ntype (\n\tbindRequestBodyFunc func(c *cli, r *request) (contentType string, body io.Reader, err error)\n\tbeforeRequestFunc   func(*cli, *request) error\n\tafterResponseFunc   func(*cli, *response) error\n)\n\nvar (\n\thdrContentTypeKey     = http.CanonicalHeaderKey(\"Content-Type\")\n\thdrContentEncodingKey = http.CanonicalHeaderKey(\"Content-Encoding\")\n\n\tplainTextType   = \"text/plain; charset=utf-8\"\n\tjsonContentType = \"application/json; charset=utf-8\"\n\tformContentType = \"multipart/form-data\"\n\n\tjsonCheck = regexp.MustCompile(` + \"`(?i:(application|text)/(json|.*\\\\+json|json\\\\-.*)(; |$))`)\\n\" +\n\t`xmlCheck  = regexp.MustCompile(` + \"`(?i:(application|text)/(xml|.*\\\\+xml)(; |$))`)\\n\" +\n\t`\n)\n\n// Configuration of client\ntype Option struct {\n\tf func(*Options)\n}\n\ntype Options struct {\n\thostUrl               string\n\tdoer                  client.Doer\n\theader                http.Header\n\trequestBodyBind       bindRequestBodyFunc\n\tresponseResultDecider ResponseResultDecider\n\tmiddlewares           []hertz_client.Middleware\n\tclientOption          []config.ClientOption\n}\n\nfunc getOptions(ops ...Option) *Options {\n\topts := &Options{}\n\tfor _, do := range ops {\n\t\tdo.f(opts)\n\t}\n\treturn opts\n}\n\n// WithHertzClientOption is used to pass configuration for the hertz client\nfunc WithHertzClientOption(opt ...config.ClientOption) Option {\n\treturn Option{func(op *Options) {\n\t\top.clientOption = append(op.clientOption, opt...)\n\t}}\n}\n\n// WithHertzClientMiddleware is used to register the middleware for the hertz client\nfunc WithHertzClientMiddleware(mws ...hertz_client.Middleware) Option {\n\treturn Option{func(op *Options) {\n\t\top.middlewares = append(op.middlewares, mws...)\n\t}}\n}\n\n// WithHertzClient is used to register a custom hertz client\nfunc WithHertzClient(client client.Doer) Option {\n\treturn Option{func(op *Options) {\n\t\top.doer = client\n\t}}\n}\n\n// WithHeader is used to add the default header, which is carried by every request\nfunc WithHeader(header http.Header) Option {\n\treturn Option{func(op *Options) {\n\t\top.header = header\n\t}}\n}\n\n// WithResponseResultDecider configure custom deserialization of http response to response struct\nfunc WithResponseResultDecider(decider ResponseResultDecider) Option {\n\treturn Option{func(op *Options) {\n\t\top.responseResultDecider = decider\n\t}}\n}\n\nfunc withHostUrl(HostUrl string) Option {\n\treturn Option{func(op *Options) {\n\t\top.hostUrl = HostUrl\n\t}}\n}\n\n// underlying client\ntype cli struct {\n\thostUrl               string\n\tdoer                  client.Doer\n\theader                http.Header\n\tbindRequestBody       bindRequestBodyFunc\n\tresponseResultDecider ResponseResultDecider\n\n\tbeforeRequest []beforeRequestFunc\n\tafterResponse []afterResponseFunc\n}\n\nfunc (c *cli) Use(mws ...hertz_client.Middleware) error {\n\tu, ok := c.doer.(use)\n\tif !ok {\n\t\treturn errors.NewPublic(\"doer does not support middleware, choose the right doer.\")\n\t}\n\tu.Use(mws...)\n\treturn nil\n}\n\nfunc newClient(opts *Options) (*cli, error) {\n\tif opts.requestBodyBind == nil {\n\t\topts.requestBodyBind = defaultRequestBodyBind\n\t}\n\tif opts.responseResultDecider == nil {\n\t\topts.responseResultDecider = defaultResponseResultDecider\n\t}\n\tif opts.doer == nil {\n\t\tcli, err := hertz_client.NewClient(opts.clientOption...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\topts.doer = cli\n\t}\n\n\tc := &cli{\n\t\thostUrl:               opts.hostUrl,\n\t\tdoer:                  opts.doer,\n\t\theader:                opts.header,\n\t\tbindRequestBody:       opts.requestBodyBind,\n\t\tresponseResultDecider: opts.responseResultDecider,\n\t\tbeforeRequest: []beforeRequestFunc{\n\t\t\tparseRequestURL,\n\t\t\tparseRequestHeader,\n\t\t\tcreateHTTPRequest,\n\t\t},\n\t\tafterResponse: []afterResponseFunc{\n\t\t\tparseResponseBody,\n\t\t},\n\t}\n\n\tif len(opts.middlewares) != 0 {\n\t\tif err := c.Use(opts.middlewares...); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn c, nil\n}\n\nfunc (c *cli) execute(req *request) (*response, error) {\n\tvar err error\n\tfor _, f := range c.beforeRequest {\n\t\tif err = f(c, req); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif hostHeader := req.header.Get(\"Host\"); hostHeader != \"\" {\n\t\treq.rawRequest.Header.SetHost(hostHeader)\n\t}\n\n\tresp := protocol.Response{}\n\n\terr = c.doer.Do(req.ctx, req.rawRequest, &resp)\n\n\tresponse := &response{\n\t\trequest:     req,\n\t\trawResponse: &resp,\n\t}\n\n\tif err != nil {\n\t\treturn response, err\n\t}\n\n\tbody, err := resp.BodyE()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif strings.EqualFold(resp.Header.Get(hdrContentEncodingKey), \"gzip\") && resp.Header.ContentLength() != 0 {\n\t\tbody, err = resp.BodyGunzip()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tresponse.bodyByte = body\n\n\tresponse.size = int64(len(response.bodyByte))\n\n\t// Apply Response middleware\n\tfor _, f := range c.afterResponse {\n\t\tif err = f(c, response); err != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn response, err\n}\n\n// r get request\nfunc (c *cli) r() *request {\n\treturn &request{\n\t\tqueryParam:     url.Values{},\n\t\theader:         http.Header{},\n\t\tpathParam:      map[string]string{},\n\t\tformParam:      map[string]string{},\n\t\tfileParam:      map[string]string{},\n\t\tclient:         c,\n\t\tqueryEnumAsInt: {{.Config.QueryEnumAsInt}},\n\t}\n}\n\ntype response struct {\n\trequest     *request\n\trawResponse *protocol.Response\n\n\tbodyByte []byte\n\tsize     int64\n}\n\n// statusCode method returns the HTTP status code for the executed request.\nfunc (r *response) statusCode() int {\n\tif r.rawResponse == nil {\n\t\treturn 0\n\t}\n\n\treturn r.rawResponse.StatusCode()\n}\n\n// body method returns HTTP response as []byte array for the executed request.\nfunc (r *response) body() []byte {\n\tif r.rawResponse == nil {\n\t\treturn []byte{}\n\t}\n\treturn r.bodyByte\n}\n\n// Header method returns the response headers\nfunc (r *response) header() http.Header {\n\tif r.rawResponse == nil {\n\t\treturn http.Header{}\n\t}\n\th := http.Header{}\n\tr.rawResponse.Header.VisitAll(func(key, value []byte) {\n\t\th.Add(string(key), string(value))\n\t})\n\n\treturn h\n}\n\ntype request struct {\n\tclient         *cli\n\turl            string\n\tmethod         string\n\tqueryEnumAsInt bool\n\tqueryParam     url.Values\n\theader         http.Header\n\tpathParam      map[string]string\n\tformParam      map[string]string\n\tfileParam      map[string]string\n\tbodyParam      interface{}\n\trawRequest     *protocol.Request\n\tctx            context.Context\n\trequestOptions []config.RequestOption\n\tresult         interface{}\n\tError          interface{}\n}\n\nfunc (r *request) setContext(ctx context.Context) *request {\n\tr.ctx = ctx\n\treturn r\n}\n\nfunc (r *request) context() context.Context {\n\treturn r.ctx\n}\n\nfunc (r *request) setHeader(header, value string) *request {\n\tr.header.Set(header, value)\n\treturn r\n}\n\nfunc (r *request) addHeader(header, value string) *request {\n\tr.header.Add(header, value)\n\treturn r\n}\n\nfunc (r *request) addHeaders(params map[string]string) *request {\n\tfor k, v := range params {\n\t\tr.addHeader(k, v)\n\t}\n\treturn r\n}\n\n\nfunc (r *request) setQueryParam(param string, value interface{}) *request {\n\tif value == nil {\n\t\treturn r\n\t}\n\tv := reflect.ValueOf(value)\n\tif v.Kind() == reflect.Pointer && v.IsNil() {\n\t\treturn r\n\t}\n\tswitch v.Kind() {\n\tcase reflect.Slice, reflect.Array:\n\t\tfor index := 0; index < v.Len(); index++ {\n\t\t\tif r.queryEnumAsInt && (v.Index(index).Kind() == reflect.Int32 || v.Index(index).Kind() == reflect.Int64) {\n\t\t\t\tr.queryParam.Add(param, fmt.Sprintf(\"%d\", v.Index(index).Interface()))\n\t\t\t} else {\n\t\t\t\tr.queryParam.Add(param, fmt.Sprint(v.Index(index).Interface()))\n\t\t\t}\n\t\t}\n\tcase reflect.Int32, reflect.Int64:\n\t\tif r.queryEnumAsInt {\n\t\t\tr.queryParam.Add(param, fmt.Sprintf(\"%d\", v.Interface()))\n\t\t} else {\n\t\t\tr.queryParam.Add(param, fmt.Sprint(v))\n\t\t}\n\tdefault:\n\t\tr.queryParam.Set(param, fmt.Sprint(v))\n\t}\n\treturn r\n}\n\nfunc (r *request) setResult(res interface{}) *request {\n\tr.result = res\n\treturn r\n}\n\nfunc (r *request) setError(err interface{}) *request {\n\tr.Error = err\n\treturn r\n}\n\nfunc (r *request) setHeaders(headers map[string]string) *request {\n\tfor h, v := range headers {\n\t\tr.setHeader(h, v)\n\t}\n\n\treturn r\n}\n\nfunc (r *request) setQueryParams(params map[string]interface{}) *request {\n\tfor p, v := range params {\n\t\tr.setQueryParam(p, v)\n\t}\n\n\treturn r\n}\n\nfunc (r *request) setPathParams(params map[string]string) *request {\n\tfor p, v := range params {\n\t\tr.pathParam[p] = v\n\t}\n\treturn r\n}\n\nfunc (r *request) setFormParams(params map[string]string) *request {\n\tfor p, v := range params {\n\t\tr.formParam[p] = v\n\t}\n\treturn r\n}\n\nfunc (r *request) setFormFileParams(params map[string]string) *request {\n\tfor p, v := range params {\n\t\tr.fileParam[p] = v\n\t}\n\treturn r\n}\n\nfunc (r *request) setBodyParam(body interface{}) *request {\n\tr.bodyParam = body\n\treturn r\n}\n\nfunc (r *request) setRequestOption(option ...config.RequestOption) *request {\n\tr.requestOptions = append(r.requestOptions, option...)\n\treturn r\n}\n\nfunc (r *request) execute(method, url string) (*response, error) {\n\tr.method = method\n\tr.url = url\n\treturn r.client.execute(r)\n}\n\nfunc parseRequestURL(c *cli, r *request) error {\n\tif len(r.pathParam) > 0 {\n\t\tfor p, v := range r.pathParam {\n\t\t\tr.url = strings.Replace(r.url, \":\"+p, url.PathEscape(v), -1)\n\t\t}\n\t}\n\n\t// Parsing request URL\n\treqURL, err := url.Parse(r.url)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// If request.URL is relative path then added c.HostURL into\n\t// the request URL otherwise request.URL will be used as-is\n\tif !reqURL.IsAbs() {\n\t\tr.url = reqURL.String()\n\t\tif len(r.url) > 0 && r.url[0] != '/' {\n\t\t\tr.url = \"/\" + r.url\n\t\t}\n\n\t\treqURL, err = url.Parse(c.hostUrl + r.url)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Adding Query Param\n\tquery := make(url.Values)\n\n\tfor k, v := range r.queryParam {\n\t\t// remove query param from client level by key\n\t\t// since overrides happens for that key in the request\n\t\tquery.Del(k)\n\t\tfor _, iv := range v {\n\t\t\tquery.Add(k, iv)\n\t\t}\n\t}\n\n\tif len(query) > 0 {\n\t\tif isStringEmpty(reqURL.RawQuery) {\n\t\t\treqURL.RawQuery = query.Encode()\n\t\t} else {\n\t\t\treqURL.RawQuery = reqURL.RawQuery + \"&\" + query.Encode()\n\t\t}\n\t}\n\n\tr.url = reqURL.String()\n\n\treturn nil\n}\n\nfunc isStringEmpty(str string) bool {\n\treturn len(strings.TrimSpace(str)) == 0\n}\n\nfunc parseRequestHeader(c *cli, r *request) error {\n\thdr := make(http.Header)\n\tif c.header != nil {\n\t\tfor k := range c.header {\n\t\t\thdr[k] = append(hdr[k], c.header[k]...)\n\t\t}\n\t}\n\n\tfor k := range r.header {\n\t\thdr.Del(k)\n\t\thdr[k] = append(hdr[k], r.header[k]...)\n\t}\n\n\tif len(r.formParam) != 0 || len(r.fileParam) != 0 {\n\t\thdr.Add(hdrContentTypeKey, formContentType)\n\t}\n\n\tr.header = hdr\n\treturn nil\n}\n\n// detectContentType method is used to figure out \"request.Body\" content type for request header\nfunc detectContentType(body interface{}) string {\n\tcontentType := plainTextType\n\tkind := reflect.Indirect(reflect.ValueOf(body)).Kind()\n\tswitch kind {\n\tcase reflect.Struct, reflect.Map:\n\t\tcontentType = jsonContentType\n\tcase reflect.String:\n\t\tcontentType = plainTextType\n\tdefault:\n\t\tif b, ok := body.([]byte); ok {\n\t\t\tcontentType = http.DetectContentType(b)\n\t\t} else if kind == reflect.Slice {\n\t\t\tcontentType = jsonContentType\n\t\t}\n\t}\n\n\treturn contentType\n}\n\nfunc defaultRequestBodyBind(c *cli, r *request) (contentType string, body io.Reader, err error) {\n\tif !isPayloadSupported(r.method) {\n\t\treturn\n\t}\n\tvar bodyBytes []byte\n\tcontentType = r.header.Get(hdrContentTypeKey)\n\tif isStringEmpty(contentType) {\n\t\tcontentType = detectContentType(r.bodyParam)\n\t\tr.header.Set(hdrContentTypeKey, contentType)\n\t}\n\tkind := reflect.Indirect(reflect.ValueOf(r.bodyParam)).Kind()\n\tif isJSONType(contentType) &&\n\t\t(kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {\n\t\tbodyBytes, err = json.Marshal(r.bodyParam)\n\t} else if isXMLType(contentType) && (kind == reflect.Struct) {\n\t\tbodyBytes, err = xml.Marshal(r.bodyParam)\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\treturn contentType, strings.NewReader(string(bodyBytes)), nil\n}\n\nfunc isPayloadSupported(m string) bool {\n\treturn !(m == http.MethodHead || m == http.MethodOptions || m == http.MethodGet || m == http.MethodDelete)\n}\n\nfunc createHTTPRequest(c *cli, r *request) (err error) {\n\tcontentType, body, err := c.bindRequestBody(c, r)\n\tif !isStringEmpty(contentType) {\n\t\tr.header.Set(hdrContentTypeKey, contentType)\n\t}\n\tif err == nil {\n\t\tr.rawRequest = protocol.NewRequest(r.method, r.url, body)\n\t\tif contentType == formContentType && isPayloadSupported(r.method) {\n\t\t\tif r.rawRequest.IsBodyStream() {\n\t\t\t\tr.rawRequest.ResetBody()\n\t\t\t}\n\t\t\tr.rawRequest.SetMultipartFormData(r.formParam)\n\t\t\tr.rawRequest.SetFiles(r.fileParam)\n\t\t}\n\t\tfor key, values := range r.header {\n\t\t\tfor _, val := range values {\n\t\t\t\tr.rawRequest.Header.Add(key, val)\n\t\t\t}\n\t\t}\n\t\tr.rawRequest.SetOptions(r.requestOptions...)\n\t}\n\treturn err\n}\n\nfunc silently(_ ...interface{}) {}\n\n// defaultResponseResultDecider method returns true if HTTP status code >= 400 otherwise false.\nfunc defaultResponseResultDecider(statusCode int, rawResponse *protocol.Response) bool {\n\treturn statusCode > 399\n}\n\n// IsJSONType method is to check JSON content type or not\nfunc isJSONType(ct string) bool {\n\treturn jsonCheck.MatchString(ct)\n}\n\n// IsXMLType method is to check XML content type or not\nfunc isXMLType(ct string) bool {\n\treturn xmlCheck.MatchString(ct)\n}\n\nfunc parseResponseBody(c *cli, res *response) (err error) {\n\tif res.statusCode() == http.StatusNoContent {\n\t\treturn\n\t}\n\t// Handles only JSON or XML content type\n\tct := res.header().Get(hdrContentTypeKey)\n\n\tisError := c.responseResultDecider(res.statusCode(), res.rawResponse)\n\tif isError {\n\t\tif res.request.Error != nil {\n\t\t\tif isJSONType(ct) || isXMLType(ct) {\n\t\t\t\terr = unmarshalContent(ct, res.bodyByte, res.request.Error)\n\t\t\t}\n\t\t} else {\n\t\t\tjsonByte, jsonErr := json.Marshal(map[string]interface{}{\n\t\t\t\t\"status_code\": res.rawResponse.StatusCode(),\n\t\t\t\t\"body\":        string(res.bodyByte),\n\t\t\t})\n\t\t\tif jsonErr != nil {\n\t\t\t\treturn jsonErr\n\t\t\t}\n\t\t\terr = errors.NewPublic(string(jsonByte))\n\t\t}\n\t} else if res.request.result != nil {\n\t\tif isJSONType(ct) || isXMLType(ct) {\n\t\t\terr = unmarshalContent(ct, res.bodyByte, res.request.result)\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\n// unmarshalContent content into object from JSON or XML\nfunc unmarshalContent(ct string, b []byte, d interface{}) (err error) {\n\tif isJSONType(ct) {\n\t\terr = json.Unmarshal(b, d)\n\t} else if isXMLType(ct) {\n\t\terr = xml.Unmarshal(b, d)\n\t}\n\n\treturn\n}\n\n`\n\nvar idlClientTpl = `// Code generated by hertz generator.\n\npackage {{.PackageName}}\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n{{- range $k, $v := .Imports}}\n\t{{$k}} \"{{$v.Package}}\"\n{{- end}}\n)\n\n// unused protection\nvar (\n\t_ = fmt.Formatter(nil)\n)\n\ntype Client interface {\n\t{{range $_, $MethodInfo := .ClientMethods}}\n\t\t{{$MethodInfo.Name}}(context context.Context, req *{{$MethodInfo.RequestTypeName}}, reqOpt ...config.RequestOption) (resp *{{$MethodInfo.ReturnTypeName}}, rawResponse *protocol.Response, err error)\n\t{{end}}\n}\n\ntype {{.ServiceName}}Client struct {\n\tclient *cli\n}\n\nfunc New{{.ServiceName}}Client(hostUrl string, ops ...Option) (Client, error) {\n\topts := getOptions(append(ops, withHostUrl(hostUrl))...)\n\tcli, err := newClient(opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &{{.ServiceName}}Client{\n\t\tclient: cli,\n\t}, nil\n}\n\n{{range $_, $MethodInfo := .ClientMethods}}\nfunc (s *{{$.ServiceName}}Client) {{$MethodInfo.Name}}(context context.Context, req *{{$MethodInfo.RequestTypeName}}, reqOpt ...config.RequestOption) (resp *{{$MethodInfo.ReturnTypeName}}, rawResponse *protocol.Response, err error) {\n\thttpResp := &{{$MethodInfo.ReturnTypeName}}{}\n\tret, err := s.client.r().\n\t\tsetContext(context).\n\t\tsetQueryParams(map[string]interface{}{\n\t\t\t{{$MethodInfo.QueryParamsCode}}\n\t\t}).\n\t\tsetPathParams(map[string]string{\n\t\t\t{{$MethodInfo.PathParamsCode}}\n\t\t}).\n\t\taddHeaders(map[string]string{\n\t\t\t{{$MethodInfo.HeaderParamsCode}}\n\t\t}).\n\t\tsetFormParams(map[string]string{\n\t\t\t{{$MethodInfo.FormValueCode}}\n\t\t}).\n\t\tsetFormFileParams(map[string]string{\n\t\t\t{{$MethodInfo.FormFileCode}}\n\t\t}).\n\t\t{{$MethodInfo.BodyParamsCode}}\n\t\tsetRequestOption(reqOpt...).\n\t\tsetResult(httpResp).\n\t\texecute(\"{{if EqualFold $MethodInfo.HTTPMethod \"Any\"}}POST{{else}}{{ $MethodInfo.HTTPMethod }}{{end}}\", \"{{$MethodInfo.Path}}\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n    \n\tresp = httpResp\n\trawResponse = ret.rawResponse\n\treturn resp, rawResponse, nil\n}\n{{end}}\n\nvar defaultClient, _ = New{{.ServiceName}}Client(\"{{.BaseDomain}}\")\n\nfunc ConfigDefaultClient(ops ...Option) (err error) {\n\tdefaultClient, err = New{{.ServiceName}}Client(\"{{.BaseDomain}}\", ops...)\n\treturn\n}\n\n{{range $_, $MethodInfo := .ClientMethods}}\nfunc {{$MethodInfo.Name}}(context context.Context, req *{{$MethodInfo.RequestTypeName}}, reqOpt ...config.RequestOption) (resp *{{$MethodInfo.ReturnTypeName}}, rawResponse *protocol.Response, err error) {\n\treturn defaultClient.{{$MethodInfo.Name}}(context, req, reqOpt...)\n}\n{{end}}\n`\n"
  },
  {
    "path": "cmd/hz/generator/router.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n)\n\ntype Router struct {\n\tFilePath        string\n\tPackageName     string\n\tHandlerPackages map[string]string // {{basename}}:{{import_path}}\n\tRouter          *RouterNode\n}\n\ntype RouterNode struct {\n\tGroupName         string // current group name(the parent middleware name), used to register route. example: {{.GroupName}}.{{HttpMethod}}\n\tMiddleWare        string // current node middleware, used to be group name for children.\n\tHandlerMiddleware string\n\tGroupMiddleware   string\n\tPathPrefix        string\n\n\tPath     string\n\tParent   *RouterNode\n\tChildren childrenRouterInfo\n\n\tHandler             string // {{HandlerPackage}}.{{HandlerName}}\n\tHandlerPackage      string\n\tHandlerPackageAlias string\n\tHttpMethod          string\n}\n\ntype RegisterInfo struct {\n\tPackageName string\n\tDepPkgAlias string\n\tDepPkg      string\n}\n\n// NewRouterTree contains \"/\" as root node\nfunc NewRouterTree() *RouterNode {\n\treturn &RouterNode{\n\t\tGroupName:       \"root\",\n\t\tMiddleWare:      \"root\",\n\t\tGroupMiddleware: \"root\",\n\t\tPath:            \"/\",\n\t\tParent:          nil,\n\t}\n}\n\nfunc (routerNode *RouterNode) Sort() {\n\tsort.Sort(routerNode.Children)\n}\n\nfunc (routerNode *RouterNode) Update(method *HttpMethod, handlerType, handlerPkg string, sortRouter bool) error {\n\tif method.Path == \"\" {\n\t\treturn fmt.Errorf(\"empty path for method '%s'\", method.Name)\n\t}\n\tpaths := strings.Split(method.Path, \"/\")\n\tif paths[0] == \"\" {\n\t\tpaths = paths[1:]\n\t}\n\tparent, last := routerNode.FindNearest(paths, method.HTTPMethod, sortRouter)\n\tif last == len(paths) {\n\t\treturn fmt.Errorf(\"path '%s' has been registered\", method.Path)\n\t}\n\tname := util.ToVarName(paths[:last])\n\tparent.Insert(name, method, handlerType, paths[last:], handlerPkg, sortRouter)\n\tparent.Sort()\n\treturn nil\n}\n\nfunc (routerNode *RouterNode) RawHandlerName() string {\n\tparts := strings.Split(routerNode.Handler, \".\")\n\thandlerName := parts[len(parts)-1]\n\treturn handlerName\n}\n\n// DyeGroupName traverses the routing tree in depth and names the handler/group middleware for each node.\n// If snakeStyleMiddleware is set to true, the name style of the middleware will use snake name style.\nfunc (routerNode *RouterNode) DyeGroupName(snakeStyleMiddleware bool) error {\n\tgroups := []string{\"root\"}\n\n\thook := func(layer int, node *RouterNode) error {\n\t\tnode.GroupName = groups[layer]\n\t\tif node.MiddleWare == \"\" {\n\t\t\tpname := node.Path\n\t\t\tif len(pname) > 1 && pname[0] == '/' {\n\t\t\t\tpname = pname[1:]\n\t\t\t}\n\n\t\t\tif node.Parent != nil {\n\t\t\t\tnode.PathPrefix = node.Parent.PathPrefix + \"_\" + util.ToGoFuncName(pname)\n\t\t\t} else {\n\t\t\t\tnode.PathPrefix = \"_\" + util.ToGoFuncName(pname)\n\t\t\t}\n\n\t\t\thandlerMiddlewareName := \"\"\n\t\t\tisLeafNode := false\n\t\t\tif len(node.Handler) != 0 {\n\t\t\t\thandlerMiddlewareName = node.RawHandlerName()\n\t\t\t\t// If it is a leaf node, then \"group middleware name\" and \"handler middleware name\" are the same\n\t\t\t\tif len(node.Children) == 0 {\n\t\t\t\t\tpname = handlerMiddlewareName\n\t\t\t\t\tisLeafNode = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpname = convertToMiddlewareName(pname)\n\t\t\thandlerMiddlewareName = convertToMiddlewareName(handlerMiddlewareName)\n\n\t\t\tif isLeafNode {\n\t\t\t\tname, err := util.GetMiddlewareUniqueName(pname)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"get unique name for middleware '%s' failed, err: %v\", name, err)\n\t\t\t\t}\n\t\t\t\tpname = name\n\t\t\t\thandlerMiddlewareName = name\n\t\t\t} else {\n\t\t\t\tvar err error\n\t\t\t\tpname, err = util.GetMiddlewareUniqueName(pname)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"get unique name for middleware '%s' failed, err: %v\", pname, err)\n\t\t\t\t}\n\t\t\t\thandlerMiddlewareName, err = util.GetMiddlewareUniqueName(handlerMiddlewareName)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"get unique name for middleware '%s' failed, err: %v\", handlerMiddlewareName, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tnode.MiddleWare = \"_\" + pname\n\t\t\tif len(node.Handler) != 0 {\n\t\t\t\tnode.HandlerMiddleware = \"_\" + handlerMiddlewareName\n\t\t\t\tif snakeStyleMiddleware {\n\t\t\t\t\tnode.HandlerMiddleware = \"_\" + node.RawHandlerName()\n\t\t\t\t}\n\t\t\t}\n\t\t\tnode.GroupMiddleware = node.MiddleWare\n\t\t\tif snakeStyleMiddleware {\n\t\t\t\tnode.GroupMiddleware = node.PathPrefix\n\t\t\t}\n\t\t}\n\t\tif layer >= len(groups)-1 {\n\t\t\tgroups = append(groups, node.MiddleWare)\n\t\t} else {\n\t\t\tgroups[layer+1] = node.MiddleWare\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Deep traversal from the 0th level of the routing tree.\n\terr := routerNode.DFS(0, hook)\n\treturn err\n}\n\nfunc (routerNode *RouterNode) DFS(i int, hook func(layer int, node *RouterNode) error) error {\n\tif routerNode == nil {\n\t\treturn nil\n\t}\n\terr := hook(i, routerNode)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, n := range routerNode.Children {\n\t\terr = n.DFS(i+1, hook)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nvar handlerPkgMap map[string]string\n\nfunc (routerNode *RouterNode) Insert(name string, method *HttpMethod, handlerType string, paths []string, handlerPkg string, sortRouter bool) {\n\tcur := routerNode\n\tfor i, p := range paths {\n\t\tc := &RouterNode{\n\t\t\tPath:   \"/\" + p,\n\t\t\tParent: cur,\n\t\t}\n\t\tif i == len(paths)-1 {\n\t\t\t// generate handler by method\n\t\t\tif len(handlerPkg) != 0 {\n\t\t\t\t// get a unique package alias for every handler\n\t\t\t\tpkgAlias := filepath.Base(handlerPkg)\n\t\t\t\tpkgAlias = util.ToVarName([]string{pkgAlias})\n\t\t\t\tval, exist := handlerPkgMap[handlerPkg]\n\t\t\t\tif !exist {\n\t\t\t\t\tpkgAlias, _ = util.GetHandlerPackageUniqueName(pkgAlias)\n\t\t\t\t\tif len(handlerPkgMap) == 0 {\n\t\t\t\t\t\thandlerPkgMap = make(map[string]string, 10)\n\t\t\t\t\t}\n\t\t\t\t\thandlerPkgMap[handlerPkg] = pkgAlias\n\t\t\t\t} else {\n\t\t\t\t\tpkgAlias = val\n\t\t\t\t}\n\t\t\t\tc.HandlerPackageAlias = pkgAlias\n\t\t\t\tc.Handler = pkgAlias + \".\" + method.Name\n\t\t\t\tc.HandlerPackage = handlerPkg\n\t\t\t\tmethod.RefPackage = c.HandlerPackage\n\t\t\t\tmethod.RefPackageAlias = c.HandlerPackageAlias\n\t\t\t} else { // generate handler by service\n\t\t\t\tc.Handler = handlerType + \".\" + method.Name\n\t\t\t\tif len(method.RefPackage) != 0 {\n\t\t\t\t\tc.Handler = method.RefPackageAlias + \".\" + method.Name\n\t\t\t\t\tc.HandlerPackageAlias = method.RefPackageAlias\n\t\t\t\t\tc.HandlerPackage = method.RefPackage\n\t\t\t\t}\n\t\t\t}\n\t\t\tc.HttpMethod = getHttpMethod(method.HTTPMethod)\n\t\t}\n\t\tif cur.Children == nil {\n\t\t\tcur.Children = make([]*RouterNode, 0, 1)\n\t\t}\n\t\tcur.Children = append(cur.Children, c)\n\t\tif sortRouter {\n\t\t\tsort.Sort(cur.Children)\n\t\t}\n\t\tcur = c\n\t}\n}\n\nfunc getHttpMethod(method string) string {\n\tif strings.EqualFold(method, \"Any\") {\n\t\treturn \"Any\"\n\t}\n\treturn strings.ToUpper(method)\n}\n\nfunc (routerNode *RouterNode) FindNearest(paths []string, method string, sortRouter bool) (*RouterNode, int) {\n\tns := len(paths)\n\tcur := routerNode\n\ti := 0\n\tpath := paths[i]\n\tfor j := 0; j < len(cur.Children); j++ {\n\t\tc := cur.Children[j]\n\t\ttmpMethod := \"\" // group do not have http method\n\t\tif i == ns {    // only i==ns, the path is http method node\n\t\t\ttmpMethod = method\n\t\t}\n\t\tif (\"/\" + path) == c.Path {\n\t\t\tif sortRouter && !strings.EqualFold(c.HttpMethod, tmpMethod) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ti++\n\t\t\tif i == ns {\n\t\t\t\treturn cur, i - 1\n\t\t\t}\n\t\t\tpath = paths[i]\n\t\t\tcur = c\n\t\t\tj = -1\n\t\t}\n\t}\n\treturn cur, i\n}\n\ntype childrenRouterInfo []*RouterNode\n\n// Len is the number of elements in the collection.\nfunc (c childrenRouterInfo) Len() int {\n\treturn len(c)\n}\n\n// Less reports whether the element with\n// index i should sort before the element with index j.\nfunc (c childrenRouterInfo) Less(i, j int) bool {\n\tif c[i].HttpMethod == \"\" && c[j].HttpMethod != \"\" {\n\t\treturn false\n\t}\n\tif c[i].HttpMethod != \"\" && c[j].HttpMethod == \"\" {\n\t\treturn true\n\t}\n\t// remove non-litter char\n\t// eg. /a -> a\n\t//     /:a -> a\n\tci := removeNonLetterPrefix(c[i].Path)\n\tcj := removeNonLetterPrefix(c[j].Path)\n\n\t// if ci == cj, use HTTP method for sort, preventing sorting inconsistencies\n\tif ci == cj {\n\t\treturn c[i].HttpMethod < c[j].HttpMethod\n\t}\n\n\treturn ci < cj\n}\n\nfunc removeNonLetterPrefix(str string) string {\n\tfor i, char := range str {\n\t\tif unicode.IsLetter(char) || unicode.IsDigit(char) {\n\t\t\treturn str[i:]\n\t\t}\n\t}\n\treturn str\n}\n\n// Swap swaps the elements with indexes i and j.\nfunc (c childrenRouterInfo) Swap(i, j int) {\n\tc[i], c[j] = c[j], c[i]\n}\n\nvar (\n\tregRegisterV3 = regexp.MustCompile(insertPointPatternNew)\n\tregImport     = regexp.MustCompile(`import \\(\\n`)\n)\n\nfunc (pkgGen *HttpPackageGenerator) updateRegister(pkg, rDir, pkgName string) error {\n\tif pkgGen.tplsInfo[registerTplName].Disable {\n\t\treturn nil\n\t}\n\tregister := RegisterInfo{\n\t\tPackageName: filepath.Base(rDir),\n\t\tDepPkgAlias: strings.ReplaceAll(pkgName, \"/\", \"_\"),\n\t\tDepPkg:      pkg,\n\t}\n\tregisterPath := filepath.Join(rDir, registerTplName)\n\tisExist, err := util.PathExist(registerPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !isExist {\n\t\treturn pkgGen.TemplateGenerator.Generate(register, registerTplName, registerPath, false)\n\t}\n\n\tfile, err := ioutil.ReadFile(registerPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"read register '%s' failed, err: %v\", registerPath, err.Error())\n\t}\n\n\tinsertReg := register.DepPkgAlias + \".Register(r)\\n\"\n\n\tif !checkDupRegister(file, insertReg) {\n\t\tfile, err = util.AddImport(registerPath, register.DepPkgAlias, register.DepPkg)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tsubIndexReg := regRegisterV3.FindSubmatchIndex(file)\n\t\tif len(subIndexReg) != 2 || subIndexReg[0] < 1 {\n\t\t\treturn fmt.Errorf(\"wrong format %s: insert-point '%s' not found\", string(file), insertPointPatternNew)\n\t\t}\n\n\t\tbufReg := bytes.NewBuffer(nil)\n\t\tbufReg.Write(file[:subIndexReg[1]])\n\t\tbufReg.WriteString(\"\\n\\t\" + insertReg)\n\t\tbufReg.Write(file[subIndexReg[1]:])\n\n\t\tpkgGen.files = append(pkgGen.files, File{registerPath, string(bufReg.Bytes()), false, registerTplName})\n\t}\n\n\treturn nil\n}\n\nfunc checkDupRegister(file []byte, insertReg string) bool {\n\treturn bytes.Contains(file, []byte(\"\\t\"+insertReg)) || bytes.Contains(file, []byte(\" \"+insertReg))\n}\n\nfunc appendMw(mws []string, mw string) ([]string, string) {\n\tfor i := 0; true; i++ {\n\t\tif i == math.MaxInt {\n\t\t\tbreak\n\t\t}\n\t\tif !stringsIncludes(mws, mw) {\n\t\t\tmws = append(mws, mw)\n\t\t\tbreak\n\t\t}\n\t\tmw += strconv.Itoa(i)\n\t}\n\treturn mws, mw\n}\n\nfunc stringsIncludes(strs []string, str string) bool {\n\tfor _, s := range strs {\n\t\tif s == str {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (pkgGen *HttpPackageGenerator) genRouter(pkg *HttpPackage, root *RouterNode, handlerPackage, routerDir, routerPackage string) error {\n\terr := root.DyeGroupName(pkgGen.SnakeStyleMiddleware)\n\tif err != nil {\n\t\treturn err\n\t}\n\trouter := Router{\n\t\tFilePath:    filepath.Join(routerDir, util.BaseNameAndTrim(pkg.IdlName)+\".go\"),\n\t\tPackageName: filepath.Base(routerDir),\n\t\tHandlerPackages: map[string]string{\n\t\t\tutil.BaseName(handlerPackage, \"\"): handlerPackage,\n\t\t},\n\t\tRouter: root,\n\t}\n\n\thandlerMap := make(map[string]string)\n\thook := func(layer int, node *RouterNode) error {\n\t\tif len(node.HandlerPackage) != 0 {\n\t\t\thandlerMap[node.HandlerPackageAlias] = node.HandlerPackage\n\t\t}\n\t\treturn nil\n\t}\n\troot.DFS(0, hook)\n\tif len(handlerMap) != 0 {\n\t\trouter.HandlerPackages = handlerMap\n\t}\n\n\tif pkgGen.SnakeStyleMiddleware { // unique middleware name for SnakeStyleMiddleware\n\t\tmws := []string{}\n\t\thook := func(layer int, node *RouterNode) error {\n\t\t\tif len(node.Children) == 0 {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tgroupMwName := node.GroupMiddleware\n\t\t\thandlerMwName := node.HandlerMiddleware\n\t\t\tif len(groupMwName) != 0 {\n\t\t\t\tmws, groupMwName = appendMw(mws, groupMwName)\n\t\t\t}\n\t\t\tif len(handlerMwName) != 0 {\n\t\t\t\tmws, handlerMwName = appendMw(mws, handlerMwName)\n\t\t\t}\n\t\t\tif groupMwName != node.GroupMiddleware {\n\t\t\t\tnode.GroupMiddleware = groupMwName\n\t\t\t}\n\t\t\tif handlerMwName != node.HandlerMiddleware {\n\t\t\t\tnode.HandlerMiddleware = handlerMwName\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\troot.DFS(0, hook)\n\t}\n\n\t// store router info\n\tpkg.RouterInfo = &router\n\n\tif !pkgGen.tplsInfo[routerTplName].Disable {\n\t\tif err := pkgGen.TemplateGenerator.Generate(router, routerTplName, router.FilePath, false); err != nil {\n\t\t\treturn fmt.Errorf(\"generate router %s failed, err: %v\", router.FilePath, err.Error())\n\t\t}\n\t}\n\tif err := pkgGen.updateMiddlewareReg(router, middlewareTplName, filepath.Join(routerDir, \"middleware.go\")); err != nil {\n\t\treturn fmt.Errorf(\"generate middleware %s failed, err: %v\", filepath.Join(routerDir, \"middleware.go\"), err.Error())\n\t}\n\n\tif err := pkgGen.updateRegister(routerPackage, pkgGen.RouterDir, pkg.Package); err != nil {\n\t\treturn fmt.Errorf(\"update register for %s failed, err: %v\", filepath.Join(routerDir, registerTplName), err.Error())\n\t}\n\treturn nil\n}\n\nfunc (pkgGen *HttpPackageGenerator) updateMiddlewareReg(router interface{}, middlewareTpl, filePath string) error {\n\tif pkgGen.tplsInfo[middlewareTpl].Disable {\n\t\treturn nil\n\t}\n\tisExist, err := util.PathExist(filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !isExist {\n\t\treturn pkgGen.TemplateGenerator.Generate(router, middlewareTpl, filePath, false)\n\t}\n\tvar middlewareList []string\n\n\t_ = router.(Router).Router.DFS(0, func(layer int, node *RouterNode) error {\n\t\t// non-leaf node will generate group middleware\n\t\tif node.Children.Len() > 0 && len(node.GroupMiddleware) > 0 {\n\t\t\tmiddlewareList = append(middlewareList, node.GroupMiddleware)\n\t\t}\n\t\tif len(node.HandlerMiddleware) > 0 {\n\t\t\tmiddlewareList = append(middlewareList, node.HandlerMiddleware)\n\t\t}\n\t\treturn nil\n\t})\n\n\tfile, err := ioutil.ReadFile(filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, mw := range middlewareList {\n\t\tmwNamePattern := fmt.Sprintf(\" %sMw\", mw)\n\t\tif pkgGen.SnakeStyleMiddleware {\n\t\t\tmwNamePattern = fmt.Sprintf(\" %s_mw\", mw)\n\t\t}\n\t\tif bytes.Contains(file, []byte(mwNamePattern)) {\n\t\t\tcontinue\n\t\t}\n\t\tmiddlewareSingleTpl := pkgGen.tpls[middlewareSingleTplName]\n\t\tif middlewareSingleTpl == nil {\n\t\t\treturn fmt.Errorf(\"tpl %s not found\", middlewareSingleTplName)\n\t\t}\n\t\tdata := make(map[string]string, 1)\n\t\tdata[\"MiddleWare\"] = mw\n\t\tmiddlewareFunc := bytes.NewBuffer(nil)\n\t\terr = middlewareSingleTpl.Execute(middlewareFunc, data)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"execute template \\\"%s\\\" failed, %v\", middlewareSingleTplName, err)\n\t\t}\n\n\t\tbuf := bytes.NewBuffer(nil)\n\t\t_, err = buf.Write(file)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"write middleware \\\"%s\\\" failed, %v\", mw, err)\n\t\t}\n\t\t_, err = buf.Write(middlewareFunc.Bytes())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"write middleware \\\"%s\\\" failed, %v\", mw, err)\n\t\t}\n\t\tfile = buf.Bytes()\n\t}\n\n\tpkgGen.files = append(pkgGen.files, File{filePath, string(file), false, middlewareTplName})\n\n\treturn nil\n}\n\n// convertToMiddlewareName converts a route path to a middleware name\nfunc convertToMiddlewareName(path string) string {\n\tpath = util.ToVarName([]string{path})\n\tpath = strings.ToLower(path)\n\treturn path\n}\n"
  },
  {
    "path": "cmd/hz/generator/router_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport \"testing\"\n\nfunc Test_checkDupRegister(t *testing.T) {\n\ttype args struct {\n\t\tfile      []byte\n\t\tinsertReg string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"dup tab\",\n\t\t\targs: args{\n\t\t\t\tfile:      []byte(\"package main\\n\\nimport (\\n\\t\\\"hertz.io/hertz/pkg/app\\\"\\n)\\n\\nfunc register() {\\n\\tapp.Register(r)\\n}\"),\n\t\t\t\tinsertReg: \"app.Register(r)\\n\",\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"dup space\",\n\t\t\targs: args{\n\t\t\t\tfile:      []byte(\"package main\\n\\nimport (\\n\\t\\\"hertz.io/hertz/pkg/app\\\"\\n)\\n\\nfunc register() {\\n   app.Register(r)\\n}\"),\n\t\t\t\tinsertReg: \"app.Register(r)\\n\",\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not dup prefix\",\n\t\t\targs: args{\n\t\t\t\tfile:      []byte(\"package main\\n\\nimport (\\n\\t\\\"hertz.io/hertz/pkg/app_2\\\"\\n)\\n\\nfunc register() {\\n\\tapp_2.Register(r)\\n}\"),\n\t\t\t\tinsertReg: \"app.Register(r)\\n\",\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not dup subfix\",\n\t\t\targs: args{\n\t\t\t\tfile:      []byte(\"package main\\n\\nimport (\\n\\t\\\"hertz.io/hertz/pkg/xapp\\\"\\n)\\n\\nfunc register() {\\n xapp.Register(r)\\n}\"),\n\t\t\t\tinsertReg: \"app.Register(r)\\n\",\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := checkDupRegister(tt.args.file, tt.args.insertReg); got != tt.want {\n\t\t\t\tt.Errorf(\"checkDupRegister() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/generator/template.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n)\n\nvar DefaultDelimiters = [2]string{\"{{\", \"}}\"}\n\ntype TemplateConfig struct {\n\tLayouts []Template `yaml:\"layouts\"`\n}\n\nconst (\n\tSkip   = \"skip\"\n\tCover  = \"cover\"\n\tAppend = \"append\"\n)\n\ntype Template struct {\n\tDefault        bool           // Is it the default template\n\tPath           string         `yaml:\"path\"`            // The generated path and its filename, such as biz/handler/ping.go\n\tDelims         [2]string      `yaml:\"delims\"`          // Template Action Instruction Identifier, default: \"{{}}\"\n\tBody           string         `yaml:\"body\"`            // Render template, currently only supports go template syntax\n\tDisable        bool           `yaml:\"disable\"`         // Disable generating file, used to disable default package template\n\tLoopMethod     bool           `yaml:\"loop_method\"`     // Loop generate files based on \"method\"\n\tLoopService    bool           `yaml:\"loop_service\"`    // Loop generate files based on \"service\"\n\tUpdateBehavior UpdateBehavior `yaml:\"update_behavior\"` // Update command behavior; 0:unchanged, 1:regenerate, 2:append\n}\n\ntype UpdateBehavior struct {\n\tType string `yaml:\"type\"` // Update behavior type: skip/cover/append\n\t// the following variables are used for append update\n\tAppendKey      string   `yaml:\"append_key\"`         // Append content based in key; for example: 'method'/'service'\n\tInsertKey      string   `yaml:\"insert_key\"`         // Insert content by \"insert_key\"\n\tAppendTpl      string   `yaml:\"append_content_tpl\"` // Append content if UpdateBehavior is \"append\"\n\tImportTpl      []string `yaml:\"import_tpl\"`         // Import insert template\n\tAppendLocation string   `yaml:\"append_location\"`    // AppendLocation specifies the location of append,  the default is the end of the file\n}\n\n// TemplateGenerator contains information about the output template\ntype TemplateGenerator struct {\n\tOutputDir    string\n\tConfig       *TemplateConfig\n\tExcludes     []string\n\ttpls         map[string]*template.Template // \"template name\" -> \"Template\", it is used get the \"parsed template\" directly\n\ttplsInfo     map[string]*Template          // \"template name\" -> \"template info\", it is used to get the original \"template information\"\n\tdirs         map[string]bool\n\tisPackageTpl bool\n\n\tfiles         []File\n\texcludedFiles map[string]*File\n}\n\nfunc (tg *TemplateGenerator) Init() error {\n\tif tg.Config == nil {\n\t\treturn errors.New(\"config not set yet\")\n\t}\n\n\tif tg.tpls == nil {\n\t\ttg.tpls = make(map[string]*template.Template, len(tg.Config.Layouts))\n\t}\n\tif tg.tplsInfo == nil {\n\t\ttg.tplsInfo = make(map[string]*Template, len(tg.Config.Layouts))\n\t}\n\tif tg.dirs == nil {\n\t\ttg.dirs = make(map[string]bool)\n\t}\n\n\tfor _, l := range tg.Config.Layouts {\n\t\tif tg.isPackageTpl && IsDefaultPackageTpl(l.Path) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// check if is a directory\n\t\tvar noFile bool\n\t\tif strings.HasSuffix(l.Path, string(filepath.Separator)) {\n\t\t\tnoFile = true\n\t\t}\n\t\tpath := l.Path\n\t\tif filepath.IsAbs(path) {\n\t\t\treturn fmt.Errorf(\"absolute template path '%s' is not allowed\", path)\n\t\t}\n\t\tdir := filepath.Dir(path)\n\t\tisExist, err := util.PathExist(filepath.Join(tg.OutputDir, dir))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"check directory '%s' failed, err: %v\", dir, err.Error())\n\t\t}\n\t\tif isExist {\n\t\t\ttg.dirs[dir] = true\n\t\t} else {\n\t\t\ttg.dirs[dir] = false\n\t\t}\n\n\t\tif noFile {\n\t\t\tcontinue\n\t\t}\n\n\t\t// parse templates\n\t\tif _, ok := tg.tpls[path]; ok {\n\t\t\tcontinue\n\t\t}\n\t\terr = tg.loadLayout(l, path, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\texcludes := make(map[string]*File, len(tg.Excludes))\n\tfor _, f := range tg.Excludes {\n\t\texcludes[f] = &File{}\n\t}\n\n\ttg.excludedFiles = excludes\n\treturn nil\n}\n\nfunc (tg *TemplateGenerator) loadLayout(layout Template, tplName string, isDefaultTpl bool) error {\n\tdelims := DefaultDelimiters\n\tif layout.Delims[0] != \"\" && layout.Delims[1] != \"\" {\n\t\tdelims = layout.Delims\n\t}\n\t// insert template funcs\n\ttpl := template.New(tplName).Funcs(funcMap)\n\ttpl = tpl.Delims(delims[0], delims[1])\n\tvar err error\n\tif tpl, err = tpl.Parse(layout.Body); err != nil {\n\t\treturn fmt.Errorf(\"parse template '%s' failed, err: %v\", tplName, err.Error())\n\t}\n\tlayout.Default = isDefaultTpl\n\ttg.tpls[tplName] = tpl\n\ttg.tplsInfo[tplName] = &layout\n\treturn nil\n}\n\nfunc (tg *TemplateGenerator) Generate(input interface{}, tplName, filepath string, noRepeat bool) error {\n\t// check if \"*\" (global scope) data exists, and stores it to all\n\tvar all map[string]interface{}\n\tif data, ok := input.(map[string]interface{}); ok {\n\t\tad, ok := data[\"*\"]\n\t\tif ok {\n\t\t\tall = ad.(map[string]interface{})\n\t\t}\n\t\tif all == nil {\n\t\t\tall = map[string]interface{}{}\n\t\t}\n\t\tall[\"hzVersion\"] = meta.Version\n\t}\n\n\tfile := bytes.NewBuffer(nil)\n\tif tplName != \"\" {\n\t\ttpl := tg.tpls[tplName]\n\t\tif tpl == nil {\n\t\t\treturn fmt.Errorf(\"tpl %s not found\", tplName)\n\t\t}\n\t\tif err := tpl.Execute(file, input); err != nil {\n\t\t\treturn fmt.Errorf(\"render template '%s' failed, err: %v\", tplName, err.Error())\n\t\t}\n\n\t\tin := File{filepath, string(file.Bytes()), noRepeat, tplName}\n\t\ttg.files = append(tg.files, in)\n\t\treturn nil\n\t}\n\n\tfor path, tpl := range tg.tpls {\n\t\tfile.Reset()\n\t\tvar fd interface{}\n\t\t// search and merge rendering data\n\t\tif data, ok := input.(map[string]interface{}); ok {\n\t\t\ttd := map[string]interface{}{}\n\t\t\ttmp, ok := data[path]\n\t\t\tif ok {\n\t\t\t\ttd = tmp.(map[string]interface{})\n\t\t\t}\n\t\t\tfor k, v := range all {\n\t\t\t\ttd[k] = v\n\t\t\t}\n\t\t\tfd = td\n\t\t} else {\n\t\t\tfd = input\n\t\t}\n\t\tif err := tpl.Execute(file, fd); err != nil {\n\t\t\treturn fmt.Errorf(\"render template '%s' failed, err: %v\", path, err.Error())\n\t\t}\n\n\t\tin := File{path, string(file.Bytes()), noRepeat, tpl.Name()}\n\t\ttg.files = append(tg.files, in)\n\t}\n\n\treturn nil\n}\n\nfunc (tg *TemplateGenerator) Persist() error {\n\tfiles := tg.files\n\toutPath := tg.OutputDir\n\tif !filepath.IsAbs(outPath) {\n\t\toutPath, _ = filepath.Abs(outPath)\n\t}\n\n\tfor _, data := range files {\n\t\t// check for -E flags\n\t\tif _, ok := tg.excludedFiles[filepath.Join(data.Path)]; ok {\n\t\t\tcontinue\n\t\t}\n\n\t\t// lint file\n\t\tif err := data.Lint(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// create rendered file\n\t\tabPath := filepath.Join(outPath, data.Path)\n\t\tabDir := filepath.Dir(abPath)\n\t\tisExist, err := util.PathExist(abDir)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"check directory '%s' failed, err: %v\", abDir, err.Error())\n\t\t}\n\t\tif !isExist {\n\t\t\tif err := os.MkdirAll(abDir, os.FileMode(0o744)); err != nil {\n\t\t\t\treturn fmt.Errorf(\"mkdir %s failed, err: %v\", abDir, err.Error())\n\t\t\t}\n\t\t}\n\n\t\terr = func() error {\n\t\t\tfileMode := os.FileMode(0o644)\n\t\t\tif strings.HasSuffix(abPath, \".sh\") {\n\t\t\t\tfileMode = os.FileMode(0o755)\n\t\t\t}\n\t\t\tif err := os.WriteFile(abPath, []byte(data.Content), fileMode); err != nil {\n\t\t\t\treturn fmt.Errorf(\"write file '%s' failed, err: %v\", abPath, err)\n\t\t\t}\n\t\t\treturn nil\n\t\t}()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\ttg.files = tg.files[:0]\n\treturn nil\n}\n\nfunc (tg *TemplateGenerator) GetFormatAndExcludedFiles() ([]File, error) {\n\tvar files []File\n\toutPath := tg.OutputDir\n\tif !filepath.IsAbs(outPath) {\n\t\toutPath, _ = filepath.Abs(outPath)\n\t}\n\n\tfor _, data := range tg.Files() {\n\t\tif _, ok := tg.excludedFiles[filepath.Join(data.Path)]; ok {\n\t\t\tcontinue\n\t\t}\n\n\t\t// check repeat files\n\t\tlogs.Infof(\"Write %s\", data.Path)\n\t\tisExist, err := util.PathExist(filepath.Join(data.Path))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"check file '%s' failed, err: %v\", data.Path, err.Error())\n\t\t}\n\t\tif isExist && data.NoRepeat {\n\t\t\tif data.FileTplName == handlerTplName {\n\t\t\t\tlogs.Warnf(\"Handler file(%s) has been generated.\\n If you want to re-generate it, please copy and delete the file to prevent the already written code from being deleted.\", data.Path)\n\t\t\t} else if data.FileTplName == routerTplName {\n\t\t\t\tlogs.Warnf(\"Router file(%s) has been generated.\\n If you want to re-generate it, please delete the file.\", data.Path)\n\t\t\t} else {\n\t\t\t\tlogs.Warnf(\"file '%s' already exists, so drop the generated file\", data.Path)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// lint file\n\t\tif err := data.Lint(); err != nil {\n\t\t\tlogs.Warnf(\"Lint file: %s failed:\\n %s\\n\", data.Path, data.Content)\n\t\t}\n\t\tfiles = append(files, data)\n\t}\n\n\treturn files, nil\n}\n\nfunc (tg *TemplateGenerator) Files() []File {\n\treturn tg.files\n}\n\nfunc (tg *TemplateGenerator) Degenerate() error {\n\toutPath := tg.OutputDir\n\tif !filepath.IsAbs(outPath) {\n\t\toutPath, _ = filepath.Abs(outPath)\n\t}\n\tfor path := range tg.tpls {\n\t\tabPath := filepath.Join(outPath, path)\n\t\tif err := os.RemoveAll(abPath); err != nil {\n\t\t\treturn fmt.Errorf(\"remove file '%s' failed, err: %v\", path, err.Error())\n\t\t}\n\t}\n\tfor dir, exist := range tg.dirs {\n\t\tif !exist {\n\t\t\tabDir := filepath.Join(outPath, dir)\n\t\t\tif err := os.RemoveAll(abDir); err != nil {\n\t\t\t\treturn fmt.Errorf(\"remove directory '%s' failed, err: %v\", dir, err.Error())\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/hz/generator/template_funcs.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage generator\n\nimport (\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/Masterminds/sprig/v3\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n)\n\nvar funcMap = func() template.FuncMap {\n\tm := template.FuncMap{\n\t\t\"GetUniqueHandlerOutDir\": getUniqueHandlerOutDir,\n\t\t\"ToSnakeCase\":            util.ToSnakeCase,\n\t\t\"Split\":                  strings.Split,\n\t\t\"Trim\":                   strings.Trim,\n\t\t\"EqualFold\":              strings.EqualFold,\n\t}\n\tfor key, f := range sprig.TxtFuncMap() {\n\t\tm[key] = f\n\t}\n\treturn m\n}()\n\n// getUniqueHandlerOutDir uses to get unique \"api.handler_path\"\nfunc getUniqueHandlerOutDir(methods []*HttpMethod) (ret []string) {\n\toutDirMap := make(map[string]string)\n\tfor _, method := range methods {\n\t\tif _, exist := outDirMap[method.OutputDir]; !exist {\n\t\t\toutDirMap[method.OutputDir] = method.OutputDir\n\t\t\tret = append(ret, method.OutputDir)\n\t\t}\n\t}\n\n\treturn ret\n}\n"
  },
  {
    "path": "cmd/hz/go.mod",
    "content": "module github.com/cloudwego/hertz/cmd/hz\n\ngo 1.16\n\nrequire (\n\tgithub.com/Masterminds/sprig/v3 v3.2.3\n\tgithub.com/cloudwego/thriftgo v0.4.2-0.20250604064713-0e1e704080b1\n\tgithub.com/hashicorp/go-version v1.5.0\n\tgithub.com/jhump/protoreflect v1.12.0\n\tgithub.com/urfave/cli/v2 v2.23.0\n\tgolang.org/x/tools v0.6.0\n\tgoogle.golang.org/protobuf v1.28.0\n\tgopkg.in/yaml.v2 v2.4.0\n)\n"
  },
  {
    "path": "cmd/hz/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=\ngithub.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=\ngithub.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=\ngithub.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=\ngithub.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=\ngithub.com/bytedance/gopkg v0.1.1 h1:3azzgSkiaw79u24a+w9arfH8OfnQQ4MHUt9lJFREEaE=\ngithub.com/bytedance/gopkg v0.1.1/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudwego/gopkg v0.1.4 h1:EoQiCG4sTonTPHxOGE0VlQs+sQR+Hsi2uN0qqwu8O50=\ngithub.com/cloudwego/gopkg v0.1.4/go.mod h1:FQuXsRWRsSqJLsMVd5SYzp8/Z1y5gXKnVvRrWUOsCMI=\ngithub.com/cloudwego/thriftgo v0.4.2-0.20250604064713-0e1e704080b1 h1:iuQJK+ZEtb0uA9cTjWW65gj2R0UB03GFZRD8IwAbDaE=\ngithub.com/cloudwego/thriftgo v0.4.2-0.20250604064713-0e1e704080b1/go.mod h1:/D4zRAEj1t3/Tq1bVGDMnRt3wxpHfalXfZWvq/n4YmY=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=\ngithub.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E=\ngithub.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=\ngithub.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=\ngithub.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=\ngithub.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=\ngithub.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=\ngithub.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=\ngithub.com/jhump/protoreflect v1.12.0 h1:1NQ4FpWMgn3by/n1X0fbeKEUxP1wBt7+Oitpv01HR10=\ngithub.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=\ngithub.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=\ngithub.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=\ngithub.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=\ngithub.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=\ngithub.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=\ngithub.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/urfave/cli/v2 v2.23.0 h1:pkly7gKIeYv3olPAeNajNpLjeJrmTPYCoZWaV+2VfvE=\ngithub.com/urfave/cli/v2 v2.23.0/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=\ngithub.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=\ngithub.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=\ngolang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=\ngolang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=\ngolang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=\ngoogle.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "cmd/hz/main.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"os\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/app\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n)\n\nfunc main() {\n\t// run in plugin mode\n\tapp.PluginMode()\n\n\t// run in normal mode\n\tRun()\n}\n\nfunc Run() {\n\tdefer func() {\n\t\tlogs.Flush()\n\t}()\n\n\tcli := app.Init()\n\terr := cli.Run(os.Args)\n\tif err != nil {\n\t\tlogs.Errorf(\"%v\\n\", err)\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/meta/const.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage meta\n\nimport (\n\t\"path/filepath\"\n\t\"runtime\"\n)\n\n// Version hz version\nconst Version = \"v0.9.7\"\n\nconst DefaultServiceName = \"hertz_service\"\n\n// Mode hz run modes\ntype Mode int\n\n// SysType is the running program's operating system type\nconst SysType = runtime.GOOS\n\nconst WindowsOS = \"windows\"\n\nconst EnvPluginMode = \"HERTZ_PLUGIN_MODE\"\n\n// hz Commands\nconst (\n\tCmdUpdate = \"update\"\n\tCmdNew    = \"new\"\n\tCmdModel  = \"model\"\n\tCmdClient = \"client\"\n)\n\n// hz IDLs\nconst (\n\tIdlThrift = \"thrift\"\n\tIdlProto  = \"proto\"\n)\n\n// Third-party Compilers\nconst (\n\tTpCompilerThrift = \"thriftgo\"\n\tTpCompilerProto  = \"protoc\"\n)\n\n// hz Plugins\nconst (\n\tProtocPluginName = \"protoc-gen-hertz\"\n\tThriftPluginName = \"thrift-gen-hertz\"\n)\n\n// hz Errors\nconst (\n\tLoadError           = 1\n\tGenerateLayoutError = 2\n\tPersistError        = 3\n\tPluginError         = 4\n)\n\n// Package Dir\nconst (\n\tModelDir   = \"biz\" + string(filepath.Separator) + \"model\"\n\tRouterDir  = \"biz\" + string(filepath.Separator) + \"router\"\n\tHandlerDir = \"biz\" + string(filepath.Separator) + \"handler\"\n)\n\n// Backend Model Backends\ntype Backend string\n\nconst (\n\tBackendGolang Backend = \"golang\"\n)\n\n// template const value\nconst (\n\tSetBodyParam = \"setBodyParam(req).\\n\"\n)\n\n// TheUseOptionMessage indicates that the generating of 'model code' is aborted due to the -use option for thrift IDL.\nconst TheUseOptionMessage = \"'model code' is not generated due to the '-use' option\"\n\nconst AddThriftReplace = \"do not generate 'go.mod', please add 'replace github.com/apache/thrift => github.com/apache/thrift v0.13.0' to your 'go.mod'\"\n"
  },
  {
    "path": "cmd/hz/meta/manifest.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage meta\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\tgv \"github.com/hashicorp/go-version\"\n\t\"gopkg.in/yaml.v2\"\n)\n\nconst ManifestFile = \".hz\"\n\ntype Manifest struct {\n\tVersion    string `yaml:\"hz version\"`\n\tHandlerDir string `yaml:\"handlerDir\"`\n\tModelDir   string `yaml:\"modelDir\"`\n\tRouterDir  string `yaml:\"routerDir\"`\n}\n\nvar GoVersion *gv.Version\n\nfunc init() {\n\t// valid by unit test already, so no need to check error\n\tGoVersion, _ = gv.NewVersion(Version)\n}\n\nfunc (manifest *Manifest) InitAndValidate(dir string) error {\n\tm, err := loadConfigFile(filepath.Join(dir, ManifestFile))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"can not load \\\".hz\\\", err: %v\", err)\n\t}\n\n\tif len(m.Version) == 0 {\n\t\treturn fmt.Errorf(\"can not get hz version form \\\".hz\\\", current project doesn't belong to hertz framework\")\n\t}\n\n\t*manifest = *m\n\t_, err = gv.NewVersion(manifest.Version)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid hz version in \\\".hz\\\", err: %v\", err)\n\t}\n\n\treturn nil\n}\n\nconst hzTitle = \"// Code generated by hz. DO NOT EDIT.\"\n\nfunc (manifest *Manifest) String() string {\n\tconf, _ := yaml.Marshal(*manifest)\n\n\treturn hzTitle + \"\\n\\n\" +\n\t\tstring(conf)\n}\n\nfunc (manifest *Manifest) Persist(dir string) error {\n\tfile := filepath.Join(dir, ManifestFile)\n\tfd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(0o644))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer fd.Close()\n\t_, err = fd.WriteString(manifest.String())\n\treturn err\n}\n\n// loadConfigFile load config file from path\nfunc loadConfigFile(path string) (*Manifest, error) {\n\tfile, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar manifest Manifest\n\tfile = bytes.TrimPrefix(file, []byte(hzTitle))\n\tif err = yaml.Unmarshal(file, &manifest); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode \\\".hz\\\" failed, err: %v\", err)\n\t}\n\treturn &manifest, nil\n}\n"
  },
  {
    "path": "cmd/hz/meta/manifest_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage meta\n\nimport (\n\t\"testing\"\n\n\tgv \"github.com/hashicorp/go-version\"\n)\n\nfunc TestValidate(t *testing.T) {\n\t_, err := gv.NewVersion(Version)\n\tif err != nil {\n\t\tt.Fatalf(\"not a valid version: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/protobuf/api/api.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.30.0\n// \tprotoc        v3.21.12\n// source: api.proto\n\npackage api\n\nimport (\n\treflect \"reflect\"\n\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\tdescriptorpb \"google.golang.org/protobuf/types/descriptorpb\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\nvar file_api_proto_extTypes = []protoimpl.ExtensionInfo{\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50101,\n\t\tName:          \"api.raw_body\",\n\t\tTag:           \"bytes,50101,opt,name=raw_body\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50102,\n\t\tName:          \"api.query\",\n\t\tTag:           \"bytes,50102,opt,name=query\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50103,\n\t\tName:          \"api.header\",\n\t\tTag:           \"bytes,50103,opt,name=header\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50104,\n\t\tName:          \"api.cookie\",\n\t\tTag:           \"bytes,50104,opt,name=cookie\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50105,\n\t\tName:          \"api.body\",\n\t\tTag:           \"bytes,50105,opt,name=body\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50106,\n\t\tName:          \"api.path\",\n\t\tTag:           \"bytes,50106,opt,name=path\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50107,\n\t\tName:          \"api.vd\",\n\t\tTag:           \"bytes,50107,opt,name=vd\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50108,\n\t\tName:          \"api.form\",\n\t\tTag:           \"bytes,50108,opt,name=form\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50109,\n\t\tName:          \"api.js_conv\",\n\t\tTag:           \"bytes,50109,opt,name=js_conv\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50110,\n\t\tName:          \"api.file_name\",\n\t\tTag:           \"bytes,50110,opt,name=file_name\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50111,\n\t\tName:          \"api.none\",\n\t\tTag:           \"bytes,50111,opt,name=none\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50131,\n\t\tName:          \"api.form_compatible\",\n\t\tTag:           \"bytes,50131,opt,name=form_compatible\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50132,\n\t\tName:          \"api.js_conv_compatible\",\n\t\tTag:           \"bytes,50132,opt,name=js_conv_compatible\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50133,\n\t\tName:          \"api.file_name_compatible\",\n\t\tTag:           \"bytes,50133,opt,name=file_name_compatible\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50134,\n\t\tName:          \"api.none_compatible\",\n\t\tTag:           \"bytes,50134,opt,name=none_compatible\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.FieldOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         51001,\n\t\tName:          \"api.go_tag\",\n\t\tTag:           \"bytes,51001,opt,name=go_tag\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50201,\n\t\tName:          \"api.get\",\n\t\tTag:           \"bytes,50201,opt,name=get\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50202,\n\t\tName:          \"api.post\",\n\t\tTag:           \"bytes,50202,opt,name=post\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50203,\n\t\tName:          \"api.put\",\n\t\tTag:           \"bytes,50203,opt,name=put\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50204,\n\t\tName:          \"api.delete\",\n\t\tTag:           \"bytes,50204,opt,name=delete\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50205,\n\t\tName:          \"api.patch\",\n\t\tTag:           \"bytes,50205,opt,name=patch\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50206,\n\t\tName:          \"api.options\",\n\t\tTag:           \"bytes,50206,opt,name=options\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50207,\n\t\tName:          \"api.head\",\n\t\tTag:           \"bytes,50207,opt,name=head\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50208,\n\t\tName:          \"api.any\",\n\t\tTag:           \"bytes,50208,opt,name=any\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50301,\n\t\tName:          \"api.gen_path\",\n\t\tTag:           \"bytes,50301,opt,name=gen_path\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50302,\n\t\tName:          \"api.api_version\",\n\t\tTag:           \"bytes,50302,opt,name=api_version\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50303,\n\t\tName:          \"api.tag\",\n\t\tTag:           \"bytes,50303,opt,name=tag\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50304,\n\t\tName:          \"api.name\",\n\t\tTag:           \"bytes,50304,opt,name=name\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50305,\n\t\tName:          \"api.api_level\",\n\t\tTag:           \"bytes,50305,opt,name=api_level\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50306,\n\t\tName:          \"api.serializer\",\n\t\tTag:           \"bytes,50306,opt,name=serializer\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50307,\n\t\tName:          \"api.param\",\n\t\tTag:           \"bytes,50307,opt,name=param\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50308,\n\t\tName:          \"api.baseurl\",\n\t\tTag:           \"bytes,50308,opt,name=baseurl\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50309,\n\t\tName:          \"api.handler_path\",\n\t\tTag:           \"bytes,50309,opt,name=handler_path\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MethodOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50331,\n\t\tName:          \"api.handler_path_compatible\",\n\t\tTag:           \"bytes,50331,opt,name=handler_path_compatible\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.EnumValueOptions)(nil),\n\t\tExtensionType: (*int32)(nil),\n\t\tField:         50401,\n\t\tName:          \"api.http_code\",\n\t\tTag:           \"varint,50401,opt,name=http_code\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.ServiceOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50402,\n\t\tName:          \"api.base_domain\",\n\t\tTag:           \"bytes,50402,opt,name=base_domain\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.ServiceOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50731,\n\t\tName:          \"api.base_domain_compatible\",\n\t\tTag:           \"bytes,50731,opt,name=base_domain_compatible\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.ServiceOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50732,\n\t\tName:          \"api.service_path\",\n\t\tTag:           \"bytes,50732,opt,name=service_path\",\n\t\tFilename:      \"api.proto\",\n\t},\n\t{\n\t\tExtendedType:  (*descriptorpb.MessageOptions)(nil),\n\t\tExtensionType: (*string)(nil),\n\t\tField:         50830,\n\t\tName:          \"api.reserve\",\n\t\tTag:           \"bytes,50830,opt,name=reserve\",\n\t\tFilename:      \"api.proto\",\n\t},\n}\n\n// Extension fields to descriptorpb.FieldOptions.\nvar (\n\t// optional string raw_body = 50101;\n\tE_RawBody = &file_api_proto_extTypes[0]\n\t// optional string query = 50102;\n\tE_Query = &file_api_proto_extTypes[1]\n\t// optional string header = 50103;\n\tE_Header = &file_api_proto_extTypes[2]\n\t// optional string cookie = 50104;\n\tE_Cookie = &file_api_proto_extTypes[3]\n\t// optional string body = 50105;\n\tE_Body = &file_api_proto_extTypes[4]\n\t// optional string path = 50106;\n\tE_Path = &file_api_proto_extTypes[5]\n\t// optional string vd = 50107;\n\tE_Vd = &file_api_proto_extTypes[6]\n\t// optional string form = 50108;\n\tE_Form = &file_api_proto_extTypes[7]\n\t// optional string js_conv = 50109;\n\tE_JsConv = &file_api_proto_extTypes[8]\n\t// optional string file_name = 50110;\n\tE_FileName = &file_api_proto_extTypes[9]\n\t// optional string none = 50111;\n\tE_None = &file_api_proto_extTypes[10]\n\t// 50131~50160 used to extend field option by hz\n\t//\n\t// optional string form_compatible = 50131;\n\tE_FormCompatible = &file_api_proto_extTypes[11]\n\t// optional string js_conv_compatible = 50132;\n\tE_JsConvCompatible = &file_api_proto_extTypes[12]\n\t// optional string file_name_compatible = 50133;\n\tE_FileNameCompatible = &file_api_proto_extTypes[13]\n\t// optional string none_compatible = 50134;\n\tE_NoneCompatible = &file_api_proto_extTypes[14]\n\t// optional string go_tag = 51001;\n\tE_GoTag = &file_api_proto_extTypes[15]\n)\n\n// Extension fields to descriptorpb.MethodOptions.\nvar (\n\t// optional string get = 50201;\n\tE_Get = &file_api_proto_extTypes[16]\n\t// optional string post = 50202;\n\tE_Post = &file_api_proto_extTypes[17]\n\t// optional string put = 50203;\n\tE_Put = &file_api_proto_extTypes[18]\n\t// optional string delete = 50204;\n\tE_Delete = &file_api_proto_extTypes[19]\n\t// optional string patch = 50205;\n\tE_Patch = &file_api_proto_extTypes[20]\n\t// optional string options = 50206;\n\tE_Options = &file_api_proto_extTypes[21]\n\t// optional string head = 50207;\n\tE_Head = &file_api_proto_extTypes[22]\n\t// optional string any = 50208;\n\tE_Any = &file_api_proto_extTypes[23]\n\t// optional string gen_path = 50301;\n\tE_GenPath = &file_api_proto_extTypes[24] // The path specified by the user when the client code is generated, with a higher priority than api_version\n\t// optional string api_version = 50302;\n\tE_ApiVersion = &file_api_proto_extTypes[25] // Specify the value of the :version variable in path when the client code is generated\n\t// optional string tag = 50303;\n\tE_Tag = &file_api_proto_extTypes[26] // rpc tag, can be multiple, separated by commas\n\t// optional string name = 50304;\n\tE_Name = &file_api_proto_extTypes[27] // Name of rpc\n\t// optional string api_level = 50305;\n\tE_ApiLevel = &file_api_proto_extTypes[28] // Interface Level\n\t// optional string serializer = 50306;\n\tE_Serializer = &file_api_proto_extTypes[29] // Serialization method\n\t// optional string param = 50307;\n\tE_Param = &file_api_proto_extTypes[30] // Whether client requests take public parameters\n\t// optional string baseurl = 50308;\n\tE_Baseurl = &file_api_proto_extTypes[31] // Baseurl used in ttnet routing\n\t// optional string handler_path = 50309;\n\tE_HandlerPath = &file_api_proto_extTypes[32] // handler_path specifies the path to generate the method\n\t// 50331~50360 used to extend method option by hz\n\t//\n\t// optional string handler_path_compatible = 50331;\n\tE_HandlerPathCompatible = &file_api_proto_extTypes[33] // handler_path specifies the path to generate the method\n)\n\n// Extension fields to descriptorpb.EnumValueOptions.\nvar (\n\t// optional int32 http_code = 50401;\n\tE_HttpCode = &file_api_proto_extTypes[34]\n)\n\n// Extension fields to descriptorpb.ServiceOptions.\nvar (\n\t// optional string base_domain = 50402;\n\tE_BaseDomain = &file_api_proto_extTypes[35]\n\t// 50731~50760 used to extend service option by hz\n\t//\n\t// optional string base_domain_compatible = 50731;\n\tE_BaseDomainCompatible = &file_api_proto_extTypes[36]\n\t// optional string service_path = 50732;\n\tE_ServicePath = &file_api_proto_extTypes[37]\n)\n\n// Extension fields to descriptorpb.MessageOptions.\nvar (\n\t// optional string reserve = 50830;\n\tE_Reserve = &file_api_proto_extTypes[38]\n)\n\nvar File_api_proto protoreflect.FileDescriptor\n\nvar file_api_proto_rawDesc = []byte{\n\t0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69,\n\t0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,\n\t0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x3a, 0x3a, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1d,\n\t0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,\n\t0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb5, 0x87,\n\t0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x61, 0x77, 0x42, 0x6f, 0x64, 0x79, 0x3a, 0x35,\n\t0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,\n\t0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb6, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,\n\t0x71, 0x75, 0x65, 0x72, 0x79, 0x3a, 0x37, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12,\n\t0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,\n\t0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb7,\n\t0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x3a, 0x37,\n\t0x0a, 0x06, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,\n\t0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64,\n\t0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb8, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x06, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x3a, 0x33, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12,\n\t0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,\n\t0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb9,\n\t0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x3a, 0x33, 0x0a, 0x04,\n\t0x70, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x18, 0xba, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74,\n\t0x68, 0x3a, 0x2f, 0x0a, 0x02, 0x76, 0x64, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,\n\t0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbb, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,\n\t0x76, 0x64, 0x3a, 0x33, 0x0a, 0x04, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f,\n\t0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65,\n\t0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbc, 0x87, 0x03, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x04, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x38, 0x0a, 0x07, 0x6a, 0x73, 0x5f, 0x63, 0x6f,\n\t0x6e, 0x76, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,\n\t0x73, 0x18, 0xbd, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6a, 0x73, 0x43, 0x6f, 0x6e,\n\t0x76, 0x3a, 0x3c, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d,\n\t0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,\n\t0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbe, 0x87,\n\t0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x3a,\n\t0x33, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,\n\t0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbf, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x6e, 0x6f, 0x6e, 0x65, 0x3a, 0x48, 0x0a, 0x0f, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x63, 0x6f, 0x6d,\n\t0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,\n\t0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd3, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e,\n\t0x66, 0x6f, 0x72, 0x6d, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x4d,\n\t0x0a, 0x12, 0x6a, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x76, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74,\n\t0x69, 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x18, 0xd4, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6a, 0x73, 0x43,\n\t0x6f, 0x6e, 0x76, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x51, 0x0a,\n\t0x14, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61,\n\t0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd5, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x66, 0x69,\n\t0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65,\n\t0x3a, 0x48, 0x0a, 0x0f, 0x6e, 0x6f, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69,\n\t0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f,\n\t0x6e, 0x73, 0x18, 0xd6, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x6e, 0x65,\n\t0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x36, 0x0a, 0x06, 0x67, 0x6f,\n\t0x5f, 0x74, 0x61, 0x67, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x18, 0xb9, 0x8e, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x6f, 0x54,\n\t0x61, 0x67, 0x3a, 0x32, 0x0a, 0x03, 0x67, 0x65, 0x74, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67,\n\t0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68,\n\t0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x99, 0x88, 0x03, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x03, 0x67, 0x65, 0x74, 0x3a, 0x34, 0x0a, 0x04, 0x70, 0x6f, 0x73, 0x74, 0x12, 0x1e,\n\t0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,\n\t0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9a,\n\t0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x73, 0x74, 0x3a, 0x32, 0x0a, 0x03,\n\t0x70, 0x75, 0x74, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x18, 0x9b, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x70, 0x75, 0x74,\n\t0x3a, 0x38, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f,\n\t0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74,\n\t0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9c, 0x88, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x3a, 0x36, 0x0a, 0x05, 0x70, 0x61,\n\t0x74, 0x63, 0x68, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x18, 0x9d, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74,\n\t0x63, 0x68, 0x3a, 0x3a, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e,\n\t0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,\n\t0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9e, 0x88,\n\t0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x34,\n\t0x0a, 0x04, 0x68, 0x65, 0x61, 0x64, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f,\n\t0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9f, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x68, 0x65, 0x61, 0x64, 0x3a, 0x32, 0x0a, 0x03, 0x61, 0x6e, 0x79, 0x12, 0x1e, 0x2e, 0x67, 0x6f,\n\t0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65,\n\t0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xa0, 0x88, 0x03, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x6e, 0x79, 0x3a, 0x3b, 0x0a, 0x08, 0x67, 0x65, 0x6e, 0x5f,\n\t0x70, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x18, 0xfd, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x65,\n\t0x6e, 0x50, 0x61, 0x74, 0x68, 0x3a, 0x41, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72,\n\t0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x18, 0xfe, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70,\n\t0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x32, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x12,\n\t0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,\n\t0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,\n\t0xff, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x3a, 0x34, 0x0a, 0x04,\n\t0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x18, 0x80, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,\n\t0x6d, 0x65, 0x3a, 0x3d, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12,\n\t0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,\n\t0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,\n\t0x81, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x70, 0x69, 0x4c, 0x65, 0x76, 0x65,\n\t0x6c, 0x3a, 0x40, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12,\n\t0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,\n\t0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,\n\t0x82, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69,\n\t0x7a, 0x65, 0x72, 0x3a, 0x36, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x67,\n\t0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d,\n\t0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x83, 0x89, 0x03,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x3a, 0x3a, 0x0a, 0x07, 0x62,\n\t0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f,\n\t0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x84, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,\n\t0x62, 0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x3a, 0x43, 0x0a, 0x0c, 0x68, 0x61, 0x6e, 0x64, 0x6c,\n\t0x65, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64,\n\t0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x85, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0b, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x50, 0x61, 0x74, 0x68, 0x3a, 0x58, 0x0a, 0x17,\n\t0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6d,\n\t0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64,\n\t0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9b, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x15, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6d, 0x70,\n\t0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x40, 0x0a, 0x09, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x63,\n\t0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f,\n\t0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xe1, 0x89, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08,\n\t0x68, 0x74, 0x74, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x3a, 0x42, 0x0a, 0x0b, 0x62, 0x61, 0x73, 0x65,\n\t0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,\n\t0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xe2, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0a, 0x62, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x3a, 0x57, 0x0a, 0x16,\n\t0x62, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x70,\n\t0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xab, 0x8c, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x14, 0x62, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x61,\n\t0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x44, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x5f, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4f,\n\t0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x8c, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,\n\t0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x61, 0x74, 0x68, 0x3a, 0x3b, 0x0a, 0x07, 0x72,\n\t0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,\n\t0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x8e, 0x8d, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x07, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61, 0x70, 0x69,\n}\n\nvar file_api_proto_goTypes = []interface{}{\n\t(*descriptorpb.FieldOptions)(nil),     // 0: google.protobuf.FieldOptions\n\t(*descriptorpb.MethodOptions)(nil),    // 1: google.protobuf.MethodOptions\n\t(*descriptorpb.EnumValueOptions)(nil), // 2: google.protobuf.EnumValueOptions\n\t(*descriptorpb.ServiceOptions)(nil),   // 3: google.protobuf.ServiceOptions\n\t(*descriptorpb.MessageOptions)(nil),   // 4: google.protobuf.MessageOptions\n}\nvar file_api_proto_depIdxs = []int32{\n\t0,  // 0: api.raw_body:extendee -> google.protobuf.FieldOptions\n\t0,  // 1: api.query:extendee -> google.protobuf.FieldOptions\n\t0,  // 2: api.header:extendee -> google.protobuf.FieldOptions\n\t0,  // 3: api.cookie:extendee -> google.protobuf.FieldOptions\n\t0,  // 4: api.body:extendee -> google.protobuf.FieldOptions\n\t0,  // 5: api.path:extendee -> google.protobuf.FieldOptions\n\t0,  // 6: api.vd:extendee -> google.protobuf.FieldOptions\n\t0,  // 7: api.form:extendee -> google.protobuf.FieldOptions\n\t0,  // 8: api.js_conv:extendee -> google.protobuf.FieldOptions\n\t0,  // 9: api.file_name:extendee -> google.protobuf.FieldOptions\n\t0,  // 10: api.none:extendee -> google.protobuf.FieldOptions\n\t0,  // 11: api.form_compatible:extendee -> google.protobuf.FieldOptions\n\t0,  // 12: api.js_conv_compatible:extendee -> google.protobuf.FieldOptions\n\t0,  // 13: api.file_name_compatible:extendee -> google.protobuf.FieldOptions\n\t0,  // 14: api.none_compatible:extendee -> google.protobuf.FieldOptions\n\t0,  // 15: api.go_tag:extendee -> google.protobuf.FieldOptions\n\t1,  // 16: api.get:extendee -> google.protobuf.MethodOptions\n\t1,  // 17: api.post:extendee -> google.protobuf.MethodOptions\n\t1,  // 18: api.put:extendee -> google.protobuf.MethodOptions\n\t1,  // 19: api.delete:extendee -> google.protobuf.MethodOptions\n\t1,  // 20: api.patch:extendee -> google.protobuf.MethodOptions\n\t1,  // 21: api.options:extendee -> google.protobuf.MethodOptions\n\t1,  // 22: api.head:extendee -> google.protobuf.MethodOptions\n\t1,  // 23: api.any:extendee -> google.protobuf.MethodOptions\n\t1,  // 24: api.gen_path:extendee -> google.protobuf.MethodOptions\n\t1,  // 25: api.api_version:extendee -> google.protobuf.MethodOptions\n\t1,  // 26: api.tag:extendee -> google.protobuf.MethodOptions\n\t1,  // 27: api.name:extendee -> google.protobuf.MethodOptions\n\t1,  // 28: api.api_level:extendee -> google.protobuf.MethodOptions\n\t1,  // 29: api.serializer:extendee -> google.protobuf.MethodOptions\n\t1,  // 30: api.param:extendee -> google.protobuf.MethodOptions\n\t1,  // 31: api.baseurl:extendee -> google.protobuf.MethodOptions\n\t1,  // 32: api.handler_path:extendee -> google.protobuf.MethodOptions\n\t1,  // 33: api.handler_path_compatible:extendee -> google.protobuf.MethodOptions\n\t2,  // 34: api.http_code:extendee -> google.protobuf.EnumValueOptions\n\t3,  // 35: api.base_domain:extendee -> google.protobuf.ServiceOptions\n\t3,  // 36: api.base_domain_compatible:extendee -> google.protobuf.ServiceOptions\n\t3,  // 37: api.service_path:extendee -> google.protobuf.ServiceOptions\n\t4,  // 38: api.reserve:extendee -> google.protobuf.MessageOptions\n\t39, // [39:39] is the sub-list for method output_type\n\t39, // [39:39] is the sub-list for method input_type\n\t39, // [39:39] is the sub-list for extension type_name\n\t0,  // [0:39] is the sub-list for extension extendee\n\t0,  // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_api_proto_init() }\nfunc file_api_proto_init() {\n\tif File_api_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_api_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   0,\n\t\t\tNumExtensions: 39,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_api_proto_goTypes,\n\t\tDependencyIndexes: file_api_proto_depIdxs,\n\t\tExtensionInfos:    file_api_proto_extTypes,\n\t}.Build()\n\tFile_api_proto = out.File\n\tfile_api_proto_rawDesc = nil\n\tfile_api_proto_goTypes = nil\n\tfile_api_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "cmd/hz/protobuf/api/api.proto",
    "content": "syntax = \"proto2\";\n\npackage api;\n\nimport \"google/protobuf/descriptor.proto\";\n\noption go_package = \"/api\";\n\nextend google.protobuf.FieldOptions {\n  optional string raw_body = 50101;\n  optional string query = 50102;\n  optional string header = 50103;\n  optional string cookie = 50104;\n  optional string body = 50105;\n  optional string path = 50106;\n  optional string vd = 50107;\n  optional string form = 50108;\n  optional string js_conv = 50109;\n  optional string file_name = 50110;\n  optional string none = 50111;\n\n  // 50131~50160 used to extend field option by hz\n  optional string form_compatible = 50131;\n  optional string js_conv_compatible = 50132;\n  optional string file_name_compatible = 50133;\n  optional string none_compatible = 50134;\n  // 50135 is reserved to vt_compatible\n  // optional FieldRules vt_compatible = 50135;\n\n  optional string go_tag = 51001;\n}\n\nextend google.protobuf.MethodOptions {\n  optional string get = 50201;\n  optional string post = 50202;\n  optional string put = 50203;\n  optional string delete = 50204;\n  optional string patch = 50205;\n  optional string options = 50206;\n  optional string head = 50207;\n  optional string any = 50208;\n  optional string gen_path = 50301; // The path specified by the user when the client code is generated, with a higher priority than api_version\n  optional string api_version = 50302; // Specify the value of the :version variable in path when the client code is generated\n  optional string tag = 50303; // rpc tag, can be multiple, separated by commas\n  optional string name = 50304; // Name of rpc\n  optional string api_level = 50305; // Interface Level\n  optional string serializer = 50306; // Serialization method\n  optional string param = 50307; // Whether client requests take public parameters\n  optional string baseurl = 50308; // Baseurl used in ttnet routing\n  optional string handler_path = 50309; // handler_path specifies the path to generate the method\n\n  // 50331~50360 used to extend method option by hz\n  optional string handler_path_compatible = 50331; // handler_path specifies the path to generate the method\n}\n\nextend google.protobuf.EnumValueOptions {\n  optional int32 http_code = 50401;\n\n  // 50431~50460 used to extend enum option by hz\n}\n\nextend google.protobuf.ServiceOptions {\n  optional string base_domain = 50402;\n\n  // 50731~50760 used to extend service option by hz\n  optional string base_domain_compatible = 50731;\n  optional string service_path = 50732;\n}\n\nextend google.protobuf.MessageOptions {\n  // optional FieldRules msg_vt = 50111;\n\n  optional string reserve = 50830;\n  // 550831 is reserved to msg_vt_compatible\n  // optional FieldRules msg_vt_compatible = 50831;\n}"
  },
  {
    "path": "cmd/hz/protobuf/ast.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage protobuf\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/generator\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/protobuf/api\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n\t\"github.com/jhump/protoreflect/desc\"\n\t\"google.golang.org/protobuf/compiler/protogen\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/reflect/protoreflect\"\n\t\"google.golang.org/protobuf/runtime/protoimpl\"\n\t\"google.golang.org/protobuf/types/descriptorpb\"\n)\n\nvar BaseProto = descriptorpb.FileDescriptorProto{}\n\n// getGoPackage get option go_package\n// If pkgMap is specified, the specified value is used as the go_package;\n// If go package is not specified, then the value of package is used as go_package.\nfunc getGoPackage(f *descriptorpb.FileDescriptorProto, pkgMap map[string]string) string {\n\tif f.Options == nil {\n\t\tf.Options = new(descriptorpb.FileOptions)\n\t}\n\tif f.Options.GoPackage == nil {\n\t\tf.Options.GoPackage = new(string)\n\t}\n\tgoPkg := f.Options.GetGoPackage()\n\n\t// if go_package has \";\", for example go_package=\"/a/b/c;d\", we will use \"/a/b/c\" as go_package\n\tif strings.Contains(goPkg, \";\") {\n\t\tpkg := strings.Split(goPkg, \";\")\n\t\tif len(pkg) == 2 {\n\t\t\tlogs.Warnf(\"The go_package of the file(%s) is \\\"%s\\\", hz will use \\\"%s\\\" as the go_package.\", f.GetName(), goPkg, pkg[0])\n\t\t\tgoPkg = pkg[0]\n\t\t}\n\n\t}\n\n\tif goPkg == \"\" {\n\t\tgoPkg = f.GetPackage()\n\t}\n\tif opt, ok := pkgMap[f.GetName()]; ok {\n\t\treturn opt\n\t}\n\treturn goPkg\n}\n\nfunc switchBaseType(typ descriptorpb.FieldDescriptorProto_Type) *model.Type {\n\tswitch typ {\n\tcase descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:\n\t\treturn nil\n\tcase descriptorpb.FieldDescriptorProto_TYPE_INT64:\n\t\treturn model.TypeInt64\n\tcase descriptorpb.FieldDescriptorProto_TYPE_INT32:\n\t\treturn model.TypeInt32\n\tcase descriptorpb.FieldDescriptorProto_TYPE_UINT64:\n\t\treturn model.TypeUint64\n\tcase descriptorpb.FieldDescriptorProto_TYPE_UINT32:\n\t\treturn model.TypeUint32\n\tcase descriptorpb.FieldDescriptorProto_TYPE_FIXED64:\n\t\treturn model.TypeUint64\n\tcase descriptorpb.FieldDescriptorProto_TYPE_FIXED32:\n\t\treturn model.TypeUint32\n\tcase descriptorpb.FieldDescriptorProto_TYPE_BOOL:\n\t\treturn model.TypeBool\n\tcase descriptorpb.FieldDescriptorProto_TYPE_STRING:\n\t\treturn model.TypeString\n\tcase descriptorpb.FieldDescriptorProto_TYPE_BYTES:\n\t\treturn model.TypeBinary\n\tcase descriptorpb.FieldDescriptorProto_TYPE_SFIXED32:\n\t\treturn model.TypeInt32\n\tcase descriptorpb.FieldDescriptorProto_TYPE_SFIXED64:\n\t\treturn model.TypeInt64\n\tcase descriptorpb.FieldDescriptorProto_TYPE_SINT32:\n\t\treturn model.TypeInt32\n\tcase descriptorpb.FieldDescriptorProto_TYPE_SINT64:\n\t\treturn model.TypeInt64\n\tcase descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:\n\t\treturn model.TypeFloat64\n\tcase descriptorpb.FieldDescriptorProto_TYPE_FLOAT:\n\t\treturn model.TypeFloat32\n\t}\n\treturn nil\n}\n\nfunc astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver, cmdType string, gen *protogen.Plugin) ([]*generator.Service, error) {\n\tresolver.ExportReferred(true, false)\n\tss := ast.GetService()\n\tout := make([]*generator.Service, 0, len(ss))\n\tvar merges model.Models\n\n\tfor _, s := range ss {\n\t\tservice := &generator.Service{\n\t\t\tName: s.GetName(),\n\t\t}\n\n\t\tservice.BaseDomain = \"\"\n\t\tdomainAnno := getCompatibleAnnotation(s.GetOptions(), api.E_BaseDomain, api.E_BaseDomainCompatible)\n\t\tif cmdType == meta.CmdClient {\n\t\t\tval, ok := domainAnno.(string)\n\t\t\tif ok && len(val) != 0 {\n\t\t\t\tservice.BaseDomain = val\n\t\t\t}\n\t\t}\n\n\t\tms := s.GetMethod()\n\t\tmethods := make([]*generator.HttpMethod, 0, len(ms))\n\t\tclientMethods := make([]*generator.ClientMethod, 0, len(ms))\n\t\tservicePathAnno := checkFirstOption(api.E_ServicePath, s.GetOptions())\n\t\tservicePath := \"\"\n\t\tif val, ok := servicePathAnno.(string); ok {\n\t\t\tservicePath = val\n\t\t}\n\t\tfor _, m := range ms {\n\t\t\trs := getAllOptions(HttpMethodOptions, m.GetOptions())\n\t\t\tif len(rs) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\thttpOpts := httpOptions{}\n\t\t\tfor k, v := range rs {\n\t\t\t\thttpOpts = append(httpOpts, httpOption{\n\t\t\t\t\tmethod: k,\n\t\t\t\t\tpath:   v.(string),\n\t\t\t\t})\n\t\t\t}\n\t\t\t// turn the map into a slice and sort it to make sure getting the results in the same order every time\n\t\t\tsort.Sort(httpOpts)\n\n\t\t\tvar handlerOutDir string\n\t\t\tgenPath := getCompatibleAnnotation(m.GetOptions(), api.E_HandlerPath, api.E_HandlerPathCompatible)\n\t\t\thandlerOutDir, ok := genPath.(string)\n\t\t\tif !ok || len(handlerOutDir) == 0 {\n\t\t\t\thandlerOutDir = \"\"\n\t\t\t}\n\t\t\tif len(handlerOutDir) == 0 {\n\t\t\t\thandlerOutDir = servicePath\n\t\t\t}\n\n\t\t\t// protoGoInfo can get generated \"Go Info\" for proto file.\n\t\t\t// the type name may be different between \"***.proto\" and \"***.pb.go\"\n\t\t\tprotoGoInfo, exist := gen.FilesByPath[ast.GetName()]\n\t\t\tif !exist {\n\t\t\t\treturn nil, fmt.Errorf(\"file(%s) can not exist\", ast.GetName())\n\t\t\t}\n\t\t\tmethodGoInfo, err := getMethod(protoGoInfo, s, m)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tinputGoType := methodGoInfo.Input\n\t\t\toutputGoType := methodGoInfo.Output\n\n\t\t\treqName := m.GetInputType()\n\t\t\tsb, err := resolver.ResolveIdentifier(reqName)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treqName = util.BaseName(sb.Scope.GetOptions().GetGoPackage(), \"\") + \".\" + inputGoType.GoIdent.GoName\n\t\t\treqRawName := inputGoType.GoIdent.GoName\n\t\t\treqPackage := util.BaseName(sb.Scope.GetOptions().GetGoPackage(), \"\")\n\t\t\trespName := m.GetOutputType()\n\t\t\tst, err := resolver.ResolveIdentifier(respName)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\trespName = util.BaseName(st.Scope.GetOptions().GetGoPackage(), \"\") + \".\" + outputGoType.GoIdent.GoName\n\t\t\trespRawName := outputGoType.GoIdent.GoName\n\t\t\trespPackage := util.BaseName(sb.Scope.GetOptions().GetGoPackage(), \"\")\n\n\t\t\tvar serializer string\n\t\t\tsl, sv := checkFirstOptions(SerializerOptions, m.GetOptions())\n\t\t\tif sl != \"\" {\n\t\t\t\tserializer = sv.(string)\n\t\t\t}\n\n\t\t\tmethod := &generator.HttpMethod{\n\t\t\t\tName:       util.CamelString(m.GetName()),\n\t\t\t\tHTTPMethod: httpOpts[0].method,\n\t\t\t\tPath:       httpOpts[0].path,\n\t\t\t\tSerializer: serializer,\n\t\t\t\tOutputDir:  handlerOutDir,\n\t\t\t\tGenHandler: true,\n\t\t\t}\n\n\t\t\tgoOptMapAlias := make(map[string]string, 1)\n\t\t\trefs := resolver.ExportReferred(false, true)\n\t\t\tmethod.Models = make(map[string]*model.Model, len(refs))\n\t\t\tfor _, ref := range refs {\n\t\t\t\tif val, exist := method.Models[ref.Model.PackageName]; exist {\n\t\t\t\t\tif val.Package == ref.Model.Package {\n\t\t\t\t\t\tmethod.Models[ref.Model.PackageName] = ref.Model\n\t\t\t\t\t\tgoOptMapAlias[ref.Model.Package] = ref.Model.PackageName\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfile := filepath.Base(ref.Model.FilePath)\n\t\t\t\t\t\tfileName := strings.Split(file, \".\")\n\t\t\t\t\t\tnewPkg := fileName[len(fileName)-2] + \"_\" + val.PackageName\n\t\t\t\t\t\tmethod.Models[newPkg] = ref.Model\n\t\t\t\t\t\tgoOptMapAlias[ref.Model.Package] = newPkg\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tmethod.Models[ref.Model.PackageName] = ref.Model\n\t\t\t\tgoOptMapAlias[ref.Model.Package] = ref.Model.PackageName\n\t\t\t}\n\t\t\tmerges = service.Models\n\t\t\tmerges.MergeMap(method.Models)\n\t\t\tif goOptMapAlias[sb.Scope.GetOptions().GetGoPackage()] != \"\" {\n\t\t\t\treqName = goOptMapAlias[sb.Scope.GetOptions().GetGoPackage()] + \".\" + inputGoType.GoIdent.GoName\n\t\t\t}\n\t\t\tif goOptMapAlias[sb.Scope.GetOptions().GetGoPackage()] != \"\" {\n\t\t\t\trespName = goOptMapAlias[st.Scope.GetOptions().GetGoPackage()] + \".\" + outputGoType.GoIdent.GoName\n\t\t\t}\n\t\t\tmethod.RequestTypeName = reqName\n\t\t\tmethod.RequestTypeRawName = reqRawName\n\t\t\tmethod.RequestTypePackage = reqPackage\n\t\t\tmethod.ReturnTypeName = respName\n\t\t\tmethod.ReturnTypeRawName = respRawName\n\t\t\tmethod.ReturnTypePackage = respPackage\n\n\t\t\tmethods = append(methods, method)\n\t\t\tfor idx, anno := range httpOpts {\n\t\t\t\tif idx == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttmp := *method\n\t\t\t\ttmp.HTTPMethod = anno.method\n\t\t\t\ttmp.Path = anno.path\n\t\t\t\ttmp.GenHandler = false\n\t\t\t\tmethods = append(methods, &tmp)\n\t\t\t}\n\n\t\t\tif cmdType == meta.CmdClient {\n\t\t\t\tclientMethod := &generator.ClientMethod{}\n\t\t\t\tclientMethod.HttpMethod = method\n\t\t\t\terr := parseAnnotationToClient(clientMethod, gen, ast, s, m)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tclientMethods = append(clientMethods, clientMethod)\n\t\t\t}\n\t\t}\n\n\t\tservice.ClientMethods = clientMethods\n\t\tservice.Methods = methods\n\t\tservice.Models = merges\n\t\tout = append(out, service)\n\t}\n\treturn out, nil\n}\n\nfunc getCompatibleAnnotation(options proto.Message, anno, compatibleAnno *protoimpl.ExtensionInfo) interface{} {\n\tif proto.HasExtension(options, anno) {\n\t\treturn checkFirstOption(anno, options)\n\t} else if proto.HasExtension(options, compatibleAnno) {\n\t\treturn checkFirstOption(compatibleAnno, options)\n\t}\n\n\treturn nil\n}\n\nfunc parseAnnotationToClient(clientMethod *generator.ClientMethod, gen *protogen.Plugin, ast *descriptorpb.FileDescriptorProto, s *descriptorpb.ServiceDescriptorProto, m *descriptorpb.MethodDescriptorProto) error {\n\tfile, exist := gen.FilesByPath[ast.GetName()]\n\tif !exist {\n\t\treturn fmt.Errorf(\"file(%s) can not exist\", ast.GetName())\n\t}\n\tmethod, err := getMethod(file, s, m)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// pb input type must be message\n\tinputType := method.Input\n\tvar (\n\t\thasBodyAnnotation bool\n\t\thasFormAnnotation bool\n\t)\n\tfor _, f := range inputType.Fields {\n\t\thasAnnotation := false\n\t\tisStringFieldType := false\n\t\tif f.Desc.Kind() == protoreflect.StringKind {\n\t\t\tisStringFieldType = true\n\t\t}\n\t\tif proto.HasExtension(f.Desc.Options(), api.E_Query) {\n\t\t\thasAnnotation = true\n\t\t\tqueryAnnos := proto.GetExtension(f.Desc.Options(), api.E_Query)\n\t\t\tval := checkSnakeName(queryAnnos.(string))\n\t\t\tclientMethod.QueryParamsCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", val, f.GoName)\n\t\t}\n\n\t\tif proto.HasExtension(f.Desc.Options(), api.E_Path) {\n\t\t\thasAnnotation = true\n\t\t\tpathAnnos := proto.GetExtension(f.Desc.Options(), api.E_Path)\n\t\t\tval := pathAnnos.(string)\n\t\t\tif isStringFieldType {\n\t\t\t\tclientMethod.PathParamsCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", val, f.GoName)\n\t\t\t} else {\n\t\t\t\tclientMethod.PathParamsCode += fmt.Sprintf(\"%q: fmt.Sprint(req.Get%s()),\\n\", val, f.GoName)\n\t\t\t}\n\t\t}\n\n\t\tif proto.HasExtension(f.Desc.Options(), api.E_Header) {\n\t\t\thasAnnotation = true\n\t\t\theaderAnnos := proto.GetExtension(f.Desc.Options(), api.E_Header)\n\t\t\tval := headerAnnos.(string)\n\t\t\tif isStringFieldType {\n\t\t\t\tclientMethod.HeaderParamsCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", val, f.GoName)\n\t\t\t} else {\n\t\t\t\tclientMethod.HeaderParamsCode += fmt.Sprintf(\"%q: fmt.Sprint(req.Get%s()),\\n\", val, f.GoName)\n\t\t\t}\n\t\t}\n\n\t\tif formAnnos := getCompatibleAnnotation(f.Desc.Options(), api.E_Form, api.E_FormCompatible); formAnnos != nil {\n\t\t\thasAnnotation = true\n\t\t\thasFormAnnotation = true\n\t\t\tval := checkSnakeName(formAnnos.(string))\n\t\t\tif isStringFieldType {\n\t\t\t\tclientMethod.FormValueCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", val, f.GoName)\n\t\t\t} else {\n\t\t\t\tclientMethod.FormValueCode += fmt.Sprintf(\"%q: fmt.Sprint(req.Get%s()),\\n\", val, f.GoName)\n\t\t\t}\n\t\t}\n\n\t\tif proto.HasExtension(f.Desc.Options(), api.E_Body) {\n\t\t\thasAnnotation = true\n\t\t\thasBodyAnnotation = true\n\t\t}\n\n\t\tif fileAnnos := getCompatibleAnnotation(f.Desc.Options(), api.E_FileName, api.E_FileNameCompatible); fileAnnos != nil {\n\t\t\thasAnnotation = true\n\t\t\thasFormAnnotation = true\n\t\t\tval := fileAnnos.(string)\n\t\t\tclientMethod.FormFileCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", val, f.GoName)\n\t\t}\n\t\tif proto.HasExtension(f.Desc.Options(), api.E_Cookie) {\n\t\t\thasAnnotation = true\n\t\t\t// cookie do nothing\n\t\t}\n\t\tif !hasAnnotation && strings.EqualFold(clientMethod.HTTPMethod, \"get\") {\n\t\t\tclientMethod.QueryParamsCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", checkSnakeName(string(f.Desc.Name())), f.GoName)\n\t\t}\n\t}\n\tclientMethod.BodyParamsCode = meta.SetBodyParam\n\tif hasBodyAnnotation && hasFormAnnotation {\n\t\tclientMethod.FormValueCode = \"\"\n\t\tclientMethod.FormFileCode = \"\"\n\t}\n\tif !hasBodyAnnotation && hasFormAnnotation {\n\t\tclientMethod.BodyParamsCode = \"\"\n\t}\n\n\treturn nil\n}\n\nfunc getMethod(file *protogen.File, s *descriptorpb.ServiceDescriptorProto, m *descriptorpb.MethodDescriptorProto) (*protogen.Method, error) {\n\tfor _, f := range file.Services {\n\t\tif f.Desc.Name() == protoreflect.Name(s.GetName()) {\n\t\t\tfor _, method := range f.Methods {\n\t\t\t\tif string(method.Desc.Name()) == m.GetName() {\n\t\t\t\t\treturn method, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"can not find method: %s\", m.GetName())\n}\n\n//---------------------------------Model--------------------------------\n\nfunc astToModel(ast *descriptorpb.FileDescriptorProto, rs *Resolver) (*model.Model, error) {\n\tmain := rs.mainPkg.Model\n\tif main == nil {\n\t\tmain = new(model.Model)\n\t}\n\n\tmainFileDes := rs.files.PbReflect[ast.GetName()]\n\tisProto3 := mainFileDes.IsProto3()\n\t// Enums\n\tems := ast.GetEnumType()\n\tenums := make([]model.Enum, 0, len(ems))\n\tfor _, e := range ems {\n\t\tem := model.Enum{\n\t\t\tScope:  main,\n\t\t\tName:   e.GetName(),\n\t\t\tGoType: \"int32\",\n\t\t}\n\t\tes := e.GetValue()\n\t\tvs := make([]model.Constant, 0, len(es))\n\t\tfor _, ee := range es {\n\t\t\tvs = append(vs, model.Constant{\n\t\t\t\tScope: main,\n\t\t\t\tName:  ee.GetName(),\n\t\t\t\tType:  model.TypeInt32,\n\t\t\t\tValue: model.IntExpression{Src: int(ee.GetNumber())},\n\t\t\t})\n\t\t}\n\t\tem.Values = vs\n\t\tenums = append(enums, em)\n\t}\n\tmain.Enums = enums\n\n\t// Structs\n\tsts := ast.GetMessageType()\n\tstructs := make([]model.Struct, 0, len(sts)*2)\n\toneofs := make([]model.Oneof, 0, 1)\n\tfor _, st := range sts {\n\t\tstMessage := mainFileDes.FindMessage(ast.GetPackage() + \".\" + st.GetName())\n\t\tstLeadingComments := getMessageLeadingComments(stMessage)\n\t\ts := model.Struct{\n\t\t\tScope:           main,\n\t\t\tName:            st.GetName(),\n\t\t\tCategory:        model.CategoryStruct,\n\t\t\tLeadingComments: stLeadingComments,\n\t\t}\n\n\t\tns := st.GetNestedType()\n\t\tnestedMessageInfoMap := getNestedMessageInfoMap(stMessage)\n\t\tfor _, nt := range ns {\n\t\t\tif IsMapEntry(nt) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnestedMessageInfo := nestedMessageInfoMap[nt.GetName()]\n\t\t\tnestedMessageLeadingComment := getMessageLeadingComments(nestedMessageInfo)\n\t\t\ts := model.Struct{\n\t\t\t\tScope:           main,\n\t\t\t\tName:            st.GetName() + \"_\" + nt.GetName(),\n\t\t\t\tCategory:        model.CategoryStruct,\n\t\t\t\tLeadingComments: nestedMessageLeadingComment,\n\t\t\t}\n\t\t\tfs := nt.GetField()\n\t\t\tns := nt.GetNestedType()\n\t\t\tvs := make([]model.Field, 0, len(fs))\n\n\t\t\toneofMap := make(map[string]model.Field)\n\t\t\toneofType, err := resolveOneof(nestedMessageInfo, oneofMap, rs, isProto3, s, ns)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\toneofs = append(oneofs, oneofType...)\n\n\t\t\tchoiceSet := make(map[string]bool)\n\n\t\t\tfor _, f := range fs {\n\t\t\t\tif field, exist := oneofMap[f.GetName()]; exist {\n\t\t\t\t\tif _, ex := choiceSet[field.Name]; !ex {\n\t\t\t\t\t\tchoiceSet[field.Name] = true\n\t\t\t\t\t\tvs = append(vs, field)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tdv := f.GetDefaultValue()\n\t\t\t\tfieldLeadingComments, fieldTrailingComments := getFiledComments(f, nestedMessageInfo)\n\t\t\t\tt, err := rs.ResolveType(f, ns)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tfield := model.Field{\n\t\t\t\t\tScope:            &s,\n\t\t\t\t\tName:             util.CamelString(f.GetName()),\n\t\t\t\t\tType:             t,\n\t\t\t\t\tLeadingComments:  fieldLeadingComments,\n\t\t\t\t\tTrailingComments: fieldTrailingComments,\n\t\t\t\t\tIsPointer:        isPointer(f, isProto3),\n\t\t\t\t}\n\t\t\t\tif dv != \"\" {\n\t\t\t\t\tfield.IsSetDefault = true\n\t\t\t\t\tfield.DefaultValue, err = parseDefaultValue(f.GetType(), f.GetDefaultValue())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\terr = injectTagsToModel(f, &field, true)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tvs = append(vs, field)\n\t\t\t}\n\t\t\tcheckDuplicatedFileName(vs)\n\t\t\ts.Fields = vs\n\t\t\tstructs = append(structs, s)\n\t\t}\n\n\t\tfs := st.GetField()\n\t\tvs := make([]model.Field, 0, len(fs))\n\n\t\toneofMap := make(map[string]model.Field)\n\t\toneofType, err := resolveOneof(stMessage, oneofMap, rs, isProto3, s, ns)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\toneofs = append(oneofs, oneofType...)\n\n\t\tchoiceSet := make(map[string]bool)\n\n\t\tfor _, f := range fs {\n\t\t\tif field, exist := oneofMap[f.GetName()]; exist {\n\t\t\t\tif _, ex := choiceSet[field.Name]; !ex {\n\t\t\t\t\tchoiceSet[field.Name] = true\n\t\t\t\t\tvs = append(vs, field)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdv := f.GetDefaultValue()\n\t\t\tfieldLeadingComments, fieldTrailingComments := getFiledComments(f, stMessage)\n\t\t\tt, err := rs.ResolveType(f, ns)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tfield := model.Field{\n\t\t\t\tScope:            &s,\n\t\t\t\tName:             util.CamelString(f.GetName()),\n\t\t\t\tType:             t,\n\t\t\t\tLeadingComments:  fieldLeadingComments,\n\t\t\t\tTrailingComments: fieldTrailingComments,\n\t\t\t\tIsPointer:        isPointer(f, isProto3),\n\t\t\t}\n\t\t\tif dv != \"\" {\n\t\t\t\tfield.IsSetDefault = true\n\t\t\t\tfield.DefaultValue, err = parseDefaultValue(f.GetType(), f.GetDefaultValue())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t\terr = injectTagsToModel(f, &field, true)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvs = append(vs, field)\n\t\t}\n\t\tcheckDuplicatedFileName(vs)\n\t\ts.Fields = vs\n\t\tstructs = append(structs, s)\n\n\t}\n\tmain.Oneofs = oneofs\n\tmain.Structs = structs\n\n\t// In case of only the service refers another model, therefore scanning service is necessary\n\tss := ast.GetService()\n\tfor _, s := range ss {\n\t\tms := s.GetMethod()\n\t\tfor _, m := range ms {\n\t\t\t_, err := rs.ResolveIdentifier(m.GetInputType())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t_, err = rs.ResolveIdentifier(m.GetOutputType())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn main, nil\n}\n\n// getMessageLeadingComments can get struct LeadingComment\nfunc getMessageLeadingComments(stMessage *desc.MessageDescriptor) string {\n\tif stMessage == nil {\n\t\treturn \"\"\n\t}\n\tstComments := stMessage.GetSourceInfo().GetLeadingComments()\n\tstComments = formatComments(stComments)\n\n\treturn stComments\n}\n\n// getFiledComments can get field LeadingComments and field TailingComments for field\nfunc getFiledComments(f *descriptorpb.FieldDescriptorProto, stMessage *desc.MessageDescriptor) (string, string) {\n\tif stMessage == nil {\n\t\treturn \"\", \"\"\n\t}\n\n\tfieldNum := f.GetNumber()\n\tfield := stMessage.FindFieldByNumber(fieldNum)\n\tfieldInfo := field.GetSourceInfo()\n\n\tfieldLeadingComments := fieldInfo.GetLeadingComments()\n\tfieldTailingComments := fieldInfo.GetTrailingComments()\n\n\tfieldLeadingComments = formatComments(fieldLeadingComments)\n\tfieldTailingComments = formatComments(fieldTailingComments)\n\n\treturn fieldLeadingComments, fieldTailingComments\n}\n\n// formatComments can format the comments for beauty\nfunc formatComments(comments string) string {\n\tif len(comments) == 0 {\n\t\treturn \"\"\n\t}\n\n\tcomments = util.TrimLastChar(comments)\n\tcomments = util.AddSlashForComments(comments)\n\n\treturn comments\n}\n\n// getNestedMessageInfoMap can get all nested struct\nfunc getNestedMessageInfoMap(stMessage *desc.MessageDescriptor) map[string]*desc.MessageDescriptor {\n\tnestedMessage := stMessage.GetNestedMessageTypes()\n\tnestedMessageInfoMap := make(map[string]*desc.MessageDescriptor, len(nestedMessage))\n\n\tfor _, nestedMsg := range nestedMessage {\n\t\tnestedMsgName := nestedMsg.GetName()\n\t\tnestedMessageInfoMap[nestedMsgName] = nestedMsg\n\t}\n\n\treturn nestedMessageInfoMap\n}\n\nfunc parseDefaultValue(typ descriptorpb.FieldDescriptorProto_Type, val string) (model.Literal, error) {\n\tswitch typ {\n\tcase descriptorpb.FieldDescriptorProto_TYPE_BYTES, descriptorpb.FieldDescriptorProto_TYPE_STRING:\n\t\treturn model.StringExpression{Src: val}, nil\n\tcase descriptorpb.FieldDescriptorProto_TYPE_BOOL:\n\t\treturn model.BoolExpression{Src: val == \"true\"}, nil\n\tcase descriptorpb.FieldDescriptorProto_TYPE_DOUBLE,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_FLOAT,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_INT64,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_UINT64,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_INT32,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_FIXED64,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_FIXED32,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_UINT32,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_ENUM,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_SFIXED32,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_SFIXED64,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_SINT32,\n\t\tdescriptorpb.FieldDescriptorProto_TYPE_SINT64:\n\t\treturn model.NumberExpression{Src: val}, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported type %s\", typ.String())\n\t}\n}\n\nfunc isPointer(f *descriptorpb.FieldDescriptorProto, isProto3 bool) bool {\n\tif f.GetType() == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE || f.GetType() == descriptorpb.FieldDescriptorProto_TYPE_BYTES {\n\t\treturn false\n\t}\n\n\tif !isProto3 {\n\t\tif f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\n\tswitch f.GetLabel() {\n\tcase descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL:\n\t\tif !f.GetProto3Optional() {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc resolveOneof(stMessage *desc.MessageDescriptor, oneofMap map[string]model.Field, rs *Resolver, isProto3 bool, s model.Struct, ns []*descriptorpb.DescriptorProto) ([]model.Oneof, error) {\n\toneofs := make([]model.Oneof, 0, 1)\n\tif len(stMessage.GetOneOfs()) != 0 {\n\t\tfor _, oneof := range stMessage.GetOneOfs() {\n\t\t\tif isProto3 {\n\t\t\t\tif oneof.IsSynthetic() {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\toneofName := oneof.GetName()\n\t\t\tmessageName := s.Name\n\t\t\ttypeName := \"is\" + messageName + \"_\" + oneofName\n\t\t\tfield := model.Field{\n\t\t\t\tScope:     &s,\n\t\t\t\tName:      util.CamelString(oneofName),\n\t\t\t\tType:      model.NewOneofType(typeName),\n\t\t\t\tIsPointer: false,\n\t\t\t}\n\n\t\t\toneofComment := oneof.GetSourceInfo().GetLeadingComments()\n\t\t\toneofComment = formatComments(oneofComment)\n\t\t\tvar oneofLeadingComments string\n\t\t\tif oneofComment == \"\" {\n\t\t\t\toneofLeadingComments = fmt.Sprintf(\" Types that are assignable to %s:\\n\", oneofName)\n\t\t\t} else {\n\t\t\t\toneofLeadingComments = fmt.Sprintf(\"%s\\n//\\n// Types that are assignable to %s:\\n\", oneofComment, oneofName)\n\t\t\t}\n\t\t\tfor idx, ch := range oneof.GetChoices() {\n\t\t\t\tif idx == len(oneof.GetChoices())-1 {\n\t\t\t\t\toneofLeadingComments = oneofLeadingComments + fmt.Sprintf(\"//  *%s_%s\", messageName, ch.GetName())\n\t\t\t\t} else {\n\t\t\t\t\toneofLeadingComments = oneofLeadingComments + fmt.Sprintf(\"//  *%s_%s\\n\", messageName, ch.GetName())\n\t\t\t\t}\n\t\t\t}\n\t\t\tfield.LeadingComments = oneofLeadingComments\n\n\t\t\tchoices := make([]model.Choice, 0, len(oneof.GetChoices()))\n\t\t\tfor _, ch := range oneof.GetChoices() {\n\t\t\t\tt, err := rs.ResolveType(ch.AsFieldDescriptorProto(), ns)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tchoice := model.Choice{\n\t\t\t\t\tMessageName: messageName,\n\t\t\t\t\tChoiceName:  ch.GetName(),\n\t\t\t\t\tType:        t,\n\t\t\t\t}\n\t\t\t\tchoices = append(choices, choice)\n\t\t\t\toneofMap[ch.GetName()] = field\n\t\t\t}\n\n\t\t\toneofType := model.Oneof{\n\t\t\t\tMessageName:   messageName,\n\t\t\t\tOneofName:     oneofName,\n\t\t\t\tInterfaceName: typeName,\n\t\t\t\tChoices:       choices,\n\t\t\t}\n\n\t\t\toneofs = append(oneofs, oneofType)\n\t\t}\n\t}\n\treturn oneofs, nil\n}\n\nfunc getNewFieldName(fieldName string, fieldNameSet map[string]bool) string {\n\tif _, ex := fieldNameSet[fieldName]; ex {\n\t\tfieldName = fieldName + \"_\"\n\t\treturn getNewFieldName(fieldName, fieldNameSet)\n\t}\n\treturn fieldName\n}\n\nfunc checkDuplicatedFileName(vs []model.Field) {\n\tfieldNameSet := make(map[string]bool)\n\tfor i := 0; i < len(vs); i++ {\n\t\tif _, ex := fieldNameSet[vs[i].Name]; ex {\n\t\t\tnewName := getNewFieldName(vs[i].Name, fieldNameSet)\n\t\t\tfieldNameSet[newName] = true\n\t\t\tvs[i].Name = newName\n\t\t} else {\n\t\t\tfieldNameSet[vs[i].Name] = true\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/protobuf/plugin.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Copyright (c) 2018 The Go Authors. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *  * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *  * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *  * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protobuf\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/config\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n\tgengo \"google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo\"\n\t\"google.golang.org/protobuf/compiler/protogen\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/runtime/protoimpl\"\n\t\"google.golang.org/protobuf/types/descriptorpb\"\n\t\"google.golang.org/protobuf/types/pluginpb\"\n)\n\ntype Plugin struct {\n\t*protogen.Plugin\n\tPackage      string\n\tRecursive    bool\n\tOutDir       string\n\tModelDir     string\n\tUseDir       string\n\tIdlClientDir string\n\tRmTags       RemoveTags\n\tPkgMap       map[string]string\n\tlogger       *logs.StdLogger\n}\n\ntype RemoveTags []string\n\nfunc (rm *RemoveTags) Exist(tag string) bool {\n\tfor _, rmTag := range *rm {\n\t\tif rmTag == tag {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar debugPlugin = os.Getenv(\"HERTZ_DEBUG_PLUGIN\") != \"\"\n\nfunc (plugin *Plugin) Run() int {\n\tplugin.setLogger()\n\targs := &config.Argument{}\n\tdefer func() {\n\t\tif args == nil {\n\t\t\treturn\n\t\t}\n\t\tif args.Verbose {\n\t\t\tverboseLog := plugin.recvVerboseLogger()\n\t\t\tif len(verboseLog) != 0 {\n\t\t\t\tfmt.Fprintf(os.Stderr, verboseLog)\n\t\t\t}\n\t\t} else {\n\t\t\twarning := plugin.recvWarningLogger()\n\t\t\tif len(warning) != 0 {\n\t\t\t\tfmt.Fprintf(os.Stderr, warning)\n\t\t\t}\n\t\t}\n\t}()\n\t// read protoc request\n\tin, err := ioutil.ReadAll(os.Stdin)\n\tif err != nil {\n\t\tlogs.Errorf(\"read request failed: %s\\n\", err.Error())\n\t\treturn meta.PluginError\n\t}\n\n\treq := &pluginpb.CodeGeneratorRequest{}\n\terr = proto.Unmarshal(in, req)\n\tif err != nil {\n\t\tlogs.Errorf(\"unmarshal request failed: %s\\n\", err.Error())\n\t\treturn meta.PluginError\n\t}\n\n\targs, err = plugin.parseArgs(*req.Parameter)\n\tif err != nil {\n\t\tlogs.Errorf(\"parse args failed: %s\\n\", err.Error())\n\t\treturn meta.PluginError\n\t}\n\n\tif debugPlugin {\n\t\tos.WriteFile(\"./req.pb\", in, 0644)\n\t\tjs, err := json.Marshal(args)\n\t\tif err != nil {\n\t\t\tlogs.Errorf(\"marshal request failed: %s\\n\", err.Error())\n\t\t\treturn meta.PluginError\n\t\t}\n\t\tos.WriteFile(\"./args.json\", js, 0644)\n\t}\n\n\tCheckTagOption(args)\n\t// generate\n\terr = plugin.Handle(req, args)\n\tif err != nil {\n\t\tlogs.Errorf(\"generate failed: %s\\n\", err.Error())\n\t\treturn meta.PluginError\n\t}\n\treturn 0\n}\n\nfunc (plugin *Plugin) setLogger() {\n\tplugin.logger = logs.NewStdLogger(logs.LevelInfo)\n\tplugin.logger.Defer = true\n\tplugin.logger.ErrOnly = true\n\tlogs.SetLogger(plugin.logger)\n}\n\nfunc (plugin *Plugin) recvWarningLogger() string {\n\twarns := plugin.logger.Warn()\n\tplugin.logger.Flush()\n\tlogs.SetLogger(logs.NewStdLogger(logs.LevelInfo))\n\treturn warns\n}\n\nfunc (plugin *Plugin) recvVerboseLogger() string {\n\tinfo := plugin.logger.Out()\n\twarns := plugin.logger.Warn()\n\tverboseLog := string(info) + warns\n\tplugin.logger.Flush()\n\tlogs.SetLogger(logs.NewStdLogger(logs.LevelInfo))\n\treturn verboseLog\n}\n\nfunc (plugin *Plugin) parseArgs(param string) (*config.Argument, error) {\n\targs := new(config.Argument)\n\tparams := strings.Split(param, \",\")\n\terr := args.Unpack(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tplugin.Package, err = args.GetGoPackage()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tplugin.Recursive = !args.NoRecurse\n\tplugin.ModelDir, err = args.GetModelDir()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tplugin.OutDir = args.OutDir\n\tplugin.PkgMap = args.OptPkgMap\n\tplugin.UseDir = args.Use\n\treturn args, nil\n}\n\nfunc (plugin *Plugin) Response(resp *pluginpb.CodeGeneratorResponse) error {\n\tout, err := proto.Marshal(resp)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal response failed: %s\", err.Error())\n\t}\n\t_, err = os.Stdout.Write(out)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"write response failed: %s\", err.Error())\n\t}\n\treturn nil\n}\n\nfunc (plugin *Plugin) Handle(req *pluginpb.CodeGeneratorRequest, args *config.Argument) error {\n\tplugin.fixGoPackage(req, plugin.PkgMap, args.TrimGoPackage)\n\n\t// new plugin\n\topts := protogen.Options{}\n\tgen, err := opts.New(req)\n\tplugin.Plugin = gen\n\tplugin.RmTags = args.RmTags\n\tif err != nil {\n\t\treturn fmt.Errorf(\"new protoc plugin failed: %s\", err.Error())\n\t}\n\t// plugin start working\n\terr = plugin.GenerateFiles(gen)\n\tif err != nil {\n\t\t// Error within the plugin will be responded by the plugin.\n\t\t// But if the plugin does not response correctly, the error is returned to the upper level.\n\t\terr := fmt.Errorf(\"generate model file failed: %s\", err.Error())\n\t\tgen.Error(err)\n\t\tresp := gen.Response()\n\t\terr2 := plugin.Response(resp)\n\t\tif err2 != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\tif args.CmdType == meta.CmdModel {\n\t\tresp := gen.Response()\n\t\t// plugin stop working\n\t\terr = plugin.Response(resp)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"write response failed: %s\", err.Error())\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tfiles := gen.Request.ProtoFile\n\tmaps := make(map[string]*descriptorpb.FileDescriptorProto, len(files))\n\tfor _, file := range files {\n\t\tmaps[file.GetName()] = file\n\t}\n\tmain := maps[gen.Request.FileToGenerate[len(gen.Request.FileToGenerate)-1]]\n\tdeps := make(map[string]*descriptorpb.FileDescriptorProto, len(main.GetDependency()))\n\tfor _, dep := range main.GetDependency() {\n\t\tif f, ok := maps[dep]; !ok {\n\t\t\terr := fmt.Errorf(\"dependency file not found: %s\", dep)\n\t\t\tgen.Error(err)\n\t\t\tresp := gen.Response()\n\t\t\terr2 := plugin.Response(resp)\n\t\t\tif err2 != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t} else {\n\t\t\tdeps[dep] = f\n\t\t}\n\t}\n\n\tpkgFiles, err := plugin.genHttpPackage(main, deps, args)\n\tif err != nil {\n\t\terr := fmt.Errorf(\"generate package files failed: %s\", err.Error())\n\t\tgen.Error(err)\n\t\tresp := gen.Response()\n\t\terr2 := plugin.Response(resp)\n\t\tif err2 != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\t// construct plugin response\n\tresp := gen.Response()\n\t// all files that need to be generated are returned to protoc\n\tfor _, pkgFile := range pkgFiles {\n\t\tfilePath := pkgFile.Path\n\t\tcontent := pkgFile.Content\n\t\trenderFile := &pluginpb.CodeGeneratorResponse_File{\n\t\t\tName:    &filePath,\n\t\t\tContent: &content,\n\t\t}\n\t\tresp.File = append(resp.File, renderFile)\n\t}\n\n\t// plugin stop working\n\terr = plugin.Response(resp)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"write response failed: %s\", err.Error())\n\t}\n\n\treturn nil\n}\n\n// fixGoPackage will update go_package to store all the model files in ${model_dir}\nfunc (plugin *Plugin) fixGoPackage(req *pluginpb.CodeGeneratorRequest, pkgMap map[string]string, trimGoPackage string) {\n\tgopkg := plugin.Package\n\tfor _, f := range req.ProtoFile {\n\t\tif strings.HasPrefix(f.GetPackage(), \"google.protobuf\") {\n\t\t\tcontinue\n\t\t}\n\t\tif len(trimGoPackage) != 0 && strings.HasPrefix(f.GetOptions().GetGoPackage(), trimGoPackage) {\n\t\t\t*f.Options.GoPackage = strings.TrimPrefix(*f.Options.GoPackage, trimGoPackage)\n\t\t}\n\n\t\topt := getGoPackage(f, pkgMap)\n\t\tif !strings.Contains(opt, gopkg) {\n\t\t\tif strings.HasPrefix(opt, \"/\") {\n\t\t\t\topt = gopkg + opt\n\t\t\t} else {\n\t\t\t\topt = gopkg + \"/\" + opt\n\t\t\t}\n\t\t}\n\t\timpt := plugin.fixModelPathAndPackage(opt)\n\t\t*f.Options.GoPackage = impt\n\t}\n}\n\n// fixModelPathAndPackage will modify the go_package to adapt the go_package of the hz,\n// for example adding the go module and model dir.\nfunc (plugin *Plugin) fixModelPathAndPackage(pkg string) (impt string) {\n\tif strings.HasPrefix(pkg, plugin.Package) {\n\t\t// NOTE: no idea why we need util.ImportToSanitizedPath\n\t\t// it seems like we only need to convert the last part of a package from /a.b.c -> /a_b_c\n\t\t// \"cloudwego/hertz/biz/model/a/b/c\" -> \"/biz/model/a/b/c\"\n\t\timpt = util.ImportToSanitizedPath(pkg[len(plugin.Package):])\n\t\timpt = filepath.ToSlash(impt) // we always use package path instead of filepath in this func\n\n\t\t// \"/biz/model/a/b/c\" -> \"biz/model/a/b/c\"\n\t\timpt = strings.TrimPrefix(impt, \"/\")\n\t}\n\tif plugin.ModelDir != \"\" && plugin.ModelDir != \".\" {\n\t\tmodelPkg := filepath.ToSlash(plugin.ModelDir)\n\t\tif !strings.HasPrefix(impt, modelPkg) {\n\t\t\timpt = path.Join(modelPkg, impt) // make sure all models under plugin.ModelDir\n\t\t}\n\t}\n\timpt = path.Join(plugin.Package, impt)\n\treturn\n}\n\nfunc (plugin *Plugin) GenerateFiles(pluginPb *protogen.Plugin) error {\n\tidl := pluginPb.Request.FileToGenerate[len(pluginPb.Request.FileToGenerate)-1]\n\tpluginPb.SupportedFeatures = gengo.SupportedFeatures\n\tfor _, f := range pluginPb.Files {\n\t\tif f.Proto.GetName() == idl {\n\t\t\terr := plugin.GenerateFile(pluginPb, f)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\timpt := string(f.GoImportPath)\n\t\t\tif strings.HasPrefix(impt, plugin.Package) {\n\t\t\t\timpt = impt[len(plugin.Package):]\n\t\t\t}\n\t\t\tplugin.IdlClientDir = impt\n\t\t} else if plugin.Recursive {\n\t\t\tif strings.HasPrefix(f.Proto.GetPackage(), \"google.protobuf\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terr := plugin.GenerateFile(pluginPb, f)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (plugin *Plugin) GenerateFile(gen *protogen.Plugin, f *protogen.File) error {\n\timpt := string(f.GoImportPath)\n\tif strings.HasPrefix(impt, plugin.Package) {\n\t\timpt = impt[len(plugin.Package):]\n\t}\n\tf.GeneratedFilenamePrefix = filepath.Join(filepath.FromSlash(impt), util.BaseName(f.Proto.GetName(), \".proto\"))\n\tf.Generate = true\n\t// if use third-party model, no model code is generated within the project\n\tif len(plugin.UseDir) != 0 {\n\t\treturn nil\n\t}\n\tfile, err := generateFile(gen, f, plugin.RmTags)\n\tif err != nil || file == nil {\n\t\treturn fmt.Errorf(\"generate file %s failed: %s\", f.Proto.GetName(), err.Error())\n\t}\n\treturn nil\n}\n\n// generateFile generates the contents of a .pb.go file.\nfunc generateFile(gen *protogen.Plugin, file *protogen.File, rmTags RemoveTags) (*protogen.GeneratedFile, error) {\n\tfilename := file.GeneratedFilenamePrefix + \".pb.go\"\n\tg := gen.NewGeneratedFile(filename, file.GoImportPath)\n\tf := newFileInfo(file)\n\n\tgenStandaloneComments(g, f, int32(FileDescriptorProto_Syntax_field_number))\n\tgenGeneratedHeader(gen, g, f)\n\tgenStandaloneComments(g, f, int32(FileDescriptorProto_Package_field_number))\n\n\tpackageDoc := genPackageKnownComment(f)\n\tg.P(packageDoc, \"package \", f.GoPackageName)\n\tg.P()\n\n\t// Emit a static check that enforces a minimum version of the proto package.\n\tif gengo.GenerateVersionMarkers {\n\t\tg.P(\"const (\")\n\t\tg.P(\"// Verify that this generated code is sufficiently up-to-date.\")\n\t\tg.P(\"_ = \", protoimplPackage.Ident(\"EnforceVersion\"), \"(\", protoimpl.GenVersion, \" - \", protoimplPackage.Ident(\"MinVersion\"), \")\")\n\t\tg.P(\"// Verify that runtime/protoimpl is sufficiently up-to-date.\")\n\t\tg.P(\"_ = \", protoimplPackage.Ident(\"EnforceVersion\"), \"(\", protoimplPackage.Ident(\"MaxVersion\"), \" - \", protoimpl.GenVersion, \")\")\n\t\tg.P(\")\")\n\t\tg.P()\n\t}\n\n\tfor i, imps := 0, f.Desc.Imports(); i < imps.Len(); i++ {\n\t\tgenImport(gen, g, f, imps.Get(i))\n\t}\n\tfor _, enum := range f.allEnums {\n\t\tgenEnum(g, f, enum)\n\t}\n\tvar err error\n\tfor _, message := range f.allMessages {\n\t\terr = genMessage(g, f, message, rmTags)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tgenExtensions(g, f)\n\n\tgenReflectFileDescriptor(gen, g, f)\n\n\treturn g, nil\n}\n\nfunc genMessage(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, rmTags RemoveTags) error {\n\tif m.Desc.IsMapEntry() {\n\t\treturn nil\n\t}\n\n\t// Message type declaration.\n\tg.Annotate(m.GoIdent.GoName, m.Location)\n\tleadingComments := appendDeprecationSuffix(m.Comments.Leading,\n\t\tm.Desc.Options().(*descriptorpb.MessageOptions).GetDeprecated())\n\tg.P(leadingComments,\n\t\t\"type \", m.GoIdent, \" struct {\")\n\terr := genMessageFields(g, f, m, rmTags)\n\tif err != nil {\n\t\treturn err\n\t}\n\tg.P(\"}\")\n\tg.P()\n\n\tgenMessageKnownFunctions(g, f, m)\n\tgenMessageDefaultDecls(g, f, m)\n\tgenMessageMethods(g, f, m)\n\tgenMessageOneofWrapperTypes(g, f, m)\n\treturn nil\n}\n\nfunc genMessageFields(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, rmTags RemoveTags) error {\n\tsf := f.allMessageFieldsByPtr[m]\n\tgenMessageInternalFields(g, f, m, sf)\n\tvar err error\n\tfor _, field := range m.Fields {\n\t\terr = genMessageField(g, f, m, field, sf, rmTags)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc genMessageField(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, field *protogen.Field, sf *structFields, rmTags RemoveTags) error {\n\tif oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {\n\t\t// It would be a bit simpler to iterate over the oneofs below,\n\t\t// but generating the field here keeps the contents of the Go\n\t\t// struct in the same order as the contents of the source\n\t\t// .proto file.\n\t\tif oneof.Fields[0] != field {\n\t\t\treturn nil // only generate for first appearance\n\t\t}\n\n\t\ttags := structTags{\n\t\t\t{\"protobuf_oneof\", string(oneof.Desc.Name())},\n\t\t}\n\t\tif m.isTracked {\n\t\t\ttags = append(tags, gotrackTags...)\n\t\t}\n\n\t\tg.Annotate(m.GoIdent.GoName+\".\"+oneof.GoName, oneof.Location)\n\t\tleadingComments := oneof.Comments.Leading\n\t\tif leadingComments != \"\" {\n\t\t\tleadingComments += \"\\n\"\n\t\t}\n\t\tss := []string{fmt.Sprintf(\" Types that are assignable to %s:\\n\", oneof.GoName)}\n\t\tfor _, field := range oneof.Fields {\n\t\t\tss = append(ss, \"\\t*\"+field.GoIdent.GoName+\"\\n\")\n\t\t}\n\t\tleadingComments += protogen.Comments(strings.Join(ss, \"\"))\n\t\tg.P(leadingComments,\n\t\t\toneof.GoName, \" \", oneofInterfaceName(oneof), tags)\n\t\tsf.append(oneof.GoName)\n\t\treturn nil\n\t}\n\tgoType, pointer := fieldGoType(g, f, field)\n\tif pointer {\n\t\tgoType = \"*\" + goType\n\t}\n\ttags := structTags{\n\t\t{\"protobuf\", fieldProtobufTagValue(field)},\n\t\t//{\"json\", fieldJSONTagValue(field)},\n\t}\n\tif field.Desc.IsMap() {\n\t\tkey := field.Message.Fields[0]\n\t\tval := field.Message.Fields[1]\n\t\ttags = append(tags, structTags{\n\t\t\t{\"protobuf_key\", fieldProtobufTagValue(key)},\n\t\t\t{\"protobuf_val\", fieldProtobufTagValue(val)},\n\t\t}...)\n\t}\n\n\terr := injectTagsToStructTags(field.Desc, &tags, true, rmTags)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif m.isTracked {\n\t\ttags = append(tags, gotrackTags...)\n\t}\n\n\tname := field.GoName\n\tif field.Desc.IsWeak() {\n\t\tname = WeakFieldPrefix_goname + name\n\t}\n\tg.Annotate(m.GoIdent.GoName+\".\"+name, field.Location)\n\tleadingComments := appendDeprecationSuffix(field.Comments.Leading,\n\t\tfield.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())\n\tg.P(leadingComments,\n\t\tname, \" \", goType, tags,\n\t\ttrailingComment(field.Comments.Trailing))\n\tsf.append(field.GoName)\n\treturn nil\n}\n\nfunc (plugin *Plugin) getIdlInfo(ast *descriptorpb.FileDescriptorProto, deps map[string]*descriptorpb.FileDescriptorProto, args *config.Argument) (*generator.HttpPackage, error) {\n\tif ast == nil {\n\t\treturn nil, fmt.Errorf(\"ast is nil\")\n\t}\n\n\tpkg := getGoPackage(ast, map[string]string{})\n\tmain := &model.Model{\n\t\tFilePath:    ast.GetName(),\n\t\tPackage:     pkg,\n\t\tPackageName: util.BaseName(pkg, \"\"),\n\t}\n\tfileInfo := FileInfos{\n\t\tOfficial:  deps,\n\t\tPbReflect: nil,\n\t}\n\trs, err := NewResolver(ast, fileInfo, main, map[string]string{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"new protobuf resolver failed, err:%v\", err)\n\t}\n\terr = rs.LoadAll(ast)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tservices, err := astToService(ast, rs, args.CmdType, plugin.Plugin)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar models model.Models\n\tfor _, s := range services {\n\t\tmodels.MergeArray(s.Models)\n\t}\n\n\treturn &generator.HttpPackage{\n\t\tServices: services,\n\t\tIdlName:  ast.GetName(),\n\t\tPackage:  util.BaseName(pkg, \"\"),\n\t\tModels:   models,\n\t}, nil\n}\n\nfunc (plugin *Plugin) genHttpPackage(ast *descriptorpb.FileDescriptorProto, deps map[string]*descriptorpb.FileDescriptorProto, args *config.Argument) ([]generator.File, error) {\n\toptions := CheckTagOption(args)\n\tidl, err := plugin.getIdlInfo(ast, deps, args)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcustomPackageTemplate := args.CustomizePackage\n\tpkg, err := args.GetGoPackage()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thandlerDir, err := args.GetHandlerDir()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trouterDir, err := args.GetRouterDir()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmodelDir, err := args.GetModelDir()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclientDir, err := args.GetClientDir()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsg := generator.HttpPackageGenerator{\n\t\tConfigPath: customPackageTemplate,\n\t\tHandlerDir: handlerDir,\n\t\tRouterDir:  routerDir,\n\t\tModelDir:   modelDir,\n\t\tUseDir:     args.Use,\n\t\tClientDir:  clientDir,\n\t\tTemplateGenerator: generator.TemplateGenerator{\n\t\t\tOutputDir: args.OutDir,\n\t\t\tExcludes:  args.Excludes,\n\t\t},\n\t\tProjPackage:          pkg,\n\t\tOptions:              options,\n\t\tHandlerByMethod:      args.HandlerByMethod,\n\t\tCmdType:              args.CmdType,\n\t\tIdlClientDir:         plugin.IdlClientDir,\n\t\tForceClientDir:       args.ForceClientDir,\n\t\tBaseDomain:           args.BaseDomain,\n\t\tQueryEnumAsInt:       args.QueryEnumAsInt,\n\t\tSnakeStyleMiddleware: args.SnakeStyleMiddleware,\n\t\tSortRouter:           args.SortRouter,\n\t\tForceUpdateClient:    args.ForceUpdateClient,\n\t}\n\n\tif args.ModelBackend != \"\" {\n\t\tsg.Backend = meta.Backend(args.ModelBackend)\n\t}\n\tgenerator.SetDefaultTemplateConfig()\n\n\terr = sg.Generate(idl)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"generate http package error: %v\", err)\n\t}\n\tfiles, err := sg.GetFormatAndExcludedFiles()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"persist http package error: %v\", err)\n\t}\n\treturn files, nil\n}\n"
  },
  {
    "path": "cmd/hz/protobuf/plugin_stubs.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Copyright (c) 2018 The Go Authors. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *  * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *  * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *  * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protobuf\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\t_ \"unsafe\"\n\n\t\"google.golang.org/protobuf/compiler/protogen\"\n\t\"google.golang.org/protobuf/reflect/protoreflect\"\n)\n\n// Field numbers for google.protobuf.FileDescriptorProto.\nconst (\n\tFileDescriptorProto_Name_field_number             protoreflect.FieldNumber = 1\n\tFileDescriptorProto_Package_field_number          protoreflect.FieldNumber = 2\n\tFileDescriptorProto_Dependency_field_number       protoreflect.FieldNumber = 3\n\tFileDescriptorProto_PublicDependency_field_number protoreflect.FieldNumber = 10\n\tFileDescriptorProto_WeakDependency_field_number   protoreflect.FieldNumber = 11\n\tFileDescriptorProto_MessageType_field_number      protoreflect.FieldNumber = 4\n\tFileDescriptorProto_EnumType_field_number         protoreflect.FieldNumber = 5\n\tFileDescriptorProto_Service_field_number          protoreflect.FieldNumber = 6\n\tFileDescriptorProto_Extension_field_number        protoreflect.FieldNumber = 7\n\tFileDescriptorProto_Options_field_number          protoreflect.FieldNumber = 8\n\tFileDescriptorProto_SourceCodeInfo_field_number   protoreflect.FieldNumber = 9\n\tFileDescriptorProto_Syntax_field_number           protoreflect.FieldNumber = 12\n)\n\nconst WeakFieldPrefix_goname = \"XXX_weak_\"\n\ntype fileInfo struct {\n\t*protogen.File\n\n\tallEnums      []*enumInfo\n\tallMessages   []*messageInfo\n\tallExtensions []*extensionInfo\n\n\tallEnumsByPtr         map[*enumInfo]int    // value is index into allEnums\n\tallMessagesByPtr      map[*messageInfo]int // value is index into allMessages\n\tallMessageFieldsByPtr map[*messageInfo]*structFields\n\n\t// needRawDesc specifies whether the generator should emit logic to provide\n\t// the legacy raw descriptor in GZIP'd form.\n\t// This is updated by enum and message generation logic as necessary,\n\t// and checked at the end of file generation.\n\tneedRawDesc bool\n}\n\ntype enumInfo struct {\n\t*protogen.Enum\n\n\tgenJSONMethod    bool\n\tgenRawDescMethod bool\n}\n\ntype messageInfo struct {\n\t*protogen.Message\n\n\tgenRawDescMethod  bool\n\tgenExtRangeMethod bool\n\n\tisTracked bool\n\thasWeak   bool\n}\n\ntype extensionInfo struct {\n\t*protogen.Extension\n}\n\ntype structFields struct {\n\tcount      int\n\tunexported map[int]string\n}\n\nfunc (sf *structFields) append(name string) {\n\tif r, _ := utf8.DecodeRuneInString(name); !unicode.IsUpper(r) {\n\t\tif sf.unexported == nil {\n\t\t\tsf.unexported = make(map[int]string)\n\t\t}\n\t\tsf.unexported[sf.count] = name\n\t}\n\tsf.count++\n}\n\ntype structTags [][2]string\n\nfunc (tags structTags) String() string {\n\tif len(tags) == 0 {\n\t\treturn \"\"\n\t}\n\tvar ss []string\n\tfor _, tag := range tags {\n\t\t// NOTE: When quoting the value, we need to make sure the backtick\n\t\t// character does not appear. Convert all cases to the escaped hex form.\n\t\tkey := tag[0]\n\t\tval := strings.Replace(strconv.Quote(tag[1]), \"`\", `\\x60`, -1)\n\t\tss = append(ss, fmt.Sprintf(\"%s:%s\", key, val))\n\t}\n\treturn \"`\" + strings.Join(ss, \" \") + \"`\"\n}\n\ntype goImportPath interface {\n\tString() string\n\tIdent(string) protogen.GoIdent\n}\n\ntype trailingComment protogen.Comments\n\nfunc (c trailingComment) String() string {\n\ts := strings.TrimSuffix(protogen.Comments(c).String(), \"\\n\")\n\tif strings.Contains(s, \"\\n\") {\n\t\t// We don't support multi-lined trailing comments as it is unclear\n\t\t// how to best render them in the generated code.\n\t\treturn \"\"\n\t}\n\treturn s\n}\n\n//go:linkname gotrackTags google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.gotrackTags\nvar gotrackTags structTags\n\nvar (\n\tprotoPackage         goImportPath = protogen.GoImportPath(\"google.golang.org/protobuf/proto\")\n\tprotoifacePackage    goImportPath = protogen.GoImportPath(\"google.golang.org/protobuf/runtime/protoiface\")\n\tprotoimplPackage     goImportPath = protogen.GoImportPath(\"google.golang.org/protobuf/runtime/protoimpl\")\n\tprotojsonPackage     goImportPath = protogen.GoImportPath(\"google.golang.org/protobuf/encoding/protojson\")\n\tprotoreflectPackage  goImportPath = protogen.GoImportPath(\"google.golang.org/protobuf/reflect/protoreflect\")\n\tprotoregistryPackage goImportPath = protogen.GoImportPath(\"google.golang.org/protobuf/reflect/protoregistry\")\n)\n\n//go:linkname newFileInfo google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.newFileInfo\nfunc newFileInfo(file *protogen.File) *fileInfo\n\n//go:linkname genPackageKnownComment google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genPackageKnownComment\nfunc genPackageKnownComment(f *fileInfo) protogen.Comments\n\n//go:linkname genStandaloneComments google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genStandaloneComments\nfunc genStandaloneComments(g *protogen.GeneratedFile, f *fileInfo, n int32)\n\n//go:linkname genGeneratedHeader google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genGeneratedHeader\nfunc genGeneratedHeader(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo)\n\n//go:linkname genImport google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genImport\nfunc genImport(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, imp protoreflect.FileImport)\n\n//go:linkname genEnum google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genEnum\nfunc genEnum(g *protogen.GeneratedFile, f *fileInfo, e *enumInfo)\n\n//go:linkname genMessageInternalFields google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genMessageInternalFields\nfunc genMessageInternalFields(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, sf *structFields)\n\n//go:linkname genExtensions google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genExtensions\nfunc genExtensions(g *protogen.GeneratedFile, f *fileInfo)\n\n//go:linkname genReflectFileDescriptor google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genReflectFileDescriptor\nfunc genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo)\n\n//go:linkname appendDeprecationSuffix google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.appendDeprecationSuffix\nfunc appendDeprecationSuffix(prefix protogen.Comments, deprecated bool) protogen.Comments\n\n//go:linkname genMessageDefaultDecls google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genMessageDefaultDecls\nfunc genMessageDefaultDecls(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo)\n\n//go:linkname genMessageKnownFunctions google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genMessageKnownFunctions\nfunc genMessageKnownFunctions(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo)\n\n//go:linkname genMessageMethods google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genMessageMethods\nfunc genMessageMethods(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo)\n\n//go:linkname genMessageOneofWrapperTypes google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genMessageOneofWrapperTypes\nfunc genMessageOneofWrapperTypes(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo)\n\n//go:linkname oneofInterfaceName google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.oneofInterfaceName\nfunc oneofInterfaceName(oneof *protogen.Oneof) string\n\n//go:linkname fieldGoType google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.fieldGoType\nfunc fieldGoType(g *protogen.GeneratedFile, f *fileInfo, field *protogen.Field) (goType string, pointer bool)\n\n//go:linkname fieldProtobufTagValue google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.fieldProtobufTagValue\nfunc fieldProtobufTagValue(field *protogen.Field) string\n\n//go:linkname fieldJSONTagValue google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.fieldJSONTagValue\nfunc fieldJSONTagValue(field *protogen.Field) string\n"
  },
  {
    "path": "cmd/hz/protobuf/plugin_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage protobuf\n\nimport (\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/pluginpb\"\n)\n\nfunc TestPlugin_Handle(t *testing.T) {\n\treturn // where is \"../testdata/request_protoc.out\" ??????\n\n\tin, err := ioutil.ReadFile(\"../testdata/request_protoc.out\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq := &pluginpb.CodeGeneratorRequest{}\n\terr = proto.Unmarshal(in, req)\n\tif err != nil {\n\t\tt.Fatalf(\"unmarshal stdin request error: %v\", err)\n\t}\n\n\t// prepare args\n\tplu := &Plugin{}\n\tplu.setLogger()\n\targs, _ := plu.parseArgs(*req.Parameter)\n\n\tplu.Handle(req, args)\n\tplu.recvWarningLogger()\n}\n\nfunc TestFixModelPathAndPackage(t *testing.T) {\n\tplu := &Plugin{}\n\tplu.Package = \"cloudwego/hertz\"\n\tplu.ModelDir = meta.ModelDir\n\t// default model dir\n\tret1 := [][]string{\n\t\t{\"a/b/c\", \"cloudwego/hertz/biz/model/a/b/c\"},\n\t\t{\"biz/model/a/b/c\", \"cloudwego/hertz/biz/model/a/b/c\"},\n\t\t{\"cloudwego/hertz/a/b/c\", \"cloudwego/hertz/biz/model/a/b/c\"},\n\t\t{\"cloudwego/hertz/biz/model/a/b/c\", \"cloudwego/hertz/biz/model/a/b/c\"},\n\t}\n\tfor _, r := range ret1 {\n\t\ttmp := r[0]\n\t\tif !strings.Contains(tmp, plu.Package) {\n\t\t\tif strings.HasPrefix(tmp, \"/\") {\n\t\t\t\ttmp = plu.Package + tmp\n\t\t\t} else {\n\t\t\t\ttmp = plu.Package + \"/\" + tmp\n\t\t\t}\n\t\t}\n\t\tresult := plu.fixModelPathAndPackage(tmp)\n\t\tif result != r[1] {\n\t\t\tt.Fatalf(\"want go package: %s, but get: %s\", r[1], result)\n\t\t}\n\t}\n\n\tplu.ModelDir = \"model_test\"\n\t// customized model dir\n\tret2 := [][]string{\n\t\t{\"a/b/c\", \"cloudwego/hertz/model_test/a/b/c\"},\n\t\t{\"model_test/a/b/c\", \"cloudwego/hertz/model_test/a/b/c\"},\n\t\t{\"cloudwego/hertz/a/b/c\", \"cloudwego/hertz/model_test/a/b/c\"},\n\t\t{\"cloudwego/hertz/model_test/a/b/c\", \"cloudwego/hertz/model_test/a/b/c\"},\n\t}\n\tfor _, r := range ret2 {\n\t\ttmp := r[0]\n\t\tif !strings.Contains(tmp, plu.Package) {\n\t\t\tif strings.HasPrefix(tmp, \"/\") {\n\t\t\t\ttmp = plu.Package + tmp\n\t\t\t} else {\n\t\t\t\ttmp = plu.Package + \"/\" + tmp\n\t\t\t}\n\t\t}\n\t\tresult := plu.fixModelPathAndPackage(tmp)\n\t\tif result != r[1] {\n\t\t\tt.Fatalf(\"want go package: %s, but get: %s\", r[1], result)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/protobuf/resolver.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage protobuf\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/jhump/protoreflect/desc\"\n\t\"google.golang.org/protobuf/types/descriptorpb\"\n)\n\ntype Symbol struct {\n\tSpace   string\n\tName    string\n\tIsValue bool\n\tType    *model.Type\n\tValue   interface{}\n\tScope   *descriptorpb.FileDescriptorProto\n}\n\ntype NameSpace map[string]*Symbol\n\nvar (\n\tConstTrue = Symbol{\n\t\tIsValue: true,\n\t\tType:    model.TypeBool,\n\t\tValue:   true,\n\t\tScope:   &BaseProto,\n\t}\n\tConstFalse = Symbol{\n\t\tIsValue: true,\n\t\tType:    model.TypeBool,\n\t\tValue:   false,\n\t\tScope:   &BaseProto,\n\t}\n\tConstEmptyString = Symbol{\n\t\tIsValue: true,\n\t\tType:    model.TypeString,\n\t\tValue:   \"\",\n\t\tScope:   &BaseProto,\n\t}\n)\n\ntype PackageReference struct {\n\tIncludeBase string\n\tIncludePath string\n\tModel       *model.Model\n\tAst         *descriptorpb.FileDescriptorProto\n\tReferred    bool\n}\n\nfunc getReferPkgMap(pkgMap map[string]string, incs []*descriptorpb.FileDescriptorProto, mainModel *model.Model) (map[string]*PackageReference, error) {\n\tvar err error\n\tout := make(map[string]*PackageReference, len(pkgMap))\n\tpkgAliasMap := make(map[string]string, len(incs))\n\t// bugfix: add main package to avoid namespace conflict\n\tmainPkg := mainModel.Package\n\tmainPkgName := mainModel.PackageName\n\tmainPkgName, err = util.GetPackageUniqueName(mainPkgName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpkgAliasMap[mainPkg] = mainPkgName\n\tfor _, inc := range incs {\n\t\tpkg := getGoPackage(inc, pkgMap)\n\t\tpath := inc.GetName()\n\t\tbase := util.BaseName(path, \".proto\")\n\t\tfileName := inc.GetName()\n\t\tpkgName := util.BaseName(pkg, \"\")\n\t\tif pn, exist := pkgAliasMap[pkg]; exist {\n\t\t\tpkgName = pn\n\t\t} else {\n\t\t\tpkgName, err = util.GetPackageUniqueName(pkgName)\n\t\t\tpkgAliasMap[pkg] = pkgName\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"get package unique name failed, err: %v\", err)\n\t\t\t}\n\t\t}\n\t\tout[fileName] = &PackageReference{base, path, &model.Model{\n\t\t\tFilePath:    path,\n\t\t\tPackage:     pkg,\n\t\t\tPackageName: pkgName,\n\t\t}, inc, false}\n\t}\n\n\treturn out, nil\n}\n\ntype FileInfos struct {\n\tOfficial  map[string]*descriptorpb.FileDescriptorProto\n\tPbReflect map[string]*desc.FileDescriptor\n}\n\ntype Resolver struct {\n\t// idl symbols\n\trootName string\n\troot     NameSpace\n\tdeps     map[string]NameSpace\n\n\t// exported models\n\tmainPkg PackageReference\n\trefPkgs map[string]*PackageReference\n\n\tfiles FileInfos\n}\n\nfunc updateFiles(fileName string, files FileInfos) (FileInfos, error) {\n\tfile, _ := files.PbReflect[fileName]\n\tif file == nil {\n\t\treturn FileInfos{}, fmt.Errorf(\"%s not found\", fileName)\n\t}\n\tfileDep := file.GetDependencies()\n\n\tmaps := make(map[string]*descriptorpb.FileDescriptorProto, len(fileDep)+1)\n\tsourceInfoMap := make(map[string]*desc.FileDescriptor, len(fileDep)+1)\n\tfor _, dep := range fileDep {\n\t\tast := dep.AsFileDescriptorProto()\n\t\tmaps[dep.GetName()] = ast\n\t\tsourceInfoMap[dep.GetName()] = dep\n\t}\n\tast := file.AsFileDescriptorProto()\n\tmaps[file.GetName()] = ast\n\tsourceInfoMap[file.GetName()] = file\n\n\tnewFileInfo := FileInfos{\n\t\tOfficial:  maps,\n\t\tPbReflect: sourceInfoMap,\n\t}\n\n\treturn newFileInfo, nil\n}\n\nfunc NewResolver(ast *descriptorpb.FileDescriptorProto, files FileInfos, model *model.Model, pkgMap map[string]string) (*Resolver, error) {\n\tfile := ast.GetName()\n\tdeps := ast.GetDependency()\n\tvar err error\n\tif files.PbReflect != nil {\n\t\tfiles, err = updateFiles(file, files)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tincs := make([]*descriptorpb.FileDescriptorProto, 0, len(deps))\n\tfor _, dep := range deps {\n\t\tif v, ok := files.Official[dep]; ok {\n\t\t\tincs = append(incs, v)\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"%s not found\", dep)\n\t\t}\n\t}\n\tpm, err := getReferPkgMap(pkgMap, incs, model)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get package map failed, err: %v\", err)\n\t}\n\treturn &Resolver{\n\t\troot:    make(NameSpace),\n\t\tdeps:    make(map[string]NameSpace),\n\t\trefPkgs: pm,\n\t\tfiles:   files,\n\t\tmainPkg: PackageReference{\n\t\t\tIncludeBase: util.BaseName(file, \".proto\"),\n\t\t\tIncludePath: file,\n\t\t\tModel:       model,\n\t\t\tAst:         ast,\n\t\t\tReferred:    false,\n\t\t},\n\t}, nil\n}\n\nfunc (resolver *Resolver) GetRefModel(includeBase string) (*model.Model, error) {\n\tif includeBase == \"\" {\n\t\treturn resolver.mainPkg.Model, nil\n\t}\n\tref, ok := resolver.refPkgs[includeBase]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%s not found\", includeBase)\n\t}\n\treturn ref.Model, nil\n}\n\nfunc (resolver *Resolver) getBaseType(f *descriptorpb.FieldDescriptorProto, nested []*descriptorpb.DescriptorProto) (*model.Type, error) {\n\tbt := switchBaseType(f.GetType())\n\tif bt != nil {\n\t\treturn checkListType(bt, f.GetLabel()), nil\n\t}\n\n\tnt := getNestedType(f, nested)\n\tif nt != nil {\n\t\tfields := nt.GetField()\n\t\tif IsMapEntry(nt) {\n\t\t\tt := *model.TypeBaseMap\n\t\t\ttk, err := resolver.ResolveType(fields[0], nt.GetNestedType())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\ttv, err := resolver.ResolveType(fields[1], nt.GetNestedType())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tt.Extra = []*model.Type{tk, tv}\n\t\t\treturn &t, nil\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc IsMapEntry(nt *descriptorpb.DescriptorProto) bool {\n\tfields := nt.GetField()\n\treturn len(fields) == 2 && fields[0].GetName() == \"key\" && fields[1].GetName() == \"value\"\n}\n\nfunc checkListType(typ *model.Type, label descriptorpb.FieldDescriptorProto_Label) *model.Type {\n\tif label == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {\n\t\tt := *model.TypeBaseList\n\t\tt.Extra = []*model.Type{typ}\n\t\treturn &t\n\t}\n\treturn typ\n}\n\nfunc getNestedType(f *descriptorpb.FieldDescriptorProto, nested []*descriptorpb.DescriptorProto) *descriptorpb.DescriptorProto {\n\ttName := f.GetTypeName()\n\tentry := util.SplitPackageName(tName, \"\")\n\tfor _, nt := range nested {\n\t\tif nt.GetName() == entry {\n\t\t\treturn nt\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (resolver *Resolver) ResolveType(f *descriptorpb.FieldDescriptorProto, nested []*descriptorpb.DescriptorProto) (*model.Type, error) {\n\tbt, err := resolver.getBaseType(f, nested)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif bt != nil {\n\t\treturn bt, nil\n\t}\n\n\ttName := f.GetTypeName()\n\tsymbol, err := resolver.ResolveIdentifier(tName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdeepType := checkListType(symbol.Type, f.GetLabel())\n\treturn deepType, nil\n}\n\nfunc (resolver *Resolver) ResolveIdentifier(id string) (ret *Symbol, err error) {\n\tret = resolver.Get(id)\n\tif ret == nil {\n\t\treturn nil, fmt.Errorf(\"not found identifier %s\", id)\n\t}\n\n\tvar ref *PackageReference\n\tif _, ok := resolver.deps[ret.Space]; ok {\n\t\tref = resolver.refPkgs[ret.Scope.GetName()]\n\t\tif ref != nil {\n\t\t\tref.Referred = true\n\t\t\tret.Type.Scope = ref.Model\n\t\t}\n\t}\n\t// bugfix: root & dep file has the same package(namespace), the 'ret' will miss the namespace match for root.\n\t// This results in a lack of dependencies in the generated handlers.\n\tif ref == nil && ret.Scope == resolver.mainPkg.Ast {\n\t\tresolver.mainPkg.Referred = true\n\t\tret.Type.Scope = resolver.mainPkg.Model\n\t}\n\treturn\n}\n\nfunc (resolver *Resolver) getFieldType(f *descriptorpb.FieldDescriptorProto, nested []*descriptorpb.DescriptorProto) (*model.Type, error) {\n\tdt, err := resolver.getBaseType(f, nested)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif dt != nil {\n\t\treturn dt, nil\n\t}\n\tsb := resolver.Get(f.GetTypeName())\n\tif sb != nil {\n\t\treturn sb.Type, nil\n\t}\n\treturn nil, fmt.Errorf(\"not found type %s\", f.GetTypeName())\n}\n\nfunc (resolver *Resolver) Get(name string) *Symbol {\n\tif strings.HasPrefix(name, \".\"+resolver.rootName) {\n\t\tid := strings.TrimPrefix(name, \".\"+resolver.rootName+\".\")\n\t\tif v, ok := resolver.root[id]; ok {\n\t\t\treturn v\n\t\t}\n\t}\n\n\t// directly map first\n\tvar space string\n\tif idx := strings.LastIndex(name, \".\"); idx >= 0 && idx < len(name)-1 {\n\t\tspace = strings.TrimLeft(name[:idx], \".\")\n\t}\n\tif ns, ok := resolver.deps[space]; ok {\n\t\tid := strings.TrimPrefix(name, \".\"+space+\".\")\n\t\tif s, ok := ns[id]; ok {\n\t\t\treturn s\n\t\t}\n\t}\n\n\t// iterate check nested type in dependencies\n\tfor s, m := range resolver.deps {\n\t\tif strings.HasPrefix(name, \".\"+s) {\n\t\t\tid := strings.TrimPrefix(name, \".\"+s+\".\")\n\t\t\tif s, ok := m[id]; ok {\n\t\t\t\treturn s\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (resolver *Resolver) ExportReferred(all, needMain bool) (ret []*PackageReference) {\n\tfor _, v := range resolver.refPkgs {\n\t\tif all {\n\t\t\tret = append(ret, v)\n\t\t} else if v.Referred {\n\t\t\tret = append(ret, v)\n\t\t}\n\t\tv.Referred = false\n\t}\n\n\tif needMain && (all || resolver.mainPkg.Referred) {\n\t\tret = append(ret, &resolver.mainPkg)\n\t}\n\tresolver.mainPkg.Referred = false\n\treturn\n}\n\nfunc (resolver *Resolver) LoadAll(ast *descriptorpb.FileDescriptorProto) error {\n\tvar err error\n\tresolver.root, err = resolver.LoadOne(ast)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"load main idl failed: %s\", err)\n\t}\n\tresolver.rootName = ast.GetPackage()\n\n\tincludes := ast.GetDependency()\n\tastMap := make(map[string]NameSpace, len(includes))\n\tfor _, dep := range includes {\n\t\tfile, ok := resolver.files.Official[dep]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"not found included idl %s\", dep)\n\t\t}\n\t\tdepNamespace, err := resolver.LoadOne(file)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"load idl '%s' failed: %s\", dep, err)\n\t\t}\n\t\tns, existed := astMap[file.GetPackage()]\n\t\tif existed {\n\t\t\tdepNamespace = mergeNamespace(ns, depNamespace)\n\t\t}\n\t\tastMap[file.GetPackage()] = depNamespace\n\t}\n\tresolver.deps = astMap\n\treturn nil\n}\n\nfunc mergeNamespace(first, second NameSpace) NameSpace {\n\tfor k, v := range second {\n\t\tif _, existed := first[k]; !existed {\n\t\t\tfirst[k] = v\n\t\t}\n\t}\n\treturn first\n}\n\nfunc LoadBaseIdentifier(ast *descriptorpb.FileDescriptorProto) map[string]*Symbol {\n\tret := make(NameSpace, len(ast.GetEnumType())+len(ast.GetMessageType())+len(ast.GetExtension())+len(ast.GetService()))\n\n\tret[\"true\"] = &ConstTrue\n\tret[\"false\"] = &ConstFalse\n\tret[`\"\"`] = &ConstEmptyString\n\tret[\"bool\"] = &Symbol{\n\t\tType:  model.TypeBool,\n\t\tScope: ast,\n\t}\n\tret[\"uint32\"] = &Symbol{\n\t\tType:  model.TypeUint32,\n\t\tScope: ast,\n\t}\n\tret[\"uint64\"] = &Symbol{\n\t\tType:  model.TypeUint64,\n\t\tScope: ast,\n\t}\n\tret[\"fixed32\"] = &Symbol{\n\t\tType:  model.TypeUint32,\n\t\tScope: ast,\n\t}\n\tret[\"fixed64\"] = &Symbol{\n\t\tType:  model.TypeUint64,\n\t\tScope: ast,\n\t}\n\tret[\"int32\"] = &Symbol{\n\t\tType:  model.TypeInt32,\n\t\tScope: ast,\n\t}\n\tret[\"int64\"] = &Symbol{\n\t\tType:  model.TypeInt64,\n\t\tScope: ast,\n\t}\n\tret[\"sint32\"] = &Symbol{\n\t\tType:  model.TypeInt32,\n\t\tScope: ast,\n\t}\n\tret[\"sint64\"] = &Symbol{\n\t\tType:  model.TypeInt64,\n\t\tScope: ast,\n\t}\n\tret[\"sfixed32\"] = &Symbol{\n\t\tType:  model.TypeInt32,\n\t\tScope: ast,\n\t}\n\tret[\"sfixed64\"] = &Symbol{\n\t\tType:  model.TypeInt64,\n\t\tScope: ast,\n\t}\n\tret[\"double\"] = &Symbol{\n\t\tType:  model.TypeFloat64,\n\t\tScope: ast,\n\t}\n\tret[\"float\"] = &Symbol{\n\t\tType:  model.TypeFloat32,\n\t\tScope: ast,\n\t}\n\tret[\"string\"] = &Symbol{\n\t\tType:  model.TypeString,\n\t\tScope: ast,\n\t}\n\tret[\"bytes\"] = &Symbol{\n\t\tType:  model.TypeBinary,\n\t\tScope: ast,\n\t}\n\treturn ret\n}\n\nfunc (resolver *Resolver) LoadOne(ast *descriptorpb.FileDescriptorProto) (NameSpace, error) {\n\tret := LoadBaseIdentifier(ast)\n\tspace := util.BaseName(ast.GetPackage(), \"\")\n\tprefix := \".\" + space\n\n\tfor _, e := range ast.GetEnumType() {\n\t\tname := strings.TrimLeft(e.GetName(), prefix)\n\t\tret[e.GetName()] = &Symbol{\n\t\t\tName:    name,\n\t\t\tSpace:   space,\n\t\t\tIsValue: false,\n\t\t\tValue:   e,\n\t\t\tScope:   ast,\n\t\t\tType:    model.NewEnumType(name, model.CategoryEnum),\n\t\t}\n\t\tfor _, ee := range e.GetValue() {\n\t\t\tname := strings.TrimLeft(ee.GetName(), prefix)\n\t\t\tret[ee.GetName()] = &Symbol{\n\t\t\t\tName:    name,\n\t\t\t\tSpace:   space,\n\t\t\t\tIsValue: true,\n\t\t\t\tValue:   ee,\n\t\t\t\tScope:   ast,\n\t\t\t\tType:    model.NewCategoryType(model.TypeInt, model.CategoryEnum),\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, mt := range ast.GetMessageType() {\n\t\tname := strings.TrimLeft(mt.GetName(), prefix)\n\t\tret[mt.GetName()] = &Symbol{\n\t\t\tName:    name,\n\t\t\tSpace:   space,\n\t\t\tIsValue: false,\n\t\t\tValue:   mt,\n\t\t\tScope:   ast,\n\t\t\tType:    model.NewStructType(name, model.CategoryStruct),\n\t\t}\n\n\t\tfor _, nt := range mt.GetNestedType() {\n\t\t\tntname := name + \"_\" + nt.GetName()\n\t\t\tret[name+\".\"+nt.GetName()] = &Symbol{\n\t\t\t\tName:    ntname,\n\t\t\t\tSpace:   space,\n\t\t\t\tIsValue: false,\n\t\t\t\tValue:   nt,\n\t\t\t\tScope:   ast,\n\t\t\t\tType:    model.NewStructType(ntname, model.CategoryStruct),\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, s := range ast.GetService() {\n\t\tname := strings.TrimLeft(s.GetName(), prefix)\n\t\tret[s.GetName()] = &Symbol{\n\t\t\tName:    name,\n\t\t\tSpace:   space,\n\t\t\tIsValue: false,\n\t\t\tValue:   s,\n\t\t\tScope:   ast,\n\t\t\tType:    model.NewFuncType(name, model.CategoryService),\n\t\t}\n\t}\n\n\treturn ret, nil\n}\n\nfunc (resolver *Resolver) GetFiles() FileInfos {\n\treturn resolver.files\n}\n"
  },
  {
    "path": "cmd/hz/protobuf/tag_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage protobuf\n\nimport (\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"google.golang.org/protobuf/compiler/protogen\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/pluginpb\"\n)\n\nfunc TestTagGenerate(t *testing.T) {\n\ttype TagStruct struct {\n\t\tAnnotation   string\n\t\tGeneratedTag string\n\t\tActualTag    string\n\t}\n\n\ttagList := []TagStruct{\n\t\t{\n\t\t\tAnnotation:   \"query\",\n\t\t\tGeneratedTag: \"protobuf:\\\"bytes,1,opt,name=QueryTag\\\" json:\\\"QueryTag,omitempty\\\" query:\\\"query\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"raw_body\",\n\t\t\tGeneratedTag: \"protobuf:\\\"bytes,2,opt,name=RawBodyTag\\\" json:\\\"RawBodyTag,omitempty\\\" raw_body:\\\"raw_body\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"path\",\n\t\t\tGeneratedTag: \"protobuf:\\\"bytes,3,opt,name=PathTag\\\" json:\\\"PathTag,omitempty\\\" path:\\\"path\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"form\",\n\t\t\tGeneratedTag: \"protobuf:\\\"bytes,4,opt,name=FormTag\\\" form:\\\"form\\\" json:\\\"FormTag,omitempty\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"cookie\",\n\t\t\tGeneratedTag: \"protobuf:\\\"bytes,5,opt,name=CookieTag\\\" cookie:\\\"cookie\\\" json:\\\"CookieTag,omitempty\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"header\",\n\t\t\tGeneratedTag: \"protobuf:\\\"bytes,6,opt,name=HeaderTag\\\" header:\\\"header\\\" json:\\\"HeaderTag,omitempty\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"body\",\n\t\t\tGeneratedTag: \"bytes,7,opt,name=BodyTag\\\" form:\\\"body\\\" json:\\\"body,omitempty\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"go.tag\",\n\t\t\tGeneratedTag: \"bytes,8,opt,name=GoTag\\\" form:\\\"form\\\" goTag:\\\"tag\\\" header:\\\"header\\\" json:\\\"json\\\" query:\\\"query\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"vd\",\n\t\t\tGeneratedTag: \"bytes,9,opt,name=VdTag\\\" form:\\\"VdTag\\\" json:\\\"VdTag,omitempty\\\" query:\\\"VdTag\\\" vd:\\\"$!='?'\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"non\",\n\t\t\tGeneratedTag: \"bytes,10,opt,name=DefaultTag\\\" form:\\\"DefaultTag\\\" json:\\\"DefaultTag,omitempty\\\" query:\\\"DefaultTag\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"query required\",\n\t\t\tGeneratedTag: \"bytes,11,req,name=ReqQuery\\\" json:\\\"ReqQuery,required\\\" query:\\\"query,required\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"query optional\",\n\t\t\tGeneratedTag: \"bytes,12,opt,name=OptQuery\\\" json:\\\"OptQuery,omitempty\\\" query:\\\"query\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"body required\",\n\t\t\tGeneratedTag: \"protobuf:\\\"bytes,13,req,name=ReqBody\\\" form:\\\"body,required\\\" json:\\\"body,required\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"body optional\",\n\t\t\tGeneratedTag: \"protobuf:\\\"bytes,14,opt,name=OptBody\\\" form:\\\"body\\\" json:\\\"body,omitempty\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"go.tag required\",\n\t\t\tGeneratedTag: \"protobuf:\\\"bytes,15,req,name=ReqGoTag\\\" form:\\\"ReqGoTag,required\\\" json:\\\"json\\\" query:\\\"ReqGoTag,required\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"go.tag optional\",\n\t\t\tGeneratedTag: \"bytes,16,opt,name=OptGoTag\\\" form:\\\"OptGoTag\\\" json:\\\"json\\\" query:\\\"OptGoTag\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"go tag cover query\",\n\t\t\tGeneratedTag: \"bytes,17,req,name=QueryGoTag\\\" json:\\\"QueryGoTag,required\\\" query:\\\"queryTag\\\"\",\n\t\t},\n\t}\n\n\tin, err := ioutil.ReadFile(\"./test_data/protobuf_tag_test.out\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq := &pluginpb.CodeGeneratorRequest{}\n\terr = proto.Unmarshal(in, req)\n\tif err != nil {\n\t\tt.Fatalf(\"unmarshal stdin request error: %v\", err)\n\t}\n\n\topts := protogen.Options{}\n\tgen, err := opts.New(req)\n\n\tfor _, f := range gen.Files {\n\t\tif f.Proto.GetName() == \"test_tag.proto\" {\n\t\t\tfileInfo := newFileInfo(f)\n\t\t\tfor _, message := range fileInfo.allMessages {\n\t\t\t\tfor idx, field := range message.Fields {\n\t\t\t\t\ttags := structTags{\n\t\t\t\t\t\t{\"protobuf\", fieldProtobufTagValue(field)},\n\t\t\t\t\t}\n\t\t\t\t\terr = injectTagsToStructTags(field.Desc, &tags, true, nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t\tvar actualTag string\n\t\t\t\t\tfor i, tag := range tags {\n\t\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\t\tactualTag = tag[0] + \":\" + \"\\\"\" + tag[1] + \"\\\"\"\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tactualTag = actualTag + \" \" + tag[0] + \":\" + \"\\\"\" + tag[1] + \"\\\"\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\ttagList[idx].ActualTag = actualTag\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor i := range tagList {\n\t\tif !strings.Contains(tagList[i].ActualTag, tagList[i].GeneratedTag) {\n\t\t\tt.Fatalf(\"expected tag: '%s', but autual tag: '%s'\", tagList[i].GeneratedTag, tagList[i].ActualTag)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/protobuf/tags.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage protobuf\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/config\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/protobuf/api\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/reflect/protoreflect\"\n\t\"google.golang.org/protobuf/runtime/protoimpl\"\n\t\"google.golang.org/protobuf/types/descriptorpb\"\n)\n\nvar (\n\tjsonSnakeName             = false\n\tunsetOmitempty            = false\n\tprotobufCamelJSONTagStyle = false\n)\n\nfunc CheckTagOption(args *config.Argument) (ret []generator.Option) {\n\tif args == nil {\n\t\treturn\n\t}\n\tif args.SnakeName {\n\t\tjsonSnakeName = true\n\t}\n\tif args.UnsetOmitempty {\n\t\tunsetOmitempty = true\n\t}\n\tif args.JSONEnumStr {\n\t\tret = append(ret, generator.OptionMarshalEnumToText)\n\t}\n\tif args.ProtobufCamelJSONTag {\n\t\tprotobufCamelJSONTagStyle = true\n\t}\n\treturn ret\n}\n\nfunc checkSnakeName(name string) string {\n\tif jsonSnakeName {\n\t\tname = util.ToSnakeCase(name)\n\t}\n\treturn name\n}\n\nvar (\n\tHttpMethodOptions = map[*protoimpl.ExtensionInfo]string{\n\t\tapi.E_Get:     \"GET\",\n\t\tapi.E_Post:    \"POST\",\n\t\tapi.E_Put:     \"PUT\",\n\t\tapi.E_Patch:   \"PATCH\",\n\t\tapi.E_Delete:  \"DELETE\",\n\t\tapi.E_Options: \"OPTIONS\",\n\t\tapi.E_Head:    \"HEAD\",\n\t\tapi.E_Any:     \"Any\",\n\t}\n\n\tBindingTags = map[*protoimpl.ExtensionInfo]string{\n\t\tapi.E_Path:   \"path\",\n\t\tapi.E_Query:  \"query\",\n\t\tapi.E_Header: \"header\",\n\t\tapi.E_Cookie: \"cookie\",\n\t\tapi.E_Body:   \"json\",\n\t\t// Do not change the relative order of \"api.E_Form\" and \"api.E_Body\", so that \"api.E_Form\" can overwrite the form tag generated by \"api.E_Body\"\n\t\tapi.E_Form:           \"form\",\n\t\tapi.E_FormCompatible: \"form\",\n\t\tapi.E_RawBody:        \"raw_body\",\n\t}\n\n\tValidatorTags = map[*protoimpl.ExtensionInfo]string{api.E_Vd: \"vd\"}\n\n\tSerializerOptions = map[*protoimpl.ExtensionInfo]string{api.E_Serializer: \"serializer\"}\n)\n\ntype httpOption struct {\n\tmethod string\n\tpath   string\n}\n\ntype httpOptions []httpOption\n\nfunc (s httpOptions) Len() int {\n\treturn len(s)\n}\n\nfunc (s httpOptions) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc (s httpOptions) Less(i, j int) bool {\n\treturn s[i].method < s[j].method\n}\n\nfunc getAllOptions(extensions map[*protoimpl.ExtensionInfo]string, opts ...protoreflect.ProtoMessage) map[string]interface{} {\n\tout := map[string]interface{}{}\n\tfor _, opt := range opts {\n\t\tfor e, t := range extensions {\n\t\t\tif proto.HasExtension(opt, e) {\n\t\t\t\tv := proto.GetExtension(opt, e)\n\t\t\t\tout[t] = v\n\t\t\t}\n\t\t}\n\t}\n\treturn out\n}\n\nfunc checkFirstOptions(extensions map[*protoimpl.ExtensionInfo]string, opts ...protoreflect.ProtoMessage) (string, interface{}) {\n\tfor _, opt := range opts {\n\t\tfor e, t := range extensions {\n\t\t\tif proto.HasExtension(opt, e) {\n\t\t\t\tv := proto.GetExtension(opt, e)\n\t\t\t\treturn t, v\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", nil\n}\n\nfunc checkFirstOption(ext *protoimpl.ExtensionInfo, opts ...protoreflect.ProtoMessage) interface{} {\n\tfor _, opt := range opts {\n\t\tif proto.HasExtension(opt, ext) {\n\t\t\tv := proto.GetExtension(opt, ext)\n\t\t\treturn v\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc checkOption(ext *protoimpl.ExtensionInfo, opts ...protoreflect.ProtoMessage) (ret []interface{}) {\n\tfor _, opt := range opts {\n\t\tif proto.HasExtension(opt, ext) {\n\t\t\tv := proto.GetExtension(opt, ext)\n\t\t\tret = append(ret, v)\n\t\t}\n\t}\n\treturn\n}\n\nfunc tag(k string, v interface{}) model.Tag {\n\treturn model.Tag{\n\t\tKey:   k,\n\t\tValue: fmt.Sprintf(\"%v\", v),\n\t}\n}\n\n//-----------------------------------For Compiler---------------------------\n\nfunc defaultBindingTags(f *descriptorpb.FieldDescriptorProto) []model.Tag {\n\topts := f.GetOptions()\n\tout := make([]model.Tag, 3)\n\tif v := checkFirstOption(api.E_Body, opts); v != nil {\n\t\tval := getJsonValue(f, v.(string))\n\t\tout[0] = tag(\"json\", val)\n\t} else {\n\t\tout[0] = jsonTag(f)\n\t}\n\tif v := checkFirstOption(api.E_Query, opts); v != nil {\n\t\tval := checkRequire(f, v.(string))\n\t\tout[1] = tag(BindingTags[api.E_Query], val)\n\t} else {\n\t\tval := checkRequire(f, checkSnakeName(f.GetName()))\n\t\tout[1] = tag(BindingTags[api.E_Query], val)\n\t}\n\tif v := checkFirstOption(api.E_Form, opts); v != nil {\n\t\tval := checkRequire(f, v.(string))\n\t\tout[2] = tag(BindingTags[api.E_Form], val)\n\t} else {\n\t\tval := checkRequire(f, checkSnakeName(f.GetName()))\n\t\tout[2] = tag(BindingTags[api.E_Form], val)\n\t}\n\treturn out\n}\n\nfunc jsonTag(f *descriptorpb.FieldDescriptorProto) (ret model.Tag) {\n\tret.Key = \"json\"\n\tret.Value = checkSnakeName(f.GetJsonName())\n\tif v := checkFirstOption(api.E_JsConv, f.GetOptions()); v != nil {\n\t\tret.Value += \",string\"\n\t} else if v := checkFirstOption(api.E_JsConvCompatible, f.GetOptions()); v != nil {\n\t\tret.Value += \",string\"\n\t}\n\tif !unsetOmitempty && f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL {\n\t\tret.Value += \",omitempty\"\n\t} else if f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {\n\t\tret.Value += \",required\"\n\t}\n\treturn\n}\n\nfunc injectTagsToModel(f *descriptorpb.FieldDescriptorProto, gf *model.Field, needDefault bool) error {\n\tas := f.GetOptions()\n\n\ttags := gf.Tags\n\tif tags == nil {\n\t\ttags = make([]model.Tag, 0, 4)\n\t}\n\n\t// binding tags\n\tif needDefault {\n\t\ttags = append(tags, defaultBindingTags(f)...)\n\t}\n\tfor k, v := range BindingTags {\n\t\tif vv := checkFirstOption(k, as); vv != nil {\n\t\t\ttags.Remove(v)\n\t\t\tif v == \"json\" {\n\t\t\t\tvv = getJsonValue(f, vv.(string))\n\t\t\t} else {\n\t\t\t\tvv = checkRequire(f, vv.(string))\n\t\t\t}\n\t\t\ttags = append(tags, tag(v, vv))\n\t\t}\n\t}\n\n\t// validator tags\n\tfor k, v := range ValidatorTags {\n\t\tfor _, vv := range checkOption(k, as) {\n\t\t\ttags = append(tags, tag(v, vv))\n\t\t}\n\t}\n\n\t// go.tags\n\tfor _, v := range checkOption(api.E_GoTag, as) {\n\t\tgts := util.SplitGoTags(v.(string))\n\t\tfor _, gt := range gts {\n\t\t\tsp := strings.SplitN(gt, \":\", 2)\n\t\t\tif len(sp) != 2 {\n\t\t\t\treturn fmt.Errorf(\"invalid go tag: %s\", v)\n\t\t\t}\n\t\t\tvv, err := strconv.Unquote(sp[1])\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid go.tag value: %s, err: %v\", sp[1], err.Error())\n\t\t\t}\n\t\t\tkey := sp[0]\n\t\t\ttags.Remove(key)\n\t\t\ttags = append(tags, model.Tag{\n\t\t\t\tKey:   key,\n\t\t\t\tValue: vv,\n\t\t\t})\n\t\t}\n\t}\n\n\tsort.Sort(tags)\n\tgf.Tags = tags\n\treturn nil\n}\n\nfunc getJsonValue(f *descriptorpb.FieldDescriptorProto, val string) string {\n\tif v := checkFirstOption(api.E_JsConv, f.GetOptions()); v != nil {\n\t\tval += \",string\"\n\t} else if v := checkFirstOption(api.E_JsConvCompatible, f.GetOptions()); v != nil {\n\t\tval += \",string\"\n\t}\n\tif !unsetOmitempty && f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL {\n\t\tval += \",omitempty\"\n\t} else if f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {\n\t\tval += \",required\"\n\t}\n\n\treturn val\n}\n\nfunc checkRequire(f *descriptorpb.FieldDescriptorProto, val string) string {\n\tif f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {\n\t\tval += \",required\"\n\t}\n\n\treturn val\n}\n\n//-------------------------For plugin---------------------------------\n\nfunc m2s(mt model.Tag) (ret [2]string) {\n\tret[0] = mt.Key\n\tret[1] = mt.Value\n\treturn ret\n}\n\nfunc reflectJsonTag(f protoreflect.FieldDescriptor) (ret model.Tag) {\n\tret.Key = \"json\"\n\tif protobufCamelJSONTagStyle {\n\t\tret.Value = checkSnakeName(f.JSONName())\n\t} else {\n\t\tret.Value = checkSnakeName(string(f.Name()))\n\t}\n\tif v := checkFirstOption(api.E_Body, f.Options()); v != nil {\n\t\tret.Value += \",string\"\n\t}\n\tif descriptorpb.FieldDescriptorProto_Label(f.Cardinality()) == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {\n\t\tret.Value += \",required\"\n\t} else if !unsetOmitempty {\n\t\tret.Value += \",omitempty\"\n\t}\n\treturn\n}\n\nfunc defaultBindingStructTags(f protoreflect.FieldDescriptor) []model.Tag {\n\topts := f.Options()\n\tout := make([]model.Tag, 3)\n\tbindingTags := []*protoimpl.ExtensionInfo{\n\t\tapi.E_Path,\n\t\tapi.E_Query,\n\t\tapi.E_Form,\n\t\tapi.E_FormCompatible,\n\t\tapi.E_Header,\n\t\tapi.E_Cookie,\n\t\tapi.E_Body,\n\t\tapi.E_RawBody,\n\t}\n\t// If the user provides an annotation, return json tag directly\n\tfor _, tag := range bindingTags {\n\t\tif vv := checkFirstOption(tag, opts); vv != nil {\n\t\t\tout[0] = reflectJsonTag(f)\n\t\t\treturn out[:1]\n\t\t}\n\t}\n\n\tif v := checkFirstOption(api.E_Body, opts); v != nil {\n\t\tval := getStructJsonValue(f, v.(string))\n\t\tout[0] = tag(\"json\", val)\n\t} else {\n\t\tt := reflectJsonTag(f)\n\t\tt.IsDefault = true\n\t\tout[0] = t\n\t}\n\tif v := checkFirstOption(api.E_Query, opts); v != nil {\n\t\tval := checkStructRequire(f, v.(string))\n\t\tout[1] = tag(BindingTags[api.E_Query], val)\n\t} else {\n\t\tval := checkStructRequire(f, checkSnakeName(string(f.Name())))\n\t\tt := tag(BindingTags[api.E_Query], val)\n\t\tt.IsDefault = true\n\t\tout[1] = t\n\t}\n\tif v := checkFirstOption(api.E_Form, opts); v != nil {\n\t\tval := checkStructRequire(f, v.(string))\n\t\tout[2] = tag(BindingTags[api.E_Form], val)\n\t} else {\n\t\tif v := checkFirstOption(api.E_FormCompatible, opts); v != nil { // compatible form_compatible\n\t\t\tval := checkStructRequire(f, v.(string))\n\t\t\tt := tag(BindingTags[api.E_Form], val)\n\t\t\tt.IsDefault = true\n\t\t\tout[2] = t\n\t\t} else {\n\t\t\tval := checkStructRequire(f, checkSnakeName(string(f.Name())))\n\t\t\tt := tag(BindingTags[api.E_Form], val)\n\t\t\tt.IsDefault = true\n\t\t\tout[2] = t\n\t\t}\n\t}\n\treturn out\n}\n\nfunc injectTagsToStructTags(f protoreflect.FieldDescriptor, out *structTags, needDefault bool, rmTags RemoveTags) error {\n\tas := f.Options()\n\t// binding tags\n\ttags := model.Tags(make([]model.Tag, 0, 6))\n\n\tif needDefault {\n\t\ttags = append(tags, defaultBindingStructTags(f)...)\n\t}\n\tfor k, v := range BindingTags {\n\t\tif vv := checkFirstOption(k, as); vv != nil {\n\t\t\ttags.Remove(v)\n\t\t\t// body annotation will generate \"json\" & \"form\" tag for protobuf\n\t\t\tif v == \"json\" {\n\t\t\t\tformVal := vv\n\t\t\t\tvv = getStructJsonValue(f, vv.(string))\n\t\t\t\tformVal = checkStructRequire(f, formVal.(string))\n\t\t\t\ttags = append(tags, tag(\"form\", formVal))\n\t\t\t} else {\n\t\t\t\tvv = checkStructRequire(f, vv.(string))\n\t\t\t}\n\t\t\ttags = append(tags, tag(v, vv))\n\t\t}\n\t}\n\n\t// validator tags\n\tfor k, v := range ValidatorTags {\n\t\tif vv := checkFirstOption(k, as); vv != nil {\n\t\t\ttags = append(tags, tag(v, vv))\n\t\t}\n\t}\n\n\tif v := checkFirstOption(api.E_GoTag, as); v != nil {\n\t\tgts := util.SplitGoTags(v.(string))\n\t\tfor _, gt := range gts {\n\t\t\tsp := strings.SplitN(gt, \":\", 2)\n\t\t\tif len(sp) != 2 {\n\t\t\t\treturn fmt.Errorf(\"invalid go tag: %s\", v)\n\t\t\t}\n\t\t\tvv, err := strconv.Unquote(sp[1])\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid go.tag value: %s, err: %v\", sp[1], err.Error())\n\t\t\t}\n\t\t\tkey := sp[0]\n\t\t\ttags.Remove(key)\n\t\t\ttags = append(tags, model.Tag{\n\t\t\t\tKey:   key,\n\t\t\t\tValue: vv,\n\t\t\t})\n\t\t}\n\t}\n\tdisableTag := false\n\tif vv := checkFirstOption(api.E_None, as); vv != nil {\n\t\tif strings.EqualFold(vv.(string), \"true\") {\n\t\t\tdisableTag = true\n\t\t}\n\t} else if vv := checkFirstOption(api.E_NoneCompatible, as); vv != nil {\n\t\tif strings.EqualFold(vv.(string), \"true\") {\n\t\t\tdisableTag = true\n\t\t}\n\t}\n\tfor _, t := range tags {\n\t\tif t.IsDefault && rmTags.Exist(t.Key) {\n\t\t\ttags.Remove(t.Key)\n\t\t}\n\t}\n\tsort.Sort(tags)\n\tfor _, t := range tags {\n\t\tif disableTag {\n\t\t\t*out = append(*out, [2]string{t.Key, \"-\"})\n\t\t} else {\n\t\t\t*out = append(*out, m2s(t))\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getStructJsonValue(f protoreflect.FieldDescriptor, val string) string {\n\tif v := checkFirstOption(api.E_JsConv, f.Options()); v != nil {\n\t\tval += \",string\"\n\t} else if v := checkFirstOption(api.E_JsConvCompatible, f.Options()); v != nil {\n\t\tval += \",string\"\n\t}\n\n\tif descriptorpb.FieldDescriptorProto_Label(f.Cardinality()) == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {\n\t\tval += \",required\"\n\t} else if !unsetOmitempty {\n\t\tval += \",omitempty\"\n\t}\n\n\treturn val\n}\n\nfunc checkStructRequire(f protoreflect.FieldDescriptor, val string) string {\n\tif descriptorpb.FieldDescriptorProto_Label(f.Cardinality()) == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {\n\t\tval += \",required\"\n\t}\n\n\treturn val\n}\n"
  },
  {
    "path": "cmd/hz/protobuf/test_data/test_tag.proto",
    "content": "syntax = \"proto2\";\n\npackage test;\n\noption go_package = \"cloudwego.hertz.hz\";\n\nimport \"api.proto\";\n\nmessage MultiTagReq {\n  // basic feature\n  optional string QueryTag = 1 [(api.query)=\"query\"];\n  optional string RawBodyTag = 2 [(api.raw_body)=\"raw_body\"];\n  optional string PathTag = 3 [(api.path)=\"path\"];\n  optional string FormTag = 4 [(api.form)=\"form\"];\n  optional string CookieTag = 5 [(api.cookie)=\"cookie\"];\n  optional string HeaderTag = 6 [(api.header)=\"header\"];\n  optional string BodyTag = 7 [(api.body)=\"body\"];\n  optional string GoTag = 8 [(api.go_tag)=\"json:\\\"json\\\" query:\\\"query\\\" form:\\\"form\\\" header:\\\"header\\\" goTag:\\\"tag\\\"\"];\n  optional string VdTag = 9 [(api.vd)=\"$!='?'\"];\n  optional string DefaultTag = 10;\n\n  // optional / required\n  required string ReqQuery = 11 [(api.query)=\"query\"];\n  optional string OptQuery = 12 [(api.query)=\"query\"];\n  required string ReqBody = 13 [(api.body)=\"body\"];\n  optional string OptBody = 14 [(api.body)=\"body\"];\n  required string ReqGoTag = 15 [(api.go_tag)=\"json:\\\"json\\\"\"];\n  optional string OptGoTag = 16 [(api.go_tag)=\"json:\\\"json\\\"\"];\n\n  // gotag cover feature\n  required string QueryGoTag = 17 [(api.query)=\"query\", (api.go_tag)=\"query:\\\"queryTag\\\"\"];\n}\n"
  },
  {
    "path": "cmd/hz/test_hz_unix.sh",
    "content": "#! /usr/bin/env bash\n\n\nset -e\n\n# const value define\nmoduleName=\"github.com/cloudwego/hertz/cmd/hz/test\"\ncurDir=`pwd`\nthriftIDL=$curDir\"/testdata/thrift/psm.thrift\"\nprotobuf2IDL=$curDir\"/testdata/protobuf2/psm/psm.proto\"\nproto2Search=$curDir\"/testdata/protobuf2\"\nprotobuf3IDL=$curDir\"/testdata/protobuf3/psm/psm.proto\"\nproto3Search=$curDir\"/testdata/protobuf3\"\nprotoSearch=\"/usr/local/include\"\n\ncompile_hz() {\n  go build -o hz\n}\n\n\nPATH_BIN=$PWD/bin\nmkdir -p $PATH_BIN\nexport PATH=$PATH_BIN:$PATH\n\ninstall_dependent_tools() {\n  # install thriftgo\n  go install github.com/cloudwego/thriftgo@latest\n\n  # install protoc\n  wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip\n  unzip -d protoc-3.19.4-linux-x86_64 protoc-3.19.4-linux-x86_64.zip\n  cp protoc-3.19.4-linux-x86_64/bin/protoc $PATH_BIN\n  cp -r protoc-3.19.4-linux-x86_64/include/google $PATH_BIN\n}\n\ngo_tidy_build() {\n  # make sure we get the latest version for testing\n  go get github.com/cloudwego/hertz@develop\n  go mod tidy && go build .\n}\n\ntest_thrift() {\n  mkdir -p test\n  cd test\n  ../hz new --idl=$thriftIDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router\n  go_tidy_build\n  ../hz update --idl=$thriftIDL\n  ../hz model --idl=$thriftIDL --model_dir=hertz_model\n  ../hz client --idl=$thriftIDL --client_dir=hertz_client\n  cd ..\n  rm -rf test\n}\n\ntest_protobuf2() {\n  # test protobuf2\n  mkdir -p test\n  cd test\n  ../hz new -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router\n  go_tidy_build\n  ../hz update -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL\n  ../hz model -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --model_dir=hertz_model\n  ../hz client -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --client_dir=hertz_client\n  cd ..\n  rm -rf test\n}\n\ntest_protobuf3() {\n  # test protobuf2\n  mkdir -p test\n  cd test\n  ../hz new -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router\n  go_tidy_build\n  ../hz update -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL\n  ../hz model -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --model_dir=hertz_model\n  ../hz client -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --client_dir=hertz_client\n  cd ..\n  rm -rf test\n}\n\nmain() {\n  compile_hz\n  install_dependent_tools\n  echo \"test thrift......\"\n  test_thrift\n  echo \"test protobuf2......\"\n  test_protobuf2\n  echo \"test protobuf3......\"\n  test_protobuf3\n  echo \"hz execute success\"\n}\nmain\n"
  },
  {
    "path": "cmd/hz/test_hz_windows.sh",
    "content": "#! /usr/bin/env bash\n\nset -e\n\n# const value define\nmoduleName=\"github.com/cloudwego/hertz/cmd/hz/test\"\ncurDir=`pwd`\nthriftIDL=$curDir\"/testdata/thrift/psm.thrift\"\nprotobuf2IDL=$curDir\"/testdata/protobuf2/psm/psm.proto\"\nproto2Search=$curDir\"/testdata/protobuf2\"\nprotobuf3IDL=$curDir\"/testdata/protobuf3/psm/psm.proto\"\nproto3Search=$curDir\"/testdata/protobuf3\"\nprotoSearch=$curDir\"/testdata/include\"\n\ncompile_hz() {\n  go install .\n}\n\ninstall_dependent_tools() {\n # install thriftgo\n go install github.com/cloudwego/thriftgo@latest\n}\n\ngo_tidy_build() {\n  # make sure we get the latest version for testing\n  go get github.com/cloudwego/hertz@develop\n  go mod tidy && go build .\n}\n\ntest_thrift() {\n  # test thrift\n  mkdir -p test\n  cd test\n  hz new --idl=$thriftIDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router\n  go_tidy_build\n  hz update --idl=$thriftIDL\n  hz model --idl=$thriftIDL --model_dir=hertz_model\n  hz client --idl=$thriftIDL --client_dir=hertz_client\n  cd ..\n  rm -rf test\n}\n\ntest_protobuf2() {\n  # test protobuf2\n  mkdir -p test\n  cd test\n  hz new -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router\n  go_tidy_build\n  hz update -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL\n  hz model -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --model_dir=hertz_model\n  hz client -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --client_dir=hertz_client\n  cd ..\n  rm -rf test\n}\n\ntest_protobuf3() {\n  # test protobuf2\n  mkdir -p test\n  cd test\n  hz new -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router\n  go_tidy_build\n  hz update -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL\n  hz model -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --model_dir=hertz_model\n  hz client -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --client_dir=hertz_client\n  cd ..\n  rm -rf test\n}\n\nmain() {\n  compile_hz\n  install_dependent_tools\n# todo: add thrift test when thriftgo fixed windows\n  echo \"test thrift......\"\n  test_thrift\n  echo \"test protobuf2......\"\n  test_protobuf2\n  echo \"test protobuf3......\"\n  test_protobuf3\n  echo \"hz execute success\"\n}\nmain\n"
  },
  {
    "path": "cmd/hz/testdata/protobuf2/api.proto",
    "content": "syntax = \"proto2\";\n\npackage api;\n\nimport \"google/protobuf/descriptor.proto\";\n\noption go_package = \"github.com/cloudwego/hertz/cmd/hz/test/hertz_model/api\";\n\nextend google.protobuf.FieldOptions {\n  optional string raw_body = 50101;\n  optional string query = 50102;\n  optional string header = 50103;\n  optional string cookie = 50104;\n  optional string body = 50105;\n  optional string path = 50106;\n  optional string vd = 50107;\n  optional string form = 50108;\n  optional string js_conv = 50109;\n  optional string file_name = 50110;\n  optional string none = 50111;\n\n  // 50131~50160 used to extend field option by hz\n  optional string form_compatible = 50131;\n  optional string js_conv_compatible = 50132;\n  optional string file_name_compatible = 50133;\n  optional string none_compatible = 50134;\n\n  optional string go_tag = 51001;\n}\n\nextend google.protobuf.MethodOptions {\n  optional string get = 50201;\n  optional string post = 50202;\n  optional string put = 50203;\n  optional string delete = 50204;\n  optional string patch = 50205;\n  optional string options = 50206;\n  optional string head = 50207;\n  optional string any = 50208;\n  optional string gen_path = 50301; // The path specified by the user when the client code is generated, with a higher priority than api_version\n  optional string api_version = 50302; // Specify the value of the :version variable in path when the client code is generated\n  optional string tag = 50303; // rpc tag, can be multiple, separated by commas\n  optional string name = 50304; // Name of rpc\n  optional string api_level = 50305; // Interface Level\n  optional string serializer = 50306; // Serialization method\n  optional string param = 50307; // Whether client requests take public parameters\n  optional string baseurl = 50308; // Baseurl used in ttnet routing\n  optional string handler_path = 50309; // handler_path specifies the path to generate the method\n\n  // 50331~50360 used to extend method option by hz\n  optional string handler_path_compatible = 50331; // handler_path specifies the path to generate the method\n}\n\nextend google.protobuf.EnumValueOptions {\n  optional int32 http_code = 50401;\n\n  // 50431~50460 used to extend enum option by hz\n}\n\nextend google.protobuf.ServiceOptions {\n  optional string base_domain = 50402;\n\n  // 50731~50760 used to extend service option by hz\n  optional string base_domain_compatible = 50731;\n}"
  },
  {
    "path": "cmd/hz/testdata/protobuf2/other/other.proto",
    "content": "syntax = \"proto2\";\n\npackage hertz.other;\n\nimport \"other/other_base.proto\";\n\noption go_package = \"github.com/cloudwego/hertz/cmd/hz/test/hertz_model/other\";\n\nmessage OtherType {\n  optional string IsBaseString = 1;\n  optional OtherBaseType IsOtherBaseType = 2;\n}"
  },
  {
    "path": "cmd/hz/testdata/protobuf2/other/other_base.proto",
    "content": "syntax = \"proto2\";\n\npackage hertz.other;\n\noption go_package = \"github.com/cloudwego/hertz/cmd/hz/test/hertz_model/other\";\n\nmessage OtherBaseType {\n  optional string IsOtherBaseTypeString = 1;\n}"
  },
  {
    "path": "cmd/hz/testdata/protobuf2/psm/base.proto",
    "content": "syntax = \"proto2\";\n\npackage base;\n\noption go_package = \"github.com/cloudwego/hertz/cmd/hz/test/hertz_model/psm\";\n\nmessage Base {\n  optional string IsBaseString = 1;\n}\n\nenum BaseEnumType {\n  TWEET = 0;\n  RETWEET = 1;\n}"
  },
  {
    "path": "cmd/hz/testdata/protobuf2/psm/psm.proto",
    "content": "syntax = \"proto2\";\n\npackage psm;\n\noption go_package = \"github.com/cloudwego/hertz/cmd/hz/test/hertz_model/psm\";\n\nimport \"api.proto\";\nimport \"base.proto\";\nimport \"other/other.proto\";\n\nenum EnumType {\n  TWEET = 0;\n  RETWEET = 1;\n}\nmessage UnusedMessageType {\n  optional string IsUnusedMessageType = 1;\n}\n\nmessage BaseType {\n  optional base.Base IsBaseType = 1;\n}\n\nmessage MultiTypeReq {\n  // basic type (leading comments)\n  optional bool IsBoolOpt = 1;\n  required bool IsBoolReq = 2;\n  optional int32 IsInt32Opt = 3;\n  required int32 IsInt32Req = 4;\n  optional int64 IsInt64Opt = 5;\n  optional uint32 IsUInt32Opt = 6;\n  optional uint64 IsUInt64Opt = 7;\n  optional sint32 IsSInt32Opt = 8;\n  optional sint64 IsSInt64Opt = 9;\n  optional fixed32 IsFix32Opt = 10;\n  optional fixed64 IsFix64Opt = 11;\n  optional sfixed32 IsSFix32Opt = 12;\n  optional sfixed64 IsSFix64Opt = 13;\n  optional double IsDoubleOpt = 14;\n  required double IsDoubleReq = 15;\n  optional float IsFloatOpt = 16;\n  optional string IsStringOpt = 17;\n  required string IsStringReq = 18;\n  optional bytes IsBytesOpt = 19;\n  optional bytes IsBytesReq = 20;\n\n  // slice\n  repeated string IsRepeatedString = 21;\n  repeated BaseType IsRepeatedBaseType = 22;\n\n  // map\n  map<string, string> IsStringMap = 23;\n  map<string, BaseType> IsBaseTypeMap = 24;\n\n  // oneof\n  // multiple comments\n  oneof TestOneof {\n    string IsOneofString = 25;\n    BaseType IsOneofBaseType = 26;\n    int32 IsOneofInt = 100;\n    bool IsOneofBool = 101;\n    double IsOneoDouble = 102;\n    bytes IsOneofBytes = 103;\n  }\n\n  // this is oneof2, one field in oneof\n  oneof TestOneof2 {\n    string IsOneof2String = 104;\n  }\n\n  message NestedMessageType {\n    optional string IsNestedString = 1;\n    optional BaseType IsNestedBaseType = 2;\n    repeated BaseType IsNestedRepeatedBaseType = 3;\n    // nested oneof\n    oneof NestedMsgOneof {\n      string IsNestedMsgOneofString = 4;\n      EnumType IsNestedMsgOneofEnumType = 5;\n    }\n  }\n  // nested message\n  optional NestedMessageType IsNestedType = 27;\n\n  // other dependency\n  optional base.Base IsCurrentPackageBase = 28;\n  optional hertz.other.OtherType IsOtherType = 29;\n\n  // enum\n  optional EnumType IsEnumTypeOpt = 30;\n  required EnumType IsEnumTypeReq = 31;\n  repeated EnumType IsEnumTypeList = 32;\n  optional base.BaseEnumType IsBaseEnumType = 33;\n}\n\nmessage MultiTagReq {\n  optional string QueryTag = 1 [(api.query) = \"query\", (api.none) = \"true\"];\n  optional string RawBodyTag = 2 [(api.raw_body) = \"raw_body\"];\n  optional string CookieTag = 3 [(api.cookie) = \"cookie\"];\n  optional string BodyTag = 4 [(api.body) = \"body\"];\n  optional string PathTag = 5 [(api.path) = \"path\"];\n  optional string VdTag = 6 [(api.vd) = \"$!='?'\"];\n  optional string FormTag = 7 [(api.form) = \"form\"];\n  optional string DefaultTag = 8 [(api.go_tag) = \"FFF:\\\"fff\\\" json:\\\"json\\\"\"];\n}\n\nmessage CompatibleAnnoReq {\n  optional string FormCompatibleTag = 1 [(api.form_compatible) = \"form\"];\n  optional string FilenameCompatibleTag = 2 [(api.file_name_compatible) = \"file_name\"];\n  optional string NoneCompatibleTag = 3 [(api.none_compatible) = \"true\"];\n  optional string JsConvCompatibleTag = 4 [(api.js_conv_compatible) = \"true\"];\n}\n\nmessage Resp {\n  optional string Resp = 1;\n}\n\nmessage MultiNameStyleMessage {\n  optional string hertz = 1;\n  optional string Hertz = 2;\n  optional string hertz_demo = 3;\n  optional string hertz_demo_idl = 4;\n  optional string hertz_Idl = 5;\n  optional string hertzDemo = 6;\n  optional string h = 7;\n  optional string H = 8;\n  optional string hertz_ = 9;\n}\n\nservice Hertz {\n  rpc Method1(MultiTypeReq) returns(Resp) {\n    option (api.get) = \"/company/department/group/user:id/name\";\n  }\n  rpc Method2(MultiTypeReq) returns(Resp) {\n    option (api.post) = \"/company/department/group/user:id/sex\";\n  }\n  rpc Method3(MultiTypeReq) returns(Resp) {\n    option (api.put) = \"/company/department/group/user:id/number\";\n  }\n  rpc Method4(MultiTypeReq) returns(Resp) {\n    option (api.delete) = \"/company/department/group/user:id/age\";\n  }\n\n\n  rpc Method5(MultiTagReq) returns(Resp) {\n    option (api.options) = \"/school/class/student/name\";\n  }\n  rpc Method6(MultiTagReq) returns(Resp) {\n    option (api.head) = \"/school/class/student/number\";\n  }\n  rpc Method7(MultiTagReq) returns(Resp) {\n    option (api.patch) = \"/school/class/student/sex\";\n  }\n  rpc Method8(MultiTagReq) returns(Resp) {\n    option (api.any) = \"/school/class/student/grade/*subjects\";\n  }\n}"
  },
  {
    "path": "cmd/hz/testdata/protobuf3/api.proto",
    "content": "syntax = \"proto2\";\n\npackage api;\n\nimport \"google/protobuf/descriptor.proto\";\n\noption go_package = \"github.com/cloudwego/hertz/cmd/hz/test/hertz_model/api\";\n\nextend google.protobuf.FieldOptions {\n  optional string raw_body = 50101;\n  optional string query = 50102;\n  optional string header = 50103;\n  optional string cookie = 50104;\n  optional string body = 50105;\n  optional string path = 50106;\n  optional string vd = 50107;\n  optional string form = 50108;\n  optional string js_conv = 50109;\n  optional string file_name = 50110;\n  optional string none = 50111;\n\n  // 50131~50160 used to extend field option by hz\n  optional string form_compatible = 50131;\n  optional string js_conv_compatible = 50132;\n  optional string file_name_compatible = 50133;\n  optional string none_compatible = 50134;\n\n  optional string go_tag = 51001;\n}\n\nextend google.protobuf.MethodOptions {\n  optional string get = 50201;\n  optional string post = 50202;\n  optional string put = 50203;\n  optional string delete = 50204;\n  optional string patch = 50205;\n  optional string options = 50206;\n  optional string head = 50207;\n  optional string any = 50208;\n  optional string gen_path = 50301; // The path specified by the user when the client code is generated, with a higher priority than api_version\n  optional string api_version = 50302; // Specify the value of the :version variable in path when the client code is generated\n  optional string tag = 50303; // rpc tag, can be multiple, separated by commas\n  optional string name = 50304; // Name of rpc\n  optional string api_level = 50305; // Interface Level\n  optional string serializer = 50306; // Serialization method\n  optional string param = 50307; // Whether client requests take public parameters\n  optional string baseurl = 50308; // Baseurl used in ttnet routing\n  optional string handler_path = 50309; // handler_path specifies the path to generate the method\n\n  // 50331~50360 used to extend method option by hz\n  optional string handler_path_compatible = 50331; // handler_path specifies the path to generate the method\n}\n\nextend google.protobuf.EnumValueOptions {\n  optional int32 http_code = 50401;\n\n  // 50431~50460 used to extend enum option by hz\n}\n\nextend google.protobuf.ServiceOptions {\n  optional string base_domain = 50402;\n\n  // 50731~50760 used to extend service option by hz\n  optional string base_domain_compatible = 50731;\n}"
  },
  {
    "path": "cmd/hz/testdata/protobuf3/other/other.proto",
    "content": "syntax = \"proto2\";\n\npackage hertz.other;\n\nimport \"other/other_base.proto\";\n\noption go_package = \"github.com/cloudwego/hertz/cmd/hz/test/hertz_model/other\";\n\nmessage OtherType {\n  optional string IsBaseString = 1;\n  optional OtherBaseType IsOtherBaseType = 2;\n}"
  },
  {
    "path": "cmd/hz/testdata/protobuf3/other/other_base.proto",
    "content": "syntax = \"proto2\";\n\npackage hertz.other;\n\noption go_package = \"github.com/cloudwego/hertz/cmd/hz/test/hertz_model/other\";\n\nmessage OtherBaseType {\n  optional string IsOtherBaseTypeString = 1;\n}"
  },
  {
    "path": "cmd/hz/testdata/protobuf3/psm/base.proto",
    "content": "syntax = \"proto2\";\n\npackage base;\n\noption go_package = \"github.com/cloudwego/hertz/cmd/hz/test/hertz_model/psm\";\n\nmessage Base {\n  optional string IsBaseString = 1;\n}"
  },
  {
    "path": "cmd/hz/testdata/protobuf3/psm/psm.proto",
    "content": "syntax = \"proto3\";\n\npackage psm;\n\noption go_package = \"github.com/cloudwego/hertz/cmd/hz/test/hertz_model/psm\";\n\nimport \"api.proto\";\nimport \"base.proto\";\nimport \"other/other.proto\";\n\nenum EnumType {\n    TWEET = 0;\n    RETWEET = 1;\n}\nmessage UnusedMessageType {\n  optional string IsUnusedMessageType = 1;\n}\n\nmessage BaseType {\n  optional base.Base IsBaseType = 1;\n}\n\nmessage MultiTypeReq {\n  // basic type (leading comments)\n  optional bool IsBoolOpt = 1;\n  optional int32 IsInt32Opt = 3;\n  int64 IsInt64Default = 5;\n  optional uint32 IsUInt32Opt = 6;\n  uint64 IsUInt64Default = 7;\n  optional sint32 IsSInt32Opt = 8;\n  sint64 IsSInt64Default = 9;\n  optional fixed32 IsFix32Opt = 10;\n  optional fixed64 IsFix64Opt = 11;\n  optional sfixed32 IsSFix32Opt = 12;\n  optional sfixed64 IsSFix64Opt = 13;\n  optional double IsDoubleOpt = 14;\n  optional float IsFloatOpt = 16;\n  optional string IsStringOpt = 17;\n  optional bytes IsBytesOpt = 19;\n  bytes IsBytesDefault = 20;\n\n  // slice\n  repeated string IsRepeatedString = 21;\n  repeated BaseType IsRepeatedBaseType = 22;\n\n  // map\n  map<string, string> IsStringMap = 23;\n  map<string, BaseType> IsBaseTypeMap = 24;\n\n  // oneof\n  oneof TestOneof {\n    string IsOneofString = 25;\n    BaseType IsOneofBaseTypeString = 26;\n  }\n\n  oneof TestOneof2 {\n    string IsOneofString2 = 100;\n  }\n\n  // nested message\n  message NestedMessageType {\n    oneof NestedOneof {\n      string YYY = 4;\n      string GGG = 5;\n    }\n    optional string IsNestedString = 1;\n    optional BaseType IsNestedBaseType = 2;\n    repeated BaseType IsNestedRepeatedBaseType = 3;\n  }\n  optional NestedMessageType IsNestedType = 27;\n\n  // other dependency\n  optional base.Base IsCurrentPackageBase = 28;\n  optional hertz.other.OtherType IsOtherType = 29;\n\n  // enum\n  optional EnumType IsEnumTypeOpt = 30;\n  EnumType IsEnumTypeDefault = 31;\n}\n\nmessage MultiTagReq {\n  optional string QueryTag = 1 [(api.query) = \"query\", (api.none) = \"true\"];\n  optional string RawBodyTag = 2 [(api.raw_body)=\"raw_body\"];\n  optional string CookieTag = 3 [(api.cookie)=\"cookie\"];\n  optional string BodyTag = 4 [(api.body)=\"body\"];\n  optional string PathTag = 5 [(api.path)=\"path\"];\n  optional string VdTag = 6 [(api.vd)=\"$!='?'\"];\n  optional string DefaultTag = 7;\n  oneof TestOneof {\n    string IsOneofString = 25;\n    BaseType IsOneofBaseTypeString = 26;\n  }\n}\n\nmessage CompatibleAnnoReq {\n  optional string FormCompatibleTag = 1 [(api.form_compatible) = \"form\"];\n  optional string FilenameCompatibleTag = 2 [(api.file_name_compatible) = \"file_name\"];\n  optional string NoneCompatibleTag = 3 [(api.none_compatible) = \"true\"];\n  optional string JsConvCompatibleTag = 4 [(api.js_conv_compatible) = \"true\"];\n}\n\nmessage Resp {\n  optional string Resp = 1;\n}\n\nservice Hertz {\n  rpc Method1(MultiTypeReq) returns(Resp) {\n    option (api.get)=\"/company/department/group/user:id/name\";\n  }\n  rpc Method2(MultiTypeReq) returns(Resp) {\n    option (api.post)=\"/company/department/group/user:id/sex\";\n  }\n  rpc Method3(MultiTypeReq) returns(Resp) {\n    option (api.put)=\"/company/department/group/user:id/number\";\n  }\n  rpc Method4(MultiTypeReq) returns(Resp) {\n    option (api.delete)=\"/company/department/group/user:id/age\";\n  }\n\n\n  rpc Method5(MultiTagReq) returns(Resp) {\n    option (api.options)=\"/school/class/student/name\";\n  }\n  rpc Method6(MultiTagReq) returns(Resp) {\n    option (api.head)=\"/school/class/student/number\";\n  }\n  rpc Method7(MultiTagReq) returns(Resp) {\n    option (api.patch)=\"/school/class/student/sex\";\n  }\n  rpc Method8(MultiTagReq) returns(Resp) {\n    option (api.any)=\"/school/class/student/grade/*subjects\";\n  }\n}"
  },
  {
    "path": "cmd/hz/testdata/thrift/common.thrift",
    "content": "namespace go toutiao.middleware.hertz\n\nstruct CommonType {\n    1: required string IsCommonString;\n    2: optional string TTT;\n    3: required bool HHH;\n    4: required Base GGG;\n}\n\nstruct Base {\n    1: optional string AAA;\n    2: optional i32 BBB;\n}"
  },
  {
    "path": "cmd/hz/testdata/thrift/data/basic_data.thrift",
    "content": "namespace go toutiao.middleware.hertz_data\n\nstruct BasicDataType {\n    1: optional string IsBasicDataString;\n}"
  },
  {
    "path": "cmd/hz/testdata/thrift/data/data.thrift",
    "content": "include \"basic_data.thrift\"\n\nnamespace go toutiao.middleware.hertz_data\n\nstruct DataType {\n    1: optional basic_data.BasicDataType IsDataString;\n}"
  },
  {
    "path": "cmd/hz/testdata/thrift/psm.thrift",
    "content": "include \"common.thrift\"\ninclude \"data/data.thrift\"\n\nnamespace go toutiao.middleware.hertz\n\nconst string STRING_CONST = \"hertz\";\n\nenum EnumType {\n    TWEET,\n    RETWEET = 2,\n}\n\ntypedef i32 MyInteger\n\nstruct BaseType {\n    1: string GoTag = \"test\" (go.tag=\"json:\\\"go\\\" goTag:\\\"tag\\\"\");\n    2: optional string IsBaseString = \"test\";\n    3: optional common.CommonType IsDepCommonType = {\"IsCommonString\":\"test\", \"TTT\":\"test\", \"HHH\":true, \"GGG\": {\"AAA\":\"test\",\"BBB\":32}};\n    4: optional EnumType IsBaseTypeEnum = 1;\n}\n\ntypedef common.CommonType FFF\n\ntypedef BaseType MyBaseType\n\nstruct MultiTypeReq {\n    // basic type (leading comments)\n    1: optional bool IsBoolOpt = true; // trailing comments\n    2: required bool IsBoolReq;\n    3: optional byte IsByteOpt = 8;\n    4: required byte IsByteReq;\n    //5: optional i8 IsI8Opt; // unsupported i8, suggest byte\n    //6: required i8 IsI8Req = 5; // default\n    7: optional i16 IsI16Opt = 16;\n    8: optional i32 IsI32Opt;\n    9: optional i64 IsI64Opt;\n    10: optional double IsDoubleOpt;\n    11: required double IsDoubleReq;\n    12: optional string IsStringOpt = \"test\";\n    13: required string IsStringReq;\n\n    14: optional list<string> IsList;\n    22: required list<string> IsListReq;\n    15: optional set<string> IsSet;\n    16: optional map<string, string> IsMap;\n    21: optional map<string, BaseType> IsStructMap;\n\n    // struct type\n    17: optional BaseType IsBaseType; // use struct name\n    18: optional MyBaseType IsMyBaseType; // use typedef for struct\n    19: optional common.CommonType IsCommonType = {\"IsCommonString\": \"fffff\"};\n    20: optional data.DataType IsDataType; // multi-dependent struct\n}\n\ntypedef data.DataType IsMyDataType\n\nstruct MultiTagReq {\n    1: string QueryTag (api.query=\"query\");\n    2: string RawBodyTag (api.raw_body=\"raw_body\");\n    3: string PathTag (api.path=\"path\");\n    4: string FormTag (api.form=\"form\");\n    5: string CookieTag (api.cookie=\"cookie\");\n    6: string HeaderTag (api.header=\"header\");\n    7: string ProtobufTag (api.protobuf=\"protobuf\");\n    8: string BodyTag (api.body=\"body\");\n    9: string GoTag (go.tag=\"json:\\\"go\\\" goTag:\\\"tag\\\"\");\n    10: string VdTag (api.vd=\"$!='?'\");\n    11: string DefaultTag;\n}\n\nstruct Resp {\n    1: string Resp = \"this is Resp\";\n}\n\nstruct MultiNameStyleReq {\n  1: optional string hertz;\n  2: optional string Hertz;\n  3: optional string hertz_demo;\n  4: optional string hertz_demo_idl;\n  5: optional string hertz_Idl;\n  6: optional string hertzDemo;\n  7: optional string h;\n  8: optional string H;\n  9: optional string hertz_;\n}\n\nstruct MultiDefaultReq {\n  1: optional bool IsBoolOpt = true;\n  2: required bool IsBoolReq = false;\n  3: optional i32 IsI32Opt = 32;\n  4: required i32 IsI32Req = 32;\n  5: optional string IsStringOpt = \"test\";\n  6: required string IsStringReq = \"test\";\n\n  14: optional list<string> IsListOpt = [\"test\", \"ttt\", \"sdsds\"];\n  22: required list<string> IsListReq = [\"test\", \"ttt\", \"sdsds\"];\n  15: optional set<string> IsSet = [\"test\", \"ttt\", \"sdsds\"];\n  16: optional map<string, string> IsMapOpt = {\"test\": \"ttt\", \"ttt\": \"lll\"};\n  17: required map<string, string> IsMapReq = {\"test\": \"ttt\", \"ttt\": \"lll\"};\n  21: optional map<string, BaseType> IsStructMapOpt = {\"test\": {\"GoTag\":\"fff\", \"IsBaseTypeEnum\":1, \"IsBaseString\":\"ddd\", \"IsDepCommonType\": {\"IsCommonString\":\"fffffff\", \"TTT\":\"ttt\", \"HHH\":true, \"GGG\": {\"AAA\":\"test\",\"BBB\":32}}}};\n  25: required map<string, BaseType> IsStructMapReq = {\"test\": {\"GoTag\":\"fff\", \"IsBaseTypeEnum\":1, \"IsBaseString\":\"ddd\", \"IsDepCommonType\": {\"IsCommonString\":\"fffffff\", \"TTT\":\"ttt\", \"HHH\":true, \"GGG\": {\"AAA\":\"test\",\"BBB\":32}}}};\n\n  23: optional common.CommonType IsDepCommonTypeOpt = {\"IsCommonString\":\"fffffff\", \"TTT\":\"ttt\", \"HHH\":true, \"GGG\": {\"AAA\":\"test\",\"BBB\":32}};\n  24: required common.CommonType IsDepCommonTypeReq = {\"IsCommonString\":\"fffffff\", \"TTT\":\"ttt\", \"HHH\":true, \"GGG\": {\"AAA\":\"test\",\"BBB\":32}};\n}\n\ntypedef map<string, string> IsTypedefContainer\n\nservice Hertz {\n    Resp Method1(1: MultiTypeReq request) (api.get=\"/company/department/group/user:id/name\", api.handler_path=\"v1\");\n    Resp Method2(1: MultiTagReq request) (api.post=\"/company/department/group/user:id/sex\", api.handler_path=\"v1\");\n    Resp Method3(1: BaseType request) (api.put=\"/company/department/group/user:id/number\", api.handler_path=\"v1\");\n    Resp Method4(1: data.DataType request) (api.delete=\"/company/department/group/user:id/age\", api.handler_path=\"v1\");\n\n    Resp Method5(1: MultiTypeReq request) (api.options=\"/school/class/student/name\", api.handler_path=\"v2\");\n    Resp Method6(1: MultiTagReq request) (api.head=\"/school/class/student/number\", api.handler_path=\"v2\");\n    Resp Method7(1: MultiTagReq request) (api.patch=\"/school/class/student/sex\", api.handler_path=\"v2\");\n    Resp Method8(1: BaseType request) (api.any=\"/school/class/student/grade/*subjects\", api.handler_path=\"v2\");\n\n    Resp Method9(1: IsTypedefContainer request) (api.get=\"/typedef/container\", api.handler_path=\"v2\");\n    Resp Method10(1:  map<string, string> request) (api.get=\"/container\", api.handler_path=\"v2\");\n}"
  },
  {
    "path": "cmd/hz/thrift/ast.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage thrift\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/config\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n\t\"github.com/cloudwego/thriftgo/generator/golang\"\n\t\"github.com/cloudwego/thriftgo/generator/golang/styles\"\n\t\"github.com/cloudwego/thriftgo/parser\"\n\t\"github.com/cloudwego/thriftgo/semantic\"\n)\n\n/*---------------------------Import-----------------------------*/\n\nfunc getGoPackage(ast *parser.Thrift, pkgMap map[string]string) string {\n\tfilePackage := ast.GetFilename()\n\tif opt, ok := pkgMap[filePackage]; ok {\n\t\treturn opt\n\t} else {\n\t\tgoPackage := ast.GetNamespaceOrReferenceName(\"go\")\n\t\tif goPackage != \"\" {\n\t\t\treturn util.SplitPackage(goPackage, \"\")\n\t\t}\n\t\t// If namespace is not declared, the file name (without the extension) is used as the package name\n\t\treturn util.SplitPackage(filePackage, \".thrift\")\n\t}\n}\n\n/*---------------------------Service-----------------------------*/\n\nfunc astToService(ast *parser.Thrift, resolver *Resolver, args *config.Argument) ([]*generator.Service, error) {\n\tss := ast.GetServices()\n\tout := make([]*generator.Service, 0, len(ss))\n\tvar models model.Models\n\textendServices := getExtendServices(ast)\n\tfor _, s := range ss {\n\t\t// if the service is extended, it is not processed\n\t\tif extendServices.exist(s.Name) && args.EnableExtends {\n\t\t\tlogs.Debugf(\"%s is extended, so skip it\\n\", s.Name)\n\t\t\tcontinue\n\t\t}\n\n\t\tresolver.ExportReferred(true, false)\n\t\tservice := &generator.Service{\n\t\t\tName: s.GetName(),\n\t\t}\n\t\tservice.BaseDomain = \"\"\n\t\tdomainAnno := getAnnotation(s.Annotations, ApiBaseDomain)\n\t\tif len(domainAnno) == 1 {\n\t\t\tif args.CmdType == meta.CmdClient {\n\t\t\t\tservice.BaseDomain = domainAnno[0]\n\t\t\t}\n\t\t}\n\t\tservice.ServiceGroup = \"\"\n\t\tgroupAnno := getAnnotation(s.Annotations, ApiServiceGroup)\n\t\tif len(groupAnno) == 1 {\n\t\t\tif args.CmdType != meta.CmdClient {\n\t\t\t\tservice.ServiceGroup = groupAnno[0]\n\t\t\t}\n\t\t}\n\t\tservice.ServiceGenDir = \"\"\n\t\tserviceGenDirAnno := getAnnotation(s.Annotations, ApiServiceGenDir)\n\t\tif len(serviceGenDirAnno) == 1 {\n\t\t\tif args.CmdType != meta.CmdClient {\n\t\t\t\tservice.ServiceGenDir = serviceGenDirAnno[0]\n\t\t\t}\n\t\t}\n\t\tms := s.GetFunctions()\n\t\tif len(s.Extends) != 0 && args.EnableExtends {\n\t\t\t// all the services that are extended to the current service\n\t\t\textendsFuncs, err := getAllExtendFunction(s, ast, resolver, args)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"parser extend function failed, err=%v\", err)\n\t\t\t}\n\t\t\tms = append(ms, extendsFuncs...)\n\t\t}\n\t\tmethods := make([]*generator.HttpMethod, 0, len(ms))\n\t\tclientMethods := make([]*generator.ClientMethod, 0, len(ms))\n\t\tservicePathAnno := getAnnotation(s.Annotations, ApiServicePath)\n\t\tservicePath := \"\"\n\t\tif len(servicePathAnno) > 0 {\n\t\t\tservicePath = servicePathAnno[0]\n\t\t}\n\t\tfor _, m := range ms {\n\t\t\trs := getAnnotations(m.Annotations, HttpMethodAnnotations)\n\t\t\tif len(rs) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\thttpAnnos := httpAnnotations{}\n\t\t\tfor k, v := range rs {\n\t\t\t\thttpAnnos = append(httpAnnos, httpAnnotation{\n\t\t\t\t\tmethod: k,\n\t\t\t\t\tpath:   v,\n\t\t\t\t})\n\t\t\t}\n\t\t\t// turn the map into a slice and sort it to make sure getting the results in the same order every time\n\t\t\tsort.Sort(httpAnnos)\n\t\t\thandlerOutDir := servicePath\n\t\t\tgenPaths := getAnnotation(m.Annotations, ApiGenPath)\n\t\t\tif len(genPaths) == 1 {\n\t\t\t\thandlerOutDir = genPaths[0]\n\t\t\t} else if len(genPaths) > 0 {\n\t\t\t\treturn nil, fmt.Errorf(\"too many 'api.handler_path' for %s\", m.Name)\n\t\t\t}\n\n\t\t\thmethod, path := httpAnnos[0].method, httpAnnos[0].path\n\t\t\tif len(path) == 0 || path[0] == \"\" {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid api.%s  for %s.%s: %s\", hmethod, s.Name, m.Name, path)\n\t\t\t}\n\n\t\t\tvar reqName, reqRawName, reqPackage string\n\t\t\tif len(m.Arguments) >= 1 {\n\t\t\t\tif len(m.Arguments) > 1 {\n\t\t\t\t\tlogs.Warnf(\"function '%s' has more than one argument, but only the first can be used in hertz now\", m.GetName())\n\t\t\t\t}\n\t\t\t\tvar err error\n\t\t\t\treqName, err = resolver.ResolveTypeName(m.Arguments[0].GetType())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif strings.Contains(reqName, \".\") && !m.Arguments[0].GetType().Category.IsContainerType() {\n\t\t\t\t\t// If reqName contains \".\" , then it must be of the form \"pkg.name\".\n\t\t\t\t\t// so reqRawName='name', reqPackage='pkg'\n\t\t\t\t\tnames := strings.Split(reqName, \".\")\n\t\t\t\t\tif len(names) != 2 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"request name: %s is wrong\", reqName)\n\t\t\t\t\t}\n\t\t\t\t\treqRawName = names[1]\n\t\t\t\t\treqPackage = names[0]\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar respName, respRawName, respPackage string\n\t\t\tif !m.Oneway {\n\t\t\t\tvar err error\n\t\t\t\trespName, err = resolver.ResolveTypeName(m.GetFunctionType())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif strings.Contains(respName, \".\") && !m.GetFunctionType().Category.IsContainerType() {\n\t\t\t\t\tnames := strings.Split(respName, \".\")\n\t\t\t\t\tif len(names) != 2 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"response name: %s is wrong\", respName)\n\t\t\t\t\t}\n\t\t\t\t\t// If respName contains \".\" , then it must be of the form \"pkg.name\".\n\t\t\t\t\t// so respRawName='name', respPackage='pkg'\n\t\t\t\t\trespRawName = names[1]\n\t\t\t\t\trespPackage = names[0]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsr, _ := util.GetFirstKV(getAnnotations(m.Annotations, SerializerTags))\n\t\t\tmethod := &generator.HttpMethod{\n\t\t\t\tName:               util.CamelString(m.GetName()),\n\t\t\t\tHTTPMethod:         hmethod,\n\t\t\t\tRequestTypeName:    reqName,\n\t\t\t\tRequestTypeRawName: reqRawName,\n\t\t\t\tRequestTypePackage: reqPackage,\n\t\t\t\tReturnTypeName:     respName,\n\t\t\t\tReturnTypeRawName:  respRawName,\n\t\t\t\tReturnTypePackage:  respPackage,\n\t\t\t\tPath:               path[0],\n\t\t\t\tSerializer:         sr,\n\t\t\t\tOutputDir:          handlerOutDir,\n\t\t\t\tGenHandler:         true,\n\t\t\t\t// Annotations:     m.Annotations,\n\t\t\t}\n\t\t\trefs := resolver.ExportReferred(false, true)\n\t\t\tmethod.Models = make(map[string]*model.Model, len(refs))\n\t\t\tfor _, ref := range refs {\n\t\t\t\tif v, ok := method.Models[ref.Model.PackageName]; ok && (v.Package != ref.Model.Package) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Package name: %s  redeclared in %s and %s \", ref.Model.PackageName, v.Package, ref.Model.Package)\n\t\t\t\t}\n\t\t\t\tmethod.Models[ref.Model.PackageName] = ref.Model\n\t\t\t}\n\t\t\tmodels.MergeMap(method.Models)\n\t\t\tmethods = append(methods, method)\n\t\t\tfor idx, anno := range httpAnnos {\n\t\t\t\tfor i := 0; i < len(anno.path); i++ {\n\t\t\t\t\tif idx == 0 && i == 0 { // idx==0 && i==0 has been added above\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tnewMethod, err := newHTTPMethod(s, m, method, i, anno)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tmethods = append(methods, newMethod)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif args.CmdType == meta.CmdClient {\n\t\t\t\tclientMethod := &generator.ClientMethod{}\n\t\t\t\tclientMethod.HttpMethod = method\n\t\t\t\trt, err := resolver.ResolveIdentifier(m.Arguments[0].GetType().GetName())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\terr = parseAnnotationToClient(clientMethod, m.Arguments[0].GetType(), rt, args.EnableClientOptional)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tclientMethods = append(clientMethods, clientMethod)\n\t\t\t}\n\t\t}\n\n\t\tservice.ClientMethods = clientMethods\n\t\tservice.Methods = methods\n\t\tservice.Models = models\n\t\tout = append(out, service)\n\t}\n\treturn out, nil\n}\n\nfunc newHTTPMethod(s *parser.Service, m *parser.Function, method *generator.HttpMethod, i int, anno httpAnnotation) (*generator.HttpMethod, error) {\n\tnewMethod := *method\n\thmethod, path := anno.method, anno.path\n\tif path[i] == \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid api.%s for %s.%s: %s\", hmethod, s.Name, m.Name, path[i])\n\t}\n\tnewMethod.HTTPMethod = hmethod\n\tnewMethod.Path = path[i]\n\tnewMethod.GenHandler = false\n\treturn &newMethod, nil\n}\n\nfunc parseAnnotationToClient(clientMethod *generator.ClientMethod, p *parser.Type, symbol ResolvedSymbol, enableOptional bool) error {\n\tif p == nil {\n\t\treturn fmt.Errorf(\"get type failed for parse annotatoon to client\")\n\t}\n\ttypeName := p.GetName()\n\tif strings.Contains(typeName, \".\") {\n\t\tret := strings.Split(typeName, \".\")\n\t\ttypeName = ret[len(ret)-1]\n\t}\n\tscope, err := golang.BuildScope(thriftgoUtil, symbol.Scope)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"can not build scope for %s\", p.Name)\n\t}\n\tthriftgoUtil.SetRootScope(scope)\n\tst := scope.StructLike(typeName)\n\tif st == nil {\n\t\tlogs.Infof(\"the type '%s' for method '%s' is base type, so skip parse client info\\n\")\n\t\treturn nil\n\t}\n\tvar (\n\t\thasBodyAnnotation bool\n\t\thasFormAnnotation bool\n\t)\n\tfor _, field := range st.Fields() {\n\t\thasAnnotation := false\n\t\tisStringFieldType := false\n\t\tisOptional := false\n\t\tif field.GetType().String() == \"string\" {\n\t\t\tisStringFieldType = true\n\t\t}\n\t\tif field.GetRequiredness() == parser.FieldType_Optional {\n\t\t\tisOptional = true\n\t\t}\n\t\tif anno := getAnnotation(field.Annotations, AnnotationQuery); len(anno) > 0 {\n\t\t\thasAnnotation = true\n\t\t\tquery := checkSnakeName(anno[0])\n\t\t\tif isOptional && enableOptional {\n\t\t\t\tclientMethod.QueryParamsCode += fmt.Sprintf(\"%q: func() interface{} {\\n\\t\\t\\t\\tif req.IsSet%s() {\\n\\t\\t\\t\\t\\treturn req.Get%s()\\n\\t\\t\\t\\t} else {\\n\\t\\t\\t\\t\\treturn nil\\n\\t\\t\\t\\t}}(),\\n\", query, field.GoName().String(), field.GoName().String())\n\t\t\t} else {\n\t\t\t\tclientMethod.QueryParamsCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", query, field.GoName().String())\n\t\t\t}\n\t\t}\n\n\t\tif anno := getAnnotation(field.Annotations, AnnotationPath); len(anno) > 0 {\n\t\t\thasAnnotation = true\n\t\t\tpath := anno[0]\n\t\t\tif isStringFieldType {\n\t\t\t\tclientMethod.PathParamsCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", path, field.GoName().String())\n\t\t\t} else {\n\t\t\t\tclientMethod.PathParamsCode += fmt.Sprintf(\"%q: fmt.Sprint(req.Get%s()),\\n\", path, field.GoName().String())\n\t\t\t}\n\t\t}\n\n\t\tif anno := getAnnotation(field.Annotations, AnnotationHeader); len(anno) > 0 {\n\t\t\thasAnnotation = true\n\t\t\theader := anno[0]\n\t\t\tif isStringFieldType {\n\t\t\t\tclientMethod.HeaderParamsCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", header, field.GoName().String())\n\t\t\t} else {\n\t\t\t\tclientMethod.HeaderParamsCode += fmt.Sprintf(\"%q: fmt.Sprint(req.Get%s()),\\n\", header, field.GoName().String())\n\t\t\t}\n\t\t}\n\n\t\tif anno := getAnnotation(field.Annotations, AnnotationForm); len(anno) > 0 {\n\t\t\thasAnnotation = true\n\t\t\tform := checkSnakeName(anno[0])\n\t\t\thasFormAnnotation = true\n\t\t\tif isStringFieldType {\n\t\t\t\tclientMethod.FormValueCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", form, field.GoName().String())\n\t\t\t} else {\n\t\t\t\tclientMethod.FormValueCode += fmt.Sprintf(\"%q: fmt.Sprint(req.Get%s()),\\n\", form, field.GoName().String())\n\t\t\t}\n\t\t}\n\n\t\tif anno := getAnnotation(field.Annotations, AnnotationBody); len(anno) > 0 {\n\t\t\thasAnnotation = true\n\t\t\thasBodyAnnotation = true\n\t\t}\n\n\t\tif anno := getAnnotation(field.Annotations, AnnotationFileName); len(anno) > 0 {\n\t\t\thasAnnotation = true\n\t\t\tfileName := anno[0]\n\t\t\thasFormAnnotation = true\n\t\t\tclientMethod.FormFileCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", fileName, field.GoName().String())\n\t\t}\n\t\tif anno := getAnnotation(field.Annotations, AnnotationCookie); len(anno) > 0 {\n\t\t\thasAnnotation = true\n\t\t\t// cookie do nothing\n\t\t}\n\t\tif !hasAnnotation && strings.EqualFold(clientMethod.HTTPMethod, \"get\") {\n\t\t\tif isOptional && enableOptional {\n\t\t\t\tclientMethod.QueryParamsCode += fmt.Sprintf(\"%q: func() interface{} {\\n\\t\\t\\t\\tif req.IsSet%s() {\\n\\t\\t\\t\\t\\treturn req.Get%s()\\n\\t\\t\\t\\t} else {\\n\\t\\t\\t\\t\\treturn nil\\n\\t\\t\\t\\t}}(),\\n\", checkSnakeName(field.GetName()), field.GoName().String(), field.GoName().String())\n\t\t\t} else {\n\t\t\t\tclientMethod.QueryParamsCode += fmt.Sprintf(\"%q: req.Get%s(),\\n\", checkSnakeName(field.GetName()), field.GoName().String())\n\t\t\t}\n\t\t}\n\t}\n\tclientMethod.BodyParamsCode = meta.SetBodyParam\n\tif hasBodyAnnotation && hasFormAnnotation {\n\t\tclientMethod.FormValueCode = \"\"\n\t\tclientMethod.FormFileCode = \"\"\n\t}\n\tif !hasBodyAnnotation && hasFormAnnotation {\n\t\tclientMethod.BodyParamsCode = \"\"\n\t}\n\n\treturn nil\n}\n\ntype extendServiceList []string\n\nfunc (svr extendServiceList) exist(serviceName string) bool {\n\tfor _, s := range svr {\n\t\tif s == serviceName {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc getExtendServices(ast *parser.Thrift) (res extendServiceList) {\n\tfor a := range ast.DepthFirstSearch() {\n\t\tfor _, svc := range a.Services {\n\t\t\tif len(svc.Extends) > 0 {\n\t\t\t\tres = append(res, svc.Extends)\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\nfunc getAllExtendFunction(svc *parser.Service, ast *parser.Thrift, resolver *Resolver, args *config.Argument) (res []*parser.Function, err error) {\n\tif len(svc.Extends) == 0 {\n\t\treturn\n\t}\n\tparts := semantic.SplitType(svc.Extends)\n\tswitch len(parts) {\n\tcase 1:\n\t\tif resolver.mainPkg.Ast.Filename == ast.Filename { // extended current service for master IDL\n\t\t\textendSvc, found := ast.GetService(parts[0])\n\t\t\tif found {\n\t\t\t\tfuncs := extendSvc.GetFunctions()\n\t\t\t\t// determine if it still has extends\n\t\t\t\textendFuncs, err := getAllExtendFunction(extendSvc, ast, resolver, args)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tres = append(res, append(funcs, extendFuncs...)...)\n\t\t\t}\n\t\t\treturn res, nil\n\t\t} else { // extended current service for other IDL\n\t\t\textendSvc, found := ast.GetService(parts[0])\n\t\t\tif found {\n\t\t\t\tbase, err := addResolverDependency(resolver, ast, args)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tfuncs := extendSvc.GetFunctions()\n\t\t\t\tfor _, f := range funcs {\n\t\t\t\t\tprocessExtendsType(f, base)\n\t\t\t\t}\n\t\t\t\textendFuncs, err := getAllExtendFunction(extendSvc, ast, resolver, args)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tres = append(res, append(funcs, extendFuncs...)...)\n\t\t\t}\n\t\t\treturn res, nil\n\t\t}\n\tcase 2:\n\t\trefAst, found := ast.GetReference(parts[0])\n\t\tbase, err := addResolverDependency(resolver, refAst, args)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// ff the service extends from other files, it has to resolve the dependencies of other files as well\n\t\tfor _, dep := range refAst.Includes {\n\t\t\t_, err := addResolverDependency(resolver, dep.Reference, args)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif found {\n\t\t\textendSvc, found := refAst.GetService(parts[1])\n\t\t\tif found {\n\t\t\t\tfuncs := extendSvc.GetFunctions()\n\t\t\t\tfor _, f := range funcs {\n\t\t\t\t\tprocessExtendsType(f, base)\n\t\t\t\t}\n\t\t\t\textendFuncs, err := getAllExtendFunction(extendSvc, refAst, resolver, args)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tres = append(res, append(funcs, extendFuncs...)...)\n\t\t\t}\n\t\t}\n\t\treturn res, nil\n\t}\n\n\treturn res, nil\n}\n\nfunc processExtendsType(f *parser.Function, base string) {\n\t// the method of other file is extended, and the package of req/resp needs to be changed\n\t// ex. base.thrift -> Resp Method(Req){}\n\t//\t\t\t\t\t  base.Resp Method(base.Req){}\n\tif len(f.Arguments) > 0 {\n\t\tif f.Arguments[0].Type.Category.IsContainerType() {\n\t\t\tswitch f.Arguments[0].Type.Category {\n\t\t\tcase parser.Category_Set, parser.Category_List:\n\t\t\t\tif !strings.Contains(f.Arguments[0].Type.ValueType.Name, \".\") && f.Arguments[0].Type.ValueType.Category.IsStruct() {\n\t\t\t\t\tf.Arguments[0].Type.ValueType.Name = base + \".\" + f.Arguments[0].Type.ValueType.Name\n\t\t\t\t}\n\t\t\tcase parser.Category_Map:\n\t\t\t\tif !strings.Contains(f.Arguments[0].Type.ValueType.Name, \".\") && f.Arguments[0].Type.ValueType.Category.IsStruct() {\n\t\t\t\t\tf.Arguments[0].Type.ValueType.Name = base + \".\" + f.Arguments[0].Type.ValueType.Name\n\t\t\t\t}\n\t\t\t\tif !strings.Contains(f.Arguments[0].Type.KeyType.Name, \".\") && f.Arguments[0].Type.KeyType.Category.IsStruct() {\n\t\t\t\t\tf.Arguments[0].Type.KeyType.Name = base + \".\" + f.Arguments[0].Type.KeyType.Name\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif !strings.Contains(f.Arguments[0].Type.Name, \".\") && f.Arguments[0].Type.Category.IsStruct() {\n\t\t\t\tf.Arguments[0].Type.Name = base + \".\" + f.Arguments[0].Type.Name\n\t\t\t}\n\t\t}\n\t}\n\n\tif f.FunctionType.Category.IsContainerType() {\n\t\tswitch f.FunctionType.Category {\n\t\tcase parser.Category_Set, parser.Category_List:\n\t\t\tif !strings.Contains(f.FunctionType.ValueType.Name, \".\") && f.FunctionType.ValueType.Category.IsStruct() {\n\t\t\t\tf.FunctionType.ValueType.Name = base + \".\" + f.FunctionType.ValueType.Name\n\t\t\t}\n\t\tcase parser.Category_Map:\n\t\t\tif !strings.Contains(f.FunctionType.ValueType.Name, \".\") && f.FunctionType.ValueType.Category.IsStruct() {\n\t\t\t\tf.FunctionType.ValueType.Name = base + \".\" + f.FunctionType.ValueType.Name\n\t\t\t}\n\t\t\tif !strings.Contains(f.FunctionType.KeyType.Name, \".\") && f.FunctionType.KeyType.Category.IsStruct() {\n\t\t\t\tf.FunctionType.KeyType.Name = base + \".\" + f.FunctionType.KeyType.Name\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif !strings.Contains(f.FunctionType.Name, \".\") && f.FunctionType.Category.IsStruct() {\n\t\t\tf.FunctionType.Name = base + \".\" + f.FunctionType.Name\n\t\t}\n\t}\n}\n\nfunc getUniqueResolveDependentName(name string, resolver *Resolver) string {\n\trawName := name\n\tfor i := 0; i < 10000; i++ {\n\t\tif _, exist := resolver.deps[name]; !exist {\n\t\t\treturn name\n\t\t}\n\t\tname = rawName + fmt.Sprint(i)\n\t}\n\n\treturn name\n}\n\nfunc addResolverDependency(resolver *Resolver, ast *parser.Thrift, args *config.Argument) (string, error) {\n\tnamespace, err := resolver.LoadOne(ast)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tbaseName := util.BaseName(ast.Filename, \".thrift\")\n\tif refPkg, exist := resolver.refPkgs[baseName]; !exist {\n\t\tresolver.deps[baseName] = namespace\n\t} else {\n\t\tif ast.Filename != refPkg.Ast.Filename {\n\t\t\tbaseName = getUniqueResolveDependentName(baseName, resolver)\n\t\t\tresolver.deps[baseName] = namespace\n\t\t}\n\t}\n\tpkg := getGoPackage(ast, args.OptPkgMap)\n\timpt := ast.Filename\n\tpkgName := util.SplitPackageName(pkg, \"\")\n\tpkgName, err = util.GetPackageUniqueName(pkgName)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tref := &PackageReference{baseName, impt, &model.Model{\n\t\tFilePath:    ast.Filename,\n\t\tPackage:     pkg,\n\t\tPackageName: pkgName,\n\t}, ast, false}\n\tif _, exist := resolver.refPkgs[baseName]; !exist {\n\t\tresolver.refPkgs[baseName] = ref\n\t}\n\n\treturn baseName, nil\n}\n\n/*---------------------------Model-----------------------------*/\n\nvar BaseThrift = parser.Thrift{}\n\nvar baseTypes = map[string]string{\n\t\"bool\":   \"bool\",\n\t\"byte\":   \"int8\",\n\t\"i8\":     \"int8\",\n\t\"i16\":    \"int16\",\n\t\"i32\":    \"int32\",\n\t\"i64\":    \"int64\",\n\t\"double\": \"float64\",\n\t\"string\": \"string\",\n\t\"binary\": \"[]byte\",\n}\n\nfunc switchBaseType(typ *parser.Type) *model.Type {\n\tswitch typ.Name {\n\tcase \"bool\":\n\t\treturn model.TypeBool\n\tcase \"byte\":\n\t\treturn model.TypeByte\n\tcase \"i8\":\n\t\treturn model.TypeInt8\n\tcase \"i16\":\n\t\treturn model.TypeInt16\n\tcase \"i32\":\n\t\treturn model.TypeInt32\n\tcase \"i64\":\n\t\treturn model.TypeInt64\n\tcase \"int\":\n\t\treturn model.TypeInt\n\tcase \"double\":\n\t\treturn model.TypeFloat64\n\tcase \"string\":\n\t\treturn model.TypeString\n\tcase \"binary\":\n\t\treturn model.TypeBinary\n\t}\n\treturn nil\n}\n\nfunc newBaseType(typ *model.Type, cg model.Category) *model.Type {\n\tcyp := *typ\n\tcyp.Category = cg\n\treturn &cyp\n}\n\nfunc newStructType(name string, cg model.Category) *model.Type {\n\treturn &model.Type{\n\t\tName:     name,\n\t\tScope:    nil,\n\t\tKind:     model.KindStruct,\n\t\tCategory: cg,\n\t\tIndirect: false,\n\t\tExtra:    nil,\n\t\tHasNew:   true,\n\t}\n}\n\nfunc newEnumType(name string, cg model.Category) *model.Type {\n\treturn &model.Type{\n\t\tName:     name,\n\t\tScope:    &model.BaseModel,\n\t\tKind:     model.KindInt,\n\t\tCategory: cg,\n\t}\n}\n\nfunc newFuncType(name string, cg model.Category) *model.Type {\n\treturn &model.Type{\n\t\tName:     name,\n\t\tScope:    nil,\n\t\tKind:     model.KindFunc,\n\t\tCategory: cg,\n\t\tIndirect: false,\n\t\tExtra:    nil,\n\t\tHasNew:   false,\n\t}\n}\n\nfunc (resolver *Resolver) getFieldType(typ *parser.Type) (*model.Type, error) {\n\tif dt, _ := resolver.getBaseType(typ); dt != nil {\n\t\treturn dt, nil\n\t}\n\tsb := resolver.Get(typ.Name)\n\tif sb != nil {\n\t\treturn sb.Type, nil\n\t}\n\treturn nil, fmt.Errorf(\"unknown type: %s\", typ.Name)\n}\n\ntype ResolvedSymbol struct {\n\tBase string\n\tSrc  string\n\t*Symbol\n}\n\nfunc (rs ResolvedSymbol) Expression() string {\n\tbase, err := NameStyle.Identify(rs.Base)\n\tif err != nil {\n\t\tlogs.Warnf(\"%s naming style for %s failed, fall back to %s, please refer to the variable manually!\", NameStyle.Name(), rs.Base, rs.Base)\n\t\tbase = rs.Base\n\t}\n\t// base type no need to do name style\n\tif model.IsBaseType(rs.Type) {\n\t\t// base type mapping\n\t\tif val, exist := baseTypes[rs.Base]; exist {\n\t\t\tbase = val\n\t\t}\n\t}\n\tif rs.Src != \"\" {\n\t\tif !rs.IsValue && model.IsBaseType(rs.Type) {\n\t\t\treturn base\n\t\t}\n\t\treturn fmt.Sprintf(\"%s.%s\", rs.Src, base)\n\t}\n\treturn base\n}\n\nfunc astToModel(ast *parser.Thrift, rs *Resolver) (*model.Model, error) {\n\tmain := rs.mainPkg.Model\n\tif main == nil {\n\t\tmain = new(model.Model)\n\t}\n\n\t// typedefs\n\ttds := ast.GetTypedefs()\n\ttypdefs := make([]model.TypeDef, 0, len(tds))\n\tfor _, t := range tds {\n\t\ttd := model.TypeDef{\n\t\t\tScope: main,\n\t\t\tAlias: t.Alias,\n\t\t}\n\t\tif bt, err := rs.ResolveType(t.Type); bt == nil || err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%s has no type definition, error: %s\", t.String(), err)\n\t\t} else {\n\t\t\ttd.Type = bt\n\t\t}\n\t\ttypdefs = append(typdefs, td)\n\t}\n\tmain.Typedefs = typdefs\n\n\t// constants\n\tcts := ast.GetConstants()\n\tconstants := make([]model.Constant, 0, len(cts))\n\tvariables := make([]model.Variable, 0, len(cts))\n\tfor _, c := range cts {\n\t\tft, err := rs.ResolveType(c.Type)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif ft.Name == model.TypeBaseList.Name || ft.Name == model.TypeBaseMap.Name || ft.Name == model.TypeBaseSet.Name {\n\t\t\tresolveValue, err := rs.ResolveConstantValue(c.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvt := model.Variable{\n\t\t\t\tScope: main,\n\t\t\t\tName:  c.Name,\n\t\t\t\tType:  ft,\n\t\t\t\tValue: resolveValue,\n\t\t\t}\n\t\t\tvariables = append(variables, vt)\n\t\t} else {\n\t\t\tresolveValue, err := rs.ResolveConstantValue(c.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tct := model.Constant{\n\t\t\t\tScope: main,\n\t\t\t\tName:  c.Name,\n\t\t\t\tType:  ft,\n\t\t\t\tValue: resolveValue,\n\t\t\t}\n\t\t\tconstants = append(constants, ct)\n\t\t}\n\t}\n\tmain.Constants = constants\n\tmain.Variables = variables\n\n\t// Enums\n\tems := ast.GetEnums()\n\tenums := make([]model.Enum, 0, len(ems))\n\tfor _, e := range ems {\n\t\tem := model.Enum{\n\t\t\tScope:  main,\n\t\t\tName:   e.GetName(),\n\t\t\tGoType: \"int64\",\n\t\t}\n\t\tvs := make([]model.Constant, 0, len(e.Values))\n\t\tfor _, ee := range e.Values {\n\t\t\tvs = append(vs, model.Constant{\n\t\t\t\tScope: main,\n\t\t\t\tName:  ee.Name,\n\t\t\t\tType:  model.TypeInt64,\n\t\t\t\tValue: model.IntExpression{Src: int(ee.Value)},\n\t\t\t})\n\t\t}\n\t\tem.Values = vs\n\t\tenums = append(enums, em)\n\t}\n\tmain.Enums = enums\n\n\t// Structs\n\tsts := make([]*parser.StructLike, 0, len(ast.Structs))\n\tsts = append(sts, ast.Structs...)\n\tstructs := make([]model.Struct, 0, len(ast.Structs)+len(ast.Unions)+len(ast.Exceptions))\n\tfor _, st := range sts {\n\t\ts := model.Struct{\n\t\t\tScope:           main,\n\t\t\tName:            st.GetName(),\n\t\t\tCategory:        model.CategoryStruct,\n\t\t\tLeadingComments: removeCommentsSlash(st.GetReservedComments()),\n\t\t}\n\n\t\tvs := make([]model.Field, 0, len(st.Fields))\n\t\tfor _, f := range st.Fields {\n\t\t\tfieldName, _ := (&styles.ThriftGo{}).Identify(f.Name)\n\t\t\tisP, err := isPointer(f, rs)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tresolveType, err := rs.ResolveType(f.Type)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tfield := model.Field{\n\t\t\t\tScope: &s,\n\t\t\t\tName:  fieldName,\n\t\t\t\tType:  resolveType,\n\t\t\t\t// IsSetDefault:    f.IsSetDefault(),\n\t\t\t\tLeadingComments: removeCommentsSlash(f.GetReservedComments()),\n\t\t\t\tIsPointer:       isP,\n\t\t\t}\n\t\t\terr = injectTags(f, &field, true, true)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvs = append(vs, field)\n\t\t}\n\t\tcheckDuplicatedFileName(vs)\n\t\ts.Fields = vs\n\t\tstructs = append(structs, s)\n\t}\n\n\tsts = make([]*parser.StructLike, 0, len(ast.Unions))\n\tsts = append(sts, ast.Unions...)\n\tfor _, st := range sts {\n\t\ts := model.Struct{\n\t\t\tScope:           main,\n\t\t\tName:            st.GetName(),\n\t\t\tCategory:        model.CategoryUnion,\n\t\t\tLeadingComments: removeCommentsSlash(st.GetReservedComments()),\n\t\t}\n\t\tvs := make([]model.Field, 0, len(st.Fields))\n\t\tfor _, f := range st.Fields {\n\t\t\tfieldName, _ := (&styles.ThriftGo{}).Identify(f.Name)\n\t\t\tisP, err := isPointer(f, rs)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tresolveType, err := rs.ResolveType(f.Type)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tfield := model.Field{\n\t\t\t\tScope:           &s,\n\t\t\t\tName:            fieldName,\n\t\t\t\tType:            resolveType,\n\t\t\t\tLeadingComments: removeCommentsSlash(f.GetReservedComments()),\n\t\t\t\tIsPointer:       isP,\n\t\t\t}\n\t\t\terr = injectTags(f, &field, true, true)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvs = append(vs, field)\n\t\t}\n\t\tcheckDuplicatedFileName(vs)\n\t\ts.Fields = vs\n\t\tstructs = append(structs, s)\n\t}\n\n\tsts = make([]*parser.StructLike, 0, len(ast.Exceptions))\n\tsts = append(sts, ast.Exceptions...)\n\tfor _, st := range sts {\n\t\ts := model.Struct{\n\t\t\tScope:           main,\n\t\t\tName:            st.GetName(),\n\t\t\tCategory:        model.CategoryException,\n\t\t\tLeadingComments: removeCommentsSlash(st.GetReservedComments()),\n\t\t}\n\t\tvs := make([]model.Field, 0, len(st.Fields))\n\t\tfor _, f := range st.Fields {\n\t\t\tfieldName, _ := (&styles.ThriftGo{}).Identify(f.Name)\n\t\t\tisP, err := isPointer(f, rs)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tresolveType, err := rs.ResolveType(f.Type)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tfield := model.Field{\n\t\t\t\tScope:           &s,\n\t\t\t\tName:            fieldName,\n\t\t\t\tType:            resolveType,\n\t\t\t\tLeadingComments: removeCommentsSlash(f.GetReservedComments()),\n\t\t\t\tIsPointer:       isP,\n\t\t\t}\n\t\t\terr = injectTags(f, &field, true, true)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvs = append(vs, field)\n\t\t}\n\t\tcheckDuplicatedFileName(vs)\n\t\ts.Fields = vs\n\t\tstructs = append(structs, s)\n\t}\n\tmain.Structs = structs\n\n\t// In case of only the service refers another model, therefore scanning service is necessary\n\tss := ast.GetServices()\n\tvar err error\n\tfor _, s := range ss {\n\t\tfor _, m := range s.GetFunctions() {\n\t\t\t_, err = rs.ResolveType(m.GetFunctionType())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tfor _, a := range m.GetArguments() {\n\t\t\t\t_, err = rs.ResolveType(a.GetType())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn main, nil\n}\n\n// removeCommentsSlash can remove double slash for comments with thrift\nfunc removeCommentsSlash(comments string) string {\n\tif comments == \"\" {\n\t\treturn \"\"\n\t}\n\n\treturn comments[2:]\n}\n\nfunc isPointer(f *parser.Field, rs *Resolver) (bool, error) {\n\ttyp, err := rs.ResolveType(f.GetType())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif typ == nil {\n\t\treturn false, fmt.Errorf(\"can not get type: %s for %s\", f.GetType(), f.GetName())\n\t}\n\tif typ.Kind == model.KindStruct || typ.Kind == model.KindMap || typ.Kind == model.KindSlice {\n\t\treturn false, nil\n\t}\n\n\tif f.GetRequiredness().IsOptional() {\n\t\treturn true, nil\n\t} else {\n\t\treturn false, nil\n\t}\n}\n\nfunc getNewFieldName(fieldName string, fieldNameSet map[string]bool) string {\n\tif _, ex := fieldNameSet[fieldName]; ex {\n\t\tfieldName = fieldName + \"_\"\n\t\treturn getNewFieldName(fieldName, fieldNameSet)\n\t}\n\treturn fieldName\n}\n\nfunc checkDuplicatedFileName(vs []model.Field) {\n\tfieldNameSet := make(map[string]bool)\n\tfor i := 0; i < len(vs); i++ {\n\t\tif _, ex := fieldNameSet[vs[i].Name]; ex {\n\t\t\tnewName := getNewFieldName(vs[i].Name, fieldNameSet)\n\t\t\tfieldNameSet[newName] = true\n\t\t\tvs[i].Name = newName\n\t\t} else {\n\t\t\tfieldNameSet[vs[i].Name] = true\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/thrift/plugin.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage thrift\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/config\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n\t\"github.com/cloudwego/thriftgo/generator/backend\"\n\t\"github.com/cloudwego/thriftgo/generator/golang\"\n\t\"github.com/cloudwego/thriftgo/generator/golang/styles\"\n\t\"github.com/cloudwego/thriftgo/parser\"\n\tthriftgo_plugin \"github.com/cloudwego/thriftgo/plugin\"\n)\n\ntype Plugin struct {\n\treq    *thriftgo_plugin.Request\n\targs   *config.Argument\n\tlogger *logs.StdLogger\n\trmTags []string\n}\n\nvar debugPlugin = os.Getenv(\"HERTZ_DEBUG_PLUGIN\") != \"\"\n\nfunc NewPlugin(args *config.Argument, req *thriftgo_plugin.Request) *Plugin {\n\tret := &Plugin{\n\t\targs: args,\n\t\treq:  req,\n\t}\n\tret.setLogger()\n\treturn ret\n}\n\nfunc (plugin *Plugin) Run() int {\n\tplugin.setLogger()\n\targs := &config.Argument{}\n\tdefer func() {\n\t\tif args == nil {\n\t\t\treturn\n\t\t}\n\t\tif args.Verbose {\n\t\t\tverboseLog := plugin.recvVerboseLogger()\n\t\t\tif len(verboseLog) != 0 {\n\t\t\t\tfmt.Fprintf(os.Stderr, verboseLog)\n\t\t\t}\n\t\t} else {\n\t\t\twarning := plugin.recvWarningLogger()\n\t\t\tif len(warning) != 0 {\n\t\t\t\tfmt.Fprintf(os.Stderr, warning)\n\t\t\t}\n\t\t}\n\t}()\n\n\tin, err := plugin.handleRequest()\n\tif err != nil {\n\t\tlogs.Errorf(\"handle request failed: %s\", err.Error())\n\t\treturn meta.PluginError\n\t}\n\n\targs, err = plugin.parseArgs()\n\tif err != nil {\n\t\tlogs.Errorf(\"parse args failed: %s\", err.Error())\n\t\treturn meta.PluginError\n\t}\n\n\tif debugPlugin {\n\t\tos.WriteFile(\"./req.tf\", in, 0644)\n\t\tjs, err := json.Marshal(args)\n\t\tif err != nil {\n\t\t\tlogs.Errorf(\"marshal request failed: %s\\n\", err.Error())\n\t\t\treturn meta.PluginError\n\t\t}\n\t\tos.WriteFile(\"./args.json\", js, 0644)\n\t}\n\n\tres, err := plugin.Handle(args)\n\tif err != nil {\n\t\tlogs.Errorf(\"handle failed: %s\", err.Error())\n\t\treturn meta.PluginError\n\t}\n\n\tif res != nil {\n\t\tif err = plugin.response(res); err != nil {\n\t\t\tlogs.Errorf(\"response failed: %s\", err.Error())\n\t\t\treturn meta.PluginError\n\t\t}\n\t}\n\n\treturn 0\n}\n\nfunc (plugin *Plugin) Handle(args *config.Argument) (*thriftgo_plugin.Response, error) {\n\tplugin.rmTags = args.RmTags\n\tif args.CmdType == meta.CmdModel {\n\t\t// check tag options for model mode\n\t\tCheckTagOption(plugin.args)\n\t\tres, err := plugin.GetResponse(nil, args.OutDir)\n\t\tif err != nil {\n\t\t\tlogs.Errorf(\"get response failed: %s\", err.Error())\n\t\t\treturn nil, err\n\t\t}\n\t\tif err := plugin.response(res); err != nil {\n\t\t\tlogs.Errorf(\"response failed: %s\", err.Error())\n\t\t\treturn nil, err\n\t\t}\n\t\treturn nil, nil\n\t}\n\n\terr := plugin.initNameStyle()\n\tif err != nil {\n\t\tlogs.Errorf(\"init naming style failed: %s\", err.Error())\n\t\treturn nil, err\n\t}\n\n\toptions := CheckTagOption(plugin.args)\n\n\tpkgInfo, err := plugin.getPackageInfo()\n\tif err != nil {\n\t\tlogs.Errorf(\"get http package info failed: %s\", err.Error())\n\t\treturn nil, err\n\t}\n\n\tcustomPackageTemplate := args.CustomizePackage\n\tpkg, err := args.GetGoPackage()\n\tif err != nil {\n\t\tlogs.Errorf(\"get go package failed: %s\", err.Error())\n\t\treturn nil, err\n\t}\n\thandlerDir, err := args.GetHandlerDir()\n\tif err != nil {\n\t\tlogs.Errorf(\"get handler dir failed: %s\", err.Error())\n\t\treturn nil, err\n\t}\n\trouterDir, err := args.GetRouterDir()\n\tif err != nil {\n\t\tlogs.Errorf(\"get router dir failed: %s\", err.Error())\n\t\treturn nil, err\n\t}\n\tmodelDir, err := args.GetModelDir()\n\tif err != nil {\n\t\tlogs.Errorf(\"get model dir failed: %s\", err.Error())\n\t\treturn nil, err\n\t}\n\tclientDir, err := args.GetClientDir()\n\tif err != nil {\n\t\tlogs.Errorf(\"get client dir failed: %s\", err.Error())\n\t\treturn nil, err\n\t}\n\tsg := generator.HttpPackageGenerator{\n\t\tConfigPath: customPackageTemplate,\n\t\tHandlerDir: handlerDir,\n\t\tRouterDir:  routerDir,\n\t\tModelDir:   modelDir,\n\t\tUseDir:     args.Use,\n\t\tClientDir:  clientDir,\n\t\tTemplateGenerator: generator.TemplateGenerator{\n\t\t\tOutputDir: args.OutDir,\n\t\t\tExcludes:  args.Excludes,\n\t\t},\n\t\tProjPackage:          pkg,\n\t\tOptions:              options,\n\t\tHandlerByMethod:      args.HandlerByMethod,\n\t\tCmdType:              args.CmdType,\n\t\tIdlClientDir:         util.SubDir(modelDir, pkgInfo.Package),\n\t\tForceClientDir:       args.ForceClientDir,\n\t\tBaseDomain:           args.BaseDomain,\n\t\tQueryEnumAsInt:       args.QueryEnumAsInt,\n\t\tSnakeStyleMiddleware: args.SnakeStyleMiddleware,\n\t\tSortRouter:           args.SortRouter,\n\t\tForceUpdateClient:    args.ForceUpdateClient,\n\t}\n\tif args.ModelBackend != \"\" {\n\t\tsg.Backend = meta.Backend(args.ModelBackend)\n\t}\n\tgenerator.SetDefaultTemplateConfig()\n\n\terr = sg.Generate(pkgInfo)\n\tif err != nil {\n\t\tlogs.Errorf(\"generate package failed: %s\", err.Error())\n\t\treturn nil, err\n\t}\n\tif len(args.Use) != 0 {\n\t\terr = sg.Persist()\n\t\tif err != nil {\n\t\t\tlogs.Errorf(\"persist file failed within '-use' option: %s\", err.Error())\n\t\t\treturn nil, err\n\t\t}\n\t\tres := thriftgo_plugin.BuildErrorResponse(errors.New(meta.TheUseOptionMessage).Error())\n\t\terr = plugin.response(res)\n\t\tif err != nil {\n\t\t\tlogs.Errorf(\"response failed: %s\", err.Error())\n\t\t\treturn nil, err\n\t\t}\n\t\treturn nil, nil\n\t}\n\tfiles, err := sg.GetFormatAndExcludedFiles()\n\tif err != nil {\n\t\tlogs.Errorf(\"format file failed: %s\", err.Error())\n\t\treturn nil, err\n\t}\n\tres, err := plugin.GetResponse(files, sg.OutputDir)\n\tif err != nil {\n\t\tlogs.Errorf(\"get response failed: %s\", err.Error())\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n\nfunc (plugin *Plugin) setLogger() {\n\tplugin.logger = logs.NewStdLogger(logs.LevelInfo)\n\tplugin.logger.Defer = true\n\tplugin.logger.ErrOnly = true\n\tlogs.SetLogger(plugin.logger)\n}\n\nfunc (plugin *Plugin) recvWarningLogger() string {\n\twarns := plugin.logger.Warn()\n\tplugin.logger.Flush()\n\tlogs.SetLogger(logs.NewStdLogger(logs.LevelInfo))\n\treturn warns\n}\n\nfunc (plugin *Plugin) recvVerboseLogger() string {\n\tinfo := plugin.logger.Out()\n\twarns := plugin.logger.Warn()\n\tverboseLog := string(info) + warns\n\tplugin.logger.Flush()\n\tlogs.SetLogger(logs.NewStdLogger(logs.LevelInfo))\n\treturn verboseLog\n}\n\nfunc (plugin *Plugin) handleRequest() ([]byte, error) {\n\tdata, err := ioutil.ReadAll(os.Stdin)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read request failed: %s\", err.Error())\n\t}\n\treq, err := thriftgo_plugin.UnmarshalRequest(data)\n\tif err != nil {\n\t\treturn data, fmt.Errorf(\"unmarshal request failed: %s\", err.Error())\n\t}\n\tplugin.req = req\n\t// init thriftgo utils\n\tthriftgoUtil = golang.NewCodeUtils(backend.DummyLogFunc())\n\tthriftgoUtil.HandleOptions(req.GeneratorParameters)\n\n\treturn data, nil\n}\n\nfunc (plugin *Plugin) parseArgs() (*config.Argument, error) {\n\tif plugin.req == nil {\n\t\treturn nil, fmt.Errorf(\"request is nil\")\n\t}\n\targs := new(config.Argument)\n\terr := args.Unpack(plugin.req.PluginParameters)\n\tif err != nil {\n\t\tlogs.Errorf(\"unpack args failed: %s\", err.Error())\n\t}\n\tplugin.args = args\n\treturn args, nil\n}\n\n// initNameStyle initializes the naming style based on the \"naming_style\" option for thrift.\nfunc (plugin *Plugin) initNameStyle() error {\n\tif len(plugin.args.ThriftOptions) == 0 {\n\t\treturn nil\n\t}\n\tfor _, opt := range plugin.args.ThriftOptions {\n\t\tparts := strings.SplitN(opt, \"=\", 2)\n\t\tif len(parts) == 2 && parts[0] == \"naming_style\" {\n\t\t\tNameStyle = styles.NewNamingStyle(parts[1])\n\t\t\tif NameStyle == nil {\n\t\t\t\treturn fmt.Errorf(\"do not support \\\"%s\\\" naming style\", parts[1])\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (plugin *Plugin) getPackageInfo() (*generator.HttpPackage, error) {\n\treq := plugin.req\n\targs := plugin.args\n\n\tast := req.GetAST()\n\tif ast == nil {\n\t\treturn nil, fmt.Errorf(\"no ast\")\n\t}\n\tlogs.Infof(\"Processing %s\", ast.GetFilename())\n\n\tpkgMap := args.OptPkgMap\n\tpkg := getGoPackage(ast, pkgMap)\n\tmain := &model.Model{\n\t\tFilePath:    ast.Filename,\n\t\tPackage:     pkg,\n\t\tPackageName: util.SplitPackageName(pkg, \"\"),\n\t}\n\trs, err := NewResolver(ast, main, pkgMap)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"new thrift resolver failed, err:%v\", err)\n\t}\n\terr = rs.LoadAll(ast)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tidlPackage := getGoPackage(ast, pkgMap)\n\tif idlPackage == \"\" {\n\t\treturn nil, fmt.Errorf(\"go package for '%s' is not defined\", ast.GetFilename())\n\t}\n\n\tservices, err := astToService(ast, rs, args)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar models model.Models\n\tfor _, s := range services {\n\t\tmodels.MergeArray(s.Models)\n\t}\n\n\treturn &generator.HttpPackage{\n\t\tServices: services,\n\t\tIdlName:  ast.GetFilename(),\n\t\tPackage:  idlPackage,\n\t\tModels:   models,\n\t}, nil\n}\n\nfunc (plugin *Plugin) response(res *thriftgo_plugin.Response) error {\n\tdata, err := thriftgo_plugin.MarshalResponse(res)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal response failed: %s\", err.Error())\n\t}\n\t_, err = os.Stdout.Write(data)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"write response failed: %s\", err.Error())\n\t}\n\treturn nil\n}\n\nfunc (plugin *Plugin) InsertTag() ([]*thriftgo_plugin.Generated, error) {\n\tvar res []*thriftgo_plugin.Generated\n\n\tif plugin.args.NoRecurse {\n\t\toutPath := plugin.req.OutputPath\n\t\tpackageName := getGoPackage(plugin.req.AST, nil)\n\t\tfileName := util.BaseNameAndTrim(plugin.req.AST.GetFilename()) + \".go\"\n\t\toutPath = filepath.Join(outPath, packageName, fileName)\n\t\tfor _, st := range plugin.req.AST.Structs {\n\t\t\tstName := st.GetName()\n\t\t\tfor _, f := range st.Fields {\n\t\t\t\tfieldName := f.GetName()\n\t\t\t\ttagString, err := getTagString(f, plugin.rmTags)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tinsertPointer := \"struct.\" + stName + \".\" + fieldName + \".\" + \"tag\"\n\t\t\t\tgen := &thriftgo_plugin.Generated{\n\t\t\t\t\tContent:        tagString,\n\t\t\t\t\tName:           &outPath,\n\t\t\t\t\tInsertionPoint: &insertPointer,\n\t\t\t\t}\n\t\t\t\tres = append(res, gen)\n\t\t\t}\n\t\t}\n\t\treturn res, nil\n\t}\n\n\tfor ast := range plugin.req.AST.DepthFirstSearch() {\n\t\toutPath := plugin.req.OutputPath\n\t\tpackageName := getGoPackage(ast, nil)\n\t\tfileName := util.BaseNameAndTrim(ast.GetFilename()) + \".go\"\n\t\toutPath = filepath.Join(outPath, packageName, fileName)\n\n\t\tfor _, st := range ast.Structs {\n\t\t\tstName := st.GetName()\n\t\t\tfor _, f := range st.Fields {\n\t\t\t\tfieldName := f.GetName()\n\t\t\t\ttagString, err := getTagString(f, plugin.rmTags)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tinsertPointer := \"struct.\" + stName + \".\" + fieldName + \".\" + \"tag\"\n\t\t\t\tgen := &thriftgo_plugin.Generated{\n\t\t\t\t\tContent:        tagString,\n\t\t\t\t\tName:           &outPath,\n\t\t\t\t\tInsertionPoint: &insertPointer,\n\t\t\t\t}\n\t\t\t\tres = append(res, gen)\n\t\t\t}\n\t\t}\n\t}\n\treturn res, nil\n}\n\nfunc (plugin *Plugin) GetResponse(files []generator.File, outputDir string) (*thriftgo_plugin.Response, error) {\n\tvar contents []*thriftgo_plugin.Generated\n\tfor _, file := range files {\n\t\tfilePath := filepath.Join(outputDir, file.Path)\n\t\tcontent := &thriftgo_plugin.Generated{\n\t\t\tContent: file.Content,\n\t\t\tName:    &filePath,\n\t\t}\n\t\tcontents = append(contents, content)\n\t}\n\n\tinsertTag, err := plugin.InsertTag()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcontents = append(contents, insertTag...)\n\n\treturn &thriftgo_plugin.Response{\n\t\tContents: contents,\n\t}, nil\n}\n\nfunc getTagString(f *parser.Field, rmTags []string) (string, error) {\n\tfield := model.Field{}\n\terr := injectTags(f, &field, true, false)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdisableTag := false\n\tif v := getAnnotation(f.Annotations, AnnotationNone); len(v) > 0 {\n\t\tif strings.EqualFold(v[0], \"true\") {\n\t\t\tdisableTag = true\n\t\t}\n\t}\n\n\tfor _, rmTag := range rmTags {\n\t\tfor _, t := range field.Tags {\n\t\t\tif t.IsDefault && strings.EqualFold(t.Key, rmTag) {\n\t\t\t\tfield.Tags.Remove(t.Key)\n\t\t\t}\n\t\t}\n\t}\n\n\tvar tagString string\n\ttags := field.Tags\n\tfor idx, tag := range tags {\n\t\tvalue := tag.Value\n\t\tif disableTag {\n\t\t\tvalue = \"-\"\n\t\t}\n\t\tif idx == 0 {\n\t\t\ttagString += \" \" + tag.Key + \":\\\"\" + value + \"\\\"\" + \" \"\n\t\t} else if idx == len(tags)-1 {\n\t\t\ttagString += tag.Key + \":\\\"\" + value + \"\\\"\"\n\t\t} else {\n\t\t\ttagString += tag.Key + \":\\\"\" + value + \"\\\"\" + \" \"\n\t\t}\n\t}\n\n\treturn tagString, nil\n}\n"
  },
  {
    "path": "cmd/hz/thrift/plugin_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage thrift\n\nimport (\n\t\"io/ioutil\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/generator\"\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/thriftgo/plugin\"\n)\n\nfunc TestRun(t *testing.T) {\n\tdata, err := ioutil.ReadFile(\"../testdata/request_thrift.out\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq, err := plugin.UnmarshalRequest(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tplu := new(Plugin)\n\tplu.setLogger()\n\n\tplu.req = req\n\n\t_, err = plu.parseArgs()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\toptions := CheckTagOption(plu.args)\n\n\tpkgInfo, err := plu.getPackageInfo()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\targs := plu.args\n\tcustomPackageTemplate := args.CustomizePackage\n\tpkg, err := args.GetGoPackage()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\thandlerDir, err := args.GetHandlerDir()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trouterDir, err := args.GetRouterDir()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmodelDir, err := args.GetModelDir()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tclientDir, err := args.GetClientDir()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tsg := generator.HttpPackageGenerator{\n\t\tConfigPath: customPackageTemplate,\n\t\tHandlerDir: handlerDir,\n\t\tRouterDir:  routerDir,\n\t\tModelDir:   modelDir,\n\t\tUseDir:     args.Use,\n\t\tClientDir:  clientDir,\n\t\tTemplateGenerator: generator.TemplateGenerator{\n\t\t\tOutputDir: args.OutDir,\n\t\t\tExcludes:  args.Excludes,\n\t\t},\n\t\tProjPackage:          pkg,\n\t\tOptions:              options,\n\t\tHandlerByMethod:      args.HandlerByMethod,\n\t\tCmdType:              args.CmdType,\n\t\tForceClientDir:       args.ForceClientDir,\n\t\tBaseDomain:           args.BaseDomain,\n\t\tQueryEnumAsInt:       args.QueryEnumAsInt,\n\t\tSnakeStyleMiddleware: args.SnakeStyleMiddleware,\n\t\tSortRouter:           args.SortRouter,\n\t}\n\tif args.ModelBackend != \"\" {\n\t\tsg.Backend = meta.Backend(args.ModelBackend)\n\t}\n\n\terr = sg.Generate(pkgInfo)\n\tif err != nil {\n\t\tt.Fatalf(\"generate package failed: %v\", err)\n\t}\n\tfiles, err := sg.GetFormatAndExcludedFiles()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := plu.GetResponse(files, sg.OutputDir)\n\tif err != nil {\n\t\treturn\n\t}\n\tplu.response(res)\n\tif err != nil {\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/thrift/resolver.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage thrift\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/thriftgo/parser\"\n)\n\nvar (\n\tConstTrue = Symbol{\n\t\tIsValue: true,\n\t\tType:    model.TypeBool,\n\t\tValue:   true,\n\t\tScope:   &BaseThrift,\n\t}\n\tConstFalse = Symbol{\n\t\tIsValue: true,\n\t\tType:    model.TypeBool,\n\t\tValue:   false,\n\t\tScope:   &BaseThrift,\n\t}\n\tConstEmptyString = Symbol{\n\t\tIsValue: true,\n\t\tType:    model.TypeString,\n\t\tValue:   \"\",\n\t\tScope:   &BaseThrift,\n\t}\n)\n\ntype PackageReference struct {\n\tIncludeBase string\n\tIncludePath string\n\tModel       *model.Model\n\tAst         *parser.Thrift\n\tReferred    bool\n}\n\nfunc getReferPkgMap(pkgMap map[string]string, incs []*parser.Include, mainModel *model.Model) (map[string]*PackageReference, error) {\n\tvar err error\n\tout := make(map[string]*PackageReference, len(pkgMap))\n\tpkgAliasMap := make(map[string]string, len(incs))\n\t// bugfix: add main package to avoid namespace conflict\n\tmainPkg := mainModel.Package\n\tmainPkgName := mainModel.PackageName\n\tmainPkgName, err = util.GetPackageUniqueName(mainPkgName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpkgAliasMap[mainPkg] = mainPkgName\n\tfor _, inc := range incs {\n\t\tpkg := getGoPackage(inc.Reference, pkgMap)\n\t\timpt := inc.GetPath()\n\t\tbase := util.BaseNameAndTrim(impt)\n\t\tpkgName := util.SplitPackageName(pkg, \"\")\n\t\tif pn, exist := pkgAliasMap[pkg]; exist {\n\t\t\tpkgName = pn\n\t\t} else {\n\t\t\tpkgName, err = util.GetPackageUniqueName(pkgName)\n\t\t\tpkgAliasMap[pkg] = pkgName\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"get package unique name failed, err: %v\", err)\n\t\t\t}\n\t\t}\n\t\tout[base] = &PackageReference{base, impt, &model.Model{\n\t\t\tFilePath:    inc.Path,\n\t\t\tPackage:     pkg,\n\t\t\tPackageName: pkgName,\n\t\t}, inc.Reference, false}\n\t}\n\n\treturn out, nil\n}\n\ntype Symbol struct {\n\tIsValue bool\n\tType    *model.Type\n\tValue   interface{}\n\tScope   *parser.Thrift\n}\n\ntype NameSpace map[string]*Symbol\n\ntype Resolver struct {\n\t// idl symbols\n\troot NameSpace\n\tdeps map[string]NameSpace\n\n\t// exported models\n\tmainPkg PackageReference\n\trefPkgs map[string]*PackageReference\n}\n\nfunc NewResolver(ast *parser.Thrift, model *model.Model, pkgMap map[string]string) (*Resolver, error) {\n\tpm, err := getReferPkgMap(pkgMap, ast.GetIncludes(), model)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get package map failed, err: %v\", err)\n\t}\n\tfile := ast.GetFilename()\n\treturn &Resolver{\n\t\troot:    make(NameSpace),\n\t\tdeps:    make(map[string]NameSpace),\n\t\trefPkgs: pm,\n\t\tmainPkg: PackageReference{\n\t\t\tIncludeBase: util.BaseNameAndTrim(file),\n\t\t\tIncludePath: ast.GetFilename(),\n\t\t\tModel:       model,\n\t\t\tAst:         ast,\n\t\t\tReferred:    false,\n\t\t},\n\t}, nil\n}\n\nfunc (resolver *Resolver) GetRefModel(includeBase string) (*model.Model, error) {\n\tif includeBase == \"\" {\n\t\treturn resolver.mainPkg.Model, nil\n\t}\n\tref, ok := resolver.refPkgs[includeBase]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"not found include %s\", includeBase)\n\t}\n\treturn ref.Model, nil\n}\n\nfunc (resolver *Resolver) getBaseType(typ *parser.Type) (*model.Type, bool) {\n\ttt := switchBaseType(typ)\n\tif tt != nil {\n\t\treturn tt, true\n\t}\n\tif typ.Name == \"map\" {\n\t\tt := *model.TypeBaseMap\n\t\treturn &t, false\n\t}\n\tif typ.Name == \"list\" {\n\t\tt := *model.TypeBaseList\n\t\treturn &t, false\n\t}\n\tif typ.Name == \"set\" {\n\t\tt := *model.TypeBaseList\n\t\treturn &t, false\n\t}\n\treturn nil, false\n}\n\nfunc (resolver *Resolver) ResolveType(typ *parser.Type) (*model.Type, error) {\n\tbt, base := resolver.getBaseType(typ)\n\tif bt != nil {\n\t\tif base {\n\t\t\treturn bt, nil\n\t\t} else {\n\t\t\tif typ.Name == model.TypeBaseMap.Name {\n\t\t\t\tresolveKey, err := resolver.ResolveType(typ.KeyType)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tresolveValue, err := resolver.ResolveType(typ.ValueType)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbt.Extra = append(bt.Extra, resolveKey, resolveValue)\n\t\t\t} else if typ.Name == model.TypeBaseList.Name || typ.Name == model.TypeBaseSet.Name {\n\t\t\t\tresolveValue, err := resolver.ResolveType(typ.ValueType)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbt.Extra = append(bt.Extra, resolveValue)\n\t\t\t} else {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid DefinitionType(%+v)\", bt)\n\t\t\t}\n\t\t\treturn bt, nil\n\t\t}\n\t}\n\n\tid := typ.GetName()\n\trs, err := resolver.ResolveIdentifier(id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsb := rs.Symbol\n\tif sb == nil {\n\t\treturn nil, fmt.Errorf(\"not found identifier %s\", id)\n\t}\n\treturn sb.Type, nil\n}\n\nfunc (resolver *Resolver) ResolveConstantValue(constant *parser.ConstValue) (model.Literal, error) {\n\tswitch constant.Type {\n\tcase parser.ConstType_ConstInt:\n\t\treturn model.IntExpression{Src: int(constant.TypedValue.GetInt())}, nil\n\tcase parser.ConstType_ConstDouble:\n\t\treturn model.DoubleExpression{Src: constant.TypedValue.GetDouble()}, nil\n\tcase parser.ConstType_ConstLiteral:\n\t\treturn model.StringExpression{Src: constant.TypedValue.GetLiteral()}, nil\n\tcase parser.ConstType_ConstList:\n\t\teleType, err := switchConstantType(constant.Type)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tret := model.ListExpression{\n\t\t\tElementType: eleType,\n\t\t}\n\t\tfor _, i := range constant.TypedValue.List {\n\t\t\telem, err := resolver.ResolveConstantValue(i)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tret.Elements = append(ret.Elements, elem)\n\t\t}\n\t\treturn ret, nil\n\tcase parser.ConstType_ConstMap:\n\t\tkeyType, err := switchConstantType(constant.TypedValue.Map[0].Key.Type)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvalueType, err := switchConstantType(constant.TypedValue.Map[0].Value.Type)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tret := model.MapExpression{\n\t\t\tKeyType:   keyType,\n\t\t\tValueType: valueType,\n\t\t\tElements:  make(map[string]model.Literal, len(constant.TypedValue.Map)),\n\t\t}\n\t\tfor _, v := range constant.TypedValue.Map {\n\t\t\tvalue, err := resolver.ResolveConstantValue(v.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tret.Elements[v.Key.String()] = value\n\t\t}\n\t\treturn ret, nil\n\tcase parser.ConstType_ConstIdentifier:\n\t\treturn resolver.ResolveIdentifier(*constant.TypedValue.Identifier)\n\t}\n\treturn model.StringExpression{Src: constant.String()}, nil\n}\n\nfunc (resolver *Resolver) ResolveIdentifier(id string) (ret ResolvedSymbol, err error) {\n\tsb := resolver.Get(id)\n\tif sb == nil {\n\t\treturn ResolvedSymbol{}, fmt.Errorf(\"identifier '%s' not found\", id)\n\t}\n\tret.Symbol = sb\n\tret.Base = id\n\tif sb.Scope == &BaseThrift {\n\t\treturn\n\t}\n\tif sb.Scope == resolver.mainPkg.Ast {\n\t\tresolver.mainPkg.Referred = true\n\t\tret.Src = resolver.mainPkg.Model.PackageName\n\t\treturn\n\t}\n\n\tidx := strings.LastIndex(id, \".\")\n\tdepName := id[:idx]\n\ttypeName := id[idx+1:]\n\tif ref, ok := resolver.refPkgs[depName]; ok {\n\t\tref.Referred = true\n\t\tret.Base = typeName\n\t\tret.Src = ref.Model.PackageName\n\t\tif ret.Type == nil {\n\t\t\tret.Type = &model.Type{}\n\t\t}\n\t\tret.Type.Scope = ref.Model\n\t} else {\n\t\treturn ResolvedSymbol{}, fmt.Errorf(\"can't resolve identifier '%s'\", id)\n\t}\n\n\treturn\n}\n\nfunc (resolver *Resolver) ResolveTypeName(typ *parser.Type) (string, error) {\n\tif typ.GetIsTypedef() {\n\t\trt, err := resolver.ResolveIdentifier(typ.GetName())\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\treturn rt.Expression(), nil\n\t}\n\tswitch typ.GetCategory() {\n\tcase parser.Category_Map:\n\t\tkeyType, err := resolver.ResolveTypeName(typ.GetKeyType())\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif typ.GetKeyType().GetCategory().IsStruct() {\n\t\t\tkeyType = \"*\" + keyType\n\t\t}\n\t\tvalueType, err := resolver.ResolveTypeName(typ.GetValueType())\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif typ.GetValueType().GetCategory().IsStruct() {\n\t\t\tvalueType = \"*\" + valueType\n\t\t}\n\t\treturn fmt.Sprintf(\"map[%s]%s\", keyType, valueType), nil\n\tcase parser.Category_List, parser.Category_Set:\n\t\t// list/set -> []element for thriftgo\n\t\t// valueType refers the element type for list/set\n\t\telemType, err := resolver.ResolveTypeName(typ.GetValueType())\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif typ.GetValueType().GetCategory().IsStruct() {\n\t\t\telemType = \"*\" + elemType\n\t\t}\n\t\treturn fmt.Sprintf(\"[]%s\", elemType), err\n\t}\n\trt, err := resolver.ResolveIdentifier(typ.GetName())\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn rt.Expression(), nil\n}\n\nfunc (resolver *Resolver) Get(name string) *Symbol {\n\ts, ok := resolver.root[name]\n\tif ok {\n\t\treturn s\n\t}\n\tif strings.Contains(name, \".\") {\n\t\tidx := strings.LastIndex(name, \".\")\n\t\tdepName := name[:idx]\n\t\ttypeName := name[idx+1:]\n\t\tif ref, ok := resolver.deps[depName]; ok {\n\t\t\tif ss, ok := ref[typeName]; ok {\n\t\t\t\treturn ss\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (resolver *Resolver) ExportReferred(all, needMain bool) (ret []*PackageReference) {\n\tfor _, v := range resolver.refPkgs {\n\t\tif all {\n\t\t\tret = append(ret, v)\n\t\t\tv.Referred = false\n\t\t} else if v.Referred {\n\t\t\tret = append(ret, v)\n\t\t\tv.Referred = false\n\t\t}\n\t}\n\tif needMain && (all || resolver.mainPkg.Referred) {\n\t\tret = append(ret, &resolver.mainPkg)\n\t}\n\tresolver.mainPkg.Referred = false\n\treturn\n}\n\nfunc (resolver *Resolver) LoadAll(ast *parser.Thrift) error {\n\tvar err error\n\tresolver.root, err = resolver.LoadOne(ast)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"load root package: %s\", err)\n\t}\n\n\tincludes := ast.GetIncludes()\n\tastMap := make(map[string]NameSpace, len(includes))\n\tfor _, dep := range includes {\n\t\tbName := util.BaseName(dep.Path, \".thrift\")\n\t\tastMap[bName], err = resolver.LoadOne(dep.Reference)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"load idl %s: %s\", dep.Path, err)\n\t\t}\n\t}\n\tresolver.deps = astMap\n\tfor _, td := range ast.Typedefs {\n\t\tname := td.GetAlias()\n\t\tif _, ex := resolver.root[name]; ex {\n\t\t\tif resolver.root[name].Type != nil {\n\t\t\t\ttyp := newTypedefType(resolver.root[name].Type, name)\n\t\t\t\tresolver.root[name].Type = &typ\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tsym := resolver.Get(td.Type.GetName())\n\t\ttyp := newTypedefType(sym.Type, name)\n\t\tresolver.root[name].Type = &typ\n\t}\n\treturn nil\n}\n\nfunc LoadBaseIdentifier() NameSpace {\n\tret := make(NameSpace, 16)\n\n\tret[\"true\"] = &ConstTrue\n\tret[\"false\"] = &ConstFalse\n\tret[`\"\"`] = &ConstEmptyString\n\tret[\"bool\"] = &Symbol{\n\t\tType:  model.TypeBool,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"byte\"] = &Symbol{\n\t\tType:  model.TypeByte,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"i8\"] = &Symbol{\n\t\tType:  model.TypeInt8,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"i16\"] = &Symbol{\n\t\tType:  model.TypeInt16,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"i32\"] = &Symbol{\n\t\tType:  model.TypeInt32,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"i64\"] = &Symbol{\n\t\tType:  model.TypeInt64,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"int\"] = &Symbol{\n\t\tType:  model.TypeInt,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"double\"] = &Symbol{\n\t\tType:  model.TypeFloat64,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"string\"] = &Symbol{\n\t\tType:  model.TypeString,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"binary\"] = &Symbol{\n\t\tType:  model.TypeBinary,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"list\"] = &Symbol{\n\t\tType:  model.TypeBaseList,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"set\"] = &Symbol{\n\t\tType:  model.TypeBaseSet,\n\t\tScope: &BaseThrift,\n\t}\n\tret[\"map\"] = &Symbol{\n\t\tType:  model.TypeBaseMap,\n\t\tScope: &BaseThrift,\n\t}\n\treturn ret\n}\n\nfunc (resolver *Resolver) LoadOne(ast *parser.Thrift) (NameSpace, error) {\n\tret := LoadBaseIdentifier()\n\n\tfor _, e := range ast.Enums {\n\t\tprefix := e.GetName()\n\t\tret[prefix] = &Symbol{\n\t\t\tIsValue: false,\n\t\t\tValue:   e,\n\t\t\tScope:   ast,\n\t\t\tType:    newEnumType(prefix, model.CategoryEnum),\n\t\t}\n\t\tfor _, ee := range e.Values {\n\t\t\tname := prefix + \".\" + ee.GetName()\n\t\t\tif _, exist := ret[name]; exist {\n\t\t\t\treturn nil, fmt.Errorf(\"duplicated identifier '%s' in %s\", name, ast.Filename)\n\t\t\t}\n\n\t\t\tret[name] = &Symbol{\n\t\t\t\tIsValue: true,\n\t\t\t\tValue:   ee,\n\t\t\t\tScope:   ast,\n\t\t\t\tType:    newBaseType(model.TypeInt, model.CategoryEnum),\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, e := range ast.Constants {\n\t\tname := e.GetName()\n\t\tif _, exist := ret[name]; exist {\n\t\t\treturn nil, fmt.Errorf(\"duplicated identifier '%s' in %s\", name, ast.Filename)\n\t\t}\n\t\tgt, _ := resolver.getBaseType(e.Type)\n\t\tret[name] = &Symbol{\n\t\t\tIsValue: true,\n\t\t\tValue:   e,\n\t\t\tScope:   ast,\n\t\t\tType:    gt,\n\t\t}\n\t}\n\n\tfor _, e := range ast.Structs {\n\t\tname := e.GetName()\n\t\tif _, exist := ret[name]; exist {\n\t\t\treturn nil, fmt.Errorf(\"duplicated identifier '%s' in %s\", name, ast.Filename)\n\t\t}\n\t\tret[name] = &Symbol{\n\t\t\tIsValue: false,\n\t\t\tValue:   e,\n\t\t\tScope:   ast,\n\t\t\tType:    newStructType(name, model.CategoryStruct),\n\t\t}\n\t}\n\n\tfor _, e := range ast.Unions {\n\t\tname := e.GetName()\n\t\tif _, exist := ret[name]; exist {\n\t\t\treturn nil, fmt.Errorf(\"duplicated identifier '%s' in %s\", name, ast.Filename)\n\t\t}\n\t\tret[name] = &Symbol{\n\t\t\tIsValue: false,\n\t\t\tValue:   e,\n\t\t\tScope:   ast,\n\t\t\tType:    newStructType(name, model.CategoryStruct),\n\t\t}\n\t}\n\n\tfor _, e := range ast.Exceptions {\n\t\tname := e.GetName()\n\t\tif _, exist := ret[name]; exist {\n\t\t\treturn nil, fmt.Errorf(\"duplicated identifier '%s' in %s\", name, ast.Filename)\n\t\t}\n\t\tret[name] = &Symbol{\n\t\t\tIsValue: false,\n\t\t\tValue:   e,\n\t\t\tScope:   ast,\n\t\t\tType:    newStructType(name, model.CategoryStruct),\n\t\t}\n\t}\n\n\tfor _, e := range ast.Services {\n\t\tname := e.GetName()\n\t\tif _, exist := ret[name]; exist {\n\t\t\treturn nil, fmt.Errorf(\"duplicated identifier '%s' in %s\", name, ast.Filename)\n\t\t}\n\t\tret[name] = &Symbol{\n\t\t\tIsValue: false,\n\t\t\tValue:   e,\n\t\t\tScope:   ast,\n\t\t\tType:    newFuncType(name, model.CategoryService),\n\t\t}\n\t}\n\n\tfor _, td := range ast.Typedefs {\n\t\tname := td.GetAlias()\n\t\tif _, exist := ret[name]; exist {\n\t\t\treturn nil, fmt.Errorf(\"duplicated identifier '%s' in %s\", name, ast.Filename)\n\t\t}\n\t\tgt, _ := resolver.getBaseType(td.Type)\n\t\tif gt == nil {\n\t\t\tsym := ret[td.Type.Name]\n\t\t\tif sym != nil {\n\t\t\t\tgt = sym.Type\n\t\t\t}\n\t\t}\n\t\tret[name] = &Symbol{\n\t\t\tIsValue: false,\n\t\t\tValue:   td,\n\t\t\tScope:   ast,\n\t\t\tType:    gt,\n\t\t}\n\t}\n\n\treturn ret, nil\n}\n\nfunc switchConstantType(constant parser.ConstType) (*model.Type, error) {\n\tswitch constant {\n\tcase parser.ConstType_ConstInt:\n\t\treturn model.TypeInt, nil\n\tcase parser.ConstType_ConstDouble:\n\t\treturn model.TypeFloat64, nil\n\tcase parser.ConstType_ConstLiteral:\n\t\treturn model.TypeString, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown constant type %d\", constant)\n\t}\n}\n\nfunc newTypedefType(t *model.Type, name string) model.Type {\n\ttmp := t\n\ttyp := *tmp\n\ttyp.Name = name\n\ttyp.Category = model.CategoryTypedef\n\treturn typ\n}\n"
  },
  {
    "path": "cmd/hz/thrift/tag_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage thrift\n\nimport (\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/config\"\n\t\"github.com/cloudwego/thriftgo/plugin\"\n)\n\nfunc TestInsertTag(t *testing.T) {\n\tdata, err := ioutil.ReadFile(\"./test_data/thrift_tag_test.out\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq, err := plugin.UnmarshalRequest(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tplu := new(Plugin)\n\tplu.req = req\n\tplu.args = new(config.Argument)\n\n\ttype TagStruct struct {\n\t\tAnnotation   string\n\t\tGeneratedTag string\n\t\tActualTag    string\n\t}\n\n\ttagList := []TagStruct{\n\t\t{\n\t\t\tAnnotation:   \"query\",\n\t\t\tGeneratedTag: \"json:\\\"DefaultQueryTag\\\" query:\\\"query\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"raw_body\",\n\t\t\tGeneratedTag: \"json:\\\"RawBodyTag\\\" raw_body:\\\"raw_body\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"path\",\n\t\t\tGeneratedTag: \"json:\\\"PathTag\\\" path:\\\"path\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"form\",\n\t\t\tGeneratedTag: \"form:\\\"form\\\" json:\\\"FormTag\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"cookie\",\n\t\t\tGeneratedTag: \"cookie:\\\"cookie\\\" json:\\\"CookieTag\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"header\",\n\t\t\tGeneratedTag: \"header:\\\"header\\\" json:\\\"HeaderTag\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"body\",\n\t\t\tGeneratedTag: \"form:\\\"body\\\" json:\\\"body\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"go.tag\",\n\t\t\tGeneratedTag: \"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"vd\",\n\t\t\tGeneratedTag: \"form:\\\"VdTag\\\" json:\\\"VdTag\\\" query:\\\"VdTag\\\" vd:\\\"$!='?'\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"non\",\n\t\t\tGeneratedTag: \"form:\\\"DefaultTag\\\" json:\\\"DefaultTag\\\" query:\\\"DefaultTag\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"query required\",\n\t\t\tGeneratedTag: \"json:\\\"ReqQuery,required\\\" query:\\\"query,required\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"query optional\",\n\t\t\tGeneratedTag: \"json:\\\"OptQuery,omitempty\\\" query:\\\"query\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"body required\",\n\t\t\tGeneratedTag: \"form:\\\"body,required\\\" json:\\\"body,required\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"body optional\",\n\t\t\tGeneratedTag: \"form:\\\"body\\\" json:\\\"body,omitempty\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"go.tag required\",\n\t\t\tGeneratedTag: \"form:\\\"ReqGoTag,required\\\" query:\\\"ReqGoTag,required\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"go.tag optional\",\n\t\t\tGeneratedTag: \"form:\\\"OptGoTag\\\" query:\\\"OptGoTag\\\"\",\n\t\t},\n\t\t{\n\t\t\tAnnotation:   \"go tag cover query\",\n\t\t\tGeneratedTag: \"form:\\\"QueryGoTag,required\\\" json:\\\"QueryGoTag,required\\\"\",\n\t\t},\n\t}\n\n\ttags, err := plu.InsertTag()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfor i, tag := range tags {\n\t\ttagList[i].ActualTag = tag.Content\n\t\tif !strings.Contains(tagList[i].ActualTag, tagList[i].GeneratedTag) {\n\t\t\tt.Fatalf(\"expected tag: '%s', but autual tag: '%s'\", tagList[i].GeneratedTag, tagList[i].ActualTag)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/thrift/tags.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage thrift\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/config\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator\"\n\t\"github.com/cloudwego/hertz/cmd/hz/generator/model\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util\"\n\t\"github.com/cloudwego/thriftgo/parser\"\n)\n\nconst (\n\tAnnotationQuery    = \"api.query\"\n\tAnnotationForm     = \"api.form\"\n\tAnnotationPath     = \"api.path\"\n\tAnnotationHeader   = \"api.header\"\n\tAnnotationCookie   = \"api.cookie\"\n\tAnnotationBody     = \"api.body\"\n\tAnnotationRawBody  = \"api.raw_body\"\n\tAnnotationJsConv   = \"api.js_conv\"\n\tAnnotationNone     = \"api.none\"\n\tAnnotationFileName = \"api.file_name\"\n\n\tAnnotationValidator = \"api.vd\"\n\n\tAnnotationGoTag = \"go.tag\"\n)\n\nconst (\n\tApiGet        = \"api.get\"\n\tApiPost       = \"api.post\"\n\tApiPut        = \"api.put\"\n\tApiPatch      = \"api.patch\"\n\tApiDelete     = \"api.delete\"\n\tApiOptions    = \"api.options\"\n\tApiHEAD       = \"api.head\"\n\tApiAny        = \"api.any\"\n\tApiPath       = \"api.path\"\n\tApiSerializer = \"api.serializer\"\n\tApiGenPath    = \"api.handler_path\"\n)\n\nconst (\n\tApiBaseDomain    = \"api.base_domain\"\n\tApiServiceGroup  = \"api.service_group\"\n\tApiServiceGenDir = \"api.service_gen_dir\" // handler_dir for handler_by_service\n\tApiServicePath   = \"api.service_path\"    // declare the path to the service's handler according to this annotation for handler_by_method\n)\n\nvar (\n\tHttpMethodAnnotations = map[string]string{\n\t\tApiGet:     \"GET\",\n\t\tApiPost:    \"POST\",\n\t\tApiPut:     \"PUT\",\n\t\tApiPatch:   \"PATCH\",\n\t\tApiDelete:  \"DELETE\",\n\t\tApiOptions: \"OPTIONS\",\n\t\tApiHEAD:    \"HEAD\",\n\t\tApiAny:     \"ANY\",\n\t}\n\n\tHttpMethodOptionAnnotations = map[string]string{\n\t\tApiGenPath: \"handler_path\",\n\t}\n\n\tBindingTags = map[string]string{\n\t\tAnnotationPath:    \"path\",\n\t\tAnnotationQuery:   \"query\",\n\t\tAnnotationHeader:  \"header\",\n\t\tAnnotationCookie:  \"cookie\",\n\t\tAnnotationBody:    \"json\",\n\t\tAnnotationForm:    \"form\",\n\t\tAnnotationRawBody: \"raw_body\",\n\t}\n\n\tSerializerTags = map[string]string{\n\t\tApiSerializer: \"serializer\",\n\t}\n\n\tValidatorTags = map[string]string{AnnotationValidator: \"vd\"}\n)\n\nvar (\n\tjsonSnakeName  = false\n\tunsetOmitempty = false\n)\n\nfunc CheckTagOption(args *config.Argument) []generator.Option {\n\tvar ret []generator.Option\n\tif args == nil {\n\t\treturn ret\n\t}\n\tif args.SnakeName {\n\t\tjsonSnakeName = true\n\t}\n\tif args.UnsetOmitempty {\n\t\tunsetOmitempty = true\n\t}\n\tif args.JSONEnumStr {\n\t\tret = append(ret, generator.OptionMarshalEnumToText)\n\t}\n\treturn ret\n}\n\nfunc checkSnakeName(name string) string {\n\tif jsonSnakeName {\n\t\tname = util.ToSnakeCase(name)\n\t}\n\treturn name\n}\n\nfunc getAnnotation(input parser.Annotations, target string) []string {\n\tif len(input) == 0 {\n\t\treturn nil\n\t}\n\tfor _, anno := range input {\n\t\tif strings.ToLower(anno.Key) == target {\n\t\t\treturn anno.Values\n\t\t}\n\t}\n\n\treturn []string{}\n}\n\ntype httpAnnotation struct {\n\tmethod string\n\tpath   []string\n}\n\ntype httpAnnotations []httpAnnotation\n\nfunc (s httpAnnotations) Len() int {\n\treturn len(s)\n}\n\nfunc (s httpAnnotations) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc (s httpAnnotations) Less(i, j int) bool {\n\treturn s[i].method < s[j].method\n}\n\nfunc getAnnotations(input parser.Annotations, targets map[string]string) map[string][]string {\n\tif len(input) == 0 || len(targets) == 0 {\n\t\treturn nil\n\t}\n\tout := map[string][]string{}\n\tfor k, t := range targets {\n\t\tvar ret *parser.Annotation\n\t\tfor _, anno := range input {\n\t\t\tif strings.ToLower(anno.Key) == k {\n\t\t\t\tret = anno\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif ret == nil {\n\t\t\tcontinue\n\t\t}\n\t\tout[t] = ret.Values\n\t}\n\treturn out\n}\n\nfunc defaultBindingTags(f *parser.Field) []model.Tag {\n\tout := make([]model.Tag, 3)\n\tbindingTags := []string{\n\t\tAnnotationQuery,\n\t\tAnnotationForm,\n\t\tAnnotationPath,\n\t\tAnnotationHeader,\n\t\tAnnotationCookie,\n\t\tAnnotationBody,\n\t\tAnnotationRawBody,\n\t}\n\n\tfor _, tag := range bindingTags {\n\t\tif v := getAnnotation(f.Annotations, tag); len(v) > 0 {\n\t\t\tout[0] = jsonTag(f)\n\t\t\treturn out[:1]\n\t\t}\n\t}\n\n\tif v := getAnnotation(f.Annotations, AnnotationBody); len(v) > 0 {\n\t\tval := getJsonValue(f, v[0])\n\t\tout[0] = tag(\"json\", val)\n\t} else {\n\t\tt := jsonTag(f)\n\t\tt.IsDefault = true\n\t\tout[0] = t\n\t}\n\tif v := getAnnotation(f.Annotations, AnnotationQuery); len(v) > 0 {\n\t\tval := checkRequire(f, v[0])\n\t\tout[1] = tag(BindingTags[AnnotationQuery], val)\n\t} else {\n\t\tval := checkRequire(f, checkSnakeName(f.Name))\n\t\tt := tag(BindingTags[AnnotationQuery], val)\n\t\tt.IsDefault = true\n\t\tout[1] = t\n\t}\n\tif v := getAnnotation(f.Annotations, AnnotationForm); len(v) > 0 {\n\t\tval := checkRequire(f, v[0])\n\t\tout[2] = tag(BindingTags[AnnotationForm], val)\n\t} else {\n\t\tval := checkRequire(f, checkSnakeName(f.Name))\n\t\tt := tag(BindingTags[AnnotationForm], val)\n\t\tt.IsDefault = true\n\t\tout[2] = t\n\t}\n\treturn out\n}\n\nfunc jsonTag(f *parser.Field) (ret model.Tag) {\n\tret.Key = \"json\"\n\tret.Value = checkSnakeName(f.Name)\n\n\tif v := getAnnotation(f.Annotations, AnnotationJsConv); len(v) > 0 {\n\t\tret.Value += \",string\"\n\t}\n\tif !unsetOmitempty && f.Requiredness == parser.FieldType_Optional {\n\t\tret.Value += \",omitempty\"\n\t} else if f.Requiredness == parser.FieldType_Required {\n\t\tret.Value += \",required\"\n\t}\n\treturn\n}\n\nfunc tag(k, v string) model.Tag {\n\treturn model.Tag{\n\t\tKey:   k,\n\t\tValue: v,\n\t}\n}\n\nfunc annotationToTags(as parser.Annotations, targets map[string]string) (tags []model.Tag) {\n\trets := getAnnotations(as, targets)\n\tfor k, v := range rets {\n\t\tfor _, vv := range v {\n\t\t\ttags = append(tags, model.Tag{\n\t\t\t\tKey:   k,\n\t\t\t\tValue: vv,\n\t\t\t})\n\t\t}\n\t}\n\treturn\n}\n\nfunc injectTags(f *parser.Field, gf *model.Field, needDefault, needGoTag bool) error {\n\tas := f.Annotations\n\tif as == nil {\n\t\tas = parser.Annotations{}\n\t}\n\ttags := gf.Tags\n\tif tags == nil {\n\t\ttags = make([]model.Tag, 0, len(as))\n\t}\n\n\tif needDefault {\n\t\ttags = append(tags, defaultBindingTags(f)...)\n\t}\n\n\t// binding tags\n\tbts := annotationToTags(as, BindingTags)\n\tfor _, t := range bts {\n\t\tkey := t.Key\n\t\ttags.Remove(key)\n\t\tif key == \"json\" {\n\t\t\tformVal := t.Value\n\t\t\tt.Value = getJsonValue(f, t.Value)\n\t\t\tformVal = checkRequire(f, formVal)\n\t\t\ttags = append(tags, tag(\"form\", formVal))\n\t\t} else {\n\t\t\tt.Value = checkRequire(f, t.Value)\n\t\t}\n\t\ttags = append(tags, t)\n\t}\n\n\t// validator tags\n\ttags = append(tags, annotationToTags(as, ValidatorTags)...)\n\n\t// the tag defined by gotag with higher priority\n\tcheckGoTag(as, &tags)\n\n\t// go.tags for compiler mode\n\tif needGoTag {\n\t\trets := getAnnotation(as, AnnotationGoTag)\n\t\tfor _, v := range rets {\n\t\t\tgts := util.SplitGoTags(v)\n\t\t\tfor _, gt := range gts {\n\t\t\t\tsp := strings.SplitN(gt, \":\", 2)\n\t\t\t\tif len(sp) != 2 {\n\t\t\t\t\treturn fmt.Errorf(\"invalid go tag: %s\", v)\n\t\t\t\t}\n\t\t\t\tvv, err := strconv.Unquote(sp[1])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"invalid go.tag value: %s, err: %v\", sp[1], err.Error())\n\t\t\t\t}\n\t\t\t\tkey := sp[0]\n\t\t\t\ttags.Remove(key)\n\t\t\t\ttags = append(tags, model.Tag{\n\t\t\t\t\tKey:   key,\n\t\t\t\t\tValue: vv,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.Sort(tags)\n\tgf.Tags = tags\n\treturn nil\n}\n\nfunc getJsonValue(f *parser.Field, val string) string {\n\tif v := getAnnotation(f.Annotations, AnnotationJsConv); len(v) > 0 {\n\t\tval += \",string\"\n\t}\n\tif !unsetOmitempty && f.Requiredness == parser.FieldType_Optional {\n\t\tval += \",omitempty\"\n\t} else if f.Requiredness == parser.FieldType_Required {\n\t\tval += \",required\"\n\t}\n\n\treturn val\n}\n\nfunc checkRequire(f *parser.Field, val string) string {\n\tif f.Requiredness == parser.FieldType_Required {\n\t\tval += \",required\"\n\t}\n\n\treturn val\n}\n\n// checkGoTag removes the tag defined in gotag\nfunc checkGoTag(as parser.Annotations, tags *model.Tags) error {\n\trets := getAnnotation(as, AnnotationGoTag)\n\tfor _, v := range rets {\n\t\tgts := util.SplitGoTags(v)\n\t\tfor _, gt := range gts {\n\t\t\tsp := strings.SplitN(gt, \":\", 2)\n\t\t\tif len(sp) != 2 {\n\t\t\t\treturn fmt.Errorf(\"invalid go tag: %s\", v)\n\t\t\t}\n\t\t\tkey := sp[0]\n\t\t\ttags.Remove(key)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/hz/thrift/test_data/test_tag.thrift",
    "content": "namespace go cloudwego.hertz.hz\n\nstruct MultiTagReq {\n    // basic feature\n    1: string DefaultQueryTag (api.query=\"query\");\n    2: string RawBodyTag (api.raw_body=\"raw_body\");\n    3: string PathTag (api.path=\"path\");\n    4: string FormTag (api.form=\"form\");\n    5: string CookieTag (api.cookie=\"cookie\");\n    6: string HeaderTag (api.header=\"header\");\n    7: string BodyTag (api.body=\"body\");\n    8: string GoTag (go.tag=\"json:\\\"json\\\" query:\\\"query\\\" form:\\\"form\\\" header:\\\"header\\\" goTag:\\\"tag\\\"\");\n    9: string VdTag (api.vd=\"$!='?'\");\n    10: string DefaultTag;\n\n    // optional / required\n    11: required string ReqQuery (api.query=\"query\");\n    12: optional string OptQuery (api.query=\"query\");\n    13: required string ReqBody (api.body=\"body\");\n    14: optional string OptBody (api.body=\"body\");\n    15: required string ReqGoTag (go.tag=\"json:\\\"json\\\"\");\n    16: optional string OptGoTag (go.tag=\"json:\\\"json\\\"\");\n\n    // gotag cover feature\n    17: required string QueryGoTag (apt.query=\"query\", go.tag=\"query:\\\"queryTag\\\"\")\n}"
  },
  {
    "path": "cmd/hz/thrift/thriftgo_util.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage thrift\n\nimport (\n\t\"github.com/cloudwego/thriftgo/generator/golang\"\n\t\"github.com/cloudwego/thriftgo/generator/golang/styles\"\n)\n\nvar thriftgoUtil *golang.CodeUtils\n\nvar NameStyle = styles.NewNamingStyle(\"thriftgo\")\n"
  },
  {
    "path": "cmd/hz/util/ast.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage util\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/format\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"path/filepath\"\n\n\t\"golang.org/x/tools/go/ast/astutil\"\n)\n\nfunc AddImport(file, alias, impt string) ([]byte, error) {\n\tfset := token.NewFileSet()\n\tpath, _ := filepath.Abs(file)\n\tf, err := parser.ParseFile(fset, path, nil, parser.ParseComments)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"can not parse ast for file: %s, err: %v\", path, err)\n\t}\n\n\treturn addImport(fset, f, alias, impt)\n}\n\nfunc AddImportForContent(fileContent []byte, alias, impt string) ([]byte, error) {\n\tfset := token.NewFileSet()\n\tf, err := parser.ParseFile(fset, \"\", fileContent, parser.ParseComments)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"can not parse ast for file: %s, err: %v\", fileContent, err)\n\t}\n\n\treturn addImport(fset, f, alias, impt)\n}\n\nfunc addImport(fset *token.FileSet, f *ast.File, alias, impt string) ([]byte, error) {\n\tadded := astutil.AddNamedImport(fset, f, alias, impt)\n\tif !added {\n\t\treturn nil, fmt.Errorf(\"can not add import \\\"%s\\\" for file: %s\", impt, f.Name.Name)\n\t}\n\tvar output []byte\n\tbuffer := bytes.NewBuffer(output)\n\terr := format.Node(buffer, fset, f)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"can not add import for file: %s, err: %v\", f.Name.Name, err)\n\t}\n\n\treturn buffer.Bytes(), nil\n}\n"
  },
  {
    "path": "cmd/hz/util/ast_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage util\n\nimport (\n\t\"bytes\"\n\t\"go/format\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"testing\"\n\n\t\"golang.org/x/tools/go/ast/astutil\"\n)\n\nfunc TestAddImport(t *testing.T) {\n\tinserts := [][]string{\n\t\t{\n\t\t\t\"ctx\",\n\t\t\t\"context\",\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\t\"context\",\n\t\t},\n\t}\n\tfiles := [][]string{\n\t\t{\n\t\t\t`package foo\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n`,\n\t\t\t`package foo\n\nimport (\n\tctx \"context\"\n\t\"fmt\"\n\t\"time\"\n)\n`,\n\t\t},\n\t\t{\n\t\t\t`package foo\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n`,\n\t\t\t`package foo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n)\n`,\n\t\t},\n\t}\n\tfor idx, file := range files {\n\t\tfset := token.NewFileSet()\n\t\tf, err := parser.ParseFile(fset, \"\", file[0], parser.ImportsOnly)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"can not parse ast for file\")\n\t\t}\n\t\tastutil.AddNamedImport(fset, f, inserts[idx][0], inserts[idx][1])\n\t\tvar output []byte\n\t\tbuffer := bytes.NewBuffer(output)\n\t\terr = format.Node(buffer, fset, f)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"can add import for file\")\n\t\t}\n\t\tif buffer.String() != file[1] {\n\t\t\tt.Fatalf(\"insert import failed\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/util/data.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage util\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n)\n\nfunc CopyStringSlice(from, to *[]string) {\n\tn := len(*from)\n\tm := len(*to)\n\tif n > m {\n\t\tn = m\n\t}\n\tfor i := 0; i < n; i++ {\n\t\t(*to)[i] = (*from)[i]\n\t}\n\t*to = (*to)[:n]\n}\n\nfunc CopyString2StringMap(from, to map[string]string) {\n\tfor k := range to {\n\t\tdelete(to, k)\n\t}\n\tfor k, v := range from {\n\t\tto[k] = v\n\t}\n}\n\nfunc PackArgs(c interface{}) (res []string, err error) {\n\tt := reflect.TypeOf(c)\n\tv := reflect.ValueOf(c)\n\tif reflect.TypeOf(c).Kind() == reflect.Ptr {\n\t\tt = t.Elem()\n\t\tv = v.Elem()\n\t}\n\tif t.Kind() != reflect.Struct {\n\t\treturn nil, errors.New(\"passed c must be struct or pointer of struct\")\n\t}\n\n\tfor i := 0; i < t.NumField(); i++ {\n\t\tf := t.Field(i)\n\t\tx := v.Field(i)\n\t\tn := f.Name\n\n\t\tif x.IsZero() {\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch x.Kind() {\n\t\tcase reflect.Bool:\n\t\t\tif x.Bool() == false {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres = append(res, n+\"=\"+fmt.Sprint(x.Bool()))\n\t\tcase reflect.String:\n\t\t\tif x.String() == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres = append(res, n+\"=\"+x.String())\n\t\tcase reflect.Slice:\n\t\t\tif x.Len() == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tft := f.Type.Elem()\n\t\t\tif ft.Kind() != reflect.String {\n\t\t\t\treturn nil, fmt.Errorf(\"slice field %v must be '[]string', err: %v\", f.Name, err.Error())\n\t\t\t}\n\t\t\tvar ss []string\n\t\t\tfor i := 0; i < x.Len(); i++ {\n\t\t\t\tss = append(ss, x.Index(i).String())\n\t\t\t}\n\t\t\tres = append(res, n+\"=\"+strings.Join(ss, \";\"))\n\t\tcase reflect.Map:\n\t\t\tif x.Len() == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfk := f.Type.Key()\n\t\t\tif fk.Kind() != reflect.String {\n\t\t\t\treturn nil, fmt.Errorf(\"map field %v must be 'map[string]string', err: %v\", f.Name, err.Error())\n\t\t\t}\n\t\t\tfv := f.Type.Elem()\n\t\t\tif fv.Kind() != reflect.String {\n\t\t\t\treturn nil, fmt.Errorf(\"map field %v must be 'map[string]string', err: %v\", f.Name, err.Error())\n\t\t\t}\n\t\t\tvar sk []string\n\t\t\tit := x.MapRange()\n\t\t\tfor it.Next() {\n\t\t\t\tsk = append(sk, it.Key().String()+\"=\"+it.Value().String())\n\t\t\t}\n\t\t\tres = append(res, n+\"=\"+strings.Join(sk, \";\"))\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unsupported field type: %+v, err: %v\", f, err.Error())\n\t\t}\n\t}\n\treturn res, nil\n}\n\nfunc UnpackArgs(args []string, c interface{}) error {\n\tm, err := MapForm(args)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unmarshal args failed, err: %v\", err.Error())\n\t}\n\n\tt := reflect.TypeOf(c).Elem()\n\tv := reflect.ValueOf(c).Elem()\n\tif t.Kind() != reflect.Struct {\n\t\treturn errors.New(\"passed c must be struct or pointer of struct\")\n\t}\n\n\tfor i := 0; i < t.NumField(); i++ {\n\t\tf := t.Field(i)\n\t\tx := v.Field(i)\n\t\tn := f.Name\n\t\tvalues, ok := m[n]\n\t\tif !ok || len(values) == 0 || values[0] == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tswitch x.Kind() {\n\t\tcase reflect.Bool:\n\t\t\tif len(values) != 1 {\n\t\t\t\treturn fmt.Errorf(\"field %s can't be assigned multi values: %v\", n, values)\n\t\t\t}\n\t\t\tx.SetBool(values[0] == \"true\")\n\t\tcase reflect.String:\n\t\t\tif len(values) != 1 {\n\t\t\t\treturn fmt.Errorf(\"field %s can't be assigned multi values: %v\", n, values)\n\t\t\t}\n\t\t\tx.SetString(values[0])\n\t\tcase reflect.Slice:\n\t\t\tif len(values) != 1 {\n\t\t\t\treturn fmt.Errorf(\"field %s can't be assigned multi values: %v\", n, values)\n\t\t\t}\n\t\t\tss := strings.Split(values[0], \";\")\n\t\t\tif x.Type().Elem().Kind() == reflect.Int {\n\t\t\t\tn := reflect.MakeSlice(x.Type(), len(ss), len(ss))\n\t\t\t\tfor i, s := range ss {\n\t\t\t\t\tval, err := strconv.ParseInt(s, 10, 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tn.Index(i).SetInt(val)\n\t\t\t\t}\n\t\t\t\tx.Set(n)\n\t\t\t} else {\n\t\t\t\tfor _, s := range ss {\n\t\t\t\t\tval := reflect.Append(x, reflect.ValueOf(s))\n\t\t\t\t\tx.Set(val)\n\t\t\t\t}\n\t\t\t}\n\t\tcase reflect.Map:\n\t\t\tif len(values) != 1 {\n\t\t\t\treturn fmt.Errorf(\"field %s can't be assigned multi values: %v\", n, values)\n\t\t\t}\n\t\t\tss := strings.Split(values[0], \";\")\n\t\t\tout := make(map[string]string, len(ss))\n\t\t\tfor _, s := range ss {\n\t\t\t\tsk := strings.SplitN(s, \"=\", 2)\n\t\t\t\tif len(sk) != 2 {\n\t\t\t\t\treturn fmt.Errorf(\"map filed %v invalid key-value pair '%v'\", n, s)\n\t\t\t\t}\n\t\t\t\tout[sk[0]] = sk[1]\n\t\t\t}\n\t\t\tx.Set(reflect.ValueOf(out))\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"field %s has unsupported type %+v\", n, f.Type)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc MapForm(input []string) (map[string][]string, error) {\n\tout := make(map[string][]string, len(input))\n\n\tfor _, str := range input {\n\t\tparts := strings.SplitN(str, \"=\", 2)\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"invalid argument: '%s'\", str)\n\t\t}\n\t\tkey, val := parts[0], parts[1]\n\t\tout[key] = append(out[key], val)\n\t}\n\n\treturn out, nil\n}\n\nfunc GetFirstKV(m map[string][]string) (string, []string) {\n\tfor k, v := range m {\n\t\treturn k, v\n\t}\n\treturn \"\", nil\n}\n\nfunc ToCamelCase(name string) string {\n\treturn CamelString(name)\n}\n\nfunc ToSnakeCase(name string) string {\n\treturn SnakeString(name)\n}\n\n// unifyPath will convert \"\\\" to \"/\" in path if the os is windows\nfunc unifyPath(path string) string {\n\tif IsWindows() {\n\t\tpath = strings.ReplaceAll(path, \"\\\\\", \"/\")\n\t}\n\treturn path\n}\n\n// BaseName get base name for path. ex: \"github.com/p.s.m\" => \"p.s.m\"\nfunc BaseName(include, subFixToTrim string) string {\n\tinclude = unifyPath(include)\n\tsubFixToTrim = unifyPath(subFixToTrim)\n\tlast := include\n\tif id := strings.LastIndex(last, \"/\"); id >= 0 && id < len(last)-1 {\n\t\tlast = last[id+1:]\n\t}\n\tif !strings.HasSuffix(last, subFixToTrim) {\n\t\treturn last\n\t}\n\treturn last[:len(last)-len(subFixToTrim)]\n}\n\nfunc BaseNameAndTrim(include string) string {\n\tinclude = unifyPath(include)\n\tlast := include\n\tif id := strings.LastIndex(last, \"/\"); id >= 0 && id < len(last)-1 {\n\t\tlast = last[id+1:]\n\t}\n\n\tif id := strings.LastIndex(last, \".\"); id != -1 {\n\t\tlast = last[:id]\n\t}\n\treturn last\n}\n\nfunc SplitPackageName(pkg, subFixToTrim string) string {\n\tpkg = unifyPath(pkg)\n\tsubFixToTrim = unifyPath(subFixToTrim)\n\tlast := SplitPackage(pkg, subFixToTrim)\n\tif id := strings.LastIndex(last, \"/\"); id >= 0 && id < len(last)-1 {\n\t\tlast = last[id+1:]\n\t}\n\treturn last\n}\n\nfunc SplitPackage(pkg, subFixToTrim string) string {\n\tpkg = unifyPath(pkg)\n\tsubFixToTrim = unifyPath(subFixToTrim)\n\tlast := strings.TrimSuffix(pkg, subFixToTrim)\n\tif id := strings.LastIndex(last, \"/\"); id >= 0 && id < len(last)-1 {\n\t\tlast = last[id+1:]\n\t}\n\treturn strings.ReplaceAll(last, \".\", \"/\")\n}\n\n// ImportToSanitizedPath converts import path to file system path and replaces dots with underscores in the last component.\n// For example: \"github.com/example/v1.2\" becomes \"github.com/example/v1_2\" on Unix systems.\n// NOTE: no idea about the background, it might caused go import package issues before?\nfunc ImportToSanitizedPath(path string) string {\n\tpath = filepath.FromSlash(path)\n\tif i := strings.LastIndex(path, string(filepath.Separator)); i >= 0 && i < len(path)-1 && strings.Contains(path[i+1:], \".\") {\n\t\tbase := strings.ReplaceAll(path[i+1:], \".\", \"_\")\n\t\tdir := path[:i]\n\t\treturn dir + string(filepath.Separator) + base\n\t}\n\treturn path\n}\n\nfunc ToVarName(paths []string) string {\n\tps := strings.Join(paths, \"__\")\n\tinput := []byte(url.PathEscape(ps))\n\tout := make([]byte, 0, len(input))\n\tfor i := 0; i < len(input); i++ {\n\t\tc := input[i]\n\t\tif c == ':' || c == '*' {\n\t\t\tcontinue\n\t\t}\n\t\tif (c >= '0' && c <= '9' && i != 0) || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_') {\n\t\t\tout = append(out, c)\n\t\t} else {\n\t\t\tout = append(out, '_')\n\t\t}\n\t}\n\n\treturn string(out)\n}\n\nfunc SplitGoTags(input string) []string {\n\tout := make([]string, 0, 4)\n\tns := len(input)\n\n\tflag := false\n\tprev := 0\n\ti := 0\n\tfor i = 0; i < ns; i++ {\n\t\tc := input[i]\n\t\tif c == '\"' {\n\t\t\tflag = !flag\n\t\t}\n\t\tif !flag && c == ' ' {\n\t\t\tif prev < i {\n\t\t\t\tout = append(out, input[prev:i])\n\t\t\t}\n\t\t\tprev = i + 1\n\t\t}\n\t}\n\tif i != 0 && prev < i {\n\t\tout = append(out, input[prev:i])\n\t}\n\n\treturn out\n}\n\nfunc SubPackage(mod, dir string) string {\n\tif dir == \"\" {\n\t\treturn mod\n\t}\n\treturn path.Join(mod, filepath.ToSlash(dir))\n}\n\nfunc SubDir(root, subPkg string) string {\n\treturn filepath.Join(root, filepath.FromSlash(subPkg))\n}\n\nvar (\n\tuniquePackageName        = map[string]bool{}\n\tuniqueMiddlewareName     = map[string]bool{}\n\tuniqueHandlerPackageName = map[string]bool{}\n)\n\n// GetPackageUniqueName can get a non-repeating variable name for package alias\nfunc GetPackageUniqueName(name string) (string, error) {\n\tname, err := getUniqueName(name, uniquePackageName)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"can not generate unique name for package '%s', err: %v\", name, err)\n\t}\n\n\treturn name, nil\n}\n\n// GetMiddlewareUniqueName can get a non-repeating variable name for middleware name\nfunc GetMiddlewareUniqueName(name string) (string, error) {\n\tname, err := getUniqueName(name, uniqueMiddlewareName)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"can not generate routing group for path '%s', err: %v\", name, err)\n\t}\n\n\treturn name, nil\n}\n\nfunc GetHandlerPackageUniqueName(name string) (string, error) {\n\tname, err := getUniqueName(name, uniqueHandlerPackageName)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"can not generate unique handler package name: '%s', err: %v\", name, err)\n\t}\n\n\treturn name, nil\n}\n\n// getUniqueName can get a non-repeating variable name\nfunc getUniqueName(name string, uniqueNameSet map[string]bool) (string, error) {\n\tuniqueName := name\n\tif _, exist := uniqueNameSet[uniqueName]; exist {\n\t\tfor i := 0; i < 10000; i++ {\n\t\t\tuniqueName = uniqueName + fmt.Sprintf(\"%d\", i)\n\t\t\tif _, exist := uniqueNameSet[uniqueName]; !exist {\n\t\t\t\tlogs.Infof(\"There is a package name with the same name, change %s to %s\", name, uniqueName)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tuniqueName = name\n\t\t\tif i == 9999 {\n\t\t\t\treturn \"\", fmt.Errorf(\"there is too many same package for %s\", name)\n\t\t\t}\n\t\t}\n\t}\n\tuniqueNameSet[uniqueName] = true\n\n\treturn uniqueName, nil\n}\n\nvar validFuncReg = regexp.MustCompile(\"[_0-9a-zA-Z]\")\n\n// ToGoFuncName converts a string to a function naming style for go\nfunc ToGoFuncName(s string) string {\n\tss := []byte(s)\n\tfor i := range ss {\n\t\tif !validFuncReg.Match([]byte{s[i]}) {\n\t\t\tss[i] = '_'\n\t\t}\n\t}\n\treturn string(ss)\n}\n"
  },
  {
    "path": "cmd/hz/util/data_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage util\n\nimport \"testing\"\n\nfunc TestUniqueName(t *testing.T) {\n\ttype UniqueName struct {\n\t\tName         string\n\t\tExpectedName string\n\t\tActualName   string\n\t}\n\n\tnameList := []UniqueName{\n\t\t{\n\t\t\tName:         \"aaa\",\n\t\t\tExpectedName: \"aaa\",\n\t\t},\n\t\t{\n\t\t\tName:         \"aaa\",\n\t\t\tExpectedName: \"aaa0\",\n\t\t},\n\t\t{\n\t\t\tName:         \"aaa0\",\n\t\t\tExpectedName: \"aaa00\",\n\t\t},\n\t\t{\n\t\t\tName:         \"aaa0\",\n\t\t\tExpectedName: \"aaa01\",\n\t\t},\n\t\t{\n\t\t\tName:         \"aaa00\",\n\t\t\tExpectedName: \"aaa000\",\n\t\t},\n\t\t{\n\t\t\tName:         \"aaa\",\n\t\t\tExpectedName: \"aaa1\",\n\t\t},\n\t\t{\n\t\t\tName:         \"aaa\",\n\t\t\tExpectedName: \"aaa2\",\n\t\t},\n\t\t{\n\t\t\tName:         \"aaa\",\n\t\t\tExpectedName: \"aaa3\",\n\t\t},\n\t\t{\n\t\t\tName:         \"aaa\",\n\t\t\tExpectedName: \"aaa4\",\n\t\t},\n\t}\n\tfor _, name := range nameList {\n\t\tname.ActualName, _ = getUniqueName(name.Name, uniquePackageName)\n\t\tif name.ActualName != name.ExpectedName {\n\t\t\tt.Errorf(\"%s name expected unique name '%s', actually get '%s'\", name.Name, name.ExpectedName, name.ActualName)\n\t\t}\n\t}\n\tfor _, name := range nameList {\n\t\tname.ActualName, _ = getUniqueName(name.Name, uniqueMiddlewareName)\n\t\tif name.ActualName != name.ExpectedName {\n\t\t\tt.Errorf(\"%s name expected unique name '%s', actually get '%s'\", name.Name, name.ExpectedName, name.ActualName)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/hz/util/env.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage util\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"go/build\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n)\n\nfunc GetGOPATH() (gopath string, err error) {\n\tps := filepath.SplitList(os.Getenv(\"GOPATH\"))\n\tif len(ps) > 0 {\n\t\tgopath = ps[0]\n\t}\n\tif gopath == \"\" {\n\t\tcmd := exec.Command(\"go\", \"env\", \"GOPATH\")\n\t\tvar out bytes.Buffer\n\t\tcmd.Stderr = &out\n\t\tcmd.Stdout = &out\n\t\tif err := cmd.Run(); err == nil {\n\t\t\tgopath = strings.Trim(out.String(), \" \\t\\n\\r\")\n\t\t}\n\t}\n\tif gopath == \"\" {\n\t\tps := GetBuildGoPaths()\n\t\tif len(ps) > 0 {\n\t\t\tgopath = ps[0]\n\t\t}\n\t}\n\tisExist, err := PathExist(gopath)\n\tif !isExist {\n\t\treturn \"\", err\n\t}\n\treturn strings.Replace(gopath, \"/\", string(os.PathSeparator), -1), nil\n}\n\n// GetBuildGoPaths returns the list of Go path directories.\nfunc GetBuildGoPaths() []string {\n\tvar all []string\n\tfor _, p := range filepath.SplitList(build.Default.GOPATH) {\n\t\tif p == \"\" || p == build.Default.GOROOT {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(p, \"~\") {\n\t\t\tcontinue\n\t\t}\n\t\tall = append(all, p)\n\t}\n\tfor k, v := range all {\n\t\tif strings.HasSuffix(v, \"/\") || strings.HasSuffix(v, string(os.PathSeparator)) {\n\t\t\tv = v[:len(v)-1]\n\t\t}\n\t\tall[k] = v\n\t}\n\treturn all\n}\n\nvar goModReg = regexp.MustCompile(`^\\s*module\\s+(\\S+)\\s*`)\n\n// SearchGoMod searches go.mod from the given directory (which must be an absolute path) to\n// the root directory. When the go.mod is found, its module name and path will be returned.\nfunc SearchGoMod(cwd string, recurse bool) (moduleName, path string, found bool) {\n\tfor {\n\t\tpath = filepath.Join(cwd, \"go.mod\")\n\t\tdata, err := ioutil.ReadFile(path)\n\t\tif err == nil {\n\t\t\tfor _, line := range strings.Split(string(data), \"\\n\") {\n\t\t\t\tm := goModReg.FindStringSubmatch(line)\n\t\t\t\tif m != nil {\n\t\t\t\t\treturn m[1], cwd, true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Sprintf(\"<module name not found in '%s'>\", path), path, true\n\t\t}\n\n\t\tif !os.IsNotExist(err) {\n\t\t\treturn\n\t\t}\n\t\tif !recurse {\n\t\t\tbreak\n\t\t}\n\t\tcwd = filepath.Dir(cwd)\n\t\t// the root directory will return itself by using \"filepath.Dir()\"; to prevent dead loops, so jump out\n\t\tif cwd == filepath.Dir(cwd) {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn\n}\n\nfunc InitGoMod(module string) error {\n\tisExist, err := PathExist(\"go.mod\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif isExist {\n\t\treturn nil\n\t}\n\tgg, err := exec.LookPath(\"go\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tcmd := &exec.Cmd{\n\t\tPath:   gg,\n\t\tArgs:   []string{\"go\", \"mod\", \"init\", module},\n\t\tStdin:  os.Stdin,\n\t\tStdout: os.Stdout,\n\t\tStderr: os.Stderr,\n\t}\n\treturn cmd.Run()\n}\n\nfunc IsWindows() bool {\n\treturn meta.SysType == meta.WindowsOS\n}\n"
  },
  {
    "path": "cmd/hz/util/fs.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage util\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc PathExist(path string) (bool, error) {\n\tabPath, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\t_, err = os.Stat(abPath)\n\tif err != nil {\n\t\treturn os.IsExist(err), nil\n\t}\n\treturn true, nil\n}\n\nfunc RelativePath(path string) (string, error) {\n\tpath, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tcwd, err := os.Getwd()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tret, _ := filepath.Rel(cwd, path)\n\treturn ret, nil\n}\n"
  },
  {
    "path": "cmd/hz/util/logs/api.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage logs\n\nfunc init() {\n\tdefaultLogger = NewStdLogger(LevelInfo)\n}\n\nfunc SetLogger(logger Logger) {\n\tdefaultLogger = logger\n}\n\nconst (\n\tLevelDebug = 1 + iota\n\tLevelInfo\n\tLevelWarn\n\tLevelError\n)\n\n// TODO: merge with hertz logger package\ntype Logger interface {\n\tDebugf(format string, v ...interface{})\n\tInfof(format string, v ...interface{})\n\tWarnf(format string, v ...interface{})\n\tErrorf(format string, v ...interface{})\n\tFlush()\n\tSetLevel(level int) error\n}\n\nvar defaultLogger Logger\n\nfunc Errorf(format string, v ...interface{}) {\n\tdefaultLogger.Errorf(format, v...)\n}\n\nfunc Warnf(format string, v ...interface{}) {\n\tdefaultLogger.Warnf(format, v...)\n}\n\nfunc Infof(format string, v ...interface{}) {\n\tdefaultLogger.Infof(format, v...)\n}\n\nfunc Debugf(format string, v ...interface{}) {\n\tdefaultLogger.Debugf(format, v...)\n}\n\nfunc Error(format string, v ...interface{}) {\n\tdefaultLogger.Errorf(format, v...)\n}\n\nfunc Warn(format string, v ...interface{}) {\n\tdefaultLogger.Warnf(format, v...)\n}\n\nfunc Info(format string, v ...interface{}) {\n\tdefaultLogger.Infof(format, v...)\n}\n\nfunc Debug(format string, v ...interface{}) {\n\tdefaultLogger.Debugf(format, v...)\n}\n\nfunc Flush() {\n\tdefaultLogger.Flush()\n}\n\nfunc SetLevel(level int) {\n\tdefaultLogger.SetLevel(level)\n}\n"
  },
  {
    "path": "cmd/hz/util/logs/std.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage logs\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n)\n\ntype StdLogger struct {\n\tlevel      int\n\toutLogger  *log.Logger\n\twarnLogger *log.Logger\n\terrLogger  *log.Logger\n\tout        *bytes.Buffer\n\twarn       *bytes.Buffer\n\terr        *bytes.Buffer\n\tDefer      bool\n\tErrOnly    bool\n}\n\nfunc NewStdLogger(level int) *StdLogger {\n\tout := bytes.NewBuffer(nil)\n\twarn := bytes.NewBuffer(nil)\n\terr := bytes.NewBuffer(nil)\n\treturn &StdLogger{\n\t\tlevel:      level,\n\t\toutLogger:  log.New(out, \"[INFO]\", log.Llongfile),\n\t\twarnLogger: log.New(warn, \"[WARN]\", log.Llongfile),\n\t\terrLogger:  log.New(err, \"[ERROR]\", log.Llongfile),\n\t\tout:        out,\n\t\twarn:       warn,\n\t\terr:        err,\n\t}\n}\n\nfunc (stdLogger *StdLogger) Debugf(format string, v ...interface{}) {\n\tif stdLogger.level > LevelDebug {\n\t\treturn\n\t}\n\tstdLogger.outLogger.Output(3, fmt.Sprintf(format, v...))\n\tif !stdLogger.Defer {\n\t\tstdLogger.FlushOut()\n\t}\n}\n\nfunc (stdLogger *StdLogger) Infof(format string, v ...interface{}) {\n\tif stdLogger.level > LevelInfo {\n\t\treturn\n\t}\n\tstdLogger.outLogger.Output(3, fmt.Sprintf(format, v...))\n\tif !stdLogger.Defer {\n\t\tstdLogger.FlushOut()\n\t}\n}\n\nfunc (stdLogger *StdLogger) Warnf(format string, v ...interface{}) {\n\tif stdLogger.level > LevelWarn {\n\t\treturn\n\t}\n\tstdLogger.warnLogger.Output(3, fmt.Sprintf(format, v...))\n\tif !stdLogger.Defer {\n\t\tstdLogger.FlushErr()\n\t}\n}\n\nfunc (stdLogger *StdLogger) Errorf(format string, v ...interface{}) {\n\tif stdLogger.level > LevelError {\n\t\treturn\n\t}\n\tstdLogger.errLogger.Output(3, fmt.Sprintf(format, v...))\n\tif !stdLogger.Defer {\n\t\tstdLogger.FlushErr()\n\t}\n}\n\nfunc (stdLogger *StdLogger) Flush() {\n\tstdLogger.FlushErr()\n\tif !stdLogger.ErrOnly {\n\t\tstdLogger.FlushOut()\n\t}\n}\n\nfunc (stdLogger *StdLogger) FlushOut() {\n\tos.Stderr.Write(stdLogger.out.Bytes())\n\tstdLogger.out.Reset()\n}\n\nfunc (stdLogger *StdLogger) Err() string {\n\treturn string(stdLogger.err.Bytes())\n}\n\nfunc (stdLogger *StdLogger) Warn() string {\n\treturn string(stdLogger.warn.Bytes())\n}\n\nfunc (stdLogger *StdLogger) FlushErr() {\n\tos.Stderr.Write(stdLogger.err.Bytes())\n\tstdLogger.err.Reset()\n}\n\nfunc (stdLogger *StdLogger) OutLines() []string {\n\tlines := bytes.Split(stdLogger.out.Bytes(), []byte(\"[INFO]\"))\n\tvar rets []string\n\tfor _, line := range lines {\n\t\trets = append(rets, string(line))\n\t}\n\treturn rets\n}\n\nfunc (stdLogger *StdLogger) Out() []byte {\n\treturn stdLogger.out.Bytes()\n}\n\nfunc (stdLogger *StdLogger) SetLevel(level int) error {\n\tswitch level {\n\tcase LevelDebug, LevelInfo, LevelWarn, LevelError:\n\t\tbreak\n\tdefault:\n\t\treturn errors.New(\"invalid log level\")\n\t}\n\tstdLogger.level = level\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/hz/util/string.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage util\n\nimport (\n\t\"reflect\"\n\t\"strings\"\n\t\"unicode/utf8\"\n\t\"unsafe\"\n)\n\nfunc Str2Bytes(in string) (out []byte) {\n\top := (*reflect.SliceHeader)(unsafe.Pointer(&out))\n\tip := (*reflect.StringHeader)(unsafe.Pointer(&in))\n\top.Data = ip.Data\n\top.Cap = ip.Len\n\top.Len = ip.Len\n\treturn\n}\n\nfunc Bytes2Str(in []byte) (out string) {\n\top := (*reflect.StringHeader)(unsafe.Pointer(&out))\n\tip := (*reflect.SliceHeader)(unsafe.Pointer(&in))\n\top.Data = ip.Data\n\top.Len = ip.Len\n\treturn\n}\n\n// TrimLastChar can remove the last char for s\nfunc TrimLastChar(s string) string {\n\tr, size := utf8.DecodeLastRuneInString(s)\n\tif r == utf8.RuneError && (size == 0 || size == 1) {\n\t\tsize = 0\n\t}\n\treturn s[:len(s)-size]\n}\n\n// AddSlashForComments can adjust the format of multi-line comments\nfunc AddSlashForComments(s string) string {\n\ts = strings.Replace(s, \"\\n\", \"\\n//\", -1)\n\treturn s\n}\n\n// CamelString converts the string 's' to a camel string\nfunc CamelString(s string) string {\n\tdata := make([]byte, 0, len(s))\n\tj := false\n\tk := false\n\tnum := len(s) - 1\n\tfor i := 0; i <= num; i++ {\n\t\td := s[i]\n\t\tif k == false && d >= 'A' && d <= 'Z' {\n\t\t\tk = true\n\t\t}\n\t\tif d >= 'a' && d <= 'z' && (j || k == false) {\n\t\t\td = d - 32\n\t\t\tj = false\n\t\t\tk = true\n\t\t}\n\t\tif k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' {\n\t\t\tj = true\n\t\t\tcontinue\n\t\t}\n\t\tdata = append(data, d)\n\t}\n\treturn Bytes2Str(data[:])\n}\n\n// SnakeString converts the string 's' to a snake string\nfunc SnakeString(s string) string {\n\tdata := make([]byte, 0, len(s)*2)\n\tj := false\n\tfor _, d := range Str2Bytes(s) {\n\t\tif d >= 'A' && d <= 'Z' {\n\t\t\tif j {\n\t\t\t\tdata = append(data, '_')\n\t\t\t\tj = false\n\t\t\t}\n\t\t} else if d != '_' {\n\t\t\tj = true\n\t\t}\n\t\tdata = append(data, d)\n\t}\n\treturn strings.ToLower(Bytes2Str(data))\n}\n"
  },
  {
    "path": "cmd/hz/util/tool_install.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage util\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/cmd/hz/meta\"\n\t\"github.com/cloudwego/hertz/cmd/hz/util/logs\"\n\tgv \"github.com/hashicorp/go-version\"\n)\n\nconst ThriftgoMiniVersion = \"v0.2.0\"\n\n// QueryVersion will query the version of the corresponding executable.\nfunc QueryVersion(exe string) (version string, err error) {\n\tvar buf strings.Builder\n\tcmd := &exec.Cmd{\n\t\tPath: exe,\n\t\tArgs: []string{\n\t\t\texe, \"--version\",\n\t\t},\n\t\tStdin:  os.Stdin,\n\t\tStdout: &buf,\n\t\tStderr: &buf,\n\t}\n\terr = cmd.Run()\n\tif err == nil {\n\t\tversion = strings.Split(buf.String(), \" \")[1]\n\t\tif strings.HasSuffix(version, \"\\n\") {\n\t\t\tversion = version[:len(version)-1]\n\t\t}\n\t}\n\treturn\n}\n\n// ShouldUpdate will return \"true\" when current is lower than latest.\nfunc ShouldUpdate(current, latest string) bool {\n\tcv, err := gv.NewVersion(current)\n\tif err != nil {\n\t\treturn false\n\t}\n\tlv, err := gv.NewVersion(latest)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn cv.Compare(lv) < 0\n}\n\n// InstallAndCheckThriftgo will automatically install thriftgo and judge whether it is installed successfully.\nfunc InstallAndCheckThriftgo() error {\n\texe, err := exec.LookPath(\"go\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"can not find tool 'go': %v\", err)\n\t}\n\tvar buf strings.Builder\n\tcmd := &exec.Cmd{\n\t\tPath: exe,\n\t\tArgs: []string{\n\t\t\texe, \"install\", \"github.com/cloudwego/thriftgo@latest\",\n\t\t},\n\t\tStdin:  os.Stdin,\n\t\tStdout: &buf,\n\t\tStderr: &buf,\n\t}\n\n\tdone := make(chan error)\n\tlogs.Infof(\"installing thriftgo automatically\")\n\tgo func() {\n\t\tdone <- cmd.Run()\n\t}()\n\tselect {\n\tcase err = <-done:\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"can not install thriftgo, err: %v. Please install it manual, and make sure the version of thriftgo is greater than v0.2.0\", cmd.Stderr)\n\t\t}\n\tcase <-time.After(time.Second * 30):\n\t\treturn fmt.Errorf(\"install thriftgo time out.Please install it manual, and make sure the version of thriftgo is greater than v0.2.0\")\n\t}\n\n\texist, err := CheckCompiler(meta.TpCompilerThrift)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"check %s exist failed, err: %v\", meta.TpCompilerThrift, err)\n\t}\n\tif !exist {\n\t\treturn fmt.Errorf(\"install thriftgo failed. Please install it manual, and make sure the version of thriftgo is greater than v0.2.0\")\n\t}\n\n\treturn nil\n}\n\n// CheckCompiler will check if the tool exists.\nfunc CheckCompiler(tool string) (bool, error) {\n\tpath, err := exec.LookPath(tool)\n\tif err != nil {\n\t\tgoPath, err := GetGOPATH()\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"get 'GOPATH' failed for find %s : %v\", tool, path)\n\t\t}\n\t\tpath = filepath.Join(goPath, \"bin\", tool)\n\t}\n\n\tisExist, err := PathExist(path)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"can not check %s exist, err: %v\", tool, err)\n\t}\n\tif !isExist {\n\t\treturn false, nil\n\t}\n\n\treturn true, nil\n}\n\n// CheckAndUpdateThriftgo checks the version of thriftgo and updates the tool to the latest version if its version is less than v0.2.0.\nfunc CheckAndUpdateThriftgo() error {\n\tpath, err := exec.LookPath(meta.TpCompilerThrift)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"can not find %s\", meta.TpCompilerThrift)\n\t}\n\tcurVersion, err := QueryVersion(path)\n\tlogs.Infof(\"current thriftgo version is %s\", curVersion)\n\tif ShouldUpdate(curVersion, ThriftgoMiniVersion) {\n\t\tlogs.Infof(\" current thriftgo version is less than v0.2.0, so update thriftgo version\")\n\t\terr = InstallAndCheckThriftgo()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"update thriftgo version failed, err: %v\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/hz/util/tool_install_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage util\n\nimport \"testing\"\n\nfunc TestQueryVersion(t *testing.T) {\n\tlowVersion := \"v0.1.0\"\n\tequalVersion := \"v0.2.0\"\n\thighVersion := \"v0.3.0\"\n\n\tif ShouldUpdate(lowVersion, ThriftgoMiniVersion) {\n\t}\n\n\tif ShouldUpdate(equalVersion, ThriftgoMiniVersion) {\n\t\tt.Fatal(\"should not be updated\")\n\t}\n\n\tif ShouldUpdate(highVersion, ThriftgoMiniVersion) {\n\t\tt.Fatal(\"should not be updated\")\n\t}\n}\n"
  },
  {
    "path": "examples/html_rendering/index.tmpl",
    "content": "<html>\n<h1>\n    {[{ .title }]}\n</h1>\n</html>"
  },
  {
    "path": "examples/html_rendering/main.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/app/server\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc formatAsDate(t time.Time) string {\n\tyear, month, day := t.Date()\n\treturn fmt.Sprintf(\"%d/%02d/%02d\", year, month, day)\n}\n\nfunc main() {\n\t// set interval to 0 means using fs-watching mechanism.\n\th := server.Default(server.WithAutoReloadRender(true, 0))\n\n\th.Delims(\"{[{\", \"}]}\")\n\n\th.SetFuncMap(template.FuncMap{\n\t\t\"formatAsDate\": formatAsDate,\n\t})\n\th.LoadHTMLGlob(\"./examples/html_rendering/*\")\n\n\th.GET(\"/index\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.HTML(consts.StatusOK, \"index.tmpl\", utils.H{\n\t\t\t\"title\": \"Main website\",\n\t\t})\n\t})\n\n\th.GET(\"/raw\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.HTML(consts.StatusOK, \"template.html\", utils.H{\n\t\t\t\"now\": time.Date(2017, 0o7, 0o1, 0, 0, 0, 0, time.UTC),\n\t\t})\n\t})\n\n\th.Spin()\n}\n"
  },
  {
    "path": "examples/html_rendering/template.html",
    "content": "<h1>Date: {[{.now | formatAsDate}]}</h1>"
  },
  {
    "path": "examples/standard/main.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/app/server\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\ntype Test struct {\n\tA string\n\tB string\n}\n\nfunc main() {\n\th := server.Default()\n\th.StaticFS(\"/\", &app.FS{Root: \"./\", GenerateIndexPages: true})\n\n\th.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.JSON(consts.StatusOK, utils.H{\"ping\": \"pong\"})\n\t})\n\n\th.GET(\"/json\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.JSON(consts.StatusOK, &Test{\n\t\t\tA: \"aaa\",\n\t\t\tB: \"bbb\",\n\t\t})\n\t})\n\n\th.GET(\"/redirect\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Redirect(consts.StatusMovedPermanently, []byte(\"http://www.google.com/\"))\n\t})\n\n\tv1 := h.Group(\"/v1\")\n\t{\n\t\tv1.GET(\"/hello/:name\", func(c context.Context, ctx *app.RequestContext) {\n\t\t\tfmt.Fprintf(ctx, \"Hi %s, this is the response from Hertz.\\n\", ctx.Param(\"name\"))\n\t\t})\n\t}\n\n\th.Spin()\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/cloudwego/hertz\n\ngo 1.19\n\nrequire (\n\tgithub.com/bytedance/gopkg v0.1.3\n\tgithub.com/bytedance/sonic v1.15.0\n\tgithub.com/cloudwego/gopkg v0.1.11-0.20260303065100-1e5551ecf390\n\tgithub.com/cloudwego/netpoll v0.7.3-0.20260305035010-81277e4f7b67\n\tgithub.com/fsnotify/fsnotify v1.5.4\n\tgithub.com/stretchr/testify v1.10.0\n\tgithub.com/tidwall/gjson v1.14.4\n\tgolang.org/x/sync v0.8.0\n\tgolang.org/x/sys v0.24.0\n\tgoogle.golang.org/protobuf v1.34.1\n)\n\nrequire (\n\tgithub.com/bytedance/sonic/loader v0.5.0 // indirect\n\tgithub.com/cloudwego/base64x v0.1.6 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.9 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/tidwall/match v1.1.1 // indirect\n\tgithub.com/tidwall/pretty v1.2.0 // indirect\n\tgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirect\n\tgolang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/bytedance/gopkg v0.1.1/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=\ngithub.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=\ngithub.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=\ngithub.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=\ngithub.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=\ngithub.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=\ngithub.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=\ngithub.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=\ngithub.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=\ngithub.com/cloudwego/gopkg v0.1.4/go.mod h1:FQuXsRWRsSqJLsMVd5SYzp8/Z1y5gXKnVvRrWUOsCMI=\ngithub.com/cloudwego/gopkg v0.1.11-0.20260303065100-1e5551ecf390 h1:DERt3Cue/q307RWCd+pPXvzuqmujrrzORgShkeU4Q0s=\ngithub.com/cloudwego/gopkg v0.1.11-0.20260303065100-1e5551ecf390/go.mod h1:wQv2rXOgrRCYdIrOce+xnAF7MA30CkofQZ3JHZOXY+8=\ngithub.com/cloudwego/netpoll v0.7.3-0.20260305035010-81277e4f7b67 h1:0dwPCnAMoeEupEKCAR4paadnmaq39MVdxvmEVlwcu3g=\ngithub.com/cloudwego/netpoll v0.7.3-0.20260305035010-81277e4f7b67/go.mod h1:PI+YrmyS7cIr0+SD4seJz3Eo3ckkXdu2ZVKBLhURLNU=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=\ngithub.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=\ngithub.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=\ngithub.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=\ngithub.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=\ngithub.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=\ngithub.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngolang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=\ngolang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=\ngolang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=\ngolang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngoogle.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=\ngoogle.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "internal/bytesconv/bytesconv.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytesconv\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\nconst (\n\tupperhex = \"0123456789ABCDEF\"\n\tlowerhex = \"0123456789abcdef\"\n)\n\nfunc LowercaseBytes(b []byte) {\n\tfor i := 0; i < len(b); i++ {\n\t\tp := &b[i]\n\t\t*p = ToLowerTable[*p]\n\t}\n}\n\n// B2s converts byte slice to a string without memory allocation.\n// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .\n//\n// Note it may break if string and/or slice header will change\n// in the future go versions.\nfunc B2s(b []byte) string {\n\treturn *(*string)(unsafe.Pointer(&b))\n}\n\ntype sliceHeader struct {\n\tData unsafe.Pointer\n\tLen  int\n\tCap  int\n}\n\n// S2b converts string to a byte slice without memory allocation.\n//\n// Note it may break if string and/or slice header will change\n// in the future go versions.\nfunc S2b(s string) (b []byte) {\n\t*(*string)(unsafe.Pointer(&b)) = s\n\t(*sliceHeader)(unsafe.Pointer(&b)).Cap = len(s)\n\treturn\n}\n\nfunc EncodedIntHexLen(n uint64) int {\n\tif n == 0 {\n\t\treturn 1\n\t}\n\ti := 0\n\tfor n > 0 {\n\t\ti++\n\t\tn >>= 4\n\t}\n\treturn i\n}\n\nfunc AppendIntHex(b []byte, n uint64) []byte {\n\tif n == 0 {\n\t\treturn append(b, '0')\n\t}\n\tvar tmp [16]byte // 64 / 4 = 16\n\ti := len(tmp)\n\tfor n > 0 {\n\t\ti--\n\t\ttmp[i] = lowerhex[n&0xf]\n\t\tn >>= 4\n\t}\n\treturn append(b, tmp[i:]...)\n}\n\nfunc ReadHexInt(r network.Reader) (int, error) {\n\tn := 0\n\ti := 0\n\tvar k int\n\tfor {\n\t\tbuf, err := r.Peek(1)\n\t\tif err != nil {\n\t\t\tr.Skip(1)\n\n\t\t\tif i > 0 {\n\t\t\t\treturn n, nil\n\t\t\t}\n\t\t\treturn -1, err\n\t\t}\n\n\t\tc := buf[0]\n\t\tk = int(Hex2intTable[c])\n\t\tif k == 16 {\n\t\t\tif i == 0 {\n\t\t\t\tr.Skip(1)\n\t\t\t\treturn -1, errEmptyHexNum\n\t\t\t}\n\t\t\treturn n, nil\n\t\t}\n\t\tif i >= maxHexIntChars {\n\t\t\tr.Skip(1)\n\t\t\treturn -1, errTooLargeHexNum\n\t\t}\n\n\t\tr.Skip(1)\n\t\tn = (n << 4) | k\n\t\ti++\n\t}\n}\n\nfunc ParseUintBuf(b []byte) (int, int, error) {\n\tn := len(b)\n\tif n == 0 {\n\t\treturn -1, 0, errEmptyInt\n\t}\n\tv := 0\n\tfor i := 0; i < n; i++ {\n\t\tc := b[i]\n\t\tk := c - '0'\n\t\tif k > 9 {\n\t\t\tif i == 0 {\n\t\t\t\treturn -1, i, errUnexpectedFirstChar\n\t\t\t}\n\t\t\treturn v, i, nil\n\t\t}\n\t\tvNew := 10*v + int(k)\n\t\t// Test for overflow.\n\t\tif vNew < v {\n\t\t\treturn -1, i, errTooLongInt\n\t\t}\n\t\tv = vNew\n\t}\n\treturn v, n, nil\n}\n\n// AppendUint appends n to dst and returns the extended dst.\nfunc AppendUint(dst []byte, n int) []byte {\n\tif n < 0 {\n\t\tpanic(\"BUG: int must be positive\")\n\t}\n\n\tvar b [20]byte\n\tbuf := b[:]\n\ti := len(buf)\n\tvar q int\n\tfor n >= 10 {\n\t\ti--\n\t\tq = n / 10\n\t\tbuf[i] = '0' + byte(n-q*10)\n\t\tn = q\n\t}\n\ti--\n\tbuf[i] = '0' + byte(n)\n\n\tdst = append(dst, buf[i:]...)\n\treturn dst\n}\n\n// AppendHTTPDate appends HTTP-compliant representation of date\n// to dst and returns the extended dst.\nfunc AppendHTTPDate(dst []byte, date time.Time) []byte {\n\treturn date.UTC().AppendFormat(dst, http.TimeFormat)\n}\n\nfunc AppendQuotedPath(dst, src []byte) []byte {\n\t// Fix issue in https://github.com/golang/go/issues/11202\n\tif len(src) == 1 && src[0] == '*' {\n\t\treturn append(dst, '*')\n\t}\n\n\tfor _, c := range src {\n\t\tif QuotedPathShouldEscapeTable[int(c)] != 0 {\n\t\t\tdst = append(dst, '%', upperhex[c>>4], upperhex[c&15])\n\t\t} else {\n\t\t\tdst = append(dst, c)\n\t\t}\n\t}\n\treturn dst\n}\n\n// AppendQuotedArg appends url-encoded src to dst and returns appended dst.\nfunc AppendQuotedArg(dst, src []byte) []byte {\n\tfor _, c := range src {\n\t\tswitch {\n\t\tcase c == ' ':\n\t\t\tdst = append(dst, '+')\n\t\tcase QuotedArgShouldEscapeTable[int(c)] != 0:\n\t\t\tdst = append(dst, '%', upperhex[c>>4], upperhex[c&0xf])\n\t\tdefault:\n\t\t\tdst = append(dst, c)\n\t\t}\n\t}\n\treturn dst\n}\n\n// ParseHTTPDate parses HTTP-compliant (RFC1123) date.\nfunc ParseHTTPDate(date []byte) (time.Time, error) {\n\treturn time.Parse(time.RFC1123, B2s(date))\n}\n\n// ParseUint parses uint from buf.\nfunc ParseUint(buf []byte) (int, error) {\n\tv, n, err := ParseUintBuf(buf)\n\tif n != len(buf) {\n\t\treturn -1, errUnexpectedTrailingChar\n\t}\n\treturn v, err\n}\n"
  },
  {
    "path": "internal/bytesconv/bytesconv_32.go",
    "content": "//go:build !amd64 && !arm64 && !ppc64\n\n/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytesconv\n\nconst (\n\tmaxHexIntChars = 7\n)\n"
  },
  {
    "path": "internal/bytesconv/bytesconv_32_test.go",
    "content": "//go:build !amd64 && !arm64 && !ppc64\n\n/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytesconv\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestReadHexInt(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, v := range []struct {\n\t\ts string\n\t\tn int\n\t}{\n\t\t//errTooLargeHexNum \"too large hex number\"\n\t\t//{\"0123456789abcdef\", -1},\n\t\t{\"0\", 0},\n\t\t{\"fF\", 0xff},\n\t\t{\"00abc\", 0xabc},\n\t\t{\"7fffffff\", 0x7fffffff},\n\t\t{\"000\", 0},\n\t\t{\"1234ZZZ\", 0x1234},\n\t} {\n\t\ttestReadHexInt(t, v.s, v.n)\n\t}\n}\n\nfunc TestParseUint(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, v := range []struct {\n\t\ts string\n\t\ti int\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"123\", 123},\n\t\t{\"123456789\", 123456789},\n\t\t{\"2147483647\", 2147483647},\n\t} {\n\t\tn, err := ParseUint(S2b(v.s))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v. s=%q n=%v\", err, v.s, n)\n\t\t}\n\t\tassert.DeepEqual(t, n, v.i)\n\t}\n}\n\nfunc TestParseUintError(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, v := range []struct {\n\t\ts string\n\t}{\n\t\t{\"\"},\n\t\t{\"cloudwego123\"},\n\t\t{\"1234.545\"},\n\t\t{\"-2147483648\"},\n\t\t{\"2147483648\"},\n\t\t{\"4294967295\"},\n\t} {\n\t\tn, err := ParseUint(S2b(v.s))\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error when parsing %q. obtained %d\", v.s, n)\n\t\t}\n\t\tif n >= 0 {\n\t\t\tt.Fatalf(\"Unexpected n=%d when parsing %q. Expected negative num\", n, v.s)\n\t\t}\n\t}\n}\n\nfunc TestAppendUint(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, s := range []struct {\n\t\tn int\n\t}{\n\t\t{0},\n\t\t{123},\n\t\t{0x7fffffff},\n\t} {\n\t\texpectedS := fmt.Sprintf(\"%d\", s.n)\n\t\ts := AppendUint(nil, s.n)\n\t\tassert.DeepEqual(t, expectedS, B2s(s))\n\t}\n}\n"
  },
  {
    "path": "internal/bytesconv/bytesconv_64.go",
    "content": "//go:build amd64 || arm64 || ppc64\n\n/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytesconv\n\nconst (\n\tmaxHexIntChars = 15\n)\n"
  },
  {
    "path": "internal/bytesconv/bytesconv_64_test.go",
    "content": "//go:build amd64 || arm64 || ppc64\n\n/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytesconv\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestReadHexInt(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, v := range []struct {\n\t\ts string\n\t\tn int\n\t}{\n\t\t//errTooLargeHexNum \"too large hex number\"\n\t\t//{\"0123456789abcdef\", -1},\n\t\t{\"0\", 0},\n\t\t{\"fF\", 0xff},\n\t\t{\"00abc\", 0xabc},\n\t\t{\"7fffffff\", 0x7fffffff},\n\t\t{\"000\", 0},\n\t\t{\"1234ZZZ\", 0x1234},\n\t\t{\"7ffffffffffffff\", 0x7ffffffffffffff},\n\t} {\n\t\ttestReadHexInt(t, v.s, v.n)\n\t}\n}\n\nfunc TestParseUint(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, v := range []struct {\n\t\ts string\n\t\ti int\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"123\", 123},\n\t\t{\"1234567890\", 1234567890},\n\t\t{\"123456789012345678\", 123456789012345678},\n\t\t{\"9223372036854775807\", 9223372036854775807},\n\t} {\n\t\tn, err := ParseUint(S2b(v.s))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v. s=%q n=%v\", err, v.s, n)\n\t\t}\n\t\tassert.DeepEqual(t, n, v.i)\n\t}\n}\n\nfunc TestParseUintError(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, v := range []struct {\n\t\ts string\n\t}{\n\t\t{\"\"},\n\t\t{\"cloudwego123\"},\n\t\t{\"1234.545\"},\n\t\t{\"-9223372036854775808\"},\n\t\t{\"9223372036854775808\"},\n\t\t{\"18446744073709551615\"},\n\t} {\n\t\tn, err := ParseUint(S2b(v.s))\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error when parsing %q. obtained %d\", v.s, n)\n\t\t}\n\t\tif n >= 0 {\n\t\t\tt.Fatalf(\"Unexpected n=%d when parsing %q. Expected negative num\", n, v.s)\n\t\t}\n\t}\n}\n\nfunc TestAppendUint(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, s := range []struct {\n\t\tn int\n\t}{\n\t\t{0},\n\t\t{123},\n\t\t{0x7fffffffffffffff},\n\t} {\n\t\texpectedS := fmt.Sprintf(\"%d\", s.n)\n\t\ts := AppendUint(nil, s.n)\n\t\tassert.DeepEqual(t, expectedS, B2s(s))\n\t}\n}\n"
  },
  {
    "path": "internal/bytesconv/bytesconv_table.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytesconv\n\n// Code generated by go run bytesconv_table_gen.go; DO NOT EDIT.\n// See bytesconv_table_gen.go for more information about these tables.\n\nconst (\n\tHex2intTable                = \"\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\a\\b\\t\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\n\\v\\f\\r\\x0e\\x0f\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\n\\v\\f\\r\\x0e\\x0f\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\\x10\"\n\tToLowerTable                = \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\a\\b\\t\\n\\v\\f\\r\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f !\\\"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\"\n\tToUpperTable                = \"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\a\\b\\t\\n\\v\\f\\r\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\"\n\tQuotedArgShouldEscapeTable  = \"\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x01\\x01\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x01\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\"\n\tQuotedPathShouldEscapeTable = \"\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x01\\x00\\x01\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x01\\x01\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x01\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\"\n\tValidCookieValueTable       = \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n\tValidHeaderFieldValueTable  = \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\"\n\tValidHeaderFieldNameTable   = \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x01\\x01\\x00\\x01\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n)\n"
  },
  {
    "path": "internal/bytesconv/bytesconv_table_gen.go",
    "content": "//go:build ignore\n\n/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"go/format\"\n\t\"io/ioutil\"\n\t\"log\"\n)\n\nconst (\n\ttoLower = 'a' - 'A'\n)\n\nfunc main() {\n\thex2intTable := func() [256]byte {\n\t\tvar b [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\tc := byte(16)\n\t\t\tif i >= '0' && i <= '9' {\n\t\t\t\tc = byte(i) - '0'\n\t\t\t} else if i >= 'a' && i <= 'f' {\n\t\t\t\tc = byte(i) - 'a' + 10\n\t\t\t} else if i >= 'A' && i <= 'F' {\n\t\t\t\tc = byte(i) - 'A' + 10\n\t\t\t}\n\t\t\tb[i] = c\n\t\t}\n\t\treturn b\n\t}()\n\n\ttoLowerTable := func() [256]byte {\n\t\tvar a [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\tc := byte(i)\n\t\t\tif c >= 'A' && c <= 'Z' {\n\t\t\t\tc += toLower\n\t\t\t}\n\t\t\ta[i] = c\n\t\t}\n\t\treturn a\n\t}()\n\n\ttoUpperTable := func() [256]byte {\n\t\tvar a [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\tc := byte(i)\n\t\t\tif c >= 'a' && c <= 'z' {\n\t\t\t\tc -= toLower\n\t\t\t}\n\t\t\ta[i] = c\n\t\t}\n\t\treturn a\n\t}()\n\n\tquotedArgShouldEscapeTable := func() [256]byte {\n\t\t// According to RFC 3986 §2.3\n\t\tvar a [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\ta[i] = 1\n\t\t}\n\n\t\t// ALPHA\n\t\tfor i := int('a'); i <= int('z'); i++ {\n\t\t\ta[i] = 0\n\t\t}\n\t\tfor i := int('A'); i <= int('Z'); i++ {\n\t\t\ta[i] = 0\n\t\t}\n\n\t\t// DIGIT\n\t\tfor i := int('0'); i <= int('9'); i++ {\n\t\t\ta[i] = 0\n\t\t}\n\n\t\t// Unreserved characters\n\t\tfor _, v := range `-_.~` {\n\t\t\ta[v] = 0\n\t\t}\n\n\t\treturn a\n\t}()\n\n\tquotedPathShouldEscapeTable := func() [256]byte {\n\t\t// The implementation here equal to net/url shouldEscape(s, encodePath)\n\t\t//\n\t\t// The RFC allows : @ & = + $ but saves / ; , for assigning\n\t\t// meaning to individual path segments. This package\n\t\t// only manipulates the path as a whole, so we allow those\n\t\t// last three as well. That leaves only ? to escape.\n\t\ta := quotedArgShouldEscapeTable\n\n\t\tfor _, v := range `$&+,/:;=@` {\n\t\t\ta[v] = 0\n\t\t}\n\n\t\treturn a\n\t}()\n\n\tvalidCookieValueTable := func() [256]byte {\n\t\t// The implementation here is equal to net/http validCookieValueByte(b byte)\n\t\t// see https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1\n\t\tvar a [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\ta[i] = 0\n\t\t}\n\n\t\tfor i := 0x20; i < 0x7f; i++ {\n\t\t\ta[i] = 1\n\t\t}\n\n\t\ta['\"'] = 0\n\t\ta[';'] = 0\n\t\ta['\\\\'] = 0\n\n\t\treturn a\n\t}()\n\n\tvalidHeaderFieldValueTable := func() [256]byte {\n\t\t// The implementation here is equal to httpguts.ValidHeaderFieldValue\n\t\tvar a [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\ta[i] = 1\n\t\t}\n\n\t\tfor i := 0; i < ' '; i++ {\n\t\t\ta[i] = 0\n\t\t}\n\n\t\t// del CTL\n\t\ta[0x7f] = 0\n\t\t// tab\n\t\ta['\\t'] = 1\n\n\t\treturn a\n\t}()\n\n\tvalidHeaderFieldNameTable := func() [256]byte {\n\t\t// The implementation here is equal to httpguts ValidHeaderFieldName(string)\n\t\t// see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2\n\t\t//\n\t\t//  RFC 7230 says:\n\t\t//   header-field   = field-name \":\" OWS field-value OWS\n\t\t//   field-name     = token\n\t\t//   token          = 1*tchar\n\t\t//   tchar = \"!\" / \"#\" / \"$\" / \"%\" / \"&\" / \"'\" / \"*\" / \"+\" / \"-\" / \".\" /\n\t\t//           \"^\" / \"_\" / \"`\" / \"|\" / \"~\" / DIGIT / ALPHA\n\t\tvar a [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\ta[i] = 0\n\t\t}\n\n\t\ta['!'] = 1\n\t\ta['#'] = 1\n\t\ta['$'] = 1\n\t\ta['%'] = 1\n\t\ta['&'] = 1\n\t\ta['\\''] = 1\n\t\ta['*'] = 1\n\t\ta['+'] = 1\n\t\ta['-'] = 1\n\t\ta['.'] = 1\n\t\ta['^'] = 1\n\t\ta['_'] = 1\n\t\ta['`'] = 1\n\t\ta['|'] = 1\n\t\ta['~'] = 1\n\n\t\t// ALPHA\n\t\tfor i := int('a'); i <= int('z'); i++ {\n\t\t\ta[i] = 1\n\t\t}\n\t\tfor i := int('A'); i <= int('Z'); i++ {\n\t\t\ta[i] = 1\n\t\t}\n\n\t\t// DIGIT\n\t\tfor i := int('0'); i <= int('9'); i++ {\n\t\t\ta[i] = 1\n\t\t}\n\n\t\treturn a\n\t}()\n\n\tw := new(bytes.Buffer)\n\tw.WriteString(pre)\n\tfmt.Fprintf(w, \"const (\\n\")\n\tfmt.Fprintf(w, \"\\tHex2intTable = %q\\n\", hex2intTable)\n\tfmt.Fprintf(w, \"\\tToLowerTable = %q\\n\", toLowerTable)\n\tfmt.Fprintf(w, \"\\tToUpperTable = %q\\n\", toUpperTable)\n\tfmt.Fprintf(w, \"\\tQuotedArgShouldEscapeTable = %q\\n\", quotedArgShouldEscapeTable)\n\tfmt.Fprintf(w, \"\\tQuotedPathShouldEscapeTable = %q\\n\", quotedPathShouldEscapeTable)\n\tfmt.Fprintf(w, \"\\tValidCookieValueTable = %q\\n\", validCookieValueTable)\n\tfmt.Fprintf(w, \"\\tValidHeaderFieldValueTable = %q\\n\", validHeaderFieldValueTable)\n\tfmt.Fprintf(w, \"\\tValidHeaderFieldNameTable = %q\\n\", validHeaderFieldNameTable)\n\tfmt.Fprintf(w, \")\\n\")\n\n\tsource, err := format.Source(w.Bytes())\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := ioutil.WriteFile(\"bytesconv_table.go\", source, 0o660); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nconst pre = `/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytesconv\n\n// Code generated by go run bytesconv_table_gen.go; DO NOT EDIT.\n// See bytesconv_table_gen.go for more information about these tables.\n\n`\n"
  },
  {
    "path": "internal/bytesconv/bytesconv_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage bytesconv\n\nimport (\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n)\n\nfunc TestAppendDate(t *testing.T) {\n\tt.Parallel()\n\t// GMT+8\n\tshanghaiTimeZone := time.FixedZone(\"Asia/Shanghai\", 8*60*60)\n\n\tfor _, c := range []struct {\n\t\tname    string\n\t\tdate    time.Time\n\t\tdateStr string\n\t}{\n\t\t{\n\t\t\tname:    \"UTC\",\n\t\t\tdate:    time.Date(2022, 6, 15, 11, 12, 13, 123, time.UTC),\n\t\t\tdateStr: \"Wed, 15 Jun 2022 11:12:13 GMT\",\n\t\t},\n\t\t{\n\t\t\tname:    \"Asia/Shanghai\",\n\t\t\tdate:    time.Date(2022, 6, 15, 3, 12, 45, 999, shanghaiTimeZone),\n\t\t\tdateStr: \"Tue, 14 Jun 2022 19:12:45 GMT\",\n\t\t},\n\t} {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\ts := AppendHTTPDate(nil, c.date)\n\t\t\tassert.DeepEqual(t, c.dateStr, B2s(s))\n\t\t})\n\t}\n}\n\nfunc TestLowercaseBytes(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, v := range []struct {\n\t\tb1, b2 []byte\n\t}{\n\t\t{[]byte(\"CLOUDWEGO-HERTZ\"), []byte(\"cloudwego-hertz\")},\n\t\t{[]byte(\"CLOUDWEGO\"), []byte(\"cloudwego\")},\n\t\t{[]byte(\"HERTZ\"), []byte(\"hertz\")},\n\t} {\n\t\tLowercaseBytes(v.b1)\n\t\tassert.DeepEqual(t, v.b2, v.b1)\n\t}\n}\n\n// The test converts byte slice to a string without memory allocation.\nfunc TestB2s(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, v := range []struct {\n\t\ts string\n\t\tb []byte\n\t}{\n\t\t{\"cloudwego-hertz\", []byte(\"cloudwego-hertz\")},\n\t\t{\"cloudwego\", []byte(\"cloudwego\")},\n\t\t{\"hertz\", []byte(\"hertz\")},\n\t} {\n\t\tassert.DeepEqual(t, v.s, B2s(v.b))\n\t}\n}\n\n// The test converts string to a byte slice without memory allocation.\nfunc TestS2b(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, v := range []struct {\n\t\ts string\n\t\tb []byte\n\t}{\n\t\t{\"cloudwego-hertz\", []byte(\"cloudwego-hertz\")},\n\t\t{\"cloudwego\", []byte(\"cloudwego\")},\n\t\t{\"hertz\", []byte(\"hertz\")},\n\t} {\n\t\tassert.DeepEqual(t, S2b(v.s), v.b)\n\t}\n}\n\nfunc TestAppendIntHex(t *testing.T) {\n\ttestCases := []struct {\n\t\tb        []byte\n\t\tn        uint64\n\t\texpected string\n\t}{\n\t\t{[]byte{}, 0, \"0\"},\n\t\t{[]byte{}, 1, \"1\"},\n\t\t{[]byte{}, 10, \"a\"},\n\t\t{[]byte{}, 15, \"f\"},\n\t\t{[]byte{}, 16, \"10\"},\n\t\t{[]byte{}, 255, \"ff\"},\n\t\t{[]byte{}, 256, \"100\"},\n\t\t{[]byte{}, 123456789, \"75bcd15\"},\n\t\t{[]byte{}, 0xffffffffffffffff, \"ffffffffffffffff\"},\n\t\t{[]byte(\"pre-\"), 255, \"pre-ff\"},\n\t\t{[]byte(\"start\"), 0, \"start0\"},\n\t}\n\tfor _, tc := range testCases {\n\t\tresult := AppendIntHex(tc.b, tc.n)\n\t\tif string(result) != tc.expected {\n\t\t\tt.Fatalf(\"AppendIntHex(%q, %d) = %q; want %q\", tc.b, tc.n, result, tc.expected)\n\t\t}\n\n\t\tactualLen := EncodedIntHexLen(tc.n)\n\t\texpectedLen := len(result) - len(tc.b)\n\t\tif actualLen != expectedLen {\n\t\t\tt.Fatalf(\"EncodedIntHexLen(%d) = %d; want %d\", tc.n, actualLen, expectedLen)\n\t\t}\n\t}\n}\n\n// common test function for 32bit and 64bit\nfunc testReadHexInt(t *testing.T, s string, expectedN int) {\n\tzr := mock.NewZeroCopyReader(s)\n\tn, err := ReadHexInt(zr)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %v. s=%q\", err, s)\n\t}\n\tassert.DeepEqual(t, n, expectedN)\n}\n\nfunc TestAppendQuotedPath(t *testing.T) {\n\tt.Parallel()\n\n\t// Test all characters\n\tpathSegment := make([]byte, 256)\n\tfor i := 0; i < 256; i++ {\n\t\tpathSegment[i] = byte(i)\n\t}\n\tfor _, s := range []struct {\n\t\tpath string\n\t}{\n\t\t{\"/\"},\n\t\t{\"//\"},\n\t\t{\"/foo/bar\"},\n\t\t{\"*\"},\n\t\t{\"/foo/\" + B2s(pathSegment)},\n\t} {\n\t\tu := url.URL{Path: s.path}\n\t\texpectedS := u.EscapedPath()\n\t\tres := B2s(AppendQuotedPath(nil, S2b(s.path)))\n\t\tassert.DeepEqual(t, expectedS, res)\n\t}\n}\n\nfunc TestAppendQuotedArg(t *testing.T) {\n\tt.Parallel()\n\n\t// Sync with url.QueryEscape\n\tallcases := make([]byte, 256)\n\tfor i := 0; i < 256; i++ {\n\t\tallcases[i] = byte(i)\n\t}\n\tres := B2s(AppendQuotedArg(nil, allcases))\n\texpect := url.QueryEscape(B2s(allcases))\n\tassert.DeepEqual(t, expect, res)\n}\n\nfunc TestParseHTTPDate(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, v := range []struct {\n\t\tt string\n\t}{\n\t\t{\"Thu, 04 Feb 2010 21:00:57 PST\"},\n\t\t{\"Mon, 02 Jan 2006 15:04:05 MST\"},\n\t} {\n\t\tt1, err := time.Parse(time.RFC1123, v.t)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v. t=%q\", err, v.t)\n\t\t}\n\t\tt2, err := ParseHTTPDate(S2b(t1.Format(time.RFC1123)))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v. t=%q\", err, v.t)\n\t\t}\n\t\tassert.DeepEqual(t, t1, t2)\n\t}\n}\n\n// For test only, but it will import golang.org/x/net/http.\n// So comment out all this code. Keep this for the full context.\n//func TestValidHeaderFieldValueTable(t *testing.T) {\n//\tt.Parallel()\n//\n//\t// Test all characters\n//\tallBytes := make([]byte, 0)\n//\tfor i := 0; i < 256; i++ {\n//\t\tallBytes = append(allBytes, byte(i))\n//\t}\n//\tfor _, s := range allBytes {\n//\t\tss := []byte{s}\n//\t\texpectedS := httpguts.ValidHeaderFieldValue(string(ss))\n//\t\tres := func() bool {\n//\t\t\treturn ValidHeaderFieldValueTable[s] != 0\n//\t\t}()\n//\n//\t\tassert.DeepEqual(t, expectedS, res)\n//\t}\n//}\n\n// For test only, but it will import golang.org/x/net/http.\n// So comment out all this code. Keep this for the full context.\n//func TestValidHeaderFieldNameTable(t *testing.T) {\n//\tt.Parallel()\n//\n//\t// Test all characters\n//\tallBytes := make([]byte, 0)\n//\tfor i := 0; i < 256; i++ {\n//\t\tallBytes = append(allBytes, byte(i))\n//\t}\n//\tfor _, s := range allBytes {\n//\t\tss := []byte{s}\n//\t\texpectedS := httpguts.ValidHeaderFieldName(string(ss))\n//\t\tres := func() bool {\n//\t\t\treturn ValidHeaderFieldNameTable[s] != 0\n//\t\t}()\n//\n//\t\tassert.DeepEqual(t, expectedS, res)\n//\t}\n//}\n"
  },
  {
    "path": "internal/bytesconv/bytesconv_timing_test.go",
    "content": "/*\n * Copyright 2024 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage bytesconv\n\nimport (\n\t\"testing\"\n)\n\n// For test only, but it will import golang.org/x/net/http.\n// So comment out all this code. Keep this for the full context.\n//func BenchmarkValidHeaderFiledValueTable(b *testing.B) {\n//\t// Test all characters\n//\tallBytes := make([]string, 0)\n//\tfor i := 0; i < 256; i++ {\n//\t\tallBytes = append(allBytes, string([]byte{byte(i)}))\n//\t}\n//\n//\tfor i := 0; i < b.N; i++ {\n//\t\tfor _, s := range allBytes {\n//\t\t\t_ = httpguts.ValidHeaderFieldValue(s)\n//\t\t}\n//\t}\n//}\n\nfunc BenchmarkValidHeaderFiledValueTableHertz(b *testing.B) {\n\t// Test all characters\n\tallBytes := make([]byte, 0)\n\tfor i := 0; i < 256; i++ {\n\t\tallBytes = append(allBytes, byte(i))\n\t}\n\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, s := range allBytes {\n\t\t\t_ = func() bool {\n\t\t\t\treturn ValidHeaderFieldValueTable[s] != 0\n\t\t\t}()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/bytesconv/doc.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The files in bytesconv package are forked from fasthttp[github.com/valyala/fasthttp],\n// and we keep the original Copyright[Copyright 2015 fasthttp authors] and License of fasthttp for those files.\n// We also need to modify as we need, the modifications are Copyright of 2022 CloudWeGo Authors.\n// Thanks for fasthttp authors! Below is the source code information:\n// \t\tRepo: github.com/valyala/fasthttp\n//\t\tForked Version: v1.36.0\n\npackage bytesconv\n"
  },
  {
    "path": "internal/bytesconv/errors.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytesconv\n\nimport \"errors\"\n\nvar (\n\terrEmptyInt               = errors.New(\"empty integer\")\n\terrUnexpectedFirstChar    = errors.New(\"unexpected first char found. Expecting 0-9\")\n\terrUnexpectedTrailingChar = errors.New(\"unexpected trailing char found. Expecting 0-9\")\n\terrTooLongInt             = errors.New(\"too long int\")\n\terrEmptyHexNum            = errors.New(\"empty hex number\")\n\terrTooLargeHexNum         = errors.New(\"too large hex number\")\n)\n"
  },
  {
    "path": "internal/bytestr/bytes.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package bytestr defines some common bytes\npackage bytestr\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nvar (\n\tDefaultServerName  = []byte(\"hertz\")\n\tDefaultUserAgent   = []byte(\"hertz\")\n\tDefaultContentType = []byte(\"text/plain; charset=utf-8\")\n)\n\nvar (\n\tStrBackSlash        = []byte(\"\\\\\")\n\tStrSlash            = []byte(\"/\")\n\tStrSlashSlash       = []byte(\"//\")\n\tStrSlashDotDot      = []byte(\"/..\")\n\tStrSlashDotSlash    = []byte(\"/./\")\n\tStrSlashDotDotSlash = []byte(\"/../\")\n\tStrCRLF             = []byte(\"\\r\\n\")\n\tStrHTTP             = []byte(\"http\")\n\tStrHTTPS            = []byte(\"https\")\n\tStrHTTP11           = []byte(\"HTTP/1.1\")\n\tStrColon            = []byte(\":\")\n\tStrStar             = []byte(\"*\")\n\tStrColonSlashSlash  = []byte(\"://\")\n\tStrColonSpace       = []byte(\": \")\n\tStrCommaSpace       = []byte(\", \")\n\tStrAt               = []byte(\"@\")\n\tStrSD               = []byte(\"sd\")\n\n\tStrResponseContinue = []byte(\"HTTP/1.1 100 Continue\\r\\n\\r\\n\")\n\n\tStrGet     = []byte(consts.MethodGet)\n\tStrHead    = []byte(consts.MethodHead)\n\tStrPost    = []byte(consts.MethodPost)\n\tStrPut     = []byte(consts.MethodPut)\n\tStrDelete  = []byte(consts.MethodDelete)\n\tStrConnect = []byte(consts.MethodConnect)\n\tStrOptions = []byte(consts.MethodOptions)\n\tStrTrace   = []byte(consts.MethodTrace)\n\tStrPatch   = []byte(consts.MethodPatch)\n\n\tStrExpect           = []byte(consts.HeaderExpect)\n\tStrConnection       = []byte(consts.HeaderConnection)\n\tStrContentLength    = []byte(consts.HeaderContentLength)\n\tStrContentType      = []byte(consts.HeaderContentType)\n\tStrDate             = []byte(consts.HeaderDate)\n\tStrHost             = []byte(consts.HeaderHost)\n\tStrServer           = []byte(consts.HeaderServer)\n\tStrTransferEncoding = []byte(consts.HeaderTransferEncoding)\n\n\tStrUserAgent          = []byte(consts.HeaderUserAgent)\n\tStrCookie             = []byte(consts.HeaderCookie)\n\tStrLocation           = []byte(consts.HeaderLocation)\n\tStrContentRange       = []byte(consts.HeaderContentRange)\n\tStrContentEncoding    = []byte(consts.HeaderContentEncoding)\n\tStrAcceptEncoding     = []byte(consts.HeaderAcceptEncoding)\n\tStrSetCookie          = []byte(consts.HeaderSetCookie)\n\tStrAuthorization      = []byte(consts.HeaderAuthorization)\n\tStrRange              = []byte(consts.HeaderRange)\n\tStrLastModified       = []byte(consts.HeaderLastModified)\n\tStrAcceptRanges       = []byte(consts.HeaderAcceptRanges)\n\tStrIfModifiedSince    = []byte(consts.HeaderIfModifiedSince)\n\tStrTE                 = []byte(consts.HeaderTE)\n\tStrTrailer            = []byte(consts.HeaderTrailer)\n\tStrMaxForwards        = []byte(consts.HeaderMaxForwards)\n\tStrProxyConnection    = []byte(consts.HeaderProxyConnection)\n\tStrProxyAuthenticate  = []byte(consts.HeaderProxyAuthenticate)\n\tStrProxyAuthorization = []byte(consts.HeaderProxyAuthorization)\n\tStrWWWAuthenticate    = []byte(consts.HeaderWWWAuthenticate)\n\n\tStrCookieExpires        = []byte(\"expires\")\n\tStrCookieDomain         = []byte(\"domain\")\n\tStrCookiePath           = []byte(\"path\")\n\tStrCookieHTTPOnly       = []byte(\"HttpOnly\")\n\tStrCookieSecure         = []byte(\"secure\")\n\tStrCookieMaxAge         = []byte(\"max-age\")\n\tStrCookieSameSite       = []byte(\"SameSite\")\n\tStrCookieSameSiteLax    = []byte(\"Lax\")\n\tStrCookieSameSiteStrict = []byte(\"Strict\")\n\tStrCookieSameSiteNone   = []byte(\"None\")\n\tStrCookiePartitioned    = []byte(\"Partitioned\")\n\n\tStrClose            = []byte(\"close\")\n\tStrGzip             = []byte(\"gzip\")\n\tStrDeflate          = []byte(\"deflate\")\n\tStrKeepAlive        = []byte(\"keep-alive\")\n\tStrUpgrade          = []byte(\"Upgrade\")\n\tStrChunked          = []byte(\"chunked\")\n\tStrIdentity         = []byte(\"identity\")\n\tStr100Continue      = []byte(\"100-continue\")\n\tStrBoundary         = []byte(\"boundary\")\n\tStrBytes            = []byte(\"bytes\")\n\tStrTextSlash        = []byte(\"text/\")\n\tStrApplicationSlash = []byte(\"application/\")\n\tStrBasicSpace       = []byte(\"Basic \")\n\n\t// http2\n\tStrClientPreface = []byte(consts.ClientPreface)\n)\n\nvar ( // content types\n\tMIMEPostForm        = []byte(\"application/x-www-form-urlencoded\")\n\tMIMEFormData        = []byte(\"multipart/form-data\")\n\tMIMETextEventStream = []byte(\"text/event-stream\") // for server-sent events\n)\n"
  },
  {
    "path": "internal/nocopy/nocopy.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package nocopy defines the NoCopy struct\npackage nocopy\n\n// NoCopy defines the nocopy struct.\n// Embed this type into a struct, which mustn't be copied,\n// so `go vet` gives a warning if this struct is copied.\n//\n// See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details.\n// and also: https://stackoverflow.com/questions/52494458/nocopy-minimal-example\ntype NoCopy struct{}\n\nfunc (*NoCopy) Lock()   {}\nfunc (*NoCopy) Unlock() {}\n"
  },
  {
    "path": "internal/stats/stats_util.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage stats\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/stats\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo\"\n)\n\n// Record records the event to HTTPStats.\nfunc Record(ti traceinfo.TraceInfo, event stats.Event, err error) {\n\tif ti == nil {\n\t\treturn\n\t}\n\tif err != nil {\n\t\tti.Stats().Record(event, stats.StatusError, err.Error())\n\t} else {\n\t\tti.Stats().Record(event, stats.StatusInfo, \"\")\n\t}\n}\n\n// CalcEventCostUs calculates the duration between start and end and returns in microsecond.\nfunc CalcEventCostUs(start, end traceinfo.Event) uint64 {\n\tif start == nil || end == nil || start.IsNil() || end.IsNil() {\n\t\treturn 0\n\t}\n\treturn uint64(end.Time().Sub(start.Time()).Microseconds())\n}\n"
  },
  {
    "path": "internal/stats/stats_util_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage stats\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/stats\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo\"\n)\n\nfunc TestUtil(t *testing.T) {\n\tassert.Assert(t, CalcEventCostUs(nil, nil) == 0)\n\n\tti := traceinfo.NewTraceInfo()\n\n\t// nil context\n\tRecord(ti, stats.HTTPStart, nil)\n\tRecord(ti, stats.HTTPFinish, nil)\n\n\tst := ti.Stats()\n\tassert.Assert(t, st != nil)\n\n\ts, e := st.GetEvent(stats.HTTPStart), st.GetEvent(stats.HTTPFinish)\n\tassert.Assert(t, s == nil)\n\tassert.Assert(t, e == nil)\n\n\t// stats disabled\n\tRecord(ti, stats.HTTPStart, nil)\n\ttime.Sleep(time.Millisecond)\n\tRecord(ti, stats.HTTPFinish, nil)\n\n\tst = ti.Stats()\n\tassert.Assert(t, st != nil)\n\n\ts, e = st.GetEvent(stats.HTTPStart), st.GetEvent(stats.HTTPFinish)\n\tassert.Assert(t, s == nil)\n\tassert.Assert(t, e == nil)\n\n\t// stats enabled\n\tst = ti.Stats()\n\tst.(interface{ SetLevel(stats.Level) }).SetLevel(stats.LevelBase)\n\n\tRecord(ti, stats.HTTPStart, nil)\n\ttime.Sleep(time.Millisecond)\n\tRecord(ti, stats.HTTPFinish, nil)\n\n\ts, e = st.GetEvent(stats.HTTPStart), st.GetEvent(stats.HTTPFinish)\n\tassert.Assert(t, s != nil, s)\n\tassert.Assert(t, e != nil, e)\n\tassert.Assert(t, CalcEventCostUs(s, e) > 0)\n}\n"
  },
  {
    "path": "internal/stats/tracer.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage stats\n\nimport (\n\t\"context\"\n\t\"runtime/debug\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/stats\"\n)\n\n// Controller controls tracers.\ntype Controller struct {\n\ttracers []tracer.Tracer\n}\n\n// Append appends a new tracer to the controller.\nfunc (ctl *Controller) Append(col tracer.Tracer) {\n\tctl.tracers = append(ctl.tracers, col)\n}\n\n// DoStart starts the tracers.\nfunc (ctl *Controller) DoStart(ctx context.Context, c *app.RequestContext) context.Context {\n\tdefer ctl.tryRecover()\n\tRecord(c.GetTraceInfo(), stats.HTTPStart, nil)\n\n\tfor _, col := range ctl.tracers {\n\t\tctx = col.Start(ctx, c)\n\t}\n\treturn ctx\n}\n\n// DoFinish calls the tracers in reversed order.\nfunc (ctl *Controller) DoFinish(ctx context.Context, c *app.RequestContext, err error) {\n\tdefer ctl.tryRecover()\n\tRecord(c.GetTraceInfo(), stats.HTTPFinish, err)\n\tif err != nil {\n\t\tc.GetTraceInfo().Stats().SetError(err)\n\t}\n\n\t// reverse the order\n\tfor i := len(ctl.tracers) - 1; i >= 0; i-- {\n\t\tctl.tracers[i].Finish(ctx, c)\n\t}\n}\n\nfunc (ctl *Controller) HasTracer() bool {\n\treturn ctl != nil && len(ctl.tracers) > 0\n}\n\nfunc (ctl *Controller) tryRecover() {\n\tif err := recover(); err != nil {\n\t\thlog.SystemLogger().Warnf(\"Panic happened during tracer call. This doesn't affect the http call, but may lead to lack of monitor data such as metrics and logs: %s, %s\", err, string(debug.Stack()))\n\t}\n}\n"
  },
  {
    "path": "internal/stats/tracer_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage stats\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo\"\n)\n\ntype mockTracer struct {\n\torder         int\n\tstack         *[]int\n\tpanicAtStart  bool\n\tpanicAtFinish bool\n}\n\nfunc (mt *mockTracer) Start(ctx context.Context, c *app.RequestContext) context.Context {\n\tif mt.panicAtStart {\n\t\tpanic(fmt.Sprintf(\"panicked at start: Tracer(%d)\", mt.order))\n\t}\n\t*mt.stack = append(*mt.stack, mt.order)\n\treturn context.WithValue(ctx, mt, mt.order)\n}\n\nfunc (mt *mockTracer) Finish(ctx context.Context, c *app.RequestContext) {\n\tif mt.panicAtFinish {\n\t\tpanic(fmt.Sprintf(\"panicked at finish: Tracer(%d)\", mt.order))\n\t}\n\t*mt.stack = append(*mt.stack, -mt.order)\n}\n\nfunc TestOrder(t *testing.T) {\n\tvar c Controller\n\tvar stack []int\n\tt1 := &mockTracer{order: 1, stack: &stack}\n\tt2 := &mockTracer{order: 2, stack: &stack}\n\tctx := app.NewContext(16)\n\tc.Append(t1)\n\tc.Append(t2)\n\n\tctx0 := context.Background()\n\tctx1 := c.DoStart(ctx0, ctx)\n\tassert.Assert(t, ctx1 != ctx0)\n\tassert.Assert(t, len(stack) == 2 && stack[0] == 1 && stack[1] == 2, stack)\n\n\tc.DoFinish(ctx1, ctx, nil)\n\tassert.Assert(t, len(stack) == 4 && stack[2] == -2 && stack[3] == -1, stack)\n}\n\nfunc TestPanic(t *testing.T) {\n\tvar c Controller\n\tvar stack []int\n\tt1 := &mockTracer{order: 1, stack: &stack, panicAtStart: true, panicAtFinish: true}\n\tt2 := &mockTracer{order: 2, stack: &stack}\n\tctx := app.NewContext(16)\n\tctx.SetTraceInfo(traceinfo.NewTraceInfo())\n\tc.Append(t1)\n\tc.Append(t2)\n\n\tctx0 := context.Background()\n\tctx1 := c.DoStart(ctx0, ctx)\n\tassert.Assert(t, ctx1 != ctx0)\n\tassert.Assert(t, len(stack) == 0) // t1's panic skips all subsequent Starts\n\n\terr := errors.New(\"some error\")\n\tc.DoFinish(ctx1, ctx, err)\n\tassert.Assert(t, len(stack) == 1 && stack[0] == -2, stack)\n}\n"
  },
  {
    "path": "internal/tagexpr/LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2019 Bytedance Inc.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "internal/tagexpr/README.md",
    "content": "# go-tagexpr\n\noriginally from https://github.com/bytedance/go-tagexpr v2.9.2\n"
  },
  {
    "path": "internal/tagexpr/example_test.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr_test\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/cloudwego/hertz/internal/tagexpr\"\n)\n\nfunc Example() {\n\ttype T struct {\n\t\tA  int             `tagexpr:\"$<0||$>=100\"`\n\t\tB  string          `tagexpr:\"len($)>1 && regexp('^\\\\w*$')\"`\n\t\tC  bool            `tagexpr:\"expr1:(f.g)$>0 && $; expr2:'C must be true when T.f.g>0'\"`\n\t\td  []string        `tagexpr:\"@:len($)>0 && $[0]=='D'; msg:sprintf('invalid d: %v',$)\"`\n\t\te  map[string]int  `tagexpr:\"len($)==$['len']\"`\n\t\te2 map[string]*int `tagexpr:\"len($)==$['len']\"`\n\t\tf  struct {\n\t\t\tg int `tagexpr:\"$\"`\n\t\t}\n\t\th int `tagexpr:\"$>minVal\"`\n\t}\n\n\tvm := tagexpr.New(\"tagexpr\")\n\tt := &T{\n\t\tA:  107,\n\t\tB:  \"abc\",\n\t\tC:  true,\n\t\td:  []string{\"x\", \"y\"},\n\t\te:  map[string]int{\"len\": 1},\n\t\te2: map[string]*int{\"len\": new(int)},\n\t\tf: struct {\n\t\t\tg int `tagexpr:\"$\"`\n\t\t}{1},\n\t\th: 10,\n\t}\n\n\ttagExpr, err := vm.Run(t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(tagExpr.Eval(\"A\"))\n\tfmt.Println(tagExpr.Eval(\"B\"))\n\tfmt.Println(tagExpr.Eval(\"C@expr1\"))\n\tfmt.Println(tagExpr.Eval(\"C@expr2\"))\n\tif !tagExpr.Eval(\"d\").(bool) {\n\t\tfmt.Println(tagExpr.Eval(\"d@msg\"))\n\t}\n\tfmt.Println(tagExpr.Eval(\"e\"))\n\tfmt.Println(tagExpr.Eval(\"e2\"))\n\tfmt.Println(tagExpr.Eval(\"f.g\"))\n\tfmt.Println(tagExpr.EvalWithEnv(\"h\", map[string]interface{}{\"minVal\": 9}))\n\tfmt.Println(tagExpr.EvalWithEnv(\"h\", map[string]interface{}{\"minVal\": 11}))\n\n\t// Output:\n\t// true\n\t// true\n\t// true\n\t// C must be true when T.f.g>0\n\t// invalid d: [x y]\n\t// true\n\t// false\n\t// 1\n\t// true\n\t// false\n}\n"
  },
  {
    "path": "internal/tagexpr/expr.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\ntype variableKeyType string\n\nconst variableKey variableKeyType = \"__ENV_KEY__\"\n\n// Expr expression\ntype Expr struct {\n\texpr ExprNode\n}\n\n// parseExpr parses the expression.\nfunc parseExpr(expr string) (*Expr, error) {\n\te := newGroupExprNode()\n\tp := &Expr{\n\t\texpr: e,\n\t}\n\ts := expr\n\terr := p.parseExprNode(&s, e)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsortPriority(e)\n\treturn p, nil\n}\n\nfunc (p *Expr) parseExprNode(expr *string, e ExprNode) error {\n\ttrimLeftSpace(expr)\n\tif *expr == \"\" {\n\t\treturn nil\n\t}\n\toperand := p.readSelectorExprNode(expr)\n\tif operand == nil {\n\t\toperand = p.readRangeKvExprNode(expr)\n\t\tif operand == nil {\n\t\t\tvar subExprNode *string\n\t\t\toperand, subExprNode = readGroupExprNode(expr)\n\t\t\tif operand != nil {\n\t\t\t\terr := p.parseExprNode(subExprNode, operand)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\toperand = p.parseOperand(expr)\n\t\t\t}\n\t\t}\n\t}\n\tif operand == nil {\n\t\treturn fmt.Errorf(\"syntax error: %q\", *expr)\n\t}\n\ttrimLeftSpace(expr)\n\toperator := p.parseOperator(expr)\n\tif operator == nil {\n\t\te.SetRightOperand(operand)\n\t\toperand.SetParent(e)\n\t\treturn nil\n\t}\n\tif _, ok := e.(*groupExprNode); ok {\n\t\toperator.SetLeftOperand(operand)\n\t\toperand.SetParent(operator)\n\t\te.SetRightOperand(operator)\n\t\toperator.SetParent(e)\n\t} else {\n\t\toperator.SetParent(e.Parent())\n\t\toperator.Parent().SetRightOperand(operator)\n\t\toperator.SetLeftOperand(e)\n\t\te.SetParent(operator)\n\t\te.SetRightOperand(operand)\n\t\toperand.SetParent(e)\n\t}\n\treturn p.parseExprNode(expr, operator)\n}\n\nfunc (p *Expr) parseOperand(expr *string) (e ExprNode) {\n\tfor _, fn := range funcList {\n\t\tif e = fn(p, expr); e != nil {\n\t\t\treturn e\n\t\t}\n\t}\n\tif e = readStringExprNode(expr); e != nil {\n\t\treturn e\n\t}\n\tif e = readDigitalExprNode(expr); e != nil {\n\t\treturn e\n\t}\n\tif e = readBoolExprNode(expr); e != nil {\n\t\treturn e\n\t}\n\tif e = readNilExprNode(expr); e != nil {\n\t\treturn e\n\t}\n\tif e = readVariableExprNode(expr); e != nil {\n\t\treturn e\n\t}\n\treturn nil\n}\n\nfunc (*Expr) parseOperator(expr *string) (e ExprNode) {\n\ts := *expr\n\tif len(s) < 2 {\n\t\treturn nil\n\t}\n\tdefer func() {\n\t\tif e != nil && *expr == s {\n\t\t\t*expr = (*expr)[2:]\n\t\t}\n\t}()\n\ta := s[:2]\n\tswitch a {\n\t// case \"<<\":\n\t// case \">>\":\n\t// case \"&^\":\n\tcase \"||\":\n\t\treturn newOrExprNode()\n\tcase \"&&\":\n\t\treturn newAndExprNode()\n\tcase \"==\":\n\t\treturn newEqualExprNode()\n\tcase \">=\":\n\t\treturn newGreaterEqualExprNode()\n\tcase \"<=\":\n\t\treturn newLessEqualExprNode()\n\tcase \"!=\":\n\t\treturn newNotEqualExprNode()\n\t}\n\tdefer func() {\n\t\tif e != nil {\n\t\t\t*expr = (*expr)[1:]\n\t\t}\n\t}()\n\tswitch a[0] {\n\t// case '&':\n\t// case '|':\n\t// case '^':\n\tcase '+':\n\t\treturn newAdditionExprNode()\n\tcase '-':\n\t\treturn newSubtractionExprNode()\n\tcase '*':\n\t\treturn newMultiplicationExprNode()\n\tcase '/':\n\t\treturn newDivisionExprNode()\n\tcase '%':\n\t\treturn newRemainderExprNode()\n\tcase '<':\n\t\treturn newLessExprNode()\n\tcase '>':\n\t\treturn newGreaterExprNode()\n\t}\n\treturn nil\n}\n\n// run calculates the value of expression.\nfunc (p *Expr) run(field string, tagExpr *TagExpr) interface{} {\n\treturn p.expr.Run(context.Background(), field, tagExpr)\n}\n\nfunc (p *Expr) runWithEnv(field string, tagExpr *TagExpr, env map[string]interface{}) interface{} {\n\tctx := context.WithValue(context.Background(), variableKey, env)\n\treturn p.expr.Run(ctx, field, tagExpr)\n}\n\n/**\n * Priority:\n * () ! bool float64 string nil\n * * / %\n * + -\n * < <= > >=\n * == !=\n * &&\n * ||\n**/\n\nfunc sortPriority(e ExprNode) {\n\tfor subSortPriority(e.RightOperand(), false) {\n\t}\n}\n\nfunc subSortPriority(e ExprNode, isLeft bool) bool {\n\tif e == nil {\n\t\treturn false\n\t}\n\tleftChanged := subSortPriority(e.LeftOperand(), true)\n\trightChanged := subSortPriority(e.RightOperand(), false)\n\tif getPriority(e) > getPriority(e.LeftOperand()) {\n\t\tleftOperandToParent(e, isLeft)\n\t\treturn true\n\t}\n\treturn leftChanged || rightChanged\n}\n\nfunc leftOperandToParent(e ExprNode, isLeft bool) {\n\tle := e.LeftOperand()\n\tif le == nil {\n\t\treturn\n\t}\n\tp := e.Parent()\n\tle.SetParent(p)\n\tif p != nil {\n\t\tif isLeft {\n\t\t\tp.SetLeftOperand(le)\n\t\t} else {\n\t\t\tp.SetRightOperand(le)\n\t\t}\n\t}\n\te.SetParent(le)\n\te.SetLeftOperand(le.RightOperand())\n\tle.RightOperand().SetParent(e)\n\tle.SetRightOperand(e)\n}\n\nfunc getPriority(e ExprNode) (i int) {\n\t// defer func() {\n\t// \tprintf(\"expr:%T %d\\n\", e, i)\n\t// }()\n\tswitch e.(type) {\n\tdefault: // () ! bool float64 string nil\n\t\treturn 7\n\tcase *multiplicationExprNode, *divisionExprNode, *remainderExprNode: // * / %\n\t\treturn 6\n\tcase *additionExprNode, *subtractionExprNode: // + -\n\t\treturn 5\n\tcase *lessExprNode, *lessEqualExprNode, *greaterExprNode, *greaterEqualExprNode: // < <= > >=\n\t\treturn 4\n\tcase *equalExprNode, *notEqualExprNode: // == !=\n\t\treturn 3\n\tcase *andExprNode: // &&\n\t\treturn 2\n\tcase *orExprNode: // ||\n\t\treturn 1\n\t}\n}\n\n// ExprNode expression interface\ntype ExprNode interface {\n\tSetParent(ExprNode)\n\tParent() ExprNode\n\tLeftOperand() ExprNode\n\tRightOperand() ExprNode\n\tSetLeftOperand(ExprNode)\n\tSetRightOperand(ExprNode)\n\tString() string\n\tRun(context.Context, string, *TagExpr) interface{}\n}\n\n// var _ ExprNode = new(exprBackground)\n\ntype exprBackground struct {\n\tparent       ExprNode\n\tleftOperand  ExprNode\n\trightOperand ExprNode\n}\n\nfunc (eb *exprBackground) SetParent(e ExprNode) {\n\teb.parent = e\n}\n\nfunc (eb *exprBackground) Parent() ExprNode {\n\treturn eb.parent\n}\n\nfunc (eb *exprBackground) LeftOperand() ExprNode {\n\treturn eb.leftOperand\n}\n\nfunc (eb *exprBackground) RightOperand() ExprNode {\n\treturn eb.rightOperand\n}\n\nfunc (eb *exprBackground) SetLeftOperand(left ExprNode) {\n\teb.leftOperand = left\n}\n\nfunc (eb *exprBackground) SetRightOperand(right ExprNode) {\n\teb.rightOperand = right\n}\n\nfunc (*exprBackground) Run(context.Context, string, *TagExpr) interface{} { return nil }\n"
  },
  {
    "path": "internal/tagexpr/expr_test.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"math\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestExpr(t *testing.T) {\n\tcases := []struct {\n\t\texpr string\n\t\tval  interface{}\n\t}{\n\t\t// Simple string\n\t\t{expr: \"'a'\", val: \"a\"},\n\t\t{expr: \"('a')\", val: \"a\"},\n\t\t// Simple digital\n\t\t{expr: \" 10 \", val: 10.0},\n\t\t{expr: \"(10)\", val: 10.0},\n\t\t// Simple bool\n\t\t{expr: \"true\", val: true},\n\t\t{expr: \"!true\", val: false},\n\t\t{expr: \"!!true\", val: true},\n\t\t{expr: \"false\", val: false},\n\t\t{expr: \"!false\", val: true},\n\t\t{expr: \"!!false\", val: false},\n\t\t{expr: \"(false)\", val: false},\n\t\t{expr: \"(!false)\", val: true},\n\t\t{expr: \"(!!false)\", val: false},\n\t\t{expr: \"!!(!false)\", val: true},\n\t\t{expr: \"!(!false)\", val: false},\n\t\t// Join string\n\t\t{expr: \"'true '+('a')\", val: \"true a\"},\n\t\t{expr: \"'a'+('b'+'c')+'d'\", val: \"abcd\"},\n\t\t// Arithmetic operator\n\t\t{expr: \"1+7+2\", val: 10.0},\n\t\t{expr: \"1+(7)+(2)\", val: 10.0},\n\t\t{expr: \"1.1+ 2\", val: 3.1},\n\t\t{expr: \"-1.1+4\", val: 2.9},\n\t\t{expr: \"10-7-2\", val: 1.0},\n\t\t{expr: \"20/2\", val: 10.0},\n\t\t{expr: \"1/0\", val: math.NaN()},\n\t\t{expr: \"20%2\", val: 0.0},\n\t\t{expr: \"6 % 5\", val: 1.0},\n\t\t{expr: \"20%7 %5\", val: 1.0},\n\t\t{expr: \"1*2+7+2.2\", val: 11.2},\n\t\t{expr: \"-20/2+1+2\", val: -7.0},\n\t\t{expr: \"20/2+1-2-1\", val: 8.0},\n\t\t{expr: \"30/(2+1)/5-2-1\", val: -1.0},\n\t\t{expr: \"100/(( 2+8)*5 )-(1 +1- 0)\", val: 0.0},\n\t\t{expr: \"(2*3)+(4*2)\", val: 14.0},\n\t\t{expr: \"1+(2*(3+4))\", val: 15.0},\n\t\t{expr: \"20%(7%5)\", val: 0.0},\n\t\t// Relational operator\n\t\t{expr: \"50 == 5\", val: false},\n\t\t{expr: \"'50'==50\", val: true},\n\t\t{expr: \"'50'=='50'\", val: true},\n\t\t{expr: \"'50' =='5' == true\", val: false},\n\t\t{expr: \"50== 50 == false\", val: false},\n\t\t{expr: \"50== 50 == true ==true==true\", val: true},\n\t\t{expr: \"50 != 5\", val: true},\n\t\t{expr: \"'50'!=50\", val: false},\n\t\t{expr: \"'50'!= '50'\", val: false},\n\t\t{expr: \"'50' !='5' != true\", val: false},\n\t\t{expr: \"50!= 50 == false\", val: true},\n\t\t{expr: \"50== 50 != true ==true!=true\", val: true},\n\t\t{expr: \"50 > 5\", val: true},\n\t\t{expr: \"50.1 > 50.1\", val: false},\n\t\t{expr: \"3.2 > 2.1\", val: true},\n\t\t{expr: \"'3.2' > '2.1'\", val: true},\n\t\t{expr: \"'13.2'>'2.1'\", val: false},\n\t\t{expr: \"3.2 >= 2.1\", val: true},\n\t\t{expr: \"2.1 >= 2.1\", val: true},\n\t\t{expr: \"2.05 >= 2.1\", val: false},\n\t\t{expr: \"'2.05'>='2.1'\", val: false},\n\t\t{expr: \"'12.05'>='2.1'\", val: false},\n\t\t{expr: \"50 < 5\", val: false},\n\t\t{expr: \"50.1 < 50.1\", val: false},\n\t\t{expr: \"3 <12.11\", val: true},\n\t\t{expr: \"3.2 < 2.1\", val: false},\n\t\t{expr: \"'3.2' < '2.1'\", val: false},\n\t\t{expr: \"'13.2' < '2.1'\", val: true},\n\t\t{expr: \"3.2 <= 2.1\", val: false},\n\t\t{expr: \"2.1 <= 2.1\", val: true},\n\t\t{expr: \"2.05 <= 2.1\", val: true},\n\t\t{expr: \"'2.05'<='2.1'\", val: true},\n\t\t{expr: \"'12.05'<='2.1'\", val: true},\n\t\t// Logical operator\n\t\t{expr: \"!('13.2' < '2.1')\", val: false},\n\t\t{expr: \"(3.2 <= 2.1) &&true\", val: false},\n\t\t{expr: \"true&&(2.1<=2.1)\", val: true},\n\t\t{expr: \"(2.05<=2.1)&&false\", val: false},\n\t\t{expr: \"true&&!true&&false\", val: false},\n\t\t{expr: \"true&&true&&true\", val: true},\n\t\t{expr: \"true&&true&&false\", val: false},\n\t\t{expr: \"false&&true&&true\", val: false},\n\t\t{expr: \"true && false && true\", val: false},\n\t\t{expr: \"true||false\", val: true},\n\t\t{expr: \"false ||true\", val: true},\n\t\t{expr: \"true&&true || false\", val: true},\n\t\t{expr: \"true&&false || false\", val: false},\n\t\t{expr: \"true && false || true \", val: true},\n\t}\n\tfor _, c := range cases {\n\t\tt.Log(c.expr)\n\t\tvm, err := parseExpr(c.expr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tval := vm.run(\"\", nil)\n\t\tif !reflect.DeepEqual(val, c.val) {\n\t\t\tif f, ok := c.val.(float64); ok && math.IsNaN(f) && math.IsNaN(val.(float64)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Fatalf(\"expr: %q, got: %v, expect: %v\", c.expr, val, c.val)\n\t\t}\n\t}\n}\n\nfunc TestExprWithEnv(t *testing.T) {\n\tcases := []struct {\n\t\texpr string\n\t\tval  interface{}\n\t}{\n\t\t// env: a = 10, b = \"string value\",\n\t\t{expr: \"a\", val: 10.0},\n\t\t{expr: \"b\", val: \"string value\"},\n\t\t{expr: \"a>10\", val: false},\n\t\t{expr: \"a<11\", val: true},\n\t\t{expr: \"a+1\", val: 11.0},\n\t\t{expr: \"a==10\", val: true},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Log(c.expr)\n\t\tvm, err := parseExpr(c.expr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tval := vm.runWithEnv(\"\", nil, map[string]interface{}{\"a\": 10, \"b\": \"string value\"})\n\t\tif !reflect.DeepEqual(val, c.val) {\n\t\t\tif f, ok := c.val.(float64); ok && math.IsNaN(f) && math.IsNaN(val.(float64)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Fatalf(\"expr: %q, got: %v, expect: %v\", c.expr, val, c.val)\n\t\t}\n\t}\n}\n\nfunc TestPriority(t *testing.T) {\n\tcases := []struct {\n\t\texpr string\n\t\tval  interface{}\n\t}{\n\t\t{expr: \"false||true&&8==8\", val: true},\n\t\t{expr: \"1+2>5-4\", val: true},\n\t\t{expr: \"1+2*4/2\", val: 5.0},\n\t\t{expr: \"(true||false)&&false||false\", val: false},\n\t\t{expr: \"true||false&&false||false\", val: true},\n\t\t{expr: \"true||1<0&&'a'!='a'||0!=0\", val: true},\n\t}\n\tfor _, c := range cases {\n\t\tt.Log(c.expr)\n\t\tvm, err := parseExpr(c.expr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tval := vm.run(\"\", nil)\n\t\tif !reflect.DeepEqual(val, c.val) {\n\t\t\tif f, ok := c.val.(float64); ok && math.IsNaN(f) && math.IsNaN(val.(float64)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Fatalf(\"expr: %q, got: %v, expect: %v\", c.expr, val, c.val)\n\t\t}\n\t}\n}\n\nfunc TestBuiltInFunc(t *testing.T) {\n\tcases := []struct {\n\t\texpr string\n\t\tval  interface{}\n\t}{\n\t\t{expr: \"len('abc')\", val: 3.0},\n\t\t{expr: \"len('abc')+2*2/len('cd')\", val: 5.0},\n\t\t{expr: \"len(0)\", val: 0.0},\n\n\t\t{expr: \"regexp('a\\\\d','a0')\", val: true},\n\t\t{expr: \"regexp('^a\\\\d$','a0')\", val: true},\n\t\t{expr: \"regexp('a\\\\d','a')\", val: false},\n\t\t{expr: \"regexp('^a\\\\d$','a')\", val: false},\n\n\t\t{expr: \"sprintf('test string: %s','a')\", val: \"test string: a\"},\n\t\t{expr: \"sprintf('test string: %s','a'+'b')\", val: \"test string: ab\"},\n\t\t{expr: \"sprintf('test string: %s,%v','a',1)\", val: \"test string: a,1\"},\n\t\t{expr: \"sprintf('')+'a'\", val: \"a\"},\n\t\t{expr: \"sprintf('%v',10+2*2)\", val: \"14\"},\n\t}\n\tfor _, c := range cases {\n\t\tt.Log(c.expr)\n\t\tvm, err := parseExpr(c.expr)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tval := vm.run(\"\", nil)\n\t\tif !reflect.DeepEqual(val, c.val) {\n\t\t\tif f, ok := c.val.(float64); ok && math.IsNaN(f) && math.IsNaN(val.(float64)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Fatalf(\"expr: %q, got: %v, expect: %v\", c.expr, val, c.val)\n\t\t}\n\t}\n}\n\nfunc TestSyntaxIncorrect(t *testing.T) {\n\tcases := []struct {\n\t\tincorrectExpr string\n\t}{\n\t\t{incorrectExpr: \"1 + + 'a'\"},\n\t\t{incorrectExpr: \"regexp()\"},\n\t\t{incorrectExpr: \"regexp('^'+'a','a')\"},\n\t\t{incorrectExpr: \"regexp('^a','a','b')\"},\n\t\t{incorrectExpr: \"sprintf()\"},\n\t\t{incorrectExpr: \"sprintf(0)\"},\n\t\t{incorrectExpr: \"sprintf('a'+'b')\"},\n\t}\n\tfor _, c := range cases {\n\t\t_, err := parseExpr(c.incorrectExpr)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expect syntax incorrect: %s\", c.incorrectExpr)\n\t\t} else {\n\t\t\tt.Log(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/tagexpr/handler.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport \"reflect\"\n\n// FieldHandler field handler\ntype FieldHandler struct {\n\tselector string\n\tfield    *fieldVM\n\texpr     *TagExpr\n}\n\nfunc newFieldHandler(expr *TagExpr, fieldSelector string, field *fieldVM) *FieldHandler {\n\treturn &FieldHandler{\n\t\tselector: fieldSelector,\n\t\tfield:    field,\n\t\texpr:     expr,\n\t}\n}\n\n// StringSelector returns the field selector of string type.\nfunc (f *FieldHandler) StringSelector() string {\n\treturn f.selector\n}\n\n// FieldSelector returns the field selector of FieldSelector type.\nfunc (f *FieldHandler) FieldSelector() FieldSelector {\n\treturn FieldSelector(f.selector)\n}\n\n// Value returns the field value.\n// NOTE:\n//\n//\tIf initZero==true, initialize nil pointer to zero value\nfunc (f *FieldHandler) Value(initZero bool) reflect.Value {\n\treturn f.field.reflectValueGetter(f.expr.ptr, initZero)\n}\n\n// EvalFuncs returns the tag expression eval functions.\nfunc (f *FieldHandler) EvalFuncs() map[ExprSelector]func() interface{} {\n\ttargetTagExpr, _ := f.expr.checkout(f.selector)\n\tevals := make(map[ExprSelector]func() interface{}, len(f.field.exprs))\n\tfor k, v := range f.field.exprs {\n\t\texpr := v\n\t\texprSelector := ExprSelector(k)\n\t\tevals[exprSelector] = func() interface{} {\n\t\t\treturn expr.run(exprSelector.Name(), targetTagExpr)\n\t\t}\n\t}\n\treturn evals\n}\n\n// StructField returns the field StructField object.\nfunc (f *FieldHandler) StructField() reflect.StructField {\n\treturn f.field.structField\n}\n\n// ExprHandler expr handler\ntype ExprHandler struct {\n\tbase       string\n\tpath       string\n\tselector   string\n\texpr       *TagExpr\n\ttargetExpr *TagExpr\n}\n\nfunc newExprHandler(te, tte *TagExpr, base, es string) *ExprHandler {\n\treturn &ExprHandler{\n\t\tbase:       base,\n\t\tselector:   es,\n\t\texpr:       te,\n\t\ttargetExpr: tte,\n\t}\n}\n\n// TagExpr returns the *TagExpr.\nfunc (e *ExprHandler) TagExpr() *TagExpr {\n\treturn e.expr\n}\n\n// StringSelector returns the expression selector of string type.\nfunc (e *ExprHandler) StringSelector() string {\n\treturn e.selector\n}\n\n// ExprSelector returns the expression selector of ExprSelector type.\nfunc (e *ExprHandler) ExprSelector() ExprSelector {\n\treturn ExprSelector(e.selector)\n}\n\n// Path returns the path description of the expression.\nfunc (e *ExprHandler) Path() string {\n\tif e.path == \"\" {\n\t\tif e.targetExpr.path == \"\" {\n\t\t\te.path = e.selector\n\t\t} else {\n\t\t\te.path = e.targetExpr.path + FieldSeparator + e.selector\n\t\t}\n\t}\n\treturn e.path\n}\n\n// Eval evaluate the value of the struct tag expression.\n// NOTE:\n//\n//\tresult types: float64, string, bool, nil\nfunc (e *ExprHandler) Eval() interface{} {\n\treturn e.expr.s.exprs[e.selector].run(e.base, e.targetExpr)\n}\n\n// EvalFloat evaluates the value of the struct tag expression.\n// NOTE:\n//\n//\tIf the expression value type is not float64, return 0.\nfunc (e *ExprHandler) EvalFloat() float64 {\n\tr, _ := e.Eval().(float64)\n\treturn r\n}\n\n// EvalString evaluates the value of the struct tag expression.\n// NOTE:\n//\n//\tIf the expression value type is not string, return \"\".\nfunc (e *ExprHandler) EvalString() string {\n\tr, _ := e.Eval().(string)\n\treturn r\n}\n\n// EvalBool evaluates the value of the struct tag expression.\n// NOTE:\n//\n//\tIf the expression value is not 0, '' or nil, return true.\nfunc (e *ExprHandler) EvalBool() bool {\n\treturn FakeBool(e.Eval())\n}\n"
  },
  {
    "path": "internal/tagexpr/selector.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"strings\"\n)\n\nconst (\n\t// FieldSeparator in the expression selector,\n\t// the separator between field names\n\tFieldSeparator = \".\"\n\t// ExprNameSeparator in the expression selector,\n\t// the separator of the field name and expression name\n\tExprNameSeparator = \"@\"\n\t// DefaultExprName the default name of single model expression\n\tDefaultExprName = ExprNameSeparator\n)\n\n// FieldSelector expression selector\ntype FieldSelector string\n\n// Name returns the current field name.\nfunc (f FieldSelector) Name() string {\n\ts := string(f)\n\tidx := strings.LastIndex(s, FieldSeparator)\n\tif idx == -1 {\n\t\treturn s\n\t}\n\treturn s[idx+1:]\n}\n\n// Split returns the path segments and the current field name.\nfunc (f FieldSelector) Split() (paths []string, name string) {\n\ts := string(f)\n\ta := strings.Split(s, FieldSeparator)\n\tidx := len(a) - 1\n\tif idx > 0 {\n\t\treturn a[:idx], a[idx]\n\t}\n\treturn nil, s\n}\n\n// Parent returns the parent FieldSelector.\nfunc (f FieldSelector) Parent() (string, bool) {\n\ts := string(f)\n\ti := strings.LastIndex(s, FieldSeparator)\n\tif i < 0 {\n\t\treturn \"\", false\n\t}\n\treturn s[:i], true\n}\n\n// String returns string type value.\nfunc (f FieldSelector) String() string {\n\treturn string(f)\n}\n\n// ExprSelector expression selector\ntype ExprSelector string\n\n// Name returns the name of the expression.\nfunc (e ExprSelector) Name() string {\n\ts := string(e)\n\tatIdx := strings.LastIndex(s, ExprNameSeparator)\n\tif atIdx == -1 {\n\t\treturn DefaultExprName\n\t}\n\treturn s[atIdx+1:]\n}\n\n// Field returns the field selector it belongs to.\nfunc (e ExprSelector) Field() string {\n\ts := string(e)\n\tidx := strings.LastIndex(s, ExprNameSeparator)\n\tif idx != -1 {\n\t\ts = s[:idx]\n\t}\n\treturn s\n}\n\n// ParentField returns the parent field selector it belongs to.\nfunc (e ExprSelector) ParentField() (string, bool) {\n\treturn FieldSelector(e.Field()).Parent()\n}\n\n// Split returns the field selector and the expression name.\nfunc (e ExprSelector) Split() (field FieldSelector, name string) {\n\ts := string(e)\n\tatIdx := strings.LastIndex(s, ExprNameSeparator)\n\tif atIdx == -1 {\n\t\treturn FieldSelector(s), DefaultExprName\n\t}\n\treturn FieldSelector(s[:atIdx]), s[atIdx+1:]\n}\n\n// String returns string type value.\nfunc (e ExprSelector) String() string {\n\treturn string(e)\n}\n"
  },
  {
    "path": "internal/tagexpr/selector_test.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"testing\"\n)\n\nfunc TestExprSelector(t *testing.T) {\n\tes := ExprSelector(\"F1.Index\")\n\tfield, ok := es.ParentField()\n\tif !ok {\n\t\tt.Fatal(\"not ok\")\n\t}\n\tif \"F1\" != field {\n\t\tt.Fatal(field)\n\t}\n}\n"
  },
  {
    "path": "internal/tagexpr/spec_func.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// --------------------------- Custom function ---------------------------\n\nvar funcList = map[string]func(p *Expr, expr *string) ExprNode{}\n\n// MustRegFunc registers function expression.\n// NOTE:\n//\n//\texample: len($), regexp(\"\\\\d\") or regexp(\"\\\\d\",$);\n//\tIf @force=true, allow to cover the existed same @funcName;\n//\tThe go number types always are float64;\n//\tThe go string types always are string;\n//\tPanic if there is an error.\nfunc MustRegFunc(funcName string, fn func(...interface{}) interface{}, force ...bool) {\n\terr := RegFunc(funcName, fn, force...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// RegFunc registers function expression.\n// NOTE:\n//\n//\texample: len($), regexp(\"\\\\d\") or regexp(\"\\\\d\",$);\n//\tIf @force=true, allow to cover the existed same @funcName;\n//\tThe go number types always are float64;\n//\tThe go string types always are string.\nfunc RegFunc(funcName string, fn func(...interface{}) interface{}, force ...bool) error {\n\tif len(force) == 0 || !force[0] {\n\t\t_, ok := funcList[funcName]\n\t\tif ok {\n\t\t\treturn fmt.Errorf(\"duplicate registration expression function: %s\", funcName)\n\t\t}\n\t}\n\tfuncList[funcName] = newFunc(funcName, fn)\n\treturn nil\n}\n\nfunc (p *Expr) parseFuncSign(funcName string, expr *string) (boolOpposite *bool, signOpposite *bool, args []ExprNode, found bool) {\n\tprefix := funcName + \"(\"\n\tlength := len(funcName)\n\tlast, boolOpposite, signOpposite := getBoolAndSignOpposite(expr)\n\tif !strings.HasPrefix(last, prefix) {\n\t\treturn\n\t}\n\t*expr = last[length:]\n\tlastStr := *expr\n\tsubExprNode := readPairedSymbol(expr, '(', ')')\n\tif subExprNode == nil {\n\t\treturn\n\t}\n\t*subExprNode = \",\" + *subExprNode\n\tfor {\n\t\tif strings.HasPrefix(*subExprNode, \",\") {\n\t\t\t*subExprNode = (*subExprNode)[1:]\n\t\t\toperand := newGroupExprNode()\n\t\t\terr := p.parseExprNode(trimLeftSpace(subExprNode), operand)\n\t\t\tif err != nil {\n\t\t\t\t*expr = lastStr\n\t\t\t\treturn\n\t\t\t}\n\t\t\tsortPriority(operand)\n\t\t\targs = append(args, operand)\n\t\t} else {\n\t\t\t*expr = lastStr\n\t\t\treturn\n\t\t}\n\t\ttrimLeftSpace(subExprNode)\n\t\tif len(*subExprNode) == 0 {\n\t\t\tfound = true\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc newFunc(funcName string, fn func(...interface{}) interface{}) func(*Expr, *string) ExprNode {\n\treturn func(p *Expr, expr *string) ExprNode {\n\t\tboolOpposite, signOpposite, args, found := p.parseFuncSign(funcName, expr)\n\t\tif !found {\n\t\t\treturn nil\n\t\t}\n\t\treturn &funcExprNode{\n\t\t\tfn:           fn,\n\t\t\tboolOpposite: boolOpposite,\n\t\t\tsignOpposite: signOpposite,\n\t\t\targs:         args,\n\t\t}\n\t}\n}\n\ntype funcExprNode struct {\n\texprBackground\n\targs         []ExprNode\n\tfn           func(...interface{}) interface{}\n\tboolOpposite *bool\n\tsignOpposite *bool\n}\n\nfunc (f *funcExprNode) String() string {\n\treturn \"func()\"\n}\n\nfunc (f *funcExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tvar args []interface{}\n\tif n := len(f.args); n > 0 {\n\t\targs = make([]interface{}, n)\n\t\tfor k, v := range f.args {\n\t\t\targs[k] = v.Run(ctx, currField, tagExpr)\n\t\t}\n\t}\n\treturn realValue(f.fn(args...), f.boolOpposite, f.signOpposite)\n}\n\n// --------------------------- Built-in function ---------------------------\nfunc init() {\n\tfuncList[\"regexp\"] = readRegexpFuncExprNode\n\tfuncList[\"sprintf\"] = readSprintfFuncExprNode\n\tfuncList[\"range\"] = readRangeFuncExprNode\n\t// len: Built-in function len, the length of struct field X\n\tMustRegFunc(\"len\", func(args ...interface{}) (n interface{}) {\n\t\tif len(args) != 1 {\n\t\t\treturn 0\n\t\t}\n\t\tv := args[0]\n\t\tswitch e := v.(type) {\n\t\tcase string:\n\t\t\treturn float64(len(e))\n\t\tcase float64, bool, nil:\n\t\t\treturn 0\n\t\t}\n\t\tdefer func() {\n\t\t\tif recover() != nil {\n\t\t\t\tn = 0\n\t\t\t}\n\t\t}()\n\t\treturn float64(reflect.ValueOf(v).Len())\n\t}, true)\n\t// mblen: get the length of string field X (character number)\n\tMustRegFunc(\"mblen\", func(args ...interface{}) (n interface{}) {\n\t\tif len(args) != 1 {\n\t\t\treturn 0\n\t\t}\n\t\tv := args[0]\n\t\tswitch e := v.(type) {\n\t\tcase string:\n\t\t\treturn float64(len([]rune(e)))\n\t\tcase float64, bool, nil:\n\t\t\treturn 0\n\t\t}\n\t\tdefer func() {\n\t\t\tif recover() != nil {\n\t\t\t\tn = 0\n\t\t\t}\n\t\t}()\n\t\treturn float64(reflect.ValueOf(v).Len())\n\t}, true)\n\n\t// in: Check if the first parameter is one of the enumerated parameters\n\tMustRegFunc(\"in\", func(args ...interface{}) interface{} {\n\t\tswitch len(args) {\n\t\tcase 0:\n\t\t\treturn true\n\t\tcase 1:\n\t\t\treturn false\n\t\tdefault:\n\t\t\telem := args[0]\n\t\t\tset := args[1:]\n\t\t\tfor _, e := range set {\n\t\t\t\tif elem == e {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t}, true)\n}\n\ntype regexpFuncExprNode struct {\n\texprBackground\n\tre           *regexp.Regexp\n\tboolOpposite bool\n}\n\nfunc (re *regexpFuncExprNode) String() string {\n\treturn \"regexp()\"\n}\n\nfunc readRegexpFuncExprNode(p *Expr, expr *string) ExprNode {\n\tlast, boolOpposite, _ := getBoolAndSignOpposite(expr)\n\tif !strings.HasPrefix(last, \"regexp(\") {\n\t\treturn nil\n\t}\n\t*expr = last[6:]\n\tlastStr := *expr\n\tsubExprNode := readPairedSymbol(expr, '(', ')')\n\tif subExprNode == nil {\n\t\treturn nil\n\t}\n\ts := readPairedSymbol(trimLeftSpace(subExprNode), '\\'', '\\'')\n\tif s == nil {\n\t\t*expr = lastStr\n\t\treturn nil\n\t}\n\trege, err := regexp.Compile(*s)\n\tif err != nil {\n\t\t*expr = lastStr\n\t\treturn nil\n\t}\n\toperand := newGroupExprNode()\n\ttrimLeftSpace(subExprNode)\n\tif strings.HasPrefix(*subExprNode, \",\") {\n\t\t*subExprNode = (*subExprNode)[1:]\n\t\terr = p.parseExprNode(trimLeftSpace(subExprNode), operand)\n\t\tif err != nil {\n\t\t\t*expr = lastStr\n\t\t\treturn nil\n\t\t}\n\t} else {\n\t\tcurrFieldVal := \"$\"\n\t\tp.parseExprNode(&currFieldVal, operand)\n\t}\n\ttrimLeftSpace(subExprNode)\n\tif *subExprNode != \"\" {\n\t\t*expr = lastStr\n\t\treturn nil\n\t}\n\te := &regexpFuncExprNode{\n\t\tre: rege,\n\t}\n\tif boolOpposite != nil {\n\t\te.boolOpposite = *boolOpposite\n\t}\n\te.SetRightOperand(operand)\n\treturn e\n}\n\nfunc (re *regexpFuncExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tparam := re.rightOperand.Run(ctx, currField, tagExpr)\n\tswitch v := param.(type) {\n\tcase string:\n\t\tbol := re.re.MatchString(v)\n\t\tif re.boolOpposite {\n\t\t\treturn !bol\n\t\t}\n\t\treturn bol\n\tcase float64, bool:\n\t\treturn false\n\t}\n\tv := reflect.ValueOf(param)\n\tif v.Kind() == reflect.String {\n\t\tbol := re.re.MatchString(v.String())\n\t\tif re.boolOpposite {\n\t\t\treturn !bol\n\t\t}\n\t\treturn bol\n\t}\n\treturn false\n}\n\ntype sprintfFuncExprNode struct {\n\texprBackground\n\tformat string\n\targs   []ExprNode\n}\n\nfunc (se *sprintfFuncExprNode) String() string {\n\treturn \"sprintf()\"\n}\n\nfunc readSprintfFuncExprNode(p *Expr, expr *string) ExprNode {\n\tif !strings.HasPrefix(*expr, \"sprintf(\") {\n\t\treturn nil\n\t}\n\t*expr = (*expr)[7:]\n\tlastStr := *expr\n\tsubExprNode := readPairedSymbol(expr, '(', ')')\n\tif subExprNode == nil {\n\t\treturn nil\n\t}\n\tformat := readPairedSymbol(trimLeftSpace(subExprNode), '\\'', '\\'')\n\tif format == nil {\n\t\t*expr = lastStr\n\t\treturn nil\n\t}\n\te := &sprintfFuncExprNode{\n\t\tformat: *format,\n\t}\n\tfor {\n\t\ttrimLeftSpace(subExprNode)\n\t\tif len(*subExprNode) == 0 {\n\t\t\treturn e\n\t\t}\n\t\tif strings.HasPrefix(*subExprNode, \",\") {\n\t\t\t*subExprNode = (*subExprNode)[1:]\n\t\t\toperand := newGroupExprNode()\n\t\t\terr := p.parseExprNode(trimLeftSpace(subExprNode), operand)\n\t\t\tif err != nil {\n\t\t\t\t*expr = lastStr\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tsortPriority(operand)\n\t\t\te.args = append(e.args, operand)\n\t\t} else {\n\t\t\t*expr = lastStr\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (se *sprintfFuncExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tvar args []interface{}\n\tif n := len(se.args); n > 0 {\n\t\targs = make([]interface{}, n)\n\t\tfor i, e := range se.args {\n\t\t\targs[i] = e.Run(ctx, currField, tagExpr)\n\t\t}\n\t}\n\treturn fmt.Sprintf(se.format, args...)\n}\n"
  },
  {
    "path": "internal/tagexpr/spec_func_test.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr_test\n\nimport (\n\t\"reflect\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/tagexpr\"\n)\n\nfunc TestFunc(t *testing.T) {\n\temailRegexp := regexp.MustCompile(\n\t\t\"^([A-Za-z0-9_\\\\-\\\\.\\u4e00-\\u9fa5])+\\\\@([A-Za-z0-9_\\\\-\\\\.])+\\\\.([A-Za-z]{2,8})$\",\n\t)\n\ttagexpr.RegFunc(\"email\", func(args ...interface{}) interface{} {\n\t\tif len(args) == 0 {\n\t\t\treturn false\n\t\t}\n\t\ts, ok := args[0].(string)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tt.Log(s)\n\t\treturn emailRegexp.MatchString(s)\n\t})\n\n\tvm := tagexpr.New(\"te\")\n\n\ttype T struct {\n\t\tEmail string `te:\"email($)\"`\n\t}\n\tcases := []struct {\n\t\temail  string\n\t\texpect bool\n\t}{\n\t\t{\"\", false},\n\t\t{\"henrylee2cn@gmail.com\", true},\n\t}\n\n\tobj := new(T)\n\tfor _, c := range cases {\n\t\tobj.Email = c.email\n\t\tte := vm.MustRun(obj)\n\t\tgot := te.EvalBool(\"Email\")\n\t\tif got != c.expect {\n\t\t\tt.Fatalf(\"email: %s, expect: %v, but got: %v\", c.email, c.expect, got)\n\t\t}\n\t}\n\n\t// test len\n\ttype R struct {\n\t\tStr string `vd:\"mblen($)<6\"`\n\t}\n\tlenCases := []struct {\n\t\tstr    string\n\t\texpect bool\n\t}{\n\t\t{\"123\", true},\n\t\t{\"一二三四五六七\", false},\n\t\t{\"一二三四五\", true},\n\t}\n\n\tlenObj := new(R)\n\tvm = tagexpr.New(\"vd\")\n\tfor _, lenCase := range lenCases {\n\t\tlenObj.Str = lenCase.str\n\t\tte := vm.MustRun(lenObj)\n\t\tgot := te.EvalBool(\"Str\")\n\t\tif got != lenCase.expect {\n\t\t\tt.Fatalf(\"string: %v, expect: %v, but got: %v\", lenCase.str, lenCase.expect, got)\n\t\t}\n\t}\n}\n\nfunc TestRangeIn(t *testing.T) {\n\tvm := tagexpr.New(\"te\")\n\ttype S struct {\n\t\tF []string `te:\"range($, in(#v, '', 'ttp', 'euttp'))\"`\n\t}\n\ta := []string{\"ttp\", \"\", \"euttp\"}\n\tr := vm.MustRun(S{\n\t\tF: a,\n\t\t// F: b,\n\t})\n\texpect := []interface{}{true, true, true}\n\tactual := r.Eval(\"F\")\n\tif !reflect.DeepEqual(expect, actual) {\n\t\tt.Fatal(\"not equal\", expect, actual)\n\t}\n}\n"
  },
  {
    "path": "internal/tagexpr/spec_operand.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// --------------------------- Operand ---------------------------\n\ntype groupExprNode struct {\n\texprBackground\n\tboolOpposite *bool\n\tsignOpposite *bool\n}\n\nfunc newGroupExprNode() ExprNode { return &groupExprNode{} }\n\nfunc readGroupExprNode(expr *string) (grp ExprNode, subExprNode *string) {\n\tlast, boolOpposite, signOpposite := getBoolAndSignOpposite(expr)\n\tsptr := readPairedSymbol(&last, '(', ')')\n\tif sptr == nil {\n\t\treturn nil, nil\n\t}\n\t*expr = last\n\te := &groupExprNode{boolOpposite: boolOpposite, signOpposite: signOpposite}\n\treturn e, sptr\n}\n\nfunc (ge *groupExprNode) String() string {\n\treturn \"()\"\n}\n\nfunc (ge *groupExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tif ge.rightOperand == nil {\n\t\treturn nil\n\t}\n\treturn realValue(ge.rightOperand.Run(ctx, currField, tagExpr), ge.boolOpposite, ge.signOpposite)\n}\n\ntype boolExprNode struct {\n\texprBackground\n\tval bool\n}\n\nfunc (be *boolExprNode) String() string {\n\treturn fmt.Sprintf(\"%v\", be.val)\n}\n\nvar boolRegexp = regexp.MustCompile(`^!*(true|false)([\\)\\],\\|&!= \\t]{1}|$)`)\n\nfunc readBoolExprNode(expr *string) ExprNode {\n\ts := boolRegexp.FindString(*expr)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tlast := s[len(s)-1]\n\tif last != 'e' {\n\t\ts = s[:len(s)-1]\n\t}\n\t*expr = (*expr)[len(s):]\n\te := &boolExprNode{}\n\tif strings.Contains(s, \"t\") {\n\t\te.val = (len(s)-4)&1 == 0\n\t} else {\n\t\te.val = (len(s)-5)&1 == 1\n\t}\n\treturn e\n}\n\nfunc (be *boolExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\treturn be.val\n}\n\ntype stringExprNode struct {\n\texprBackground\n\tval interface{}\n}\n\nfunc (se *stringExprNode) String() string {\n\treturn fmt.Sprintf(\"%v\", se.val)\n}\n\nfunc readStringExprNode(expr *string) ExprNode {\n\tlast, boolOpposite, _ := getBoolAndSignOpposite(expr)\n\tsptr := readPairedSymbol(&last, '\\'', '\\'')\n\tif sptr == nil {\n\t\treturn nil\n\t}\n\t*expr = last\n\te := &stringExprNode{val: realValue(*sptr, boolOpposite, nil)}\n\treturn e\n}\n\nfunc (se *stringExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\treturn se.val\n}\n\ntype digitalExprNode struct {\n\texprBackground\n\tval interface{}\n}\n\nfunc (de *digitalExprNode) String() string {\n\treturn fmt.Sprintf(\"%v\", de.val)\n}\n\nvar digitalRegexp = regexp.MustCompile(`^[\\+\\-]?\\d+(\\.\\d+)?([\\)\\],\\+\\-\\*\\/%><\\|&!=\\^ \\t\\\\]|$)`)\n\nfunc readDigitalExprNode(expr *string) ExprNode {\n\tlast, boolOpposite := getOpposite(expr, \"!\")\n\ts := digitalRegexp.FindString(last)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tif r := s[len(s)-1]; r < '0' || r > '9' {\n\t\ts = s[:len(s)-1]\n\t}\n\t*expr = last[len(s):]\n\tf64, _ := strconv.ParseFloat(s, 64)\n\treturn &digitalExprNode{val: realValue(f64, boolOpposite, nil)}\n}\n\nfunc (de *digitalExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\treturn de.val\n}\n\ntype nilExprNode struct {\n\texprBackground\n\tval interface{}\n}\n\nfunc (ne *nilExprNode) String() string {\n\treturn \"<nil>\"\n}\n\nvar nilRegexp = regexp.MustCompile(`^nil([\\)\\],\\|&!= \\t]{1}|$)`)\n\nfunc readNilExprNode(expr *string) ExprNode {\n\tlast, boolOpposite := getOpposite(expr, \"!\")\n\ts := nilRegexp.FindString(last)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\t*expr = last[3:]\n\treturn &nilExprNode{val: realValue(nil, boolOpposite, nil)}\n}\n\nfunc (ne *nilExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\treturn ne.val\n}\n\ntype variableExprNode struct {\n\texprBackground\n\tboolOpposite *bool\n\tval          string\n}\n\nfunc (ve *variableExprNode) String() string {\n\treturn fmt.Sprintf(\"%v\", ve.val)\n}\n\nfunc (ve *variableExprNode) Run(ctx context.Context, variableName string, _ *TagExpr) interface{} {\n\tenvObj := ctx.Value(variableKey)\n\tif envObj == nil {\n\t\treturn nil\n\t}\n\n\tenv := envObj.(map[string]interface{})\n\tif len(env) == 0 {\n\t\treturn nil\n\t}\n\n\tif value, ok := env[ve.val]; ok && value != nil {\n\t\treturn realValue(value, ve.boolOpposite, nil)\n\t} else {\n\t\treturn nil\n\t}\n}\n\nvar variableRegex = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*`)\n\nfunc readVariableExprNode(expr *string) ExprNode {\n\tlast, boolOpposite := getOpposite(expr, \"!\")\n\tvariable := variableRegex.FindString(last)\n\tif variable == \"\" {\n\t\treturn nil\n\t}\n\n\t*expr = (*expr)[len(*expr)-len(last)+len(variable):]\n\n\treturn &variableExprNode{\n\t\tval:          variable,\n\t\tboolOpposite: boolOpposite,\n\t}\n}\n\nfunc getBoolAndSignOpposite(expr *string) (last string, boolOpposite *bool, signOpposite *bool) {\n\tlast, boolOpposite = getOpposite(expr, \"!\")\n\tlast = strings.TrimLeft(last, \"+\")\n\tlast, signOpposite = getOpposite(&last, \"-\")\n\tlast = strings.TrimLeft(last, \"+\")\n\treturn\n}\n\nfunc getOpposite(expr *string, cutset string) (string, *bool) {\n\tlast := strings.TrimLeft(*expr, cutset)\n\tn := len(*expr) - len(last)\n\tif n == 0 {\n\t\treturn last, nil\n\t}\n\tbol := n&1 == 1\n\treturn last, &bol\n}\n\nfunc toString(i interface{}, enforce bool) (string, bool) {\n\tswitch vv := i.(type) {\n\tcase string:\n\t\treturn vv, true\n\tcase nil:\n\t\treturn \"\", false\n\tdefault:\n\t\trv := dereferenceValue(reflect.ValueOf(i))\n\t\tif rv.Kind() == reflect.String {\n\t\t\treturn rv.String(), true\n\t\t}\n\t\tif enforce {\n\t\t\tif rv.IsValid() && rv.CanInterface() {\n\t\t\t\treturn fmt.Sprint(rv.Interface()), true\n\t\t\t} else {\n\t\t\t\treturn fmt.Sprint(i), true\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc toFloat64(i interface{}, tryParse bool) (float64, bool) {\n\tvar v float64\n\tok := true\n\tswitch t := i.(type) {\n\tcase float64:\n\t\tv = t\n\tcase float32:\n\t\tv = float64(t)\n\tcase int:\n\t\tv = float64(t)\n\tcase int8:\n\t\tv = float64(t)\n\tcase int16:\n\t\tv = float64(t)\n\tcase int32:\n\t\tv = float64(t)\n\tcase int64:\n\t\tv = float64(t)\n\tcase uint:\n\t\tv = float64(t)\n\tcase uint8:\n\t\tv = float64(t)\n\tcase uint16:\n\t\tv = float64(t)\n\tcase uint32:\n\t\tv = float64(t)\n\tcase uint64:\n\t\tv = float64(t)\n\tcase nil:\n\t\tok = false\n\tdefault:\n\t\trv := dereferenceValue(reflect.ValueOf(t))\n\t\tswitch rv.Kind() {\n\t\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\t\tv = float64(rv.Int())\n\t\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:\n\t\t\tv = float64(rv.Uint())\n\t\tcase reflect.Float32, reflect.Float64:\n\t\t\tv = rv.Float()\n\t\tdefault:\n\t\t\tif tryParse {\n\t\t\t\tif s, ok := toString(i, false); ok {\n\t\t\t\t\tvar err error\n\t\t\t\t\tv, err = strconv.ParseFloat(s, 64)\n\t\t\t\t\treturn v, err == nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tok = false\n\t\t}\n\t}\n\treturn v, ok\n}\n\nfunc realValue(v interface{}, boolOpposite *bool, signOpposite *bool) interface{} {\n\tif boolOpposite != nil {\n\t\tbol := FakeBool(v)\n\t\tif *boolOpposite {\n\t\t\treturn !bol\n\t\t}\n\t\treturn bol\n\t}\n\tswitch t := v.(type) {\n\tcase float64, string:\n\tcase float32:\n\t\tv = float64(t)\n\tcase int:\n\t\tv = float64(t)\n\tcase int8:\n\t\tv = float64(t)\n\tcase int16:\n\t\tv = float64(t)\n\tcase int32:\n\t\tv = float64(t)\n\tcase int64:\n\t\tv = float64(t)\n\tcase uint:\n\t\tv = float64(t)\n\tcase uint8:\n\t\tv = float64(t)\n\tcase uint16:\n\t\tv = float64(t)\n\tcase uint32:\n\t\tv = float64(t)\n\tcase uint64:\n\t\tv = float64(t)\n\tcase []interface{}:\n\t\tfor k, v := range t {\n\t\t\tt[k] = realValue(v, boolOpposite, signOpposite)\n\t\t}\n\tdefault:\n\t\trv := dereferenceValue(reflect.ValueOf(v))\n\t\tswitch rv.Kind() {\n\t\tcase reflect.String:\n\t\t\tv = rv.String()\n\t\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\t\tv = float64(rv.Int())\n\t\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:\n\t\t\tv = float64(rv.Uint())\n\t\tcase reflect.Float32, reflect.Float64:\n\t\t\tv = rv.Float()\n\t\t}\n\t}\n\tif signOpposite != nil && *signOpposite {\n\t\tif f, ok := v.(float64); ok {\n\t\t\tv = -f\n\t\t}\n\t}\n\treturn v\n}\n"
  },
  {
    "path": "internal/tagexpr/spec_operator.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"context\"\n\t\"math\"\n)\n\n// --------------------------- Operator ---------------------------\n\ntype additionExprNode struct{ exprBackground }\n\nfunc (ae *additionExprNode) String() string {\n\treturn \"+\"\n}\n\nfunc newAdditionExprNode() ExprNode { return &additionExprNode{} }\n\nfunc (ae *additionExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\t// positive number or Addition\n\tv0 := ae.leftOperand.Run(ctx, currField, tagExpr)\n\tv1 := ae.rightOperand.Run(ctx, currField, tagExpr)\n\tif s0, ok := toFloat64(v0, false); ok {\n\t\ts1, _ := toFloat64(v1, true)\n\t\treturn s0 + s1\n\t}\n\tif s0, ok := toString(v0, false); ok {\n\t\ts1, _ := toString(v1, true)\n\t\treturn s0 + s1\n\t}\n\treturn v0\n}\n\ntype multiplicationExprNode struct{ exprBackground }\n\nfunc (ae *multiplicationExprNode) String() string {\n\treturn \"*\"\n}\n\nfunc newMultiplicationExprNode() ExprNode { return &multiplicationExprNode{} }\n\nfunc (ae *multiplicationExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tv0, _ := toFloat64(ae.leftOperand.Run(ctx, currField, tagExpr), true)\n\tv1, _ := toFloat64(ae.rightOperand.Run(ctx, currField, tagExpr), true)\n\treturn v0 * v1\n}\n\ntype divisionExprNode struct{ exprBackground }\n\nfunc (de *divisionExprNode) String() string {\n\treturn \"/\"\n}\n\nfunc newDivisionExprNode() ExprNode { return &divisionExprNode{} }\n\nfunc (de *divisionExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tv1, _ := toFloat64(de.rightOperand.Run(ctx, currField, tagExpr), true)\n\tif v1 == 0 {\n\t\treturn math.NaN()\n\t}\n\tv0, _ := toFloat64(de.leftOperand.Run(ctx, currField, tagExpr), true)\n\treturn v0 / v1\n}\n\ntype subtractionExprNode struct{ exprBackground }\n\nfunc (de *subtractionExprNode) String() string {\n\treturn \"-\"\n}\n\nfunc newSubtractionExprNode() ExprNode { return &subtractionExprNode{} }\n\nfunc (de *subtractionExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tv0, _ := toFloat64(de.leftOperand.Run(ctx, currField, tagExpr), true)\n\tv1, _ := toFloat64(de.rightOperand.Run(ctx, currField, tagExpr), true)\n\treturn v0 - v1\n}\n\ntype remainderExprNode struct{ exprBackground }\n\nfunc (re *remainderExprNode) String() string {\n\treturn \"%\"\n}\n\nfunc newRemainderExprNode() ExprNode { return &remainderExprNode{} }\n\nfunc (re *remainderExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tv1, _ := toFloat64(re.rightOperand.Run(ctx, currField, tagExpr), true)\n\tif v1 == 0 {\n\t\treturn math.NaN()\n\t}\n\tv0, _ := toFloat64(re.leftOperand.Run(ctx, currField, tagExpr), true)\n\treturn float64(int64(v0) % int64(v1))\n}\n\ntype equalExprNode struct{ exprBackground }\n\nfunc (ee *equalExprNode) String() string {\n\treturn \"==\"\n}\n\nfunc newEqualExprNode() ExprNode { return &equalExprNode{} }\n\nfunc (ee *equalExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tv0 := ee.leftOperand.Run(ctx, currField, tagExpr)\n\tv1 := ee.rightOperand.Run(ctx, currField, tagExpr)\n\tif v0 == v1 {\n\t\treturn true\n\t}\n\tif s0, ok := toFloat64(v0, false); ok {\n\t\tif s1, ok := toFloat64(v1, true); ok {\n\t\t\treturn s0 == s1\n\t\t}\n\t}\n\tif s0, ok := toString(v0, false); ok {\n\t\tif s1, ok := toString(v1, true); ok {\n\t\t\treturn s0 == s1\n\t\t}\n\t\treturn false\n\t}\n\tswitch r := v0.(type) {\n\tcase bool:\n\t\tr1, ok := v1.(bool)\n\t\tif ok {\n\t\t\treturn r == r1\n\t\t}\n\tcase nil:\n\t\treturn v1 == nil\n\t}\n\treturn false\n}\n\ntype notEqualExprNode struct{ equalExprNode }\n\nfunc (ne *notEqualExprNode) String() string {\n\treturn \"!=\"\n}\n\nfunc newNotEqualExprNode() ExprNode { return &notEqualExprNode{} }\n\nfunc (ne *notEqualExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\treturn !ne.equalExprNode.Run(ctx, currField, tagExpr).(bool)\n}\n\ntype greaterExprNode struct{ exprBackground }\n\nfunc (ge *greaterExprNode) String() string {\n\treturn \">\"\n}\n\nfunc newGreaterExprNode() ExprNode { return &greaterExprNode{} }\n\nfunc (ge *greaterExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tv0 := ge.leftOperand.Run(ctx, currField, tagExpr)\n\tv1 := ge.rightOperand.Run(ctx, currField, tagExpr)\n\tif s0, ok := toFloat64(v0, false); ok {\n\t\tif s1, ok := toFloat64(v1, true); ok {\n\t\t\treturn s0 > s1\n\t\t}\n\t}\n\tif s0, ok := toString(v0, false); ok {\n\t\tif s1, ok := toString(v1, true); ok {\n\t\t\treturn s0 > s1\n\t\t}\n\t\treturn false\n\t}\n\treturn false\n}\n\ntype greaterEqualExprNode struct{ exprBackground }\n\nfunc (ge *greaterEqualExprNode) String() string {\n\treturn \">=\"\n}\n\nfunc newGreaterEqualExprNode() ExprNode { return &greaterEqualExprNode{} }\n\nfunc (ge *greaterEqualExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tv0 := ge.leftOperand.Run(ctx, currField, tagExpr)\n\tv1 := ge.rightOperand.Run(ctx, currField, tagExpr)\n\tif s0, ok := toFloat64(v0, false); ok {\n\t\tif s1, ok := toFloat64(v1, true); ok {\n\t\t\treturn s0 >= s1\n\t\t}\n\t}\n\tif s0, ok := toString(v0, false); ok {\n\t\tif s1, ok := toString(v1, true); ok {\n\t\t\treturn s0 >= s1\n\t\t}\n\t\treturn false\n\t}\n\treturn false\n}\n\ntype lessExprNode struct{ exprBackground }\n\nfunc (le *lessExprNode) String() string {\n\treturn \"<\"\n}\n\nfunc newLessExprNode() ExprNode { return &lessExprNode{} }\n\nfunc (le *lessExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tv0 := le.leftOperand.Run(ctx, currField, tagExpr)\n\tv1 := le.rightOperand.Run(ctx, currField, tagExpr)\n\tif s0, ok := toFloat64(v0, false); ok {\n\t\tif s1, ok := toFloat64(v1, true); ok {\n\t\t\treturn s0 < s1\n\t\t}\n\t}\n\tif s0, ok := toString(v0, false); ok {\n\t\tif s1, ok := toString(v1, true); ok {\n\t\t\treturn s0 < s1\n\t\t}\n\t\treturn false\n\t}\n\treturn false\n}\n\ntype lessEqualExprNode struct{ exprBackground }\n\nfunc (le *lessEqualExprNode) String() string {\n\treturn \"<=\"\n}\n\nfunc newLessEqualExprNode() ExprNode { return &lessEqualExprNode{} }\n\nfunc (le *lessEqualExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tv0 := le.leftOperand.Run(ctx, currField, tagExpr)\n\tv1 := le.rightOperand.Run(ctx, currField, tagExpr)\n\tif s0, ok := toFloat64(v0, false); ok {\n\t\tif s1, ok := toFloat64(v1, true); ok {\n\t\t\treturn s0 <= s1\n\t\t}\n\t}\n\tif s0, ok := toString(v0, false); ok {\n\t\tif s1, ok := toString(v1, true); ok {\n\t\t\treturn s0 <= s1\n\t\t}\n\t\treturn false\n\t}\n\treturn false\n}\n\ntype andExprNode struct{ exprBackground }\n\nfunc (ae *andExprNode) String() string {\n\treturn \"&&\"\n}\n\nfunc newAndExprNode() ExprNode { return &andExprNode{} }\n\nfunc (ae *andExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tfor _, e := range [2]ExprNode{ae.leftOperand, ae.rightOperand} {\n\t\tif !FakeBool(e.Run(ctx, currField, tagExpr)) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\ntype orExprNode struct{ exprBackground }\n\nfunc (oe *orExprNode) String() string {\n\treturn \"||\"\n}\n\nfunc newOrExprNode() ExprNode { return &orExprNode{} }\n\nfunc (oe *orExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tfor _, e := range [2]ExprNode{oe.leftOperand, oe.rightOperand} {\n\t\tif FakeBool(e.Run(ctx, currField, tagExpr)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "internal/tagexpr/spec_range.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"regexp\"\n)\n\ntype rangeCtxKey string\n\nconst (\n\trangeKey   rangeCtxKey = \"#k\"\n\trangeValue rangeCtxKey = \"#v\"\n\trangeLen   rangeCtxKey = \"##\"\n)\n\ntype rangeKvExprNode struct {\n\texprBackground\n\tctxKey       rangeCtxKey\n\tboolOpposite *bool\n\tsignOpposite *bool\n}\n\nfunc (re *rangeKvExprNode) String() string {\n\treturn string(re.ctxKey)\n}\n\nfunc (p *Expr) readRangeKvExprNode(expr *string) ExprNode {\n\tname, boolOpposite, signOpposite, found := findRangeKv(expr)\n\tif !found {\n\t\treturn nil\n\t}\n\toperand := &rangeKvExprNode{\n\t\tctxKey:       rangeCtxKey(name),\n\t\tboolOpposite: boolOpposite,\n\t\tsignOpposite: signOpposite,\n\t}\n\t// fmt.Printf(\"operand: %#v\\n\", operand)\n\treturn operand\n}\n\nvar rangeKvRegexp = regexp.MustCompile(`^([\\!\\+\\-]*)(#[kv#])([\\)\\[\\],\\+\\-\\*\\/%><\\|&!=\\^ \\t\\\\]|$)`)\n\nfunc findRangeKv(expr *string) (name string, boolOpposite, signOpposite *bool, found bool) {\n\traw := *expr\n\ta := rangeKvRegexp.FindAllStringSubmatch(raw, -1)\n\tif len(a) != 1 {\n\t\treturn\n\t}\n\tr := a[0]\n\tname = r[2]\n\t*expr = (*expr)[len(a[0][0])-len(r[3]):]\n\tprefix := r[1]\n\tif len(prefix) == 0 {\n\t\tfound = true\n\t\treturn\n\t}\n\t_, boolOpposite, signOpposite = getBoolAndSignOpposite(&prefix)\n\tfound = true\n\treturn\n}\n\nfunc (re *rangeKvExprNode) Run(ctx context.Context, _ string, _ *TagExpr) interface{} {\n\tvar v interface{}\n\tswitch val := ctx.Value(re.ctxKey).(type) {\n\tcase reflect.Value:\n\t\tif !val.IsValid() || !val.CanInterface() {\n\t\t\treturn nil\n\t\t}\n\t\tv = val.Interface()\n\tdefault:\n\t\tv = val\n\t}\n\treturn realValue(v, re.boolOpposite, re.signOpposite)\n}\n\ntype rangeFuncExprNode struct {\n\texprBackground\n\tobject       ExprNode\n\telemExprNode ExprNode\n\tboolOpposite *bool\n\tsignOpposite *bool\n}\n\nfunc (e *rangeFuncExprNode) String() string {\n\treturn \"range()\"\n}\n\n// range($, gt($v,10))\n// range($, $v>10)\nfunc readRangeFuncExprNode(p *Expr, expr *string) ExprNode {\n\tboolOpposite, signOpposite, args, found := p.parseFuncSign(\"range\", expr)\n\tif !found {\n\t\treturn nil\n\t}\n\tif len(args) != 2 {\n\t\treturn nil\n\t}\n\treturn &rangeFuncExprNode{\n\t\tboolOpposite: boolOpposite,\n\t\tsignOpposite: signOpposite,\n\t\tobject:       args[0],\n\t\telemExprNode: args[1],\n\t}\n}\n\nfunc (e *rangeFuncExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tvar r []interface{}\n\tobj := e.object.Run(ctx, currField, tagExpr)\n\t// fmt.Printf(\"%v\\n\", obj)\n\tobjval := reflect.ValueOf(obj)\n\tswitch objval.Kind() {\n\tcase reflect.Array, reflect.Slice:\n\t\tcount := objval.Len()\n\t\tr = make([]interface{}, count)\n\t\tctx = context.WithValue(ctx, rangeLen, count)\n\t\tfor i := 0; i < count; i++ {\n\t\t\t// fmt.Printf(\"%#v,  (%v)\\n\", e.elemExprNode, objval.Index(i))\n\t\t\tr[i] = realValue(e.elemExprNode.Run(\n\t\t\t\tcontext.WithValue(\n\t\t\t\t\tcontext.WithValue(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\trangeKey, i,\n\t\t\t\t\t),\n\t\t\t\t\trangeValue, objval.Index(i),\n\t\t\t\t),\n\t\t\t\tcurrField, tagExpr,\n\t\t\t), e.boolOpposite, e.signOpposite)\n\t\t}\n\tcase reflect.Map:\n\t\tkeys := objval.MapKeys()\n\t\tcount := len(keys)\n\t\tr = make([]interface{}, count)\n\t\tctx = context.WithValue(ctx, rangeLen, count)\n\t\tfor i, key := range keys {\n\t\t\tr[i] = realValue(e.elemExprNode.Run(\n\t\t\t\tcontext.WithValue(\n\t\t\t\t\tcontext.WithValue(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\trangeKey, key,\n\t\t\t\t\t),\n\t\t\t\t\trangeValue, objval.MapIndex(key),\n\t\t\t\t),\n\t\t\t\tcurrField, tagExpr,\n\t\t\t), e.boolOpposite, e.signOpposite)\n\t\t}\n\tdefault:\n\t}\n\treturn r\n}\n"
  },
  {
    "path": "internal/tagexpr/spec_range_test.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr_test\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/tagexpr\"\n)\n\nfunc TestIssue12(t *testing.T) {\n\tvm := tagexpr.New(\"te\")\n\ttype I int\n\ttype S struct {\n\t\tF    []I              `te:\"range($, '>'+sprintf('%v:%v', #k, #v+2+len($)))\"`\n\t\tFs   [][]I            `te:\"range($, range(#v, '>'+sprintf('%v:%v', #k, #v+2+##)))\"`\n\t\tM    map[string]I     `te:\"range($, '>'+sprintf('%s:%v', #k, #v+2+##))\"`\n\t\tMFs  []map[string][]I `te:\"range($, range(#v, range(#v, '>'+sprintf('%v:%v', #k, #v+2+##))))\"`\n\t\tMFs2 []map[string][]I `te:\"range($, range(#v, range(#v, '>'+sprintf('%v:%v', #k, #v+2+##))))\"`\n\t}\n\ta := []I{2, 3}\n\tr := vm.MustRun(S{\n\t\tF:    a,\n\t\tFs:   [][]I{a},\n\t\tM:    map[string]I{\"m0\": 2, \"m1\": 3},\n\t\tMFs:  []map[string][]I{{\"m\": a}},\n\t\tMFs2: []map[string][]I{},\n\t})\n\tassertEqual(t, []interface{}{\">0:6\", \">1:7\"}, r.Eval(\"F\"))\n\tassertEqual(t, []interface{}{[]interface{}{\">0:6\", \">1:7\"}}, r.Eval(\"Fs\"))\n\tassertEqual(t, []interface{}{[]interface{}{[]interface{}{\">0:6\", \">1:7\"}}}, r.Eval(\"MFs\"))\n\tassertEqual(t, []interface{}{}, r.Eval(\"MFs2\"))\n\tassertEqual(t, true, r.EvalBool(\"MFs2\"))\n\n\t// result may not stable for map\n\tgot := r.Eval(\"M\")\n\tif !reflect.DeepEqual([]interface{}{\">m0:6\", \">m1:7\"}, got) &&\n\t\t!reflect.DeepEqual([]interface{}{\">m1:7\", \">m0:6\"}, got) {\n\t\tt.Fatal(got)\n\t}\n}\n"
  },
  {
    "path": "internal/tagexpr/spec_selector.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n)\n\ntype selectorExprNode struct {\n\texprBackground\n\tfield, name  string\n\tsubExprs     []ExprNode\n\tboolOpposite *bool\n\tsignOpposite *bool\n}\n\nfunc (se *selectorExprNode) String() string {\n\treturn fmt.Sprintf(\"(%s)%s\", se.field, se.name)\n}\n\nfunc (p *Expr) readSelectorExprNode(expr *string) ExprNode {\n\tfield, name, subSelector, boolOpposite, signOpposite, found := findSelector(expr)\n\tif !found {\n\t\treturn nil\n\t}\n\toperand := &selectorExprNode{\n\t\tfield:        field,\n\t\tname:         name,\n\t\tboolOpposite: boolOpposite,\n\t\tsignOpposite: signOpposite,\n\t}\n\toperand.subExprs = make([]ExprNode, 0, len(subSelector))\n\tfor _, s := range subSelector {\n\t\tgrp := newGroupExprNode()\n\t\terr := p.parseExprNode(&s, grp)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\tsortPriority(grp)\n\t\toperand.subExprs = append(operand.subExprs, grp)\n\t}\n\treturn operand\n}\n\nvar selectorRegexp = regexp.MustCompile(`^([\\!\\+\\-]*)(\\([ \\t]*[A-Za-z_]+[A-Za-z0-9_\\.]*[ \\t]*\\))?(\\$)([\\)\\[\\],\\+\\-\\*\\/%><\\|&!=\\^ \\t\\\\]|$)`)\n\nfunc findSelector(expr *string) (field string, name string, subSelector []string, boolOpposite, signOpposite *bool, found bool) {\n\traw := *expr\n\ta := selectorRegexp.FindAllStringSubmatch(raw, -1)\n\tif len(a) != 1 {\n\t\treturn\n\t}\n\tr := a[0]\n\tif s0 := r[2]; len(s0) > 0 {\n\t\tfield = strings.TrimSpace(s0[1 : len(s0)-1])\n\t}\n\tname = r[3]\n\t*expr = (*expr)[len(a[0][0])-len(r[4]):]\n\tfor {\n\t\tsub := readPairedSymbol(expr, '[', ']')\n\t\tif sub == nil {\n\t\t\tbreak\n\t\t}\n\t\tif *sub == \"\" || (*sub)[0] == '[' {\n\t\t\t*expr = raw\n\t\t\treturn \"\", \"\", nil, nil, nil, false\n\t\t}\n\t\tsubSelector = append(subSelector, strings.TrimSpace(*sub))\n\t}\n\tprefix := r[1]\n\tif len(prefix) == 0 {\n\t\tfound = true\n\t\treturn\n\t}\n\t_, boolOpposite, signOpposite = getBoolAndSignOpposite(&prefix)\n\tfound = true\n\treturn\n}\n\nfunc (se *selectorExprNode) Run(ctx context.Context, currField string, tagExpr *TagExpr) interface{} {\n\tvar subFields []interface{}\n\tif n := len(se.subExprs); n > 0 {\n\t\tsubFields = make([]interface{}, n)\n\t\tfor i, e := range se.subExprs {\n\t\t\tsubFields[i] = e.Run(ctx, currField, tagExpr)\n\t\t}\n\t}\n\tfield := se.field\n\tif field == \"\" {\n\t\tfield = currField\n\t}\n\tv := tagExpr.getValue(field, subFields)\n\treturn realValue(v, se.boolOpposite, se.signOpposite)\n}\n"
  },
  {
    "path": "internal/tagexpr/spec_test.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestReadPairedSymbol(t *testing.T) {\n\tcases := []struct {\n\t\tleft, right             rune\n\t\texpr, val, lastExprNode string\n\t}{\n\t\t{left: '\\'', right: '\\'', expr: \"'true '+'a'\", val: \"true \", lastExprNode: \"+'a'\"},\n\t\t{left: '(', right: ')', expr: \"((0+1)/(2-1)*9)%2\", val: \"(0+1)/(2-1)*9\", lastExprNode: \"%2\"},\n\t\t{left: '(', right: ')', expr: `(\\)\\(\\))`, val: `)()`},\n\t\t{left: '\\'', right: '\\'', expr: `'\\\\'`, val: `\\\\`},\n\t\t{left: '\\'', right: '\\'', expr: `'\\'\\''`, val: `''`},\n\t}\n\tfor _, c := range cases {\n\t\tt.Log(c.expr)\n\t\texpr := c.expr\n\t\tgot := readPairedSymbol(&expr, c.left, c.right)\n\t\tif got == nil {\n\t\t\tt.Fatalf(\"expr: %q, got: %v, %q, want: %q, %q\", c.expr, got, expr, c.val, c.lastExprNode)\n\t\t} else if *got != c.val || expr != c.lastExprNode {\n\t\t\tt.Fatalf(\"expr: %q, got: %q, %q, want: %q, %q\", c.expr, *got, expr, c.val, c.lastExprNode)\n\t\t}\n\t}\n}\n\nfunc TestReadBoolExprNode(t *testing.T) {\n\tcases := []struct {\n\t\texpr         string\n\t\tval          bool\n\t\tlastExprNode string\n\t}{\n\t\t{expr: \"false\", val: false, lastExprNode: \"\"},\n\t\t{expr: \"true\", val: true, lastExprNode: \"\"},\n\t\t{expr: \"true \", val: true, lastExprNode: \" \"},\n\t\t{expr: \"!true&\", val: false, lastExprNode: \"&\"},\n\t\t{expr: \"!false|\", val: true, lastExprNode: \"|\"},\n\t\t{expr: \"!!!!false =\", val: !!!!false, lastExprNode: \" =\"}, //nolint:staticcheck // SA4013: negating a boolean twice has no effect\n\t}\n\tfor _, c := range cases {\n\t\tt.Log(c.expr)\n\t\texpr := c.expr\n\t\te := readBoolExprNode(&expr)\n\t\tgot := e.Run(context.TODO(), \"\", nil).(bool)\n\t\tif got != c.val || expr != c.lastExprNode {\n\t\t\tt.Fatalf(\"expr: %s, got: %v, %s, want: %v, %s\", c.expr, got, expr, c.val, c.lastExprNode)\n\t\t}\n\t}\n}\n\nfunc TestReadDigitalExprNode(t *testing.T) {\n\tcases := []struct {\n\t\texpr         string\n\t\tval          float64\n\t\tlastExprNode string\n\t}{\n\t\t{expr: \"0.1 +1\", val: 0.1, lastExprNode: \" +1\"},\n\t\t{expr: \"-1\\\\1\", val: -1, lastExprNode: \"\\\\1\"},\n\t\t{expr: \"1a\", val: 0, lastExprNode: \"\"},\n\t\t{expr: \"1\", val: 1, lastExprNode: \"\"},\n\t\t{expr: \"1.1\", val: 1.1, lastExprNode: \"\"},\n\t\t{expr: \"1.1/\", val: 1.1, lastExprNode: \"/\"},\n\t}\n\tfor _, c := range cases {\n\t\texpr := c.expr\n\t\te := readDigitalExprNode(&expr)\n\t\tif c.expr == \"1a\" {\n\t\t\tif e != nil {\n\t\t\t\tt.Fatalf(\"expr: %s, got:%v, want:%v\", c.expr, e.Run(context.TODO(), \"\", nil), nil)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tgot := e.Run(context.TODO(), \"\", nil).(float64)\n\t\tif got != c.val || expr != c.lastExprNode {\n\t\t\tt.Fatalf(\"expr: %s, got: %f, %s, want: %f, %s\", c.expr, got, expr, c.val, c.lastExprNode)\n\t\t}\n\t}\n}\n\nfunc TestFindSelector(t *testing.T) {\n\tcases := []struct {\n\t\texpr         string\n\t\tfield        string\n\t\tname         string\n\t\tsubSelector  []string\n\t\tboolOpposite bool\n\t\tsignOpposite bool\n\t\tfound        bool\n\t\tlast         string\n\t}{\n\t\t{expr: \"$\", name: \"$\", found: true},\n\t\t{expr: \"!!$\", name: \"$\", found: true},\n\t\t{expr: \"!$\", name: \"$\", boolOpposite: true, found: true},\n\t\t{expr: \"+$\", name: \"$\", found: true},\n\t\t{expr: \"--$\", name: \"$\", found: true},\n\t\t{expr: \"-$\", name: \"$\", signOpposite: true, found: true},\n\t\t{expr: \"---$\", name: \"$\", signOpposite: true, found: true},\n\t\t{expr: \"()$\", last: \"()$\"},\n\t\t{expr: \"(0)$\", last: \"(0)$\"},\n\t\t{expr: \"(A)$\", field: \"A\", name: \"$\", found: true},\n\t\t{expr: \"+(A)$\", field: \"A\", name: \"$\", found: true},\n\t\t{expr: \"++(A)$\", field: \"A\", name: \"$\", found: true},\n\t\t{expr: \"!(A)$\", field: \"A\", name: \"$\", boolOpposite: true, found: true},\n\t\t{expr: \"-(A)$\", field: \"A\", name: \"$\", signOpposite: true, found: true},\n\t\t{expr: \"(A0)$\", field: \"A0\", name: \"$\", found: true},\n\t\t{expr: \"!!(A0)$\", field: \"A0\", name: \"$\", found: true},\n\t\t{expr: \"--(A0)$\", field: \"A0\", name: \"$\", found: true},\n\t\t{expr: \"(A0)$(A1)$\", last: \"(A0)$(A1)$\"},\n\t\t{expr: \"(A0)$ $(A1)$\", field: \"A0\", name: \"$\", found: true, last: \" $(A1)$\"},\n\t\t{expr: \"$a\", last: \"$a\"},\n\t\t{expr: \"$[1]['a']\", name: \"$\", subSelector: []string{\"1\", \"'a'\"}, found: true},\n\t\t{expr: \"$[1][]\", last: \"$[1][]\"},\n\t\t{expr: \"$[[]]\", last: \"$[[]]\"},\n\t\t{expr: \"$[[[]]]\", last: \"$[[[]]]\"},\n\t\t{expr: \"$[(A)$[1]]\", name: \"$\", subSelector: []string{\"(A)$[1]\"}, found: true},\n\t\t{expr: \"$>0&&$<10\", name: \"$\", found: true, last: \">0&&$<10\"},\n\t}\n\tfor _, c := range cases {\n\t\tlast := c.expr\n\t\tfield, name, subSelector, boolOpposite, signOpposite, found := findSelector(&last)\n\t\tif found != c.found {\n\t\t\tt.Fatalf(\"%q found: got: %v, want: %v\", c.expr, found, c.found)\n\t\t}\n\t\tif c.boolOpposite && (boolOpposite == nil || !*boolOpposite) {\n\t\t\tt.Fatalf(\"%q boolOpposite: got: %v, want: %v\", c.expr, boolOpposite, c.boolOpposite)\n\t\t}\n\t\tif c.signOpposite && (signOpposite == nil || !*signOpposite) {\n\t\t\tt.Fatalf(\"%q signOpposite: got: %v, want: %v\", c.expr, signOpposite, c.signOpposite)\n\t\t}\n\t\tif field != c.field {\n\t\t\tt.Fatalf(\"%q field: got: %q, want: %q\", c.expr, field, c.field)\n\t\t}\n\t\tif name != c.name {\n\t\t\tt.Fatalf(\"%q name: got: %q, want: %q\", c.expr, name, c.name)\n\t\t}\n\t\tif !reflect.DeepEqual(subSelector, c.subSelector) {\n\t\t\tt.Fatalf(\"%q subSelector: got: %v, want: %v\", c.expr, subSelector, c.subSelector)\n\t\t}\n\t\tif last != c.last {\n\t\t\tt.Fatalf(\"%q last: got: %q, want: %q\", c.expr, last, c.last)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/tagexpr/tagexpr.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//\thttp://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package tagexpr is an interesting go struct tag expression syntax for field validation, etc.\npackage tagexpr\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"unsafe\"\n)\n\n// Internally unified data types\ntype (\n\tNumber  = float64\n\tNull    = interface{}\n\tBoolean = bool\n\tString  = string\n)\n\n// VM struct tag expression interpreter\ntype VM struct {\n\ttagName   string\n\tstructJar map[uintptr]*structVM\n\trw        sync.RWMutex\n}\n\n// structVM tag expression set of struct\ntype structVM struct {\n\tvm                         *VM\n\tname                       string\n\tfields                     map[string]*fieldVM\n\tfieldSelectorList          []string\n\tfieldsWithIndirectStructVM []*fieldVM\n\texprs                      map[string]*Expr\n\texprSelectorList           []string\n\tifaceTagExprGetters        []func(unsafe.Pointer, string, func(*TagExpr, error) error) error\n\terr                        error\n}\n\n// fieldVM tag expression set of struct field\ntype fieldVM struct {\n\tstructField            reflect.StructField\n\tptrDeep                int\n\tgetPtr                 func(unsafe.Pointer) unsafe.Pointer\n\telemType               reflect.Type\n\telemKind               reflect.Kind\n\tvalueGetter            func(unsafe.Pointer) interface{}\n\treflectValueGetter     func(unsafe.Pointer, bool) reflect.Value\n\texprs                  map[string]*Expr\n\torigin                 *structVM\n\tmapKeyStructVM         *structVM\n\tmapOrSliceElemStructVM *structVM\n\tmapOrSliceIfaceKinds   [2]bool // [value, key/index]\n\tfieldSelector          string\n\ttagOp                  string\n}\n\n// New creates a tag expression interpreter that uses tagName as the tag name.\n// NOTE:\n//\n//\tIf no tagName is specified, no tag expression will be interpreted,\n//\tbut still can operate the various fields.\nfunc New(tagName ...string) *VM {\n\tif len(tagName) == 0 {\n\t\ttagName = append(tagName, \"\")\n\t}\n\treturn &VM{\n\t\ttagName:   tagName[0],\n\t\tstructJar: make(map[uintptr]*structVM, 256),\n\t}\n}\n\n// MustRun is similar to Run, but panic when error.\nfunc (vm *VM) MustRun(structOrStructPtrOrReflectValue interface{}) *TagExpr {\n\tte, err := vm.Run(structOrStructPtrOrReflectValue)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn te\n}\n\nvar (\n\tunsupportedNil        = errors.New(\"unsupported data: nil\")\n\tunsupportedCannotAddr = errors.New(\"unsupported data: can not addr\")\n)\n\n// Run returns the tag expression handler of the @structPtrOrReflectValue.\n// NOTE:\n//\n//\tIf the structure type has not been warmed up,\n//\tit will be slower when it is first called.\n//\n// Disable new -d=checkptr behaviour for Go 1.14\n//\n//go:nocheckptr\nfunc (vm *VM) Run(structPtrOrReflectValue interface{}) (*TagExpr, error) {\n\tvar v reflect.Value\n\tswitch t := structPtrOrReflectValue.(type) {\n\tcase reflect.Value:\n\t\tv = dereferenceValue(t)\n\tdefault:\n\t\tv = dereferenceValue(reflect.ValueOf(t))\n\t}\n\tif err := checkStructMapAddr(v); err != nil {\n\t\treturn nil, err\n\t}\n\n\tptr := rvPtr(v)\n\tif ptr == nil {\n\t\treturn nil, unsupportedNil\n\t}\n\n\ttid := rvType(v)\n\tvar err error\n\tvm.rw.RLock()\n\ts, ok := vm.structJar[tid]\n\tvm.rw.RUnlock()\n\tif !ok {\n\t\tvm.rw.Lock()\n\t\ts, ok = vm.structJar[tid]\n\t\tif !ok {\n\t\t\ts, err = vm.registerStructLocked(v.Type())\n\t\t\tif err != nil {\n\t\t\t\tvm.rw.Unlock()\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tvm.rw.Unlock()\n\t}\n\tif s.err != nil {\n\t\treturn nil, s.err\n\t}\n\treturn s.newTagExpr(ptr, \"\"), nil\n}\n\n// RunAny returns the tag expression handler for the @v.\n// NOTE:\n//\n//\tThe @v can be structured data such as struct, map, slice, array, interface, reflcet.Value, etc.\n//\tIf the structure type has not been warmed up,\n//\tit will be slower when it is first called.\nfunc (vm *VM) RunAny(v interface{}, fn func(*TagExpr, error) error) error {\n\tvv, isReflectValue := v.(reflect.Value)\n\tif !isReflectValue {\n\t\tvv = reflect.ValueOf(v)\n\t}\n\treturn vm.subRunAll(false, \"\", vv, fn)\n}\n\n// check type: struct{F map[T1]T2}\nfunc checkStructMapAddr(v reflect.Value) error {\n\tif !v.IsValid() || v.CanAddr() || v.NumField() != 1 || v.Field(0).Kind() != reflect.Map {\n\t\treturn nil\n\t}\n\treturn unsupportedCannotAddr\n}\n\nfunc (vm *VM) subRunAll(omitNil bool, tePath string, value reflect.Value, fn func(*TagExpr, error) error) error {\n\trv := dereferenceInterfaceValue(value)\n\tif !rv.IsValid() {\n\t\treturn nil\n\t}\n\trt := dereferenceType(rv.Type())\n\trv = dereferenceValue(rv)\n\tswitch rt.Kind() {\n\tcase reflect.Struct:\n\t\tif len(tePath) == 0 {\n\t\t\tif err := checkStructMapAddr(rv); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tptr := rvPtr(rv)\n\t\tif ptr == nil {\n\t\t\tif omitNil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fn(nil, unsupportedNil)\n\t\t}\n\t\treturn fn(vm.subRun(tePath, rt, rvType(rv), ptr))\n\n\tcase reflect.Slice, reflect.Array:\n\t\tcount := rv.Len()\n\t\tif count == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tswitch dereferenceType(rv.Type().Elem()).Kind() {\n\t\tcase reflect.Struct, reflect.Interface, reflect.Slice, reflect.Array, reflect.Map:\n\t\t\tfor i := count - 1; i >= 0; i-- {\n\t\t\t\terr := vm.subRunAll(omitNil, tePath+\"[\"+strconv.Itoa(i)+\"]\", rv.Index(i), fn)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\treturn nil\n\t\t}\n\n\tcase reflect.Map:\n\t\tif rv.Len() == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tvar canKey, canValue bool\n\t\trt := rv.Type()\n\t\tswitch dereferenceType(rt.Key()).Kind() {\n\t\tcase reflect.Struct, reflect.Interface, reflect.Slice, reflect.Array, reflect.Map:\n\t\t\tcanKey = true\n\t\t}\n\t\tswitch dereferenceType(rt.Elem()).Kind() {\n\t\tcase reflect.Struct, reflect.Interface, reflect.Slice, reflect.Array, reflect.Map:\n\t\t\tcanValue = true\n\t\t}\n\t\tif !canKey && !canValue {\n\t\t\treturn nil\n\t\t}\n\t\tfor _, key := range rv.MapKeys() {\n\t\t\tif canKey {\n\t\t\t\terr := vm.subRunAll(omitNil, tePath+\"{k}\", key, fn)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif canValue {\n\t\t\t\terr := vm.subRunAll(omitNil, tePath+\"{v for k=\"+key.String()+\"}\", rv.MapIndex(key), fn)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (vm *VM) subRun(path string, t reflect.Type, tid uintptr, ptr unsafe.Pointer) (*TagExpr, error) {\n\tvar err error\n\tvm.rw.RLock()\n\ts, ok := vm.structJar[tid]\n\tvm.rw.RUnlock()\n\tif !ok {\n\t\tvm.rw.Lock()\n\t\ts, ok = vm.structJar[tid]\n\t\tif !ok {\n\t\t\ts, err = vm.registerStructLocked(t)\n\t\t\tif err != nil {\n\t\t\t\tvm.rw.Unlock()\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tvm.rw.Unlock()\n\t}\n\tif s.err != nil {\n\t\treturn nil, s.err\n\t}\n\treturn s.newTagExpr(ptr, path), nil\n}\n\nfunc (vm *VM) registerStructLocked(structType reflect.Type) (*structVM, error) {\n\tstructType, err := vm.getStructType(structType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttid := rtType(structType)\n\ts, had := vm.structJar[tid]\n\tif had {\n\t\treturn s, s.err\n\t}\n\ts = vm.newStructVM()\n\ts.name = structType.String()\n\tvm.structJar[tid] = s\n\tnumField := structType.NumField()\n\tvar structField reflect.StructField\n\tvar sub *structVM\n\tfor i := 0; i < numField; i++ {\n\t\tstructField = structType.Field(i)\n\t\tif f := structField; !f.IsExported() &&\n\t\t\tstrings.HasPrefix(f.Type.PkgPath(), \"google.golang.org/protobuf\") {\n\t\t\t// Skip unexported protobuf internal fields to prevent stack overflow.\n\t\t\t// Fields like `state protoimpl.MessageState` may contain cyclic references\n\t\t\t// after unmarshaling, causing infinite recursion during field processing.\n\t\t\t// See: https://github.com/cloudwego/hertz/issues/1410\n\t\t\t//\n\t\t\t// This is a temporary solution,\n\t\t\t// we should fix cyclic references when calling Range\n\t\t\tcontinue\n\t\t}\n\t\tfield, ok, err := s.newFieldVM(structField)\n\t\tif err != nil {\n\t\t\ts.err = err\n\t\t\treturn nil, err\n\t\t}\n\t\t// skip omitted tag\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tswitch field.elemKind {\n\t\tdefault:\n\t\t\tfield.setUnsupportedGetter()\n\t\t\tswitch field.elemKind {\n\t\t\tcase reflect.Struct:\n\t\t\t\tsub, err = vm.registerStructLocked(field.structField.Type)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.err = err\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\ts.mergeSubStructVM(field, sub)\n\t\t\tcase reflect.Interface:\n\t\t\t\ts.setIfaceTagExprGetter(field)\n\t\t\t}\n\t\tcase reflect.Float32, reflect.Float64,\n\t\t\treflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,\n\t\t\treflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:\n\t\t\tfield.setFloatGetter()\n\t\tcase reflect.String:\n\t\t\tfield.setStringGetter()\n\t\tcase reflect.Bool:\n\t\t\tfield.setBoolGetter()\n\t\tcase reflect.Array, reflect.Slice, reflect.Map:\n\t\t\terr = vm.registerIndirectStructLocked(field)\n\t\t\tif err != nil {\n\t\t\t\ts.err = err\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\treturn s, nil\n}\n\nfunc (vm *VM) registerIndirectStructLocked(field *fieldVM) error {\n\tfield.setLengthGetter()\n\tif field.tagOp == tagOmit {\n\t\treturn nil\n\t}\n\ta := make([]reflect.Type, 1, 2)\n\ta[0] = derefType(field.elemType.Elem())\n\tif field.elemKind == reflect.Map {\n\t\ta = append(a, derefType(field.elemType.Key()))\n\t}\n\tfor i, t := range a {\n\t\tkind := t.Kind()\n\t\tswitch kind {\n\t\tcase reflect.Interface:\n\t\t\tfield.mapOrSliceIfaceKinds[i] = true\n\t\t\tfield.origin.fieldsWithIndirectStructVM = appendDistinct(field.origin.fieldsWithIndirectStructVM, field)\n\t\tcase reflect.Slice, reflect.Array, reflect.Map:\n\t\t\ttt := t.Elem()\n\t\t\tcheckMap := kind == reflect.Map\n\t\tF2:\n\t\t\tfor {\n\t\t\t\tswitch tt.Kind() {\n\t\t\t\tcase reflect.Slice, reflect.Array, reflect.Map, reflect.Ptr:\n\t\t\t\t\ttt = tt.Elem()\n\t\t\t\tcase reflect.Struct:\n\t\t\t\t\t_, err := vm.registerStructLocked(tt)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tfield.mapOrSliceIfaceKinds[i] = true\n\t\t\t\t\tfield.origin.fieldsWithIndirectStructVM = appendDistinct(field.origin.fieldsWithIndirectStructVM, field)\n\t\t\t\t\tbreak F2\n\t\t\t\tdefault:\n\t\t\t\t\tbreak F2\n\t\t\t\t}\n\t\t\t}\n\t\t\tif checkMap {\n\t\t\t\ttt = t.Key()\n\t\t\t\tcheckMap = false\n\t\t\t\tgoto F2\n\t\t\t}\n\t\tcase reflect.Struct:\n\t\t\ts, err := vm.registerStructLocked(t)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif len(s.exprSelectorList) > 0 ||\n\t\t\t\tlen(s.ifaceTagExprGetters) > 0 ||\n\t\t\t\tlen(s.fieldsWithIndirectStructVM) > 0 {\n\t\t\t\tif i == 0 {\n\t\t\t\t\tfield.mapOrSliceElemStructVM = s\n\t\t\t\t} else {\n\t\t\t\t\tfield.mapKeyStructVM = s\n\t\t\t\t}\n\t\t\t\tfield.origin.fieldsWithIndirectStructVM = appendDistinct(field.origin.fieldsWithIndirectStructVM, field)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc appendDistinct(a []*fieldVM, i *fieldVM) []*fieldVM {\n\thas := false\n\tfor _, e := range a {\n\t\tif e == i {\n\t\t\thas = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !has {\n\t\treturn append(a, i)\n\t}\n\treturn a\n}\n\nfunc (vm *VM) newStructVM() *structVM {\n\treturn &structVM{\n\t\tvm:                         vm,\n\t\tfields:                     make(map[string]*fieldVM, 32),\n\t\tfieldSelectorList:          make([]string, 0, 32),\n\t\tfieldsWithIndirectStructVM: make([]*fieldVM, 0, 32),\n\t\texprs:                      make(map[string]*Expr, 64),\n\t\texprSelectorList:           make([]string, 0, 64),\n\t}\n}\n\nfunc (s *structVM) newFieldVM(structField reflect.StructField) (*fieldVM, bool, error) {\n\ttag := structField.Tag.Get(s.vm.tagName)\n\tif tag == tagOmit {\n\t\treturn nil, false, nil\n\t}\n\tf := &fieldVM{\n\t\tstructField:   structField,\n\t\texprs:         make(map[string]*Expr, 8),\n\t\torigin:        s,\n\t\tfieldSelector: structField.Name,\n\t}\n\terr := f.parseExprs(tag)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\ts.fields[f.fieldSelector] = f\n\ts.fieldSelectorList = append(s.fieldSelectorList, f.fieldSelector)\n\n\tt := structField.Type\n\tvar ptrDeep int\n\tfor t.Kind() == reflect.Ptr {\n\t\tt = t.Elem()\n\t\tptrDeep++\n\t}\n\tf.ptrDeep = ptrDeep\n\n\toffset := structField.Offset\n\tf.getPtr = func(ptr unsafe.Pointer) unsafe.Pointer {\n\t\tif ptr == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn unsafe.Pointer(uintptr(ptr) + offset)\n\t}\n\n\tf.elemType = t\n\tf.elemKind = t.Kind()\n\tf.reflectValueGetter = func(ptr unsafe.Pointer, initZero bool) reflect.Value {\n\t\tv := f.packRawFrom(ptr)\n\t\tif initZero {\n\t\t\tf.ensureInit(v)\n\t\t}\n\t\treturn v\n\t}\n\n\treturn f, true, nil\n}\n\nfunc (f *fieldVM) ensureInit(v reflect.Value) {\n\tif safeIsNil(v) && v.CanSet() {\n\t\tnewField := reflect.New(f.elemType).Elem()\n\t\tfor i := 0; i < f.ptrDeep; i++ {\n\t\t\tif newField.CanAddr() {\n\t\t\t\tnewField = newField.Addr()\n\t\t\t} else {\n\t\t\t\tnewField2 := reflect.New(newField.Type())\n\t\t\t\tnewField2.Elem().Set(newField)\n\t\t\t\tnewField = newField2\n\t\t\t}\n\t\t}\n\t\tv.Set(newField)\n\t}\n}\n\nfunc (s *structVM) mergeSubStructVM(field *fieldVM, sub *structVM) {\n\tfield.origin = sub\n\tfieldsWithIndirectStructVM := make(map[*fieldVM]struct{}, len(sub.fieldsWithIndirectStructVM))\n\tfor _, subField := range sub.fieldsWithIndirectStructVM {\n\t\tfieldsWithIndirectStructVM[subField] = struct{}{}\n\t}\n\tfor _, k := range sub.fieldSelectorList {\n\t\tv := sub.fields[k]\n\t\tf := s.newChildField(field, v, true)\n\t\tif _, ok := fieldsWithIndirectStructVM[v]; ok {\n\t\t\ts.fieldsWithIndirectStructVM = append(s.fieldsWithIndirectStructVM, f)\n\t\t\t// TODO: maybe needed?\n\t\t\t// delete(fieldsWithIndirectStructVM, v)\n\t\t}\n\t}\n\t// TODO: maybe needed?\n\t// for v := range fieldsWithIndirectStructVM {\n\t// \tf := s.newChildField(field, v, false)\n\t// \ts.fieldsWithIndirectStructVM = append(s.fieldsWithIndirectStructVM, f)\n\t// }\n\n\tfor _, _subFn := range sub.ifaceTagExprGetters {\n\t\tsubFn := _subFn\n\t\ts.ifaceTagExprGetters = append(s.ifaceTagExprGetters, func(ptr unsafe.Pointer, pathPrefix string, fn func(*TagExpr, error) error) error {\n\t\t\tptr = field.getElemPtr(ptr)\n\t\t\tif ptr == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tvar path string\n\t\t\tif pathPrefix == \"\" {\n\t\t\t\tpath = field.fieldSelector\n\t\t\t} else {\n\t\t\t\tpath = pathPrefix + FieldSeparator + field.fieldSelector\n\t\t\t}\n\t\t\treturn subFn(ptr, path, fn)\n\t\t})\n\t}\n}\n\nfunc (s *structVM) newChildField(parent *fieldVM, child *fieldVM, toBind bool) *fieldVM {\n\tf := &fieldVM{\n\t\tstructField:            child.structField,\n\t\texprs:                  make(map[string]*Expr, len(child.exprs)),\n\t\tptrDeep:                child.ptrDeep,\n\t\telemType:               child.elemType,\n\t\telemKind:               child.elemKind,\n\t\torigin:                 child.origin,\n\t\tmapKeyStructVM:         child.mapKeyStructVM,\n\t\tmapOrSliceElemStructVM: child.mapOrSliceElemStructVM,\n\t\tmapOrSliceIfaceKinds:   child.mapOrSliceIfaceKinds,\n\t\tfieldSelector:          parent.fieldSelector + FieldSeparator + child.fieldSelector,\n\t}\n\tif parent.tagOp != tagOmit {\n\t\tf.tagOp = child.tagOp\n\t} else {\n\t\tf.tagOp = parent.tagOp\n\t}\n\tf.getPtr = func(ptr unsafe.Pointer) unsafe.Pointer {\n\t\tptr = parent.getElemPtr(ptr)\n\t\tif ptr == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn child.getPtr(ptr)\n\t}\n\tif child.valueGetter != nil {\n\t\tif parent.ptrDeep == 0 {\n\t\t\tf.valueGetter = func(ptr unsafe.Pointer) interface{} {\n\t\t\t\treturn child.valueGetter(parent.getPtr(ptr))\n\t\t\t}\n\t\t\tf.reflectValueGetter = func(ptr unsafe.Pointer, initZero bool) reflect.Value {\n\t\t\t\treturn child.reflectValueGetter(parent.getPtr(ptr), initZero)\n\t\t\t}\n\t\t} else {\n\t\t\tf.valueGetter = func(ptr unsafe.Pointer) interface{} {\n\t\t\t\tnewField := reflect.NewAt(parent.structField.Type, parent.getPtr(ptr))\n\t\t\t\tfor i := 0; i < parent.ptrDeep; i++ {\n\t\t\t\t\tnewField = newField.Elem()\n\t\t\t\t}\n\t\t\t\tif newField.IsNil() {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn child.valueGetter(unsafe.Pointer(newField.Pointer()))\n\t\t\t}\n\t\t\tf.reflectValueGetter = func(ptr unsafe.Pointer, initZero bool) reflect.Value {\n\t\t\t\tnewField := reflect.NewAt(parent.structField.Type, parent.getPtr(ptr))\n\t\t\t\tif initZero {\n\t\t\t\t\tparent.ensureInit(newField.Elem())\n\t\t\t\t}\n\t\t\t\tfor i := 0; i < parent.ptrDeep; i++ {\n\t\t\t\t\tnewField = newField.Elem()\n\t\t\t\t}\n\t\t\t\tif (newField == reflect.Value{}) || (!initZero && newField.IsNil()) {\n\t\t\t\t\treturn reflect.Value{}\n\t\t\t\t}\n\t\t\t\treturn child.reflectValueGetter(unsafe.Pointer(newField.Pointer()), initZero)\n\t\t\t}\n\t\t}\n\t}\n\n\tif toBind {\n\t\ts.fields[f.fieldSelector] = f\n\t\ts.fieldSelectorList = append(s.fieldSelectorList, f.fieldSelector)\n\t\tif parent.tagOp != tagOmit {\n\t\t\tfor k, v := range child.exprs {\n\t\t\t\tselector := parent.fieldSelector + FieldSeparator + k\n\t\t\t\tf.exprs[selector] = v\n\t\t\t\ts.exprs[selector] = v\n\t\t\t\ts.exprSelectorList = append(s.exprSelectorList, selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn f\n}\n\nfunc (f *fieldVM) getElemPtr(ptr unsafe.Pointer) unsafe.Pointer {\n\tptr = f.getPtr(ptr)\n\tfor i := f.ptrDeep; ptr != nil && i > 0; i-- {\n\t\tptr = ptrElem(ptr)\n\t}\n\treturn ptr\n}\n\nfunc (f *fieldVM) packRawFrom(ptr unsafe.Pointer) reflect.Value {\n\treturn reflect.NewAt(f.structField.Type, f.getPtr(ptr)).Elem()\n}\n\nfunc (f *fieldVM) packElemFrom(ptr unsafe.Pointer) reflect.Value {\n\treturn reflect.NewAt(f.elemType, f.getElemPtr(ptr)).Elem()\n}\n\nfunc (s *structVM) setIfaceTagExprGetter(f *fieldVM) {\n\tif f.tagOp == tagOmit {\n\t\treturn\n\t}\n\ts.ifaceTagExprGetters = append(s.ifaceTagExprGetters, func(ptr unsafe.Pointer, pathPrefix string, fn func(*TagExpr, error) error) error {\n\t\tv := f.packElemFrom(ptr)\n\t\tif !v.IsValid() || v.IsNil() {\n\t\t\treturn nil\n\t\t}\n\t\tvar path string\n\t\tif pathPrefix == \"\" {\n\t\t\tpath = f.fieldSelector\n\t\t} else {\n\t\t\tpath = pathPrefix + FieldSeparator + f.fieldSelector\n\t\t}\n\t\treturn s.vm.subRunAll(f.tagOp == tagOmitNil, path, v, fn)\n\t})\n}\n\nfunc (f *fieldVM) setFloatGetter() {\n\tif f.ptrDeep == 0 {\n\t\tf.valueGetter = func(ptr unsafe.Pointer) interface{} {\n\t\t\tptr = f.getPtr(ptr)\n\t\t\tif ptr == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn getFloat64(f.elemKind, ptr)\n\t\t}\n\t} else {\n\t\tf.valueGetter = func(ptr unsafe.Pointer) interface{} {\n\t\t\tv := f.packElemFrom(ptr)\n\t\t\tif v.CanAddr() {\n\t\t\t\treturn getFloat64(f.elemKind, unsafe.Pointer(v.UnsafeAddr()))\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (f *fieldVM) setBoolGetter() {\n\tif f.ptrDeep == 0 {\n\t\tf.valueGetter = func(ptr unsafe.Pointer) interface{} {\n\t\t\tptr = f.getPtr(ptr)\n\t\t\tif ptr == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn *(*bool)(ptr)\n\t\t}\n\t} else {\n\t\tf.valueGetter = func(ptr unsafe.Pointer) interface{} {\n\t\t\tv := f.packElemFrom(ptr)\n\t\t\tif v.IsValid() {\n\t\t\t\treturn v.Bool()\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (f *fieldVM) setStringGetter() {\n\tif f.ptrDeep == 0 {\n\t\tf.valueGetter = func(ptr unsafe.Pointer) interface{} {\n\t\t\tptr = f.getPtr(ptr)\n\t\t\tif ptr == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn *(*string)(ptr)\n\t\t}\n\t} else {\n\t\tf.valueGetter = func(ptr unsafe.Pointer) interface{} {\n\t\t\tv := f.packElemFrom(ptr)\n\t\t\tif v.IsValid() {\n\t\t\t\treturn v.String()\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (f *fieldVM) setLengthGetter() {\n\tf.valueGetter = func(ptr unsafe.Pointer) interface{} {\n\t\tv := f.packElemFrom(ptr)\n\t\tif v.IsValid() {\n\t\t\treturn v.Interface()\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc (f *fieldVM) setUnsupportedGetter() {\n\tf.valueGetter = func(ptr unsafe.Pointer) interface{} {\n\t\traw := f.packRawFrom(ptr)\n\t\tif safeIsNil(raw) {\n\t\t\treturn nil\n\t\t}\n\t\tv := raw\n\t\tfor i := 0; i < f.ptrDeep; i++ {\n\t\t\tv = v.Elem()\n\t\t}\n\t\tfor v.Kind() == reflect.Interface {\n\t\t\tv = v.Elem()\n\t\t}\n\t\treturn anyValueGetter(raw, v)\n\t}\n}\n\nfunc (vm *VM) getStructType(t reflect.Type) (reflect.Type, error) {\n\tstructType := t\n\tfor structType.Kind() == reflect.Ptr {\n\t\tstructType = structType.Elem()\n\t}\n\tif structType.Kind() != reflect.Struct {\n\t\treturn nil, fmt.Errorf(\"unsupported type: %s\", t.String())\n\t}\n\treturn structType, nil\n}\n\nfunc (s *structVM) newTagExpr(ptr unsafe.Pointer, path string) *TagExpr {\n\tte := &TagExpr{\n\t\ts:    s,\n\t\tptr:  ptr,\n\t\tsub:  make(map[string]*TagExpr, 8),\n\t\tpath: strings.TrimPrefix(path, \".\"),\n\t}\n\treturn te\n}\n\n// TagExpr struct tag expression evaluator\ntype TagExpr struct {\n\ts    *structVM\n\tptr  unsafe.Pointer\n\tsub  map[string]*TagExpr\n\tpath string\n}\n\n// EvalFloat evaluates the value of the struct tag expression by the selector expression.\n// NOTE:\n//\n//\tIf the expression value type is not float64, return 0.\nfunc (t *TagExpr) EvalFloat(exprSelector string) float64 {\n\tr, _ := t.Eval(exprSelector).(float64)\n\treturn r\n}\n\n// EvalString evaluates the value of the struct tag expression by the selector expression.\n// NOTE:\n//\n//\tIf the expression value type is not string, return \"\".\nfunc (t *TagExpr) EvalString(exprSelector string) string {\n\tr, _ := t.Eval(exprSelector).(string)\n\treturn r\n}\n\n// EvalBool evaluates the value of the struct tag expression by the selector expression.\n// NOTE:\n//\n//\tIf the expression value is not 0, '' or nil, return true.\nfunc (t *TagExpr) EvalBool(exprSelector string) bool {\n\treturn FakeBool(t.Eval(exprSelector))\n}\n\n// FakeBool fakes any type as a boolean.\nfunc FakeBool(v interface{}) bool {\n\tswitch r := v.(type) {\n\tcase float64:\n\t\treturn r != 0\n\tcase float32:\n\t\treturn r != 0\n\tcase int:\n\t\treturn r != 0\n\tcase int8:\n\t\treturn r != 0\n\tcase int16:\n\t\treturn r != 0\n\tcase int32:\n\t\treturn r != 0\n\tcase int64:\n\t\treturn r != 0\n\tcase uint:\n\t\treturn r != 0\n\tcase uint8:\n\t\treturn r != 0\n\tcase uint16:\n\t\treturn r != 0\n\tcase uint32:\n\t\treturn r != 0\n\tcase uint64:\n\t\treturn r != 0\n\tcase string:\n\t\treturn r != \"\"\n\tcase bool:\n\t\treturn r\n\tcase nil, error:\n\t\treturn false\n\tcase []interface{}:\n\t\tbol := true\n\t\tfor _, v := range r {\n\t\t\tbol = bol && FakeBool(v)\n\t\t}\n\t\treturn bol\n\tdefault:\n\t\t// https://github.com/bytedance/go-tagexpr/blob/v2.9.2/tagexpr.go#L801\n\t\t// the original implementation either returns false or panics for default case\n\t\t// we always return false for unsupported types to avoid introducing new behavior\n\t\treturn false\n\t}\n}\n\n// Field returns the field handler specified by the selector.\nfunc (t *TagExpr) Field(fieldSelector string) (fh *FieldHandler, found bool) {\n\tf, ok := t.s.fields[fieldSelector]\n\tif !ok {\n\t\treturn nil, false\n\t}\n\treturn newFieldHandler(t, fieldSelector, f), true\n}\n\n// RangeFields loop through each field.\n// When fn returns false, interrupt traversal and return false.\nfunc (t *TagExpr) RangeFields(fn func(*FieldHandler) bool) bool {\n\tif list := t.s.fieldSelectorList; len(list) > 0 {\n\t\tfields := t.s.fields\n\t\tfor _, fieldSelector := range list {\n\t\t\tif !fn(newFieldHandler(t, fieldSelector, fields[fieldSelector])) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n// Eval evaluates the value of the struct tag expression by the selector expression.\n// NOTE:\n//\n//\tformat: fieldName, fieldName.exprName, fieldName1.fieldName2.exprName1\n//\tresult types: float64, string, bool, nil\nfunc (t *TagExpr) Eval(exprSelector string) interface{} {\n\texpr, ok := t.s.exprs[exprSelector]\n\tif !ok {\n\t\t// Compatible with single mode or the expression with the name @\n\t\tif strings.HasSuffix(exprSelector, ExprNameSeparator) {\n\t\t\texprSelector = exprSelector[:len(exprSelector)-1]\n\t\t\tif strings.HasSuffix(exprSelector, ExprNameSeparator) {\n\t\t\t\texprSelector = exprSelector[:len(exprSelector)-1]\n\t\t\t}\n\t\t\texpr, ok = t.s.exprs[exprSelector]\n\t\t}\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t}\n\tdir, base := splitFieldSelector(exprSelector)\n\ttargetTagExpr, err := t.checkout(dir)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn expr.run(base, targetTagExpr)\n}\n\n// EvalWithEnv evaluates the value with the given env\n// NOTE:\n//\n//\tformat: fieldName, fieldName.exprName, fieldName1.fieldName2.exprName1\n//\tresult types: float64, string, bool, nil\nfunc (t *TagExpr) EvalWithEnv(exprSelector string, env map[string]interface{}) interface{} {\n\texpr, ok := t.s.exprs[exprSelector]\n\tif !ok {\n\t\t// Compatible with single mode or the expression with the name @\n\t\tif strings.HasSuffix(exprSelector, ExprNameSeparator) {\n\t\t\texprSelector = exprSelector[:len(exprSelector)-1]\n\t\t\tif strings.HasSuffix(exprSelector, ExprNameSeparator) {\n\t\t\t\texprSelector = exprSelector[:len(exprSelector)-1]\n\t\t\t}\n\t\t\texpr, ok = t.s.exprs[exprSelector]\n\t\t}\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t}\n\tdir, base := splitFieldSelector(exprSelector)\n\ttargetTagExpr, err := t.checkout(dir)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn expr.runWithEnv(base, targetTagExpr, env)\n}\n\n// Range loop through each tag expression.\n// When fn returns false, interrupt traversal and return false.\n// NOTE:\n//\n//\teval result types: float64, string, bool, nil\nfunc (t *TagExpr) Range(fn func(*ExprHandler) error) error {\n\tvar err error\n\tif list := t.s.exprSelectorList; len(list) > 0 {\n\t\tfor _, es := range list {\n\t\t\tdir, base := splitFieldSelector(es)\n\t\t\ttargetTagExpr, err := t.checkout(dir)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terr = fn(newExprHandler(t, targetTagExpr, base, es))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tptr := t.ptr\n\n\tif list := t.s.fieldsWithIndirectStructVM; len(list) > 0 {\n\t\tfor _, f := range list {\n\t\t\tv := f.packElemFrom(ptr)\n\t\t\tif !v.IsValid() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tomitNil := f.tagOp == tagOmitNil\n\t\t\tmapKeyStructVM := f.mapKeyStructVM\n\t\t\tmapOrSliceElemStructVM := f.mapOrSliceElemStructVM\n\t\t\tvalueIface := f.mapOrSliceIfaceKinds[0]\n\t\t\tkeyIface := f.mapOrSliceIfaceKinds[1]\n\n\t\t\tif f.elemKind == reflect.Map &&\n\t\t\t\t(mapOrSliceElemStructVM != nil || mapKeyStructVM != nil || valueIface || keyIface) {\n\t\t\t\tkeyPath := f.fieldSelector + \"{k}\"\n\t\t\t\tfor _, key := range v.MapKeys() {\n\t\t\t\t\tif mapKeyStructVM != nil {\n\t\t\t\t\t\tp := rvPtr(derefValue(key))\n\t\t\t\t\t\tif omitNil && p == nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\terr = mapKeyStructVM.newTagExpr(p, keyPath).Range(fn)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if keyIface {\n\t\t\t\t\t\terr = t.subRange(omitNil, keyPath, key, fn)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif mapOrSliceElemStructVM != nil {\n\t\t\t\t\t\tp := rvPtr(derefValue(v.MapIndex(key)))\n\t\t\t\t\t\tif omitNil && p == nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\terr = mapOrSliceElemStructVM.newTagExpr(p, f.fieldSelector+\"{v for k=\"+key.String()+\"}\").Range(fn)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if valueIface {\n\t\t\t\t\t\terr = t.subRange(omitNil, f.fieldSelector+\"{v for k=\"+key.String()+\"}\", v.MapIndex(key), fn)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t} else if mapOrSliceElemStructVM != nil || valueIface {\n\t\t\t\t// slice or array\n\t\t\t\tfor i := v.Len() - 1; i >= 0; i-- {\n\t\t\t\t\tif mapOrSliceElemStructVM != nil {\n\t\t\t\t\t\tp := rvPtr(derefValue(v.Index(i)))\n\t\t\t\t\t\tif omitNil && p == nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\terr = mapOrSliceElemStructVM.newTagExpr(p, f.fieldSelector+\"[\"+strconv.Itoa(i)+\"]\").Range(fn)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if valueIface {\n\t\t\t\t\t\terr = t.subRange(omitNil, f.fieldSelector+\"[\"+strconv.Itoa(i)+\"]\", v.Index(i), fn)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif list := t.s.ifaceTagExprGetters; len(list) > 0 {\n\t\tfor _, getter := range list {\n\t\t\terr = getter(ptr, \"\", func(te *TagExpr, err error) error {\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn te.Range(fn)\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *TagExpr) subRange(omitNil bool, path string, value reflect.Value, fn func(*ExprHandler) error) error {\n\treturn t.s.vm.subRunAll(omitNil, path, value, func(te *TagExpr, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn te.Range(fn)\n\t})\n}\n\nvar (\n\terrFieldSelector = errors.New(\"field selector does not exist\")\n\terrOmitNil       = errors.New(\"omit nil\")\n)\n\nfunc (t *TagExpr) checkout(fs string) (*TagExpr, error) {\n\tif fs == \"\" {\n\t\treturn t, nil\n\t}\n\tsubTagExpr, ok := t.sub[fs]\n\tif ok {\n\t\tif subTagExpr == nil {\n\t\t\treturn nil, errOmitNil\n\t\t}\n\t\treturn subTagExpr, nil\n\t}\n\tf, ok := t.s.fields[fs]\n\tif !ok {\n\t\treturn nil, errFieldSelector\n\t}\n\tptr := f.getElemPtr(t.ptr)\n\tif f.tagOp == tagOmitNil && ptr == nil {\n\t\tt.sub[fs] = nil\n\t\treturn nil, errOmitNil\n\t}\n\tsubTagExpr = f.origin.newTagExpr(ptr, t.path)\n\tt.sub[fs] = subTagExpr\n\treturn subTagExpr, nil\n}\n\nfunc (t *TagExpr) getValue(fieldSelector string, subFields []interface{}) (v interface{}) {\n\tf := t.s.fields[fieldSelector]\n\tif f == nil {\n\t\treturn nil\n\t}\n\tif f.valueGetter == nil {\n\t\treturn nil\n\t}\n\tv = f.valueGetter(t.ptr)\n\tif v == nil {\n\t\treturn nil\n\t}\n\tif len(subFields) == 0 {\n\t\treturn v\n\t}\n\tvv := reflect.ValueOf(v)\n\tvar kind reflect.Kind\n\tfor i, k := range subFields {\n\t\tkind = vv.Kind()\n\t\tfor kind == reflect.Ptr || kind == reflect.Interface {\n\t\t\tvv = vv.Elem()\n\t\t\tkind = vv.Kind()\n\t\t}\n\t\tswitch kind {\n\t\tcase reflect.Slice, reflect.Array, reflect.String:\n\t\t\tif float, ok := k.(float64); ok {\n\t\t\t\tidx := int(float)\n\t\t\t\tif idx >= vv.Len() {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tvv = vv.Index(idx)\n\t\t\t} else {\n\t\t\t\treturn nil\n\t\t\t}\n\t\tcase reflect.Map:\n\t\t\tk := safeConvert(reflect.ValueOf(k), vv.Type().Key())\n\t\t\tif !k.IsValid() {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tvv = vv.MapIndex(k)\n\t\tcase reflect.Struct:\n\t\t\tif float, ok := k.(float64); ok {\n\t\t\t\tidx := int(float)\n\t\t\t\tif idx < 0 || idx >= vv.NumField() {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tvv = vv.Field(idx)\n\t\t\t} else if str, ok := k.(string); ok {\n\t\t\t\tvv = vv.FieldByName(str)\n\t\t\t} else {\n\t\t\t\treturn nil\n\t\t\t}\n\t\tdefault:\n\t\t\tif i < len(subFields)-1 {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tif !vv.IsValid() {\n\t\t\treturn nil\n\t\t}\n\t}\n\traw := vv\n\tfor vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface {\n\t\tvv = vv.Elem()\n\t}\n\treturn anyValueGetter(raw, vv)\n}\n\nfunc safeConvert(v reflect.Value, t reflect.Type) reflect.Value {\n\tdefer func() { recover() }()\n\treturn v.Convert(t)\n}\n\nfunc splitFieldSelector(selector string) (dir, base string) {\n\tidx := strings.LastIndex(selector, ExprNameSeparator)\n\tif idx != -1 {\n\t\tselector = selector[:idx]\n\t}\n\tidx = strings.LastIndex(selector, FieldSeparator)\n\tif idx != -1 {\n\t\treturn selector[:idx], selector[idx+1:]\n\t}\n\treturn \"\", selector\n}\n\nfunc getFloat64(kind reflect.Kind, p unsafe.Pointer) interface{} {\n\tswitch kind {\n\tcase reflect.Float32:\n\t\treturn float64(*(*float32)(p))\n\tcase reflect.Float64:\n\t\treturn *(*float64)(p)\n\tcase reflect.Int:\n\t\treturn float64(*(*int)(p))\n\tcase reflect.Int8:\n\t\treturn float64(*(*int8)(p))\n\tcase reflect.Int16:\n\t\treturn float64(*(*int16)(p))\n\tcase reflect.Int32:\n\t\treturn float64(*(*int32)(p))\n\tcase reflect.Int64:\n\t\treturn float64(*(*int64)(p))\n\tcase reflect.Uint:\n\t\treturn float64(*(*uint)(p))\n\tcase reflect.Uint8:\n\t\treturn float64(*(*uint8)(p))\n\tcase reflect.Uint16:\n\t\treturn float64(*(*uint16)(p))\n\tcase reflect.Uint32:\n\t\treturn float64(*(*uint32)(p))\n\tcase reflect.Uint64:\n\t\treturn float64(*(*uint64)(p))\n\tcase reflect.Uintptr:\n\t\treturn float64(*(*uintptr)(p))\n\t}\n\treturn nil\n}\n\nfunc anyValueGetter(raw, elem reflect.Value) interface{} {\n\tif !elem.IsValid() || !raw.IsValid() {\n\t\treturn nil\n\t}\n\tkind := elem.Kind()\n\tswitch kind {\n\tcase reflect.Float32, reflect.Float64,\n\t\treflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,\n\t\treflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:\n\t\tif elem.CanAddr() {\n\t\t\treturn getFloat64(kind, unsafe.Pointer(elem.UnsafeAddr()))\n\t\t}\n\t\tswitch kind {\n\t\tcase reflect.Float32, reflect.Float64:\n\t\t\treturn elem.Float()\n\t\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\t\treturn float64(elem.Int())\n\t\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:\n\t\t\treturn float64(elem.Uint())\n\t\t}\n\tcase reflect.String:\n\t\treturn elem.String()\n\tcase reflect.Bool:\n\t\treturn elem.Bool()\n\t}\n\tif raw.CanInterface() {\n\t\treturn raw.Interface()\n\t}\n\treturn nil\n}\n\nfunc safeIsNil(v reflect.Value) bool {\n\tif !v.IsValid() {\n\t\treturn true\n\t}\n\tswitch v.Kind() {\n\tcase reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr,\n\t\treflect.UnsafePointer, reflect.Interface, reflect.Slice:\n\t\treturn v.IsNil()\n\t}\n\treturn false\n}\n\n//go:nocheckptr\nfunc ptrElem(ptr unsafe.Pointer) unsafe.Pointer {\n\treturn unsafe.Pointer(*(*uintptr)(ptr))\n}\n\nfunc derefType(t reflect.Type) reflect.Type {\n\tfor t.Kind() == reflect.Ptr {\n\t\tt = t.Elem()\n\t}\n\treturn t\n}\n\nfunc derefValue(v reflect.Value) reflect.Value {\n\tfor v.Kind() == reflect.Ptr {\n\t\tv = v.Elem()\n\t}\n\treturn v\n}\n"
  },
  {
    "path": "internal/tagexpr/tagexpr_test.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr_test\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/tagexpr\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n)\n\nfunc assertEqual(t *testing.T, v1, v2 interface{}, msgs ...interface{}) {\n\tt.Helper()\n\tif reflect.DeepEqual(v1, v2) {\n\t\treturn\n\t}\n\tt.Fatal(fmt.Sprintf(\"not equal %v %v\", v1, v2) + \"\\n\" + fmt.Sprint(msgs...))\n}\n\nfunc BenchmarkTagExpr(b *testing.B) {\n\ttype T struct {\n\t\ta int `bench:\"$%3\"`\n\t}\n\tvm := tagexpr.New(\"bench\")\n\tvm.MustRun(new(T)) // warm up\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\tt := &T{10}\n\tfor i := 0; i < b.N; i++ {\n\t\ttagExpr, err := vm.Run(t)\n\t\tif err != nil {\n\t\t\tb.FailNow()\n\t\t}\n\t\tif tagExpr.EvalFloat(\"a\") != 1 {\n\t\t\tb.FailNow()\n\t\t}\n\t}\n}\n\nfunc BenchmarkReflect(b *testing.B) {\n\ttype T struct {\n\t\ta int `remainder:\"3\"`\n\t}\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\tt := &T{1}\n\tfor i := 0; i < b.N; i++ {\n\t\tv := reflect.ValueOf(t).Elem()\n\t\tft, ok := v.Type().FieldByName(\"a\")\n\t\tif !ok {\n\t\t\tb.FailNow()\n\t\t}\n\t\tx, err := strconv.ParseInt(ft.Tag.Get(\"remainder\"), 10, 64)\n\t\tif err != nil {\n\t\t\tb.FailNow()\n\t\t}\n\t\tfv := v.FieldByName(\"a\")\n\t\tif fv.Int()%x != 1 {\n\t\t\tb.FailNow()\n\t\t}\n\t}\n}\n\nfunc Test(t *testing.T) {\n\tg := &struct {\n\t\t_ int\n\t\th string `tagexpr:\"$\"`\n\t\ts []string\n\t\tm map[string][]string\n\t}{\n\t\th: \"haha\",\n\t\ts: []string{\"1\"},\n\t\tm: map[string][]string{\"0\": {\"2\"}},\n\t}\n\td := \"ddd\"\n\te := new(int)\n\t*e = 3\n\ttype iface interface{}\n\tcases := []struct {\n\t\ttagName   string\n\t\tstructure interface{}\n\t\ttests     map[string]interface{}\n\t}{\n\t\t{\n\t\t\ttagName: \"tagexpr\",\n\t\t\tstructure: &struct {\n\t\t\t\tA     int              `tagexpr:\"$>0&&$<10&&!''&&!!!0&&!nil&&$\"`\n\t\t\t\tA2    int              `tagexpr:\"@:$>0&&$<10\"`\n\t\t\t\tb     string           `tagexpr:\"is:$=='test';msg:sprintf('expect: test, but got: %s',$)\"`\n\t\t\t\tc     float32          `tagexpr:\"(A)$+$\"`\n\t\t\t\td     *string          `tagexpr:\"$\"`\n\t\t\t\te     **int            `tagexpr:\"$\"`\n\t\t\t\tf     *[3]int          `tagexpr:\"x:len($)\"`\n\t\t\t\tg     string           `tagexpr:\"x:!regexp('xxx',$);y:regexp('g\\\\d{3}$')\"`\n\t\t\t\th     []string         `tagexpr:\"x:$[1];y:$[10]\"`\n\t\t\t\ti     map[string]int   `tagexpr:\"x:$['a'];y:$[0];z:$==nil\"`\n\t\t\t\ti2    *map[string]int  `tagexpr:\"x:$['a'];y:$[0];z:$\"`\n\t\t\t\tj, j2 iface            `tagexpr:\"@:$==1;y:$\"`\n\t\t\t\tk     *iface           `tagexpr:\"$==nil\"`\n\t\t\t\tm     *struct{ i int } `tagexpr:\"@:$;x:$['a']['x']\"`\n\t\t\t}{\n\t\t\t\tA:  5.0,\n\t\t\t\tA2: 5.0,\n\t\t\t\tb:  \"x\",\n\t\t\t\tc:  1,\n\t\t\t\td:  &d,\n\t\t\t\te:  &e,\n\t\t\t\tf:  new([3]int),\n\t\t\t\tg:  \"g123\",\n\t\t\t\th:  []string{\"\", \"hehe\"},\n\t\t\t\ti:  map[string]int{\"a\": 7},\n\t\t\t\tj2: iface(1),\n\t\t\t\tm:  &struct{ i int }{1},\n\t\t\t},\n\t\t\ttests: map[string]interface{}{\n\t\t\t\t\"A\":     true,\n\t\t\t\t\"A2\":    true,\n\t\t\t\t\"b@is\":  false,\n\t\t\t\t\"b@msg\": \"expect: test, but got: x\",\n\t\t\t\t\"c\":     6.0,\n\t\t\t\t\"d\":     d,\n\t\t\t\t\"e\":     float64(*e),\n\t\t\t\t\"f@x\":   float64(3),\n\t\t\t\t\"g@x\":   true,\n\t\t\t\t\"g@y\":   true,\n\t\t\t\t\"h@x\":   \"hehe\",\n\t\t\t\t\"h@y\":   nil,\n\t\t\t\t\"i@x\":   7.0,\n\t\t\t\t\"i@y\":   nil,\n\t\t\t\t\"i@z\":   false,\n\t\t\t\t\"i2@x\":  nil,\n\t\t\t\t\"i2@y\":  nil,\n\t\t\t\t\"i2@z\":  nil,\n\t\t\t\t\"j\":     false,\n\t\t\t\t\"j@y\":   nil,\n\t\t\t\t\"j2\":    true,\n\t\t\t\t\"j2@y\":  1.0,\n\t\t\t\t\"k\":     true,\n\t\t\t\t\"m\":     &struct{ i int }{1},\n\t\t\t\t\"m@x\":   nil,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttagName: \"tagexpr\",\n\t\t\tstructure: &struct {\n\t\t\t\tA int    `tagexpr:\"$>0&&$<10\"`\n\t\t\t\tb string `tagexpr:\"is:$=='test';msg:sprintf('expect: test, but got: %s',$)\"`\n\t\t\t\tc struct {\n\t\t\t\t\t_ int\n\t\t\t\t\td bool `tagexpr:\"$\"`\n\t\t\t\t}\n\t\t\t\te *struct {\n\t\t\t\t\t_ int\n\t\t\t\t\tf bool `tagexpr:\"$\"`\n\t\t\t\t}\n\t\t\t\tg **struct {\n\t\t\t\t\t_ int\n\t\t\t\t\th string `tagexpr:\"$\"`\n\t\t\t\t\ts []string\n\t\t\t\t\tm map[string][]string\n\t\t\t\t} `tagexpr:\"$['h']\"`\n\t\t\t\ti string  `tagexpr:\"(g.s)$[0]+(g.m)$['0'][0]==$\"`\n\t\t\t\tj bool    `tagexpr:\"!$\"`\n\t\t\t\tk int     `tagexpr:\"!$\"`\n\t\t\t\tm *int    `tagexpr:\"$==nil\"`\n\t\t\t\tn *bool   `tagexpr:\"$==nil\"`\n\t\t\t\tp *string `tagexpr:\"$\"`\n\t\t\t}{\n\t\t\t\tA: 5,\n\t\t\t\tb: \"x\",\n\t\t\t\tc: struct {\n\t\t\t\t\t_ int\n\t\t\t\t\td bool `tagexpr:\"$\"`\n\t\t\t\t}{d: true},\n\t\t\t\te: &struct {\n\t\t\t\t\t_ int\n\t\t\t\t\tf bool `tagexpr:\"$\"`\n\t\t\t\t}{f: true},\n\t\t\t\tg: &g,\n\t\t\t\ti: \"12\",\n\t\t\t},\n\t\t\ttests: map[string]interface{}{\n\t\t\t\t\"A\":     true,\n\t\t\t\t\"b@is\":  false,\n\t\t\t\t\"b@msg\": \"expect: test, but got: x\",\n\t\t\t\t\"c.d\":   true,\n\t\t\t\t\"e.f\":   true,\n\t\t\t\t\"g\":     \"haha\",\n\t\t\t\t\"g.h\":   \"haha\",\n\t\t\t\t\"i\":     true,\n\t\t\t\t\"j\":     true,\n\t\t\t\t\"k\":     true,\n\t\t\t\t\"m\":     true,\n\t\t\t\t\"n\":     true,\n\t\t\t\t\"p\":     nil,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttagName: \"p\",\n\t\t\tstructure: &struct {\n\t\t\t\tq *struct {\n\t\t\t\t\tx int\n\t\t\t\t} `p:\"(q.x)$\"`\n\t\t\t}{},\n\t\t\ttests: map[string]interface{}{\n\t\t\t\t\"q\": nil,\n\t\t\t},\n\t\t},\n\t}\n\tfor i, c := range cases {\n\t\tvm := tagexpr.New(c.tagName)\n\t\t// vm.WarmUp(c.structure)\n\t\ttagExpr, err := vm.Run(c.structure)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tfor selector, value := range c.tests {\n\t\t\tval := tagExpr.Eval(selector)\n\t\t\tif !reflect.DeepEqual(val, value) {\n\t\t\t\tt.Fatalf(\"Eval Serial: %d, selector: %q, got: %v, expect: %v\", i, selector, val, value)\n\t\t\t}\n\t\t}\n\t\ttagExpr.Range(func(eh *tagexpr.ExprHandler) error {\n\t\t\tes := eh.ExprSelector()\n\t\t\tt.Logf(\"Range selector: %s, field: %q exprName: %q\", es, es.Field(), es.Name())\n\t\t\tvalue := c.tests[es.String()]\n\t\t\tval := eh.Eval()\n\t\t\tif !reflect.DeepEqual(val, value) {\n\t\t\t\tt.Fatalf(\"Range NO: %d, selector: %q, got: %v, expect: %v\", i, es, val, value)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n}\n\nfunc TestFieldNotInit(t *testing.T) {\n\tg := &struct {\n\t\t_ int\n\t\th string\n\t\ts []string\n\t\tm map[string][]string\n\t}{\n\t\th: \"haha\",\n\t\ts: []string{\"1\"},\n\t\tm: map[string][]string{\"0\": {\"2\"}},\n\t}\n\tstructure := &struct {\n\t\tA int\n\t\tb string\n\t\tc struct {\n\t\t\t_ int\n\t\t\td *bool `expr:\"test:nil\"`\n\t\t}\n\t\te *struct {\n\t\t\t_ int\n\t\t\tf bool\n\t\t}\n\t\tg **struct {\n\t\t\t_ int\n\t\t\th string\n\t\t\ts []string\n\t\t\tm map[string][]string\n\t\t}\n\t\ti string\n\t\tj bool\n\t\tk int\n\t\tm *int\n\t\tn *bool\n\t\tp *string\n\t}{\n\t\tA: 5,\n\t\tb: \"x\",\n\t\te: &struct {\n\t\t\t_ int\n\t\t\tf bool\n\t\t}{f: true},\n\t\tg: &g,\n\t\ti: \"12\",\n\t}\n\tvm := tagexpr.New(\"expr\")\n\te, err := vm.Run(structure)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcases := []struct {\n\t\tfieldSelector string\n\t\tvalue         interface{}\n\t}{\n\t\t{\"A\", structure.A},\n\t\t{\"b\", structure.b},\n\t\t{\"c\", structure.c},\n\t\t{\"c._\", 0},\n\t\t{\"c.d\", structure.c.d},\n\t\t{\"e\", structure.e},\n\t\t{\"e._\", 0},\n\t\t{\"e.f\", structure.e.f},\n\t\t{\"g\", structure.g},\n\t\t{\"g._\", 0},\n\t\t{\"g.h\", (*structure.g).h},\n\t\t{\"g.s\", (*structure.g).s},\n\t\t{\"g.m\", (*structure.g).m},\n\t\t{\"i\", structure.i},\n\t\t{\"j\", structure.j},\n\t\t{\"k\", structure.k},\n\t\t{\"m\", structure.m},\n\t\t{\"n\", structure.n},\n\t\t{\"p\", structure.p},\n\t}\n\tfor _, c := range cases {\n\t\tfh, _ := e.Field(c.fieldSelector)\n\t\tval := fh.Value(false).Interface()\n\t\tassertEqual(t, c.value, val, c.fieldSelector)\n\t}\n\tvar i int\n\te.RangeFields(func(fh *tagexpr.FieldHandler) bool {\n\t\tval := fh.Value(false).Interface()\n\t\tif fh.StringSelector() == \"c.d\" {\n\t\t\tif fh.EvalFuncs()[\"c.d@test\"] == nil {\n\t\t\t\tt.Fatal(\"nil\")\n\t\t\t}\n\t\t}\n\t\tassertEqual(t, cases[i].value, val, fh.StringSelector())\n\t\ti++\n\t\treturn true\n\t})\n\tvar wall uint64 = 1024\n\tunix := time.Unix(1549186325, int64(wall))\n\te, err = vm.Run(&unix)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfh, _ := e.Field(\"wall\")\n\tval := fh.Value(false).Interface()\n\tif !reflect.DeepEqual(val, wall) {\n\t\tt.Fatalf(\"Time.wall: got: %v(%[1]T), expect: %v(%[2]T)\", val, wall)\n\t}\n}\n\nfunc TestFieldInitZero(t *testing.T) {\n\tg := &struct {\n\t\t_ int\n\t\th string\n\t\ts []string\n\t\tm map[string][]string\n\t}{\n\t\th: \"haha\",\n\t\ts: []string{\"1\"},\n\t\tm: map[string][]string{\"0\": {\"2\"}},\n\t}\n\n\tstructure := &struct {\n\t\tA int\n\t\tb string\n\t\tc struct {\n\t\t\t_ int\n\t\t\td *bool\n\t\t}\n\t\te *struct {\n\t\t\t_ int\n\t\t\tf bool\n\t\t}\n\t\tg **struct {\n\t\t\t_ int\n\t\t\th string\n\t\t\ts []string\n\t\t\tm map[string][]string\n\t\t}\n\t\tg2 ****struct {\n\t\t\t_ int\n\t\t\th string\n\t\t\ts []string\n\t\t\tm map[string][]string\n\t\t}\n\t\ti string\n\t\tj bool\n\t\tk int\n\t\tm *int\n\t\tn *bool\n\t\tp *string\n\t}{\n\t\tA: 5,\n\t\tb: \"x\",\n\t\te: &struct {\n\t\t\t_ int\n\t\t\tf bool\n\t\t}{f: true},\n\t\tg: &g,\n\t\ti: \"12\",\n\t}\n\n\tvm := tagexpr.New(\"\")\n\te, err := vm.Run(structure)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcases := []struct {\n\t\tfieldSelector string\n\t\tvalue         interface{}\n\t}{\n\t\t{\"A\", structure.A},\n\t\t{\"b\", structure.b},\n\t\t{\"c\", struct {\n\t\t\t_ int\n\t\t\td *bool\n\t\t}{}},\n\t\t{\"c._\", 0},\n\t\t{\"c.d\", new(bool)},\n\t\t{\"e\", structure.e},\n\t\t{\"e._\", 0},\n\t\t{\"e.f\", structure.e.f},\n\t\t{\"g\", structure.g},\n\t\t{\"g._\", 0},\n\t\t{\"g.h\", (*structure.g).h},\n\t\t{\"g.s\", (*structure.g).s},\n\t\t{\"g.m\", (*structure.g).m},\n\t\t{\"g2.m\", (map[string][]string)(nil)},\n\t\t{\"i\", structure.i},\n\t\t{\"j\", structure.j},\n\t\t{\"k\", structure.k},\n\t\t{\"m\", new(int)},\n\t\t{\"n\", new(bool)},\n\t\t{\"p\", new(string)},\n\t}\n\tfor _, c := range cases {\n\t\tfh, _ := e.Field(c.fieldSelector)\n\t\tval := fh.Value(true).Interface()\n\t\tassertEqual(t, c.value, val, c.fieldSelector)\n\t}\n}\n\nfunc TestOperator(t *testing.T) {\n\ttype Tmp1 struct {\n\t\tA string `tagexpr:$==\"1\"||$==\"2\"||$=\"3\"` //nolint:govet\n\t\tB []int  `tagexpr:len($)>=10&&$[0]<10`   //nolint:govet\n\t\tC interface{}\n\t}\n\n\ttype Tmp2 struct {\n\t\tA *Tmp1\n\t\tB interface{}\n\t}\n\n\ttype Target struct {\n\t\tA int             `tagexpr:\"-$+$<=10\"`\n\t\tB int             `tagexpr:\"+$-$<=10\"`\n\t\tC int             `tagexpr:\"-$+(M)$*(N)$/$%(D.B)$[2]+$==1\"`\n\t\tD *Tmp1           `tagexpr:\"(D.A)$!=nil\"`\n\t\tE string          `tagexpr:\"((D.A)$=='1'&&len($)>1)||((D.A)$=='2'&&len($)>2)||((D.A)$=='3'&&len($)>3)\"`\n\t\tF map[string]int  `tagexpr:\"x:len($);y:$['a']>10&&$['b']>1\"`\n\t\tG *map[string]int `tagexpr:\"x:$['a']+(F)$['a']>20\"`\n\t\tH []string        `tagexpr:\"len($)>=1&&len($)<10&&$[0]=='123'&&$[1]!='456'\"`\n\t\tI interface{}     `tagexpr:\"$!=nil\"`\n\t\tK *string         `tagexpr:\"len((D.A)$)+len($)<10&&len((D.A)$+$)<10\"`\n\t\tL **string        `tagexpr:\"false\"`\n\t\tM float64         `tagexpr:\"$/2>10&&$%2==0\"`\n\t\tN *float64        `tagexpr:\"($+$*$-$/$+1)/$==$+1\"`\n\t\tO *[3]float64     `tagexpr:\"$[0]>10&&$[0]<20||$[0]>20&&$[0]<30\"`\n\t\tP *Tmp2           `tagexpr:\"x:$!=nil;y:len((P.A.A)$)<=1&&(P.A.B)$[0]==1;z:$['A']['C']==nil;w:$['A']['B'][0]==1;r:$[0][1][2]==3;s1:$[2]==nil;s2:$[0][3]==nil;s3:(ZZ)$;s4:(P.B)$!=nil\"`\n\t\tQ *Tmp2           `tagexpr:\"s1:$['A']['B']!=nil;s2:(Q.A)$['B']!=nil;s3:$['A']['C']==nil;s4:(Q.A)$['C']==nil;s5:(Q.A)$['B'][0]==1;s6:$['X']['Z']==nil\"`\n\t}\n\n\tk := \"123456\"\n\tn := float64(-12.5)\n\to := [3]float64{15, 9, 9}\n\tcases := []struct {\n\t\ttagName   string\n\t\tstructure interface{}\n\t\ttests     map[string]interface{}\n\t}{\n\t\t{\n\t\t\ttagName: \"tagexpr\",\n\t\t\tstructure: &Target{\n\t\t\t\tA: 5,\n\t\t\t\tB: 10,\n\t\t\t\tC: -10,\n\t\t\t\tD: &Tmp1{A: \"3\", B: []int{1, 2, 3}},\n\t\t\t\tE: \"1234\",\n\t\t\t\tF: map[string]int{\"a\": 11, \"b\": 9},\n\t\t\t\tG: &map[string]int{\"a\": 11},\n\t\t\t\tH: []string{\"123\", \"45\"},\n\t\t\t\tI: struct{}{},\n\t\t\t\tK: &k,\n\t\t\t\tL: nil,\n\t\t\t\tM: float64(30),\n\t\t\t\tN: &n,\n\t\t\t\tO: &o,\n\t\t\t\tP: &Tmp2{A: &Tmp1{A: \"3\", B: []int{1, 2, 3}}, B: struct{}{}},\n\t\t\t\tQ: &Tmp2{A: &Tmp1{A: \"3\", B: []int{1, 2, 3}}, B: struct{}{}},\n\t\t\t},\n\t\t\ttests: map[string]interface{}{\n\t\t\t\t\"A\":   true,\n\t\t\t\t\"B\":   true,\n\t\t\t\t\"C\":   true,\n\t\t\t\t\"D\":   true,\n\t\t\t\t\"E\":   true,\n\t\t\t\t\"F@x\": float64(2),\n\t\t\t\t\"F@y\": true,\n\t\t\t\t\"G@x\": true,\n\t\t\t\t\"H\":   true,\n\t\t\t\t\"I\":   true,\n\t\t\t\t\"K\":   true,\n\t\t\t\t\"L\":   false,\n\t\t\t\t\"M\":   true,\n\t\t\t\t\"N\":   true,\n\t\t\t\t\"O\":   true,\n\n\t\t\t\t\"P@x\":  true,\n\t\t\t\t\"P@y\":  true,\n\t\t\t\t\"P@z\":  true,\n\t\t\t\t\"P@w\":  true,\n\t\t\t\t\"P@r\":  true,\n\t\t\t\t\"P@s1\": true,\n\t\t\t\t\"P@s2\": true,\n\t\t\t\t\"P@s3\": nil,\n\t\t\t\t\"P@s4\": true,\n\n\t\t\t\t\"Q@s1\": true,\n\t\t\t\t\"Q@s2\": true,\n\t\t\t\t\"Q@s3\": true,\n\t\t\t\t\"Q@s4\": true,\n\t\t\t\t\"Q@s5\": true,\n\t\t\t\t\"Q@s6\": true,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, c := range cases {\n\t\tvm := tagexpr.New(c.tagName)\n\t\t// vm.WarmUp(c.structure)\n\t\ttagExpr, err := vm.Run(c.structure)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tfor selector, value := range c.tests {\n\t\t\tval := tagExpr.Eval(selector)\n\t\t\tif !reflect.DeepEqual(val, value) {\n\t\t\t\tt.Fatalf(\"Eval NO: %d, selector: %q, got: %v, expect: %v\", i, selector, val, value)\n\t\t\t}\n\t\t}\n\t\ttagExpr.Range(func(eh *tagexpr.ExprHandler) error {\n\t\t\tes := eh.ExprSelector()\n\t\t\tt.Logf(\"Range selector: %s, field: %q exprName: %q\", es, es.Field(), es.Name())\n\t\t\tvalue := c.tests[es.String()]\n\t\t\tval := eh.Eval()\n\t\t\tif !reflect.DeepEqual(val, value) {\n\t\t\t\tt.Fatalf(\"Range NO: %d, selector: %q, got: %v, expect: %v\", i, es, val, value)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n}\n\nfunc TestStruct(t *testing.T) {\n\ttype A struct {\n\t\tB struct {\n\t\t\tC struct {\n\t\t\t\tD struct {\n\t\t\t\t\tX string `vd:\"$\"`\n\t\t\t\t}\n\t\t\t} `vd:\"@:$['D']['X']\"`\n\t\t\tC2 string `vd:\"@:(C)$['D']['X']\"`\n\t\t\tC3 string `vd:\"@:(C.D.X)$\"`\n\t\t}\n\t}\n\ta := new(A)\n\ta.B.C.D.X = \"xxx\"\n\tvm := tagexpr.New(\"vd\")\n\texpr := vm.MustRun(a)\n\tassertEqual(t, \"xxx\", expr.EvalString(\"B.C2\"))\n\tassertEqual(t, \"xxx\", expr.EvalString(\"B.C3\"))\n\tassertEqual(t, \"xxx\", expr.EvalString(\"B.C\"))\n\tassertEqual(t, \"xxx\", expr.EvalString(\"B.C.D.X\"))\n\texpr.Range(func(eh *tagexpr.ExprHandler) error {\n\t\tes := eh.ExprSelector()\n\t\tt.Logf(\"Range selector: %s, field: %q exprName: %q\", es, es.Field(), es.Name())\n\t\tif eh.Eval().(string) != \"xxx\" {\n\t\t\tt.FailNow()\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc TestStruct2(t *testing.T) {\n\ttype IframeBlock struct {\n\t\tXBlock struct {\n\t\t\tBlockType string `vd:\"$\"`\n\t\t}\n\t\tProps struct {\n\t\t\tData struct {\n\t\t\t\tDataType string `vd:\"$\"`\n\t\t\t}\n\t\t}\n\t}\n\tb := new(IframeBlock)\n\tb.XBlock.BlockType = \"BlockType\"\n\tb.Props.Data.DataType = \"DataType\"\n\tvm := tagexpr.New(\"vd\")\n\texpr := vm.MustRun(b)\n\tif expr.EvalString(\"XBlock.BlockType\") != \"BlockType\" {\n\t\tt.Fatal(expr.EvalString(\"XBlock.BlockType\"))\n\t}\n\tif expr.EvalString(\"Props.Data.DataType\") != \"DataType\" {\n\t\tt.Fatal(expr.EvalString(\"Props.Data.DataType\"))\n\t}\n}\n\nfunc TestStruct3(t *testing.T) {\n\ttype Data struct {\n\t\tDataType string `vd:\"$\"`\n\t}\n\ttype Prop struct {\n\t\tPropType string       `vd:\"$\"`\n\t\tDD       []*Data      `vd:\"$\"`\n\t\tDD2      []*Data      `vd:\"$\"`\n\t\tDataMap  map[int]Data `vd:\"$\"`\n\t\tDataMap2 map[int]Data `vd:\"$\"`\n\t}\n\ttype IframeBlock struct {\n\t\tXBlock struct {\n\t\t\tBlockType string `vd:\"$\"`\n\t\t}\n\t\tProps    []Prop        `vd:\"$\"`\n\t\tProps1   [2]Prop       `vd:\"$\"`\n\t\tProps2   []Prop        `vd:\"$\"`\n\t\tPropMap  map[int]*Prop `vd:\"$\"`\n\t\tPropMap2 map[int]*Prop `vd:\"$\"`\n\t}\n\n\tb := new(IframeBlock)\n\tb.XBlock.BlockType = \"BlockType\"\n\tp1 := Prop{\n\t\tPropType: \"p1\",\n\t\tDD: []*Data{\n\t\t\t{\"p1s1\"},\n\t\t\t{\"p1s2\"},\n\t\t\tnil,\n\t\t},\n\t\tDataMap: map[int]Data{\n\t\t\t1: {\"p1m1\"},\n\t\t\t2: {\"p1m2\"},\n\t\t\t0: {},\n\t\t},\n\t}\n\tb.Props = []Prop{p1}\n\tp2 := &Prop{\n\t\tPropType: \"p2\",\n\t\tDD: []*Data{\n\t\t\t{\"p2s1\"},\n\t\t\t{\"p2s2\"},\n\t\t\tnil,\n\t\t},\n\t\tDataMap: map[int]Data{\n\t\t\t1: {\"p2m1\"},\n\t\t\t2: {\"p2m2\"},\n\t\t\t0: {},\n\t\t},\n\t}\n\tb.Props1 = [2]Prop{p1, {}}\n\tb.PropMap = map[int]*Prop{\n\t\t9: p2,\n\t}\n\n\tvm := tagexpr.New(\"vd\")\n\texpr := vm.MustRun(b)\n\tif expr.EvalString(\"XBlock.BlockType\") != \"BlockType\" {\n\t\tt.Fatal(expr.EvalString(\"XBlock.BlockType\"))\n\t}\n\terr := expr.Range(func(eh *tagexpr.ExprHandler) error {\n\t\tes := eh.ExprSelector()\n\t\tt.Logf(\"Range selector: %s, field: %q exprName: %q, eval: %v\", eh.Path(), es.Field(), es.Name(), eh.Eval())\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestNilField(t *testing.T) {\n\ttype P struct {\n\t\tX **struct {\n\t\t\tA *[]uint16 `tagexpr:\"$\"`\n\t\t} `tagexpr:\"$\"`\n\t\tY **struct{} `tagexpr:\"$\"`\n\t}\n\tvm := tagexpr.New(\"tagexpr\")\n\tte := vm.MustRun(&P{})\n\tte.Range(func(eh *tagexpr.ExprHandler) error {\n\t\tr := eh.Eval()\n\t\tif r != nil {\n\t\t\tt.Fatal(eh.Path())\n\t\t}\n\t\treturn nil\n\t})\n\n\ttype G struct {\n\t\t// Nil1 *int `tagexpr:\"nil!=$\"`\n\t\tNil2 *int `tagexpr:\"$!=nil\"`\n\t}\n\tg := &G{\n\t\t// Nil1: new(int),\n\t\tNil2: new(int),\n\t}\n\tvm.MustRun(g).Range(func(eh *tagexpr.ExprHandler) error {\n\t\tr, ok := eh.Eval().(bool)\n\t\tif !ok || !r {\n\t\t\tt.Fatal(eh.Path())\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc TestDeepNested(t *testing.T) {\n\ttype testInner struct {\n\t\tAddress string `tagexpr:\"name:$\"`\n\t}\n\ttype struct1 struct {\n\t\tI *testInner\n\t\tA []*testInner\n\t\tX interface{}\n\t}\n\ttype struct2 struct {\n\t\tS *struct1\n\t}\n\ttype Data struct {\n\t\tS1 *struct2\n\t\tS2 *struct2\n\t}\n\tdata := &Data{\n\t\tS1: &struct2{\n\t\t\tS: &struct1{\n\t\t\t\tI: &testInner{Address: \"I:address\"},\n\t\t\t\tA: []*testInner{{Address: \"A:address\"}},\n\t\t\t\tX: []*testInner{{Address: \"X:address\"}},\n\t\t\t},\n\t\t},\n\t\tS2: &struct2{\n\t\t\tS: &struct1{\n\t\t\t\tA: []*testInner{nil},\n\t\t\t},\n\t\t},\n\t}\n\texpectKey := [...]interface{}{\"S1.S.I.Address@name\", \"S2.S.I.Address@name\", \"S1.S.A[0].Address@name\", \"S2.S.A[0].Address@name\", \"S1.S.X[0].Address@name\"}\n\texpectValue := [...]interface{}{\"I:address\", nil, \"A:address\", nil, \"X:address\"}\n\tvar i int\n\tvm := tagexpr.New(\"tagexpr\")\n\tvm.MustRun(data).Range(func(eh *tagexpr.ExprHandler) error {\n\t\tassertEqual(t, expectKey[i], eh.Path())\n\t\tassertEqual(t, expectValue[i], eh.Eval())\n\t\ti++\n\t\tt.Log(eh.Path(), eh.ExprSelector(), eh.Eval())\n\t\treturn nil\n\t})\n\tassertEqual(t, 5, i)\n}\n\nfunc TestIssue3(t *testing.T) {\n\ttype C struct {\n\t\tId    string\n\t\tIndex int32 `vd:\"$\"`\n\t\tP     *int  `vd:\"$!=nil\"`\n\t}\n\ttype A struct {\n\t\tF1 *C\n\t\tF2 *C\n\t}\n\ta := &A{\n\t\tF1: &C{\n\t\t\tId:    \"test\",\n\t\t\tIndex: 1,\n\t\t\tP:     new(int),\n\t\t},\n\t}\n\tvm := tagexpr.New(\"vd\")\n\terr := vm.MustRun(a).Range(func(eh *tagexpr.ExprHandler) error {\n\t\tswitch eh.Path() {\n\t\tcase \"F1.Index\":\n\t\t\tassertEqual(t, float64(1), eh.Eval(), eh.Path())\n\t\tcase \"F2.Index\":\n\t\t\tassertEqual(t, nil, eh.Eval(), eh.Path())\n\t\tcase \"F1.P\":\n\t\t\tassertEqual(t, true, eh.Eval(), eh.Path())\n\t\tcase \"F2.P\":\n\t\t\tassertEqual(t, false, eh.Eval(), eh.Path())\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestIssue4(t *testing.T) {\n\ttype T struct {\n\t\tA *string `te:\"len($)+mblen($)\"`\n\t\tB *string `te:\"len($)+mblen($)\"`\n\t\tC *string `te:\"len($)+mblen($)\"`\n\t}\n\tc := \"c\"\n\tv := &T{\n\t\tB: new(string),\n\t\tC: &c,\n\t}\n\tvm := tagexpr.New(\"te\")\n\terr := vm.MustRun(v).Range(func(eh *tagexpr.ExprHandler) error {\n\t\tt.Logf(\"eval:%v, path:%s\", eh.EvalFloat(), eh.Path())\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestIssue5(t *testing.T) {\n\ttype A struct {\n\t\tF1 int `vd:\"true && $ <= 24*60*60\"`        // 1500 ok\n\t\tF2 int `vd:\"$%60 == 0 && $ <= (24*60*60)\"` // 1500 ok\n\t\tF3 int `vd:\"$ <= 24*60*60\"`                // 1500 ok\n\t}\n\ta := &A{\n\t\tF1: 1500,\n\t\tF2: 1500,\n\t\tF3: 1500,\n\t}\n\tvm := tagexpr.New(\"vd\")\n\terr := vm.MustRun(a).Range(func(eh *tagexpr.ExprHandler) error {\n\t\tswitch eh.Path() {\n\t\tcase \"F1\":\n\t\t\tassertEqual(t, true, eh.Eval(), eh.Path())\n\t\tcase \"F2\":\n\t\t\tassertEqual(t, true, eh.Eval(), eh.Path())\n\t\tcase \"F3\":\n\t\t\tassertEqual(t, true, eh.Eval(), eh.Path())\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestHertzIssue1410(t *testing.T) {\n\ttype HelloReq struct {\n\t\tMeta *structpb.Struct `protobuf:\"bytes,2,opt,name=meta,proto3\" form:\"meta\" json:\"meta,omitempty\"`\n\t}\n\tx := &HelloReq{}\n\tif err := json.Unmarshal([]byte(`{\"meta\": {\"test\": \"value\"}}`), x); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tte := tagexpr.New(\"test\").MustRun(x)\n\tif err := te.Range(func(eh *tagexpr.ExprHandler) error { return nil }); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestFakeBool(t *testing.T) {\n\t// Numeric types - zero values should be false, non-zero should be true\n\ttests := []struct {\n\t\tinput    interface{}\n\t\texpected bool\n\t}{\n\t\t// Float types\n\t\t{float64(0), false},\n\t\t{float64(3.14), true},\n\t\t{float32(0), false},\n\t\t{float32(2.5), true},\n\n\t\t// Integer types\n\t\t{int(0), false}, {int(42), true},\n\t\t{int8(0), false}, {int8(127), true},\n\t\t{int16(0), false}, {int16(32767), true},\n\t\t{int32(0), false}, {int32(2147483647), true},\n\t\t{int64(0), false}, {int64(9223372036854775807), true},\n\n\t\t// Unsigned integer types\n\t\t{uint(0), false}, {uint(1), true},\n\t\t{uint8(0), false}, {uint8(255), true},\n\t\t{uint16(0), false}, {uint16(65535), true},\n\t\t{uint32(0), false}, {uint32(4294967295), true},\n\t\t{uint64(0), false}, {uint64(18446744073709551615), true},\n\n\t\t// String type\n\t\t{\"\", false},\n\t\t{\"hello\", true},\n\n\t\t// Boolean type\n\t\t{true, true},\n\t\t{false, false},\n\n\t\t// Nil and error types\n\t\t{nil, false},\n\t\t{errors.New(\"test\"), false},\n\n\t\t// Slice of interfaces - all elements must be truthy for true\n\t\t{[]interface{}{}, true},                 // empty slice -> true\n\t\t{[]interface{}{1, \"hello\", true}, true}, // all truthy -> true\n\t\t{[]interface{}{1, \"\", true}, false},     // one falsy -> false\n\t\t{[]interface{}{0, \"\", false}, false},    // all falsy -> false\n\t\t{[]interface{}{nil, nil}, false},        // nil values are falsy -> false\n\n\t\t// Unsupported types should return false\n\t\t{struct{}{}, false},\n\t\t{new(int), false},\n\t\t{make(chan int), false},\n\t\t{func() {}, false},\n\t\t{map[string]int{}, false},\n\t\t{[3]int{1, 2, 3}, false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tresult := tagexpr.FakeBool(tt.input)\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"FakeBool(%v) = %v; want %v\", tt.input, result, tt.expected)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/tagexpr/tagparser.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nconst (\n\ttagOmit    = \"-\"\n\ttagOmitNil = \"?\"\n)\n\nfunc (f *fieldVM) parseExprs(tag string) error {\n\tswitch tag {\n\tcase tagOmit, tagOmitNil:\n\t\tf.tagOp = tag\n\t\treturn nil\n\t}\n\n\tkvs, err := parseTag(tag)\n\tif err != nil {\n\t\treturn err\n\t}\n\texprSelectorPrefix := f.structField.Name\n\n\tfor exprSelector, exprString := range kvs {\n\t\texpr, err := parseExpr(exprString)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif exprSelector == ExprNameSeparator {\n\t\t\texprSelector = exprSelectorPrefix\n\t\t} else {\n\t\t\texprSelector = exprSelectorPrefix + ExprNameSeparator + exprSelector\n\t\t}\n\t\tf.exprs[exprSelector] = expr\n\t\tf.origin.exprs[exprSelector] = expr\n\t\tf.origin.exprSelectorList = append(f.origin.exprSelectorList, exprSelector)\n\t}\n\treturn nil\n}\n\nfunc parseTag(tag string) (map[string]string, error) {\n\ts := tag\n\tptr := &s\n\tkvs := make(map[string]string)\n\tfor {\n\t\tone, err := readOneExpr(ptr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif one == \"\" {\n\t\t\treturn kvs, nil\n\t\t}\n\t\tkey, val := splitExpr(one)\n\t\tif val == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"syntax error: %q expression string can not be empty\", tag)\n\t\t}\n\t\tif _, ok := kvs[key]; ok {\n\t\t\treturn nil, fmt.Errorf(\"syntax error: %q duplicate expression name %q\", tag, key)\n\t\t}\n\t\tkvs[key] = val\n\t}\n}\n\nfunc splitExpr(one string) (key, val string) {\n\tone = strings.TrimSpace(one)\n\tif one == \"\" {\n\t\treturn DefaultExprName, \"\"\n\t}\n\tvar rs []rune\n\tfor _, r := range one {\n\t\tif r == '@' ||\n\t\t\tr == '_' ||\n\t\t\t(r >= '0' && r <= '9') ||\n\t\t\t(r >= 'A' && r <= 'Z') ||\n\t\t\t(r >= 'a' && r <= 'z') {\n\t\t\trs = append(rs, r)\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\tkey = string(rs)\n\tval = strings.TrimSpace(one[len(key):])\n\tif val == \"\" || val[0] != ':' {\n\t\treturn DefaultExprName, one\n\t}\n\tval = val[1:]\n\tif key == \"\" {\n\t\tkey = DefaultExprName\n\t}\n\treturn key, val\n}\n\nfunc readOneExpr(tag *string) (string, error) {\n\ts := *(trimRightSpace(trimLeftSpace(tag)))\n\ts = strings.TrimLeft(s, \";\")\n\tif s == \"\" {\n\t\treturn \"\", nil\n\t}\n\tif s[len(s)-1] != ';' {\n\t\ts += \";\"\n\t}\n\ta := strings.SplitAfter(strings.Replace(s, \"\\\\'\", \"##\", -1), \";\")\n\tidx := -1\n\tvar patch int\n\tfor _, v := range a {\n\t\tidx += len(v)\n\t\tcount := strings.Count(v, \"'\")\n\t\tif (count+patch)%2 == 0 {\n\t\t\t*tag = s[idx+1:]\n\t\t\treturn s[:idx], nil\n\t\t}\n\t\tif count > 0 {\n\t\t\tpatch++\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"syntax error: %q unclosed single quote \\\"'\\\"\", s)\n}\n\nfunc trimLeftSpace(p *string) *string {\n\t*p = strings.TrimLeftFunc(*p, unicode.IsSpace)\n\treturn p\n}\n\nfunc trimRightSpace(p *string) *string {\n\t*p = strings.TrimRightFunc(*p, unicode.IsSpace)\n\treturn p\n}\n\nfunc readPairedSymbol(p *string, left, right rune) *string {\n\ts := *p\n\tif len(s) == 0 || rune(s[0]) != left {\n\t\treturn nil\n\t}\n\ts = s[1:]\n\tlast1 := left\n\tvar last2 rune\n\tvar leftLevel, rightLevel int\n\tescapeIndexes := make(map[int]bool)\n\tvar realEqual, escapeEqual bool\n\tfor i, r := range s {\n\t\tif realEqual, escapeEqual = equalRune(right, r, last1, last2); realEqual {\n\t\t\tif leftLevel == rightLevel {\n\t\t\t\t*p = s[i+1:]\n\t\t\t\tsub := make([]rune, 0, i)\n\t\t\t\tfor k, v := range s[:i] {\n\t\t\t\t\tif !escapeIndexes[k] {\n\t\t\t\t\t\tsub = append(sub, v)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ts = string(sub)\n\t\t\t\treturn &s\n\t\t\t}\n\t\t\trightLevel++\n\t\t} else if escapeEqual {\n\t\t\tescapeIndexes[i-1] = true\n\t\t} else if realEqual, escapeEqual = equalRune(left, r, last1, last2); realEqual {\n\t\t\tleftLevel++\n\t\t} else if escapeEqual {\n\t\t\tescapeIndexes[i-1] = true\n\t\t}\n\t\tlast2 = last1\n\t\tlast1 = r\n\t}\n\treturn nil\n}\n\nfunc equalRune(a, b, last1, last2 rune) (real, escape bool) {\n\tif a == b {\n\t\treal = last1 != '\\\\' || last2 == '\\\\'\n\t\tescape = last1 == '\\\\' && last2 != '\\\\'\n\t}\n\treturn\n}\n"
  },
  {
    "path": "internal/tagexpr/tagparser_test.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage tagexpr\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestTagparser(t *testing.T) {\n\tcases := []struct {\n\t\ttag    reflect.StructTag\n\t\texpect map[string]string\n\t\tfail   bool\n\t}{\n\t\t{\n\t\t\ttag: `tagexpr:\"$>0\"`,\n\t\t\texpect: map[string]string{\n\t\t\t\t\"@\": \"$>0\",\n\t\t\t},\n\t\t}, {\n\t\t\ttag:  `tagexpr:\"$>0;'xxx'\"`,\n\t\t\tfail: true,\n\t\t}, {\n\t\t\ttag: `tagexpr:\"$>0;b:sprintf('%[1]T; %[1]v',(X)$)\"`,\n\t\t\texpect: map[string]string{\n\t\t\t\t\"@\": `$>0`,\n\t\t\t\t\"b\": `sprintf('%[1]T; %[1]v',(X)$)`,\n\t\t\t},\n\t\t}, {\n\t\t\ttag: `tagexpr:\"a:$=='0;1;';b:sprintf('%[1]T; %[1]v',(X)$)\"`,\n\t\t\texpect: map[string]string{\n\t\t\t\t\"a\": `$=='0;1;'`,\n\t\t\t\t\"b\": `sprintf('%[1]T; %[1]v',(X)$)`,\n\t\t\t},\n\t\t}, {\n\t\t\ttag: `tagexpr:\"a:1;;b:2\"`,\n\t\t\texpect: map[string]string{\n\t\t\t\t\"a\": `1`,\n\t\t\t\t\"b\": `2`,\n\t\t\t},\n\t\t}, {\n\t\t\ttag: `tagexpr:\";a:1;;b:2;;;\"`,\n\t\t\texpect: map[string]string{\n\t\t\t\t\"a\": `1`,\n\t\t\t\t\"b\": `2`,\n\t\t\t},\n\t\t}, {\n\t\t\ttag: `tagexpr:\";a:'123\\\\'';;b:'1\\\\'23';c:'1\\\\'2\\\\'3';;\"`,\n\t\t\texpect: map[string]string{\n\t\t\t\t\"a\": `'123\\''`,\n\t\t\t\t\"b\": `'1\\'23'`,\n\t\t\t\t\"c\": `'1\\'2\\'3'`,\n\t\t\t},\n\t\t}, {\n\t\t\ttag: `tagexpr:\"email($)\"`,\n\t\t\texpect: map[string]string{\n\t\t\t\t\"@\": `email($)`,\n\t\t\t},\n\t\t}, {\n\t\t\ttag: `tagexpr:\"false\"`,\n\t\t\texpect: map[string]string{\n\t\t\t\t\"@\": `false`,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tr, e := parseTag(c.tag.Get(\"tagexpr\"))\n\t\tif e != nil == c.fail {\n\t\t\tif !reflect.DeepEqual(c.expect, r) {\n\t\t\t\tt.Fatal(c.expect, r, c.tag)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Fatalf(\"tag:%s kvs:%v, err:%v\", c.tag, r, e)\n\t\t}\n\t\tif e != nil {\n\t\t\tt.Logf(\"tag:%q, errMsg:%v\", c.tag, e)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/tagexpr/utils.go",
    "content": "/*\n * Copyright 2024 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage tagexpr\n\nimport (\n\t\"reflect\"\n\t\"unsafe\"\n)\n\nfunc init() {\n\ttesthack()\n}\n\nfunc dereferenceValue(v reflect.Value) reflect.Value {\n\tfor v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {\n\t\tv = v.Elem()\n\t}\n\treturn v\n}\n\nfunc dereferenceType(t reflect.Type) reflect.Type {\n\tfor t.Kind() == reflect.Ptr {\n\t\tt = t.Elem()\n\t}\n\treturn t\n}\n\nfunc dereferenceInterfaceValue(v reflect.Value) reflect.Value {\n\tfor v.Kind() == reflect.Interface {\n\t\tv = v.Elem()\n\t}\n\treturn v\n}\n\ntype rvtype struct { // reflect.Value\n\tabiType uintptr\n\tptr     unsafe.Pointer // data pointer\n}\n\nfunc rvPtr(rv reflect.Value) unsafe.Pointer {\n\treturn (*rvtype)(unsafe.Pointer(&rv)).ptr\n}\n\nfunc rvType(rv reflect.Value) uintptr {\n\treturn (*rvtype)(unsafe.Pointer(&rv)).abiType\n}\n\nfunc rtType(rt reflect.Type) uintptr {\n\ttype iface struct {\n\t\ttab  uintptr\n\t\tdata uintptr\n\t}\n\treturn (*iface)(unsafe.Pointer(&rt)).data\n}\n\n// quick test make sure the hack above works\nfunc testhack() {\n\ttype T1 struct {\n\t\ta int\n\t}\n\ttype T2 struct {\n\t\ta int\n\t}\n\tp0 := &T1{1}\n\tp1 := &T1{2}\n\tp2 := &T2{3}\n\n\tif rvPtr(reflect.ValueOf(p0)) != unsafe.Pointer(p0) ||\n\t\trvPtr(reflect.ValueOf(p0).Elem()) != unsafe.Pointer(p0) ||\n\t\trvPtr(reflect.ValueOf(p0)) == rvPtr(reflect.ValueOf(p1)) {\n\t\tpanic(\"rvPtr() compatibility issue found\")\n\t}\n\n\tif rvType(reflect.ValueOf(p0)) != rvType(reflect.ValueOf(p1)) ||\n\t\trvType(reflect.ValueOf(p0)) == rvType(reflect.ValueOf(p2)) ||\n\t\trvType(reflect.ValueOf(p0).Elem()) != rvType(reflect.ValueOf(p1).Elem()) ||\n\t\trvType(reflect.ValueOf(p0).Elem()) == rvType(reflect.ValueOf(p2).Elem()) {\n\t\tpanic(\"rvType() compatibility issue found\")\n\t}\n\n\tif rtType(reflect.TypeOf(p0)) != rtType(reflect.TypeOf(p1)) ||\n\t\trtType(reflect.TypeOf(p0)) == rtType(reflect.TypeOf(p2)) ||\n\t\trtType(reflect.TypeOf(p0).Elem()) != rtType(reflect.TypeOf(p1).Elem()) ||\n\t\trtType(reflect.TypeOf(p0).Elem()) == rtType(reflect.TypeOf(p2).Elem()) {\n\t\tpanic(\"rtType() compatibility issue found\")\n\t}\n}\n"
  },
  {
    "path": "internal/tagexpr/validator/README.md",
    "content": "# validator\n\noriginally from https://github.com/bytedance/go-tagexpr v2.9.2\n"
  },
  {
    "path": "internal/tagexpr/validator/default.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage validator\n\nvar defaultValidator = New(\"vd\").SetErrorFactory(defaultErrorFactory)\n\n// Default returns the default validator.\n// NOTE:\n//\n//\tThe tag name is 'vd'\nfunc Default() *Validator {\n\treturn defaultValidator\n}\n\n// Validate uses the default validator to validate whether the fields of value is valid.\n// NOTE:\n//\n//\tThe tag name is 'vd'\n//\tIf checkAll=true, validate all the error.\nfunc Validate(value interface{}, checkAll ...bool) error {\n\treturn defaultValidator.Validate(value, checkAll...)\n}\n\n// SetErrorFactory customizes the factory of validation error for the default validator.\n// NOTE:\n//\n//\tThe tag name is 'vd'\nfunc SetErrorFactory(errFactory func(fieldSelector, msg string) error) {\n\tdefaultValidator.SetErrorFactory(errFactory)\n}\n"
  },
  {
    "path": "internal/tagexpr/validator/example_test.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage validator_test\n\nimport (\n\t\"fmt\"\n\n\tvd \"github.com/cloudwego/hertz/internal/tagexpr/validator\"\n)\n\nfunc Example() {\n\ttype InfoRequest struct {\n\t\tName         string   `vd:\"($!='Alice'||(Age)$==18) && regexp('\\\\w')\"`\n\t\tAge          int      `vd:\"$>0\"`\n\t\tEmail        string   `vd:\"email($)\"`\n\t\tPhone1       string   `vd:\"phone($)\"`\n\t\tOtherPhones  []string `vd:\"range($, phone(#v,'CN'))\"`\n\t\t*InfoRequest `vd:\"?\"`\n\t\tInfo1        *InfoRequest `vd:\"?\"`\n\t\tInfo2        *InfoRequest `vd:\"-\"`\n\t}\n\tinfo := &InfoRequest{\n\t\tName:        \"Alice\",\n\t\tAge:         18,\n\t\tEmail:       \"henrylee2cn@gmail.com\",\n\t\tPhone1:      \"+8618812345678\",\n\t\tOtherPhones: []string{\"18812345679\", \"18812345680\"},\n\t}\n\tfmt.Println(vd.Validate(info))\n\n\ttype A struct {\n\t\tA    int `vd:\"$<0||$>=100\"`\n\t\tInfo interface{}\n\t}\n\tinfo.Email = \"xxx\"\n\ta := &A{A: 107, Info: info}\n\tfmt.Println(vd.Validate(a))\n\ttype B struct {\n\t\tB string `vd:\"len($)>1 && regexp('^\\\\w*$')\"`\n\t}\n\tb := &B{\"abc\"}\n\tfmt.Println(vd.Validate(b) == nil)\n\n\ttype C struct {\n\t\tC bool `vd:\"@:(S.A)$>0 && !$; msg:'C must be false when S.A>0'\"`\n\t\tS *A\n\t}\n\tc := &C{C: true, S: a}\n\tfmt.Println(vd.Validate(c))\n\n\ttype D struct {\n\t\td []string `vd:\"@:len($)>0 && $[0]=='D'; msg:sprintf('invalid d: %v',$)\"`\n\t}\n\td := &D{d: []string{\"x\", \"y\"}}\n\tfmt.Println(vd.Validate(d))\n\n\ttype E struct {\n\t\te map[string]int `vd:\"len($)==$['len']\"`\n\t}\n\te := &E{map[string]int{\"len\": 2}}\n\tfmt.Println(vd.Validate(e))\n\n\t// Customizes the factory of validation error.\n\tvd.SetErrorFactory(func(failPath, msg string) error {\n\t\treturn fmt.Errorf(`{\"succ\":false, \"error\":\"validation failed: %s\"}`, failPath)\n\t})\n\n\ttype F struct {\n\t\tf struct {\n\t\t\tg int `vd:\"$%3==0\"`\n\t\t}\n\t}\n\tf := &F{}\n\tf.f.g = 10\n\tfmt.Println(vd.Validate(f))\n\n\tfmt.Println(vd.Validate(map[string]*F{\"a\": f}))\n\tfmt.Println(vd.Validate(map[string]map[string]*F{\"a\": {\"b\": f}}))\n\tfmt.Println(vd.Validate([]map[string]*F{{\"a\": f}}))\n\tfmt.Println(vd.Validate(struct {\n\t\tA []map[string]*F\n\t}{A: []map[string]*F{{\"x\": f}}}))\n\tfmt.Println(vd.Validate(map[*F]int{f: 1}))\n\tfmt.Println(vd.Validate([][1]*F{{f}}))\n\tfmt.Println(vd.Validate((*F)(nil)))\n\tfmt.Println(vd.Validate(map[string]*F{}))\n\tfmt.Println(vd.Validate(map[string]map[string]*F{}))\n\tfmt.Println(vd.Validate([]map[string]*F{}))\n\tfmt.Println(vd.Validate([]*F{}))\n\n\t// Output:\n\t// <nil>\n\t// email format is incorrect\n\t// true\n\t// C must be false when S.A>0\n\t// invalid d: [x y]\n\t// invalid parameter: e\n\t// {\"succ\":false, \"error\":\"validation failed: f.g\"}\n\t// {\"succ\":false, \"error\":\"validation failed: {v for k=a}.f.g\"}\n\t// {\"succ\":false, \"error\":\"validation failed: {v for k=a}{v for k=b}.f.g\"}\n\t// {\"succ\":false, \"error\":\"validation failed: [0]{v for k=a}.f.g\"}\n\t// {\"succ\":false, \"error\":\"validation failed: A[0]{v for k=x}.f.g\"}\n\t// {\"succ\":false, \"error\":\"validation failed: {k}.f.g\"}\n\t// {\"succ\":false, \"error\":\"validation failed: [0][0].f.g\"}\n\t// unsupported data: nil\n\t// <nil>\n\t// <nil>\n\t// <nil>\n\t// <nil>\n}\n"
  },
  {
    "path": "internal/tagexpr/validator/func.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage validator\n\nimport (\n\t\"errors\"\n\t\"regexp\"\n\n\t\"github.com/cloudwego/hertz/internal/tagexpr\"\n)\n\n// ErrInvalidWithoutMsg verification error without error message.\nvar ErrInvalidWithoutMsg = errors.New(\"\")\n\n// MustRegFunc registers validator function expression.\n// NOTE:\n//\n//\tpanic if exist error;\n//\texample: phone($) or phone($,'CN');\n//\tIf @force=true, allow to cover the existed same @funcName;\n//\tThe go number types always are float64;\n//\tThe go string types always are string.\nfunc MustRegFunc(funcName string, fn func(args ...interface{}) error, force ...bool) {\n\terr := RegFunc(funcName, fn, force...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// RegFunc registers validator function expression.\n// NOTE:\n//\n//\texample: phone($) or phone($,'CN');\n//\tIf @force=true, allow to cover the existed same @funcName;\n//\tThe go number types always are float64;\n//\tThe go string types always are string.\nfunc RegFunc(funcName string, fn func(args ...interface{}) error, force ...bool) error {\n\treturn tagexpr.RegFunc(funcName, func(args ...interface{}) interface{} {\n\t\terr := fn(args...)\n\t\tif err == nil {\n\t\t\t// nil defaults to false, so returns true\n\t\t\treturn true\n\t\t}\n\t\treturn err\n\t}, force...)\n}\n\nfunc init() {\n\tpattern := \"^([A-Za-z0-9_\\\\-\\\\.\\u4e00-\\u9fa5])+\\\\@([A-Za-z0-9_\\\\-\\\\.])+\\\\.([A-Za-z]{2,8})$\"\n\temailRegexp := regexp.MustCompile(pattern)\n\tMustRegFunc(\"email\", func(args ...interface{}) error {\n\t\tif len(args) != 1 {\n\t\t\treturn errors.New(\"number of parameters of email function is not one\")\n\t\t}\n\t\ts, ok := args[0].(string)\n\t\tif !ok {\n\t\t\treturn errors.New(\"parameter of email function is not string type\")\n\t\t}\n\t\tmatched := emailRegexp.MatchString(s)\n\t\tif !matched {\n\t\t\t// return ErrInvalidWithoutMsg\n\t\t\treturn errors.New(\"email format is incorrect\")\n\t\t}\n\t\treturn nil\n\t}, true)\n}\n\n// Phone validation always returns true.\n//\n// Removed github.com/nyaruka/phonenumbers dependency for the following reasons:\n// 1. The tagexpr validator package is deprecated\n// 2. The phonenumbers library has unresolved issues requiring upgrades\n// 3. The phonenumbers library is memory-heavy (loads many objects into memory even when unused)\n//\n// Since this validator is deprecated, we simply return true for all phone numbers\n// instead of maintaining complex validation logic.\nfunc validatePhone(numberToParse, region string) bool {\n\treturn true\n}\n\nfunc init() {\n\t// phone: defaultRegion is 'CN'\n\tMustRegFunc(\"phone\", func(args ...interface{}) error {\n\t\tvar numberToParse, defaultRegion string\n\t\tvar ok bool\n\t\tswitch len(args) {\n\t\tdefault:\n\t\t\treturn errors.New(\"the number of parameters of phone function is not one or two\")\n\t\tcase 2:\n\t\t\tdefaultRegion, ok = args[1].(string)\n\t\t\tif !ok {\n\t\t\t\treturn errors.New(\"the 2nd parameter of phone function is not string type\")\n\t\t\t}\n\t\t\tfallthrough\n\t\tcase 1:\n\t\t\tnumberToParse, ok = args[0].(string)\n\t\t\tif !ok {\n\t\t\t\treturn errors.New(\"the 1st parameter of phone function is not string type\")\n\t\t\t}\n\t\t}\n\t\tif defaultRegion == \"\" {\n\t\t\tdefaultRegion = \"CN\"\n\t\t}\n\t\tif !validatePhone(numberToParse, defaultRegion) {\n\t\t\treturn errors.New(\"phone format is incorrect\")\n\t\t}\n\t\treturn nil\n\t}, true)\n}\n"
  },
  {
    "path": "internal/tagexpr/validator/validator.go",
    "content": "// Package validator is a powerful validator that supports struct tag expression.\n//\n// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//\thttp://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\npackage validator\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"reflect\"\n\t\"strings\"\n\t_ \"unsafe\"\n\n\t\"github.com/cloudwego/hertz/internal/tagexpr\"\n)\n\nconst (\n\t// MatchExprName the name of the expression used for validation\n\tMatchExprName = tagexpr.DefaultExprName\n\t// ErrMsgExprName the name of the expression used to specify the message\n\t// returned when validation failed\n\tErrMsgExprName = \"msg\"\n)\n\n// Validator struct fields validator\ntype Validator struct {\n\tvm         *tagexpr.VM\n\terrFactory func(failPath, msg string) error\n}\n\n// New creates a struct fields validator.\nfunc New(tagName string) *Validator {\n\tv := &Validator{\n\t\tvm:         tagexpr.New(tagName),\n\t\terrFactory: defaultErrorFactory,\n\t}\n\treturn v\n}\n\n// VM returns the struct tag expression interpreter.\nfunc (v *Validator) VM() *tagexpr.VM {\n\treturn v.vm\n}\n\n// Validate validates whether the fields of value is valid.\n// NOTE:\n//\n//\tIf checkAll=true, validate all the error.\nfunc (v *Validator) Validate(value interface{}, checkAll ...bool) error {\n\tvar all bool\n\tif len(checkAll) > 0 {\n\t\tall = checkAll[0]\n\t}\n\terrs := make([]error, 0, 8)\n\terr := v.vm.RunAny(value, func(te *tagexpr.TagExpr, err error) error {\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t\tif all {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn io.EOF\n\t\t}\n\t\tnilParentFields := make(map[string]bool, 16)\n\t\terr = te.Range(func(eh *tagexpr.ExprHandler) error {\n\t\t\tif strings.Contains(eh.StringSelector(), tagexpr.ExprNameSeparator) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tr := eh.Eval()\n\t\t\tif r == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trerr, ok := r.(error)\n\t\t\tif !ok && tagexpr.FakeBool(r) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// Ignore this error if the value of the parent is nil\n\t\t\tif pfs, ok := eh.ExprSelector().ParentField(); ok {\n\t\t\t\tif nilParentFields[pfs] {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif fh, ok := eh.TagExpr().Field(pfs); ok {\n\t\t\t\t\tv := fh.Value(false)\n\t\t\t\t\tif !v.IsValid() || (v.Kind() == reflect.Ptr && v.IsNil()) {\n\t\t\t\t\t\tnilParentFields[pfs] = true\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tmsg := eh.TagExpr().EvalString(eh.StringSelector() + tagexpr.ExprNameSeparator + ErrMsgExprName)\n\t\t\tif msg == \"\" && rerr != nil {\n\t\t\t\tmsg = rerr.Error()\n\t\t\t}\n\t\t\terrs = append(errs, v.errFactory(eh.Path(), msg))\n\t\t\tif all {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn io.EOF\n\t\t})\n\t\tif err != nil && !all {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n\tif err != io.EOF && err != nil {\n\t\treturn err\n\t}\n\tswitch len(errs) {\n\tcase 0:\n\t\treturn nil\n\tcase 1:\n\t\treturn errs[0]\n\tdefault:\n\t\tvar errStr string\n\t\tfor _, e := range errs {\n\t\t\terrStr += e.Error() + \"\\t\"\n\t\t}\n\t\treturn errors.New(errStr[:len(errStr)-1])\n\t}\n}\n\n// SetErrorFactory customizes the factory of validation error.\n// NOTE:\n//\n//\tIf errFactory==nil, the default is used\nfunc (v *Validator) SetErrorFactory(errFactory func(failPath, msg string) error) *Validator {\n\tif errFactory == nil {\n\t\terrFactory = defaultErrorFactory\n\t}\n\tv.errFactory = errFactory\n\treturn v\n}\n\n// Error validate error\ntype Error struct {\n\tFailPath, Msg string\n}\n\n// Error implements error interface.\nfunc (e *Error) Error() string {\n\tif e.Msg != \"\" {\n\t\treturn e.Msg\n\t}\n\treturn \"invalid parameter: \" + e.FailPath\n}\n\n//go:nosplit\nfunc defaultErrorFactory(failPath, msg string) error {\n\treturn &Error{\n\t\tFailPath: failPath,\n\t\tMsg:      msg,\n\t}\n}\n"
  },
  {
    "path": "internal/tagexpr/validator/validator_test.go",
    "content": "// Copyright 2019 Bytedance Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//  http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage validator_test\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"testing\"\n\n\tvd \"github.com/cloudwego/hertz/internal/tagexpr/validator\"\n)\n\nfunc assertEqualError(t *testing.T, err error, s string) {\n\tt.Helper()\n\tif err.Error() != s {\n\t\tt.Fatal(\"not equal\", err, s)\n\t}\n}\n\nfunc assertNoError(t *testing.T, err error) {\n\tt.Helper()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestNil(t *testing.T) {\n\ttype F struct {\n\t\tF struct {\n\t\t\tG int `vd:\"$%3==1\"`\n\t\t}\n\t}\n\tassertEqualError(t, vd.Validate((*F)(nil)), \"unsupported data: nil\")\n}\n\nfunc TestAll(t *testing.T) {\n\ttype T struct {\n\t\tA string `vd:\"email($)\"`\n\t\tF struct {\n\t\t\tG int `vd:\"$%3==1\"`\n\t\t}\n\t}\n\tassertEqualError(t, vd.Validate(new(T), true), \"email format is incorrect\\tinvalid parameter: F.G\")\n}\n\nfunc TestIssue1(t *testing.T) {\n\ttype MailBox struct {\n\t\tAddress *string `vd:\"email($)\"`\n\t\tName    *string\n\t}\n\ttype EmailMsg struct {\n\t\tRecipients       []*MailBox\n\t\tRecipientsCc     []*MailBox\n\t\tRecipientsBcc    []*MailBox\n\t\tSubject          *string\n\t\tContent          *string\n\t\tAttachmentIDList []string\n\t\tReplyTo          *string\n\t\tParams           map[string]string\n\t\tFromEmailAddress *string\n\t\tFromEmailName    *string\n\t}\n\ttype EmailTaskInfo struct {\n\t\tMsg         *EmailMsg\n\t\tStartTimeMS *int64\n\t\tLogTag      *string\n\t}\n\ttype BatchCreateEmailTaskRequest struct {\n\t\tInfoList []*EmailTaskInfo\n\t}\n\tinvalid := \"invalid email\"\n\treq := &BatchCreateEmailTaskRequest{\n\t\tInfoList: []*EmailTaskInfo{\n\t\t\t{\n\t\t\t\tMsg: &EmailMsg{\n\t\t\t\t\tRecipients: []*MailBox{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAddress: &invalid,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tassertEqualError(t, vd.Validate(req, false), \"email format is incorrect\")\n}\n\nfunc TestIssue2(t *testing.T) {\n\ttype a struct {\n\t\tm map[string]interface{}\n\t}\n\tA := &a{\n\t\tm: map[string]interface{}{\n\t\t\t\"1\": 1,\n\t\t\t\"2\": nil,\n\t\t},\n\t}\n\tv := vd.New(\"vd\")\n\tassertNoError(t, v.Validate(A))\n}\n\nfunc TestIssue3(t *testing.T) {\n\ttype C struct {\n\t\tId    string\n\t\tIndex int32 `vd:\"$==1\"`\n\t}\n\ttype A struct {\n\t\tF1 *C\n\t\tF2 *C\n\t}\n\ta := &A{\n\t\tF1: &C{\n\t\t\tId:    \"test\",\n\t\t\tIndex: 1,\n\t\t},\n\t}\n\tv := vd.New(\"vd\")\n\tassertNoError(t, v.Validate(a))\n}\n\nfunc TestIssue4(t *testing.T) {\n\ttype C struct {\n\t\tIndex  *int32 `vd:\"@:$!=nil;msg:'index is nil'\"`\n\t\tIndex2 *int32 `vd:\"$!=nil\"`\n\t\tIndex3 *int32 `vd:\"$!=nil\"`\n\t}\n\ttype A struct {\n\t\tF1 *C\n\t\tF2 map[string]*C\n\t\tF3 []*C\n\t}\n\tv := vd.New(\"vd\")\n\n\ta := &A{}\n\tassertNoError(t, v.Validate(a))\n\n\ta = &A{F1: new(C)}\n\tassertEqualError(t, v.Validate(a), \"index is nil\")\n\n\ta = &A{F2: map[string]*C{\"x\": {Index: new(int32)}}}\n\tassertEqualError(t, v.Validate(a), \"invalid parameter: F2{v for k=x}.Index2\")\n\n\ta = &A{F3: []*C{{Index: new(int32)}}}\n\tassertEqualError(t, v.Validate(a), \"invalid parameter: F3[0].Index2\")\n\n\ttype B struct {\n\t\tF1 *C `vd:\"$!=nil\"`\n\t\tF2 *C\n\t}\n\tb := &B{}\n\tassertEqualError(t, v.Validate(b), \"invalid parameter: F1\")\n\n\ttype D struct {\n\t\tF1 *C\n\t\tF2 *C\n\t}\n\n\ttype E struct {\n\t\tD []*D\n\t}\n\tb.F1 = new(C)\n\te := &E{D: []*D{nil}}\n\tassertNoError(t, v.Validate(e))\n}\n\nfunc TestIssue5(t *testing.T) {\n\ttype SubSheet struct{}\n\ttype CopySheet struct {\n\t\tSource      *SubSheet `json:\"source\" vd:\"$!=nil\"`\n\t\tDestination *SubSheet `json:\"destination\" vd:\"$!=nil\"`\n\t}\n\ttype UpdateSheetsRequest struct {\n\t\tCopySheet *CopySheet `json:\"copySheet\"`\n\t}\n\ttype BatchUpdateSheetRequestArg struct {\n\t\tRequests []*UpdateSheetsRequest `json:\"requests\"`\n\t}\n\tb := `{\"requests\": [{}]}`\n\tvar data BatchUpdateSheetRequestArg\n\terr := json.Unmarshal([]byte(b), &data)\n\tassertNoError(t, err)\n\tif len(data.Requests) != 1 {\n\t\tt.Fatal(len(data.Requests))\n\t}\n\tif data.Requests[0].CopySheet != nil {\n\t\tt.Fatal(data.Requests[0].CopySheet)\n\t}\n\tv := vd.New(\"vd\")\n\tassertNoError(t, v.Validate(&data))\n}\n\nfunc TestIn(t *testing.T) {\n\ttype S string\n\ttype I int16\n\ttype T struct {\n\t\tX *int `vd:\"$==nil || len($)>0\"`\n\t\tA S    `vd:\"in($,'a','b','c')\"`\n\t\tB I    `vd:\"in($,1,2.0,3)\"`\n\t}\n\tv := vd.New(\"vd\")\n\tdata := &T{}\n\terr := v.Validate(data)\n\tassertEqualError(t, err, \"invalid parameter: A\")\n\tdata.A = \"b\"\n\terr = v.Validate(data)\n\tassertEqualError(t, err, \"invalid parameter: B\")\n\tdata.B = 2\n\terr = v.Validate(data)\n\tassertNoError(t, err)\n\n\ttype T2 struct {\n\t\tC string `vd:\"in($)\"`\n\t}\n\tdata2 := &T2{}\n\terr = v.Validate(data2)\n\tassertEqualError(t, err, \"invalid parameter: C\")\n\n\ttype T3 struct {\n\t\tC string `vd:\"in($,1)\"`\n\t}\n\tdata3 := &T3{}\n\terr = v.Validate(data3)\n\tassertEqualError(t, err, \"invalid parameter: C\")\n}\n\ntype (\n\tIssue23A struct {\n\t\tB *Issue23B\n\t\tV int64 `vd:\"$==0\"`\n\t}\n\tIssue23B struct {\n\t\tA *Issue23A\n\t\tV int64 `vd:\"$==0\"`\n\t}\n)\n\nfunc TestIssue23(t *testing.T) {\n\tdata := &Issue23B{A: &Issue23A{B: new(Issue23B)}}\n\terr := vd.Validate(data, true)\n\tassertNoError(t, err)\n}\n\nfunc TestIssue24(t *testing.T) {\n\ttype SubmitDoctorImportItem struct {\n\t\tName       string   `form:\"name,required\" json:\"name,required\" query:\"name,required\"`\n\t\tAvatar     *string  `form:\"avatar,omitempty\" json:\"avatar,omitempty\" query:\"avatar,omitempty\"`\n\t\tIdcard     string   `form:\"idcard,required\" json:\"idcard,required\" query:\"idcard,required\" vd:\"len($)==18\"`\n\t\tIdcardPics []string `form:\"idcard_pics,omitempty\" json:\"idcard_pics,omitempty\" query:\"idcard_pics,omitempty\"`\n\t\tHosp       string   `form:\"hosp,required\" json:\"hosp,required\" query:\"hosp,required\"`\n\t\tHospDept   string   `form:\"hosp_dept,required\" json:\"hosp_dept,required\" query:\"hosp_dept,required\"`\n\t\tHospProv   *string  `form:\"hosp_prov,omitempty\" json:\"hosp_prov,omitempty\" query:\"hosp_prov,omitempty\"`\n\t\tHospCity   *string  `form:\"hosp_city,omitempty\" json:\"hosp_city,omitempty\" query:\"hosp_city,omitempty\"`\n\t\tHospCounty *string  `form:\"hosp_county,omitempty\" json:\"hosp_county,omitempty\" query:\"hosp_county,omitempty\"`\n\t\tProTit     string   `form:\"pro_tit,required\" json:\"pro_tit,required\" query:\"pro_tit,required\"`\n\t\tThTit      *string  `form:\"th_tit,omitempty\" json:\"th_tit,omitempty\" query:\"th_tit,omitempty\"`\n\t\tServDepts  *string  `form:\"serv_depts,omitempty\" json:\"serv_depts,omitempty\" query:\"serv_depts,omitempty\"`\n\t\tTitCerts   []string `form:\"tit_certs,omitempty\" json:\"tit_certs,omitempty\" query:\"tit_certs,omitempty\"`\n\t\tThTitCerts []string `form:\"th_tit_certs,omitempty\" json:\"th_tit_certs,omitempty\" query:\"th_tit_certs,omitempty\"`\n\t\tPracCerts  []string `form:\"prac_certs,omitempty\" json:\"prac_certs,omitempty\" query:\"prac_certs,omitempty\"`\n\t\tQualCerts  []string `form:\"qual_certs,omitempty\" json:\"qual_certs,omitempty\" query:\"qual_certs,omitempty\"`\n\t\tPracCertNo string   `form:\"prac_cert_no,required\" json:\"prac_cert_no,required\" query:\"prac_cert_no,required\" vd:\"len($)==15\"`\n\t\tGoodat     *string  `form:\"goodat,omitempty\" json:\"goodat,omitempty\" query:\"goodat,omitempty\"`\n\t\tIntro      *string  `form:\"intro,omitempty\" json:\"intro,omitempty\" query:\"intro,omitempty\"`\n\t\tLinkman    string   `form:\"linkman,required\" json:\"linkman,required\" query:\"linkman,required\" vd:\"email($)\"`\n\t\tPhone      string   `form:\"phone,required\" json:\"phone,required\" query:\"phone,required\" vd:\"phone($,'CN')\"`\n\t}\n\n\ttype SubmitDoctorImportRequest struct {\n\t\tSubmitDoctorImport []*SubmitDoctorImportItem `form:\"submit_doctor_import,required\" json:\"submit_doctor_import,required\"`\n\t}\n\tdata := &SubmitDoctorImportRequest{SubmitDoctorImport: []*SubmitDoctorImportItem{{}}}\n\terr := vd.Validate(data, true)\n\tassertEqualError(t, err, \"invalid parameter: SubmitDoctorImport[0].Idcard\\tinvalid parameter: SubmitDoctorImport[0].PracCertNo\\temail format is incorrect\")\n}\n\nfunc TestStructSliceMap(t *testing.T) {\n\ttype F struct {\n\t\tf struct {\n\t\t\tg int `vd:\"$%3==0\"`\n\t\t}\n\t}\n\tf := &F{}\n\tf.f.g = 10\n\ttype S struct {\n\t\tA map[string]*F\n\t\tB []map[string]*F\n\t\tC map[string][]map[string]F\n\t\t// _ int\n\t}\n\ts := S{\n\t\tA: map[string]*F{\"x\": f},\n\t\tB: []map[string]*F{{\"y\": f}},\n\t\tC: map[string][]map[string]F{\"z\": {{\"zz\": *f}}},\n\t}\n\terr := vd.Validate(s, true)\n\tassertEqualError(t, err, \"invalid parameter: A{v for k=x}.f.g\\tinvalid parameter: B[0]{v for k=y}.f.g\\tinvalid parameter: C{v for k=z}[0]{v for k=zz}.f.g\")\n}\n\nfunc TestIssue30(t *testing.T) {\n\ttype TStruct struct {\n\t\tTOk string `vd:\"gt($,'0') && gt($, '1')\" json:\"t_ok\"`\n\t\t// TFail string `vd:\"gt($,'0')\" json:\"t_fail\"`\n\t}\n\tvd.RegFunc(\"gt\", func(args ...interface{}) error {\n\t\treturn errors.New(\"force error\")\n\t})\n\tassertEqualError(t, vd.Validate(&TStruct{TOk: \"1\"}), \"invalid parameter: TOk\")\n\t// assertNoError(t, vd.Validate(&TStruct{TOk: \"1\", TFail: \"1\"}))\n}\n\nfunc TestIssue31(t *testing.T) {\n\ttype TStruct struct {\n\t\tA []int32 `vd:\"$ == nil || ($ != nil && range($, in(#v, 1, 2, 3))\"`\n\t}\n\tassertEqualError(t, vd.Validate(&TStruct{A: []int32{1}}), \"syntax error: \\\"($ != nil && range($, in(#v, 1, 2, 3))\\\"\")\n\tassertEqualError(t, vd.Validate(&TStruct{A: []int32{1}}), \"syntax error: \\\"($ != nil && range($, in(#v, 1, 2, 3))\\\"\")\n\tassertEqualError(t, vd.Validate(&TStruct{A: []int32{1}}), \"syntax error: \\\"($ != nil && range($, in(#v, 1, 2, 3))\\\"\")\n}\n\nfunc TestRegexp(t *testing.T) {\n\ttype TStruct struct {\n\t\tA string `vd:\"regexp('(\\\\d+\\\\.){3}\\\\d+')\"`\n\t}\n\tassertNoError(t, vd.Validate(&TStruct{A: \"0.0.0.0\"}))\n\tassertEqualError(t, vd.Validate(&TStruct{A: \"0...0\"}), \"invalid parameter: A\")\n\tassertEqualError(t, vd.Validate(&TStruct{A: \"abc1\"}), \"invalid parameter: A\")\n\tassertEqualError(t, vd.Validate(&TStruct{A: \"0?0?0?0\"}), \"invalid parameter: A\")\n}\n\nfunc TestRangeIn(t *testing.T) {\n\ttype S struct {\n\t\tF []string `vd:\"range($, in(#v, '', 'ttp', 'euttp'))\"`\n\t}\n\terr := vd.Validate(S{\n\t\tF: []string{\"ttp\", \"\", \"euttp\"},\n\t})\n\tassertNoError(t, err)\n\terr = vd.Validate(S{\n\t\tF: []string{\"ttp\", \"?\", \"euttp\"},\n\t})\n\tassertEqualError(t, err, \"invalid parameter: F\")\n}\n"
  },
  {
    "path": "internal/test/mock/binder/binder.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage binder\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\n// Binder provides a mock implementation of the Binder interface for testing.\ntype Binder struct {\n\tValidateError error // Error to return from Validate method\n}\n\n// NewBinder creates a new mock binder.\nfunc NewBinder() *Binder {\n\treturn &Binder{}\n}\n\n// NewBinderWithValidateError creates a new mock binder that returns the specified error from Validate.\nfunc NewBinderWithValidateError(err error) *Binder {\n\treturn &Binder{ValidateError: err}\n}\n\nfunc (m *Binder) Name() string {\n\treturn \"test binder\"\n}\n\nfunc (m *Binder) Bind(request *protocol.Request, i interface{}, params param.Params) error {\n\treturn nil\n}\n\nfunc (m *Binder) BindAndValidate(request *protocol.Request, i interface{}, params param.Params) error {\n\treturn nil\n}\n\nfunc (m *Binder) BindQuery(request *protocol.Request, i interface{}) error {\n\treturn nil\n}\n\nfunc (m *Binder) BindHeader(request *protocol.Request, i interface{}) error {\n\treturn nil\n}\n\nfunc (m *Binder) BindPath(request *protocol.Request, i interface{}, params param.Params) error {\n\treturn nil\n}\n\nfunc (m *Binder) BindForm(request *protocol.Request, i interface{}) error {\n\treturn nil\n}\n\nfunc (m *Binder) BindJSON(request *protocol.Request, i interface{}) error {\n\treturn nil\n}\n\nfunc (m *Binder) BindProtobuf(request *protocol.Request, i interface{}) error {\n\treturn nil\n}\n\nfunc (m *Binder) Validate(request *protocol.Request, i interface{}) error {\n\tif m.ValidateError != nil {\n\t\treturn m.ValidateError\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/test/mock/binder/binder_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage binder\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestNewBinder(t *testing.T) {\n\tbinder := NewBinder()\n\tassert.DeepEqual(t, \"test binder\", binder.Name())\n\tassert.Nil(t, binder.ValidateError)\n\n\t// Test all binding methods return nil\n\tassert.Nil(t, binder.Bind(nil, nil, nil))\n\tassert.Nil(t, binder.BindAndValidate(nil, nil, nil))\n\tassert.Nil(t, binder.BindQuery(nil, nil))\n\tassert.Nil(t, binder.BindHeader(nil, nil))\n\tassert.Nil(t, binder.BindPath(nil, nil, nil))\n\tassert.Nil(t, binder.BindForm(nil, nil))\n\tassert.Nil(t, binder.BindJSON(nil, nil))\n\tassert.Nil(t, binder.BindProtobuf(nil, nil))\n\tassert.Nil(t, binder.Validate(nil, nil))\n}\n\nfunc TestNewBinderWithValidateError(t *testing.T) {\n\ttestErr := errors.New(\"test error\")\n\tbinder := NewBinderWithValidateError(testErr)\n\tassert.DeepEqual(t, testErr, binder.ValidateError)\n}\n\nfunc TestBinderValidate(t *testing.T) {\n\t// Test no error\n\tbinder1 := NewBinder()\n\tassert.Nil(t, binder1.Validate(nil, nil))\n\n\t// Test with error\n\ttestErr := errors.New(\"validation failed\")\n\tbinder2 := NewBinderWithValidateError(testErr)\n\tassert.DeepEqual(t, testErr, binder2.Validate(nil, nil))\n}\n"
  },
  {
    "path": "internal/testutils/testutils.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage testutils\n\nimport (\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n)\n\ntype RouteEngine interface {\n\tIsRunning() bool\n}\n\nfunc WaitEngineRunning(e RouteEngine) {\n\tfor i := 0; i < 100; i++ {\n\t\tif e.IsRunning() {\n\t\t\treturn\n\t\t}\n\t\ttime.Sleep(10 * time.Millisecond)\n\t}\n\tpanic(\"not running\")\n}\n\n// NewTestListener creates a TCP listener on a random available port.\n// It calls tb.Fatal if the listener cannot be created.\n// The caller is responsible for closing the listener (usually via defer).\nfunc NewTestListener(tb testing.TB) net.Listener {\n\ttb.Helper()\n\tln, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\ttb.Fatalf(\"failed to create test listener: %s\", err)\n\t}\n\treturn ln\n}\n"
  },
  {
    "path": "internal/testutils/testutils_test.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage testutils\n\nimport (\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestNewTestListener(t *testing.T) {\n\tln := NewTestListener(t)\n\tdefer ln.Close()\n\tt.Log(ln.Addr())\n}\n\ntype routeEngine struct {\n\tRunning atomic.Bool\n}\n\nfunc (e *routeEngine) IsRunning() bool {\n\treturn e.Running.Load()\n}\n\nfunc TestWaitEngineRunning(t *testing.T) {\n\te := &routeEngine{}\n\tgo func() {\n\t\ttime.Sleep(30 * time.Millisecond)\n\t\te.Running.Store(true)\n\t}()\n\tWaitEngineRunning(e)\n}\n"
  },
  {
    "path": "licenses/LICENSE-echo.txt",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2013, Julien Schmidt\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
  },
  {
    "path": "licenses/LICENSE-fasthttp.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2021 LabStack\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "licenses/LICENSE-fsnotify",
    "content": "Copyright © 2012 The Go Authors. All rights reserved.\nCopyright © fsnotify Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright notice, this\n  list of conditions and the following disclaimer in the documentation and/or\n  other materials provided with the distribution.\n* Neither the name of Google Inc. nor the names of its contributors may be used\n  to endorse or promote products derived from this software without specific\n  prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
  },
  {
    "path": "licenses/LICENSE-gin.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Manuel Martínez-Almeida\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "licenses/LICENSE-go-version.txt",
    "content": "Mozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. “Contributor”\n\n     means each individual or legal entity that creates, contributes to the\n     creation of, or owns Covered Software.\n\n1.2. “Contributor Version”\n\n     means the combination of the Contributions of others (if any) used by a\n     Contributor and that particular Contributor’s Contribution.\n\n1.3. “Contribution”\n\n     means Covered Software of a particular Contributor.\n\n1.4. “Covered Software”\n\n     means Source Code Form to which the initial Contributor has attached the\n     notice in Exhibit A, the Executable Form of such Source Code Form, and\n     Modifications of such Source Code Form, in each case including portions\n     thereof.\n\n1.5. “Incompatible With Secondary Licenses”\n     means\n\n     a. that the initial Contributor has attached the notice described in\n        Exhibit B to the Covered Software; or\n\n     b. that the Covered Software was made available under the terms of version\n        1.1 or earlier of the License, but not also under the terms of a\n        Secondary License.\n\n1.6. “Executable Form”\n\n     means any form of the work other than Source Code Form.\n\n1.7. “Larger Work”\n\n     means a work that combines Covered Software with other material, in a separate\n     file or files, that is not Covered Software.\n\n1.8. “License”\n\n     means this document.\n\n1.9. “Licensable”\n\n     means having the right to grant, to the maximum extent possible, whether at the\n     time of the initial grant or subsequently, any and all of the rights conveyed by\n     this License.\n\n1.10. “Modifications”\n\n     means any of the following:\n\n     a. any file in Source Code Form that results from an addition to, deletion\n        from, or modification of the contents of Covered Software; or\n\n     b. any new file in Source Code Form that contains any Covered Software.\n\n1.11. “Patent Claims” of a Contributor\n\n      means any patent claim(s), including without limitation, method, process,\n      and apparatus claims, in any patent Licensable by such Contributor that\n      would be infringed, but for the grant of the License, by the making,\n      using, selling, offering for sale, having made, import, or transfer of\n      either its Contributions or its Contributor Version.\n\n1.12. “Secondary License”\n\n      means either the GNU General Public License, Version 2.0, the GNU Lesser\n      General Public License, Version 2.1, the GNU Affero General Public\n      License, Version 3.0, or any later versions of those licenses.\n\n1.13. “Source Code Form”\n\n      means the form of the work preferred for making modifications.\n\n1.14. “You” (or “Your”)\n\n      means an individual or a legal entity exercising rights under this\n      License. For legal entities, “You” includes any entity that controls, is\n      controlled by, or is under common control with You. For purposes of this\n      definition, “control” means (a) the power, direct or indirect, to cause\n      the direction or management of such entity, whether by contract or\n      otherwise, or (b) ownership of more than fifty percent (50%) of the\n      outstanding shares or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n2.1. Grants\n\n     Each Contributor hereby grants You a world-wide, royalty-free,\n     non-exclusive license:\n\n     a. under intellectual property rights (other than patent or trademark)\n        Licensable by such Contributor to use, reproduce, make available,\n        modify, display, perform, distribute, and otherwise exploit its\n        Contributions, either on an unmodified basis, with Modifications, or as\n        part of a Larger Work; and\n\n     b. under Patent Claims of such Contributor to make, use, sell, offer for\n        sale, have made, import, and otherwise transfer either its Contributions\n        or its Contributor Version.\n\n2.2. Effective Date\n\n     The licenses granted in Section 2.1 with respect to any Contribution become\n     effective for each Contribution on the date the Contributor first distributes\n     such Contribution.\n\n2.3. Limitations on Grant Scope\n\n     The licenses granted in this Section 2 are the only rights granted under this\n     License. No additional rights or licenses will be implied from the distribution\n     or licensing of Covered Software under this License. Notwithstanding Section\n     2.1(b) above, no patent license is granted by a Contributor:\n\n     a. for any code that a Contributor has removed from Covered Software; or\n\n     b. for infringements caused by: (i) Your and any other third party’s\n        modifications of Covered Software, or (ii) the combination of its\n        Contributions with other software (except as part of its Contributor\n        Version); or\n\n     c. under Patent Claims infringed by Covered Software in the absence of its\n        Contributions.\n\n     This License does not grant any rights in the trademarks, service marks, or\n     logos of any Contributor (except as may be necessary to comply with the\n     notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\n     No Contributor makes additional grants as a result of Your choice to\n     distribute the Covered Software under a subsequent version of this License\n     (see Section 10.2) or under the terms of a Secondary License (if permitted\n     under the terms of Section 3.3).\n\n2.5. Representation\n\n     Each Contributor represents that the Contributor believes its Contributions\n     are its original creation(s) or it has sufficient rights to grant the\n     rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\n     This License is not intended to limit any rights You have under applicable\n     copyright doctrines of fair use, fair dealing, or other equivalents.\n\n2.7. Conditions\n\n     Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in\n     Section 2.1.\n\n\n3. Responsibilities\n\n3.1. Distribution of Source Form\n\n     All distribution of Covered Software in Source Code Form, including any\n     Modifications that You create or to which You contribute, must be under the\n     terms of this License. You must inform recipients that the Source Code Form\n     of the Covered Software is governed by the terms of this License, and how\n     they can obtain a copy of this License. You may not attempt to alter or\n     restrict the recipients’ rights in the Source Code Form.\n\n3.2. Distribution of Executable Form\n\n     If You distribute Covered Software in Executable Form then:\n\n     a. such Covered Software must also be made available in Source Code Form,\n        as described in Section 3.1, and You must inform recipients of the\n        Executable Form how they can obtain a copy of such Source Code Form by\n        reasonable means in a timely manner, at a charge no more than the cost\n        of distribution to the recipient; and\n\n     b. You may distribute such Executable Form under the terms of this License,\n        or sublicense it under different terms, provided that the license for\n        the Executable Form does not attempt to limit or alter the recipients’\n        rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\n     You may create and distribute a Larger Work under terms of Your choice,\n     provided that You also comply with the requirements of this License for the\n     Covered Software. If the Larger Work is a combination of Covered Software\n     with a work governed by one or more Secondary Licenses, and the Covered\n     Software is not Incompatible With Secondary Licenses, this License permits\n     You to additionally distribute such Covered Software under the terms of\n     such Secondary License(s), so that the recipient of the Larger Work may, at\n     their option, further distribute the Covered Software under the terms of\n     either this License or such Secondary License(s).\n\n3.4. Notices\n\n     You may not remove or alter the substance of any license notices (including\n     copyright notices, patent notices, disclaimers of warranty, or limitations\n     of liability) contained within the Source Code Form of the Covered\n     Software, except that You may alter any license notices to the extent\n     required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\n     You may choose to offer, and to charge a fee for, warranty, support,\n     indemnity or liability obligations to one or more recipients of Covered\n     Software. However, You may do so only on Your own behalf, and not on behalf\n     of any Contributor. You must make it absolutely clear that any such\n     warranty, support, indemnity, or liability obligation is offered by You\n     alone, and You hereby agree to indemnify every Contributor for any\n     liability incurred by such Contributor as a result of warranty, support,\n     indemnity or liability terms You offer. You may include additional\n     disclaimers of warranty and limitations of liability specific to any\n     jurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n\n   If it is impossible for You to comply with any of the terms of this License\n   with respect to some or all of the Covered Software due to statute, judicial\n   order, or regulation then You must: (a) comply with the terms of this License\n   to the maximum extent possible; and (b) describe the limitations and the code\n   they affect. Such description must be placed in a text file included with all\n   distributions of the Covered Software under this License. Except to the\n   extent prohibited by statute or regulation, such description must be\n   sufficiently detailed for a recipient of ordinary skill to be able to\n   understand it.\n\n5. Termination\n\n5.1. The rights granted under this License will terminate automatically if You\n     fail to comply with any of its terms. However, if You become compliant,\n     then the rights granted under this License from a particular Contributor\n     are reinstated (a) provisionally, unless and until such Contributor\n     explicitly and finally terminates Your grants, and (b) on an ongoing basis,\n     if such Contributor fails to notify You of the non-compliance by some\n     reasonable means prior to 60 days after You have come back into compliance.\n     Moreover, Your grants from a particular Contributor are reinstated on an\n     ongoing basis if such Contributor notifies You of the non-compliance by\n     some reasonable means, this is the first time You have received notice of\n     non-compliance with this License from such Contributor, and You become\n     compliant prior to 30 days after Your receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\n     infringement claim (excluding declaratory judgment actions, counter-claims,\n     and cross-claims) alleging that a Contributor Version directly or\n     indirectly infringes any patent, then the rights granted to You by any and\n     all Contributors for the Covered Software under Section 2.1 of this License\n     shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user\n     license agreements (excluding distributors and resellers) which have been\n     validly granted by You or Your distributors under this License prior to\n     termination shall survive termination.\n\n6. Disclaimer of Warranty\n\n   Covered Software is provided under this License on an “as is” basis, without\n   warranty of any kind, either expressed, implied, or statutory, including,\n   without limitation, warranties that the Covered Software is free of defects,\n   merchantable, fit for a particular purpose or non-infringing. The entire\n   risk as to the quality and performance of the Covered Software is with You.\n   Should any Covered Software prove defective in any respect, You (not any\n   Contributor) assume the cost of any necessary servicing, repair, or\n   correction. This disclaimer of warranty constitutes an essential part of this\n   License. No use of  any Covered Software is authorized under this License\n   except under this disclaimer.\n\n7. Limitation of Liability\n\n   Under no circumstances and under no legal theory, whether tort (including\n   negligence), contract, or otherwise, shall any Contributor, or anyone who\n   distributes Covered Software as permitted above, be liable to You for any\n   direct, indirect, special, incidental, or consequential damages of any\n   character including, without limitation, damages for lost profits, loss of\n   goodwill, work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses, even if such party shall have been\n   informed of the possibility of such damages. This limitation of liability\n   shall not apply to liability for death or personal injury resulting from such\n   party’s negligence to the extent applicable law prohibits such limitation.\n   Some jurisdictions do not allow the exclusion or limitation of incidental or\n   consequential damages, so this exclusion and limitation may not apply to You.\n\n8. Litigation\n\n   Any litigation relating to this License may be brought only in the courts of\n   a jurisdiction where the defendant maintains its principal place of business\n   and such litigation shall be governed by laws of that jurisdiction, without\n   reference to its conflict-of-law provisions. Nothing in this Section shall\n   prevent a party’s ability to bring cross-claims or counter-claims.\n\n9. Miscellaneous\n\n   This License represents the complete agreement concerning the subject matter\n   hereof. If any provision of this License is held to be unenforceable, such\n   provision shall be reformed only to the extent necessary to make it\n   enforceable. Any law or regulation which provides that the language of a\n   contract shall be construed against the drafter shall not be used to construe\n   this License against a Contributor.\n\n\n10. Versions of the License\n\n10.1. New Versions\n\n      Mozilla Foundation is the license steward. Except as provided in Section\n      10.3, no one other than the license steward has the right to modify or\n      publish new versions of this License. Each version will be given a\n      distinguishing version number.\n\n10.2. Effect of New Versions\n\n      You may distribute the Covered Software under the terms of the version of\n      the License under which You originally received the Covered Software, or\n      under the terms of any subsequent version published by the license\n      steward.\n\n10.3. Modified Versions\n\n      If you create software not governed by this License, and you want to\n      create a new license for such software, you may create and use a modified\n      version of this License if you rename the license and remove any\n      references to the name of the license steward (except to note that such\n      modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses\n      If You choose to distribute Source Code Form that is Incompatible With\n      Secondary Licenses under the terms of this version of the License, the\n      notice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n\n      This Source Code Form is subject to the\n      terms of the Mozilla Public License, v.\n      2.0. If a copy of the MPL was not\n      distributed with this file, You can\n      obtain one at\n      http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular file, then\nYou may include the notice in a location (such as a LICENSE file in a relevant\ndirectory) where a recipient would be likely to look for such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - “Incompatible With Secondary Licenses” Notice\n\n      This Source Code Form is “Incompatible\n      With Secondary Licenses”, as defined by\n      the Mozilla Public License, v. 2.0."
  },
  {
    "path": "licenses/LICENSE-protobuf.txt",
    "content": "Copyright (c) 2018 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "licenses/LICENSE-protoreflect.txt",
    "content": "\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "licenses/LICENSE-sprig.txt",
    "content": "Copyright (C) 2013-2020 Masterminds\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "licenses/LICENSE-yaml.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "pkg/app/client/client.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage client\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/internal/nocopy\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network/dialer\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/client\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/factory\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/suite\"\n)\n\nvar (\n\terrorInvalidURI          = errors.NewPublic(\"invalid uri\")\n\terrorLastMiddlewareExist = errors.NewPublic(\"last middleware already set\")\n)\n\n// Do performs the given http request and fills the given http response.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.©\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// If MaxConnsPerHost is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error {\n\treturn defaultClient.Do(ctx, req, resp)\n}\n\n// DoTimeout performs the given request and waits for response during\n// the given timeout duration.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// errTimeout is returned if the response wasn't returned during\n// the given timeout.\n//\n// If MaxConnsPerHost is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\n//\n// Warning: DoTimeout does not terminate the request itself. The request will\n// continue in the background and the response will be discarded.\n// If requests take too long and the connection pool gets filled up please\n// try using a customized Client instance with a ReadTimeout config or set the request level read timeout like:\n// `req.SetOptions(config.WithReadTimeout(1 * time.Second))`\nfunc DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration) error {\n\treturn defaultClient.DoTimeout(ctx, req, resp, timeout)\n}\n\n// DoDeadline performs the given request and waits for response until\n// the given deadline.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// errTimeout is returned if the response wasn't returned until\n// the given deadline.\n//\n// If MaxConnsPerHost is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\n//\n// Warning: DoDeadline does not terminate the request itself. The request will\n// continue in the background and the response will be discarded.\n// If requests take too long and the connection pool gets filled up please\n// try using a customized Client instance with a ReadTimeout config or set the request level read timeout like:\n// `req.SetOptions(config.WithReadTimeout(1 * time.Second))`\nfunc DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time) error {\n\treturn defaultClient.DoDeadline(ctx, req, resp, deadline)\n}\n\n// DoRedirects performs the given http request and fills the given http response,\n// following up to maxRedirectsCount redirects. When the redirect count exceeds\n// maxRedirectsCount, ErrTooManyRedirects is returned.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// Response is ignored if resp is nil.\n//\n// If MaxConnsPerHost is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, maxRedirectsCount int) error {\n\t_, _, err := client.DoRequestFollowRedirects(ctx, req, resp, req.URI().String(), maxRedirectsCount, defaultClient)\n\treturn err\n}\n\n// Get returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\nfunc Get(ctx context.Context, dst []byte, url string, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\treturn defaultClient.Get(ctx, dst, url, requestOptions...)\n}\n\n// GetTimeout returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// errTimeout error is returned if url contents couldn't be fetched\n// during the given timeout.\n//\n// Warning: GetTimeout does not terminate the request itself. The request will\n// continue in the background and the response will be discarded.\n// If requests take too long and the connection pool gets filled up please\n// try using a customized Client instance with a ReadTimeout config or set the request level read timeout like:\n// `GetTimeout(ctx, dst, url, timeout, config.WithReadTimeout(1 * time.Second))`\nfunc GetTimeout(ctx context.Context, dst []byte, url string, timeout time.Duration, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\treturn defaultClient.GetTimeout(ctx, dst, url, timeout, requestOptions...)\n}\n\n// GetDeadline returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// errTimeout error is returned if url contents couldn't be fetched\n// until the given deadline.\n//\n// Warning: GetDeadline does not terminate the request itself. The request will\n// continue in the background and the response will be discarded.\n// If requests take too long and the connection pool gets filled up please\n// try using a customized Client instance with a ReadTimeout config or set the request level read timeout like:\n// `GetDeadline(ctx, dst, url, timeout, config.WithReadTimeout(1 * time.Second))`\nfunc GetDeadline(ctx context.Context, dst []byte, url string, deadline time.Time, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\treturn defaultClient.GetDeadline(ctx, dst, url, deadline, requestOptions...)\n}\n\n// Post sends POST request to the given url with the given POST arguments.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// Empty POST body is sent if postArgs is nil.\nfunc Post(ctx context.Context, dst []byte, url string, postArgs *protocol.Args, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\treturn defaultClient.Post(ctx, dst, url, postArgs, requestOptions...)\n}\n\nvar defaultClient, _ = NewClient(WithDialTimeout(consts.DefaultDialTimeout))\n\n// Client implements http client.\n//\n// Copying Client by value is prohibited. Create new instance instead.\n//\n// It is safe calling Client methods from concurrently running goroutines.\ntype Client struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\toptions *config.ClientOptions\n\n\t// Proxy specifies a function to return a proxy for a given\n\t// Request. If the function returns a non-nil error, the\n\t// request is aborted with the provided error.\n\t//\n\t// The proxy type is determined by the URL scheme.\n\t// \"http\" and \"https\" are supported. If the scheme is empty,\n\t// \"http\" is assumed.\n\t//\n\t// If Proxy is nil or returns a nil *URL, no proxy is used.\n\tProxy protocol.Proxy\n\n\t// RetryIfFunc sets the retry decision function. If nil, the client.DefaultRetryIf will be applied.\n\tRetryIfFunc client.RetryIfFunc\n\n\tclientFactory suite.ClientFactory\n\n\tmLock          sync.Mutex\n\tm              map[string]client.HostClient\n\tms             map[string]client.HostClient\n\tmws            Middleware\n\tlastMiddleware Middleware\n}\n\nfunc (c *Client) GetOptions() *config.ClientOptions {\n\treturn c.options\n}\n\nfunc (c *Client) SetRetryIfFunc(retryIf client.RetryIfFunc) {\n\tc.RetryIfFunc = retryIf\n}\n\n// Deprecated: use SetRetryIfFunc instead of SetRetryIf\nfunc (c *Client) SetRetryIf(fn func(request *protocol.Request) bool) {\n\tf := func(req *protocol.Request, resp *protocol.Response, err error) bool {\n\t\treturn fn(req)\n\t}\n\tc.SetRetryIfFunc(f)\n}\n\n// SetProxy is used to set client proxy.\n//\n// Don't SetProxy twice for a client.\n// If you want to use another proxy, please create another client and set proxy to it.\nfunc (c *Client) SetProxy(p protocol.Proxy) {\n\tc.Proxy = p\n}\n\n// Get returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\nfunc (c *Client) Get(ctx context.Context, dst []byte, url string, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\treturn client.GetURL(ctx, dst, url, c, requestOptions...)\n}\n\n// GetTimeout returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// errTimeout error is returned if url contents couldn't be fetched\n// during the given timeout.\nfunc (c *Client) GetTimeout(ctx context.Context, dst []byte, url string, timeout time.Duration, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\treturn client.GetURLTimeout(ctx, dst, url, timeout, c, requestOptions...)\n}\n\n// GetDeadline returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// errTimeout error is returned if url contents couldn't be fetched\n// until the given deadline.\nfunc (c *Client) GetDeadline(ctx context.Context, dst []byte, url string, deadline time.Time, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\treturn client.GetURLDeadline(ctx, dst, url, deadline, c, requestOptions...)\n}\n\n// Post sends POST request to the given url with the given POST arguments.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// Empty POST body is sent if postArgs is nil.\nfunc (c *Client) Post(ctx context.Context, dst []byte, url string, postArgs *protocol.Args, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\treturn client.PostURL(ctx, dst, url, postArgs, c, requestOptions...)\n}\n\n// DoTimeout performs the given request and waits for response during\n// the given timeout duration.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// errTimeout is returned if the response wasn't returned during\n// the given timeout.\n//\n// If MaxConnsPerHost is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\n//\n// Warning: DoTimeout does not terminate the request itself. The request will\n// continue in the background and the response will be discarded.\n// If requests take too long and the connection pool gets filled up please\n// try using a customized Client instance with a ReadTimeout config or set the request level read timeout like:\n// `req.SetOptions(config.WithReadTimeout(1 * time.Second))`\nfunc (c *Client) DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration) error {\n\treturn client.DoTimeout(ctx, req, resp, timeout, c)\n}\n\n// DoDeadline performs the given request and waits for response until\n// the given deadline.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// errTimeout is returned if the response wasn't returned until\n// the given deadline.\n//\n// If MaxConnsPerHost is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *Client) DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time) error {\n\treturn client.DoDeadline(ctx, req, resp, deadline, c)\n}\n\n// DoRedirects performs the given http request and fills the given http response,\n// following up to maxRedirectsCount redirects. When the redirect count exceeds\n// maxRedirectsCount, ErrTooManyRedirects is returned.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// Response is ignored if resp is nil.\n//\n// If MaxConnsPerHost is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *Client) DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, maxRedirectsCount int) error {\n\t_, _, err := client.DoRequestFollowRedirects(ctx, req, resp, req.URI().String(), maxRedirectsCount, c)\n\treturn err\n}\n\n// Do performs the given http request and fills the given http response.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// Response is ignored if resp is nil.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// If MaxConnsPerHost is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *Client) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error {\n\tif c.mws == nil {\n\t\treturn c.do(ctx, req, resp)\n\t}\n\tif c.lastMiddleware != nil {\n\t\treturn c.mws(c.lastMiddleware(c.do))(ctx, req, resp)\n\t}\n\treturn c.mws(c.do)(ctx, req, resp)\n}\n\nfunc (c *Client) do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error {\n\tif !c.options.KeepAlive {\n\t\treq.Header.SetConnectionClose(true)\n\t}\n\turi := req.URI()\n\tif uri == nil {\n\t\treturn errorInvalidURI\n\t}\n\n\tvar proxyURI *protocol.URI\n\tvar err error\n\n\tif c.Proxy != nil {\n\t\tproxyURI, err = c.Proxy(req)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"proxy error=%w\", err)\n\t\t}\n\t}\n\n\tisTLS := false\n\tscheme := uri.Scheme()\n\tif bytes.Equal(scheme, bytestr.StrHTTPS) {\n\t\tisTLS = true\n\t} else if !bytes.Equal(scheme, bytestr.StrHTTP) && !bytes.Equal(scheme, bytestr.StrSD) {\n\t\treturn fmt.Errorf(\"unsupported protocol %q. http and https are supported\", scheme)\n\t}\n\thost := uri.Host()\n\tstartCleaner := false\n\n\tc.mLock.Lock()\n\n\tm := c.m\n\tif isTLS {\n\t\tm = c.ms\n\t}\n\n\th := string(host)\n\thc := m[h]\n\tif hc == nil {\n\t\tif c.clientFactory == nil {\n\t\t\t// load http1 client by default\n\t\t\tc.clientFactory = factory.NewClientFactory(newHttp1OptionFromClient(c))\n\t\t}\n\t\thc, _ = c.clientFactory.NewHostClient()\n\t\thc.SetDynamicConfig(&client.DynamicConfig{\n\t\t\tAddr:     utils.AddMissingPort(h, isTLS),\n\t\t\tProxyURI: proxyURI,\n\t\t\tIsTLS:    isTLS,\n\t\t})\n\n\t\t// re-configure hook\n\t\tif c.options.HostClientConfigHook != nil {\n\t\t\terr = c.options.HostClientConfigHook(hc)\n\t\t\tif err != nil {\n\t\t\t\tc.mLock.Unlock()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tm[h] = hc\n\t\tif len(m) == 1 {\n\t\t\tstartCleaner = true\n\t\t}\n\t}\n\n\tc.mLock.Unlock()\n\n\tif startCleaner {\n\t\tgo c.cleaner(isTLS)\n\t}\n\n\treturn hc.Do(ctx, req, resp)\n}\n\n// CloseIdleConnections closes any connections which were previously\n// connected from previous requests but are now sitting idle in a\n// \"keep-alive\" state. It does not interrupt any connections currently\n// in use.\nfunc (c *Client) CloseIdleConnections() {\n\tc.mLock.Lock()\n\tfor _, v := range c.m {\n\t\tv.CloseIdleConnections()\n\t}\n\tfor _, v := range c.ms {\n\t\tv.CloseIdleConnections()\n\t}\n\tc.mLock.Unlock()\n}\n\nfunc (c *Client) cleaner(isTLS bool) {\n\tfor {\n\t\ttime.Sleep(10 * time.Second)\n\t\tif c.cleanHostClients(isTLS) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (c *Client) cleanHostClients(isTLS bool) bool {\n\tc.mLock.Lock()\n\tdefer c.mLock.Unlock()\n\tm := c.m\n\tif isTLS {\n\t\tm = c.ms\n\t}\n\tfor k, v := range m {\n\t\tif v.ShouldRemove() {\n\t\t\tdelete(m, k)\n\t\t\tif f, ok := v.(io.Closer); ok {\n\t\t\t\terr := f.Close()\n\t\t\t\tif err != nil {\n\t\t\t\t\thlog.Warnf(\"clean hostclient error, addr: %s, err: %s\", k, err.Error())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn len(m) == 0\n}\n\nfunc (c *Client) SetClientFactory(cf suite.ClientFactory) {\n\tc.clientFactory = cf\n}\n\n// GetDialerName returns the name of the dialer\nfunc (c *Client) GetDialerName() (dName string, err error) {\n\tdefer func() {\n\t\terr := recover()\n\t\tif err != nil {\n\t\t\tdName = \"unknown\"\n\t\t}\n\t}()\n\n\topt := c.GetOptions()\n\tif opt == nil || opt.Dialer == nil {\n\t\treturn \"\", fmt.Errorf(\"abnormal process: there is no client options or dialer\")\n\t}\n\n\tdName = reflect.TypeOf(opt.Dialer).String()\n\tdSlice := strings.Split(dName, \".\")\n\tdName = dSlice[0]\n\tif dName[0] == '*' {\n\t\tdName = dName[1:]\n\t}\n\n\treturn\n}\n\n// NewClient return a client with options\nfunc NewClient(opts ...config.ClientOption) (*Client, error) {\n\topt := config.NewClientOptions(opts)\n\tif opt.Dialer == nil {\n\t\topt.Dialer = dialer.DefaultDialer()\n\t}\n\tc := &Client{\n\t\toptions: opt,\n\t\tm:       make(map[string]client.HostClient),\n\t\tms:      make(map[string]client.HostClient),\n\t}\n\n\treturn c, nil\n}\n\nfunc (c *Client) Use(mws ...Middleware) {\n\t// Put the original middlewares to the first\n\tmiddlewares := make([]Middleware, 0, 1+len(mws))\n\tif c.mws != nil {\n\t\tmiddlewares = append(middlewares, c.mws)\n\t}\n\tmiddlewares = append(middlewares, mws...)\n\tc.mws = chain(middlewares...)\n}\n\n// UseAsLast is used to add middleware to the end of the middleware chain.\n//\n// Will return an error if last middleware has been set before, to ensure all middleware has the change to work,\n// Please use `TakeOutLastMiddleware` to take out the already set middleware.\n// Chain the middleware after or before is both Okay - but remember to put it back.\nfunc (c *Client) UseAsLast(mw Middleware) error {\n\tif c.lastMiddleware != nil {\n\t\treturn errorLastMiddlewareExist\n\t}\n\tc.lastMiddleware = mw\n\treturn nil\n}\n\n// TakeOutLastMiddleware will return the set middleware and remove it from client.\n//\n// Remember to set it back after chain it with other middleware.\nfunc (c *Client) TakeOutLastMiddleware() Middleware {\n\tlast := c.lastMiddleware\n\tc.lastMiddleware = nil\n\treturn last\n}\n\nfunc newHttp1OptionFromClient(c *Client) *http1.ClientOptions {\n\treturn &http1.ClientOptions{\n\t\tName:                          c.options.Name,\n\t\tNoDefaultUserAgentHeader:      c.options.NoDefaultUserAgentHeader,\n\t\tDialer:                        c.options.Dialer,\n\t\tDialTimeout:                   c.options.DialTimeout,\n\t\tDialDualStack:                 c.options.DialDualStack,\n\t\tTLSConfig:                     c.options.TLSConfig,\n\t\tMaxConns:                      c.options.MaxConnsPerHost,\n\t\tMaxConnDuration:               c.options.MaxConnDuration,\n\t\tMaxIdleConnDuration:           c.options.MaxIdleConnDuration,\n\t\tReadTimeout:                   c.options.ReadTimeout,\n\t\tWriteTimeout:                  c.options.WriteTimeout,\n\t\tMaxResponseBodySize:           c.options.MaxResponseBodySize,\n\t\tDisableHeaderNamesNormalizing: c.options.DisableHeaderNamesNormalizing,\n\t\tDisablePathNormalizing:        c.options.DisablePathNormalizing,\n\t\tMaxConnWaitTimeout:            c.options.MaxConnWaitTimeout,\n\t\tResponseBodyStream:            c.options.ResponseBodyStream,\n\t\tRetryConfig:                   c.options.RetryConfig,\n\t\tRetryIfFunc:                   c.RetryIfFunc,\n\t\tStateObserve:                  c.options.HostClientStateObserve,\n\t\tObservationInterval:           c.options.ObservationInterval,\n\t}\n}\n"
  },
  {
    "path": "pkg/app/client/client_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage client\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/app/client/retry\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/dialer\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/client\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/req\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n\t\"github.com/cloudwego/hertz/pkg/route\"\n)\n\nvar errTooManyRedirects = errors.New(\"too many redirects detected when doing the request\")\n\nfunc assertNil(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc waitEngineRunning(e *route.Engine) {\n\ttestutils.WaitEngineRunning(e)\n}\n\nfunc newTestOptions(t *testing.T) (*config.Options, net.Listener) {\n\tln := testutils.NewTestListener(t)\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\topt.Addr = ln.Addr().String()\n\topt.Network = \"tcp\"\n\treturn opt, ln\n}\n\nfunc fullURL(ln net.Listener, p string) string {\n\treturn \"http://\" + path.Join(ln.Addr().String(), p)\n}\n\nfunc TestCloseIdleConnections(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))\n\n\tif _, _, err := c.Get(context.Background(), nil, \"http://google.com\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconnsLen := func() int {\n\t\tc.mLock.Lock()\n\t\tdefer c.mLock.Unlock()\n\n\t\tif _, ok := c.m[\"google.com\"]; !ok {\n\t\t\treturn 0\n\t\t}\n\t\treturn c.m[\"google.com\"].ConnectionCount()\n\t}\n\n\tif conns := connsLen(); conns > 1 {\n\t\tt.Errorf(\"expected 1 conns got %d\", conns)\n\t}\n\n\tc.CloseIdleConnections()\n\n\tif conns := connsLen(); conns > 0 {\n\t\tt.Errorf(\"expected 0 conns got %d\", conns)\n\t}\n\n\tc.cleanHostClients(false)\n\n\tfunc() {\n\t\tc.mLock.Lock()\n\t\tdefer c.mLock.Unlock()\n\t\tif len(c.m) != 0 {\n\t\t\tt.Errorf(\"expected 0 conns got %d\", len(c.m))\n\t\t}\n\t}()\n}\n\nfunc TestCloseIdleTLSConnections(t *testing.T) {\n\thttpsServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\tw.Write([]byte(\"https response\"))\n\t}))\n\tdefer httpsServer.Close()\n\n\tc, _ := NewClient(\n\t\tWithTLSConfig(httpsServer.Client().Transport.(*http.Transport).TLSClientConfig),\n\t\tWithDialTimeout(1*time.Second),\n\t)\n\n\thttpsReq, httpsResp := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(httpsReq)\n\t\tprotocol.ReleaseResponse(httpsResp)\n\t}()\n\thttpsReq.SetRequestURI(httpsServer.URL)\n\tif err := c.Do(context.Background(), httpsReq, httpsResp); err != nil {\n\t\tt.Fatalf(\"HTTPS request failed: %v\", err)\n\t}\n\n\tc.CloseIdleConnections()\n\n\tc.mLock.Lock()\n\tvar totalConns int\n\tfor _, hc := range c.ms {\n\t\ttotalConns += hc.ConnectionCount()\n\t}\n\tc.mLock.Unlock()\n\n\tif totalConns > 0 {\n\t\tt.Errorf(\"expected 0 HTTPS idle connections after close, got %d\", totalConns)\n\t}\n\n\tc.cleanHostClients(true)\n\n\tc.mLock.Lock()\n\tdefer c.mLock.Unlock()\n\tif len(c.ms) != 0 {\n\t\tt.Errorf(\"expected 0 HTTPS host clients, got %d\", len(c.ms))\n\t}\n}\n\nfunc TestClientInvalidURI(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\trequests := int64(0)\n\tengine := route.NewEngine(opt)\n\tengine.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tatomic.AddInt64(&requests, 1)\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))\n\treq, res := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(res)\n\t}()\n\treq.Header.SetMethod(consts.MethodGet)\n\treq.SetRequestURI(\"http://example.com\\r\\n\\r\\nGET /\\r\\n\\r\\n\")\n\terr := c.Do(context.Background(), req, res)\n\tif err == nil {\n\t\tt.Fatal(\"expected error (missing required Host header in request)\")\n\t}\n\tif n := atomic.LoadInt64(&requests); n != 0 {\n\t\tt.Fatalf(\"0 requests expected, got %d\", n)\n\t}\n}\n\nfunc TestClientGetWithBody(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\tengine.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tbody := ctx.Request.Body()\n\t\tctx.Write(body) //nolint:errcheck\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))\n\treq, res := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(res)\n\t}()\n\treq.Header.SetMethod(consts.MethodGet)\n\treq.SetRequestURI(\"http://example.com\")\n\treq.SetBodyString(\"test\")\n\terr := c.Do(context.Background(), req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(res.Body()) == 0 {\n\t\tt.Fatal(\"missing request body\")\n\t}\n}\n\nfunc TestClientPostBodyStream(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\tengine.POST(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tbody := ctx.Request.Body()\n\t\tctx.Write(body) //nolint:errcheck\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tcStream, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)), WithResponseBodyStream(true))\n\targs := &protocol.Args{}\n\t// There is some data in databuf and others is in bodystream, so we need\n\t// to let the data exceed the max bodysize of bodystream\n\tv := \"\"\n\tfor i := 0; i < 10240; i++ {\n\t\tv += \"b\"\n\t}\n\targs.Add(\"a\", v)\n\t_, body, err := cStream.Post(context.Background(), nil, \"http://example.com\", args)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, \"a=\"+v, string(body))\n}\n\nfunc TestClientURLAuth(t *testing.T) {\n\tcases := map[string]string{\n\t\t\"foo:bar@\": \"Basic Zm9vOmJhcg==\",\n\t\t\"foo:@\":    \"Basic Zm9vOg==\",\n\t\t\":@\":       \"\",\n\t\t\"@\":        \"\",\n\t\t\"\":         \"\",\n\t}\n\tch := make(chan string, 1)\n\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\tengine.GET(\"/foo/bar\", func(c context.Context, ctx *app.RequestContext) {\n\t\tch <- string(ctx.Request.Header.Peek(consts.HeaderAuthorization))\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))\n\tfor up, expected := range cases {\n\t\treq := protocol.AcquireRequest()\n\t\treq.Header.SetMethod(consts.MethodGet)\n\t\treq.SetRequestURI(\"http://\" + up + \"example.com/foo/bar\")\n\n\t\tif err := c.Do(context.Background(), req, nil); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tval := <-ch\n\n\t\tif val != expected {\n\t\t\tt.Fatalf(\"wrong %s header: %s expected %s\", consts.HeaderAuthorization, val, expected)\n\t\t}\n\t}\n}\n\nfunc TestClientNilResp(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))\n\n\treq := protocol.AcquireRequest()\n\treq.Header.SetMethod(consts.MethodGet)\n\treq.SetRequestURI(\"http://example.com\")\n\tif err := c.Do(context.Background(), req, nil); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := c.DoTimeout(context.Background(), req, nil, time.Second); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClientParseConn(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\topt.Addr = ln.Addr().String()\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))\n\treq, res := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(res)\n\t}()\n\treq.SetRequestURI(\"http://\" + opt.Addr + \"\")\n\tif err := c.Do(context.Background(), req, res); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif res.RemoteAddr().Network() != opt.Network {\n\t\tt.Fatalf(\"req RemoteAddr parse network fail: %s, hope: %s\", res.RemoteAddr().Network(), opt.Network)\n\t}\n\tif opt.Addr != res.RemoteAddr().String() {\n\t\tt.Fatalf(\"req RemoteAddr parse addr fail: %s, hope: %s\", res.RemoteAddr().String(), opt.Addr)\n\t}\n\n\tif !regexp.MustCompile(`^127\\.0\\.0\\.1:[0-9]{4,5}$`).MatchString(res.LocalAddr().String()) {\n\t\tt.Fatalf(\"res LocalAddr addr match fail: %s, hope match: %s\", res.LocalAddr().String(), \"^127.0.0.1:[0-9]{4,5}$\")\n\t}\n}\n\nfunc TestClientPostArgs(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\tengine.POST(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tbody := ctx.Request.Body()\n\t\tif len(body) == 0 {\n\t\t\treturn\n\t\t}\n\t\tctx.Write(body) //nolint:errcheck\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))\n\treq, res := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(res)\n\t}()\n\targs := req.PostArgs()\n\targs.Add(\"addhttp2\", \"support\")\n\targs.Add(\"fast\", \"http\")\n\treq.Header.SetMethod(consts.MethodPost)\n\treq.SetRequestURI(\"http://make.hertz.great?again\")\n\terr := c.Do(context.Background(), req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(res.Body()) == 0 {\n\t\tt.Fatal(\"cannot set args as body\")\n\t}\n}\n\nfunc TestClientHeaderCase(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\tengine.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tzw := ctx.GetWriter()\n\t\tzw.WriteBinary([]byte(\"HTTP/1.1 200 OK\\r\\n\" + //nolint:errcheck\n\t\t\t\"content-type: text/plain\\r\\n\" +\n\t\t\t\"transfer-encoding: chunked\\r\\n\\r\\n\" +\n\t\t\t\"24\\r\\nThis is the data in the first chunk \\r\\n\" +\n\t\t\t\"1B\\r\\nand this is the second one \\r\\n\" +\n\t\t\t\"0\\r\\n\\r\\n\",\n\t\t))\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithDisableHeaderNamesNormalizing(true))\n\tcode, body, err := c.Get(context.Background(), nil, \"http://example.com\")\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if code != 200 {\n\t\tt.Errorf(\"expected status code 200 got %d\", code)\n\t} else if string(body) != \"This is the data in the first chunk and this is the second one \" {\n\t\tt.Errorf(\"wrong body: %q\", body)\n\t}\n}\n\nfunc TestClientReadTimeout(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\treadtimeout := 50 * time.Millisecond\n\tif runtime.GOOS == \"windows\" {\n\t\t// XXX: The windows CI instance powered by Github is quite unstable.\n\t\t// Increase readtimeout here for better testing stability.\n\t\treadtimeout = 2 * readtimeout\n\t}\n\tsleeptime := readtimeout + readtimeout/2 // must > readtimeout\n\n\tengine.GET(\"/normal\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(201, \"ok\")\n\t})\n\tengine.GET(\"/timeout\", func(c context.Context, ctx *app.RequestContext) {\n\t\ttime.Sleep(sleeptime)\n\t\tctx.String(202, \"timeout ok\")\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc := &http1.HostClient{\n\t\tClientOptions: &http1.ClientOptions{\n\t\t\tReadTimeout: readtimeout,\n\t\t\tRetryConfig: &retry.Config{MaxAttemptTimes: 1},\n\t\t\tDialer:      newMockDialerWithCustomFunc(opt.Network, opt.Addr, readtimeout, nil),\n\t\t},\n\t\tAddr: opt.Addr,\n\t}\n\n\treq := protocol.AcquireRequest()\n\tres := protocol.AcquireResponse()\n\n\treq.SetRequestURI(\"http://example.com/normal\")\n\treq.Header.SetMethod(consts.MethodGet)\n\n\t// Setting Connection: Close will make the connection be returned to the pool.\n\treq.SetConnectionClose()\n\n\tif err := c.Do(context.Background(), req, res); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq.Reset()\n\treq.SetRequestURI(\"http://example.com/timeout\")\n\treq.Header.SetMethod(consts.MethodGet)\n\treq.SetConnectionClose()\n\tres.Reset()\n\n\tt0 := time.Now()\n\terr := c.Do(context.Background(), req, res)\n\tt1 := time.Now()\n\tif !errors.Is(err, errs.ErrTimeout) {\n\t\tif err == nil {\n\t\t\tt.Errorf(\"expected ErrTimeout got nil, req url: %s, read resp body: %s, status: %d\", string(req.URI().FullURI()), string(res.Body()), res.StatusCode())\n\t\t} else {\n\t\t\tif !strings.Contains(err.Error(), \"timeout\") {\n\t\t\t\tt.Errorf(\"expected ErrTimeout got %#v\", err)\n\t\t\t}\n\t\t}\n\t}\n\tprotocol.ReleaseRequest(req)\n\tprotocol.ReleaseResponse(res)\n\tif d := t1.Sub(t0) - readtimeout; d > readtimeout/2 {\n\t\tt.Errorf(\"timeout more than expected: %v\", d)\n\t} else {\n\t\tt.Log(\"latency\", d)\n\t}\n}\n\nfunc TestClientDefaultUserAgent(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Data(consts.StatusOK, \"text/plain; charset=utf-8\", ctx.UserAgent())\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))\n\treq := protocol.AcquireRequest()\n\tres := protocol.AcquireResponse()\n\n\treq.SetRequestURI(\"http://example.com\")\n\treq.Header.SetMethod(consts.MethodGet)\n\n\terr := c.Do(context.Background(), req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif string(res.Body()) != string(bytestr.DefaultUserAgent) {\n\t\tt.Fatalf(\"User-Agent defers %q != %q\", string(res.Body()), bytestr.DefaultUserAgent)\n\t}\n}\n\nfunc TestClientSetUserAgent(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Data(consts.StatusOK, \"text/plain; charset=utf-8\", ctx.UserAgent())\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tuserAgent := \"I'm not hertz\"\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithName(userAgent))\n\treq := protocol.AcquireRequest()\n\tres := protocol.AcquireResponse()\n\n\treq.SetRequestURI(\"http://example.com\")\n\n\terr := c.Do(context.Background(), req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif string(res.Body()) != userAgent {\n\t\tt.Fatalf(\"User-Agent defers %q != %q\", string(res.Body()), userAgent)\n\t}\n}\n\nfunc TestClientNoUserAgent(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Data(consts.StatusOK, \"text/plain; charset=utf-8\", ctx.UserAgent())\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithDialTimeout(1*time.Second), WithNoDefaultUserAgentHeader(true))\n\n\treq := protocol.AcquireRequest()\n\tres := protocol.AcquireResponse()\n\n\treq.SetRequestURI(\"http://example.com\")\n\n\terr := c.Do(context.Background(), req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif string(res.Body()) != \"\" {\n\t\tt.Fatalf(\"User-Agent wrong %q != %q\", string(res.Body()), \"\")\n\t}\n}\n\nfunc TestClientDoWithCustomHeaders(t *testing.T) {\n\tch := make(chan error)\n\turi := \"/foo/bar/baz?a=b&cd=12\"\n\theaders := map[string]string{\n\t\t\"Foo\":          \"bar\",\n\t\t\"Host\":         \"xxx.com\",\n\t\t\"Content-Type\": \"asdfsdf\",\n\t\t\"a-b-c-d-f\":    \"\",\n\t}\n\tbody := \"request body\"\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.POST(\"/foo/bar/baz\", func(c context.Context, ctx *app.RequestContext) {\n\t\tzw := ctx.GetWriter()\n\n\t\tif string(ctx.Request.Header.Method()) != consts.MethodPost {\n\t\t\tch <- fmt.Errorf(\"unexpected request method: %q. Expecting %q\", ctx.Request.Header.Method(), consts.MethodPost)\n\t\t\treturn\n\t\t}\n\t\treqURI := ctx.Request.RequestURI()\n\t\tif string(reqURI) != uri {\n\t\t\tch <- fmt.Errorf(\"unexpected request uri: %q. Expecting %q\", reqURI, uri)\n\t\t\treturn\n\t\t}\n\t\tfor k, v := range headers {\n\t\t\thv := ctx.Request.Header.Peek(k)\n\t\t\tif string(hv) != v {\n\t\t\t\tch <- fmt.Errorf(\"unexpected value for header %q: %q. Expecting %q\", k, hv, v)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tcl := ctx.Request.Header.ContentLength()\n\t\tif cl != len(body) {\n\t\t\tch <- fmt.Errorf(\"unexpected content-length %d. Expecting %d\", cl, len(body))\n\t\t\treturn\n\t\t}\n\t\treqBody := ctx.Request.Body()\n\t\tif string(reqBody) != body {\n\t\t\tch <- fmt.Errorf(\"unexpected request body: %q. Expecting %q\", reqBody, body)\n\t\t\treturn\n\t\t}\n\n\t\tvar r protocol.Response\n\t\tif err := resp.Write(&r, zw); err != nil {\n\t\t\tch <- fmt.Errorf(\"cannot send response: %s\", err)\n\t\t\treturn\n\t\t}\n\t\tif err := zw.Flush(); err != nil {\n\t\t\tch <- fmt.Errorf(\"cannot flush response: %s\", err)\n\t\t\treturn\n\t\t}\n\n\t\tch <- nil\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\t// make sure that the client sends all the request headers and body.\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))\n\n\tvar req protocol.Request\n\treq.Header.SetMethod(consts.MethodPost)\n\treq.SetRequestURI(uri)\n\tfor k, v := range headers {\n\t\treq.Header.Set(k, v)\n\t}\n\treq.SetBodyString(body)\n\n\tvar resp protocol.Response\n\n\terr := c.DoTimeout(context.Background(), &req, &resp, time.Second)\n\tif err != nil {\n\t\tt.Fatalf(\"error when doing request: %s\", err)\n\t}\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\nfunc TestClientDoTimeoutDisablePathNormalizing(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.Use(func(c context.Context, ctx *app.RequestContext) {\n\t\turi := ctx.URI()\n\t\turi.DisablePathNormalizing = true\n\t\tctx.Response.Header.Set(\"received-uri\", string(uri.FullURI()))\n\t})\n\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithDisablePathNormalizing(true))\n\n\turlWithEncodedPath := \"http://example.com/encoded/Y%2BY%2FY%3D/stuff\"\n\n\tvar req protocol.Request\n\treq.SetRequestURI(urlWithEncodedPath)\n\tvar resp protocol.Response\n\tfor i := 0; i < 5; i++ {\n\t\tif err := c.DoTimeout(context.Background(), &req, &resp, time.Second); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\thv := resp.Header.Peek(\"received-uri\")\n\t\tif string(hv) != urlWithEncodedPath {\n\t\t\tt.Fatalf(\"request uri was normalized: %q. Expecting %q\", hv, urlWithEncodedPath)\n\t\t}\n\t}\n}\n\nfunc TestHostClientPendingRequests(t *testing.T) {\n\tconst concurrency = 10\n\tdoneCh := make(chan struct{})\n\treadyCh := make(chan struct{}, concurrency)\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.GET(\"/baz\", func(c context.Context, ctx *app.RequestContext) {\n\t\treadyCh <- struct{}{}\n\t\t<-doneCh\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc := &http1.HostClient{\n\t\tClientOptions: &http1.ClientOptions{\n\t\t\tDialer: newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil),\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\tpendingRequests := c.PendingRequests()\n\tif pendingRequests != 0 {\n\t\tt.Fatalf(\"non-zero pendingRequests: %d\", pendingRequests)\n\t}\n\n\tresultCh := make(chan error, concurrency)\n\tfor i := 0; i < concurrency; i++ {\n\t\tgo func() {\n\t\t\treq := protocol.AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\treq.Header.SetMethod(consts.MethodGet)\n\t\t\tresp := protocol.AcquireResponse()\n\n\t\t\tif err := c.DoTimeout(context.Background(), req, resp, 10*time.Second); err != nil {\n\t\t\t\tresultCh <- fmt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif resp.StatusCode() != consts.StatusOK {\n\t\t\t\tresultCh <- fmt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), consts.StatusOK)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresultCh <- nil\n\t\t}()\n\t}\n\n\t// wait until all the requests reach server\n\tfor i := 0; i < concurrency; i++ {\n\t\tselect {\n\t\tcase <-readyCh:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n\n\tpendingRequests = c.PendingRequests()\n\tif pendingRequests != concurrency {\n\t\tt.Fatalf(\"unexpected pendingRequests: %d. Expecting %d\", pendingRequests, concurrency)\n\t}\n\n\t// unblock request handlers on the server and wait until all the requests are finished.\n\tclose(doneCh)\n\tfor i := 0; i < concurrency; i++ {\n\t\tselect {\n\t\tcase err := <-resultCh:\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n\n\tpendingRequests = c.PendingRequests()\n\tif pendingRequests != 0 {\n\t\tt.Fatalf(\"non-zero pendingRequests: %d\", pendingRequests)\n\t}\n}\n\nfunc TestHostClientMaxConnsWithDeadline(t *testing.T) {\n\tvar (\n\t\temptyBodyCount uint8\n\t\ttimeout        = 50 * time.Millisecond\n\t\twg             sync.WaitGroup\n\t)\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.POST(\"/baz\", func(c context.Context, ctx *app.RequestContext) {\n\t\tif len(ctx.Request.Body()) == 0 {\n\t\t\temptyBodyCount++\n\t\t}\n\n\t\tctx.WriteString(\"foo\") //nolint:errcheck\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc := &http1.HostClient{\n\t\tClientOptions: &http1.ClientOptions{\n\t\t\tDialer:   newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil),\n\t\t\tMaxConns: 1,\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\tfor i := 0; i < 5; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\treq := protocol.AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\treq.Header.SetMethod(consts.MethodPost)\n\t\t\treq.SetBodyString(\"bar\")\n\t\t\tresp := protocol.AcquireResponse()\n\n\t\t\tfor {\n\t\t\t\tif err := c.DoDeadline(context.Background(), req, resp, time.Now().Add(timeout)); err != nil {\n\t\t\t\t\tif err.Error() == errs.ErrNoFreeConns.Error() {\n\t\t\t\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif resp.StatusCode() != consts.StatusOK {\n\t\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), consts.StatusOK)\n\t\t\t}\n\n\t\t\tbody := resp.Body()\n\t\t\tif string(body) != \"foo\" {\n\t\t\t\tt.Errorf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tif emptyBodyCount > 0 {\n\t\tt.Fatalf(\"at least one request body was empty\")\n\t}\n}\n\nfunc TestHostClientMaxConnDuration(t *testing.T) {\n\tconnectionCloseCount := uint32(0)\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.GET(\"/bbb/cc\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.WriteString(\"abcd\") //nolint:errcheck\n\t\tif ctx.Request.ConnectionClose() {\n\t\t\tatomic.AddUint32(&connectionCloseCount, 1)\n\t\t}\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc := &http1.HostClient{\n\t\tClientOptions: &http1.ClientOptions{\n\t\t\tDialer:          newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil),\n\t\t\tMaxConnDuration: 10 * time.Millisecond,\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\tfor i := 0; i < 5; i++ {\n\t\tstatusCode, body, err := c.Get(context.Background(), nil, \"http://aaaa.com/bbb/cc\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif statusCode != consts.StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code %d. Expecting %d\", statusCode, consts.StatusOK)\n\t\t}\n\t\tif string(body) != \"abcd\" {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t}\n\t\ttime.Sleep(c.MaxConnDuration)\n\t}\n\n\tif atomic.LoadUint32(&connectionCloseCount) == 0 {\n\t\tt.Fatalf(\"expecting at least one 'Connection: close' request header\")\n\t}\n}\n\nfunc TestHostClientMultipleAddrs(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.GET(\"/baz/aaa\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Write(ctx.Host()) //nolint:errcheck\n\t\tctx.SetConnectionClose()\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tdialsCount := make(map[string]int)\n\tc := &http1.HostClient{\n\t\tClientOptions: &http1.ClientOptions{\n\t\t\tDialer: newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, func(network, addr string, timeout time.Duration, tlsConfig *tls.Config) {\n\t\t\t\tdialsCount[addr]++\n\t\t\t}),\n\t\t},\n\t\tAddr: \"foo,bar,baz\",\n\t}\n\n\tfor i := 0; i < 9; i++ {\n\t\tstatusCode, body, err := c.Get(context.Background(), nil, \"http://foobar/baz/aaa?bbb=ddd\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif statusCode != consts.StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code %d. Expecting %d\", statusCode, consts.StatusOK)\n\t\t}\n\t\tif string(body) != \"foobar\" {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, \"foobar\")\n\t\t}\n\t}\n\n\tif len(dialsCount) != 3 {\n\t\tt.Fatalf(\"unexpected dialsCount size %d. Expecting 3\", len(dialsCount))\n\t}\n\tfor _, k := range []string{\"foo\", \"bar\", \"baz\"} {\n\t\tif dialsCount[k] != 3 {\n\t\t\tt.Fatalf(\"unexpected dialsCount for %q. Expecting 3\", k)\n\t\t}\n\t}\n}\n\nfunc TestClientFollowRedirects(t *testing.T) {\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\thandler := func(c context.Context, ctx *app.RequestContext) {\n\t\tswitch string(ctx.Path()) {\n\t\tcase \"/foo\":\n\t\t\tu := ctx.URI()\n\t\t\tu.Update(\"/xy?z=wer\")\n\t\t\tctx.Redirect(consts.StatusFound, u.FullURI())\n\t\tcase \"/xy\":\n\t\t\tu := ctx.URI()\n\t\t\tu.Update(\"/bar\")\n\t\t\tctx.Redirect(consts.StatusFound, u.FullURI())\n\t\tdefault:\n\t\t\tctx.SetContentType(consts.MIMETextPlain)\n\t\t\tctx.Response.SetBody(ctx.Path())\n\t\t}\n\t}\n\tengine.GET(\"/foo\", handler)\n\tengine.GET(\"/xy\", handler)\n\tengine.GET(\"/bar\", handler)\n\tengine.GET(\"/aaab/sss\", handler)\n\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc := &http1.HostClient{\n\t\tClientOptions: &http1.ClientOptions{\n\t\t\tDialer: newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil),\n\t\t},\n\t\tAddr: \"xxx\",\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tstatusCode, body, err := c.GetTimeout(context.Background(), nil, \"http://xxx/foo\", time.Second)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif statusCode != consts.StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\t\tif string(body) != \"/bar\" {\n\t\t\tt.Fatalf(\"unexpected response %q. Expecting %q\", body, \"/bar\")\n\t\t}\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tstatusCode, body, err := c.Get(context.Background(), nil, \"http://xxx/aaab/sss\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif statusCode != consts.StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\t\tif string(body) != \"/aaab/sss\" {\n\t\t\tt.Fatalf(\"unexpected response %q. Expecting %q\", body, \"/aaab/sss\")\n\t\t}\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\treq := protocol.AcquireRequest()\n\t\tresp := protocol.AcquireResponse()\n\n\t\treq.SetRequestURI(\"http://xxx/foo\")\n\n\t\terr := c.DoRedirects(context.Background(), req, resp, 16)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\n\t\tif statusCode := resp.StatusCode(); statusCode != consts.StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\n\t\tif body := string(resp.Body()); body != \"/bar\" {\n\t\t\tt.Fatalf(\"unexpected response %q. Expecting %q\", body, \"/bar\")\n\t\t}\n\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(resp)\n\t}\n\n\treq := protocol.AcquireRequest()\n\tresp := protocol.AcquireResponse()\n\n\treq.SetRequestURI(\"http://xxx/foo\")\n\n\terr := c.DoRedirects(context.Background(), req, resp, 0)\n\tif have, want := err, errTooManyRedirects; have.Error() != want.Error() {\n\t\tt.Fatalf(\"want error: %v, have %v\", want, have)\n\t}\n\n\tprotocol.ReleaseRequest(req)\n\tprotocol.ReleaseResponse(resp)\n}\n\nfunc TestHostClientMaxConnWaitTimeoutSuccess(t *testing.T) {\n\tvar (\n\t\temptyBodyCount uint8\n\t\twg             sync.WaitGroup\n\t)\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.POST(\"/baz\", func(c context.Context, ctx *app.RequestContext) {\n\t\tif len(ctx.Request.Body()) == 0 {\n\t\t\temptyBodyCount++\n\t\t}\n\t\ttime.Sleep(5 * time.Millisecond)\n\t\tctx.WriteString(\"foo\") //nolint:errcheck\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc := &http1.HostClient{\n\t\tClientOptions: &http1.ClientOptions{\n\t\t\tDialer:             newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil),\n\t\t\tMaxConns:           1,\n\t\t\tMaxConnWaitTimeout: 200 * time.Millisecond,\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\tfor i := 0; i < 5; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\treq := protocol.AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\treq.Header.SetMethod(consts.MethodPost)\n\t\t\treq.SetBodyString(\"bar\")\n\t\t\tresp := protocol.AcquireResponse()\n\n\t\t\tif err := c.Do(context.Background(), req, resp); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tif resp.StatusCode() != consts.StatusOK {\n\t\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), consts.StatusOK)\n\t\t\t}\n\n\t\t\tbody := resp.Body()\n\t\t\tif string(body) != \"foo\" {\n\t\t\t\tt.Errorf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tif c.WantConnectionCount() > 0 {\n\t\tt.Errorf(\"connsWait has %v items remaining\", c.WantConnectionCount())\n\t}\n\n\tif emptyBodyCount > 0 {\n\t\tt.Fatalf(\"at least one request body was empty\")\n\t}\n}\n\nfunc TestHostClientMaxConnWaitTimeoutError(t *testing.T) {\n\tvar (\n\t\temptyBodyCount uint8\n\t\twg             sync.WaitGroup\n\t)\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.POST(\"/baz\", func(c context.Context, ctx *app.RequestContext) {\n\t\tif len(ctx.Request.Body()) == 0 {\n\t\t\temptyBodyCount++\n\t\t}\n\t\ttime.Sleep(5 * time.Millisecond)\n\t\tctx.WriteString(\"foo\") //nolint:errcheck\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc := &http1.HostClient{\n\t\tClientOptions: &http1.ClientOptions{\n\t\t\tDialer:             newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil),\n\t\t\tMaxConns:           1,\n\t\t\tMaxConnWaitTimeout: 10 * time.Millisecond,\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\tvar errNoFreeConnsCount uint32\n\tfor i := 0; i < 5; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\treq := protocol.AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\treq.Header.SetMethod(consts.MethodPost)\n\t\t\treq.SetBodyString(\"bar\")\n\t\t\tresp := protocol.AcquireResponse()\n\n\t\t\tif err := c.Do(context.Background(), req, resp); err != nil {\n\t\t\t\tif err.Error() != errs.ErrNoFreeConns.Error() {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s. Expecting %s\", err.Error(), errs.ErrNoFreeConns.Error())\n\t\t\t\t}\n\t\t\t\tatomic.AddUint32(&errNoFreeConnsCount, 1)\n\t\t\t} else {\n\t\t\t\tif resp.StatusCode() != consts.StatusOK {\n\t\t\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), consts.StatusOK)\n\t\t\t\t}\n\n\t\t\t\tbody := resp.Body()\n\t\t\t\tif string(body) != \"foo\" {\n\t\t\t\t\tt.Errorf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tif c.WantConnectionCount() > 0 {\n\t\tt.Errorf(\"connsWait has %v items remaining\", c.WantConnectionCount())\n\t}\n\tif errNoFreeConnsCount == 0 {\n\t\tt.Errorf(\"unexpected errorCount: %d. Expecting > 0\", errNoFreeConnsCount)\n\t}\n\n\tif emptyBodyCount > 0 {\n\t\tt.Fatalf(\"at least one request body was empty\")\n\t}\n}\n\nfunc TestNewClient(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.SetBodyString(\"pong\")\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tclient, err := NewClient(WithDialTimeout(2 * time.Second))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tstatus, resp, err := client.Get(context.Background(), nil, fullURL(ln, \"/ping\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tif status != consts.StatusOK {\n\t\tt.Errorf(\"return http status=%v\", status)\n\t}\n\tt.Logf(\"resp=%v\\n\", string(resp))\n}\n\nfunc TestUseShortConnection(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\topt.Addr = ln.Addr().String()\n\n\tc, _ := NewClient(WithKeepAlive(false))\n\tvar wg sync.WaitGroup\n\tfor i := 0; i < 10; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif _, _, err := c.Get(context.Background(), nil, fullURL(ln, \"\")); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\tconnsLen := func() int {\n\t\tc.mLock.Lock()\n\t\tdefer c.mLock.Unlock()\n\n\t\tif _, ok := c.m[opt.Addr]; !ok {\n\t\t\treturn 0\n\t\t}\n\n\t\treturn c.m[opt.Addr].ConnectionCount()\n\t}\n\n\tif conns := connsLen(); conns > 0 {\n\t\tt.Errorf(\"expected 0 conns got %d\", conns)\n\t}\n}\n\nfunc TestPostWithFormData(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.POST(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar ans string\n\t\tctx.PostArgs().VisitAll(func(key, value []byte) {\n\t\t\tans = ans + string(key) + \"=\" + string(value) + \"&\"\n\t\t})\n\t\tans = strings.TrimRight(ans, \"&\")\n\t\tctx.Data(consts.StatusOK, \"text/plain; charset=utf-8\", []byte(ans))\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tclient, _ := NewClient()\n\treq := protocol.AcquireRequest()\n\trsp := protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(rsp)\n\t}()\n\tpostParam := map[string][]string{\n\t\t\"a\": {\"c\", \"d\", \"e\"},\n\t\t\"b\": {\"c\"},\n\t\t\"c\": {\"f\"},\n\t}\n\treq.SetFormData(map[string]string{\n\t\t\"a\": \"c\",\n\t\t\"b\": \"c\",\n\t})\n\treq.SetFormDataFromValues(url.Values{\n\t\t\"a\": []string{\"d\", \"e\"},\n\t\t\"c\": []string{\"f\"},\n\t})\n\treq.SetRequestURI(fullURL(ln, \"\"))\n\treq.SetMethod(consts.MethodPost)\n\terr := client.Do(context.Background(), req, rsp)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tfor k, v := range postParam {\n\t\tfor _, kv := range v {\n\t\t\tif !strings.Contains(string(rsp.Body()), k+\"=\"+kv) {\n\t\t\t\tt.Errorf(\"miss %v=%v\", k, kv)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestPostWithMultipartField(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.POST(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tif string(ctx.FormValue(\"a\")) != \"1\" {\n\t\t\tt.Errorf(\"field a want 1, got %v\", string(ctx.FormValue(\"a\")))\n\t\t}\n\t\tif string(ctx.FormValue(\"b\")) != \"2\" {\n\t\t\tt.Errorf(\"field b want 2, got %v\", string(ctx.FormValue(\"b\")))\n\t\t}\n\t\tt.Log(req.GetHTTP1Request(&ctx.Request).String())\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tclient, _ := NewClient()\n\treq := protocol.AcquireRequest()\n\trsp := protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(rsp)\n\t}()\n\tdata := map[string]string{\n\t\t\"a\": \"1\",\n\t\t\"b\": \"2\",\n\t}\n\treq.SetMethod(consts.MethodPost)\n\treq.SetRequestURI(fullURL(ln, \"\"))\n\treq.SetMultipartFormData(data)\n\treq.SetMultipartFormData(map[string]string{\n\t\t\"c\": \"3\",\n\t})\n\terr := client.DoTimeout(context.Background(), req, rsp, 1*time.Second)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestSetFiles(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.POST(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tform, _ := ctx.MultipartForm()\n\t\tfiles := form.File[\"files\"]\n\t\t// Upload the file to specific dst.\n\t\tfor _, file := range files {\n\t\t\tctx.SaveUploadedFile(file, filepath.Base(file.Filename))\n\t\t}\n\t\tfile1, _ := ctx.FormFile(\"file_1\")\n\t\tctx.SaveUploadedFile(file1, filepath.Base(file1.Filename))\n\t\tfile2, _ := ctx.FormFile(\"file_2\")\n\t\tctx.SaveUploadedFile(file2, filepath.Base(file2.Filename))\n\t\tctx.String(consts.StatusOK, fmt.Sprintf(\"%d files uploaded!\", len(files)+2))\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tclient, _ := NewClient()\n\treq := protocol.AcquireRequest()\n\trsp := protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(rsp)\n\t}()\n\treq.SetMethod(consts.MethodPost)\n\treq.SetRequestURI(fullURL(ln, \"\"))\n\tfiles := []string{\"../../common/testdata/test.txt\", \"../../common/testdata/proto/test.proto\", \"../../common/testdata/test.png\", \"../../common/testdata/proto/test.pb.go\"}\n\tdefer func() {\n\t\tfor _, file := range files {\n\t\t\tos.Remove(filepath.Base(file))\n\t\t}\n\t}()\n\treq.SetFile(\"files\", files[0])\n\treq.SetFile(\"files\", files[1])\n\treq.SetFiles(map[string]string{\n\t\t\"file_1\": files[2],\n\t\t\"file_2\": files[3],\n\t})\n\terr := client.DoTimeout(context.Background(), req, rsp, 1*time.Second)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestSetMultipartFields(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.POST(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tt.Log(req.GetHTTP1Request(&ctx.Request).String())\n\t\tif string(ctx.FormValue(\"a\")) != \"1\" {\n\t\t\tt.Errorf(\"field a want 1, got %v\", string(ctx.FormValue(\"a\")))\n\t\t}\n\t\tif string(ctx.FormValue(\"b\")) != \"2\" {\n\t\t\tt.Errorf(\"field b want 2, got %v\", string(ctx.FormValue(\"b\")))\n\t\t}\n\t\tfile1, _ := ctx.FormFile(\"file_1\")\n\t\tctx.SaveUploadedFile(file1, filepath.Base(file1.Filename))\n\t\tfile2, _ := ctx.FormFile(\"file_2\")\n\t\tctx.SaveUploadedFile(file2, filepath.Base(file2.Filename))\n\t\tctx.String(consts.StatusOK, fmt.Sprintf(\"%d files uploaded!\", 2))\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tclient, _ := NewClient(WithDialTimeout(50 * time.Millisecond))\n\treq := protocol.AcquireRequest()\n\trsp := protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(rsp)\n\t}()\n\tjsonStr1 := `{\"input\": {\"name\": \"Uploaded document 1\", \"_filename\" : [\"file1.txt\"]}}`\n\tjsonStr2 := `{\"input\": {\"name\": \"Uploaded document 2\", \"_filename\" : [\"file2.txt\"]}}`\n\tfiles := []string{\"upload-file-1.json\", \"upload-file-2.json\"}\n\tfields := []*protocol.MultipartField{\n\t\t{\n\t\t\tParam:       \"file_1\",\n\t\t\tFileName:    files[0],\n\t\t\tContentType: consts.MIMEApplicationJSON,\n\t\t\tReader:      strings.NewReader(jsonStr1),\n\t\t},\n\t\t{\n\t\t\tParam:       \"file_2\",\n\t\t\tFileName:    files[1],\n\t\t\tContentType: consts.MIMEApplicationJSON,\n\t\t\tReader:      strings.NewReader(jsonStr2),\n\t\t},\n\t}\n\tdefer func() {\n\t\tfor _, file := range files {\n\t\t\tos.Remove(filepath.Base(file))\n\t\t}\n\t}()\n\treq.SetMultipartFields(fields...)\n\treq.SetMultipartFormData(map[string]string{\"a\": \"1\", \"b\": \"2\"})\n\treq.SetRequestURI(fullURL(ln, \"\"))\n\treq.SetMethod(consts.MethodPost)\n\terr := client.DoTimeout(context.Background(), req, rsp, 1*time.Second)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestClientReadResponseBodyStream(t *testing.T) {\n\tpart1 := \"abcdef\"\n\tpart2 := \"ghij\"\n\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.POST(\"/\", func(ctx context.Context, c *app.RequestContext) {\n\t\tc.String(consts.StatusOK, part1+part2)\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tclient, _ := NewClient(WithResponseBodyStream(true))\n\treq, resp := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(resp)\n\t}()\n\treq.SetRequestURI(fullURL(ln, \"\"))\n\treq.SetMethod(consts.MethodPost)\n\terr := client.Do(context.Background(), req, resp)\n\tif err != nil {\n\t\tt.Errorf(\"client Do error=%v\", err.Error())\n\t}\n\tbodyStream := resp.BodyStream()\n\tif bodyStream == nil {\n\t\tt.Errorf(\"bodystream is nil\")\n\t}\n\t// Read part1 body bytes\n\tp := make([]byte, len(part1))\n\tr, err := bodyStream.Read(p)\n\tif err != nil {\n\t\tt.Errorf(\"read from bodystream error=%v\", err.Error())\n\t}\n\tif string(p) != part1 {\n\t\tt.Errorf(\"read len=%v, read content=%v; want len=%v, want content=%v\", r, string(p), len(part1), part1)\n\t}\n\tleft, _ := ioutil.ReadAll(bodyStream)\n\tif string(left) != part2 {\n\t\tt.Errorf(\"left len=%v, left content=%v; want len=%v, want content=%v\", len(left), string(left), len(part2), part2)\n\t}\n}\n\nfunc TestWithBasicAuth(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tauth := ctx.GetHeader(consts.HeaderAuthorization)\n\t\tif len(auth) < 6 {\n\t\t\tctx.SetStatusCode(consts.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\t\tpassword, err := base64.StdEncoding.DecodeString(string(auth[6:]))\n\t\tif err != nil || string(password) != \"myuser:basicauth\" {\n\t\t\tctx.SetStatusCode(consts.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tclient, _ := NewClient()\n\treq := protocol.AcquireRequest()\n\trsp := protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(rsp)\n\t}()\n\n\t// Success\n\treq.SetBasicAuth(\"myuser\", \"basicauth\")\n\treq.SetRequestURI(fullURL(ln, \"\"))\n\treq.SetMethod(consts.MethodGet)\n\terr := client.Do(context.Background(), req, rsp)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif rsp.StatusCode() == consts.StatusUnauthorized {\n\t\tt.Error(\"unexpected status code=401\")\n\t}\n\n\t// Fail\n\treq.Reset()\n\trsp.Reset()\n\treq.SetRequestURI(fullURL(ln, \"\"))\n\treq.SetMethod(consts.MethodGet)\n\terr = client.Do(context.Background(), req, rsp)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif rsp.StatusCode() != consts.StatusUnauthorized {\n\t\tt.Errorf(\"unexpected status code: %v, expected 401\", rsp.StatusCode())\n\t}\n}\n\nfunc TestClientProxyWithStandardDialer(t *testing.T) {\n\ttestCases := []struct{ httpsSite, httpsProxy bool }{\n\t\t{false, false},\n\t\t{false, true},\n\t\t{true, false},\n\t\t{true, true},\n\t}\n\tfor _, testCase := range testCases {\n\t\thttpsSite := testCase.httpsSite\n\t\thttpsProxy := testCase.httpsProxy\n\t\tt.Run(fmt.Sprintf(\"httpsSite=%v, httpsProxy=%v\", httpsSite, httpsProxy), func(t *testing.T) {\n\t\t\tsiteCh := make(chan *http.Request, 1)\n\t\t\th1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tsiteCh <- r\n\t\t\t})\n\t\t\tproxyCh := make(chan *http.Request, 1)\n\t\t\th2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tproxyCh <- r\n\t\t\t\tif r.Method == \"CONNECT\" {\n\t\t\t\t\thijacker, ok := w.(http.Hijacker)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tt.Errorf(\"hijack not allowed\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tclientConn, _, err := hijacker.Hijack()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"hijacking failed\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tres := &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tProto:      \"HTTP/1.1\",\n\t\t\t\t\t\tProtoMajor: 1,\n\t\t\t\t\t\tProtoMinor: 1,\n\t\t\t\t\t\tHeader:     make(http.Header),\n\t\t\t\t\t}\n\t\t\t\t\ttargetConn, err := net.Dial(\"tcp\", r.URL.Host)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"net.Dial(%q) failed: %v\", r.URL.Host, err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif err := res.Write(clientConn); err != nil {\n\t\t\t\t\t\tt.Errorf(\"Writing 200 OK failed: %v\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tgo io.Copy(targetConn, clientConn)\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tio.Copy(clientConn, targetConn)\n\t\t\t\t\t\ttargetConn.Close()\n\t\t\t\t\t}()\n\t\t\t\t}\n\t\t\t})\n\t\t\tvar ts *httptest.Server\n\t\t\tif httpsSite {\n\t\t\t\tts = httptest.NewTLSServer(h1)\n\t\t\t} else {\n\t\t\t\tts = httptest.NewServer(h1)\n\t\t\t}\n\t\t\tvar proxyServer *httptest.Server\n\t\t\tif httpsProxy {\n\t\t\t\tproxyServer = httptest.NewTLSServer(h2)\n\t\t\t} else {\n\t\t\t\tproxyServer = httptest.NewServer(h2)\n\t\t\t}\n\t\t\tpu := protocol.ParseURI(proxyServer.URL)\n\n\t\t\t// If neither server is HTTPS or both are, then c may be derived from either.\n\t\t\t// If only one server is HTTPS, c must be derived from that server in order\n\t\t\t// to ensure that it is configured to use the fake root CA from testcert.go.\n\t\t\tdialer.SetDialer(standard.NewDialer())\n\t\t\tvar cOpt config.ClientOption\n\t\t\tif httpsProxy {\n\t\t\t\tcOpt = WithTLSConfig(proxyServer.Client().Transport.(*http.Transport).TLSClientConfig)\n\t\t\t} else if httpsSite {\n\t\t\t\tcOpt = WithTLSConfig(ts.Client().Transport.(*http.Transport).TLSClientConfig)\n\t\t\t}\n\t\t\tvar c *Client\n\t\t\tif httpsProxy || httpsSite {\n\t\t\t\tc, _ = NewClient(cOpt)\n\t\t\t} else {\n\t\t\t\tc, _ = NewClient()\n\t\t\t}\n\t\t\tc.SetProxy(protocol.ProxyURI(pu))\n\t\t\treq, rsp := protocol.AcquireRequest(), protocol.AcquireResponse()\n\t\t\tdefer func() {\n\t\t\t\tprotocol.ReleaseRequest(req)\n\t\t\t\tprotocol.ReleaseResponse(rsp)\n\t\t\t}()\n\t\t\treq.SetRequestURI(ts.URL)\n\t\t\treq.SetMethod(consts.MethodHead)\n\t\t\terr := c.Do(context.Background(), req, rsp)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tvar got *http.Request\n\t\t\tselect {\n\t\t\tcase got = <-proxyCh:\n\t\t\tcase <-time.After(5 * time.Second):\n\t\t\t\tt.Fatal(\"timeout connecting to http proxy\")\n\t\t\t}\n\t\t\tts.Close()\n\t\t\tproxyServer.Close()\n\n\t\t\tif httpsSite {\n\t\t\t\t// First message should be a CONNECT to ask for a socket to the real server,\n\t\t\t\tif got.Method != \"CONNECT\" {\n\t\t\t\t\tt.Errorf(\"Wrong method for secure proxying: %q\", got.Method)\n\t\t\t\t}\n\t\t\t\tgotHost := got.URL.Host\n\t\t\t\tpu, err := url.Parse(ts.URL)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(\"Invalid site URL\")\n\t\t\t\t}\n\t\t\t\tif wantHost := pu.Host; gotHost != wantHost {\n\t\t\t\t\tt.Errorf(\"Got CONNECT host %q, want %q\", gotHost, wantHost)\n\t\t\t\t}\n\n\t\t\t\t// The next message on the channel should be from the site's server.\n\t\t\t\tnext := <-siteCh\n\t\t\t\tif next.Method != \"HEAD\" {\n\t\t\t\t\tt.Errorf(\"Wrong method at destination: %s\", next.Method)\n\t\t\t\t}\n\t\t\t\tif nextURL := next.URL.String(); nextURL != \"/\" {\n\t\t\t\t\tt.Errorf(\"Wrong URL at destination: %s\", nextURL)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif got.Method != \"HEAD\" {\n\t\t\t\t\tt.Errorf(\"Wrong method for destination: %q\", got.Method)\n\t\t\t\t}\n\t\t\t\tgotURL := got.URL.String()\n\t\t\t\twantURL := ts.URL + \"/\"\n\t\t\t\tif gotURL != wantURL {\n\t\t\t\t\tt.Errorf(\"Got URL %q, want %q\", gotURL, wantURL)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestClientProxyWithNetpollDialer(t *testing.T) {\n\ttestCases := []struct{ httpsSite, httpsProxy bool }{\n\t\t{false, false},\n\t\t{true, false},\n\t\t{false, true},\n\t\t{true, true},\n\t}\n\tfor _, testCase := range testCases {\n\t\thttpsSite := testCase.httpsSite\n\t\thttpsProxy := testCase.httpsProxy\n\t\tt.Run(fmt.Sprintf(\"httpsSite=%v, httpsProxy=%v\", httpsSite, httpsProxy), func(t *testing.T) {\n\t\t\tsiteCh := make(chan *http.Request, 1)\n\t\t\th1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tsiteCh <- r\n\t\t\t})\n\t\t\tproxyCh := make(chan *http.Request, 1)\n\t\t\th2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tproxyCh <- r\n\t\t\t})\n\t\t\tvar ts *httptest.Server\n\t\t\tif httpsSite {\n\t\t\t\tts = httptest.NewTLSServer(h1)\n\t\t\t} else {\n\t\t\t\tts = httptest.NewServer(h1)\n\t\t\t}\n\t\t\tvar proxyServer *httptest.Server\n\t\t\tif httpsProxy {\n\t\t\t\tproxyServer = httptest.NewTLSServer(h2)\n\t\t\t} else {\n\t\t\t\tproxyServer = httptest.NewServer(h2)\n\t\t\t}\n\t\t\tpu := protocol.ParseURI(proxyServer.URL)\n\t\t\t// If neither server is HTTPS or both are, then c may be derived from either.\n\t\t\t// If only one server is HTTPS, c must be derived from that server in order\n\t\t\t// to ensure that it is configured to use the fake root CA from testcert.go.\n\n\t\t\tc, _ := NewClient()\n\t\t\tc.SetProxy(protocol.ProxyURI(pu))\n\t\t\treq, rsp := protocol.AcquireRequest(), protocol.AcquireResponse()\n\t\t\tdefer func() {\n\t\t\t\tprotocol.ReleaseRequest(req)\n\t\t\t\tprotocol.ReleaseResponse(rsp)\n\t\t\t}()\n\t\t\treq.SetRequestURI(ts.URL)\n\t\t\treq.SetMethod(consts.MethodHead)\n\t\t\terr := c.Do(context.Background(), req, rsp)\n\t\t\tif err != nil {\n\t\t\t\tt.Log(err)\n\t\t\t\tif !httpsSite && !httpsProxy {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvar got *http.Request\n\t\t\tselect {\n\t\t\tcase got = <-proxyCh:\n\t\t\tcase <-time.After(5 * time.Second):\n\t\t\t\tt.Fatal(\"timeout connecting to http proxy\")\n\t\t\t}\n\t\t\tts.Close()\n\t\t\tproxyServer.Close()\n\n\t\t\tif got.Method != \"HEAD\" {\n\t\t\t\tt.Errorf(\"Wrong method for destination: %q\", got.Method)\n\t\t\t}\n\t\t\tgotURL := got.URL.String()\n\t\t\twantURL := ts.URL + \"/\"\n\t\t\tif gotURL != wantURL {\n\t\t\t\tt.Errorf(\"Got URL %q, want %q\", gotURL, wantURL)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestClientMiddleware(t *testing.T) {\n\tclient, _ := NewClient()\n\tmw0 := func(next Endpoint) Endpoint {\n\t\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\t\treq.SetRequestURI(\"middleware0\")\n\t\t\treturn next(ctx, req, resp)\n\t\t}\n\t}\n\tmw1 := func(next Endpoint) Endpoint {\n\t\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\t\tif string(req.RequestURI()) != \"middleware0\" {\n\t\t\t\tt.Errorf(\"Wrong request URI: %s, expected %v\", req.RequestURI(), \"middleware0\")\n\t\t\t}\n\t\t\treq.SetRequestURI(\"middleware1\")\n\t\t\treturn next(ctx, req, resp)\n\t\t}\n\t}\n\tmw2 := func(next Endpoint) Endpoint {\n\t\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\t\tif string(req.RequestURI()) != \"middleware1\" {\n\t\t\t\tt.Errorf(\"Wrong request URI: %s, expected %v\", req.RequestURI(), \"middleware1\")\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\tclient.Use(mw0)\n\tclient.Use(mw1)\n\tclient.Use(mw2)\n\n\trequest, response := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(request)\n\t\tprotocol.ReleaseResponse(response)\n\t}()\n\terr := client.Do(context.Background(), request, response)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err.Error())\n\t}\n}\n\nfunc TestClientLastMiddleware(t *testing.T) {\n\tclient, _ := NewClient()\n\tmw0 := func(next Endpoint) Endpoint {\n\t\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\t\tfinalValue0 := ctx.Value(\"final0\")\n\t\t\tassert.DeepEqual(t, \"final3\", finalValue0)\n\t\t\tfinalValue1 := ctx.Value(\"final1\")\n\t\t\tassert.DeepEqual(t, \"final1\", finalValue1)\n\t\t\tfinalValue2 := ctx.Value(\"final2\")\n\t\t\tassert.DeepEqual(t, \"final2\", finalValue2)\n\t\t\treturn nil\n\t\t}\n\t}\n\tmw1 := func(next Endpoint) Endpoint {\n\t\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\t\t//nolint:staticcheck // SA1029 no built-in type string as key\n\t\t\tctx = context.WithValue(ctx, \"final0\", \"final0\")\n\t\t\treturn next(ctx, req, resp)\n\t\t}\n\t}\n\tmw2 := func(next Endpoint) Endpoint {\n\t\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\t\t//nolint:staticcheck // SA1029 no built-in type string as key\n\t\t\tctx = context.WithValue(ctx, \"final1\", \"final1\")\n\t\t\treturn next(ctx, req, resp)\n\t\t}\n\t}\n\tmw3 := func(next Endpoint) Endpoint {\n\t\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\t\t//nolint:staticcheck // SA1029 no built-in type string as key\n\t\t\tctx = context.WithValue(ctx, \"final2\", \"final2\")\n\t\t\treturn next(ctx, req, resp)\n\t\t}\n\t}\n\tmw4 := func(next Endpoint) Endpoint {\n\t\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\t\t//nolint:staticcheck // SA1029 no built-in type string as key\n\t\t\tctx = context.WithValue(ctx, \"final0\", \"final3\")\n\t\t\treturn next(ctx, req, resp)\n\t\t}\n\t}\n\terr := client.UseAsLast(mw0)\n\tassert.Nil(t, err)\n\terr = client.UseAsLast(func(endpoint Endpoint) Endpoint {\n\t\treturn nil\n\t})\n\tassert.DeepEqual(t, errorLastMiddlewareExist, err)\n\tclient.Use(mw1)\n\tclient.Use(mw2)\n\tclient.Use(mw3)\n\tclient.Use(mw4)\n\n\trequest, response := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(request)\n\t\tprotocol.ReleaseResponse(response)\n\t}()\n\terr = client.Do(context.Background(), request, response)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err.Error())\n\t}\n\n\tlast := client.TakeOutLastMiddleware()\n\n\tassert.DeepEqual(t, reflect.ValueOf(last).Pointer(), reflect.ValueOf(mw0).Pointer())\n\tlast = client.TakeOutLastMiddleware()\n\tassert.Nil(t, last)\n}\n\nfunc TestClientReadResponseBodyStreamWithDoubleRequest(t *testing.T) {\n\tpart1 := \"\"\n\tfor i := 0; i < 8192; i++ {\n\t\tpart1 += \"a\"\n\t}\n\tpart2 := \"ghij\"\n\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.POST(\"/\", func(ctx context.Context, c *app.RequestContext) {\n\t\tc.String(consts.StatusOK, part1+part2)\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tclient, _ := NewClient(WithResponseBodyStream(true))\n\treq, resp := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(resp)\n\t}()\n\treq.SetRequestURI(fullURL(ln, \"\"))\n\treq.SetMethod(consts.MethodPost)\n\terr := client.Do(context.Background(), req, resp)\n\tif err != nil {\n\t\tt.Errorf(\"client Do error=%v\", err.Error())\n\t}\n\tbodyStream := resp.BodyStream()\n\tif bodyStream == nil {\n\t\tt.Errorf(\"bodystream is nil\")\n\t}\n\n\t// Read part1 body bytes\n\tp := make([]byte, len(part1))\n\tr, err := bodyStream.Read(p)\n\tif err != nil {\n\t\tt.Errorf(\"read from bodystream error=%v\", err.Error())\n\t}\n\tif string(p) != part1 {\n\t\tt.Errorf(\"read len=%v, read content=%v; want len=%v, want content=%v\", r, string(p), len(part1), part1)\n\t}\n\n\t// send another request and read all bodystream\n\treq1, resp1 := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req1)\n\t\tprotocol.ReleaseResponse(resp1)\n\t}()\n\treq1.SetRequestURI(fullURL(ln, \"\"))\n\treq1.SetMethod(consts.MethodPost)\n\terr = client.Do(context.Background(), req1, resp1)\n\tif err != nil {\n\t\tt.Errorf(\"client Do error=%v\", err.Error())\n\t}\n\tbodyStream1 := resp1.BodyStream()\n\tif bodyStream1 == nil {\n\t\tt.Errorf(\"bodystream1 is nil\")\n\t}\n\tdata, _ := ioutil.ReadAll(bodyStream1)\n\tif string(data) != part1+part2 {\n\t\tt.Errorf(\"read len=%v, read content=%v; want len=%v, want content=%v\", len(data), data, len(part1+part2), part1+part2)\n\t}\n\n\t// read left bodystream\n\tleft, _ := ioutil.ReadAll(bodyStream)\n\tif string(left) != part2 {\n\t\tt.Errorf(\"left len=%v, left content=%v; want len=%v, want content=%v\", len(left), string(left), len(part2), part2)\n\t}\n}\n\nfunc TestClientReadResponseBodyStreamWithConnectionClose(t *testing.T) {\n\tpart1 := \"\"\n\tfor i := 0; i < 8192; i++ {\n\t\tpart1 += \"a\"\n\t}\n\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.POST(\"/\", func(ctx context.Context, c *app.RequestContext) {\n\t\tc.String(consts.StatusOK, part1)\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tclient, _ := NewClient(WithResponseBodyStream(true))\n\n\t// first req\n\treq, resp := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req)\n\t\tprotocol.ReleaseResponse(resp)\n\t}()\n\treq.SetConnectionClose()\n\treq.SetMethod(consts.MethodPost)\n\treq.SetRequestURI(fullURL(ln, \"\"))\n\n\terr := client.Do(context.Background(), req, resp)\n\tif err != nil {\n\t\tt.Fatalf(\"client Do error=%v\", err.Error())\n\t}\n\n\tassert.DeepEqual(t, part1, string(resp.Body()))\n\n\t// second req\n\treq1, resp1 := protocol.AcquireRequest(), protocol.AcquireResponse()\n\tdefer func() {\n\t\tprotocol.ReleaseRequest(req1)\n\t\tprotocol.ReleaseResponse(resp1)\n\t}()\n\treq1.SetConnectionClose()\n\treq1.SetMethod(consts.MethodPost)\n\treq1.SetRequestURI(fullURL(ln, \"\"))\n\n\terr = client.Do(context.Background(), req1, resp1)\n\tif err != nil {\n\t\tt.Fatalf(\"client Do error=%v\", err.Error())\n\t}\n\n\tassert.DeepEqual(t, part1, string(resp1.Body()))\n}\n\ntype mockDialer struct {\n\tnetwork.Dialer\n\tcustomDialerFunc func(network, address string, timeout time.Duration, tlsConfig *tls.Config)\n\tnetwork          string\n\taddress          string\n\ttimeout          time.Duration\n}\n\nfunc (m *mockDialer) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {\n\tif m.customDialerFunc != nil {\n\t\tm.customDialerFunc(network, address, timeout, tlsConfig)\n\t}\n\treturn m.Dialer.DialConnection(m.network, m.address, m.timeout, tlsConfig)\n}\n\nfunc TestClientRetry(t *testing.T) {\n\tclient, err := NewClient(\n\t\t// Default dial function performs different in different os. So unit the performance of dial function.\n\t\tWithDialFunc(func(addr string) (network.Conn, error) {\n\t\t\treturn nil, fmt.Errorf(\"dial tcp %s: i/o timeout\", addr)\n\t\t}),\n\t\tWithRetryConfig(\n\t\t\tretry.WithMaxAttemptTimes(3),\n\t\t\tretry.WithInitDelay(100*time.Millisecond),\n\t\t\tretry.WithMaxDelay(10*time.Second),\n\t\t\tretry.WithDelayPolicy(retry.CombineDelay(retry.FixedDelayPolicy, retry.BackOffDelayPolicy)),\n\t\t),\n\t)\n\tclient.SetRetryIfFunc(func(req *protocol.Request, resp *protocol.Response, err error) bool {\n\t\treturn err != nil\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tstartTime := time.Now().UnixNano()\n\t_, resp, err := client.Get(context.Background(), nil, \"http://127.0.0.1:1234/ping\")\n\tif err != nil {\n\t\t// first delay 100+200ms , second delay 100+400ms\n\t\tif time.Duration(time.Now().UnixNano()-startTime) > 800*time.Millisecond && time.Duration(time.Now().UnixNano()-startTime) < 2*time.Second {\n\t\t\tt.Logf(\"Retry triggered : delay=%dms\\tresp=%v\\terr=%v\\n\", time.Duration(time.Now().UnixNano()-startTime)/(1*time.Millisecond), string(resp), fmt.Sprintln(err))\n\t\t} else if time.Duration(time.Now().UnixNano()-startTime) < 1*time.Second { // Compatible without triggering retry\n\t\t\tt.Logf(\"Retry not triggered : delay=%dms\\tresp=%v\\terr=%v\\n\", time.Duration(time.Now().UnixNano()-startTime)/(1*time.Millisecond), string(resp), fmt.Sprintln(err))\n\t\t} else {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\tclient2, err := NewClient(\n\t\tWithDialFunc(func(addr string) (network.Conn, error) {\n\t\t\treturn nil, fmt.Errorf(\"dial tcp %s: i/o timeout\", addr)\n\t\t}),\n\t\tWithRetryConfig(\n\t\t\tretry.WithMaxAttemptTimes(2),\n\t\t\tretry.WithInitDelay(500*time.Millisecond),\n\t\t\tretry.WithMaxJitter(1*time.Second),\n\t\t\tretry.WithDelayPolicy(retry.CombineDelay(retry.FixedDelayPolicy, retry.BackOffDelayPolicy)),\n\t\t),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tclient2.SetRetryIfFunc(func(req *protocol.Request, resp *protocol.Response, err error) bool {\n\t\treturn err != nil\n\t})\n\tstartTime = time.Now().UnixNano()\n\t_, resp, err = client2.Get(context.Background(), nil, \"http://127.0.0.1:1234/ping\")\n\tif err != nil {\n\t\t// delay max{500ms+rand([0,1))s,100ms}. Because if the MaxDelay is not set, we will use the default MaxDelay of 100ms\n\t\tif time.Duration(time.Now().UnixNano()-startTime) > 100*time.Millisecond && time.Duration(time.Now().UnixNano()-startTime) < 1100*time.Millisecond {\n\t\t\tt.Logf(\"Retry triggered : delay=%dms\\tresp=%v\\terr=%v\\n\", time.Duration(time.Now().UnixNano()-startTime)/(1*time.Millisecond), string(resp), fmt.Sprintln(err))\n\t\t} else if time.Duration(time.Now().UnixNano()-startTime) < 1*time.Second { // Compatible without triggering retry\n\t\t\tt.Logf(\"Retry not triggered : delay=%dms\\tresp=%v\\terr=%v\\n\", time.Duration(time.Now().UnixNano()-startTime)/(1*time.Millisecond), string(resp), fmt.Sprintln(err))\n\t\t} else {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\tclient3, err := NewClient(\n\t\tWithDialFunc(func(addr string) (network.Conn, error) {\n\t\t\treturn nil, fmt.Errorf(\"dial tcp %s: i/o timeout\", addr)\n\t\t}),\n\t\tWithRetryConfig(\n\t\t\tretry.WithMaxAttemptTimes(2),\n\t\t\tretry.WithInitDelay(100*time.Millisecond),\n\t\t\tretry.WithMaxDelay(5*time.Second),\n\t\t\tretry.WithMaxJitter(1*time.Second),\n\t\t\tretry.WithDelayPolicy(retry.CombineDelay(retry.FixedDelayPolicy, retry.BackOffDelayPolicy, retry.RandomDelayPolicy)),\n\t\t),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tclient3.SetRetryIfFunc(func(req *protocol.Request, resp *protocol.Response, err error) bool {\n\t\treturn err != nil\n\t})\n\tstartTime = time.Now().UnixNano()\n\t_, resp, err = client3.Get(context.Background(), nil, \"http://127.0.0.1:1234/ping\")\n\tif err != nil {\n\t\t// delay 100ms+200ms+rand([0,1))s\n\t\tif time.Duration(time.Now().UnixNano()-startTime) > 300*time.Millisecond && time.Duration(time.Now().UnixNano()-startTime) < 2300*time.Millisecond {\n\t\t\tt.Logf(\"Retry triggered : delay=%dms\\tresp=%v\\terr=%v\\n\", time.Duration(time.Now().UnixNano()-startTime)/(1*time.Millisecond), string(resp), fmt.Sprintln(err))\n\t\t} else if time.Duration(time.Now().UnixNano()-startTime) < 1*time.Second { // Compatible without triggering retry\n\t\t\tt.Logf(\"Retry not triggered : delay=%dms\\tresp=%v\\terr=%v\\n\", time.Duration(time.Now().UnixNano()-startTime)/(1*time.Millisecond), string(resp), fmt.Sprintln(err))\n\t\t} else {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\tclient4, err := NewClient(\n\t\tWithDialFunc(func(addr string) (network.Conn, error) {\n\t\t\treturn nil, fmt.Errorf(\"dial tcp %s: i/o timeout\", addr)\n\t\t}),\n\t\tWithRetryConfig(\n\t\t\tretry.WithMaxAttemptTimes(2),\n\t\t\tretry.WithInitDelay(1*time.Second),\n\t\t\tretry.WithMaxDelay(10*time.Second),\n\t\t\tretry.WithMaxJitter(5*time.Second),\n\t\t\tretry.WithDelayPolicy(retry.CombineDelay(retry.FixedDelayPolicy, retry.BackOffDelayPolicy, retry.RandomDelayPolicy)),\n\t\t),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\t/* If the retryIfFunc is not set , idempotent logic is used by default */\n\t//client4.SetRetryIfFunc(func(req *protocol.Request, resp *protocol.Response, err error) bool {\n\t//\treturn err != nil\n\t//})\n\tstartTime = time.Now().UnixNano()\n\t_, resp, err = client4.Get(context.Background(), nil, \"http://127.0.0.1:1234/ping\")\n\tif err != nil {\n\t\tif time.Duration(time.Now().UnixNano()-startTime) > 1*time.Second && time.Duration(time.Now().UnixNano()-startTime) < 9*time.Second {\n\t\t\tt.Logf(\"Retry triggered : delay=%dms\\tresp=%v\\terr=%v\\n\", time.Duration(time.Now().UnixNano()-startTime)/(1*time.Millisecond), string(resp), fmt.Sprintln(err))\n\t\t} else if time.Duration(time.Now().UnixNano()-startTime) < 1*time.Second { // Compatible without triggering retry\n\t\t\tt.Logf(\"Retry not triggered : delay=%dms\\tresp=%v\\terr=%v\\n\", time.Duration(time.Now().UnixNano()-startTime)/(1*time.Millisecond), string(resp), fmt.Sprintln(err))\n\t\t} else {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\treturn\n\t}\n}\n\nfunc TestClientHostClientConfigHookError(t *testing.T) {\n\tclient, _ := NewClient(WithHostClientConfigHook(func(hc interface{}) error {\n\t\thct, ok := hc.(*http1.HostClient)\n\t\tassert.True(t, ok)\n\t\tassert.DeepEqual(t, \"foo.bar:80\", hct.Addr)\n\t\treturn errors.New(\"hook return\")\n\t}))\n\n\treq := protocol.AcquireRequest()\n\treq.SetMethod(consts.MethodGet)\n\treq.SetRequestURI(\"http://foo.bar/\")\n\tresp := protocol.AcquireResponse()\n\terr := client.do(context.TODO(), req, resp)\n\tassert.DeepEqual(t, \"hook return\", err.Error())\n}\n\nfunc TestClientHostClientConfigHook(t *testing.T) {\n\tclient, _ := NewClient(WithHostClientConfigHook(func(hc interface{}) error {\n\t\thct, ok := hc.(*http1.HostClient)\n\t\tassert.True(t, ok)\n\t\tassert.DeepEqual(t, \"foo.bar:80\", hct.Addr)\n\t\thct.Addr = \"FOO.BAR:443\"\n\t\treturn nil\n\t}))\n\n\treq := protocol.AcquireRequest()\n\treq.SetMethod(consts.MethodGet)\n\treq.SetRequestURI(\"http://foo.bar/\")\n\tresp := protocol.AcquireResponse()\n\tclient.do(context.Background(), req, resp)\n\tclient.mLock.Lock()\n\thc := client.m[\"foo.bar\"]\n\tclient.mLock.Unlock()\n\thcr, ok := hc.(*http1.HostClient)\n\tassert.True(t, ok)\n\tassert.DeepEqual(t, \"FOO.BAR:443\", hcr.Addr)\n}\n\nfunc TestClientDialerName(t *testing.T) {\n\tclient, _ := NewClient()\n\tdName, err := client.GetDialerName()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\t// Depending on the operating system,\n\t// the default dialer has a different network library, either \"netpoll\" or \"standard\"\n\tif !(dName == \"netpoll\" || dName == \"standard\") {\n\t\tt.Errorf(\"expected 'netpoll', but get %s\", dName)\n\t}\n\n\tclient, _ = NewClient(WithDialer(&mockDialer{}))\n\tdName, err = client.GetDialerName()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif dName != \"client\" {\n\t\tt.Errorf(\"expected 'standard', but get %s\", dName)\n\t}\n\n\tclient, _ = NewClient(WithDialer(standard.NewDialer()))\n\tdName, err = client.GetDialerName()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif dName != \"standard\" {\n\t\tt.Errorf(\"expected 'standard', but get %s\", dName)\n\t}\n\n\tclient, _ = NewClient(WithDialer(&mockDialer{}))\n\tdName, err = client.GetDialerName()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif dName != \"client\" {\n\t\tt.Errorf(\"expected 'client', but get %s\", dName)\n\t}\n\n\tclient.options.Dialer = nil\n\tdName, err = client.GetDialerName()\n\tif err == nil {\n\t\tt.Errorf(\"expected an err for abnormal process\")\n\t}\n\tif dName != \"\" {\n\t\tt.Errorf(\"expected 'empty string', but get %s\", dName)\n\t}\n}\n\nfunc TestClientDoWithDialFunc(t *testing.T) {\n\tch := make(chan error, 1)\n\turi := \"/foo/bar/baz\"\n\tbody := \"request body\"\n\topt, ln := newTestOptions(t)\n\tdefer ln.Close()\n\tengine := route.NewEngine(opt)\n\n\tengine.POST(\"/foo/bar/baz\", func(c context.Context, ctx *app.RequestContext) {\n\t\tif string(ctx.Request.Header.Method()) != consts.MethodPost {\n\t\t\tch <- fmt.Errorf(\"unexpected request method: %q. Expecting %q\", ctx.Request.Header.Method(), consts.MethodPost)\n\t\t\treturn\n\t\t}\n\t\treqURI := ctx.Request.RequestURI()\n\t\tif string(reqURI) != uri {\n\t\t\tch <- fmt.Errorf(\"unexpected request uri: %q. Expecting %q\", reqURI, uri)\n\t\t\treturn\n\t\t}\n\t\tcl := ctx.Request.Header.ContentLength()\n\t\tif cl != len(body) {\n\t\t\tch <- fmt.Errorf(\"unexpected content-length %d. Expecting %d\", cl, len(body))\n\t\t\treturn\n\t\t}\n\t\treqBody := ctx.Request.Body()\n\t\tif string(reqBody) != body {\n\t\t\tch <- fmt.Errorf(\"unexpected request body: %q. Expecting %q\", reqBody, body)\n\t\t\treturn\n\t\t}\n\t\tch <- nil\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tc, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {\n\t\treturn dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)\n\t}))\n\n\tvar req protocol.Request\n\treq.Header.SetMethod(consts.MethodPost)\n\treq.SetRequestURI(uri)\n\treq.SetHost(\"xxx.com\")\n\treq.SetBodyString(body)\n\n\tvar resp protocol.Response\n\n\terr := c.Do(context.Background(), &req, &resp)\n\tif err != nil {\n\t\tt.Fatalf(\"error when doing request: %s\", err)\n\t}\n\n\tselect {\n\tcase err = <-ch:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"err = %s\", err.Error())\n\t\t}\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\nfunc TestClientState(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\topt.Addr = ln.Addr().String()\n\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tstate := int32(0)\n\tclient, _ := NewClient(\n\t\tWithMaxIdleConnDuration(75*time.Millisecond),\n\t\tWithConnStateObserve(func(hcs config.HostClientState) {\n\t\t\tswitch atomic.LoadInt32(&state) {\n\t\t\tcase int32(0):\n\t\t\t\tassert.DeepEqual(t, 1, hcs.ConnPoolState().TotalConnNum)\n\t\t\t\tassert.DeepEqual(t, 1, hcs.ConnPoolState().PoolConnNum)\n\t\t\t\tassert.DeepEqual(t, 0, hcs.ConnPoolState().MaxConns)\n\t\t\t\tassert.DeepEqual(t, opt.Addr, hcs.ConnPoolState().Addr)\n\t\t\t\tatomic.StoreInt32(&state, int32(1))\n\t\t\t\twg.Done()\n\t\t\tcase int32(1):\n\t\t\t\tassert.DeepEqual(t, 0, hcs.ConnPoolState().TotalConnNum)\n\t\t\t\tassert.DeepEqual(t, 0, hcs.ConnPoolState().PoolConnNum)\n\t\t\t\tassert.DeepEqual(t, 0, hcs.ConnPoolState().MaxConns)\n\t\t\t\tassert.DeepEqual(t, opt.Addr, hcs.ConnPoolState().Addr)\n\t\t\t\tatomic.StoreInt32(&state, int32(2))\n\t\t\t\twg.Done()\n\t\t\t}\n\t\t}, 50*time.Millisecond))\n\tclient.Get(context.Background(), nil, \"http://\"+opt.Addr)\n\twg.Wait()\n\tassert.DeepEqual(t, int32(2), atomic.LoadInt32(&state))\n}\n\nfunc TestClientRetryErr(t *testing.T) {\n\tt.Run(\"200\", func(t *testing.T) {\n\t\tln := testutils.NewTestListener(t)\n\t\tdefer ln.Close()\n\n\t\topt := config.NewOptions([]config.Option{})\n\t\topt.Listener = ln\n\t\tengine := route.NewEngine(opt)\n\t\tvar l sync.Mutex\n\t\tretryNum := 0\n\t\tengine.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\t\tl.Lock()\n\t\t\tdefer l.Unlock()\n\t\t\tretryNum += 1\n\t\t\tctx.SetStatusCode(200)\n\t\t})\n\t\tgo engine.Run()\n\t\tdefer func() {\n\t\t\tengine.Close()\n\t\t}()\n\t\twaitEngineRunning(engine)\n\n\t\tc, _ := NewClient(WithRetryConfig(retry.WithMaxAttemptTimes(3)))\n\t\t_, _, err := c.Get(context.Background(), nil, fullURL(ln, \"/ping\"))\n\t\tassert.Nil(t, err)\n\t\tl.Lock()\n\t\tassert.DeepEqual(t, 1, retryNum)\n\t\tl.Unlock()\n\t})\n\n\tt.Run(\"502\", func(t *testing.T) {\n\t\tln := testutils.NewTestListener(t)\n\t\tdefer ln.Close()\n\n\t\topt := config.NewOptions([]config.Option{})\n\t\topt.Listener = ln\n\t\tengine := route.NewEngine(opt)\n\t\tvar l sync.Mutex\n\t\tretryNum := 0\n\t\tengine.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\t\tl.Lock()\n\t\t\tdefer l.Unlock()\n\t\t\tretryNum += 1\n\t\t\tctx.SetStatusCode(502)\n\t\t})\n\t\tgo engine.Run()\n\t\tdefer func() {\n\t\t\tengine.Close()\n\t\t}()\n\t\twaitEngineRunning(engine)\n\n\t\tc, _ := NewClient(WithRetryConfig(retry.WithMaxAttemptTimes(3)))\n\t\tc.SetRetryIfFunc(func(req *protocol.Request, resp *protocol.Response, err error) bool {\n\t\t\treturn resp.StatusCode() == 502\n\t\t})\n\t\t_, _, err := c.Get(context.Background(), nil, fullURL(ln, \"/ping\"))\n\t\tassert.Nil(t, err)\n\t\tl.Lock()\n\t\tassert.DeepEqual(t, 3, retryNum)\n\t\tl.Unlock()\n\t})\n}\n\ntype mockHostClient struct {\n\tshouldRemove bool\n\tclosed       bool\n}\n\nfunc (m *mockHostClient) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error {\n\treturn nil\n}\n\nfunc (m *mockHostClient) SetDynamicConfig(dc *client.DynamicConfig) {\n}\n\nfunc (m *mockHostClient) CloseIdleConnections() {\n}\n\nfunc (m *mockHostClient) ShouldRemove() bool {\n\treturn m.shouldRemove\n}\n\nfunc (m *mockHostClient) ConnectionCount() int {\n\treturn 0\n}\n\nfunc (m *mockHostClient) Close() error {\n\tm.closed = true\n\treturn nil\n}\n\nfunc TestCleanHostClients(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tisTLS        bool\n\t\tinitMap      map[string]*mockHostClient\n\t\texpectedKeys []string\n\t\tshouldClose  bool\n\t\texpectedRes  bool\n\t}{\n\t\t{\n\t\t\tname:  \"Remove item from c.m\",\n\t\t\tisTLS: false,\n\t\t\tinitMap: map[string]*mockHostClient{\n\t\t\t\t\"google.com\": {shouldRemove: true},\n\t\t\t},\n\t\t\texpectedKeys: []string{},\n\t\t\tshouldClose:  true,\n\t\t\texpectedRes:  true,\n\t\t},\n\t\t{\n\t\t\tname:  \"Remove item from c.ms\",\n\t\t\tisTLS: true,\n\t\t\tinitMap: map[string]*mockHostClient{\n\t\t\t\t\"google.com\": {shouldRemove: true},\n\t\t\t},\n\t\t\texpectedKeys: []string{},\n\t\t\tshouldClose:  true,\n\t\t\texpectedRes:  true,\n\t\t},\n\t\t{\n\t\t\tname:  \"Do not remove non-removable client\",\n\t\t\tisTLS: false,\n\t\t\tinitMap: map[string]*mockHostClient{\n\t\t\t\t\"google.com\": {shouldRemove: false},\n\t\t\t},\n\t\t\texpectedKeys: []string{\"google.com\"},\n\t\t\tshouldClose:  false,\n\t\t\texpectedRes:  false,\n\t\t},\n\t\t{\n\t\t\tname:         \"Empty map\",\n\t\t\tisTLS:        false,\n\t\t\tinitMap:      map[string]*mockHostClient{},\n\t\t\texpectedKeys: []string{},\n\t\t\tshouldClose:  false,\n\t\t\texpectedRes:  true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcli := &Client{\n\t\t\t\tmLock: sync.Mutex{},\n\t\t\t\tm:     map[string]client.HostClient{},\n\t\t\t\tms:    map[string]client.HostClient{},\n\t\t\t}\n\n\t\t\tif tt.isTLS {\n\t\t\t\tfor k, v := range tt.initMap {\n\t\t\t\t\tcli.ms[k] = v\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor k, v := range tt.initMap {\n\t\t\t\t\tcli.m[k] = v\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresult := cli.cleanHostClients(tt.isTLS)\n\n\t\t\tvar resultMap map[string]client.HostClient\n\t\t\tif tt.isTLS {\n\t\t\t\tresultMap = cli.ms\n\t\t\t} else {\n\t\t\t\tresultMap = cli.m\n\t\t\t}\n\n\t\t\tvar keys []string\n\t\t\tfor k := range resultMap {\n\t\t\t\tkeys = append(keys, k)\n\t\t\t}\n\t\t\tassert.Assert(t, len(tt.expectedKeys) == len(keys))\n\n\t\t\tfor _, v := range tt.initMap {\n\t\t\t\tif v.shouldRemove {\n\t\t\t\t\tassert.Assert(t, v.closed, \"Expected client to be closed\")\n\t\t\t\t} else {\n\t\t\t\t\tassert.Assert(t, !v.closed, \"Client should not be closed\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tassert.Assert(t, tt.expectedRes == result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/app/client/client_unix_test.go",
    "content": "// Copyright 2023 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris\n\npackage client\n\nimport (\n\t\"crypto/tls\"\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/netpoll\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n)\n\nfunc newMockDialerWithCustomFunc(network, address string, timeout time.Duration, f func(network, address string, timeout time.Duration, tlsConfig *tls.Config)) network.Dialer {\n\tdialer := standard.NewDialer()\n\tif rand.Intn(2) == 0 {\n\t\tdialer = netpoll.NewDialer()\n\t}\n\treturn &mockDialer{\n\t\tDialer:           dialer,\n\t\tcustomDialerFunc: f,\n\t\tnetwork:          network,\n\t\taddress:          address,\n\t\ttimeout:          timeout,\n\t}\n}\n"
  },
  {
    "path": "pkg/app/client/client_windows_test.go",
    "content": "// Copyright 2023 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build windows\n\npackage client\n\nimport (\n\t\"crypto/tls\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n)\n\nfunc newMockDialerWithCustomFunc(network, address string, timeout time.Duration, f func(network, address string, timeout time.Duration, tlsConfig *tls.Config)) network.Dialer {\n\tdialer := standard.NewDialer()\n\treturn &mockDialer{\n\t\tDialer:           dialer,\n\t\tcustomDialerFunc: f,\n\t\tnetwork:          network,\n\t\taddress:          address,\n\t\ttimeout:          timeout,\n\t}\n}\n"
  },
  {
    "path": "pkg/app/client/discovery/discovery.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage discovery\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/server/registry\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n)\n\ntype TargetInfo struct {\n\tHost string\n\tTags map[string]string\n}\n\ntype Resolver interface {\n\t// Target should return a description for the given target that is suitable for being a key for cache.\n\tTarget(ctx context.Context, target *TargetInfo) string\n\n\t// Resolve returns a list of instances for the given description of a target.\n\tResolve(ctx context.Context, desc string) (Result, error)\n\n\t// Name returns the name of the resolver.\n\tName() string\n}\n\n// SynthesizedResolver synthesizes a Resolver using a resolve function.\ntype SynthesizedResolver struct {\n\tTargetFunc  func(ctx context.Context, target *TargetInfo) string\n\tResolveFunc func(ctx context.Context, key string) (Result, error)\n\tNameFunc    func() string\n}\n\nfunc (sr SynthesizedResolver) Target(ctx context.Context, target *TargetInfo) string {\n\tif sr.TargetFunc == nil {\n\t\treturn \"\"\n\t}\n\treturn sr.TargetFunc(ctx, target)\n}\n\nfunc (sr SynthesizedResolver) Resolve(ctx context.Context, key string) (Result, error) {\n\treturn sr.ResolveFunc(ctx, key)\n}\n\n// Name implements the Resolver interface\nfunc (sr SynthesizedResolver) Name() string {\n\tif sr.NameFunc == nil {\n\t\treturn \"\"\n\t}\n\treturn sr.NameFunc()\n}\n\n// Instance contains information of an instance from the target service.\ntype Instance interface {\n\tAddress() net.Addr\n\tWeight() int\n\tTag(key string) (value string, exist bool)\n}\n\ntype instance struct {\n\taddr   net.Addr\n\tweight int\n\ttags   map[string]string\n}\n\nfunc (i *instance) Address() net.Addr {\n\treturn i.addr\n}\n\nfunc (i *instance) Weight() int {\n\tif i.weight > 0 {\n\t\treturn i.weight\n\t}\n\treturn registry.DefaultWeight\n}\n\nfunc (i *instance) Tag(key string) (value string, exist bool) {\n\tvalue, exist = i.tags[key]\n\treturn\n}\n\n// NewInstance creates an Instance using the given network, address and tags\nfunc NewInstance(network, address string, weight int, tags map[string]string) Instance {\n\treturn &instance{\n\t\taddr:   utils.NewNetAddr(network, address),\n\t\tweight: weight,\n\t\ttags:   tags,\n\t}\n}\n\n// Result contains the result of service discovery process.\n// the instance list can/should be cached and CacheKey can be used to map the instance list in cache.\ntype Result struct {\n\tCacheKey  string\n\tInstances []Instance\n}\n"
  },
  {
    "path": "pkg/app/client/discovery/discovery_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage discovery\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/server/registry\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestInstance(t *testing.T) {\n\tnetwork := \"192.168.1.1\"\n\taddress := \"/hello\"\n\tweight := 1\n\tinstance := NewInstance(network, address, weight, nil)\n\n\tassert.DeepEqual(t, network, instance.Address().Network())\n\tassert.DeepEqual(t, address, instance.Address().String())\n\tassert.DeepEqual(t, weight, instance.Weight())\n\tval, ok := instance.Tag(\"name\")\n\tassert.DeepEqual(t, \"\", val)\n\tassert.False(t, ok)\n\n\tinstance2 := NewInstance(\"\", \"\", 0, nil)\n\tassert.DeepEqual(t, registry.DefaultWeight, instance2.Weight())\n}\n\nfunc TestSynthesizedResolver(t *testing.T) {\n\ttargetFunc := func(ctx context.Context, target *TargetInfo) string {\n\t\treturn \"hello\"\n\t}\n\tresolveFunc := func(ctx context.Context, key string) (Result, error) {\n\t\treturn Result{CacheKey: \"name\"}, nil\n\t}\n\tnameFunc := func() string {\n\t\treturn \"raymonder\"\n\t}\n\tresolver := SynthesizedResolver{\n\t\tTargetFunc:  targetFunc,\n\t\tResolveFunc: resolveFunc,\n\t\tNameFunc:    nameFunc,\n\t}\n\n\tassert.DeepEqual(t, \"hello\", resolver.Target(context.Background(), &TargetInfo{}))\n\tres, err := resolver.Resolve(context.Background(), \"\")\n\tassert.DeepEqual(t, \"name\", res.CacheKey)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, \"raymonder\", resolver.Name())\n\n\tresolver2 := SynthesizedResolver{\n\t\tTargetFunc:  nil,\n\t\tResolveFunc: nil,\n\t\tNameFunc:    nil,\n\t}\n\tassert.DeepEqual(t, \"\", resolver2.Target(context.Background(), &TargetInfo{}))\n\tassert.DeepEqual(t, \"\", resolver2.Name())\n}\n"
  },
  {
    "path": "pkg/app/client/loadbalance/lbcache.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage loadbalance\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/discovery\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"golang.org/x/sync/singleflight\"\n)\n\ntype cacheResult struct {\n\tres         atomic.Value // newest and previous discovery result\n\texpire      int32        // 0 = normal, 1 = expire and collect next ticker\n\tserviceName string       // service psm\n}\n\nvar (\n\tbalancerFactories    sync.Map // key: resolver name + load-balancer name\n\tbalancerFactoriesSfg singleflight.Group\n)\n\nfunc cacheKey(resolver, balancer string, opts Options) string {\n\treturn fmt.Sprintf(\"%s|%s|{%s %s}\", resolver, balancer, opts.RefreshInterval, opts.ExpireInterval)\n}\n\ntype BalancerFactory struct {\n\topts     Options\n\tcache    sync.Map // key -> LoadBalancer\n\tresolver discovery.Resolver\n\tbalancer Loadbalancer\n\tsfg      singleflight.Group\n}\n\ntype Config struct {\n\tResolver discovery.Resolver\n\tBalancer Loadbalancer\n\tLbOpts   Options\n}\n\n// NewBalancerFactory get or create a balancer with given target.\n// If it has the same key(resolver.Target(target)), we will cache and reuse the Balance.\nfunc NewBalancerFactory(config Config) *BalancerFactory {\n\tconfig.LbOpts.Check()\n\tuniqueKey := cacheKey(config.Resolver.Name(), config.Balancer.Name(), config.LbOpts)\n\tval, ok := balancerFactories.Load(uniqueKey)\n\tif ok {\n\t\treturn val.(*BalancerFactory)\n\t}\n\tval, _, _ = balancerFactoriesSfg.Do(uniqueKey, func() (interface{}, error) {\n\t\tb := &BalancerFactory{\n\t\t\topts:     config.LbOpts,\n\t\t\tresolver: config.Resolver,\n\t\t\tbalancer: config.Balancer,\n\t\t}\n\t\tgo b.watcher()\n\t\tgo b.refresh()\n\t\tbalancerFactories.Store(uniqueKey, b)\n\t\treturn b, nil\n\t})\n\treturn val.(*BalancerFactory)\n}\n\n// watch expired balancer\nfunc (b *BalancerFactory) watcher() {\n\tfor range time.Tick(b.opts.ExpireInterval) {\n\t\tb.cache.Range(func(key, value interface{}) bool {\n\t\t\tcache := value.(*cacheResult)\n\t\t\tif atomic.CompareAndSwapInt32(&cache.expire, 0, 1) {\n\t\t\t\t// 1. set expire flag\n\t\t\t\t// 2. wait next ticker for collect, maybe the balancer is used again\n\t\t\t\t// (avoid being immediate delete the balancer which had been created recently)\n\t\t\t} else {\n\t\t\t\tb.cache.Delete(key)\n\t\t\t\tb.balancer.Delete(key.(string))\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n}\n\n// cache key with resolver name prefix avoid conflict for balancer\nfunc renameResultCacheKey(res *discovery.Result, resolverName string) {\n\tres.CacheKey = resolverName + \":\" + res.CacheKey\n}\n\n// refresh is used to update service discovery information periodically.\nfunc (b *BalancerFactory) refresh() {\n\tfor range time.Tick(b.opts.RefreshInterval) {\n\t\tb.cache.Range(func(key, value interface{}) bool {\n\t\t\tres, err := b.resolver.Resolve(context.Background(), key.(string))\n\t\t\tif err != nil {\n\t\t\t\thlog.SystemLogger().Warnf(\"resolver refresh failed, key=%s error=%s\", key, err.Error())\n\t\t\t\treturn true\n\t\t\t}\n\t\t\trenameResultCacheKey(&res, b.resolver.Name())\n\t\t\tcache := value.(*cacheResult)\n\t\t\tcache.res.Store(res)\n\t\t\tatomic.StoreInt32(&cache.expire, 0)\n\t\t\tb.balancer.Rebalance(res)\n\t\t\treturn true\n\t\t})\n\t}\n}\n\nfunc (b *BalancerFactory) GetInstance(ctx context.Context, req *protocol.Request) (discovery.Instance, error) {\n\tcacheRes, err := b.getCacheResult(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tatomic.StoreInt32(&cacheRes.expire, 0)\n\tins := b.balancer.Pick(cacheRes.res.Load().(discovery.Result))\n\tif ins == nil {\n\t\thlog.SystemLogger().Errorf(\"null instance. serviceName: %s, options: %v\", string(req.Host()), req.Options())\n\t\treturn nil, errors.NewPublic(\"instance not found\")\n\t}\n\treturn ins, nil\n}\n\nfunc (b *BalancerFactory) getCacheResult(ctx context.Context, req *protocol.Request) (*cacheResult, error) {\n\ttarget := b.resolver.Target(ctx, &discovery.TargetInfo{Host: string(req.Host()), Tags: req.Options().Tags()})\n\tcr, existed := b.cache.Load(target)\n\tif existed {\n\t\treturn cr.(*cacheResult), nil\n\t}\n\tcr, err, _ := b.sfg.Do(target, func() (interface{}, error) {\n\t\tcache := &cacheResult{\n\t\t\tserviceName: string(req.Host()),\n\t\t}\n\t\tres, err := b.resolver.Resolve(ctx, target)\n\t\tif err != nil {\n\t\t\treturn cache, err\n\t\t}\n\t\trenameResultCacheKey(&res, b.resolver.Name())\n\t\tcache.res.Store(res)\n\t\tatomic.StoreInt32(&cache.expire, 0)\n\t\tb.balancer.Rebalance(res)\n\t\tb.cache.Store(target, cache)\n\t\treturn cache, nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn cr.(*cacheResult), nil\n}\n"
  },
  {
    "path": "pkg/app/client/loadbalance/lbcache_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage loadbalance\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/discovery\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\nfunc TestBuilder(t *testing.T) {\n\tins := discovery.NewInstance(\"tcp\", \"127.0.0.1:8888\", 10, map[string]string{\"a\": \"b\"})\n\tr := &discovery.SynthesizedResolver{\n\t\tResolveFunc: func(ctx context.Context, key string) (discovery.Result, error) {\n\t\t\treturn discovery.Result{CacheKey: key, Instances: []discovery.Instance{ins}}, nil\n\t\t},\n\t\tTargetFunc: func(ctx context.Context, target *discovery.TargetInfo) string {\n\t\t\treturn \"mockRoute\"\n\t\t},\n\t\tNameFunc: func() string { return t.Name() },\n\t}\n\tlb := mockLoadbalancer{\n\t\trebalanceFunc: nil,\n\t\tdeleteFunc:    nil,\n\t\tpickFunc: func(res discovery.Result) discovery.Instance {\n\t\t\tassert.Assert(t, res.CacheKey == t.Name()+\":mockRoute\", res.CacheKey)\n\t\t\tassert.Assert(t, len(res.Instances) == 1)\n\t\t\tassert.Assert(t, len(res.Instances) == 1)\n\t\t\tassert.Assert(t, res.Instances[0].Address().String() == \"127.0.0.1:8888\")\n\t\t\treturn res.Instances[0]\n\t\t},\n\t\tnameFunc: func() string { return \"Synthesized\" },\n\t}\n\tNewBalancerFactory(Config{\n\t\tBalancer: lb,\n\t\tLbOpts:   DefaultLbOpts,\n\t\tResolver: r,\n\t})\n\tb, ok := balancerFactories.Load(cacheKey(t.Name(), \"Synthesized\", DefaultLbOpts))\n\tassert.Assert(t, ok)\n\tassert.Assert(t, b != nil)\n\treq := &protocol.Request{}\n\treq.SetHost(\"hertz.api.test\")\n\tins1, err := b.(*BalancerFactory).GetInstance(context.TODO(), req)\n\tassert.Assert(t, err == nil)\n\tassert.Assert(t, ins1.Address().String() == \"127.0.0.1:8888\")\n\tassert.Assert(t, ins1.Weight() == 10)\n\tvalue, exists := ins1.Tag(\"a\")\n\tassert.Assert(t, value == \"b\")\n\tassert.Assert(t, exists == true)\n}\n\nfunc TestBalancerCache(t *testing.T) {\n\tcount := 10\n\tinss := make([]discovery.Instance, 0, count)\n\tfor i := 0; i < count; i++ {\n\t\tinss = append(inss, discovery.NewInstance(\"tcp\", fmt.Sprint(i), 10, nil))\n\t}\n\tr := &discovery.SynthesizedResolver{\n\t\tTargetFunc: func(ctx context.Context, target *discovery.TargetInfo) string {\n\t\t\treturn target.Host\n\t\t},\n\t\tResolveFunc: func(ctx context.Context, key string) (discovery.Result, error) {\n\t\t\treturn discovery.Result{CacheKey: \"svc\", Instances: inss}, nil\n\t\t},\n\t\tNameFunc: func() string { return t.Name() },\n\t}\n\tlb := NewWeightedBalancer()\n\tfor i := 0; i < count; i++ {\n\t\tblf := NewBalancerFactory(Config{\n\t\t\tBalancer: lb,\n\t\t\tLbOpts:   Options{},\n\t\t\tResolver: r,\n\t\t})\n\t\treq := &protocol.Request{}\n\t\treq.SetHost(\"svc\")\n\t\tfor a := 0; a < count; a++ {\n\t\t\taddr, err := blf.GetInstance(context.TODO(), req)\n\t\t\tassert.Assert(t, err == nil, err)\n\t\t\tt.Logf(\"count: %d addr: %s\\n\", i, addr.Address().String())\n\t\t}\n\t}\n}\n\nfunc TestBalancerRefresh(t *testing.T) {\n\tvar ins atomic.Value\n\tins.Store(discovery.NewInstance(\"tcp\", \"127.0.0.1:8888\", 10, nil))\n\tr := &discovery.SynthesizedResolver{\n\t\tTargetFunc: func(ctx context.Context, target *discovery.TargetInfo) string {\n\t\t\treturn target.Host\n\t\t},\n\t\tResolveFunc: func(ctx context.Context, key string) (discovery.Result, error) {\n\t\t\treturn discovery.Result{CacheKey: \"svc1\", Instances: []discovery.Instance{ins.Load().(discovery.Instance)}}, nil\n\t\t},\n\t\tNameFunc: func() string { return t.Name() },\n\t}\n\topts := DefaultLbOpts\n\topts.RefreshInterval = 30 * time.Millisecond\n\tblf := NewBalancerFactory(Config{\n\t\tBalancer: NewWeightedBalancer(),\n\t\tLbOpts:   opts,\n\t\tResolver: r,\n\t})\n\treq := &protocol.Request{}\n\treq.SetHost(\"svc1\")\n\taddr, err := blf.GetInstance(context.Background(), req)\n\tassert.Assert(t, err == nil, err)\n\tassert.Assert(t, addr.Address().String() == \"127.0.0.1:8888\")\n\tins.Store(discovery.NewInstance(\"tcp\", \"127.0.0.1:8889\", 10, nil))\n\taddr, err = blf.GetInstance(context.Background(), req)\n\tassert.Assert(t, err == nil, err)\n\tassert.Assert(t, addr.Address().String() == \"127.0.0.1:8888\")\n\ttime.Sleep(2 * opts.RefreshInterval)\n\taddr, err = blf.GetInstance(context.Background(), req)\n\tassert.Assert(t, err == nil, err)\n\tassert.Assert(t, addr.Address().String() == \"127.0.0.1:8889\")\n}\n\nfunc TestBalancerExpires(t *testing.T) {\n\tn := int32(1000)\n\tr := &discovery.SynthesizedResolver{\n\t\tTargetFunc: func(ctx context.Context, target *discovery.TargetInfo) string {\n\t\t\treturn target.Host\n\t\t},\n\t\tResolveFunc: func(ctx context.Context, key string) (discovery.Result, error) {\n\t\t\tins := discovery.NewInstance(\"tcp\", \"127.0.0.1:\"+strconv.Itoa(int(atomic.AddInt32(&n, 1))), 10, nil)\n\t\t\treturn discovery.Result{CacheKey: \"svc1\", Instances: []discovery.Instance{ins}}, nil\n\t\t},\n\t\tNameFunc: func() string { return t.Name() },\n\t}\n\topts := DefaultLbOpts\n\topts.ExpireInterval = 30 * time.Millisecond\n\tblf := NewBalancerFactory(Config{\n\t\tBalancer: NewWeightedBalancer(),\n\t\tLbOpts:   opts,\n\t\tResolver: r,\n\t})\n\treq := &protocol.Request{}\n\treq.SetHost(\"svc1\")\n\taddr1, err := blf.GetInstance(context.Background(), req)\n\tassert.Assert(t, err == nil, err)\n\taddr2, err := blf.GetInstance(context.Background(), req)\n\tassert.Assert(t, err == nil, err)\n\tassert.Assert(t, addr1.Address().String() == addr2.Address().String())\n\ttime.Sleep(3 * opts.ExpireInterval)\n\taddr3, err := blf.GetInstance(context.Background(), req)\n\tassert.Assert(t, err == nil, err)\n\tassert.Assert(t, addr3.Address().String() != addr2.Address().String())\n}\n\nfunc TestCacheKey(t *testing.T) {\n\tuniqueKey := cacheKey(\"hello\", \"world\", Options{RefreshInterval: 15 * time.Second, ExpireInterval: 5 * time.Minute})\n\tassert.Assert(t, uniqueKey == \"hello|world|{15s 5m0s}\")\n}\n\ntype mockLoadbalancer struct {\n\trebalanceFunc func(ch discovery.Result)\n\tdeleteFunc    func(key string)\n\tpickFunc      func(discovery.Result) discovery.Instance\n\tnameFunc      func() string\n}\n\n// Rebalance implements the Loadbalancer interface.\nfunc (m mockLoadbalancer) Rebalance(ch discovery.Result) {\n\tif m.rebalanceFunc != nil {\n\t\tm.rebalanceFunc(ch)\n\t}\n}\n\n// Delete implements the Loadbalancer interface.\nfunc (m mockLoadbalancer) Delete(ch string) {\n\tif m.deleteFunc != nil {\n\t\tm.deleteFunc(ch)\n\t}\n}\n\n// Name implements the Loadbalancer interface.\nfunc (m mockLoadbalancer) Name() string {\n\tif m.nameFunc != nil {\n\t\treturn m.nameFunc()\n\t}\n\treturn \"\"\n}\n\n// Pick implements the Loadbalancer interface.\nfunc (m mockLoadbalancer) Pick(d discovery.Result) discovery.Instance {\n\tif m.pickFunc != nil {\n\t\treturn m.pickFunc(d)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/app/client/loadbalance/loadbalance.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage loadbalance\n\nimport (\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/discovery\"\n)\n\n// Loadbalancer picks instance for the given service discovery result.\ntype Loadbalancer interface {\n\t// Pick is used to select an instance according to discovery result\n\tPick(discovery.Result) discovery.Instance\n\n\t// Rebalance is used to refresh the cache of load balance's information\n\tRebalance(discovery.Result)\n\n\t// Delete is used to delete the cache of load balance's information when it is expired\n\tDelete(string)\n\n\t// Name returns the name of the Loadbalancer.\n\tName() string\n}\n\nconst (\n\tDefaultRefreshInterval = 5 * time.Second\n\tDefaultExpireInterval  = 15 * time.Second\n)\n\nvar DefaultLbOpts = Options{\n\tRefreshInterval: DefaultRefreshInterval,\n\tExpireInterval:  DefaultExpireInterval,\n}\n\n// Options for LoadBalance option\ntype Options struct {\n\t// refresh discovery result timely\n\tRefreshInterval time.Duration\n\n\t// Balancer expire check interval\n\t// we need remove idle Balancers for resource saving\n\tExpireInterval time.Duration\n}\n\n// Check checks option's param\nfunc (v *Options) Check() {\n\tif v.RefreshInterval <= 0 {\n\t\tv.RefreshInterval = DefaultRefreshInterval\n\t}\n\tif v.ExpireInterval <= 0 {\n\t\tv.ExpireInterval = DefaultExpireInterval\n\t}\n}\n"
  },
  {
    "path": "pkg/app/client/loadbalance/weight_random.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage loadbalance\n\nimport (\n\t\"sync\"\n\n\t\"github.com/bytedance/gopkg/lang/fastrand\"\n\t\"github.com/cloudwego/hertz/pkg/app/client/discovery\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"golang.org/x/sync/singleflight\"\n)\n\ntype weightedBalancer struct {\n\tcachedWeightInfo sync.Map\n\tsfg              singleflight.Group\n}\n\ntype weightInfo struct {\n\tinstances []discovery.Instance\n\tentries   []int\n\tweightSum int\n}\n\n// NewWeightedBalancer creates a loadbalancer using weighted-random algorithm.\nfunc NewWeightedBalancer() Loadbalancer {\n\tlb := &weightedBalancer{}\n\treturn lb\n}\n\nfunc (wb *weightedBalancer) calcWeightInfo(e discovery.Result) *weightInfo {\n\tw := &weightInfo{\n\t\tinstances: make([]discovery.Instance, len(e.Instances)),\n\t\tweightSum: 0,\n\t\tentries:   make([]int, len(e.Instances)),\n\t}\n\n\tvar cnt int\n\n\tfor idx := range e.Instances {\n\t\tweight := e.Instances[idx].Weight()\n\t\tif weight > 0 {\n\t\t\tw.instances[cnt] = e.Instances[idx]\n\t\t\tw.entries[cnt] = weight\n\t\t\tw.weightSum += weight\n\t\t\tcnt++\n\t\t} else {\n\t\t\thlog.SystemLogger().Warnf(\"Invalid weight=%d on instance address=%s\", weight, e.Instances[idx].Address())\n\t\t}\n\t}\n\n\tw.instances = w.instances[:cnt]\n\n\treturn w\n}\n\n// Pick implements the Loadbalancer interface.\nfunc (wb *weightedBalancer) Pick(e discovery.Result) discovery.Instance {\n\twi, ok := wb.cachedWeightInfo.Load(e.CacheKey)\n\tif !ok {\n\t\twi, _, _ = wb.sfg.Do(e.CacheKey, func() (interface{}, error) {\n\t\t\treturn wb.calcWeightInfo(e), nil\n\t\t})\n\t\twb.cachedWeightInfo.Store(e.CacheKey, wi)\n\t}\n\n\tw := wi.(*weightInfo)\n\tif w.weightSum <= 0 {\n\t\treturn nil\n\t}\n\n\tweight := fastrand.Intn(w.weightSum)\n\tfor i := 0; i < len(w.instances); i++ {\n\t\tweight -= w.entries[i]\n\t\tif weight < 0 {\n\t\t\treturn w.instances[i]\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Rebalance implements the Loadbalancer interface.\nfunc (wb *weightedBalancer) Rebalance(e discovery.Result) {\n\twb.cachedWeightInfo.Store(e.CacheKey, wb.calcWeightInfo(e))\n}\n\n// Delete implements the Loadbalancer interface.\nfunc (wb *weightedBalancer) Delete(cacheKey string) {\n\twb.cachedWeightInfo.Delete(cacheKey)\n}\n\nfunc (wb *weightedBalancer) Name() string {\n\treturn \"weight_random\"\n}\n"
  },
  {
    "path": "pkg/app/client/loadbalance/weight_random_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage loadbalance\n\nimport (\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/discovery\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestWeightedBalancer(t *testing.T) {\n\tbalancer := NewWeightedBalancer()\n\t// nil\n\tins := balancer.Pick(discovery.Result{})\n\tassert.DeepEqual(t, ins, nil)\n\n\t// empty instance\n\te := discovery.Result{\n\t\tInstances: make([]discovery.Instance, 0),\n\t\tCacheKey:  \"a\",\n\t}\n\tbalancer.Rebalance(e)\n\tins = balancer.Pick(e)\n\tassert.DeepEqual(t, ins, nil)\n\n\t// one instance\n\tinsList := []discovery.Instance{\n\t\tdiscovery.NewInstance(\"tcp\", \"127.0.0.1:8888\", 20, nil),\n\t}\n\te = discovery.Result{\n\t\tInstances: insList,\n\t\tCacheKey:  \"b\",\n\t}\n\tbalancer.Rebalance(e)\n\tfor i := 0; i < 100; i++ {\n\t\tins = balancer.Pick(e)\n\t\tassert.DeepEqual(t, ins.Weight(), 20)\n\t}\n\n\t// multi instances, weightSum > 0\n\tinsList = []discovery.Instance{\n\t\tdiscovery.NewInstance(\"tcp\", \"127.0.0.1:8881\", 100, nil),\n\t\tdiscovery.NewInstance(\"tcp\", \"127.0.0.1:8882\", 200, nil),\n\t\tdiscovery.NewInstance(\"tcp\", \"127.0.0.1:8883\", 300, nil),\n\t\tdiscovery.NewInstance(\"tcp\", \"127.0.0.1:8884\", 400, nil),\n\t\tdiscovery.NewInstance(\"tcp\", \"127.0.0.1:8885\", 500, nil),\n\t}\n\n\tvar weightSum int\n\tfor _, ins := range insList {\n\t\tweight := ins.Weight()\n\t\tweightSum += weight\n\t}\n\n\tn := 1000000\n\tpickedStat := map[int]int{}\n\te = discovery.Result{\n\t\tInstances: insList,\n\t\tCacheKey:  \"c\",\n\t}\n\tbalancer.Rebalance(e)\n\tfor i := 0; i < n; i++ {\n\t\tins = balancer.Pick(e)\n\t\tweight := ins.Weight()\n\t\tif pickedCnt, ok := pickedStat[weight]; ok {\n\t\t\tpickedStat[weight] = pickedCnt + 1\n\t\t} else {\n\t\t\tpickedStat[weight] = 1\n\t\t}\n\t}\n\n\tfor _, ins := range insList {\n\t\tweight := ins.Weight()\n\t\texpect := float64(weight) / float64(weightSum) * float64(n)\n\t\tactual := float64(pickedStat[weight])\n\t\tdelta := math.Abs(expect - actual)\n\t\tassert.DeepEqual(t, true, delta/expect < 0.05)\n\t}\n\n\t// have instances that weight < 0\n\tinsList = []discovery.Instance{\n\t\tdiscovery.NewInstance(\"tcp\", \"127.0.0.1:8881\", 10, nil),\n\t\tdiscovery.NewInstance(\"tcp\", \"127.0.0.1:8882\", -10, nil),\n\t}\n\te = discovery.Result{\n\t\tInstances: insList,\n\t\tCacheKey:  \"d\",\n\t}\n\tbalancer.Rebalance(e)\n\tfor i := 0; i < 1000; i++ {\n\t\tins = balancer.Pick(e)\n\t\tassert.DeepEqual(t, 10, ins.Weight())\n\t}\n}\n"
  },
  {
    "path": "pkg/app/client/middleware.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage client\n\nimport (\n\t\"context\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\n// Endpoint represent one method for calling from remote.\ntype Endpoint func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error)\n\n// Middleware deal with input Endpoint and output Endpoint.\ntype Middleware func(Endpoint) Endpoint\n\n// Chain connect middlewares into one middleware.\nfunc chain(mws ...Middleware) Middleware {\n\treturn func(next Endpoint) Endpoint {\n\t\tfor i := len(mws) - 1; i >= 0; i-- {\n\t\t\tnext = mws[i](next)\n\t\t}\n\t\treturn next\n\t}\n}\n"
  },
  {
    "path": "pkg/app/client/middleware_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage client\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\nvar (\n\tbiz       = \"Biz\"\n\tbeforeMW0 = \"BeforeMiddleware0\"\n\tafterMW0  = \"AfterMiddleware0\"\n\tbeforeMW1 = \"BeforeMiddleware1\"\n\tafterMW1  = \"AfterMiddleware1\"\n)\n\nfunc invoke(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\treq.BodyBuffer().WriteString(biz)\n\treturn nil\n}\n\nfunc mockMW0(next Endpoint) Endpoint {\n\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\treq.BodyBuffer().WriteString(beforeMW0)\n\t\terr = next(ctx, req, resp)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treq.BodyBuffer().WriteString(afterMW0)\n\t\treturn nil\n\t}\n}\n\nfunc mockMW1(next Endpoint) Endpoint {\n\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\treq.BodyBuffer().WriteString(beforeMW1)\n\t\terr = next(ctx, req, resp)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treq.BodyBuffer().WriteString(afterMW1)\n\t\treturn nil\n\t}\n}\n\nfunc TestChain(t *testing.T) {\n\tmws := chain(mockMW0, mockMW1)\n\treq := protocol.AcquireRequest()\n\tmws(invoke)(context.Background(), req, nil)\n\tfinal := beforeMW0 + beforeMW1 + biz + afterMW1 + afterMW0\n\tif req.BodyBuffer().String() != final {\n\t\tt.Errorf(\"unexpected %#v, expected %#v\", req.BodyBuffer().String(), final)\n\t}\n}\n"
  },
  {
    "path": "pkg/app/client/option.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage client\n\nimport (\n\t\"crypto/tls\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/retry\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/dialer\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\n// WithDialTimeout sets dial timeout.\nfunc WithDialTimeout(dialTimeout time.Duration) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.DialTimeout = dialTimeout\n\t}}\n}\n\n// WithMaxConnsPerHost sets maximum number of connections per host which may be established.\nfunc WithMaxConnsPerHost(mc int) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.MaxConnsPerHost = mc\n\t}}\n}\n\n// WithMaxIdleConnDuration sets max idle connection duration, idle keep-alive connections are closed after this duration.\nfunc WithMaxIdleConnDuration(t time.Duration) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.MaxIdleConnDuration = t\n\t}}\n}\n\n// WithMaxConnDuration sets max connection duration, keep-alive connections are closed after this duration.\nfunc WithMaxConnDuration(t time.Duration) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.MaxConnDuration = t\n\t}}\n}\n\n// WithMaxConnWaitTimeout sets maximum duration for waiting for a free connection.\nfunc WithMaxConnWaitTimeout(t time.Duration) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.MaxConnWaitTimeout = t\n\t}}\n}\n\n// WithKeepAlive determines whether use keep-alive connection.\nfunc WithKeepAlive(b bool) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.KeepAlive = b\n\t}}\n}\n\n// WithClientReadTimeout sets maximum duration for full response reading (including body).\nfunc WithClientReadTimeout(t time.Duration) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.ReadTimeout = t\n\t}}\n}\n\n// WithTLSConfig sets tlsConfig to create a tls connection.\nfunc WithTLSConfig(cfg *tls.Config) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.TLSConfig = cfg\n\t\to.Dialer = standard.NewDialer()\n\t}}\n}\n\n// WithDialer sets the specific dialer.\nfunc WithDialer(d network.Dialer) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.Dialer = d\n\t}}\n}\n\n// WithResponseBodyStream is used to determine whether read body in stream or not.\nfunc WithResponseBodyStream(b bool) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.ResponseBodyStream = b\n\t}}\n}\n\n// WithHostClientConfigHook is used to set the function hook for re-configure the host client.\nfunc WithHostClientConfigHook(h func(hc interface{}) error) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.HostClientConfigHook = h\n\t}}\n}\n\n// WithDisableHeaderNamesNormalizing is used to set whether disable header names normalizing.\nfunc WithDisableHeaderNamesNormalizing(disable bool) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.DisableHeaderNamesNormalizing = disable\n\t}}\n}\n\n// WithName sets client name which used in User-Agent Header.\nfunc WithName(name string) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.Name = name\n\t}}\n}\n\n// WithNoDefaultUserAgentHeader sets whether no default User-Agent header.\nfunc WithNoDefaultUserAgentHeader(isNoDefaultUserAgentHeader bool) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.NoDefaultUserAgentHeader = isNoDefaultUserAgentHeader\n\t}}\n}\n\n// WithDisablePathNormalizing sets whether disable path normalizing.\nfunc WithDisablePathNormalizing(isDisablePathNormalizing bool) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.DisablePathNormalizing = isDisablePathNormalizing\n\t}}\n}\n\nfunc WithRetryConfig(opts ...retry.Option) config.ClientOption {\n\tretryCfg := &retry.Config{\n\t\tMaxAttemptTimes: consts.DefaultMaxRetryTimes,\n\t\tDelay:           1 * time.Millisecond,\n\t\tMaxDelay:        100 * time.Millisecond,\n\t\tMaxJitter:       20 * time.Millisecond,\n\t\tDelayPolicy:     retry.CombineDelay(retry.DefaultDelayPolicy),\n\t}\n\tretryCfg.Apply(opts)\n\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.RetryConfig = retryCfg\n\t}}\n}\n\n// WithWriteTimeout sets write timeout.\nfunc WithWriteTimeout(t time.Duration) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.WriteTimeout = t\n\t}}\n}\n\n// WithConnStateObserve sets the connection state observation function.\n// The first param is used to set hostclient state func.\n// The second param is used to set observation interval, default value is 5 seconds.\n// Warn: Do not start go routine in HostClientStateFunc.\nfunc WithConnStateObserve(hs config.HostClientStateFunc, interval ...time.Duration) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\to.HostClientStateObserve = hs\n\t\tif len(interval) > 0 {\n\t\t\to.ObservationInterval = interval[0]\n\t\t}\n\t}}\n}\n\n// WithDialFunc is used to set dialer function.\n// Note: WithDialFunc will overwrite custom dialer.\nfunc WithDialFunc(f network.DialFunc, dialers ...network.Dialer) config.ClientOption {\n\treturn config.ClientOption{F: func(o *config.ClientOptions) {\n\t\td := dialer.DefaultDialer()\n\t\tif len(dialers) != 0 {\n\t\t\td = dialers[0]\n\t\t}\n\t\to.Dialer = newCustomDialerWithDialFunc(d, f)\n\t}}\n}\n\n// customDialer set customDialerFunc and params to set dailFunc\ntype customDialer struct {\n\tnetwork.Dialer\n\tdialFunc network.DialFunc\n}\n\nfunc (m *customDialer) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {\n\tif m.dialFunc != nil {\n\t\treturn m.dialFunc(address)\n\t}\n\treturn m.Dialer.DialConnection(network, address, timeout, tlsConfig)\n}\n\nfunc newCustomDialerWithDialFunc(dialer network.Dialer, dialFunc network.DialFunc) network.Dialer {\n\treturn &customDialer{\n\t\tDialer:   dialer,\n\t\tdialFunc: dialFunc,\n\t}\n}\n"
  },
  {
    "path": "pkg/app/client/option_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage client\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/retry\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestClientOptions(t *testing.T) {\n\t// default\n\topt := config.NewClientOptions(nil)\n\tassert.DeepEqual(t, 0, opt.MaxConnsPerHost)\n\tassert.DeepEqual(t, consts.DefaultDialTimeout, opt.DialTimeout)\n\tassert.DeepEqual(t, consts.DefaultMaxIdleConnDuration, opt.MaxIdleConnDuration)\n\tassert.DeepEqual(t, true, opt.KeepAlive)\n\tassert.DeepEqual(t, 5*time.Second, opt.ObservationInterval)\n\n\t// config\n\topt = config.NewClientOptions([]config.ClientOption{\n\t\tWithDialTimeout(100 * time.Millisecond),\n\t\tWithMaxConnsPerHost(128),\n\t\tWithMaxIdleConnDuration(5 * time.Second),\n\t\tWithMaxConnDuration(10 * time.Second),\n\t\tWithMaxConnWaitTimeout(5 * time.Second),\n\t\tWithKeepAlive(false),\n\t\tWithClientReadTimeout(1 * time.Second),\n\t\tWithResponseBodyStream(true),\n\t\tWithRetryConfig(\n\t\t\tretry.WithMaxAttemptTimes(2),\n\t\t\tretry.WithInitDelay(100*time.Millisecond),\n\t\t\tretry.WithMaxDelay(5*time.Second),\n\t\t\tretry.WithMaxJitter(1*time.Second),\n\t\t\tretry.WithDelayPolicy(retry.CombineDelay(retry.DefaultDelayPolicy, retry.FixedDelayPolicy, retry.BackOffDelayPolicy)),\n\t\t),\n\t\tWithWriteTimeout(time.Second),\n\t\tWithConnStateObserve(nil, time.Second),\n\t})\n\tassert.DeepEqual(t, 100*time.Millisecond, opt.DialTimeout)\n\tassert.DeepEqual(t, 128, opt.MaxConnsPerHost)\n\tassert.DeepEqual(t, 5*time.Second, opt.MaxIdleConnDuration)\n\tassert.DeepEqual(t, 10*time.Second, opt.MaxConnDuration)\n\tassert.DeepEqual(t, 5*time.Second, opt.MaxConnWaitTimeout)\n\tassert.DeepEqual(t, false, opt.KeepAlive)\n\tassert.DeepEqual(t, 1*time.Second, opt.ReadTimeout)\n\tassert.DeepEqual(t, 1*time.Second, opt.WriteTimeout)\n\tassert.DeepEqual(t, true, opt.ResponseBodyStream)\n\tassert.DeepEqual(t, uint(2), opt.RetryConfig.MaxAttemptTimes)\n\tassert.DeepEqual(t, 100*time.Millisecond, opt.RetryConfig.Delay)\n\tassert.DeepEqual(t, 5*time.Second, opt.RetryConfig.MaxDelay)\n\tassert.DeepEqual(t, 1*time.Second, opt.RetryConfig.MaxJitter)\n\tassert.DeepEqual(t, 1*time.Second, opt.ObservationInterval)\n\tfor i := 0; i < 100; i++ {\n\t\tassert.DeepEqual(t, opt.RetryConfig.DelayPolicy(uint(i), nil, opt.RetryConfig), retry.CombineDelay(retry.DefaultDelayPolicy, retry.FixedDelayPolicy, retry.BackOffDelayPolicy)(uint(i), nil, opt.RetryConfig))\n\t}\n}\n"
  },
  {
    "path": "pkg/app/client/retry/option.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage retry\n\nimport \"time\"\n\n// Option is the only struct that can be used to set Retry Config.\ntype Option struct {\n\tF func(o *Config)\n}\n\n// WithMaxAttemptTimes set WithMaxAttemptTimes , including the first call.\nfunc WithMaxAttemptTimes(maxAttemptTimes uint) Option {\n\treturn Option{F: func(o *Config) {\n\t\to.MaxAttemptTimes = maxAttemptTimes\n\t}}\n}\n\n// WithInitDelay set init Delay.\nfunc WithInitDelay(delay time.Duration) Option {\n\treturn Option{F: func(o *Config) {\n\t\to.Delay = delay\n\t}}\n}\n\n// WithMaxDelay set MaxDelay.\nfunc WithMaxDelay(maxDelay time.Duration) Option {\n\treturn Option{F: func(o *Config) {\n\t\to.MaxDelay = maxDelay\n\t}}\n}\n\n// WithDelayPolicy set DelayPolicy.\nfunc WithDelayPolicy(delayPolicy DelayPolicyFunc) Option {\n\treturn Option{F: func(o *Config) {\n\t\to.DelayPolicy = delayPolicy\n\t}}\n}\n\n// WithMaxJitter set MaxJitter.\nfunc WithMaxJitter(maxJitter time.Duration) Option {\n\treturn Option{F: func(o *Config) {\n\t\to.MaxJitter = maxJitter\n\t}}\n}\n"
  },
  {
    "path": "pkg/app/client/retry/retry.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage retry\n\nimport (\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/bytedance/gopkg/lang/fastrand\"\n)\n\n// Config All configurations related to retry\ntype Config struct {\n\t// The maximum number of call attempt times, including the initial call\n\tMaxAttemptTimes uint\n\n\t// Initial retry delay time\n\tDelay time.Duration\n\n\t// Maximum retry delay time. When the retry time increases beyond this time,\n\t// this configuration will limit the upper limit of waiting time\n\tMaxDelay time.Duration\n\n\t// The maximum jitter time, which takes effect when the delay policy is configured as RandomDelay\n\tMaxJitter time.Duration\n\n\t// Delay strategy, which can combine multiple delay strategies. such as CombineDelay(BackOffDelayPolicy, RandomDelayPolicy) or BackOffDelayPolicy,etc\n\tDelayPolicy DelayPolicyFunc\n}\n\nfunc (o *Config) Apply(opts []Option) {\n\tfor _, op := range opts {\n\t\top.F(o)\n\t}\n}\n\n// DelayPolicyFunc signature of delay policy function\n// is called to return the delay of retry\ntype DelayPolicyFunc func(attempts uint, err error, retryConfig *Config) time.Duration\n\n// DefaultDelayPolicy is a DelayPolicyFunc which keep 0 delay in all iterations\nfunc DefaultDelayPolicy(_ uint, _ error, _ *Config) time.Duration {\n\treturn 0 * time.Millisecond\n}\n\n// FixedDelayPolicy is a DelayPolicyFunc which keeps delay the same through all iterations\nfunc FixedDelayPolicy(_ uint, _ error, retryConfig *Config) time.Duration {\n\treturn retryConfig.Delay\n}\n\n// RandomDelayPolicy is a DelayPolicyFunc which picks a random delay up to RetryConfig.MaxJitter, if the retryConfig.MaxJitter less than or equal to 0, the final delay is 0\nfunc RandomDelayPolicy(_ uint, _ error, retryConfig *Config) time.Duration {\n\tif retryConfig.MaxJitter <= 0 {\n\t\treturn 0 * time.Millisecond\n\t}\n\treturn time.Duration(fastrand.Int63n(int64(retryConfig.MaxJitter)))\n}\n\n// BackOffDelayPolicy is a DelayPolicyFunc which exponentially increases delay between consecutive retries, if the retryConfig.Delay less than or equal to 0, the final delay is 0\nfunc BackOffDelayPolicy(attempts uint, _ error, retryConfig *Config) time.Duration {\n\tif retryConfig.Delay <= 0 {\n\t\treturn 0 * time.Millisecond\n\t}\n\t// 1 << 63 would overflow signed int64 (time.Duration), thus 62.\n\tconst max uint = 62\n\tif attempts > max {\n\t\tattempts = max\n\t}\n\n\treturn retryConfig.Delay << attempts\n}\n\n// CombineDelay return DelayPolicyFunc, which combines the optional DelayPolicyFunc into a new DelayPolicyFunc\nfunc CombineDelay(delays ...DelayPolicyFunc) DelayPolicyFunc {\n\tconst maxInt64 = uint64(math.MaxInt64)\n\n\treturn func(attempts uint, err error, config *Config) time.Duration {\n\t\tvar total uint64\n\t\tfor _, delay := range delays {\n\t\t\ttotal += uint64(delay(attempts, err, config))\n\t\t\tif total > maxInt64 {\n\t\t\t\ttotal = maxInt64\n\t\t\t}\n\t\t}\n\n\t\treturn time.Duration(total)\n\t}\n}\n\n// Delay generate the delay time required for the current retry config, if the retryConfig.DelayPolicy == nil, the final delay is 0\nfunc Delay(attempts uint, err error, retryConfig *Config) time.Duration {\n\tif retryConfig.DelayPolicy == nil {\n\t\treturn 0 * time.Millisecond\n\t}\n\n\tdelayTime := retryConfig.DelayPolicy(attempts, err, retryConfig)\n\tif retryConfig.MaxDelay > 0 && delayTime > retryConfig.MaxDelay {\n\t\tdelayTime = retryConfig.MaxDelay\n\t}\n\treturn delayTime\n}\n"
  },
  {
    "path": "pkg/app/client/retry/retry_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage retry\n\nimport (\n\t\"math\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestApply(t *testing.T) {\n\tdelayPolicyFunc := func(attempts uint, err error, retryConfig *Config) time.Duration {\n\t\treturn time.Second\n\t}\n\toptions := []Option{}\n\toptions = append(options, WithMaxAttemptTimes(100), WithInitDelay(time.Second),\n\t\tWithMaxDelay(time.Second), WithDelayPolicy(delayPolicyFunc), WithMaxJitter(time.Second))\n\n\tconfig := Config{}\n\tconfig.Apply(options)\n\n\tassert.DeepEqual(t, uint(100), config.MaxAttemptTimes)\n\tassert.DeepEqual(t, time.Second, config.Delay)\n\tassert.DeepEqual(t, time.Second, config.MaxDelay)\n\tassert.DeepEqual(t, time.Second, Delay(0, nil, &config))\n\tassert.DeepEqual(t, time.Second, config.MaxJitter)\n}\n\nfunc TestPolicy(t *testing.T) {\n\tdur := DefaultDelayPolicy(0, nil, nil)\n\tassert.DeepEqual(t, 0*time.Millisecond, dur)\n\n\tconfig := Config{\n\t\tDelay: time.Second,\n\t}\n\tdur = FixedDelayPolicy(0, nil, &config)\n\tassert.DeepEqual(t, time.Second, dur)\n\n\tdur = RandomDelayPolicy(0, nil, &config)\n\tassert.DeepEqual(t, 0*time.Millisecond, dur)\n\tconfig.MaxJitter = time.Second * 1\n\tdur = RandomDelayPolicy(0, nil, &config)\n\tassert.NotEqual(t, time.Second*1, dur)\n\n\tdur = BackOffDelayPolicy(0, nil, &config)\n\tassert.DeepEqual(t, time.Second*1, dur)\n\tconfig.Delay = time.Duration(-1)\n\tdur = BackOffDelayPolicy(0, nil, &config)\n\tassert.DeepEqual(t, time.Second*0, dur)\n\tconfig.Delay = time.Duration(1)\n\tdur = BackOffDelayPolicy(63, nil, &config)\n\tdurExp := config.Delay << 62\n\tassert.DeepEqual(t, durExp, dur)\n\n\tdur = Delay(0, nil, &config)\n\tassert.DeepEqual(t, 0*time.Millisecond, dur)\n\tdelayPolicyFunc := func(attempts uint, err error, retryConfig *Config) time.Duration {\n\t\treturn time.Second\n\t}\n\tconfig.DelayPolicy = delayPolicyFunc\n\tconfig.MaxDelay = time.Second / 2\n\tdur = Delay(0, nil, &config)\n\tassert.DeepEqual(t, config.MaxDelay, dur)\n\n\tdelayPolicyFunc2 := func(attempts uint, err error, retryConfig *Config) time.Duration {\n\t\treturn time.Duration(math.MaxInt64)\n\t}\n\tdelayFunc := CombineDelay(delayPolicyFunc2, delayPolicyFunc)\n\tdur = delayFunc(0, nil, &config)\n\tassert.DeepEqual(t, time.Duration(math.MaxInt64), dur)\n}\n"
  },
  {
    "path": "pkg/app/context.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage app\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/binding\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/render\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\trConsts \"github.com/cloudwego/hertz/pkg/route/consts\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\nvar zeroTCPAddr = &net.TCPAddr{\n\tIP: net.IPv4zero,\n}\n\ntype Handler interface {\n\tServeHTTP(c context.Context, ctx *RequestContext)\n}\n\ntype ClientIP func(ctx *RequestContext) string\n\ntype ClientIPOptions struct {\n\tRemoteIPHeaders []string\n\tTrustedCIDRs    []*net.IPNet\n}\n\nvar defaultTrustedCIDRs = []*net.IPNet{\n\t{ // 0.0.0.0/0 (IPv4)\n\t\tIP:   net.IP{0x0, 0x0, 0x0, 0x0},\n\t\tMask: net.IPMask{0x0, 0x0, 0x0, 0x0},\n\t},\n\t{ // ::/0 (IPv6)\n\t\tIP:   net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},\n\t\tMask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},\n\t},\n}\n\nvar defaultClientIPOptions = ClientIPOptions{\n\tRemoteIPHeaders: []string{\"X-Forwarded-For\", \"X-Real-IP\"},\n\tTrustedCIDRs:    defaultTrustedCIDRs,\n}\n\nvar loopbackIP = net.ParseIP(\"127.0.0.1\")\n\n// ClientIPWithOption used to generate custom ClientIP function and set by engine.SetClientIPFunc\nfunc ClientIPWithOption(opts ClientIPOptions) ClientIP {\n\treturn func(ctx *RequestContext) string {\n\t\tremoteIPStr := \"\"\n\t\ttrustedProxy := false\n\t\tif addr := ctx.RemoteAddr(); strings.HasPrefix(addr.Network(), \"unix\") {\n\t\t\t// unix, unixgram, unixpacket is considered same as \"127.0.0.1\"\n\t\t\tremoteIPStr = addr.String()\n\t\t\ttrustedProxy = isTrustedProxy(opts.TrustedCIDRs, loopbackIP)\n\t\t} else {\n\t\t\th, _, err := net.SplitHostPort(strings.TrimSpace(addr.String()))\n\t\t\tif err != nil {\n\t\t\t\treturn \"\"\n\t\t\t}\n\t\t\tremoteIPStr = h\n\t\t\ttrustedProxy = isTrustedProxy(opts.TrustedCIDRs, net.ParseIP(h))\n\t\t}\n\n\t\tif trustedProxy {\n\t\t\tfor _, headerName := range opts.RemoteIPHeaders {\n\t\t\t\tip, valid := validateHeader(opts.TrustedCIDRs, ctx.Request.Header.Get(headerName))\n\t\t\t\tif valid {\n\t\t\t\t\treturn ip\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn remoteIPStr\n\t}\n}\n\n// isTrustedProxy will check whether the IP address is included in the trusted list according to trustedCIDRs\nfunc isTrustedProxy(trustedCIDRs []*net.IPNet, remoteIP net.IP) bool {\n\tif trustedCIDRs == nil || remoteIP == nil {\n\t\treturn false\n\t}\n\tfor _, cidr := range trustedCIDRs {\n\t\tif cidr.Contains(remoteIP) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// validateHeader will parse X-Real-IP and X-Forwarded-For header and return the Initial client IP address or an untrusted IP address\nfunc validateHeader(trustedCIDRs []*net.IPNet, header string) (clientIP string, valid bool) {\n\tif header == \"\" {\n\t\treturn \"\", false\n\t}\n\titems := strings.Split(header, \",\")\n\tfor i := len(items) - 1; i >= 0; i-- {\n\t\tipStr := strings.TrimSpace(items[i])\n\t\tip := net.ParseIP(ipStr)\n\t\tif ip == nil {\n\t\t\tbreak\n\t\t}\n\n\t\t// X-Forwarded-For is appended by proxy\n\t\t// Check IPs in reverse order and stop when find untrusted proxy\n\t\tif (i == 0) || (!isTrustedProxy(trustedCIDRs, ip)) {\n\t\t\treturn ipStr, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nvar defaultClientIP = ClientIPWithOption(defaultClientIPOptions)\n\n// SetClientIPFunc sets ClientIP function implementation to get ClientIP.\n// Deprecated: Use engine.SetClientIPFunc instead of SetClientIPFunc\nfunc SetClientIPFunc(fn ClientIP) {\n\tdefaultClientIP = fn\n}\n\ntype FormValueFunc func(*RequestContext, string) []byte\n\nvar defaultFormValue = func(ctx *RequestContext, key string) []byte {\n\tv := ctx.QueryArgs().Peek(key)\n\tif len(v) > 0 {\n\t\treturn v\n\t}\n\tv = ctx.PostArgs().Peek(key)\n\tif len(v) > 0 {\n\t\treturn v\n\t}\n\tmf, err := ctx.MultipartForm()\n\tif err == nil && mf.Value != nil {\n\t\tvv := mf.Value[key]\n\t\tif len(vv) > 0 {\n\t\t\treturn []byte(vv[0])\n\t\t}\n\t}\n\treturn nil\n}\n\ntype RequestContext struct {\n\tconn     network.Conn\n\tRequest  protocol.Request\n\tResponse protocol.Response\n\n\t// Errors is a list of errors attached to all the handlers/middlewares who used this context.\n\tErrors errors.ErrorChain\n\n\tParams     param.Params\n\thandlers   HandlersChain\n\tfullPath   string\n\tindex      int8\n\tHTMLRender render.HTMLRender\n\n\t// This mutex protect Keys map.\n\tmu sync.RWMutex\n\n\t// Keys is a key/value pair exclusively for the context of each request.\n\tKeys map[string]interface{}\n\n\thijackHandler HijackHandler\n\n\tfinishedMu sync.Mutex\n\n\t// finished means the request end.\n\tfinished chan struct{}\n\n\t// traceInfo defines the trace information.\n\ttraceInfo traceinfo.TraceInfo\n\n\t// enableTrace defines whether enable trace.\n\tenableTrace bool\n\n\t// clientIPFunc get client ip by use custom function.\n\tclientIPFunc ClientIP\n\n\t// clientIPFunc get form value by use custom function.\n\tformValueFunc FormValueFunc\n\n\tbinder binding.Binder\n\texiled bool\n}\n\n// Exile marks this RequestContext as not to be recycled.\n// Experimental features: Use with caution, it may have a slight impact on performance.\nfunc (ctx *RequestContext) Exile() {\n\tctx.exiled = true\n}\n\nfunc (ctx *RequestContext) IsExiled() bool {\n\treturn ctx.exiled\n}\n\n// Flush is the shortcut for ctx.Response.GetHijackWriter().Flush().\n// Will return nil if the response writer is not hijacked.\nfunc (ctx *RequestContext) Flush() error {\n\tif ctx.Response.GetHijackWriter() == nil {\n\t\treturn nil\n\t}\n\treturn ctx.Response.GetHijackWriter().Flush()\n}\n\nfunc (ctx *RequestContext) SetClientIPFunc(f ClientIP) {\n\tctx.clientIPFunc = f\n}\n\nfunc (ctx *RequestContext) SetFormValueFunc(f FormValueFunc) {\n\tctx.formValueFunc = f\n}\n\nfunc (ctx *RequestContext) SetBinder(binder binding.Binder) {\n\tctx.binder = binder\n}\n\nfunc (ctx *RequestContext) GetTraceInfo() traceinfo.TraceInfo {\n\treturn ctx.traceInfo\n}\n\nfunc (ctx *RequestContext) SetTraceInfo(t traceinfo.TraceInfo) {\n\tctx.traceInfo = t\n}\n\nfunc (ctx *RequestContext) IsEnableTrace() bool {\n\treturn ctx.enableTrace\n}\n\n// SetEnableTrace sets whether enable trace.\n//\n// NOTE: biz handler must not modify this value, otherwise, it may panic.\nfunc (ctx *RequestContext) SetEnableTrace(enable bool) {\n\tctx.enableTrace = enable\n}\n\n// NewContext make a pure RequestContext without any http request/response information\n//\n// Set the Request filed before use it for handlers\nfunc NewContext(maxParams uint16) *RequestContext {\n\tv := make(param.Params, 0, maxParams)\n\tctx := &RequestContext{Params: v, index: -1}\n\treturn ctx\n}\n\n// Loop fn for every k/v in Keys\nfunc (ctx *RequestContext) ForEachKey(fn func(k string, v interface{})) {\n\tctx.mu.RLock()\n\tfor key, val := range ctx.Keys {\n\t\tfn(key, val)\n\t}\n\tctx.mu.RUnlock()\n}\n\nfunc (ctx *RequestContext) SetConn(c network.Conn) {\n\tctx.conn = c\n}\n\nfunc (ctx *RequestContext) GetConn() network.Conn {\n\treturn ctx.conn\n}\n\nfunc (ctx *RequestContext) SetHijackHandler(h HijackHandler) {\n\tctx.hijackHandler = h\n}\n\nfunc (ctx *RequestContext) GetHijackHandler() HijackHandler {\n\treturn ctx.hijackHandler\n}\n\nfunc (ctx *RequestContext) GetReader() network.Reader {\n\treturn ctx.conn\n}\n\nfunc (ctx *RequestContext) GetWriter() network.Writer {\n\treturn ctx.conn\n}\n\nfunc (ctx *RequestContext) GetIndex() int8 {\n\treturn ctx.index\n}\n\n// SetIndex reset the handler's execution index\n// Disclaimer: You can loop yourself to deal with this, use wisely.\nfunc (ctx *RequestContext) SetIndex(index int8) {\n\tctx.index = index\n}\n\ntype HandlerFunc func(c context.Context, ctx *RequestContext)\n\n// HandlersChain defines a HandlerFunc array.\ntype HandlersChain []HandlerFunc\n\ntype HandlerNameOperator interface {\n\tSetHandlerName(handler HandlerFunc, name string)\n\tGetHandlerName(handler HandlerFunc) string\n}\n\nfunc SetHandlerNameOperator(o HandlerNameOperator) {\n\tinbuiltHandlerNameOperator = o\n}\n\ntype inbuiltHandlerNameOperatorStruct struct {\n\thandlerNames map[uintptr]string\n}\n\nfunc (o *inbuiltHandlerNameOperatorStruct) SetHandlerName(handler HandlerFunc, name string) {\n\to.handlerNames[getFuncAddr(handler)] = name\n}\n\nfunc (o *inbuiltHandlerNameOperatorStruct) GetHandlerName(handler HandlerFunc) string {\n\treturn o.handlerNames[getFuncAddr(handler)]\n}\n\ntype concurrentHandlerNameOperatorStruct struct {\n\thandlerNames map[uintptr]string\n\tlock         sync.RWMutex\n}\n\nfunc (o *concurrentHandlerNameOperatorStruct) SetHandlerName(handler HandlerFunc, name string) {\n\to.lock.Lock()\n\tdefer o.lock.Unlock()\n\to.handlerNames[getFuncAddr(handler)] = name\n}\n\nfunc (o *concurrentHandlerNameOperatorStruct) GetHandlerName(handler HandlerFunc) string {\n\to.lock.RLock()\n\tdefer o.lock.RUnlock()\n\treturn o.handlerNames[getFuncAddr(handler)]\n}\n\nfunc SetConcurrentHandlerNameOperator() {\n\tSetHandlerNameOperator(&concurrentHandlerNameOperatorStruct{handlerNames: map[uintptr]string{}})\n}\n\nfunc init() {\n\tinbuiltHandlerNameOperator = &inbuiltHandlerNameOperatorStruct{handlerNames: map[uintptr]string{}}\n}\n\nvar inbuiltHandlerNameOperator HandlerNameOperator\n\nfunc SetHandlerName(handler HandlerFunc, name string) {\n\tinbuiltHandlerNameOperator.SetHandlerName(handler, name)\n}\n\nfunc GetHandlerName(handler HandlerFunc) string {\n\treturn inbuiltHandlerNameOperator.GetHandlerName(handler)\n}\n\nfunc getFuncAddr(v interface{}) uintptr {\n\treturn reflect.ValueOf(reflect.ValueOf(v)).Field(1).Pointer()\n}\n\n// HijackHandler must process the hijacked connection c.\n//\n// If KeepHijackedConns is disabled, which is by default,\n// the connection c is automatically closed after returning from HijackHandler.\n//\n// The connection c must not be used after returning from the handler, if KeepHijackedConns is disabled.\n//\n// When KeepHijackedConns enabled, hertz will not Close() the connection,\n// you must do it when you need it. You must not use c in any way after calling Close().\n//\n// network.Connection provide two options of io: net.Conn and zero-copy read/write\ntype HijackHandler func(c network.Conn)\n\n// Hijack registers the given handler for connection hijacking.\n//\n// The handler is called after returning from RequestHandler\n// and sending http response. The current connection is passed\n// to the handler. The connection is automatically closed after\n// returning from the handler.\n//\n// The server skips calling the handler in the following cases:\n//\n//   - 'Connection: close' header exists in either request or response.\n//   - Unexpected error during response writing to the connection.\n//\n// The server stops processing requests from hijacked connections.\n//\n// Server limits such as Concurrency, ReadTimeout, WriteTimeout, etc.\n// aren't applied to hijacked connections.\n//\n// The handler must not retain references to ctx members.\n//\n// Arbitrary 'Connection: Upgrade' protocols may be implemented\n// with HijackHandler. For instance,\n//\n//   - WebSocket ( https://en.wikipedia.org/wiki/WebSocket )\n//   - HTTP/2.0 ( https://en.wikipedia.org/wiki/HTTP/2 )\nfunc (ctx *RequestContext) Hijack(handler HijackHandler) {\n\tctx.hijackHandler = handler\n}\n\n// Last returns the last handler of the handler chain.\n//\n// Generally speaking, the last handler is the main handler.\nfunc (c HandlersChain) Last() HandlerFunc {\n\tif length := len(c); length > 0 {\n\t\treturn c[length-1]\n\t}\n\treturn nil\n}\n\nfunc (ctx *RequestContext) Finished() <-chan struct{} {\n\tctx.finishedMu.Lock()\n\tif ctx.finished == nil {\n\t\tctx.finished = make(chan struct{})\n\t}\n\tch := ctx.finished\n\tctx.finishedMu.Unlock()\n\treturn ch\n}\n\n// GetRequest returns a copy of Request.\nfunc (ctx *RequestContext) GetRequest() (dst *protocol.Request) {\n\tdst = &protocol.Request{}\n\tctx.Request.CopyTo(dst)\n\treturn\n}\n\n// GetResponse returns a copy of Response.\nfunc (ctx *RequestContext) GetResponse() (dst *protocol.Response) {\n\tdst = &protocol.Response{}\n\tctx.Response.CopyTo(dst)\n\treturn\n}\n\n// Value returns the value associated with this context for key, or nil\n// if no value is associated with key. Successive calls to Value with\n// the same key returns the same result.\n//\n// In case the Key is reset after response, Value() return nil if ctx.Key is nil.\nfunc (ctx *RequestContext) Value(key interface{}) interface{} {\n\t// this ctx has been reset, return nil.\n\tif ctx.Keys == nil {\n\t\treturn nil\n\t}\n\tif keyString, ok := key.(string); ok {\n\t\tval, _ := ctx.Get(keyString)\n\t\treturn val\n\t}\n\treturn nil\n}\n\n// Hijacked returns true after Hijack is called.\nfunc (ctx *RequestContext) Hijacked() bool {\n\treturn ctx.hijackHandler != nil\n}\n\n// SetBodyStream sets response body stream and, optionally body size.\n//\n// bodyStream.Close() is called after finishing reading all body data\n// if it implements io.Closer.\n//\n// If bodySize is >= 0, then bodySize bytes must be provided by bodyStream\n// before returning io.EOF.\n//\n// If bodySize < 0, then bodyStream is read until io.EOF.\n//\n// See also SetBodyStreamWriter.\nfunc (ctx *RequestContext) SetBodyStream(bodyStream io.Reader, bodySize int) {\n\tctx.Response.SetBodyStream(bodyStream, bodySize)\n}\n\n// Host returns requested host.\n//\n// The host is valid until returning from RequestHandler.\nfunc (ctx *RequestContext) Host() []byte {\n\treturn ctx.URI().Host()\n}\n\n// RemoteAddr returns client address for the given request.\n//\n// If address is nil, it will return zeroTCPAddr.\nfunc (ctx *RequestContext) RemoteAddr() net.Addr {\n\tif ctx.conn == nil {\n\t\treturn zeroTCPAddr\n\t}\n\taddr := ctx.conn.RemoteAddr()\n\tif addr == nil {\n\t\treturn zeroTCPAddr\n\t}\n\treturn addr\n}\n\n// WriteString appends s to response body.\nfunc (ctx *RequestContext) WriteString(s string) (int, error) {\n\tctx.Response.AppendBodyString(s)\n\treturn len(s), nil\n}\n\n// SetContentType sets response Content-Type.\nfunc (ctx *RequestContext) SetContentType(contentType string) {\n\tctx.Response.Header.SetContentType(contentType)\n}\n\n// Path returns requested path.\n//\n// The path is valid until returning from RequestHandler.\nfunc (ctx *RequestContext) Path() []byte {\n\treturn ctx.URI().Path()\n}\n\n// NotModified resets response and sets '304 Not Modified' response status code.\nfunc (ctx *RequestContext) NotModified() {\n\tctx.Response.Reset()\n\tctx.SetStatusCode(consts.StatusNotModified)\n}\n\n// IfModifiedSince returns true if lastModified exceeds 'If-Modified-Since'\n// value from the request header.\n//\n// The function returns true also 'If-Modified-Since' request header is missing.\nfunc (ctx *RequestContext) IfModifiedSince(lastModified time.Time) bool {\n\tifModStr := ctx.Request.Header.PeekIfModifiedSinceBytes()\n\tif len(ifModStr) == 0 {\n\t\treturn true\n\t}\n\tifMod, err := bytesconv.ParseHTTPDate(ifModStr)\n\tif err != nil {\n\t\treturn true\n\t}\n\tlastModified = lastModified.Truncate(time.Second)\n\treturn ifMod.Before(lastModified)\n}\n\n// URI returns requested uri.\n//\n// The uri is valid until returning from RequestHandler.\nfunc (ctx *RequestContext) URI() *protocol.URI {\n\treturn ctx.Request.URI()\n}\n\nfunc (ctx *RequestContext) String(code int, format string, values ...interface{}) {\n\tctx.Render(code, render.String{Format: format, Data: values})\n}\n\n// FullPath returns a matched route full path. For not found routes\n// returns an empty string.\n//\n//\trouter.GET(\"/user/:id\", func(c context.Context, ctx *app.RequestContext) {\n//\t    ctx.FullPath() == \"/user/:id\" // true\n//\t})\nfunc (ctx *RequestContext) FullPath() string {\n\treturn ctx.fullPath\n}\n\nfunc (ctx *RequestContext) SetFullPath(p string) {\n\tctx.fullPath = p\n}\n\n// SetStatusCode sets response status code.\nfunc (ctx *RequestContext) SetStatusCode(statusCode int) {\n\tctx.Response.SetStatusCode(statusCode)\n}\n\n// Write writes p into response body.\nfunc (ctx *RequestContext) Write(p []byte) (int, error) {\n\tctx.Response.AppendBody(p)\n\treturn len(p), nil\n}\n\n// File writes the specified file into the body stream in an efficient way.\nfunc (ctx *RequestContext) File(filepath string) {\n\tServeFile(ctx, filepath)\n}\n\nfunc (ctx *RequestContext) FileFromFS(filepath string, fs *FS) {\n\tdefer func(old string) {\n\t\tctx.Request.URI().SetPath(old)\n\t}(string(ctx.Request.URI().Path()))\n\n\tctx.Request.URI().SetPath(filepath)\n\n\tfs.NewRequestHandler()(context.Background(), ctx)\n}\n\n// FileAttachment use an efficient way to write the file to body stream.\n//\n// When client download the file, it will rename the file as filename\nfunc (ctx *RequestContext) FileAttachment(filepath, filename string) {\n\tctx.Response.Header.Set(\"content-disposition\", fmt.Sprintf(\"attachment; filename=\\\"%s\\\"\", filename))\n\tServeFile(ctx, filepath)\n}\n\n// SetBodyString sets response body to the given value.\nfunc (ctx *RequestContext) SetBodyString(body string) {\n\tctx.Response.SetBodyString(body)\n}\n\n// SetContentTypeBytes sets response Content-Type.\n//\n// It is safe modifying contentType buffer after function return.\nfunc (ctx *RequestContext) SetContentTypeBytes(contentType []byte) {\n\tctx.Response.Header.SetContentTypeBytes(contentType)\n}\n\n// FormFile returns the first file for the provided form key.\nfunc (ctx *RequestContext) FormFile(name string) (*multipart.FileHeader, error) {\n\treturn ctx.Request.FormFile(name)\n}\n\n// FormValue returns form value associated with the given key.\n//\n// The value is searched in the following places:\n//\n//   - Query string.\n//   - POST or PUT body.\n//\n// There are more fine-grained methods for obtaining form values:\n//\n//   - QueryArgs for obtaining values from query string.\n//   - PostArgs for obtaining values from POST or PUT body.\n//   - MultipartForm for obtaining values from multipart form.\n//   - FormFile for obtaining uploaded files.\n//\n// The returned value is valid until returning from RequestHandler.\n// Use engine.SetCustomFormValueFunc to change action of FormValue.\nfunc (ctx *RequestContext) FormValue(key string) []byte {\n\tif ctx.formValueFunc != nil {\n\t\treturn ctx.formValueFunc(ctx, key)\n\t}\n\treturn defaultFormValue(ctx, key)\n}\n\nfunc (ctx *RequestContext) multipartFormValue(key string) (string, bool) {\n\tmf, err := ctx.MultipartForm()\n\tif err == nil && mf.Value != nil {\n\t\tvv := mf.Value[key]\n\t\tif len(vv) > 0 {\n\t\t\treturn vv[0], true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc (ctx *RequestContext) multipartFormValueArray(key string) ([]string, bool) {\n\tmf, err := ctx.MultipartForm()\n\tif err == nil && mf.Value != nil {\n\t\tvv := mf.Value[key]\n\t\tif len(vv) > 0 {\n\t\t\treturn vv, true\n\t\t}\n\t}\n\treturn nil, false\n}\n\nfunc (ctx *RequestContext) RequestBodyStream() io.Reader {\n\treturn ctx.Request.BodyStream()\n}\n\n// MultipartForm returns request's multipart form.\n//\n// Returns errNoMultipartForm if request's content-type\n// isn't 'multipart/form-data'.\n//\n// All uploaded temporary files are automatically deleted after\n// returning from RequestHandler. Either move or copy uploaded files\n// into new place if you want retaining them.\n//\n// Use SaveMultipartFile function for permanently saving uploaded file.\n//\n// The returned form is valid until returning from RequestHandler.\n//\n// See also FormFile and FormValue.\nfunc (ctx *RequestContext) MultipartForm() (*multipart.Form, error) {\n\treturn ctx.Request.MultipartForm()\n}\n\n// SaveUploadedFile uploads the form file to specific dst.\nfunc (ctx *RequestContext) SaveUploadedFile(file *multipart.FileHeader, dst string) error {\n\tsrc, err := file.Open()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer src.Close()\n\n\tout, err := os.Create(dst)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer out.Close()\n\n\t_, err = io.Copy(out, src)\n\treturn err\n}\n\n// SetConnectionClose sets 'Connection: close' response header.\nfunc (ctx *RequestContext) SetConnectionClose() {\n\tctx.Response.SetConnectionClose()\n}\n\n// IsGet returns true if request method is GET.\nfunc (ctx *RequestContext) IsGet() bool {\n\treturn ctx.Request.Header.IsGet()\n}\n\n// IsHead returns true if request method is HEAD.\nfunc (ctx *RequestContext) IsHead() bool {\n\treturn ctx.Request.Header.IsHead()\n}\n\n// IsPost returns true if request method is POST.\nfunc (ctx *RequestContext) IsPost() bool {\n\treturn ctx.Request.Header.IsPost()\n}\n\n// Method return request method.\n//\n// Returned value is valid until returning from RequestHandler.\nfunc (ctx *RequestContext) Method() []byte {\n\treturn ctx.Request.Header.Method()\n}\n\n// NotFound resets response and sets '404 Not Found' response status code.\nfunc (ctx *RequestContext) NotFound() {\n\tctx.Response.Reset()\n\tctx.SetStatusCode(consts.StatusNotFound)\n\tctx.SetBodyString(consts.StatusMessage(consts.StatusNotFound))\n}\n\nfunc (ctx *RequestContext) redirect(uri []byte, statusCode int) {\n\tctx.Response.Header.SetCanonical(bytestr.StrLocation, uri)\n\tstatusCode = getRedirectStatusCode(statusCode)\n\tctx.Response.SetStatusCode(statusCode)\n}\n\nfunc getRedirectStatusCode(statusCode int) int {\n\tif statusCode == consts.StatusMovedPermanently || statusCode == consts.StatusFound ||\n\t\tstatusCode == consts.StatusSeeOther || statusCode == consts.StatusTemporaryRedirect ||\n\t\tstatusCode == consts.StatusPermanentRedirect {\n\t\treturn statusCode\n\t}\n\treturn consts.StatusFound\n}\n\n// Copy returns a copy of the current context that can be safely used outside\n// the request's scope.\n//\n// NOTE: If you want to pass requestContext to a goroutine, call this method\n// to get a copy of requestContext.\nfunc (ctx *RequestContext) Copy() *RequestContext {\n\tcp := &RequestContext{\n\t\tconn:   ctx.conn,\n\t\tParams: ctx.Params,\n\t}\n\tctx.Request.CopyTo(&cp.Request)\n\tctx.Response.CopyTo(&cp.Response)\n\tcp.index = rConsts.AbortIndex\n\tcp.handlers = nil\n\tcp.Keys = map[string]interface{}{}\n\tctx.mu.RLock()\n\tfor k, v := range ctx.Keys {\n\t\tcp.Keys[k] = v\n\t}\n\tctx.mu.RUnlock()\n\tparamCopy := make([]param.Param, len(cp.Params))\n\tcopy(paramCopy, cp.Params)\n\tcp.Params = paramCopy\n\tcp.fullPath = ctx.fullPath\n\tcp.clientIPFunc = ctx.clientIPFunc\n\tcp.formValueFunc = ctx.formValueFunc\n\tcp.binder = ctx.binder\n\treturn cp\n}\n\n// Next should be used only inside middleware.\n// It executes the pending handlers in the chain inside the calling handler.\nfunc (ctx *RequestContext) Next(c context.Context) {\n\tctx.index++\n\tfor ctx.index < int8(len(ctx.handlers)) {\n\t\tctx.handlers[ctx.index](c, ctx)\n\t\tctx.index++\n\t}\n}\n\n// Handler returns the main handler.\nfunc (ctx *RequestContext) Handler() HandlerFunc {\n\treturn ctx.handlers.Last()\n}\n\n// Handlers returns the handler chain.\nfunc (ctx *RequestContext) Handlers() HandlersChain {\n\treturn ctx.handlers\n}\n\nfunc (ctx *RequestContext) SetHandlers(hc HandlersChain) {\n\tctx.handlers = hc\n}\n\n// HandlerName returns the main handler's name.\n//\n// For example if the handler is \"handleGetUsers()\", this function will return \"main.handleGetUsers\".\nfunc (ctx *RequestContext) HandlerName() string {\n\treturn utils.NameOfFunction(ctx.handlers.Last())\n}\n\nfunc (ctx *RequestContext) ResetWithoutConn() {\n\tctx.Params = ctx.Params[0:0]\n\tctx.Errors = ctx.Errors[0:0]\n\tctx.handlers = nil\n\tctx.index = -1\n\tctx.fullPath = \"\"\n\tctx.Keys = nil\n\n\tif ctx.finished != nil {\n\t\tclose(ctx.finished)\n\t\tctx.finished = nil\n\t}\n\n\tctx.Request.ResetWithoutConn()\n\tctx.Response.Reset()\n\tif ctx.IsEnableTrace() {\n\t\tctx.traceInfo.Reset()\n\t}\n}\n\n// Reset resets requestContext.\n//\n// NOTE: It is an internal function. You should not use it.\nfunc (ctx *RequestContext) Reset() {\n\tctx.ResetWithoutConn()\n\tctx.conn = nil\n}\n\n// Redirect returns an HTTP redirect to the specific location.\n// Note that this will not stop the current handler.\n// In other words, even if Redirect() is called, the remaining handlers will still be executed and cause unexpected result.\n// So it should call Abort to ensure the remaining handlers of this request will not be called.\n//\n//\tctx.Abort()\n//\treturn\nfunc (ctx *RequestContext) Redirect(statusCode int, uri []byte) {\n\tctx.redirect(uri, statusCode)\n}\n\n// Header is an intelligent shortcut for ctx.Response.Header.Set(key, value).\n// It writes a header in the response.\n// If value == \"\", this method removes the header `ctx.Response.Header.Del(key)`.\nfunc (ctx *RequestContext) Header(key, value string) {\n\tif value == \"\" {\n\t\tctx.Response.Header.Del(key)\n\t\treturn\n\t}\n\tctx.Response.Header.Set(key, value)\n}\n\n// Set is used to store a new key/value pair exclusively for this context.\n// It also lazy initializes  c.Keys if it was not used previously.\nfunc (ctx *RequestContext) Set(key string, value interface{}) {\n\tctx.mu.Lock()\n\tif ctx.Keys == nil {\n\t\tctx.Keys = make(map[string]interface{})\n\t}\n\n\tctx.Keys[key] = value\n\tctx.mu.Unlock()\n}\n\n// Get returns the value for the given key, ie: (value, true).\n// If the value does not exist it returns (nil, false)\nfunc (ctx *RequestContext) Get(key string) (value interface{}, exists bool) {\n\tctx.mu.RLock()\n\tvalue, exists = ctx.Keys[key]\n\tctx.mu.RUnlock()\n\treturn\n}\n\n// MustGet returns the value for the given key if it exists, otherwise it panics.\nfunc (ctx *RequestContext) MustGet(key string) interface{} {\n\tif value, exists := ctx.Get(key); exists {\n\t\treturn value\n\t}\n\tpanic(\"Key \\\"\" + key + \"\\\" does not exist\")\n}\n\n// GetString returns the value associated with the key as a string. Return \"\" when type is error.\nfunc (ctx *RequestContext) GetString(key string) (s string) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\ts, _ = val.(string)\n\t}\n\treturn\n}\n\n// GetBool returns the value associated with the key as a boolean. Return false when type is error.\nfunc (ctx *RequestContext) GetBool(key string) (b bool) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tb, _ = val.(bool)\n\t}\n\treturn\n}\n\n// GetInt returns the value associated with the key as an integer. Return 0 when type is error.\nfunc (ctx *RequestContext) GetInt(key string) (i int) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\ti, _ = val.(int)\n\t}\n\treturn\n}\n\n// GetInt32 returns the value associated with the key as an integer. Return int32(0) when type is error.\nfunc (ctx *RequestContext) GetInt32(key string) (i32 int32) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\ti32, _ = val.(int32)\n\t}\n\treturn\n}\n\n// GetInt64 returns the value associated with the key as an integer. Return int64(0) when type is error.\nfunc (ctx *RequestContext) GetInt64(key string) (i64 int64) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\ti64, _ = val.(int64)\n\t}\n\treturn\n}\n\n// GetUint returns the value associated with the key as an unsigned integer. Return uint(0) when type is error.\nfunc (ctx *RequestContext) GetUint(key string) (ui uint) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tui, _ = val.(uint)\n\t}\n\treturn\n}\n\n// GetUint32 returns the value associated with the key as an unsigned integer. Return uint32(0) when type is error.\nfunc (ctx *RequestContext) GetUint32(key string) (ui32 uint32) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tui32, _ = val.(uint32)\n\t}\n\treturn\n}\n\n// GetUint64 returns the value associated with the key as an unsigned integer. Return uint64(0) when type is error.\nfunc (ctx *RequestContext) GetUint64(key string) (ui64 uint64) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tui64, _ = val.(uint64)\n\t}\n\treturn\n}\n\n// GetFloat32 returns the value associated with the key as a float32. Return float32(0.0) when type is error.\nfunc (ctx *RequestContext) GetFloat32(key string) (f32 float32) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tf32, _ = val.(float32)\n\t}\n\treturn\n}\n\n// GetFloat64 returns the value associated with the key as a float64. Return 0.0 when type is error.\nfunc (ctx *RequestContext) GetFloat64(key string) (f64 float64) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tf64, _ = val.(float64)\n\t}\n\treturn\n}\n\n// GetTime returns the value associated with the key as time. Return time.Time{} when type is error.\nfunc (ctx *RequestContext) GetTime(key string) (t time.Time) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tt, _ = val.(time.Time)\n\t}\n\treturn\n}\n\n// GetDuration returns the value associated with the key as a duration. Return time.Duration(0) when type is error.\nfunc (ctx *RequestContext) GetDuration(key string) (d time.Duration) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\td, _ = val.(time.Duration)\n\t}\n\treturn\n}\n\n// GetStringSlice returns the value associated with the key as a slice of strings.\n//\n// Return []string(nil) when type is error.\nfunc (ctx *RequestContext) GetStringSlice(key string) (ss []string) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tss, _ = val.([]string)\n\t}\n\treturn\n}\n\n// GetStringMap returns the value associated with the key as a map of interfaces.\n//\n// Return map[string]interface{}(nil) when type is error.\nfunc (ctx *RequestContext) GetStringMap(key string) (sm map[string]interface{}) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tsm, _ = val.(map[string]interface{})\n\t}\n\treturn\n}\n\n// GetStringMapString returns the value associated with the key as a map of strings.\n//\n// Return map[string]string(nil) when type is error.\nfunc (ctx *RequestContext) GetStringMapString(key string) (sms map[string]string) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tsms, _ = val.(map[string]string)\n\t}\n\treturn\n}\n\n// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.\n//\n// Return map[string][]string(nil) when type is error.\nfunc (ctx *RequestContext) GetStringMapStringSlice(key string) (smss map[string][]string) {\n\tif val, ok := ctx.Get(key); ok && val != nil {\n\t\tsmss, _ = val.(map[string][]string)\n\t}\n\treturn\n}\n\n// Param returns the value of the URL param.\n// It is a shortcut for c.Params.ByName(key)\n//\n//\trouter.GET(\"/user/:id\", func(c context.Context, ctx *app.RequestContext) {\n//\t    // a GET request to /user/john\n//\t    id := ctx.Param(\"id\") // id == \"john\"\n//\t})\nfunc (ctx *RequestContext) Param(key string) string {\n\treturn ctx.Params.ByName(key)\n}\n\n// Abort prevents pending handlers from being called.\n//\n// Note that this will not stop the current handler.\n// Let's say you have an authorization middleware that validates that the current request is authorized.\n// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers\n// for this request are not called.\nfunc (ctx *RequestContext) Abort() {\n\tctx.index = rConsts.AbortIndex\n}\n\n// AbortWithStatus calls `Abort()` and writes the headers with the specified status code.\n//\n// For example, a failed attempt to authenticate a request could use: context.AbortWithStatus(401).\nfunc (ctx *RequestContext) AbortWithStatus(code int) {\n\tctx.SetStatusCode(code)\n\tctx.Abort()\n}\n\n// AbortWithMsg sets response status code to the given value and sets response body\n// to the given message.\n//\n// Warning: this will reset the response headers and body already set!\nfunc (ctx *RequestContext) AbortWithMsg(msg string, statusCode int) {\n\tctx.Response.Reset()\n\tctx.SetStatusCode(statusCode)\n\tctx.SetContentTypeBytes(bytestr.DefaultContentType)\n\tctx.SetBodyString(msg)\n\tctx.Abort()\n}\n\n// AbortWithStatusJSON calls `Abort()` and then `JSON` internally.\n//\n// This method stops the chain, writes the status code and return a JSON body.\n// It also sets the Content-Type as \"application/json\".\nfunc (ctx *RequestContext) AbortWithStatusJSON(code int, jsonObj interface{}) {\n\tctx.Abort()\n\tctx.JSON(code, jsonObj)\n}\n\n// Render writes the response headers and calls render.Render to render data.\nfunc (ctx *RequestContext) Render(code int, r render.Render) {\n\tctx.SetStatusCode(code)\n\n\tif !bodyAllowedForStatus(code) {\n\t\tr.WriteContentType(&ctx.Response)\n\t\treturn\n\t}\n\n\tif err := r.Render(&ctx.Response); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// ProtoBuf serializes the given struct as ProtoBuf into the response body.\nfunc (ctx *RequestContext) ProtoBuf(code int, obj interface{}) {\n\tctx.Render(code, render.ProtoBuf{Data: obj})\n}\n\n// JSON serializes the given struct as JSON into the response body.\n//\n// It also sets the Content-Type as \"application/json\".\nfunc (ctx *RequestContext) JSON(code int, obj interface{}) {\n\tctx.Render(code, render.JSONRender{Data: obj})\n}\n\n// PureJSON serializes the given struct as JSON into the response body.\n// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.\nfunc (ctx *RequestContext) PureJSON(code int, obj interface{}) {\n\tctx.Render(code, render.PureJSON{Data: obj})\n}\n\n// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.\n// It also sets the Content-Type as \"application/json\".\nfunc (ctx *RequestContext) IndentedJSON(code int, obj interface{}) {\n\tctx.Render(code, render.IndentedJSON{Data: obj})\n}\n\n// HTML renders the HTTP template specified by its file name.\n//\n// It also updates the HTTP code and sets the Content-Type as \"text/html\".\n// See http://golang.org/doc/articles/wiki/\nfunc (ctx *RequestContext) HTML(code int, name string, obj interface{}) {\n\tinstance := ctx.HTMLRender.Instance(name, obj)\n\tctx.Render(code, instance)\n}\n\n// Data writes some data into the body stream and updates the HTTP code.\nfunc (ctx *RequestContext) Data(code int, contentType string, data []byte) {\n\tctx.Render(code, render.Data{\n\t\tContentType: contentType,\n\t\tData:        data,\n\t})\n}\n\n// XML serializes the given struct as XML into the response body.\n//\n// It also sets the Content-Type as \"application/xml\".\nfunc (ctx *RequestContext) XML(code int, obj interface{}) {\n\tctx.Render(code, render.XML{Data: obj})\n}\n\n// AbortWithError calls `AbortWithStatus()` and `Error()` internally.\n//\n// This method stops the chain, writes the status code and pushes the specified error to `c.Errors`.\n// See RequestContext.Error() for more details.\nfunc (ctx *RequestContext) AbortWithError(code int, err error) *errors.Error {\n\tctx.AbortWithStatus(code)\n\treturn ctx.Error(err)\n}\n\n// IsAborted returns true if the current context has aborted.\nfunc (ctx *RequestContext) IsAborted() bool {\n\treturn ctx.index >= rConsts.AbortIndex\n}\n\n// Error attaches an error to the current context. The error is pushed to a list of errors.\n//\n// It's a good idea to call Error for each error that occurred during the resolution of a request.\n// A middleware can be used to collect all the errors and push them to a database together,\n// print a log, or append it in the HTTP response.\n// Error will panic if err is nil.\nfunc (ctx *RequestContext) Error(err error) *errors.Error {\n\tif err == nil {\n\t\tpanic(\"err is nil\")\n\t}\n\n\tparsedError, ok := err.(*errors.Error)\n\tif !ok {\n\t\tparsedError = &errors.Error{\n\t\t\tErr:  err,\n\t\t\tType: errors.ErrorTypePrivate,\n\t\t}\n\t}\n\n\tctx.Errors = append(ctx.Errors, parsedError)\n\treturn parsedError\n}\n\n// ContentType returns the Content-Type header of the request.\nfunc (ctx *RequestContext) ContentType() []byte {\n\treturn ctx.Request.Header.ContentType()\n}\n\n// Cookie returns the value of the request cookie key.\nfunc (ctx *RequestContext) Cookie(key string) []byte {\n\treturn ctx.Request.Header.Cookie(key)\n}\n\n// SetCookie adds a Set-Cookie header to the Response's headers.\n//\n//\tParameter introduce:\n//\tname and value is used to set cookie's name and value, eg. Set-Cookie: name=value\n//\tmaxAge is use to set cookie's expiry date, eg. Set-Cookie: name=value; max-age=1\n//\tpath and domain is used to set the scope of a cookie, eg. Set-Cookie: name=value;domain=localhost; path=/;\n//\tsecure and httpOnly is used to sent cookies securely; eg. Set-Cookie: name=value;HttpOnly; secure;\n//\tsameSite let servers specify whether/when cookies are sent with cross-site requests; eg. Set-Cookie: name=value;HttpOnly; secure; SameSite=Lax;\n//\n//\tFor example:\n//\t1. ctx.SetCookie(\"user\", \"hertz\", 1, \"/\", \"localhost\",protocol.CookieSameSiteLaxMode, true, true)\n//\tadd response header --->  Set-Cookie: user=hertz; max-age=1; domain=localhost; path=/; HttpOnly; secure; SameSite=Lax;\n//\t2. ctx.SetCookie(\"user\", \"hertz\", 10, \"/\", \"localhost\",protocol.CookieSameSiteLaxMode, false, false)\n//\tadd response header --->  Set-Cookie: user=hertz; max-age=10; domain=localhost; path=/; SameSite=Lax;\n//\t3. ctx.SetCookie(\"\", \"hertz\", 10, \"/\", \"localhost\",protocol.CookieSameSiteLaxMode, false, false)\n//\tadd response header --->  Set-Cookie: hertz; max-age=10; domain=localhost; path=/; SameSite=Lax;\n//\t4. ctx.SetCookie(\"user\", \"\", 10, \"/\", \"localhost\",protocol.CookieSameSiteLaxMode, false, false)\n//\tadd response header --->  Set-Cookie: user=; max-age=10; domain=localhost; path=/; SameSite=Lax;\nfunc (ctx *RequestContext) SetCookie(name, value string, maxAge int, path, domain string, sameSite protocol.CookieSameSite, secure, httpOnly bool) {\n\tctx.setCookie(name, value, maxAge, path, domain, sameSite, secure, httpOnly, false)\n}\n\nfunc (ctx *RequestContext) setCookie(name, value string, maxAge int, path, domain string, sameSite protocol.CookieSameSite, secure, httpOnly, partitioned bool) {\n\tif path == \"\" {\n\t\tpath = \"/\"\n\t}\n\tcookie := protocol.AcquireCookie()\n\tdefer protocol.ReleaseCookie(cookie)\n\tcookie.SetKey(name)\n\tcookie.SetValue(url.QueryEscape(value))\n\tcookie.SetMaxAge(maxAge)\n\tcookie.SetPath(path)\n\tcookie.SetDomain(domain)\n\tcookie.SetSecure(secure)\n\tcookie.SetHTTPOnly(httpOnly)\n\tcookie.SetSameSite(sameSite)\n\tcookie.SetPartitioned(partitioned)\n\tctx.Response.Header.SetCookie(cookie)\n}\n\n// SetPartitionedCookie adds a partitioned cookie to the Response's headers.\n// Use protocol.CookieSameSiteNoneMode for cross-site cookies to work.\n//\n// Usage: ctx.SetPartitionedCookie(\"user\", \"name\", 10, \"/\", \"localhost\", protocol.CookieSameSiteNoneMode, true, true)\n//\n// This adds the response header: Set-Cookie: user=name; Max-Age=10; Domain=localhost; Path=/; HttpOnly; Secure; SameSite=None; Partitioned\nfunc (ctx *RequestContext) SetPartitionedCookie(name, value string, maxAge int, path, domain string, sameSite protocol.CookieSameSite, secure, httpOnly bool) {\n\tctx.setCookie(name, value, maxAge, path, domain, sameSite, secure, httpOnly, true)\n}\n\n// UserAgent returns the value of the request user_agent.\nfunc (ctx *RequestContext) UserAgent() []byte {\n\treturn ctx.Request.Header.UserAgent()\n}\n\n// Status sets the HTTP response code.\nfunc (ctx *RequestContext) Status(code int) {\n\tctx.SetStatusCode(code)\n}\n\n// GetHeader returns value from request headers.\nfunc (ctx *RequestContext) GetHeader(key string) []byte {\n\treturn ctx.Request.Header.Peek(key)\n}\n\n// GetRawData returns body data.\nfunc (ctx *RequestContext) GetRawData() []byte {\n\treturn ctx.Request.Body()\n}\n\n// Body returns body data\nfunc (ctx *RequestContext) Body() ([]byte, error) {\n\treturn ctx.Request.BodyE()\n}\n\n// ClientIP attempts to parse the headers in the order of [X-Forwarded-For, X-Real-IP].\n// It calls RemoteIP() under the hood. If it cannot satisfy the requirements,\n// use engine.SetClientIPFunc to inject your own implementation.\nfunc (ctx *RequestContext) ClientIP() string {\n\tif ctx.clientIPFunc != nil {\n\t\treturn ctx.clientIPFunc(ctx)\n\t}\n\treturn defaultClientIP(ctx)\n}\n\n// QueryArgs returns query arguments from RequestURI.\n//\n// It doesn't return POST'ed arguments - use PostArgs() for this.\n// Returned arguments are valid until returning from RequestHandler.\n// See also PostArgs, FormValue and FormFile.\nfunc (ctx *RequestContext) QueryArgs() *protocol.Args {\n\treturn ctx.URI().QueryArgs()\n}\n\n// PostArgs returns POST arguments.\n//\n// It doesn't return query arguments from RequestURI - use QueryArgs for this.\n// Returned arguments are valid until returning from RequestHandler.\n// See also QueryArgs, FormValue and FormFile.\nfunc (ctx *RequestContext) PostArgs() *protocol.Args {\n\treturn ctx.Request.PostArgs()\n}\n\n// Query returns the keyed url query value if it exists, otherwise it returns an empty string `(\"\")`.\n//\n// For example:\n//\n//\t    GET /path?id=1234&name=Manu&value=\n//\t\t   c.Query(\"id\") == \"1234\"\n//\t\t   c.Query(\"name\") == \"Manu\"\n//\t\t   c.Query(\"value\") == \"\"\n//\t\t   c.Query(\"wtf\") == \"\"\nfunc (ctx *RequestContext) Query(key string) string {\n\tvalue, _ := ctx.GetQuery(key)\n\treturn value\n}\n\n// DefaultQuery returns the keyed url query value if it exists,\n// otherwise it returns the specified defaultValue string.\nfunc (ctx *RequestContext) DefaultQuery(key, defaultValue string) string {\n\tif value, ok := ctx.GetQuery(key); ok {\n\t\treturn value\n\t}\n\treturn defaultValue\n}\n\n// GetQuery returns the keyed url query value\n//\n// if it exists `(value, true)` (even when the value is an empty string) will be returned,\n// otherwise it returns `(\"\", false)`.\n// For example:\n//\n//\tGET /?name=Manu&lastname=\n//\t(\"Manu\", true) == c.GetQuery(\"name\")\n//\t(\"\", false) == c.GetQuery(\"id\")\n//\t(\"\", true) == c.GetQuery(\"lastname\")\nfunc (ctx *RequestContext) GetQuery(key string) (string, bool) {\n\treturn ctx.QueryArgs().PeekExists(key)\n}\n\n// PostForm returns the specified key from a POST urlencoded form or multipart form\n// when it exists, otherwise it returns an empty string `(\"\")`.\nfunc (ctx *RequestContext) PostForm(key string) string {\n\tvalue, _ := ctx.GetPostForm(key)\n\treturn value\n}\n\n// PostFormArray returns the specified key from a POST urlencoded form or multipart form\n// when it exists, otherwise it returns an empty array `([])`.\nfunc (ctx *RequestContext) PostFormArray(key string) []string {\n\tvalues, _ := ctx.GetPostFormArray(key)\n\treturn values\n}\n\n// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form\n// when it exists, otherwise it returns the specified defaultValue string.\n//\n// See: PostForm() and GetPostForm() for further information.\nfunc (ctx *RequestContext) DefaultPostForm(key, defaultValue string) string {\n\tif value, ok := ctx.GetPostForm(key); ok {\n\t\treturn value\n\t}\n\treturn defaultValue\n}\n\n// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded\n// form or multipart form when it exists `(value, true)` (even when the value is an empty string),\n// otherwise it returns (\"\", false).\n//\n// For example, during a PATCH request to update the user's email:\n//\n//\t    email=mail@example.com  -->  (\"mail@example.com\", true) := GetPostForm(\"email\") // set email to \"mail@example.com\"\n//\t\t   email=                  -->  (\"\", true) := GetPostForm(\"email\") // set email to \"\"\n//\t                            -->  (\"\", false) := GetPostForm(\"email\") // do nothing with email\nfunc (ctx *RequestContext) GetPostForm(key string) (string, bool) {\n\tif v, exists := ctx.PostArgs().PeekExists(key); exists {\n\t\treturn v, exists\n\t}\n\treturn ctx.multipartFormValue(key)\n}\n\n// GetPostFormArray is like PostFormArray(key). It returns the specified key from a POST urlencoded\n// form or multipart form when it exists `([]string, true)` (even when the value is an empty string),\n// otherwise it returns ([]string(nil), false).\n//\n// For example, during a PATCH request to update the item's tags:\n//\n//\t    tag=tag1 tag=tag2 tag=tag3  -->  ([\"tag1\", \"tag2\", \"tag3\"], true) := GetPostFormArray(\"tags\") // set tags to [\"tag1\", \"tag2\", \"tag3\"]\n//\t\t   tags=                  -->  (nil, true) := GetPostFormArray(\"tags\") // set tags to nil\n//\t                            -->  (nil, false) := GetPostFormArray(\"tags\") // do nothing with tags\nfunc (ctx *RequestContext) GetPostFormArray(key string) ([]string, bool) {\n\tvs := ctx.PostArgs().PeekAll(key)\n\tvalues := make([]string, len(vs))\n\tfor i, v := range vs {\n\t\tvalues[i] = string(v)\n\t}\n\tif len(values) == 0 {\n\t\treturn ctx.multipartFormValueArray(key)\n\t} else {\n\t\treturn values, true\n\t}\n}\n\n// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.\nfunc bodyAllowedForStatus(status int) bool {\n\tswitch {\n\tcase status >= 100 && status <= 199:\n\t\treturn false\n\tcase status == consts.StatusNoContent:\n\t\treturn false\n\tcase status == consts.StatusNotModified:\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (ctx *RequestContext) getBinder() binding.Binder {\n\tif ctx.binder != nil {\n\t\treturn ctx.binder\n\t}\n\treturn binding.DefaultBinder()\n}\n\n// BindAndValidate binds data from *RequestContext to obj and validates them if needed.\n// NOTE: obj should be a pointer.\nfunc (ctx *RequestContext) BindAndValidate(obj interface{}) error {\n\tbi := ctx.getBinder()\n\tif err := bi.Bind(&ctx.Request, obj, ctx.Params); err != nil {\n\t\treturn err\n\t}\n\treturn bi.Validate(&ctx.Request, obj)\n}\n\n// Bind binds data from *RequestContext to obj.\n// NOTE: obj should be a pointer.\nfunc (ctx *RequestContext) Bind(obj interface{}) error {\n\treturn ctx.getBinder().Bind(&ctx.Request, obj, ctx.Params)\n}\n\n// Validate validates obj\n// NOTE: obj should be a pointer.\nfunc (ctx *RequestContext) Validate(obj interface{}) error {\n\treturn ctx.getBinder().Validate(&ctx.Request, obj)\n}\n\n// BindQuery binds query parameters from *RequestContext to obj with 'query' tag. It will only use 'query' tag for binding.\n// NOTE: obj should be a pointer.\nfunc (ctx *RequestContext) BindQuery(obj interface{}) error {\n\treturn ctx.getBinder().BindQuery(&ctx.Request, obj)\n}\n\n// BindHeader binds header parameters from *RequestContext to obj with 'header' tag. It will only use 'header' tag for binding.\n// NOTE: obj should be a pointer.\nfunc (ctx *RequestContext) BindHeader(obj interface{}) error {\n\treturn ctx.getBinder().BindHeader(&ctx.Request, obj)\n}\n\n// BindPath binds router parameters from *RequestContext to obj with 'path' tag. It will only use 'path' tag for binding.\n// NOTE: obj should be a pointer.\nfunc (ctx *RequestContext) BindPath(obj interface{}) error {\n\treturn ctx.getBinder().BindPath(&ctx.Request, obj, ctx.Params)\n}\n\n// BindForm binds form parameters from *RequestContext to obj with 'form' tag. It will only use 'form' tag for binding.\n// NOTE: obj should be a pointer.\nfunc (ctx *RequestContext) BindForm(obj interface{}) error {\n\tif len(ctx.Request.Body()) == 0 {\n\t\treturn fmt.Errorf(\"missing form body\")\n\t}\n\treturn ctx.getBinder().BindForm(&ctx.Request, obj)\n}\n\n// BindJSON binds JSON body from *RequestContext.\n// NOTE: obj should be a pointer.\nfunc (ctx *RequestContext) BindJSON(obj interface{}) error {\n\treturn ctx.getBinder().BindJSON(&ctx.Request, obj)\n}\n\n// BindProtobuf binds protobuf body from *RequestContext.\n// NOTE: obj should be a pointer.\nfunc (ctx *RequestContext) BindProtobuf(obj interface{}) error {\n\treturn ctx.getBinder().BindProtobuf(&ctx.Request, obj)\n}\n\n// BindByContentType will select the binding type on the ContentType automatically.\n// NOTE: obj should be a pointer.\nfunc (ctx *RequestContext) BindByContentType(obj interface{}) error {\n\tif ctx.Request.Header.IsGet() {\n\t\treturn ctx.BindQuery(obj)\n\t}\n\tct := utils.FilterContentType(bytesconv.B2s(ctx.Request.Header.ContentType()))\n\tswitch strings.ToLower(ct) {\n\tcase consts.MIMEApplicationJSON:\n\t\treturn ctx.BindJSON(obj)\n\tcase consts.MIMEPROTOBUF:\n\t\treturn ctx.BindProtobuf(obj)\n\tcase consts.MIMEApplicationHTMLForm, consts.MIMEMultipartPOSTForm:\n\t\treturn ctx.BindForm(obj)\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported bind content-type for '%s'\", ct)\n\t}\n}\n\n// VisitAllQueryArgs calls f for each existing query arg.\n//\n// f must not retain references to key and value after returning.\n// Make key and/or value copies if you need storing them after returning.\nfunc (ctx *RequestContext) VisitAllQueryArgs(f func(key, value []byte)) {\n\tctx.QueryArgs().VisitAll(f)\n}\n\n// VisitAllPostArgs calls f for each existing post arg.\n//\n// f must not retain references to key and value after returning.\n// Make key and/or value copies if you need storing them after returning.\nfunc (ctx *RequestContext) VisitAllPostArgs(f func(key, value []byte)) {\n\tctx.Request.PostArgs().VisitAll(f)\n}\n\n// VisitAllHeaders calls f for each request header.\n//\n// f must not retain references to key and/or value after returning.\n// Copy key and/or value contents before returning if you need retaining them.\n//\n// To get the headers in order they were received use VisitAllInOrder.\nfunc (ctx *RequestContext) VisitAllHeaders(f func(key, value []byte)) {\n\tctx.Request.Header.VisitAll(f)\n}\n\n// VisitAllCookie calls f for each request cookie.\n//\n// f must not retain references to key and/or value after returning.\nfunc (ctx *RequestContext) VisitAllCookie(f func(key, value []byte)) {\n\tctx.Request.Header.VisitAllCookie(f)\n}\n"
  },
  {
    "path": "pkg/app/context_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage app\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/xml\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/internal/test/mock/binder\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/render\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/common/testdata/proto\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/req\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n\tcon \"github.com/cloudwego/hertz/pkg/route/consts\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\nfunc TestProtobuf(t *testing.T) {\n\tctx := NewContext(0)\n\tbody := proto.TestStruct{Body: []byte(\"Hello World\")}\n\tctx.ProtoBuf(consts.StatusOK, &body)\n\n\tassert.DeepEqual(t, string(ctx.Response.Body()), \"\\n\\vHello World\")\n}\n\nfunc TestPureJson(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.PureJSON(consts.StatusOK, utils.H{\n\t\t\"html\": \"<b>Hello World</b>\",\n\t})\n\tif string(ctx.Response.Body()) != \"{\\\"html\\\":\\\"<b>Hello World</b>\\\"}\\n\" {\n\t\tt.Fatalf(\"unexpected purejson: %#v, expected: %#v\", string(ctx.Response.Body()), \"<b>Hello World</b>\")\n\t}\n}\n\nfunc TestIndentedJSON(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.IndentedJSON(consts.StatusOK, utils.H{\n\t\t\"foo\":  \"bar\",\n\t\t\"html\": \"h1\",\n\t})\n\tif string(ctx.Response.Body()) != \"{\\n    \\\"foo\\\": \\\"bar\\\",\\n    \\\"html\\\": \\\"h1\\\"\\n}\" {\n\t\tt.Fatalf(\"unexpected purejson: %#v, expected: %#v\", string(ctx.Response.Body()), \"{\\n    \\\"foo\\\": \\\"bar\\\",\\n    \\\"html\\\": \\\"<b>\\\"\\n}\")\n\t}\n}\n\nfunc TestContext(t *testing.T) {\n\treqContext := NewContext(0)\n\treqContext.Set(\"testContextKey\", \"testValue\")\n\tctx := reqContext\n\tif ctx.Value(\"testContextKey\") != \"testValue\" {\n\t\tt.Fatalf(\"unexpected value: %#v, expected: %#v\", ctx.Value(\"testContextKey\"), \"testValue\")\n\t}\n}\n\nfunc TestValue(t *testing.T) {\n\tctx := NewContext(0)\n\n\tv := ctx.Value(\"testContextKey\")\n\tassert.Nil(t, v)\n\n\tctx.Set(\"testContextKey\", \"testValue\")\n\tv = ctx.Value(\"testContextKey\")\n\tassert.DeepEqual(t, \"testValue\", v)\n}\n\nfunc TestContextNotModified(t *testing.T) {\n\treqContext := NewContext(0)\n\treqContext.Response.SetStatusCode(consts.StatusOK)\n\tif reqContext.Response.StatusCode() != consts.StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %#v, expected: %#v\", reqContext.Response.StatusCode(), consts.StatusOK)\n\t}\n\treqContext.NotModified()\n\tif reqContext.Response.StatusCode() != consts.StatusNotModified {\n\t\tt.Fatalf(\"unexpected status code: %#v, expected: %#v\", reqContext.Response.StatusCode(), consts.StatusNotModified)\n\t}\n}\n\nfunc TestIfModifiedSince(t *testing.T) {\n\tctx := NewContext(0)\n\tvar req protocol.Request\n\treq.Header.Set(string(bytestr.StrIfModifiedSince), \"Mon, 02 Jan 2006 15:04:05 MST\")\n\treq.CopyTo(&ctx.Request)\n\tif !ctx.IfModifiedSince(time.Now()) {\n\t\tt.Fatalf(\"ifModifiedSince error, expected false, but get true\")\n\t}\n\ttt, _ := time.Parse(time.RFC3339, \"2004-11-12T11:45:26.371Z\")\n\tif ctx.IfModifiedSince(tt) {\n\t\tt.Fatalf(\"ifModifiedSince error, expected true, but get false\")\n\t}\n}\n\nfunc TestWrite(t *testing.T) {\n\tctx := NewContext(0)\n\tl, err := ctx.Write([]byte(\"test body\"))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %#v\", err.Error())\n\t}\n\tif l != 9 {\n\t\tt.Fatalf(\"unexpected len: %#v, expected: %#v\", l, 9)\n\t}\n\tif string(ctx.Response.BodyBytes()) != \"test body\" {\n\t\tt.Fatalf(\"unexpected body: %#v, expected: %#v\", string(ctx.Response.BodyBytes()), \"test body\")\n\t}\n}\n\nfunc TestSetConnectionClose(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.SetConnectionClose()\n\tif !ctx.Response.Header.ConnectionClose() {\n\t\tt.Fatalf(\"expected close connection, but not\")\n\t}\n}\n\nfunc TestNotFound(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.NotFound()\n\tif ctx.Response.StatusCode() != consts.StatusNotFound || string(ctx.Response.BodyBytes()) != \"Not Found\" {\n\t\tt.Fatalf(\"unexpected status code or body\")\n\t}\n}\n\nfunc TestRedirect(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Redirect(consts.StatusFound, []byte(\"/hello\"))\n\tassert.DeepEqual(t, consts.StatusFound, ctx.Response.StatusCode())\n\n\tctx.redirect([]byte(\"/hello\"), consts.StatusMovedPermanently)\n\tassert.DeepEqual(t, consts.StatusMovedPermanently, ctx.Response.StatusCode())\n}\n\nfunc TestGetRedirectStatusCode(t *testing.T) {\n\tval := getRedirectStatusCode(consts.StatusMovedPermanently)\n\tassert.DeepEqual(t, consts.StatusMovedPermanently, val)\n\n\tval = getRedirectStatusCode(consts.StatusNotFound)\n\tassert.DeepEqual(t, consts.StatusFound, val)\n}\n\nfunc TestCookie(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Request.Header.SetCookie(\"cookie\", \"test cookie\")\n\tif string(ctx.Cookie(\"cookie\")) != \"test cookie\" {\n\t\tt.Fatalf(\"unexpected cookie: %#v, expected get: %#v\", string(ctx.Cookie(\"cookie\")), \"test cookie\")\n\t}\n}\n\nfunc TestUserAgent(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Request.Header.SetUserAgentBytes([]byte(\"user agent\"))\n\tif string(ctx.UserAgent()) != \"user agent\" {\n\t\tt.Fatalf(\"unexpected user agent: %#v, expected get: %#v\", string(ctx.UserAgent()), \"user agent\")\n\t}\n}\n\nfunc TestStatus(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Status(consts.StatusOK)\n\tif ctx.Response.StatusCode() != consts.StatusOK {\n\t\tt.Fatalf(\"expected get consts.StatusOK, but not\")\n\t}\n}\n\nfunc TestPost(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Request.Header.SetMethod(consts.MethodPost)\n\tif !ctx.IsPost() {\n\t\tt.Fatalf(\"expected post method , but get: %#v\", ctx.Method())\n\t}\n\n\tif string(ctx.Method()) != consts.MethodPost {\n\t\tt.Fatalf(\"expected post method , but get: %#v\", ctx.Method())\n\t}\n}\n\nfunc TestGet(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Request.Header.SetMethod(consts.MethodPost)\n\tassert.False(t, ctx.IsGet())\n\n\tctx.Request.Header.SetMethod(consts.MethodGet)\n\tassert.True(t, ctx.IsGet())\n}\n\nfunc TestCopy(t *testing.T) {\n\tt.Parallel()\n\tctx := NewContext(0)\n\tctx.fullPath = \"full_path\"\n\tctx.Request.Header.Add(\"header_a\", \"header_value_a\")\n\tctx.Response.Header.Add(\"header_b\", \"header_value_b\")\n\tctx.Params = param.Params{\n\t\t{Key: \"key_a\", Value: \"value_a\"},\n\t\t{Key: \"key_b\", Value: \"value_b\"},\n\t\t{Key: \"key_c\", Value: \"value_b\"},\n\t\t{Key: \"key_d\", Value: \"value_b\"},\n\t\t{Key: \"key_e\", Value: \"value_b\"},\n\t\t{Key: \"key_f\", Value: \"value_b\"},\n\t\t{Key: \"key_g\", Value: \"value_b\"},\n\t\t{Key: \"key_h\", Value: \"value_b\"},\n\t\t{Key: \"key_i\", Value: \"value_b\"},\n\t}\n\tctx.Set(\"map_key_a\", \"map_value_a\")\n\tctx.Set(\"map_key_b\", \"map_value_b\")\n\tfor i := 0; i <= 10000; i++ {\n\t\tc := ctx.Copy()\n\t\tgo func(context *RequestContext) {\n\t\t\tstr, _ := context.Params.Get(\"key_a\")\n\t\t\tif str != \"value_a\" {\n\t\t\t\tt.Errorf(\"unexpected value: %#v, expected: %#v\", str, \"value_a\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif c.fullPath != \"full_path\" {\n\t\t\t\tt.Errorf(\"unexpected value: %#v, expected: %#v\", c.fullPath, \"full_path\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treqHeaderStr := context.Request.Header.Get(\"header_a\")\n\t\t\tif reqHeaderStr != \"header_value_a\" {\n\t\t\t\tt.Errorf(\"unexpected value: %#v, expected: %#v\", reqHeaderStr, \"header_value_a\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trespHeaderStr := context.Response.Header.Get(\"header_b\")\n\t\t\tif respHeaderStr != \"header_value_b\" {\n\t\t\t\tt.Errorf(\"unexpected value: %#v, expected: %#v\", respHeaderStr, \"header_value_b\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tiStr := ctx.Value(\"map_key_a\")\n\t\t\tif iStr.(string) != \"map_value_a\" {\n\t\t\t\tt.Errorf(\"unexpected value: %#v, expected: %#v\", iStr.(string), \"map_value_a\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcontext.Params = context.Params[0:0]\n\t\t\tcontext.Params = append(context.Params, param.Param{Key: \"key_a\", Value: \"value_a_\"})\n\n\t\t\tcontext.Request.Header.Reset()\n\t\t\tcontext.Request.Header.Add(\"header_a\", \"header_value_a_\")\n\t\t\tcontext.Response.Header.Reset()\n\t\t\tcontext.Response.Header.Add(\"header_b\", \"header_value_b_\")\n\t\t\tcontext.Keys = nil\n\t\t\tcontext.Keys = make(map[string]interface{})\n\t\t\tcontext.Set(\"header_value_a\", \"map_value_a_\")\n\t\t}(c)\n\t}\n}\n\nfunc TestQuery(t *testing.T) {\n\tvar r protocol.Request\n\tctx := NewContext(0)\n\ts := \"POST /foo?name=menu&value= HTTP/1.1\\r\\nHost: google.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n3  \\r\\nabc\\r\\n0\\r\\n\\r\\n\"\n\tzr := mock.NewZeroCopyReader(s)\n\terr := req.Read(&r, zr)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading chunked request: %s\", err)\n\t}\n\tr.CopyTo(&ctx.Request)\n\tif ctx.Query(\"name\") != \"menu\" {\n\t\tt.Fatalf(\"unexpected query: %#v, expected menu\", ctx.Query(\"name\"))\n\t}\n\n\tif ctx.DefaultQuery(\"name\", \"default value\") != \"menu\" {\n\t\tt.Fatalf(\"unexpected query: %#v, expected menu\", ctx.Query(\"name\"))\n\t}\n\n\tif ctx.DefaultQuery(\"defaultQuery\", \"default value\") != \"default value\" {\n\t\tt.Fatalf(\"unexpected query: %#v, expected `default value`\", ctx.Query(\"defaultQuery\"))\n\t}\n}\n\nfunc TestMethod(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Status(consts.StatusOK)\n\tif ctx.Response.StatusCode() != consts.StatusOK {\n\t\tt.Fatalf(\"expected get consts.StatusOK, but not\")\n\t}\n}\n\nfunc makeCtxByReqString(t *testing.T, s string) *RequestContext {\n\tctx := NewContext(0)\n\n\tmr := mock.NewZeroCopyReader(s)\n\tif err := req.Read(&ctx.Request, mr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\treturn ctx\n}\n\nfunc TestPostForm(t *testing.T) {\n\tt.Parallel()\n\n\tctx := makeCtxByReqString(t, `POST /upload HTTP/1.1\nHost: localhost:10000\nContent-Length: 521\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f1\"\n\nvalue1\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"fileaaa\"; filename=\"TODO\"\nContent-Type: application/octet-stream\n\n- SessionClient with referer and cookies support.\n- Client with requests' pipelining support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\n`)\n\n\tif ctx.PostForm(\"f1\") != \"value1\" {\n\t\tt.Fatalf(\"PostForm get Multipart Form data failed\")\n\t}\n\tif ctx.PostForm(\"fileaaa\") != \"\" {\n\t\tt.Fatalf(\"PostForm should not get file\")\n\t}\n\n\tctx = makeCtxByReqString(t, `POST /upload HTTP/1.1\nHost: localhost:10000\nContent-Length: 11\nContent-Type: application/x-www-form-urlencoded\n\nhello=world`)\n\n\tif ctx.PostForm(\"hello\") != \"world\" {\n\t\tt.Fatalf(\"PostForm get form failed\")\n\t}\n}\n\nfunc TestPostFormArray(t *testing.T) {\n\tt.Parallel()\n\n\tctx := makeCtxByReqString(t, `POST /upload HTTP/1.1\nHost: localhost:10000\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Length: 521\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"tag\"\n\nred\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"tag\"\n\ngreen\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"tag\"\n\nblue\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\n`)\n\tassert.DeepEqual(t, []string{\"red\", \"green\", \"blue\"}, ctx.PostFormArray(\"tag\"))\n\n\tctx = makeCtxByReqString(t, `POST /upload HTTP/1.1\nHost: localhost:10000\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\nContent-Length: 26\n\ntag=red&tag=green&tag=blue\n`)\n\tassert.DeepEqual(t, []string{\"red\", \"green\", \"blue\"}, ctx.PostFormArray(\"tag\"))\n}\n\nfunc TestDefaultPostForm(t *testing.T) {\n\tctx := makeCtxByReqString(t, `POST /upload HTTP/1.1\nHost: localhost:10000\nContent-Length: 521\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f1\"\n\nvalue1\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"fileaaa\"; filename=\"TODO\"\nContent-Type: application/octet-stream\n\n- SessionClient with referer and cookies support.\n- Client with requests' pipelining support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\n`)\n\n\tval := ctx.DefaultPostForm(\"f1\", \"no val\")\n\tassert.DeepEqual(t, \"value1\", val)\n\n\tval = ctx.DefaultPostForm(\"f99\", \"no val\")\n\tassert.DeepEqual(t, \"no val\", val)\n}\n\nfunc TestRequestContext_FormFile(t *testing.T) {\n\tt.Parallel()\n\n\ts := `POST /upload HTTP/1.1\nHost: localhost:10000\nContent-Length: 521\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f1\"\n\nvalue1\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"fileaaa\"; filename=\"TODO\"\nContent-Type: application/octet-stream\n\n- SessionClient with referer and cookies support.\n- Client with requests' pipelining support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\ntailfoobar`\n\n\tmr := mock.NewZeroCopyReader(s)\n\n\tctx := NewContext(0)\n\tif err := req.Read(&ctx.Request, mr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\ttail, err := ioutil.ReadAll(mr)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(tail) != \"tailfoobar\" {\n\t\tt.Fatalf(\"unexpected tail %q. Expecting %q\", tail, \"tailfoobar\")\n\t}\n\n\tf, err := ctx.MultipartForm()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tdefer ctx.Request.RemoveMultipartFormFiles()\n\n\t// verify files\n\tif len(f.File) != 1 {\n\t\tt.Fatalf(\"unexpected number of file values in multipart form: %d. Expecting 1\", len(f.File))\n\t}\n\tfor k, vv := range f.File {\n\t\tif k != \"fileaaa\" {\n\t\t\tt.Fatalf(\"unexpected file value name %q. Expecting %q\", k, \"fileaaa\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of file values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v.Filename != \"TODO\" {\n\t\t\tt.Fatalf(\"unexpected filename %q. Expecting %q\", v.Filename, \"TODO\")\n\t\t}\n\t\tct := v.Header.Get(\"Content-Type\")\n\t\tif ct != consts.MIMEApplicationOctetStream {\n\t\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", ct, \"application/octet-stream\")\n\t\t}\n\t}\n\n\terr = ctx.SaveUploadedFile(f.File[\"fileaaa\"][0], \"TODO\")\n\tassert.Nil(t, err)\n\tfileInfo, err := os.Stat(\"TODO\")\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, \"TODO\", fileInfo.Name())\n\tassert.DeepEqual(t, f.File[\"fileaaa\"][0].Size, fileInfo.Size())\n\terr = os.Remove(\"TODO\")\n\tassert.Nil(t, err)\n\n\tff, err := ctx.FormFile(\"fileaaa\")\n\tif err != nil || ff == nil {\n\t\tt.Fatalf(\"unexpected error happened when ctx.FormFile()\")\n\t}\n\n\tbuf := make([]byte, ff.Size)\n\tfff, _ := ff.Open()\n\tfff.Read(buf)\n\n\tif !strings.Contains(string(buf), \"- SessionClient\") {\n\t\tt.Fatalf(\"unexpected file content. Expecting %q\", \"- SessionClient\")\n\t}\n\n\tif !strings.Contains(string(buf), \"rfc7540 .\") {\n\t\tt.Fatalf(\"unexpected file content. Expecting %q\", \"rfc7540 .\")\n\t}\n}\n\nfunc TestContextRenderFileFromFS(t *testing.T) {\n\tt.Parallel()\n\n\tctx := NewContext(0)\n\tvar req protocol.Request\n\treq.Header.SetMethod(consts.MethodGet)\n\treq.SetRequestURI(\"/some/path\")\n\treq.CopyTo(&ctx.Request)\n\n\tctx.FileFromFS(\"./fs.go\", &FS{\n\t\tRoot:               \".\",\n\t\tIndexNames:         nil,\n\t\tGenerateIndexPages: false,\n\t\tAcceptByteRange:    true,\n\t})\n\n\tassert.DeepEqual(t, consts.StatusOK, ctx.Response.StatusCode())\n\tassert.True(t, strings.Contains(resp.GetHTTP1Response(&ctx.Response).String(), \"func (fs *FS) initRequestHandler() {\"))\n\t// when Go version <= 1.16, mime.TypeByExtension will return Content-Type='text/plain; charset=utf-8',\n\t// otherwise it will return Content-Type='text/x-go; charset=utf-8'\n\tassert.NotEqual(t, \"\", string(ctx.Response.Header.Peek(\"Content-Type\")))\n\tassert.DeepEqual(t, \"/some/path\", string(ctx.Request.URI().Path()))\n}\n\nfunc TestContextRenderFile(t *testing.T) {\n\tt.Parallel()\n\n\tctx := NewContext(0)\n\tvar req protocol.Request\n\treq.Header.SetMethod(consts.MethodGet)\n\treq.SetRequestURI(\"/\")\n\treq.CopyTo(&ctx.Request)\n\n\tctx.File(\"./fs.go\")\n\n\tassert.DeepEqual(t, consts.StatusOK, ctx.Response.StatusCode())\n\tassert.True(t, strings.Contains(resp.GetHTTP1Response(&ctx.Response).String(), \"func (fs *FS) initRequestHandler() {\"))\n\t// when Go version <= 1.16, mime.TypeByExtension will return Content-Type='text/plain; charset=utf-8',\n\t// otherwise it will return Content-Type='text/x-go; charset=utf-8'\n\tassert.NotEqual(t, \"\", string(ctx.Response.Header.Peek(\"Content-Type\")))\n}\n\nfunc TestContextRenderAttachment(t *testing.T) {\n\tt.Parallel()\n\n\tctx := NewContext(0)\n\tvar req protocol.Request\n\treq.Header.SetMethod(consts.MethodGet)\n\treq.SetRequestURI(\"/\")\n\treq.CopyTo(&ctx.Request)\n\tnewFilename := \"new_filename.go\"\n\n\tctx.FileAttachment(\"./context.go\", newFilename)\n\n\tassert.DeepEqual(t, consts.StatusOK, ctx.Response.StatusCode())\n\tassert.True(t, strings.Contains(resp.GetHTTP1Response(&ctx.Response).String(),\n\t\t\"func (ctx *RequestContext) FileAttachment(filepath, filename string) {\"))\n\tassert.DeepEqual(t, fmt.Sprintf(\"attachment; filename=\\\"%s\\\"\", newFilename),\n\t\tstring(ctx.Response.Header.Peek(\"Content-Disposition\")))\n}\n\nfunc TestRequestContext_Header(t *testing.T) {\n\tc := NewContext(0)\n\n\tc.Header(\"header_key\", \"header_val\")\n\tval := string(c.Response.Header.Peek(\"header_key\"))\n\tif val != \"header_val\" {\n\t\tt.Fatalf(\"unexpected %q. Expecting %q\", val, \"header_val\")\n\t}\n\n\tc.Response.Header.Del(\"header_key\")\n\tval = string(c.Response.Header.Peek(\"header_key\"))\n\tif val != \"\" {\n\t\tt.Fatalf(\"unexpected %q. Expecting %q\", val, \"\")\n\t}\n\n\tc.Header(\"header_key1\", \"header_val1\")\n\tc.Header(\"header_key1\", \"\")\n\tval = string(c.Response.Header.Peek(\"header_key1\"))\n\tif val != \"\" {\n\t\tt.Fatalf(\"unexpected %q. Expecting %q\", val, \"\")\n\t}\n}\n\nfunc TestRequestContext_Keys(t *testing.T) {\n\tc := NewContext(0)\n\trightVal := \"123\"\n\tc.Set(\"key\", rightVal)\n\tval := c.GetString(\"key\")\n\tif val != rightVal {\n\t\tt.Fatalf(\"unexpected %v. Expecting %v\", val, rightVal)\n\t}\n}\n\nfunc testFunc(c context.Context, ctx *RequestContext) {\n\tctx.Next(c)\n}\n\nfunc testFunc2(c context.Context, ctx *RequestContext) {\n\tctx.Set(\"key\", \"123\")\n}\n\nfunc TestRequestContext_Handler(t *testing.T) {\n\tc := NewContext(0)\n\tc.handlers = HandlersChain{testFunc, testFunc2}\n\n\tc.Handler()(context.Background(), c)\n\tval := c.GetString(\"key\")\n\tif val != \"123\" {\n\t\tt.Fatalf(\"unexpected %v. Expecting %v\", val, \"123\")\n\t}\n\n\tc.handlers = nil\n\thandler := c.Handler()\n\tassert.Nil(t, handler)\n}\n\nfunc TestRequestContext_Handlers(t *testing.T) {\n\tc := NewContext(0)\n\thc := HandlersChain{testFunc, testFunc2}\n\tc.SetHandlers(hc)\n\tc.Handlers()[1](context.Background(), c)\n\tval := c.GetString(\"key\")\n\tif val != \"123\" {\n\t\tt.Fatalf(\"unexpected %v. Expecting %v\", val, \"123\")\n\t}\n}\n\nfunc TestRequestContext_HandlerName(t *testing.T) {\n\tc := NewContext(0)\n\tc.handlers = HandlersChain{testFunc, testFunc2}\n\tval := c.HandlerName()\n\tif val != \"github.com/cloudwego/hertz/pkg/app.testFunc2\" {\n\t\tt.Fatalf(\"unexpected %v. Expecting %v\", val, \"github.com/cloudwego/hertz.testFunc2\")\n\t}\n}\n\nfunc TestNext(t *testing.T) {\n\tc := NewContext(0)\n\ta := 0\n\n\ttestFunc1 := func(c context.Context, ctx *RequestContext) {\n\t\ta = 1\n\t}\n\ttestFunc3 := func(c context.Context, ctx *RequestContext) {\n\t\ta = 3\n\t}\n\tc.handlers = HandlersChain{testFunc1, testFunc3}\n\n\tc.Next(context.Background())\n\n\tassert.True(t, c.index == 2)\n\tassert.DeepEqual(t, 3, a)\n}\n\nfunc TestContextError(t *testing.T) {\n\tc := NewContext(0)\n\tassert.Nil(t, c.Errors)\n\n\tfirstErr := errors.New(\"first error\")\n\tc.Error(firstErr) // nolint: errcheck\n\tassert.DeepEqual(t, 1, len(c.Errors))\n\tassert.DeepEqual(t, \"Error #01: first error\\n\", c.Errors.String())\n\n\tsecondErr := errors.New(\"second error\")\n\tc.Error(&errs.Error{ // nolint: errcheck\n\t\tErr:  secondErr,\n\t\tMeta: \"some data 2\",\n\t\tType: errs.ErrorTypePublic,\n\t})\n\tassert.DeepEqual(t, 2, len(c.Errors))\n\n\tassert.DeepEqual(t, firstErr, c.Errors[0].Err)\n\tassert.Nil(t, c.Errors[0].Meta)\n\tassert.DeepEqual(t, errs.ErrorTypePrivate, c.Errors[0].Type)\n\n\tassert.DeepEqual(t, secondErr, c.Errors[1].Err)\n\tassert.DeepEqual(t, \"some data 2\", c.Errors[1].Meta)\n\tassert.DeepEqual(t, errs.ErrorTypePublic, c.Errors[1].Type)\n\n\tassert.DeepEqual(t, c.Errors.Last(), c.Errors[1])\n\n\tdefer func() {\n\t\tif recover() == nil {\n\t\t\tt.Error(\"didn't panic\")\n\t\t}\n\t}()\n\tc.Error(nil) // nolint: errcheck\n}\n\nfunc TestContextAbortWithError(t *testing.T) {\n\tc := NewContext(0)\n\n\tc.AbortWithError(consts.StatusUnauthorized, errors.New(\"bad input\")).SetMeta(\"some input\") // nolint: errcheck\n\n\tassert.DeepEqual(t, consts.StatusUnauthorized, c.Response.StatusCode())\n\tassert.DeepEqual(t, con.AbortIndex, c.index)\n\tassert.True(t, c.IsAborted())\n}\n\nfunc TestRender(t *testing.T) {\n\tc := NewContext(0)\n\n\tc.Render(consts.StatusOK, &render.Data{\n\t\tContentType: consts.MIMEApplicationJSONUTF8,\n\t\tData:        []byte(\"{\\\"test\\\":1}\"),\n\t})\n\n\tassert.DeepEqual(t, consts.StatusOK, c.Response.StatusCode())\n\tassert.True(t, strings.Contains(string(c.Response.Body()), \"test\"))\n\n\tc.Reset()\n\tc.Render(110, &render.Data{\n\t\tContentType: \"application/json; charset=utf-8\",\n\t\tData:        []byte(\"{\\\"test\\\":1}\"),\n\t})\n\tassert.DeepEqual(t, \"application/json; charset=utf-8\", string(c.Response.Header.ContentType()))\n\tassert.DeepEqual(t, \"\", string(c.Response.Body()))\n\n\tc.Reset()\n\tc.Render(consts.StatusNoContent, &render.Data{\n\t\tContentType: \"application/json; charset=utf-8\",\n\t\tData:        []byte(\"{\\\"test\\\":1}\"),\n\t})\n\tassert.DeepEqual(t, \"application/json; charset=utf-8\", string(c.Response.Header.ContentType()))\n\tassert.DeepEqual(t, \"\", string(c.Response.Body()))\n\n\tc.Reset()\n\tc.Render(consts.StatusNotModified, &render.Data{\n\t\tContentType: \"application/json; charset=utf-8\",\n\t\tData:        []byte(\"{\\\"test\\\":1}\"),\n\t})\n\tassert.DeepEqual(t, \"application/json; charset=utf-8\", string(c.Response.Header.ContentType()))\n\tassert.DeepEqual(t, \"\", string(c.Response.Body()))\n}\n\nfunc TestHTML(t *testing.T) {\n\tc := NewContext(0)\n\n\ttmpl := template.Must(template.New(\"\").\n\t\tDelims(\"{[{\", \"}]}\").\n\t\tFuncs(template.FuncMap{}).\n\t\tParseFiles(\"../common/testdata/template/index.tmpl\"))\n\n\tr := &render.HTMLProduction{Template: tmpl}\n\tc.HTMLRender = r\n\tc.HTML(consts.StatusOK, \"index.tmpl\", utils.H{\"title\": \"Main website\"})\n\n\tassert.DeepEqual(t, []byte(\"text/html; charset=utf-8\"), c.Response.Header.Peek(\"Content-Type\"))\n\tassert.DeepEqual(t, []byte(\"<html><h1>Main website</h1></html>\"), c.Response.Body())\n}\n\ntype xmlmap map[string]interface{}\n\n// Allows type H to be used with xml.Marshal\nfunc (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {\n\tstart.Name = xml.Name{\n\t\tSpace: \"\",\n\t\tLocal: \"map\",\n\t}\n\tif err := e.EncodeToken(start); err != nil {\n\t\treturn err\n\t}\n\tfor key, value := range h {\n\t\telem := xml.StartElement{\n\t\t\tName: xml.Name{Space: \"\", Local: key},\n\t\t\tAttr: []xml.Attr{},\n\t\t}\n\t\tif err := e.EncodeElement(value, elem); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn e.EncodeToken(xml.EndElement{Name: start.Name})\n}\n\nfunc TestXML(t *testing.T) {\n\tc := NewContext(0)\n\tc.XML(consts.StatusOK, xmlmap{\"foo\": \"bar\"})\n\tassert.DeepEqual(t, []byte(\"<map><foo>bar</foo></map>\"), c.Response.Body())\n\tassert.DeepEqual(t, []byte(\"application/xml; charset=utf-8\"), c.Response.Header.Peek(\"Content-Type\"))\n}\n\nfunc TestJSON(t *testing.T) {\n\tc := NewContext(0)\n\tc.JSON(consts.StatusOK, \"test\")\n\tassert.DeepEqual(t, consts.StatusOK, c.Response.StatusCode())\n\tassert.True(t, strings.Contains(string(c.Response.Body()), \"test\"))\n}\n\nfunc TestDATA(t *testing.T) {\n\tc := NewContext(0)\n\tc.Data(consts.StatusOK, \"application/json; charset=utf-8\", []byte(\"{\\\"test\\\":1}\"))\n\tassert.DeepEqual(t, consts.StatusOK, c.Response.StatusCode())\n\tassert.True(t, strings.Contains(string(c.Response.Body()), \"test\"))\n}\n\nfunc TestContextReset(t *testing.T) {\n\tc := NewContext(0)\n\n\tc.index = 2\n\tc.Params = param.Params{param.Param{}}\n\tc.Error(errors.New(\"test\")) // nolint: errcheck\n\tc.Set(\"foo\", \"bar\")\n\tc.Finished()\n\tc.Request.SetIsTLS(true)\n\tc.ResetWithoutConn()\n\tc.Request.URI()\n\tassert.DeepEqual(t, \"https\", string(c.Request.Scheme()))\n\tassert.False(t, c.IsAborted())\n\tassert.DeepEqual(t, 0, len(c.Errors))\n\tassert.Nil(t, c.Errors.Errors())\n\tassert.Nil(t, c.Errors.ByType(errs.ErrorTypeAny))\n\tassert.DeepEqual(t, 0, len(c.Params))\n\tassert.DeepEqual(t, int8(-1), c.index)\n\tassert.Nil(t, c.finished)\n}\n\nfunc TestContextContentType(t *testing.T) {\n\tc := NewContext(0)\n\tc.Request.Header.Set(\"Content-Type\", consts.MIMEApplicationJSONUTF8)\n\tassert.DeepEqual(t, consts.MIMEApplicationJSONUTF8, bytesconv.B2s(c.ContentType()))\n}\n\ntype MockConn struct {\n\t*mock.Conn\n\n\tremote net.Addr\n}\n\nfunc (c *MockConn) RemoteAddr() net.Addr {\n\treturn c.remote\n}\n\nfunc newContextClientIPTest() *RequestContext {\n\tc := NewContext(0)\n\tc.conn = &MockConn{\n\t\tremote: &net.TCPAddr{IP: net.ParseIP(\"127.0.0.1\"), Port: 8080},\n\t}\n\tc.Request.Header.Set(\"X-Real-IP\", \" 10.10.10.10  \")\n\tc.Request.Header.Set(\"X-Forwarded-For\", \"  20.20.20.20, 30.30.30.30\")\n\treturn c\n}\n\nfunc TestClientIP(t *testing.T) {\n\tc := newContextClientIPTest()\n\t// default X-Forwarded-For and X-Real-IP behaviour\n\tassert.DeepEqual(t, \"20.20.20.20\", c.ClientIP())\n\n\tc.Request.Header.DelBytes([]byte(\"X-Forwarded-For\"))\n\tassert.DeepEqual(t, \"10.10.10.10\", c.ClientIP())\n\n\tc.Request.Header.Set(\"X-Forwarded-For\", \"30.30.30.30  \")\n\tassert.DeepEqual(t, \"30.30.30.30\", c.ClientIP())\n\n\t// No trusted CIDRS\n\tc = newContextClientIPTest()\n\topts := ClientIPOptions{\n\t\tRemoteIPHeaders: []string{\"X-Forwarded-For\", \"X-Real-IP\"},\n\t\tTrustedCIDRs:    nil,\n\t}\n\tc.SetClientIPFunc(ClientIPWithOption(opts))\n\tassert.DeepEqual(t, \"127.0.0.1\", c.ClientIP())\n\n\t_, cidr, _ := net.ParseCIDR(\"30.30.30.30/32\")\n\topts = ClientIPOptions{\n\t\tRemoteIPHeaders: []string{\"X-Forwarded-For\", \"X-Real-IP\"},\n\t\tTrustedCIDRs:    []*net.IPNet{cidr},\n\t}\n\tc.SetClientIPFunc(ClientIPWithOption(opts))\n\tassert.DeepEqual(t, \"127.0.0.1\", c.ClientIP())\n\n\t_, cidr, _ = net.ParseCIDR(\"127.0.0.1/32\")\n\topts = ClientIPOptions{\n\t\tRemoteIPHeaders: []string{\"X-Forwarded-For\", \"X-Real-IP\"},\n\t\tTrustedCIDRs:    []*net.IPNet{cidr},\n\t}\n\tc.SetClientIPFunc(ClientIPWithOption(opts))\n\tassert.DeepEqual(t, \"30.30.30.30\", c.ClientIP())\n\n\t// UDS\n\tc.conn = &MockConn{remote: &net.UnixAddr{Net: \"unix\", Name: \"/tmp/test.sock\"}}\n\tassert.DeepEqual(t, \"30.30.30.30\", c.ClientIP())\n\n\t// err: Addr not host:port\n\tc.conn = &MockConn{remote: &net.UnixAddr{Net: \"tcp\", Name: \"/tmp/test.sock\"}}\n\tassert.DeepEqual(t, \"\", c.ClientIP())\n}\n\nfunc TestSetClientIPFunc(t *testing.T) {\n\tfn := func(ctx *RequestContext) string {\n\t\treturn \"\"\n\t}\n\tSetClientIPFunc(fn)\n\tassert.DeepEqual(t, reflect.ValueOf(fn).Pointer(), reflect.ValueOf(defaultClientIP).Pointer())\n}\n\nfunc TestGetQuery(t *testing.T) {\n\tc := NewContext(0)\n\tc.Request.SetRequestURI(\"http://aaa.com?a=1&b=\")\n\tv, exists := c.GetQuery(\"b\")\n\tassert.DeepEqual(t, \"\", v)\n\tassert.DeepEqual(t, true, exists)\n}\n\nfunc TestGetPostForm(t *testing.T) {\n\tc := NewContext(0)\n\tc.Request.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm))\n\tc.Request.SetBodyString(\"a=1&b=\")\n\tv, exists := c.GetPostForm(\"b\")\n\tassert.DeepEqual(t, \"\", v)\n\tassert.DeepEqual(t, true, exists)\n}\n\nfunc TestGetPostFormArray(t *testing.T) {\n\tc := NewContext(0)\n\tc.Request.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm))\n\tc.Request.SetBodyString(\"a=1&b=2&b=3\")\n\tv, _ := c.GetPostFormArray(\"b\")\n\tassert.DeepEqual(t, []string{\"2\", \"3\"}, v)\n}\n\nfunc TestRemoteAddr(t *testing.T) {\n\tc := NewContext(0)\n\tc.Request.SetRequestURI(\"http://aaa.com?a=1&b=\")\n\taddr := c.RemoteAddr().String()\n\tassert.DeepEqual(t, \"0.0.0.0:0\", addr)\n}\n\nfunc TestRequestBodyStream(t *testing.T) {\n\tc := NewContext(0)\n\ts := \"testRequestBodyStream\"\n\tmr := bytes.NewBufferString(s)\n\tc.Request.SetBodyStream(mr, -1)\n\tdata, err := ioutil.ReadAll(c.RequestBodyStream())\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, \"testRequestBodyStream\", string(data))\n}\n\nfunc TestContextIsAborted(t *testing.T) {\n\tctx := NewContext(0)\n\tassert.False(t, ctx.IsAborted())\n\n\tctx.Abort()\n\tassert.True(t, ctx.IsAborted())\n\n\tctx.Next(context.Background())\n\tassert.True(t, ctx.IsAborted())\n\n\tctx.index++\n\tassert.True(t, ctx.IsAborted())\n}\n\nfunc TestContextAbortWithStatus(t *testing.T) {\n\tc := NewContext(0)\n\n\tc.index = 4\n\tc.AbortWithStatus(consts.StatusUnauthorized)\n\n\tassert.DeepEqual(t, con.AbortIndex, c.index)\n\tassert.DeepEqual(t, consts.StatusUnauthorized, c.Response.Header.StatusCode())\n\tassert.True(t, c.IsAborted())\n}\n\ntype testJSONAbortMsg struct {\n\tFoo string `json:\"foo\"`\n\tBar string `json:\"bar\"`\n}\n\nfunc TestContextAbortWithStatusJSON(t *testing.T) {\n\tc := NewContext(0)\n\tc.index = 4\n\n\tin := new(testJSONAbortMsg)\n\tin.Bar = \"barValue\"\n\tin.Foo = \"fooValue\"\n\n\tc.AbortWithStatusJSON(consts.StatusUnsupportedMediaType, in)\n\n\tassert.DeepEqual(t, con.AbortIndex, c.index)\n\tassert.DeepEqual(t, consts.StatusUnsupportedMediaType, c.Response.Header.StatusCode())\n\tassert.True(t, c.IsAborted())\n\n\tcontentType := c.Response.Header.Peek(\"Content-Type\")\n\tassert.DeepEqual(t, consts.MIMEApplicationJSONUTF8, string(contentType))\n\n\tjsonStringBody := c.Response.Body()\n\tassert.DeepEqual(t, \"{\\\"foo\\\":\\\"fooValue\\\",\\\"bar\\\":\\\"barValue\\\"}\", string(jsonStringBody))\n}\n\nfunc TestRequestCtxFormValue(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Request.SetRequestURI(\"/foo/bar?baz=123&aaa=bbb\")\n\tctx.Request.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm))\n\tctx.Request.SetBodyString(\"qqq=port&mmm=sddd\")\n\n\tv := ctx.FormValue(\"baz\")\n\tif string(v) != \"123\" {\n\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"123\")\n\t}\n\tv = ctx.FormValue(\"mmm\")\n\tif string(v) != \"sddd\" {\n\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"sddd\")\n\t}\n\tv = ctx.FormValue(\"aaaasdfsdf\")\n\tif len(v) > 0 {\n\t\tt.Fatalf(\"unexpected value for unknown key %q\", v)\n\t}\n\tctx.Request.Reset()\n\tctx.Request.SetFormData(map[string]string{\n\t\t\"a\": \"1\",\n\t})\n\tv = ctx.FormValue(\"a\")\n\tif string(v) != \"1\" {\n\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"1\")\n\t}\n\n\tctx.Request.Reset()\n\ts := `------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f\"\n\nfff\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\n`\n\tmr := bytes.NewBufferString(s)\n\tctx.Request.SetBodyStream(mr, -1)\n\tctx.Request.Header.SetContentLength(len(s))\n\tctx.Request.Header.SetContentTypeBytes([]byte(\"multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\"))\n\n\tv = ctx.FormValue(\"f\")\n\tif string(v) != \"fff\" {\n\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"fff\")\n\t}\n}\n\nfunc TestSetCustomFormValueFunc(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Request.SetRequestURI(\"/foo/bar?aaa=bbb\")\n\tctx.Request.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm))\n\tctx.Request.SetBodyString(\"aaa=port\")\n\n\tctx.SetFormValueFunc(func(ctx *RequestContext, key string) []byte {\n\t\tv := ctx.PostArgs().Peek(key)\n\t\tif len(v) > 0 {\n\t\t\treturn v\n\t\t}\n\t\tmf, err := ctx.MultipartForm()\n\t\tif err == nil && mf.Value != nil {\n\t\t\tvv := mf.Value[key]\n\t\t\tif len(vv) > 0 {\n\t\t\t\treturn []byte(vv[0])\n\t\t\t}\n\t\t}\n\t\tv = ctx.QueryArgs().Peek(key)\n\t\tif len(v) > 0 {\n\t\t\treturn v\n\t\t}\n\t\treturn nil\n\t})\n\n\tv := ctx.FormValue(\"aaa\")\n\tif string(v) != \"port\" {\n\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"port\")\n\t}\n}\n\nfunc TestContextSetGet(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"foo\", \"bar\")\n\n\tvalue, err := c.Get(\"foo\")\n\tassert.DeepEqual(t, \"bar\", value)\n\tassert.True(t, err)\n\n\tvalue, err = c.Get(\"foo2\")\n\tassert.Nil(t, value)\n\tassert.False(t, err)\n\n\tassert.DeepEqual(t, \"bar\", c.MustGet(\"foo\"))\n\tassert.Panic(t, func() { c.MustGet(\"no_exist\") })\n}\n\nfunc TestContextSetGetValues(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"string\", \"this is a string\")\n\tc.Set(\"int32\", int32(-42))\n\tc.Set(\"int64\", int64(42424242424242))\n\tc.Set(\"uint32\", uint32(42))\n\tc.Set(\"uint64\", uint64(42424242424242))\n\tc.Set(\"float32\", float32(4.2))\n\tc.Set(\"float64\", 4.2)\n\tvar a interface{} = 1\n\tc.Set(\"intInterface\", a)\n\n\tassert.DeepEqual(t, c.MustGet(\"string\").(string), \"this is a string\")\n\tassert.DeepEqual(t, c.MustGet(\"int32\").(int32), int32(-42))\n\tassert.DeepEqual(t, c.MustGet(\"int64\").(int64), int64(42424242424242))\n\tassert.DeepEqual(t, c.MustGet(\"uint32\").(uint32), uint32(42))\n\tassert.DeepEqual(t, c.MustGet(\"uint64\").(uint64), uint64(42424242424242))\n\tassert.DeepEqual(t, c.MustGet(\"float32\").(float32), float32(4.2))\n\tassert.DeepEqual(t, c.MustGet(\"float64\").(float64), 4.2)\n\tassert.DeepEqual(t, c.MustGet(\"intInterface\").(int), 1)\n}\n\nfunc TestContextGetString(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, \"this is a string\", c.GetString(\"string\"))\n\tc.Set(\"bool\", false)\n\tassert.DeepEqual(t, \"\", c.GetString(\"bool\"))\n}\n\nfunc TestContextSetGetBool(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"bool\", true)\n\tassert.True(t, c.GetBool(\"bool\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.False(t, c.GetBool(\"string\"))\n}\n\nfunc TestContextGetInt(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"int\", 1)\n\tassert.DeepEqual(t, 1, c.GetInt(\"int\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, 0, c.GetInt(\"string\"))\n}\n\nfunc TestContextGetInt32(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"int32\", int32(-42))\n\tassert.DeepEqual(t, int32(-42), c.GetInt32(\"int32\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, int32(0), c.GetInt32(\"string\"))\n}\n\nfunc TestContextGetInt64(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"int64\", int64(42424242424242))\n\tassert.DeepEqual(t, int64(42424242424242), c.GetInt64(\"int64\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, int64(0), c.GetInt64(\"string\"))\n}\n\nfunc TestContextGetUint(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"uint\", uint(1))\n\tassert.DeepEqual(t, uint(1), c.GetUint(\"uint\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, uint(0), c.GetUint(\"string\"))\n}\n\nfunc TestContextGetUint32(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"uint32\", uint32(42))\n\tassert.DeepEqual(t, uint32(42), c.GetUint32(\"uint32\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, uint32(0), c.GetUint32(\"string\"))\n}\n\nfunc TestContextGetUint64(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"uint64\", uint64(42424242424242))\n\tassert.DeepEqual(t, uint64(42424242424242), c.GetUint64(\"uint64\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, uint64(0), c.GetUint64(\"string\"))\n}\n\nfunc TestContextGetFloat32(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"float32\", float32(4.2))\n\tassert.DeepEqual(t, float32(4.2), c.GetFloat32(\"float32\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, float32(0.0), c.GetFloat32(\"string\"))\n}\n\nfunc TestContextGetFloat64(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"float64\", 4.2)\n\tassert.DeepEqual(t, 4.2, c.GetFloat64(\"float64\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, 0.0, c.GetFloat64(\"string\"))\n}\n\nfunc TestContextGetTime(t *testing.T) {\n\tc := &RequestContext{}\n\tt1, _ := time.Parse(\"1/2/2006 15:04:05\", \"01/01/2017 12:00:00\")\n\tc.Set(\"time\", t1)\n\tassert.DeepEqual(t, t1, c.GetTime(\"time\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, time.Time{}, c.GetTime(\"string\"))\n}\n\nfunc TestContextGetDuration(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"duration\", time.Second)\n\tassert.DeepEqual(t, time.Second, c.GetDuration(\"duration\"))\n\tc.Set(\"string\", \"this is a string\")\n\tassert.DeepEqual(t, time.Duration(0), c.GetDuration(\"string\"))\n}\n\nfunc TestContextGetStringSlice(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Set(\"slice\", []string{\"foo\"})\n\tassert.DeepEqual(t, []string{\"foo\"}, c.GetStringSlice(\"slice\"))\n\tc.Set(\"string\", \"this is a string\")\n\tvar expected []string\n\tassert.DeepEqual(t, expected, c.GetStringSlice(\"string\"))\n}\n\nfunc TestContextGetStringMap(t *testing.T) {\n\tc := &RequestContext{}\n\tm := make(map[string]interface{})\n\tm[\"foo\"] = 1\n\tc.Set(\"map\", m)\n\n\tassert.DeepEqual(t, m, c.GetStringMap(\"map\"))\n\tassert.DeepEqual(t, 1, c.GetStringMap(\"map\")[\"foo\"])\n\n\tc.Set(\"string\", \"this is a string\")\n\tvar expected map[string]interface{}\n\tassert.DeepEqual(t, expected, c.GetStringMap(\"string\"))\n}\n\nfunc TestContextGetStringMapString(t *testing.T) {\n\tc := &RequestContext{}\n\tm := make(map[string]string)\n\tm[\"foo\"] = \"bar\"\n\tc.Set(\"map\", m)\n\n\tassert.DeepEqual(t, m, c.GetStringMapString(\"map\"))\n\tassert.DeepEqual(t, \"bar\", c.GetStringMapString(\"map\")[\"foo\"])\n\n\tc.Set(\"string\", \"this is a string\")\n\tvar expected map[string]string\n\tassert.DeepEqual(t, expected, c.GetStringMapString(\"string\"))\n}\n\nfunc TestContextGetStringMapStringSlice(t *testing.T) {\n\tc := &RequestContext{}\n\tm := make(map[string][]string)\n\tm[\"foo\"] = []string{\"foo\"}\n\tc.Set(\"map\", m)\n\n\tassert.DeepEqual(t, m, c.GetStringMapStringSlice(\"map\"))\n\tassert.DeepEqual(t, []string{\"foo\"}, c.GetStringMapStringSlice(\"map\")[\"foo\"])\n\n\tc.Set(\"string\", \"this is a string\")\n\tvar expected map[string][]string\n\tassert.DeepEqual(t, expected, c.GetStringMapStringSlice(\"string\"))\n}\n\nfunc TestContextTraceInfo(t *testing.T) {\n\tctx := NewContext(0)\n\ttraceIn := traceinfo.NewTraceInfo()\n\tctx.SetTraceInfo(traceIn)\n\ttraceOut := ctx.GetTraceInfo()\n\n\tassert.DeepEqual(t, traceIn, traceOut)\n}\n\nfunc TestEnableTrace(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.SetEnableTrace(true)\n\ttrace := ctx.IsEnableTrace()\n\tassert.True(t, trace)\n}\n\nfunc TestForEachKey(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Set(\"1\", \"2\")\n\thandle := func(k string, v interface{}) {\n\t\tres := k + v.(string)\n\t\tassert.DeepEqual(t, res, \"12\")\n\t}\n\tctx.ForEachKey(handle)\n\tval, ok := ctx.Get(\"1\")\n\tassert.DeepEqual(t, val, \"2\")\n\tassert.True(t, ok)\n}\n\nfunc TestFlush(t *testing.T) {\n\tctx := NewContext(0)\n\terr := ctx.Flush()\n\tassert.Nil(t, err)\n}\n\nfunc TestConn(t *testing.T) {\n\tctx := NewContext(0)\n\n\tconn := mock.NewConn(\"\")\n\n\tctx.SetConn(conn)\n\tconnRes := ctx.GetConn()\n\n\tval1 := reflect.ValueOf(conn).Pointer()\n\tval2 := reflect.ValueOf(connRes).Pointer()\n\tassert.DeepEqual(t, val1, val2)\n}\n\nfunc TestHijackHandler(t *testing.T) {\n\tctx := NewContext(0)\n\thandle := func(c network.Conn) {\n\t\tc.SetReadTimeout(time.Duration(1) * time.Second)\n\t}\n\tctx.SetHijackHandler(handle)\n\thandleRes := ctx.GetHijackHandler()\n\n\tval1 := reflect.ValueOf(handle).Pointer()\n\tval2 := reflect.ValueOf(handleRes).Pointer()\n\tassert.DeepEqual(t, val1, val2)\n}\n\nfunc TestGetReader(t *testing.T) {\n\tctx := NewContext(0)\n\n\tconn := mock.NewConn(\"\")\n\n\tctx.SetConn(conn)\n\tconnRes := ctx.GetReader()\n\n\tval1 := reflect.ValueOf(conn).Pointer()\n\tval2 := reflect.ValueOf(connRes).Pointer()\n\tassert.DeepEqual(t, val1, val2)\n}\n\nfunc TestGetWriter(t *testing.T) {\n\tctx := NewContext(0)\n\n\tconn := mock.NewConn(\"\")\n\n\tctx.SetConn(conn)\n\tconnRes := ctx.GetWriter()\n\n\tval1 := reflect.ValueOf(conn).Pointer()\n\tval2 := reflect.ValueOf(connRes).Pointer()\n\tassert.DeepEqual(t, val1, val2)\n}\n\nfunc TestIndex(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.ResetWithoutConn()\n\texc := int8(-1)\n\tres := ctx.GetIndex()\n\tassert.DeepEqual(t, exc, res)\n\tctx.SetIndex(int8(1))\n\tres = ctx.GetIndex()\n\texc = int8(1)\n\tassert.DeepEqual(t, exc, res)\n}\n\nfunc TestConcurrentHandlerName(t *testing.T) {\n\tSetConcurrentHandlerNameOperator()\n\tdefer SetHandlerNameOperator(&inbuiltHandlerNameOperatorStruct{handlerNames: map[uintptr]string{}})\n\th := func(c context.Context, ctx *RequestContext) {}\n\tSetHandlerName(h, \"test1\")\n\tfor i := 0; i < 50; i++ {\n\t\tgo func() {\n\t\t\tname := GetHandlerName(h)\n\t\t\tassert.DeepEqual(t, \"test1\", name)\n\t\t}()\n\t}\n\n\ttime.Sleep(50 * time.Millisecond)\n\n\tgo func() {\n\t\tSetHandlerName(h, \"test2\")\n\t}()\n\n\ttime.Sleep(50 * time.Millisecond)\n\n\tname := GetHandlerName(h)\n\tassert.DeepEqual(t, \"test2\", name)\n}\n\nfunc TestHandlerName(t *testing.T) {\n\th := func(c context.Context, ctx *RequestContext) {}\n\tSetHandlerName(h, \"test1\")\n\tname := GetHandlerName(h)\n\tassert.DeepEqual(t, \"test1\", name)\n}\n\nfunc TestHijack(t *testing.T) {\n\tctx := NewContext(0)\n\th := func(c network.Conn) {}\n\tctx.Hijack(h)\n\tassert.True(t, ctx.Hijacked())\n}\n\nfunc TestFinished(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Finished()\n\n\tch := make(chan struct{})\n\tctx.finished = ch\n\tchRes := ctx.Finished()\n\n\tsend := func() {\n\t\ttime.Sleep(time.Duration(1) * time.Millisecond)\n\t\tch <- struct{}{}\n\t}\n\tgo send()\n\tval := <-chRes\n\tassert.DeepEqual(t, struct{}{}, val)\n}\n\nfunc TestString(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.String(consts.StatusOK, \"ok\")\n\tassert.DeepEqual(t, consts.StatusOK, ctx.Response.StatusCode())\n}\n\nfunc TestFullPath(t *testing.T) {\n\tctx := NewContext(0)\n\tstr := \"/hello\"\n\tctx.SetFullPath(str)\n\tval := ctx.FullPath()\n\tassert.DeepEqual(t, str, val)\n}\n\nfunc TestReset(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Reset()\n\tassert.DeepEqual(t, nil, ctx.conn)\n}\n\n// func TestParam(t *testing.T) {\n// \tctx := NewContext(0)\n// \tval := ctx.Param(\"/user/john\")\n// \tassert.DeepEqual(t, \"john\", val)\n// }\n\nfunc TestGetHeader(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Request.Header.SetContentTypeBytes([]byte(consts.MIMETextPlainUTF8))\n\tval := ctx.GetHeader(\"Content-Type\")\n\tassert.DeepEqual(t, consts.MIMETextPlainUTF8, string(val))\n}\n\nfunc TestGetRawData(t *testing.T) {\n\tctx := NewContext(0)\n\tctx.Request.SetBody([]byte(\"hello\"))\n\tval := ctx.GetRawData()\n\tassert.DeepEqual(t, \"hello\", string(val))\n\n\tval2, err := ctx.Body()\n\tassert.DeepEqual(t, val, val2)\n\tassert.Nil(t, err)\n}\n\nfunc TestRequestContext_GetRequest(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Request.Header.Set(\"key1\", \"value1\")\n\tc.Request.SetBody([]byte(\"test body\"))\n\treq := c.GetRequest()\n\tif req.Header.Get(\"key1\") != \"value1\" {\n\t\tt.Fatal(\"should have header: key1:value1\")\n\t}\n\tif string(req.Body()) != \"test body\" {\n\t\tt.Fatal(\"should have body: test body\")\n\t}\n}\n\nfunc TestRequestContext_GetResponse(t *testing.T) {\n\tc := &RequestContext{}\n\tc.Response.Header.Set(\"key1\", \"value1\")\n\tc.Response.SetBody([]byte(\"test body\"))\n\tresp := c.GetResponse()\n\tif resp.Header.Get(\"key1\") != \"value1\" {\n\t\tt.Fatal(\"should have header: key1:value1\")\n\t}\n\tif string(resp.Body()) != \"test body\" {\n\t\tt.Fatal(\"should have body: test body\")\n\t}\n}\n\nfunc TestBindAndValidate(t *testing.T) {\n\ttype Test struct {\n\t\tA string `query:\"a\"`\n\t\tB int    `query:\"b\" vd:\"$>10\"`\n\t}\n\n\tc := &RequestContext{}\n\tc.Request.SetRequestURI(\"/foo/bar?a=123&b=11\")\n\n\tvar req Test\n\terr := c.BindAndValidate(&req)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tassert.DeepEqual(t, \"123\", req.A)\n\tassert.DeepEqual(t, 11, req.B)\n\n\tc.Request.URI().Reset()\n\tc.Request.SetRequestURI(\"/foo/bar?a=123&b=9\")\n\treq = Test{}\n\terr = c.BindAndValidate(&req)\n\tif err == nil {\n\t\tt.Fatalf(\"unexpected nil, expected an error\")\n\t}\n\n\tc.Request.URI().Reset()\n\tc.Request.SetRequestURI(\"/foo/bar?a=123&b=9\")\n\treq = Test{}\n\terr = c.Bind(&req)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tassert.DeepEqual(t, \"123\", req.A)\n\tassert.DeepEqual(t, 9, req.B)\n\n\terr = c.Validate(&req)\n\tif err == nil {\n\t\tt.Fatalf(\"unexpected nil, expected an error\")\n\t}\n}\n\nfunc TestBindForm(t *testing.T) {\n\ttype Test struct {\n\t\tA string\n\t\tB int\n\t}\n\n\tc := &RequestContext{}\n\tc.Request.SetRequestURI(\"/foo/bar?a=123&b=11\")\n\tc.Request.SetBody([]byte(\"A=123&B=11\"))\n\tc.Request.Header.SetContentTypeBytes([]byte(\"application/x-www-form-urlencoded\"))\n\n\tvar req Test\n\terr := c.BindForm(&req)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tassert.DeepEqual(t, \"123\", req.A)\n\tassert.DeepEqual(t, 11, req.B)\n\n\tc.Request.SetBody([]byte(\"\"))\n\terr = c.BindForm(&req)\n\tif err == nil {\n\t\tt.Fatalf(\"expected error, but get nil\")\n\t}\n}\n\nfunc TestSetBinder(t *testing.T) {\n\tc := NewContext(0)\n\tc.SetBinder(binder.NewBinderWithValidateError(errors.New(\"test binder\")))\n\ttype T struct{}\n\treq := T{}\n\terr := c.Bind(&req)\n\tassert.Nil(t, err)\n\terr = c.Validate(&req)\n\tassert.NotNil(t, err)\n\tassert.DeepEqual(t, \"test binder\", err.Error())\n\terr = c.BindProtobuf(&req)\n\tassert.Nil(t, err)\n\terr = c.BindJSON(&req)\n\tassert.Nil(t, err)\n\terr = c.BindForm(&req)\n\tassert.NotNil(t, err)\n\terr = c.BindPath(&req)\n\tassert.Nil(t, err)\n\terr = c.BindQuery(&req)\n\tassert.Nil(t, err)\n\terr = c.BindHeader(&req)\n\tassert.Nil(t, err)\n}\n\nfunc TestRequestContext_SetCookie(t *testing.T) {\n\tc := NewContext(0)\n\tc.SetCookie(\"user\", \"hertz\", 1, \"/\", \"localhost\", protocol.CookieSameSiteNoneMode, true, true)\n\tassert.DeepEqual(t, \"user=hertz; max-age=1; domain=localhost; path=/; HttpOnly; secure; SameSite=None\", c.Response.Header.Get(\"Set-Cookie\"))\n}\n\nfunc TestRequestContext_SetPartitionedCookie(t *testing.T) {\n\tc := NewContext(0)\n\tc.SetPartitionedCookie(\"user\", \"hertz\", 1, \"/\", \"localhost\", protocol.CookieSameSiteNoneMode, true, true)\n\tassert.DeepEqual(t, \"user=hertz; max-age=1; domain=localhost; path=/; HttpOnly; secure; SameSite=None; Partitioned\", c.Response.Header.Get(\"Set-Cookie\"))\n}\n\nfunc TestRequestContext_SetCookiePathEmpty(t *testing.T) {\n\tc := NewContext(0)\n\tc.SetCookie(\"user\", \"hertz\", 1, \"\", \"localhost\", protocol.CookieSameSiteDisabled, true, true)\n\tassert.DeepEqual(t, \"user=hertz; max-age=1; domain=localhost; path=/; HttpOnly; secure\", c.Response.Header.Get(\"Set-Cookie\"))\n}\n\nfunc TestRequestContext_VisitAll(t *testing.T) {\n\tt.Run(\"VisitAllQueryArgs\", func(t *testing.T) {\n\t\tc := NewContext(0)\n\t\tvar s []string\n\t\tc.QueryArgs().Add(\"cloudwego\", \"hertz\")\n\t\tc.QueryArgs().Add(\"hello\", \"world\")\n\t\tc.VisitAllQueryArgs(func(key, value []byte) {\n\t\t\ts = append(s, string(key), string(value))\n\t\t})\n\t\tassert.DeepEqual(t, []string{\"cloudwego\", \"hertz\", \"hello\", \"world\"}, s)\n\t})\n\n\tt.Run(\"VisitAllPostArgs\", func(t *testing.T) {\n\t\tc := NewContext(0)\n\t\tvar s []string\n\t\tc.PostArgs().Add(\"cloudwego\", \"hertz\")\n\t\tc.PostArgs().Add(\"hello\", \"world\")\n\t\tc.VisitAllPostArgs(func(key, value []byte) {\n\t\t\ts = append(s, string(key), string(value))\n\t\t})\n\t\tassert.DeepEqual(t, []string{\"cloudwego\", \"hertz\", \"hello\", \"world\"}, s)\n\t})\n\n\tt.Run(\"VisitAllCookie\", func(t *testing.T) {\n\t\tc := NewContext(0)\n\t\tvar s []string\n\t\tc.Request.Header.Set(\"Cookie\", \"aaa=bbb;ccc=ddd\")\n\t\tc.VisitAllCookie(func(key, value []byte) {\n\t\t\ts = append(s, string(key), string(value))\n\t\t})\n\t\tassert.DeepEqual(t, []string{\"aaa\", \"bbb\", \"ccc\", \"ddd\"}, s)\n\t})\n\n\tt.Run(\"VisitAllHeaders\", func(t *testing.T) {\n\t\tc := NewContext(0)\n\t\tc.Request.Header.Set(\"xxx\", \"yyy\")\n\t\tc.Request.Header.Set(\"xxx2\", \"yyy2\")\n\t\tc.VisitAllHeaders(\n\t\t\tfunc(k, v []byte) {\n\t\t\t\tkey := string(k)\n\t\t\t\tvalue := string(v)\n\t\t\t\tif key != \"Xxx\" && key != \"Xxx2\" {\n\t\t\t\t\tt.Fatalf(\"Unexpected %v. Expected %v\", key, \"xxx or yyy\")\n\t\t\t\t}\n\t\t\t\tif key == \"Xxx\" && value != \"yyy\" {\n\t\t\t\t\tt.Fatalf(\"Unexpected %v. Expected %v\", value, \"yyy\")\n\t\t\t\t}\n\t\t\t\tif key == \"Xxx2\" && value != \"yyy2\" {\n\t\t\t\t\tt.Fatalf(\"Unexpected %v. Expected %v\", value, \"yyy2\")\n\t\t\t\t}\n\t\t\t})\n\t})\n}\n\nfunc BenchmarkInbuiltHandlerNameOperator(b *testing.B) {\n\tfor n := 0; n < b.N; n++ {\n\t\tfn := func(c context.Context, ctx *RequestContext) {\n\t\t}\n\t\tSetHandlerName(fn, fmt.Sprintf(\"%d\", n))\n\t\tGetHandlerName(fn)\n\t}\n}\n\nfunc BenchmarkConcurrentHandlerNameOperator(b *testing.B) {\n\tSetConcurrentHandlerNameOperator()\n\tdefer SetHandlerNameOperator(&inbuiltHandlerNameOperatorStruct{handlerNames: map[uintptr]string{}})\n\tfor n := 0; n < b.N; n++ {\n\t\tfn := func(c context.Context, ctx *RequestContext) {\n\t\t}\n\t\tSetHandlerName(fn, fmt.Sprintf(\"%d\", n))\n\t\tGetHandlerName(fn)\n\t}\n}\n"
  },
  {
    "path": "pkg/app/fs.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage app\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"context\"\n\t\"fmt\"\n\t\"html\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"mime\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/internal/nocopy\"\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/common/compress\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nvar (\n\terrDirIndexRequired   = errors.NewPublic(\"directory index required\")\n\terrNoCreatePermission = errors.NewPublic(\"no 'create file' permissions\")\n\n\trootFSOnce sync.Once\n\trootFS     = &FS{\n\t\tRoot:               \"/\",\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t\tAcceptByteRange:    true,\n\t}\n\trootFSHandler  HandlerFunc\n\tstrInvalidHost = []byte(\"invalid-host\")\n)\n\n// PathRewriteFunc must return new request path based on arbitrary ctx\n// info such as ctx.Path().\n//\n// Path rewriter is used in FS for translating the current request\n// to the local filesystem path relative to FS.Root.\n//\n// The returned path must not contain '/../' substrings due to security reasons,\n// since such paths may refer files outside FS.Root.\n//\n// The returned path may refer to ctx members. For example, ctx.Path().\ntype PathRewriteFunc func(ctx *RequestContext) []byte\n\n// FS represents settings for request handler serving static files\n// from the local filesystem.\n//\n// It is prohibited copying FS values. Create new values instead.\ntype FS struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\t// Path to the root directory to serve files from.\n\tRoot string\n\n\t// List of index file names to try opening during directory access.\n\t//\n\t// For example:\n\t//\n\t//     * index.html\n\t//     * index.htm\n\t//     * my-super-index.xml\n\t//\n\t// By default the list is empty.\n\tIndexNames []string\n\n\t// Index pages for directories without files matching IndexNames\n\t// are automatically generated if set.\n\t//\n\t// Directory index generation may be quite slow for directories\n\t// with many files (more than 1K), so it is discouraged enabling\n\t// index pages' generation for such directories.\n\t//\n\t// By default index pages aren't generated.\n\tGenerateIndexPages bool\n\n\t// Transparently compresses responses if set to true.\n\t//\n\t// The server tries minimizing CPU usage by caching compressed files.\n\t// It adds CompressedFileSuffix suffix to the original file name and\n\t// tries saving the resulting compressed file under the new file name.\n\t// So it is advisable to give the server write access to Root\n\t// and to all inner folders in order to minimize CPU usage when serving\n\t// compressed responses.\n\t//\n\t// Transparent compression is disabled by default.\n\tCompress bool\n\n\t// Enables byte range requests if set to true.\n\t//\n\t// Byte range requests are disabled by default.\n\tAcceptByteRange bool\n\n\t// Path rewriting function.\n\t//\n\t// By default request path is not modified.\n\tPathRewrite PathRewriteFunc\n\n\t// PathNotFound fires when file is not found in filesystem\n\t// this functions tries to replace \"Cannot open requested path\"\n\t// server response giving to the programmer the control of server flow.\n\t//\n\t// By default PathNotFound returns\n\t// \"Cannot open requested path\"\n\tPathNotFound HandlerFunc\n\n\t// Expiration duration for inactive file handlers.\n\t//\n\t// FSHandlerCacheDuration is used by default.\n\tCacheDuration time.Duration\n\n\t// Suffix to add to the name of cached compressed file.\n\t//\n\t// This value has sense only if Compress is set.\n\t//\n\t// FSCompressedFileSuffix is used by default.\n\tCompressedFileSuffix string\n\n\tonce sync.Once\n\th    HandlerFunc\n}\n\ntype byteRangeUpdater interface {\n\tUpdateByteRange(startPos, endPos int) error\n}\n\ntype fsSmallFileReader struct {\n\tff       *fsFile\n\tstartPos int\n\tendPos   int\n}\n\nfunc (r *fsSmallFileReader) Close() error {\n\tff := r.ff\n\tff.decReadersCount()\n\tr.ff = nil\n\tr.startPos = 0\n\tr.endPos = 0\n\tff.h.smallFileReaderPool.Put(r)\n\treturn nil\n}\n\nfunc (r *fsSmallFileReader) UpdateByteRange(startPos, endPos int) error {\n\tr.startPos = startPos\n\tr.endPos = endPos + 1\n\treturn nil\n}\n\nfunc (r *fsSmallFileReader) Read(p []byte) (int, error) {\n\ttailLen := r.endPos - r.startPos\n\tif tailLen <= 0 {\n\t\treturn 0, io.EOF\n\t}\n\tif len(p) > tailLen {\n\t\tp = p[:tailLen]\n\t}\n\n\tff := r.ff\n\tif ff.f != nil {\n\t\tn, err := ff.f.ReadAt(p, int64(r.startPos))\n\t\tr.startPos += n\n\t\treturn n, err\n\t}\n\n\tn := copy(p, ff.dirIndex[r.startPos:])\n\tr.startPos += n\n\treturn n, nil\n}\n\nfunc (r *fsSmallFileReader) WriteTo(w io.Writer) (int64, error) {\n\tff := r.ff\n\n\tvar n int\n\tvar err error\n\tif ff.f == nil {\n\t\tn, err = w.Write(ff.dirIndex[r.startPos:r.endPos])\n\t\treturn int64(n), err\n\t}\n\n\tif rf, ok := w.(io.ReaderFrom); ok {\n\t\treturn rf.ReadFrom(r)\n\t}\n\n\tcurPos := r.startPos\n\tbufv := utils.CopyBufPool.Get()\n\tbuf := bufv.([]byte)\n\tfor err == nil {\n\t\ttailLen := r.endPos - curPos\n\t\tif tailLen <= 0 {\n\t\t\tbreak\n\t\t}\n\t\tif len(buf) > tailLen {\n\t\t\tbuf = buf[:tailLen]\n\t\t}\n\t\tn, err = ff.f.ReadAt(buf, int64(curPos))\n\t\tnw, errw := w.Write(buf[:n])\n\t\tcurPos += nw\n\t\tif errw == nil && nw != n {\n\t\t\tpanic(\"BUG: Write(p) returned (n, nil), where n != len(p)\")\n\t\t}\n\t\tif err == nil {\n\t\t\terr = errw\n\t\t}\n\t}\n\tutils.CopyBufPool.Put(bufv)\n\n\tif err == io.EOF {\n\t\terr = nil\n\t}\n\treturn int64(curPos - r.startPos), err\n}\n\n// ServeFile returns HTTP response containing compressed file contents\n// from the given path.\n//\n// HTTP response may contain uncompressed file contents in the following cases:\n//\n//   - Missing 'Accept-Encoding: gzip' request header.\n//   - No write access to directory containing the file.\n//\n// Directory contents is returned if path points to directory.\n//\n// Use ServeFileUncompressed is you don't need serving compressed file contents.\nfunc ServeFile(ctx *RequestContext, path string) {\n\trootFSOnce.Do(func() {\n\t\trootFSHandler = rootFS.NewRequestHandler()\n\t})\n\tif len(path) == 0 || path[0] != '/' {\n\t\t// extend relative path to absolute path\n\t\tvar err error\n\t\tif path, err = filepath.Abs(path); err != nil {\n\t\t\thlog.SystemLogger().Errorf(\"Cannot resolve path=%q to absolute file error=%s\", path, err)\n\t\t\tctx.AbortWithMsg(\"Internal Server Error\", consts.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t}\n\tctx.Request.SetRequestURI(path)\n\trootFSHandler(context.Background(), ctx)\n}\n\n// NewRequestHandler returns new request handler with the given FS settings.\n//\n// The returned handler caches requested file handles\n// for FS.CacheDuration.\n// Make sure your program has enough 'max open files' limit aka\n// 'ulimit -n' if FS.Root folder contains many files.\n//\n// Do not create multiple request handlers from a single FS instance -\n// just reuse a single request handler.\nfunc (fs *FS) NewRequestHandler() HandlerFunc {\n\tfs.once.Do(fs.initRequestHandler)\n\treturn fs.h\n}\n\nfunc (fs *FS) initRequestHandler() {\n\troot := fs.Root\n\n\t// serve files from the current working directory if root is empty\n\tif len(root) == 0 {\n\t\troot = \".\"\n\t}\n\n\t// strip trailing slashes from the root path\n\tfor len(root) > 0 && root[len(root)-1] == '/' {\n\t\troot = root[:len(root)-1]\n\t}\n\n\tcacheDuration := fs.CacheDuration\n\tif cacheDuration <= 0 {\n\t\tcacheDuration = consts.FSHandlerCacheDuration\n\t}\n\tcompressedFileSuffix := fs.CompressedFileSuffix\n\tif len(compressedFileSuffix) == 0 {\n\t\tcompressedFileSuffix = consts.FSCompressedFileSuffix\n\t}\n\n\th := &fsHandler{\n\t\troot:                 root,\n\t\tindexNames:           fs.IndexNames,\n\t\tpathRewrite:          fs.PathRewrite,\n\t\tgenerateIndexPages:   fs.GenerateIndexPages,\n\t\tcompress:             fs.Compress,\n\t\tpathNotFound:         fs.PathNotFound,\n\t\tacceptByteRange:      fs.AcceptByteRange,\n\t\tcacheDuration:        cacheDuration,\n\t\tcompressedFileSuffix: compressedFileSuffix,\n\t\tcache:                make(map[string]*fsFile),\n\t\tcompressedCache:      make(map[string]*fsFile),\n\t}\n\n\tgo func() {\n\t\tvar pendingFiles []*fsFile\n\t\tfor {\n\t\t\ttime.Sleep(cacheDuration / 2)\n\t\t\tpendingFiles = h.cleanCache(pendingFiles)\n\t\t}\n\t}()\n\n\tfs.h = h.handleRequest\n}\n\ntype fsHandler struct {\n\troot                 string\n\tindexNames           []string\n\tpathRewrite          PathRewriteFunc\n\tpathNotFound         HandlerFunc\n\tgenerateIndexPages   bool\n\tcompress             bool\n\tacceptByteRange      bool\n\tcacheDuration        time.Duration\n\tcompressedFileSuffix string\n\n\tcache           map[string]*fsFile\n\tcompressedCache map[string]*fsFile\n\tcacheLock       sync.Mutex\n\n\tsmallFileReaderPool sync.Pool\n}\n\n// bigFileReader attempts to trigger sendfile\n// for sending big files over the wire.\ntype bigFileReader struct {\n\tf  *os.File\n\tff *fsFile\n\tr  io.Reader\n\tlr io.LimitedReader\n}\n\nfunc (r *bigFileReader) UpdateByteRange(startPos, endPos int) error {\n\tif _, err := r.f.Seek(int64(startPos), 0); err != nil {\n\t\treturn err\n\t}\n\tr.r = &r.lr\n\tr.lr.R = r.f\n\tr.lr.N = int64(endPos - startPos + 1)\n\treturn nil\n}\n\nfunc (r *bigFileReader) Read(p []byte) (int, error) {\n\treturn r.r.Read(p)\n}\n\nfunc (r *bigFileReader) WriteTo(w io.Writer) (int64, error) {\n\tif rf, ok := w.(io.ReaderFrom); ok {\n\t\t// fast path. Sendfile must be triggered\n\t\treturn rf.ReadFrom(r.r)\n\t}\n\tzw := network.NewWriter(w)\n\t// slow pathw\n\treturn utils.CopyZeroAlloc(zw, r.r)\n}\n\nfunc (r *bigFileReader) Close() error {\n\tr.r = r.f\n\tn, err := r.f.Seek(0, 0)\n\tif err == nil {\n\t\tif n != 0 {\n\t\t\tpanic(\"BUG: File.Seek(0,0) returned (non-zero, nil)\")\n\t\t}\n\n\t\tff := r.ff\n\t\tff.bigFilesLock.Lock()\n\t\tff.bigFiles = append(ff.bigFiles, r)\n\t\tff.bigFilesLock.Unlock()\n\t} else {\n\t\tr.f.Close()\n\t}\n\tr.ff.decReadersCount()\n\treturn err\n}\n\nfunc (h *fsHandler) cleanCache(pendingFiles []*fsFile) []*fsFile {\n\tvar filesToRelease []*fsFile\n\n\th.cacheLock.Lock()\n\n\t// Close files which couldn't be closed before due to non-zero\n\t// readers count on the previous run.\n\tvar remainingFiles []*fsFile\n\tfor _, ff := range pendingFiles {\n\t\tif ff.readersCount > 0 {\n\t\t\tremainingFiles = append(remainingFiles, ff)\n\t\t} else {\n\t\t\tfilesToRelease = append(filesToRelease, ff)\n\t\t}\n\t}\n\tpendingFiles = remainingFiles\n\n\tpendingFiles, filesToRelease = cleanCacheNolock(h.cache, pendingFiles, filesToRelease, h.cacheDuration)\n\tpendingFiles, filesToRelease = cleanCacheNolock(h.compressedCache, pendingFiles, filesToRelease, h.cacheDuration)\n\n\th.cacheLock.Unlock()\n\n\tfor _, ff := range filesToRelease {\n\t\tff.Release()\n\t}\n\n\treturn pendingFiles\n}\n\nfunc (h *fsHandler) compressAndOpenFSFile(filePath string) (*fsFile, error) {\n\tf, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfileInfo, err := f.Stat()\n\tif err != nil {\n\t\tf.Close()\n\t\treturn nil, fmt.Errorf(\"cannot obtain info for file %q: %s\", filePath, err)\n\t}\n\n\tif fileInfo.IsDir() {\n\t\tf.Close()\n\t\treturn nil, errDirIndexRequired\n\t}\n\n\tif strings.HasSuffix(filePath, h.compressedFileSuffix) ||\n\t\tfileInfo.Size() > consts.FsMaxCompressibleFileSize ||\n\t\t!isFileCompressible(f, consts.FsMinCompressRatio) {\n\t\treturn h.newFSFile(f, fileInfo, false)\n\t}\n\n\tcompressedFilePath := filePath + h.compressedFileSuffix\n\tabsPath, err := filepath.Abs(compressedFilePath)\n\tif err != nil {\n\t\tf.Close()\n\t\treturn nil, fmt.Errorf(\"cannot determine absolute path for %q: %s\", compressedFilePath, err)\n\t}\n\n\tflock := getFileLock(absPath)\n\tflock.Lock()\n\tff, err := h.compressFileNolock(f, fileInfo, filePath, compressedFilePath)\n\tflock.Unlock()\n\n\treturn ff, err\n}\n\nfunc (h *fsHandler) newCompressedFSFile(filePath string) (*fsFile, error) {\n\tf, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot open compressed file %q: %s\", filePath, err)\n\t}\n\tfileInfo, err := f.Stat()\n\tif err != nil {\n\t\tf.Close()\n\t\treturn nil, fmt.Errorf(\"cannot obtain info for compressed file %q: %s\", filePath, err)\n\t}\n\treturn h.newFSFile(f, fileInfo, true)\n}\n\nfunc (h *fsHandler) compressFileNolock(f *os.File, fileInfo os.FileInfo, filePath, compressedFilePath string) (*fsFile, error) {\n\t// Attempt to open compressed file created by another concurrent\n\t// goroutine.\n\t// It is safe opening such a file, since the file creation\n\t// is guarded by file mutex - see getFileLock call.\n\tif _, err := os.Stat(compressedFilePath); err == nil {\n\t\tf.Close()\n\t\treturn h.newCompressedFSFile(compressedFilePath)\n\t}\n\n\t// Create temporary file, so concurrent goroutines don't use\n\t// it until it is created.\n\ttmpFilePath := compressedFilePath + \".tmp\"\n\tzf, err := os.Create(tmpFilePath)\n\tif err != nil {\n\t\tf.Close()\n\t\tif !os.IsPermission(err) {\n\t\t\treturn nil, fmt.Errorf(\"cannot create temporary file %q: %s\", tmpFilePath, err)\n\t\t}\n\t\treturn nil, errNoCreatePermission\n\t}\n\n\tzw := compress.AcquireStacklessGzipWriter(zf, compress.CompressDefaultCompression)\n\tzrw := network.NewWriter(zw)\n\t_, err = utils.CopyZeroAlloc(zrw, f)\n\tif err1 := zw.Flush(); err == nil {\n\t\terr = err1\n\t}\n\tcompress.ReleaseStacklessGzipWriter(zw, compress.CompressDefaultCompression)\n\tzf.Close()\n\tf.Close()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error when compressing file %q to %q: %s\", filePath, tmpFilePath, err)\n\t}\n\tif err = os.Chtimes(tmpFilePath, time.Now(), fileInfo.ModTime()); err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot change modification time to %s for tmp file %q: %s\",\n\t\t\tfileInfo.ModTime(), tmpFilePath, err)\n\t}\n\tif err = os.Rename(tmpFilePath, compressedFilePath); err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot move compressed file from %q to %q: %s\", tmpFilePath, compressedFilePath, err)\n\t}\n\treturn h.newCompressedFSFile(compressedFilePath)\n}\n\nfunc (h *fsHandler) openFSFile(filePath string, mustCompress bool) (*fsFile, error) {\n\tfilePathOriginal := filePath\n\tif mustCompress {\n\t\tfilePath += h.compressedFileSuffix\n\t}\n\n\tf, err := os.Open(filePath)\n\tif err != nil {\n\t\tif mustCompress && os.IsNotExist(err) {\n\t\t\treturn h.compressAndOpenFSFile(filePathOriginal)\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tfileInfo, err := f.Stat()\n\tif err != nil {\n\t\tf.Close()\n\t\treturn nil, fmt.Errorf(\"cannot obtain info for file %q: %s\", filePath, err)\n\t}\n\n\tif fileInfo.IsDir() {\n\t\tf.Close()\n\t\tif mustCompress {\n\t\t\treturn nil, fmt.Errorf(\"directory with unexpected suffix found: %q. Suffix: %q\",\n\t\t\t\tfilePath, h.compressedFileSuffix)\n\t\t}\n\t\treturn nil, errDirIndexRequired\n\t}\n\n\tif mustCompress {\n\t\tfileInfoOriginal, err := os.Stat(filePathOriginal)\n\t\tif err != nil {\n\t\t\tf.Close()\n\t\t\treturn nil, fmt.Errorf(\"cannot obtain info for original file %q: %s\", filePathOriginal, err)\n\t\t}\n\n\t\tif fileInfoOriginal.ModTime() != fileInfo.ModTime() {\n\t\t\t// The compressed file became stale. Re-create it.\n\t\t\tf.Close()\n\t\t\tos.Remove(filePath)\n\t\t\treturn h.compressAndOpenFSFile(filePathOriginal)\n\t\t}\n\t}\n\n\treturn h.newFSFile(f, fileInfo, mustCompress)\n}\n\nfunc (h *fsHandler) newFSFile(f *os.File, fileInfo os.FileInfo, compressed bool) (*fsFile, error) {\n\tn := fileInfo.Size()\n\tcontentLength := int(n)\n\tif n != int64(contentLength) {\n\t\tf.Close()\n\t\treturn nil, fmt.Errorf(\"too big file: %d bytes\", n)\n\t}\n\n\t// detect content-type\n\text := fileExtension(fileInfo.Name(), compressed, h.compressedFileSuffix)\n\tcontentType := mime.TypeByExtension(ext)\n\tif len(contentType) == 0 {\n\t\tdata, err := readFileHeader(f, compressed)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot read header of the file %q: %s\", f.Name(), err)\n\t\t}\n\t\tcontentType = http.DetectContentType(data)\n\t}\n\n\tlastModified := fileInfo.ModTime()\n\tff := &fsFile{\n\t\th:               h,\n\t\tf:               f,\n\t\tcontentType:     contentType,\n\t\tcontentLength:   contentLength,\n\t\tcompressed:      compressed,\n\t\tlastModified:    lastModified,\n\t\tlastModifiedStr: bytesconv.AppendHTTPDate(make([]byte, 0, len(http.TimeFormat)), lastModified),\n\n\t\tt: time.Now(),\n\t}\n\treturn ff, nil\n}\n\nfunc (h *fsHandler) createDirIndex(base *protocol.URI, dirPath string, mustCompress bool) (*fsFile, error) {\n\tw := &bytebufferpool.ByteBuffer{}\n\n\tbasePathEscaped := html.EscapeString(string(base.Path()))\n\tfmt.Fprintf(w, \"<html><head><title>%s</title><style>.dir { font-weight: bold }</style></head><body>\", basePathEscaped)\n\tfmt.Fprintf(w, \"<h1>%s</h1>\", basePathEscaped)\n\tfmt.Fprintf(w, \"<ul>\")\n\n\tif len(basePathEscaped) > 1 {\n\t\tvar parentURI protocol.URI\n\t\tbase.CopyTo(&parentURI)\n\t\tparentURI.Update(string(base.Path()) + \"/..\")\n\t\tparentPathEscaped := html.EscapeString(string(parentURI.Path()))\n\t\tfmt.Fprintf(w, `<li><a href=\"%s\" class=\"dir\">..</a></li>`, parentPathEscaped)\n\t}\n\n\tf, err := os.Open(dirPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfileinfos, err := f.Readdir(0)\n\tf.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfm := make(map[string]os.FileInfo, len(fileinfos))\n\tfilenames := make([]string, 0, len(fileinfos))\n\tfor _, fi := range fileinfos {\n\t\tname := fi.Name()\n\t\tif strings.HasSuffix(name, h.compressedFileSuffix) {\n\t\t\t// Do not show compressed files on index page.\n\t\t\tcontinue\n\t\t}\n\t\tfm[name] = fi\n\t\tfilenames = append(filenames, name)\n\t}\n\n\tvar u protocol.URI\n\tbase.CopyTo(&u)\n\tu.Update(string(u.Path()) + \"/\")\n\n\tsort.Strings(filenames)\n\tfor _, name := range filenames {\n\t\tu.Update(name)\n\t\tpathEscaped := html.EscapeString(string(u.Path()))\n\t\tfi := fm[name]\n\t\tauxStr := \"dir\"\n\t\tclassName := \"dir\"\n\t\tif !fi.IsDir() {\n\t\t\tauxStr = fmt.Sprintf(\"file, %d bytes\", fi.Size())\n\t\t\tclassName = \"file\"\n\t\t}\n\t\tfmt.Fprintf(w, `<li><a href=\"%s\" class=\"%s\">%s</a>, %s, last modified %s</li>`,\n\t\t\tpathEscaped, className, html.EscapeString(name), auxStr, fsModTime(fi.ModTime()))\n\t}\n\n\tfmt.Fprintf(w, \"</ul></body></html>\")\n\tif mustCompress {\n\t\tvar zbuf bytebufferpool.ByteBuffer\n\t\tzbuf.B = compress.AppendGzipBytesLevel(zbuf.B, w.B, compress.CompressDefaultCompression)\n\t\tw = &zbuf\n\t}\n\n\tdirIndex := w.B\n\tlastModified := time.Now()\n\tff := &fsFile{\n\t\th:               h,\n\t\tdirIndex:        dirIndex,\n\t\tcontentType:     \"text/html; charset=utf-8\",\n\t\tcontentLength:   len(dirIndex),\n\t\tcompressed:      mustCompress,\n\t\tlastModified:    lastModified,\n\t\tlastModifiedStr: bytesconv.AppendHTTPDate(make([]byte, 0, len(http.TimeFormat)), lastModified),\n\n\t\tt: lastModified,\n\t}\n\treturn ff, nil\n}\n\nfunc (h *fsHandler) openIndexFile(ctx *RequestContext, dirPath string, mustCompress bool) (*fsFile, error) {\n\tfor _, indexName := range h.indexNames {\n\t\tindexFilePath := dirPath + \"/\" + indexName\n\t\tff, err := h.openFSFile(indexFilePath, mustCompress)\n\t\tif err == nil {\n\t\t\treturn ff, nil\n\t\t}\n\t\tif !os.IsNotExist(err) {\n\t\t\treturn nil, fmt.Errorf(\"cannot open file %q: %s\", indexFilePath, err)\n\t\t}\n\t}\n\n\tif !h.generateIndexPages {\n\t\treturn nil, fmt.Errorf(\"cannot access directory without index page. Directory %q\", dirPath)\n\t}\n\n\treturn h.createDirIndex(ctx.URI(), dirPath, mustCompress)\n}\n\nfunc (ff *fsFile) decReadersCount() {\n\tff.h.cacheLock.Lock()\n\tdefer ff.h.cacheLock.Unlock()\n\tff.readersCount--\n\tif ff.readersCount < 0 {\n\t\tpanic(\"BUG: negative fsFile.readersCount!\")\n\t}\n}\n\nfunc (ff *fsFile) bigFileReader() (io.Reader, error) {\n\tif ff.f == nil {\n\t\tpanic(\"BUG: ff.f must be non-nil in bigFileReader\")\n\t}\n\n\tvar r io.Reader\n\n\tff.bigFilesLock.Lock()\n\tn := len(ff.bigFiles)\n\tif n > 0 {\n\t\tr = ff.bigFiles[n-1]\n\t\tff.bigFiles = ff.bigFiles[:n-1]\n\t}\n\tff.bigFilesLock.Unlock()\n\n\tif r != nil {\n\t\treturn r, nil\n\t}\n\n\tf, err := os.Open(ff.f.Name())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot open already opened file: %s\", err)\n\t}\n\treturn &bigFileReader{\n\t\tf:  f,\n\t\tff: ff,\n\t\tr:  f,\n\t}, nil\n}\n\nfunc (ff *fsFile) NewReader() (io.Reader, error) {\n\tif ff.isBig() {\n\t\tr, err := ff.bigFileReader()\n\t\tif err != nil {\n\t\t\tff.decReadersCount()\n\t\t}\n\t\treturn r, err\n\t}\n\treturn ff.smallFileReader(), nil\n}\n\nfunc (ff *fsFile) smallFileReader() io.Reader {\n\tv := ff.h.smallFileReaderPool.Get()\n\tif v == nil {\n\t\tv = &fsSmallFileReader{}\n\t}\n\tr := v.(*fsSmallFileReader)\n\tr.ff = ff\n\tr.endPos = ff.contentLength\n\tif r.startPos > 0 {\n\t\tpanic(\"BUG: fsSmallFileReader with non-nil startPos found in the pool\")\n\t}\n\treturn r\n}\n\nfunc (h *fsHandler) handleRequest(c context.Context, ctx *RequestContext) {\n\tvar path []byte\n\tif h.pathRewrite != nil {\n\t\tpath = h.pathRewrite(ctx)\n\t} else {\n\t\tpath = ctx.Path()\n\t}\n\tpath = stripTrailingSlashes(path)\n\n\tif n := bytes.IndexByte(path, 0); n >= 0 {\n\t\thlog.SystemLogger().Errorf(\"Cannot serve path with nil byte at position=%d, path=%q\", n, path)\n\t\tctx.AbortWithMsg(\"Are you a hacker?\", consts.StatusBadRequest)\n\t\treturn\n\t}\n\tif h.pathRewrite != nil {\n\t\t// There is no need to check for '/../' if path = ctx.Path(),\n\t\t// since ctx.Path must normalize and sanitize the path.\n\n\t\tif n := bytes.Index(path, bytestr.StrSlashDotDotSlash); n >= 0 {\n\t\t\thlog.SystemLogger().Errorf(\"Cannot serve path with '/../' at position=%d due to security reasons, path=%q\", n, path)\n\t\t\tctx.AbortWithMsg(\"Internal Server Error\", consts.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t}\n\n\tmustCompress := false\n\tfileCache := h.cache\n\tbyteRange := ctx.Request.Header.PeekRange()\n\tif len(byteRange) == 0 && h.compress && ctx.Request.Header.HasAcceptEncodingBytes(bytestr.StrGzip) {\n\t\tmustCompress = true\n\t\tfileCache = h.compressedCache\n\t}\n\n\th.cacheLock.Lock()\n\tff, ok := fileCache[string(path)]\n\tif ok {\n\t\tff.readersCount++\n\t}\n\th.cacheLock.Unlock()\n\n\tif !ok {\n\t\tpathStr := string(path)\n\t\tfilePath := h.root + pathStr\n\t\tvar err error\n\t\tff, err = h.openFSFile(filePath, mustCompress)\n\n\t\tif mustCompress && err == errNoCreatePermission {\n\t\t\thlog.SystemLogger().Errorf(\"Insufficient permissions for saving compressed file for path=%q. Serving uncompressed file. \"+\n\t\t\t\t\"Allow write access to the directory with this file in order to improve hertz performance\", filePath)\n\t\t\tmustCompress = false\n\t\t\tff, err = h.openFSFile(filePath, mustCompress)\n\t\t}\n\t\tif err == errDirIndexRequired {\n\t\t\tff, err = h.openIndexFile(ctx, filePath, mustCompress)\n\t\t\tif err != nil {\n\t\t\t\thlog.SystemLogger().Errorf(\"Cannot open dir index, path=%q, error=%s\", filePath, err)\n\t\t\t\tctx.AbortWithMsg(\"Directory index is forbidden\", consts.StatusForbidden)\n\t\t\t\treturn\n\t\t\t}\n\t\t} else if err != nil {\n\t\t\thlog.SystemLogger().Errorf(\"Cannot open file=%q, error=%s\", filePath, err)\n\t\t\tif h.pathNotFound == nil {\n\t\t\t\tctx.AbortWithMsg(\"Cannot open requested path\", consts.StatusNotFound)\n\t\t\t} else {\n\t\t\t\tctx.SetStatusCode(consts.StatusNotFound)\n\t\t\t\th.pathNotFound(c, ctx)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\th.cacheLock.Lock()\n\t\tff1, ok := fileCache[pathStr]\n\t\tif !ok {\n\t\t\tfileCache[pathStr] = ff\n\t\t\tff.readersCount++\n\t\t} else {\n\t\t\tff1.readersCount++\n\t\t}\n\t\th.cacheLock.Unlock()\n\n\t\tif ok {\n\t\t\t// The file has been already opened by another\n\t\t\t// goroutine, so close the current file and use\n\t\t\t// the file opened by another goroutine instead.\n\t\t\tff.Release()\n\t\t\tff = ff1\n\t\t}\n\t}\n\n\tif !ctx.IfModifiedSince(ff.lastModified) {\n\t\tff.decReadersCount()\n\t\tctx.NotModified()\n\t\treturn\n\t}\n\n\tr, err := ff.NewReader()\n\tif err != nil {\n\t\thlog.SystemLogger().Errorf(\"Cannot obtain file reader for path=%q, error=%s\", path, err)\n\t\tctx.AbortWithMsg(\"Internal Server Error\", consts.StatusInternalServerError)\n\t\treturn\n\t}\n\n\thdr := &ctx.Response.Header\n\tif ff.compressed {\n\t\thdr.SetContentEncodingBytes(bytestr.StrGzip)\n\t}\n\n\tstatusCode := consts.StatusOK\n\tcontentLength := ff.contentLength\n\tif h.acceptByteRange {\n\t\thdr.SetCanonical(bytestr.StrAcceptRanges, bytestr.StrBytes)\n\t\tif len(byteRange) > 0 {\n\t\t\tstartPos, endPos, err := ParseByteRange(byteRange, contentLength)\n\t\t\tif err != nil {\n\t\t\t\tr.(io.Closer).Close()\n\t\t\t\thlog.SystemLogger().Errorf(\"Cannot parse byte range %q for path=%q,error=%s\", byteRange, path, err)\n\t\t\t\tctx.AbortWithMsg(\"Range Not Satisfiable\", consts.StatusRequestedRangeNotSatisfiable)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err = r.(byteRangeUpdater).UpdateByteRange(startPos, endPos); err != nil {\n\t\t\t\tr.(io.Closer).Close()\n\t\t\t\thlog.SystemLogger().Errorf(\"Cannot seek byte range %q for path=%q, error=%s\", byteRange, path, err)\n\t\t\t\tctx.AbortWithMsg(\"Internal Server Error\", consts.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\thdr.SetContentRange(startPos, endPos, contentLength)\n\t\t\tcontentLength = endPos - startPos + 1\n\t\t\tstatusCode = consts.StatusPartialContent\n\t\t}\n\t}\n\n\thdr.SetCanonical(bytestr.StrLastModified, ff.lastModifiedStr)\n\tif !ctx.IsHead() {\n\t\tctx.SetBodyStream(r, contentLength)\n\t} else {\n\t\tctx.Response.ResetBody()\n\t\tctx.Response.SkipBody = true\n\t\tctx.Response.Header.SetContentLength(contentLength)\n\t\tif rc, ok := r.(io.Closer); ok {\n\t\t\tif err := rc.Close(); err != nil {\n\t\t\t\thlog.SystemLogger().Errorf(\"Cannot close file reader: error=%s\", err)\n\t\t\t\tctx.AbortWithMsg(\"Internal Server Error\", consts.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\thdr.SetNoDefaultContentType(true)\n\tif len(hdr.ContentType()) == 0 {\n\t\tctx.SetContentType(ff.contentType)\n\t}\n\tctx.SetStatusCode(statusCode)\n}\n\ntype fsFile struct {\n\th             *fsHandler\n\tf             *os.File\n\tdirIndex      []byte\n\tcontentType   string\n\tcontentLength int\n\tcompressed    bool\n\n\tlastModified    time.Time\n\tlastModifiedStr []byte\n\n\tt            time.Time\n\treadersCount int\n\n\tbigFiles     []*bigFileReader\n\tbigFilesLock sync.Mutex\n}\n\nfunc (ff *fsFile) Release() {\n\tif ff.f != nil {\n\t\tff.f.Close()\n\n\t\tif ff.isBig() {\n\t\t\tff.bigFilesLock.Lock()\n\t\t\tfor _, r := range ff.bigFiles {\n\t\t\t\tr.f.Close()\n\t\t\t}\n\t\t\tff.bigFilesLock.Unlock()\n\t\t}\n\t}\n}\n\nfunc (ff *fsFile) isBig() bool {\n\treturn ff.contentLength > consts.MaxSmallFileSize && len(ff.dirIndex) == 0\n}\n\nfunc cleanCacheNolock(cache map[string]*fsFile, pendingFiles, filesToRelease []*fsFile, cacheDuration time.Duration) ([]*fsFile, []*fsFile) {\n\tt := time.Now()\n\tfor k, ff := range cache {\n\t\tif t.Sub(ff.t) > cacheDuration {\n\t\t\tif ff.readersCount > 0 {\n\t\t\t\t// There are pending readers on stale file handle,\n\t\t\t\t// so we cannot close it. Put it into pendingFiles\n\t\t\t\t// so it will be closed later.\n\t\t\t\tpendingFiles = append(pendingFiles, ff)\n\t\t\t} else {\n\t\t\t\tfilesToRelease = append(filesToRelease, ff)\n\t\t\t}\n\t\t\tdelete(cache, k)\n\t\t}\n\t}\n\treturn pendingFiles, filesToRelease\n}\n\nfunc stripTrailingSlashes(path []byte) []byte {\n\tfor len(path) > 0 && path[len(path)-1] == '/' {\n\t\tpath = path[:len(path)-1]\n\t}\n\treturn path\n}\n\nfunc isFileCompressible(f *os.File, minCompressRatio float64) bool {\n\t// Try compressing the first 4kb of the file\n\t// and see if it can be compressed by more than\n\t// the given minCompressRatio.\n\tb := bytebufferpool.Get()\n\tzw := compress.AcquireStacklessGzipWriter(b, compress.CompressDefaultCompression)\n\tlr := &io.LimitedReader{\n\t\tR: f,\n\t\tN: 4096,\n\t}\n\tzrw := network.NewWriter(zw)\n\t_, err := utils.CopyZeroAlloc(zrw, lr)\n\tcompress.ReleaseStacklessGzipWriter(zw, compress.CompressDefaultCompression)\n\tf.Seek(0, 0) //nolint:errcheck\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tn := 4096 - lr.N\n\tzn := len(b.B)\n\tbytebufferpool.Put(b)\n\treturn float64(zn) < float64(n)*minCompressRatio\n}\n\nvar (\n\tfilesLockMap     = make(map[string]*sync.Mutex)\n\tfilesLockMapLock sync.Mutex\n)\n\nfunc getFileLock(absPath string) *sync.Mutex {\n\tfilesLockMapLock.Lock()\n\tflock := filesLockMap[absPath]\n\tif flock == nil {\n\t\tflock = &sync.Mutex{}\n\t\tfilesLockMap[absPath] = flock\n\t}\n\tfilesLockMapLock.Unlock()\n\treturn flock\n}\n\nfunc fileExtension(path string, compressed bool, compressedFileSuffix string) string {\n\tif compressed && strings.HasSuffix(path, compressedFileSuffix) {\n\t\tpath = path[:len(path)-len(compressedFileSuffix)]\n\t}\n\tn := strings.LastIndexByte(path, '.')\n\tif n < 0 {\n\t\treturn \"\"\n\t}\n\treturn path[n:]\n}\n\nfunc readFileHeader(f *os.File, compressed bool) ([]byte, error) {\n\tr := io.Reader(f)\n\tvar zr *gzip.Reader\n\tif compressed {\n\t\tvar err error\n\t\tif zr, err = compress.AcquireGzipReader(f); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr = zr\n\t}\n\n\tlr := &io.LimitedReader{\n\t\tR: r,\n\t\tN: 512,\n\t}\n\tdata, err := ioutil.ReadAll(lr)\n\tif _, err := f.Seek(0, 0); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif zr != nil {\n\t\tcompress.ReleaseGzipReader(zr)\n\t}\n\n\treturn data, err\n}\n\nfunc fsModTime(t time.Time) time.Time {\n\treturn t.In(time.UTC).Truncate(time.Second)\n}\n\n// ParseByteRange parses 'Range: bytes=...' header value.\n//\n// It follows https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 .\nfunc ParseByteRange(byteRange []byte, contentLength int) (startPos, endPos int, err error) {\n\tb := byteRange\n\tif !bytes.HasPrefix(b, bytestr.StrBytes) {\n\t\treturn 0, 0, fmt.Errorf(\"unsupported range units: %q. Expecting %q\", byteRange, bytestr.StrBytes)\n\t}\n\n\tb = b[len(bytestr.StrBytes):]\n\tif len(b) == 0 || b[0] != '=' {\n\t\treturn 0, 0, fmt.Errorf(\"missing byte range in %q\", byteRange)\n\t}\n\tb = b[1:]\n\n\tn := bytes.IndexByte(b, '-')\n\tif n < 0 {\n\t\treturn 0, 0, fmt.Errorf(\"missing the end position of byte range in %q\", byteRange)\n\t}\n\n\tif n == 0 {\n\t\tv, err := bytesconv.ParseUint(b[n+1:])\n\t\tif err != nil {\n\t\t\treturn 0, 0, err\n\t\t}\n\t\tstartPos := contentLength - v\n\t\tif startPos < 0 {\n\t\t\tstartPos = 0\n\t\t}\n\t\treturn startPos, contentLength - 1, nil\n\t}\n\n\tif startPos, err = bytesconv.ParseUint(b[:n]); err != nil {\n\t\treturn 0, 0, err\n\t}\n\tif startPos >= contentLength {\n\t\treturn 0, 0, fmt.Errorf(\"the start position of byte range cannot exceed %d. byte range %q\", contentLength-1, byteRange)\n\t}\n\n\tb = b[n+1:]\n\tif len(b) == 0 {\n\t\treturn startPos, contentLength - 1, nil\n\t}\n\n\tif endPos, err = bytesconv.ParseUint(b); err != nil {\n\t\treturn 0, 0, err\n\t}\n\tif endPos >= contentLength {\n\t\tendPos = contentLength - 1\n\t}\n\tif endPos < startPos {\n\t\treturn 0, 0, fmt.Errorf(\"the start position of byte range cannot exceed the end position. byte range %q\", byteRange)\n\t}\n\treturn startPos, endPos, nil\n}\n\n// NewVHostPathRewriter returns path rewriter, which strips slashesCount\n// leading slashes from the path and prepends the path with request's host,\n// thus simplifying virtual hosting for static files.\n//\n// Examples:\n//\n//   - host=foobar.com, slashesCount=0, original path=\"/foo/bar\".\n//     Resulting path: \"/foobar.com/foo/bar\"\n//\n//   - host=img.aaa.com, slashesCount=1, original path=\"/images/123/456.jpg\"\n//     Resulting path: \"/img.aaa.com/123/456.jpg\"\nfunc NewVHostPathRewriter(slashesCount int) PathRewriteFunc {\n\treturn func(ctx *RequestContext) []byte {\n\t\tpath := stripLeadingSlashes(ctx.Path(), slashesCount)\n\t\thost := ctx.Host()\n\t\tif n := bytes.IndexByte(host, '/'); n >= 0 {\n\t\t\thost = nil\n\t\t}\n\t\tif len(host) == 0 {\n\t\t\thost = strInvalidHost\n\t\t}\n\t\tb := bytebufferpool.Get()\n\t\tb.B = append(b.B, '/')\n\t\tb.B = append(b.B, host...)\n\t\tb.B = append(b.B, path...)\n\t\tctx.URI().SetPathBytes(b.B)\n\t\tbytebufferpool.Put(b)\n\n\t\treturn ctx.Path()\n\t}\n}\n\nfunc stripLeadingSlashes(path []byte, stripSlashes int) []byte {\n\tfor stripSlashes > 0 && len(path) > 0 {\n\t\tif path[0] != '/' {\n\t\t\tpanic(\"BUG: path must start with slash\")\n\t\t}\n\t\tn := bytes.IndexByte(path[1:], '/')\n\t\tif n < 0 {\n\t\t\tpath = path[:0]\n\t\t\tbreak\n\t\t}\n\t\tpath = path[n+1:]\n\t\tstripSlashes--\n\t}\n\treturn path\n}\n\n// ServeFileUncompressed returns HTTP response containing file contents\n// from the given path.\n//\n// Directory contents is returned if path points to directory.\n//\n// ServeFile may be used for saving network traffic when serving files\n// with good compression ratio.\nfunc ServeFileUncompressed(ctx *RequestContext, path string) {\n\tctx.Request.Header.DelBytes(bytestr.StrAcceptEncoding)\n\tServeFile(ctx, path)\n}\n\n// NewPathSlashesStripper returns path rewriter, which strips slashesCount\n// leading slashes from the path.\n//\n// Examples:\n//\n//   - slashesCount = 0, original path: \"/foo/bar\", result: \"/foo/bar\"\n//   - slashesCount = 1, original path: \"/foo/bar\", result: \"/bar\"\n//   - slashesCount = 2, original path: \"/foo/bar\", result: \"\"\n//\n// The returned path rewriter may be used as FS.PathRewrite .\nfunc NewPathSlashesStripper(slashesCount int) PathRewriteFunc {\n\treturn func(ctx *RequestContext) []byte {\n\t\treturn stripLeadingSlashes(ctx.Path(), slashesCount)\n\t}\n}\n"
  },
  {
    "path": "pkg/app/fs_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage app\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n)\n\nfunc TestNewVHostPathRewriter(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestContext\n\tvar req protocol.Request\n\treq.Header.SetHost(\"foobar.com\")\n\treq.SetRequestURI(\"/foo/bar/baz\")\n\treq.CopyTo(&ctx.Request)\n\n\tf := NewVHostPathRewriter(0)\n\tpath := f(&ctx)\n\texpectedPath := \"/foobar.com/foo/bar/baz\"\n\tif string(path) != expectedPath {\n\t\tt.Fatalf(\"unexpected path %q. Expecting %q\", path, expectedPath)\n\t}\n\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(\"https://aaa.bbb.cc/one/two/three/four?asdf=dsf\")\n\tf = NewVHostPathRewriter(2)\n\tpath = f(&ctx)\n\texpectedPath = \"/aaa.bbb.cc/three/four\"\n\tif string(path) != expectedPath {\n\t\tt.Fatalf(\"unexpected path %q. Expecting %q\", path, expectedPath)\n\t}\n}\n\nfunc TestNewVHostPathRewriterMaliciousHost(t *testing.T) {\n\tvar ctx RequestContext\n\tvar req protocol.Request\n\treq.Header.SetHost(\"/../../../etc/passwd\")\n\treq.SetRequestURI(\"/foo/bar/baz\")\n\treq.CopyTo(&ctx.Request)\n\n\tf := NewVHostPathRewriter(0)\n\tpath := f(&ctx)\n\texpectedPath := \"/invalid-host/foo/bar/baz\"\n\tif string(path) != expectedPath {\n\t\tt.Fatalf(\"unexpected path %q. Expecting %q\", path, expectedPath)\n\t}\n}\n\nfunc testPathNotFound(t *testing.T, pathNotFoundFunc HandlerFunc) {\n\tvar ctx RequestContext\n\tvar req protocol.Request\n\treq.SetRequestURI(\"http//some.url/file\")\n\treq.CopyTo(&ctx.Request)\n\n\tfs := &FS{\n\t\tRoot:         \"./\",\n\t\tPathNotFound: pathNotFoundFunc,\n\t}\n\tfs.NewRequestHandler()(context.Background(), &ctx)\n\n\tif pathNotFoundFunc == nil {\n\t\t// different to ...\n\t\tif !bytes.Equal(ctx.Response.Body(),\n\t\t\t[]byte(\"Cannot open requested path\")) {\n\t\t\tt.Fatalf(\"response defers. Response: %q\", ctx.Response.Body())\n\t\t}\n\t} else {\n\t\t// Equals to ...\n\t\tif bytes.Equal(ctx.Response.Body(),\n\t\t\t[]byte(\"Cannot open requested path\")) {\n\t\t\tt.Fatalf(\"response defers. Response: %q\", ctx.Response.Body())\n\t\t}\n\t}\n}\n\nfunc TestPathNotFound(t *testing.T) {\n\tt.Parallel()\n\n\ttestPathNotFound(t, nil)\n}\n\nfunc TestPathNotFoundFunc(t *testing.T) {\n\tt.Parallel()\n\n\ttestPathNotFound(t, func(c context.Context, ctx *RequestContext) {\n\t\tctx.WriteString(\"Not found hehe\") //nolint:errcheck\n\t})\n}\n\nfunc TestServeFileHead(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestContext\n\tvar req protocol.Request\n\treq.Header.SetMethod(consts.MethodHead)\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\treq.CopyTo(&ctx.Request)\n\n\tServeFile(&ctx, \"fs.go\")\n\n\tvar r protocol.Response\n\tr.SkipBody = true\n\ts := resp.GetHTTP1Response(&ctx.Response).String()\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := resp.Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tce := r.Header.ContentEncoding()\n\tif len(ce) > 0 {\n\t\tt.Fatalf(\"Unexpected 'Content-Encoding' %q\", ce)\n\t}\n\n\tbody := r.Body()\n\tif len(body) > 0 {\n\t\tt.Fatalf(\"unexpected response body %q. Expecting empty body\", body)\n\t}\n\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tcontentLength := r.Header.ContentLength()\n\tif contentLength != len(expectedBody) {\n\t\tt.Fatalf(\"unexpected Content-Length: %d. expecting %d\", contentLength, len(expectedBody))\n\t}\n}\n\nfunc TestServeFileSmallNoReadFrom(t *testing.T) {\n\tt.Parallel()\n\n\tteststr := \"hello, world!\"\n\n\ttempdir, err := ioutil.TempDir(\"\", \"httpexpect\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(tempdir)\n\n\tif err := ioutil.WriteFile(\n\t\tpath.Join(tempdir, \"hello\"), []byte(teststr), 0o666); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar ctx RequestContext\n\tvar req protocol.Request\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\treq.CopyTo(&ctx.Request)\n\n\tServeFile(&ctx, path.Join(tempdir, \"hello\"))\n\n\treader, ok := ctx.Response.BodyStream().(*fsSmallFileReader)\n\tif !ok {\n\t\tt.Fatal(\"expected fsSmallFileReader\")\n\t}\n\n\tbuf := bytes.NewBuffer(nil)\n\n\tn, err := reader.WriteTo(pureWriter{buf})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif n != int64(len(teststr)) {\n\t\tt.Fatalf(\"expected %d bytes, got %d bytes\", len(teststr), n)\n\t}\n\n\tbody := buf.String()\n\tif body != teststr {\n\t\tt.Fatalf(\"expected '%s'\", teststr)\n\t}\n\n\tdata := make([]byte, len([]byte(teststr)))\n\tnn, err := reader.Read(data)\n\tassert.DeepEqual(t, len([]byte(teststr)), nn)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, teststr, string(data))\n\tassert.DeepEqual(t, reader.startPos, len([]byte(teststr)))\n\n\tnn, err = reader.Read(data)\n\tassert.DeepEqual(t, 0, nn)\n\tassert.DeepEqual(t, io.EOF, err)\n\n\tdata1 := make([]byte, 2)\n\treader.startPos = len([]byte(teststr)) - 1\n\tnn, err = reader.Read(data1)\n\tassert.DeepEqual(t, []byte(\"!\"), []byte{data1[0]})\n\tassert.DeepEqual(t, 1, nn)\n\tassert.DeepEqual(t, nil, err)\n\n\treader.startPos = 0\n\treader.ff.f = nil\n\tbuf = bytes.NewBuffer(nil)\n\treader.ff.dirIndex = make([]byte, len([]byte(teststr)))\n\tn, err = reader.WriteTo(pureWriter{buf})\n\tassert.DeepEqual(t, int64(len(teststr)), n)\n\tassert.Nil(t, err)\n}\n\ntype pureWriter struct {\n\tw io.Writer\n}\n\nfunc (pw pureWriter) Write(p []byte) (nn int, err error) {\n\treturn pw.w.Write(p)\n}\n\nfunc TestServeFileCompressed(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestContext\n\tvar req protocol.Request\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\treq.Header.Set(consts.HeaderAcceptEncoding, \"gzip\")\n\treq.CopyTo(&ctx.Request)\n\n\tServeFile(&ctx, \"fs.go\")\n\n\tvar r protocol.Response\n\ts := resp.GetHTTP1Response(&ctx.Response).String()\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := resp.Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tce := r.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"Unexpected 'Content-Encoding' %q. Expecting %q\", ce, \"gzip\")\n\t}\n\n\tbody, err := r.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body %q. expecting %q\", body, expectedBody)\n\t}\n}\n\nfunc TestServeFileUncompressed(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestContext\n\tvar req protocol.Request\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\treq.Header.Set(consts.HeaderAcceptEncoding, \"gzip\")\n\treq.CopyTo(&ctx.Request)\n\n\tServeFileUncompressed(&ctx, \"fs.go\")\n\n\tvar r protocol.Response\n\ts := resp.GetHTTP1Response(&ctx.Response).String()\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := resp.Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tce := r.Header.ContentEncoding()\n\tif len(ce) > 0 {\n\t\tt.Fatalf(\"Unexpected 'Content-Encoding' %q\", ce)\n\t}\n\n\tbody := r.Body()\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body %q. expecting %q\", body, expectedBody)\n\t}\n}\n\nfunc TestFSByteRangeConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tfs := &FS{\n\t\tRoot:            \".\",\n\t\tAcceptByteRange: true,\n\t}\n\th := fs.NewRequestHandler()\n\n\tconcurrency := 10\n\tch := make(chan struct{}, concurrency)\n\tfor i := 0; i < concurrency; i++ {\n\t\tgo func() {\n\t\t\tfor j := 0; j < 5; j++ {\n\t\t\t\ttestFSByteRange(t, h, \"/fs.go\")\n\t\t\t}\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor i := 0; i < concurrency; i++ {\n\t\tselect {\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\tcase <-ch:\n\t\t}\n\t}\n}\n\nfunc TestFSByteRangeSingleThread(t *testing.T) {\n\tt.Parallel()\n\n\tfs := &FS{\n\t\tRoot:            \".\",\n\t\tAcceptByteRange: true,\n\t}\n\th := fs.NewRequestHandler()\n\n\ttestFSByteRange(t, h, \"/fs.go\")\n}\n\nfunc testFSByteRange(t *testing.T, h HandlerFunc, filePath string) {\n\tvar ctx RequestContext\n\treq := &protocol.Request{}\n\treq.CopyTo(&ctx.Request)\n\n\texpectedBody, err := getFileContents(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"cannot read file %q: %s\", filePath, err)\n\t}\n\n\tfileSize := len(expectedBody)\n\tstartPos := rand.Intn(fileSize)\n\tendPos := rand.Intn(fileSize)\n\tif endPos < startPos {\n\t\tstartPos, endPos = endPos, startPos\n\t}\n\n\tctx.Request.SetRequestURI(filePath)\n\tctx.Request.Header.SetByteRange(startPos, endPos)\n\th(context.Background(), &ctx)\n\n\tvar r protocol.Response\n\ts := resp.GetHTTP1Response(&ctx.Response).String()\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := resp.Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s. filePath=%q\", err, filePath)\n\t}\n\tif r.StatusCode() != consts.StatusPartialContent {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d. filePath=%q\", r.StatusCode(), consts.StatusPartialContent, filePath)\n\t}\n\tcr := r.Header.Peek(consts.HeaderContentRange)\n\n\texpectedCR := fmt.Sprintf(\"bytes %d-%d/%d\", startPos, endPos, fileSize)\n\tif string(cr) != expectedCR {\n\t\tt.Fatalf(\"unexpected content-range %q. Expecting %q. filePath=%q\", cr, expectedCR, filePath)\n\t}\n\tbody := r.Body()\n\tbodySize := endPos - startPos + 1\n\tif len(body) != bodySize {\n\t\tt.Fatalf(\"unexpected body size %d. Expecting %d. filePath=%q, startPos=%d, endPos=%d\",\n\t\t\tlen(body), bodySize, filePath, startPos, endPos)\n\t}\n\n\texpectedBody = expectedBody[startPos : endPos+1]\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q. filePath=%q, startPos=%d, endPos=%d\",\n\t\t\tbody, expectedBody, filePath, startPos, endPos)\n\t}\n}\n\nfunc getFileContents(path string) ([]byte, error) {\n\tpath = \".\" + path\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\treturn ioutil.ReadAll(f)\n}\n\nfunc TestParseByteRangeSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ttestParseByteRangeSuccess(t, \"bytes=0-0\", 1, 0, 0)\n\ttestParseByteRangeSuccess(t, \"bytes=1234-6789\", 6790, 1234, 6789)\n\n\ttestParseByteRangeSuccess(t, \"bytes=123-\", 456, 123, 455)\n\ttestParseByteRangeSuccess(t, \"bytes=-1\", 1, 0, 0)\n\ttestParseByteRangeSuccess(t, \"bytes=-123\", 456, 333, 455)\n\n\t// End position exceeding content-length. It should be updated to content-length-1.\n\t// See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35\n\ttestParseByteRangeSuccess(t, \"bytes=1-2345\", 234, 1, 233)\n\ttestParseByteRangeSuccess(t, \"bytes=0-2345\", 2345, 0, 2344)\n\n\t// Start position overflow. Whole range must be returned.\n\t// See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35\n\ttestParseByteRangeSuccess(t, \"bytes=-567\", 56, 0, 55)\n}\n\nfunc testParseByteRangeSuccess(t *testing.T, v string, contentLength, startPos, endPos int) {\n\tstartPos1, endPos1, err := ParseByteRange([]byte(v), contentLength)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s. v=%q, contentLength=%d\", err, v, contentLength)\n\t}\n\tif startPos1 != startPos {\n\t\tt.Fatalf(\"unexpected startPos=%d. Expecting %d. v=%q, contentLength=%d\", startPos1, startPos, v, contentLength)\n\t}\n\tif endPos1 != endPos {\n\t\tt.Fatalf(\"unexpected endPos=%d. Expectind %d. v=%q, contentLength=%d\", endPos1, endPos, v, contentLength)\n\t}\n}\n\nfunc TestParseByteRangeError(t *testing.T) {\n\tt.Parallel()\n\n\t// invalid value\n\ttestParseByteRangeError(t, \"asdfasdfas\", 1234)\n\n\t// invalid units\n\ttestParseByteRangeError(t, \"foobar=1-34\", 600)\n\n\t// missing '-'\n\ttestParseByteRangeError(t, \"bytes=1234\", 1235)\n\n\t// non-numeric range\n\ttestParseByteRangeError(t, \"bytes=foobar\", 123)\n\ttestParseByteRangeError(t, \"bytes=1-foobar\", 123)\n\ttestParseByteRangeError(t, \"bytes=df-344\", 545)\n\n\t// multiple byte ranges\n\ttestParseByteRangeError(t, \"bytes=1-2,4-6\", 123)\n\n\t// byte range exceeding contentLength\n\ttestParseByteRangeError(t, \"bytes=123-\", 12)\n\n\t// startPos exceeding endPos\n\ttestParseByteRangeError(t, \"bytes=123-34\", 1234)\n}\n\nfunc testParseByteRangeError(t *testing.T, v string, contentLength int) {\n\t_, _, err := ParseByteRange([]byte(v), contentLength)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error when parsing byte range %q\", v)\n\t}\n}\n\nfunc TestFSCompressConcurrent(t *testing.T) {\n\t// This test can't run parallel as files in / might by changed by other tests.\n\n\tfs := &FS{\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t}\n\th := fs.NewRequestHandler()\n\n\tconcurrency := 4\n\tch := make(chan struct{}, concurrency)\n\tfor i := 0; i < concurrency; i++ {\n\t\tgo func() {\n\t\t\tfor j := 0; j < 5; j++ {\n\t\t\t\ttestFSCompress(t, h, \"/fs.go\")\n\t\t\t\ttestFSCompress(t, h, \"/\")\n\t\t\t}\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor i := 0; i < concurrency; i++ {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc TestFSCompressSingleThread(t *testing.T) {\n\t// This test can't run parallel as files in / might by changed by other tests.\n\n\tfs := &FS{\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t}\n\th := fs.NewRequestHandler()\n\n\ttestFSCompress(t, h, \"/fs.go\")\n\ttestFSCompress(t, h, \"/\")\n}\n\nfunc testFSCompress(t *testing.T, h HandlerFunc, filePath string) {\n\tvar ctx RequestContext\n\treq := &protocol.Request{}\n\treq.CopyTo(&ctx.Request)\n\n\t// request uncompressed file\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(filePath)\n\th(context.Background(), &ctx)\n\n\tvar r protocol.Response\n\ts := resp.GetHTTP1Response(&ctx.Response).String()\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := resp.Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s. filePath=%q\", err, filePath)\n\t}\n\tif r.StatusCode() != consts.StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d. filePath=%q\", r.StatusCode(), consts.StatusOK, filePath)\n\t}\n\tce := r.Header.ContentEncoding()\n\tif string(ce) != \"\" {\n\t\tt.Fatalf(\"unexpected content-encoding %q. Expecting empty string. filePath=%q\", ce, filePath)\n\t}\n\tbody := string(r.Body())\n\n\t// request compressed file\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(filePath)\n\tctx.Request.Header.Set(consts.HeaderAcceptEncoding, \"gzip\")\n\th(context.Background(), &ctx)\n\ts = resp.GetHTTP1Response(&ctx.Response).String()\n\tzr = mock.NewZeroCopyReader(s)\n\tif err := resp.Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s. filePath=%q\", err, filePath)\n\t}\n\tif r.StatusCode() != consts.StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d. filePath=%q\", r.StatusCode(), consts.StatusOK, filePath)\n\t}\n\tce = r.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected content-encoding %q. Expecting %q. filePath=%q\", ce, \"gzip\", filePath)\n\t}\n\tzbody, err := r.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when gunzipping response body: %s. filePath=%q\", err, filePath)\n\t}\n\tif string(zbody) != body {\n\t\tt.Fatalf(\"unexpected body len=%d. Expected len=%d. FilePath=%q\", len(zbody), len(body), filePath)\n\t}\n}\n\nfunc TestFileLock(t *testing.T) {\n\tt.Parallel()\n\n\tfor i := 0; i < 10; i++ {\n\t\tfilePath := fmt.Sprintf(\"foo/bar/%d.jpg\", i)\n\t\tlock := getFileLock(filePath)\n\t\tlock.Lock()\n\t\ttime.Sleep(time.Microsecond)\n\t\tlock.Unlock() // nolint:staticcheck\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tfilePath := fmt.Sprintf(\"foo/bar/%d.jpg\", i)\n\t\tlock := getFileLock(filePath)\n\t\tlock.Lock()\n\t\ttime.Sleep(time.Microsecond)\n\t\tlock.Unlock() // nolint:staticcheck\n\t}\n}\n\nfunc TestStripPathSlashes(t *testing.T) {\n\tt.Parallel()\n\n\ttestStripPathSlashes(t, \"\", 0, \"\")\n\ttestStripPathSlashes(t, \"\", 10, \"\")\n\ttestStripPathSlashes(t, \"/\", 0, \"\")\n\ttestStripPathSlashes(t, \"/\", 1, \"\")\n\ttestStripPathSlashes(t, \"/\", 10, \"\")\n\ttestStripPathSlashes(t, \"/foo/bar/baz\", 0, \"/foo/bar/baz\")\n\ttestStripPathSlashes(t, \"/foo/bar/baz\", 1, \"/bar/baz\")\n\ttestStripPathSlashes(t, \"/foo/bar/baz\", 2, \"/baz\")\n\ttestStripPathSlashes(t, \"/foo/bar/baz\", 3, \"\")\n\ttestStripPathSlashes(t, \"/foo/bar/baz\", 10, \"\")\n\n\t// trailing slash\n\ttestStripPathSlashes(t, \"/foo/bar/\", 0, \"/foo/bar\")\n\ttestStripPathSlashes(t, \"/foo/bar/\", 1, \"/bar\")\n\ttestStripPathSlashes(t, \"/foo/bar/\", 2, \"\")\n\ttestStripPathSlashes(t, \"/foo/bar/\", 3, \"\")\n}\n\nfunc testStripPathSlashes(t *testing.T, path string, stripSlashes int, expectedPath string) {\n\ts := stripLeadingSlashes([]byte(path), stripSlashes)\n\ts = stripTrailingSlashes(s)\n\tif string(s) != expectedPath {\n\t\tt.Fatalf(\"unexpected path after stripping %q with stripSlashes=%d: %q. Expecting %q\", path, stripSlashes, s, expectedPath)\n\t}\n}\n\nfunc TestFileExtension(t *testing.T) {\n\tt.Parallel()\n\n\ttestFileExtension(t, \"foo.bar\", false, \"zzz\", \".bar\")\n\ttestFileExtension(t, \"foobar\", false, \"zzz\", \"\")\n\ttestFileExtension(t, \"foo.bar.baz\", false, \"zzz\", \".baz\")\n\ttestFileExtension(t, \"\", false, \"zzz\", \"\")\n\ttestFileExtension(t, \"/a/b/c.d/efg.jpg\", false, \".zzz\", \".jpg\")\n\n\ttestFileExtension(t, \"foo.bar\", true, \".zzz\", \".bar\")\n\ttestFileExtension(t, \"foobar.zzz\", true, \".zzz\", \"\")\n\ttestFileExtension(t, \"foo.bar.baz.hertz.gz\", true, \".hertz.gz\", \".baz\")\n\ttestFileExtension(t, \"\", true, \".zzz\", \"\")\n\ttestFileExtension(t, \"/a/b/c.d/efg.jpg.xxx\", true, \".xxx\", \".jpg\")\n}\n\nfunc testFileExtension(t *testing.T, path string, compressed bool, compressedFileSuffix, expectedExt string) {\n\text := fileExtension(path, compressed, compressedFileSuffix)\n\tif ext != expectedExt {\n\t\tt.Fatalf(\"unexpected file extension for file %q: %q. Expecting %q\", path, ext, expectedExt)\n\t}\n}\n\nfunc TestServeFileContentType(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestContext\n\tvar req protocol.Request\n\treq.Header.SetMethod(consts.MethodGet)\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\treq.CopyTo(&ctx.Request)\n\n\tServeFile(&ctx, \"../common/testdata/test.png\")\n\n\tvar r protocol.Response\n\ts := resp.GetHTTP1Response(&ctx.Response).String()\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := resp.Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\texpected := []byte(consts.MIMEImagePNG)\n\tif !bytes.Equal(r.Header.ContentType(), expected) {\n\t\tt.Fatalf(\"Unexpected Content-Type, expected: %q got %q\", expected, r.Header.ContentType())\n\t}\n}\n\nfunc TestFileSmallUpdateByteRange(t *testing.T) {\n\tr := &fsSmallFileReader{}\n\terr := r.UpdateByteRange(1, 1)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, 1, r.startPos)\n\tassert.DeepEqual(t, 2, r.endPos)\n}\n"
  },
  {
    "path": "pkg/app/middlewares/client/sd/discovery.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sd\n\nimport (\n\t\"context\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client\"\n\t\"github.com/cloudwego/hertz/pkg/app/client/discovery\"\n\t\"github.com/cloudwego/hertz/pkg/app/client/loadbalance\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\n// Discovery will construct a middleware with BalancerFactory.\nfunc Discovery(resolver discovery.Resolver, opts ...ServiceDiscoveryOption) client.Middleware {\n\toptions := &ServiceDiscoveryOptions{\n\t\tBalancer: loadbalance.NewWeightedBalancer(),\n\t\tLbOpts:   loadbalance.DefaultLbOpts,\n\t\tResolver: resolver,\n\t}\n\toptions.Apply(opts)\n\n\tlbConfig := loadbalance.Config{\n\t\tResolver: options.Resolver,\n\t\tBalancer: options.Balancer,\n\t\tLbOpts:   options.LbOpts,\n\t}\n\n\tf := loadbalance.NewBalancerFactory(lbConfig)\n\treturn func(next client.Endpoint) client.Endpoint {\n\t\treturn func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\t\tif req.Options() != nil && req.Options().IsSD() {\n\t\t\t\tins, err := f.GetInstance(ctx, req)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treq.SetHost(ins.Address().String())\n\t\t\t}\n\t\t\treturn next(ctx, req, resp)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/app/middlewares/client/sd/discovery_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sd\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/discovery\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\nfunc TestDiscovery(t *testing.T) {\n\tinss := []discovery.Instance{\n\t\tdiscovery.NewInstance(\"tcp\", \"127.0.0.1:8888\", 10, nil),\n\t\tdiscovery.NewInstance(\"tcp\", \"127.0.0.1:8889\", 10, nil),\n\t}\n\tr := &discovery.SynthesizedResolver{\n\t\tTargetFunc: func(ctx context.Context, target *discovery.TargetInfo) string {\n\t\t\treturn target.Host\n\t\t},\n\t\tResolveFunc: func(ctx context.Context, key string) (discovery.Result, error) {\n\t\t\treturn discovery.Result{CacheKey: \"svc1\", Instances: inss}, nil\n\t\t},\n\t\tNameFunc: func() string { return t.Name() },\n\t}\n\n\tmw := Discovery(r)\n\tcheckMdw := func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {\n\t\tt.Log(string(req.Host()))\n\t\tassert.Assert(t, string(req.Host()) == \"127.0.0.1:8888\" || string(req.Host()) == \"127.0.0.1:8889\")\n\t\treturn nil\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\treq := &protocol.Request{}\n\t\tresp := &protocol.Response{}\n\t\treq.Options().Apply([]config.RequestOption{config.WithSD(true)})\n\t\treq.SetRequestURI(\"http://service_name\")\n\t\t_ = mw(checkMdw)(context.Background(), req, resp)\n\t}\n}\n"
  },
  {
    "path": "pkg/app/middlewares/client/sd/options.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/discovery\"\n\t\"github.com/cloudwego/hertz/pkg/app/client/loadbalance\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/registry\"\n)\n\n// ServiceDiscoveryOptions service discovery option for client\ntype ServiceDiscoveryOptions struct {\n\t// Resolver is used to client discovery\n\tResolver discovery.Resolver\n\n\t// Balancer is used to client load balance\n\tBalancer loadbalance.Loadbalancer\n\n\t// LbOpts LoadBalance option\n\tLbOpts loadbalance.Options\n}\n\nfunc (o *ServiceDiscoveryOptions) Apply(opts []ServiceDiscoveryOption) {\n\tfor _, op := range opts {\n\t\top.F(o)\n\t}\n}\n\ntype ServiceDiscoveryOption struct {\n\tF func(o *ServiceDiscoveryOptions)\n}\n\n// WithCustomizedAddrs specifies the target instance addresses when doing service discovery.\n// It overwrites the results from the Resolver\nfunc WithCustomizedAddrs(addrs ...string) ServiceDiscoveryOption {\n\treturn ServiceDiscoveryOption{\n\t\tF: func(o *ServiceDiscoveryOptions) {\n\t\t\tvar ins []discovery.Instance\n\t\t\tfor _, addr := range addrs {\n\t\t\t\tif _, err := net.ResolveTCPAddr(\"tcp\", addr); err == nil {\n\t\t\t\t\tins = append(ins, discovery.NewInstance(\"tcp\", addr, registry.DefaultWeight, nil))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, err := net.ResolveUnixAddr(\"unix\", addr); err == nil {\n\t\t\t\t\tins = append(ins, discovery.NewInstance(\"unix\", addr, registry.DefaultWeight, nil))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tpanic(fmt.Errorf(\"WithCustomizedAddrs: invalid '%s'\", addr))\n\t\t\t}\n\t\t\tif len(ins) == 0 {\n\t\t\t\tpanic(\"WithCustomizedAddrs() requires at least one argument\")\n\t\t\t}\n\n\t\t\ttargets := strings.Join(addrs, \",\")\n\t\t\to.Resolver = &discovery.SynthesizedResolver{\n\t\t\t\tResolveFunc: func(ctx context.Context, key string) (discovery.Result, error) {\n\t\t\t\t\treturn discovery.Result{\n\t\t\t\t\t\tCacheKey:  \"fixed\",\n\t\t\t\t\t\tInstances: ins,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t\tNameFunc: func() string { return targets },\n\t\t\t\tTargetFunc: func(ctx context.Context, target *discovery.TargetInfo) string {\n\t\t\t\t\treturn targets\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n}\n\n// WithLoadBalanceOptions  sets Loadbalancer and loadbalance options for hertz client\nfunc WithLoadBalanceOptions(lb loadbalance.Loadbalancer, options loadbalance.Options) ServiceDiscoveryOption {\n\treturn ServiceDiscoveryOption{F: func(o *ServiceDiscoveryOptions) {\n\t\to.LbOpts = options\n\t\to.Balancer = lb\n\t}}\n}\n"
  },
  {
    "path": "pkg/app/middlewares/client/sd/options_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sd\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/loadbalance\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestWithCustomizedAddrs(t *testing.T) {\n\tvar options []ServiceDiscoveryOption\n\toptions = append(options, WithCustomizedAddrs(\"127.0.0.1:8080\", \"/tmp/unix_ss\"))\n\topts := &ServiceDiscoveryOptions{}\n\topts.Apply(options)\n\tassert.Assert(t, opts.Resolver.Name() == \"127.0.0.1:8080,/tmp/unix_ss\")\n\tres, err := opts.Resolver.Resolve(context.Background(), \"\")\n\tassert.Assert(t, err == nil)\n\tassert.Assert(t, res.Instances[0].Address().String() == \"127.0.0.1:8080\")\n\tassert.Assert(t, res.Instances[1].Address().String() == \"/tmp/unix_ss\")\n}\n\nfunc TestWithLoadBalanceOptions(t *testing.T) {\n\tbalance := loadbalance.NewWeightedBalancer()\n\tvar options []ServiceDiscoveryOption\n\toptions = append(options, WithLoadBalanceOptions(balance, loadbalance.DefaultLbOpts))\n\topts := &ServiceDiscoveryOptions{}\n\topts.Apply(options)\n\tassert.Assert(t, opts.Balancer.Name() == \"weight_random\")\n}\n"
  },
  {
    "path": "pkg/app/middlewares/server/basic_auth/basic_auth.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage basic_auth\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n)\n\n// Accounts is an alias to map[string]string, construct with {\"username\":\"password\"}\ntype Accounts map[string]string\n\n// pairs is an alias to map[string]string, which mean {\"header\":\"username\"}\ntype pairs map[string]string\n\nfunc (p pairs) findValue(needle string) (v string, ok bool) {\n\tv, ok = p[needle]\n\treturn\n}\n\nfunc constructPairs(accounts Accounts) pairs {\n\tlength := len(accounts)\n\tp := make(pairs, length)\n\tfor user, password := range accounts {\n\t\tvalue := \"Basic \" + base64.StdEncoding.EncodeToString(bytesconv.S2b(user+\":\"+password))\n\t\tp[value] = user\n\t}\n\treturn p\n}\n\n// BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where\n// the key is the username and the value is the password, as well as the name of the Realm.\n// If the realm is empty, \"Authorization Required\" will be used by default.\n// (see http://tools.ietf.org/html/rfc2617#section-1.2)\nfunc BasicAuthForRealm(accounts Accounts, realm, userKey string) app.HandlerFunc {\n\trealm = \"Basic realm=\" + strconv.Quote(realm)\n\tp := constructPairs(accounts)\n\treturn func(ctx context.Context, c *app.RequestContext) {\n\t\t// Search user in the slice of allowed credentials\n\t\tuser, found := p.findValue(c.Request.Header.Get(\"Authorization\"))\n\t\tif !found {\n\t\t\t// Credentials doesn't match, we return 401 and abort handlers chain.\n\t\t\tc.Header(\"WWW-Authenticate\", realm)\n\t\t\tc.AbortWithStatus(http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\n\t\t// The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using\n\t\tc.Set(userKey, user)\n\t}\n}\n\n// BasicAuth is a constructor of BasicAuth verifier to hertz middleware\n// It returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where\n// the key is the username and the value is the password.\nfunc BasicAuth(accounts Accounts) app.HandlerFunc {\n\treturn BasicAuthForRealm(accounts, \"Authorization Required\", \"user\")\n}\n"
  },
  {
    "path": "pkg/app/middlewares/server/basic_auth/basic_auth_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage basic_auth\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestPairs(t *testing.T) {\n\tt1 := Accounts{\"test1\": \"value1\"}\n\tt2 := Accounts{\"test2\": \"value2\"}\n\tp1 := constructPairs(t1)\n\tp2 := constructPairs(t2)\n\n\tu1, ok1 := p1.findValue(\"Basic dGVzdDE6dmFsdWUx\")\n\tu2, ok2 := p2.findValue(\"Basic dGVzdDI6dmFsdWUy\")\n\t_, ok3 := p1.findValue(\"bad header\")\n\t_, ok4 := p2.findValue(\"bad header\")\n\tassert.True(t, ok1)\n\tassert.DeepEqual(t, \"test1\", u1)\n\tassert.True(t, ok2)\n\tassert.DeepEqual(t, \"test2\", u2)\n\tassert.False(t, ok3)\n\tassert.False(t, ok4)\n}\n\nfunc TestBasicAuth(t *testing.T) {\n\tuserName1 := \"user1\"\n\tpassword1 := \"value1\"\n\tuserName2 := \"user2\"\n\tpassword2 := \"value2\"\n\n\tc1 := app.RequestContext{}\n\tencodeStr := \"Basic \" + base64.StdEncoding.EncodeToString(bytesconv.S2b(userName1+\":\"+password1))\n\tc1.Request.Header.Add(\"Authorization\", encodeStr)\n\n\tt1 := Accounts{userName1: password1}\n\thandler := BasicAuth(t1)\n\thandler(context.TODO(), &c1)\n\n\tuser, ok := c1.Get(\"user\")\n\tassert.DeepEqual(t, userName1, user)\n\tassert.True(t, ok)\n\n\tc2 := app.RequestContext{}\n\tencodeStr = \"Basic \" + base64.StdEncoding.EncodeToString(bytesconv.S2b(userName2+\":\"+password2))\n\tc2.Request.Header.Add(\"Authorization\", encodeStr)\n\n\thandler(context.TODO(), &c2)\n\n\tuser, ok = c2.Get(\"user\")\n\tassert.Nil(t, user)\n\tassert.False(t, ok)\n}\n"
  },
  {
    "path": "pkg/app/middlewares/server/basic_auth/doc.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The files in basic_auth package are forked from gin[github.com/gin-gonic/gin],\n// and we keep the original Copyright[Copyright 2014 gin authors] and License of gin for those files.\n// We also need to modify as we need, the modifications are Copyright of 2022 CloudWeGo Authors.\n// Thanks for gin authors! Below is the source code information:\n// \t\tRepo: github.com/gin-gonic/gin\n//\t\tForked Version: v1.7.7\n\npackage basic_auth\n"
  },
  {
    "path": "pkg/app/middlewares/server/recovery/option.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage recovery\n\nimport (\n\t\"context\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\ntype (\n\toptions struct {\n\t\trecoveryHandler func(c context.Context, ctx *app.RequestContext, err interface{}, stack []byte)\n\t}\n\n\tOption func(o *options)\n)\n\nfunc defaultRecoveryHandler(c context.Context, ctx *app.RequestContext, err interface{}, stack []byte) {\n\thlog.SystemLogger().CtxErrorf(c, \"[Recovery] err=%v\\nstack=%s\", err, stack)\n\tctx.AbortWithStatus(consts.StatusInternalServerError)\n}\n\nfunc newOptions(opts ...Option) *options {\n\tcfg := &options{\n\t\trecoveryHandler: defaultRecoveryHandler,\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(cfg)\n\t}\n\n\treturn cfg\n}\n\nfunc WithRecoveryHandler(f func(c context.Context, ctx *app.RequestContext, err interface{}, stack []byte)) Option {\n\treturn func(o *options) {\n\t\to.recoveryHandler = f\n\t}\n}\n"
  },
  {
    "path": "pkg/app/middlewares/server/recovery/option_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage recovery\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc TestDefaultOption(t *testing.T) {\n\topts := newOptions()\n\tassert.DeepEqual(t, fmt.Sprintf(\"%p\", defaultRecoveryHandler), fmt.Sprintf(\"%p\", opts.recoveryHandler))\n}\n\nfunc newRecoveryHandler(c context.Context, ctx *app.RequestContext, err interface{}, stack []byte) {\n\thlog.SystemLogger().CtxErrorf(c, \"[New Recovery] panic recovered:\\n%s\\n%s\\n\",\n\t\terr, stack)\n\tctx.JSON(consts.StatusNotImplemented, utils.H{\"msg\": err.(string)})\n}\n\nfunc TestOption(t *testing.T) {\n\topts := newOptions(WithRecoveryHandler(newRecoveryHandler))\n\tassert.DeepEqual(t, fmt.Sprintf(\"%p\", newRecoveryHandler), fmt.Sprintf(\"%p\", opts.recoveryHandler))\n}\n"
  },
  {
    "path": "pkg/app/middlewares/server/recovery/recovery.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage recovery\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"runtime\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n)\n\nvar (\n\tdunno     = []byte(\"???\")\n\tcenterDot = []byte(\"·\")\n\tdot       = []byte(\".\")\n\tslash     = []byte(\"/\")\n)\n\n// Recovery returns a middleware that recovers from any panic.\n// By default, it will print the time, content, and stack information of the error and write a 500.\n// Overriding the Config configuration, you can customize the error printing logic.\nfunc Recovery(opts ...Option) app.HandlerFunc {\n\tcfg := newOptions(opts...)\n\n\treturn func(c context.Context, ctx *app.RequestContext) {\n\t\tdefer func() {\n\t\t\tif err := recover(); err != nil {\n\t\t\t\tstack := stack(3)\n\n\t\t\t\tcfg.recoveryHandler(c, ctx, err, stack)\n\t\t\t}\n\t\t}()\n\t\tctx.Next(c)\n\t}\n}\n\n// stack returns a nicely formatted stack frame, skipping skip frames.\nfunc stack(skip int) []byte {\n\tbuf := new(bytes.Buffer) // the returned data\n\t// As we loop, we open files and read them. These variables record the currently\n\t// loaded file.\n\tvar lines [][]byte\n\tvar lastFile string\n\tfor i := skip; ; i++ { // Skip the expected number of frames\n\t\tpc, file, line, ok := runtime.Caller(i)\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\t// Print this much at least.  If we can't find the source, it won't show.\n\t\tfmt.Fprintf(buf, \"%s:%d (0x%x)\\n\", file, line, pc)\n\t\tif file != lastFile {\n\t\t\tdata, err := ioutil.ReadFile(file)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlines = bytes.Split(data, []byte{'\\n'})\n\t\t\tlastFile = file\n\t\t}\n\t\tfmt.Fprintf(buf, \"\\t%s: %s\\n\", function(pc), source(lines, line))\n\t}\n\treturn buf.Bytes()\n}\n\n// source returns a space-trimmed slice of the n'th line.\nfunc source(lines [][]byte, n int) []byte {\n\tn-- // in stack trace, lines are 1-indexed but our array is 0-indexed\n\tif n < 0 || n >= len(lines) {\n\t\treturn dunno\n\t}\n\treturn bytes.TrimSpace(lines[n])\n}\n\n// function returns, if possible, the name of the function containing the PC.\nfunc function(pc uintptr) []byte {\n\tfn := runtime.FuncForPC(pc)\n\tif fn == nil {\n\t\treturn dunno\n\t}\n\tname := []byte(fn.Name())\n\t// The name includes the path name to the package, which is unnecessary\n\t// since the file name is already included.  Plus, it has center dots.\n\t// That is, we see\n\t//\truntime/debug.*T·ptrmethod\n\t// and want\n\t//\t*T.ptrmethod\n\t// Also the package path might contains dot (e.g. code.google.com/...),\n\t// so first eliminate the path prefix\n\tif lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {\n\t\tname = name[lastSlash+1:]\n\t}\n\tif period := bytes.Index(name, dot); period >= 0 {\n\t\tname = name[period+1:]\n\t}\n\tname = bytes.Replace(name, centerDot, dot, -1)\n\treturn name\n}\n"
  },
  {
    "path": "pkg/app/middlewares/server/recovery/recovery_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage recovery\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc TestRecovery(t *testing.T) {\n\tctx := app.NewContext(0)\n\tvar hc app.HandlersChain\n\thc = append(hc, func(c context.Context, ctx *app.RequestContext) {\n\t\tfmt.Println(\"this is test\")\n\t\tpanic(\"test\")\n\t})\n\tctx.SetHandlers(hc)\n\n\tRecovery()(context.Background(), ctx)\n\n\tif ctx.Response.StatusCode() != 500 {\n\t\tt.Fatalf(\"unexpected %v. Expecting %v\", ctx.Response.StatusCode(), 500)\n\t}\n}\n\nfunc TestWithRecoveryHandler(t *testing.T) {\n\tctx := app.NewContext(0)\n\tvar hc app.HandlersChain\n\thc = append(hc, func(c context.Context, ctx *app.RequestContext) {\n\t\tfmt.Println(\"this is test\")\n\t\tpanic(\"test\")\n\t})\n\tctx.SetHandlers(hc)\n\n\tRecovery(WithRecoveryHandler(newRecoveryHandler))(context.Background(), ctx)\n\n\tif ctx.Response.StatusCode() != consts.StatusNotImplemented {\n\t\tt.Fatalf(\"unexpected %v. Expecting %v\", ctx.Response.StatusCode(), 501)\n\t}\n\tassert.DeepEqual(t, \"{\\\"msg\\\":\\\"test\\\"}\", string(ctx.Response.Body()))\n}\n"
  },
  {
    "path": "pkg/app/server/binding/binder.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage binding\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype Binder interface {\n\tName() string\n\tBind(*protocol.Request, interface{}, param.Params) error\n\tBindQuery(*protocol.Request, interface{}) error\n\tBindHeader(*protocol.Request, interface{}) error\n\tBindPath(*protocol.Request, interface{}, param.Params) error\n\tBindForm(*protocol.Request, interface{}) error\n\tBindJSON(*protocol.Request, interface{}) error\n\tBindProtobuf(*protocol.Request, interface{}) error\n\n\tValidate(*protocol.Request, interface{}) error\n}\n"
  },
  {
    "path": "pkg/app/server/binding/binder_test.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage binding\n\nimport (\n\t\"encoding\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"mime/multipart\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/server/binding/testdata\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\treq2 \"github.com/cloudwego/hertz/pkg/protocol/http1/req\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype mockRequest struct {\n\tReq *protocol.Request\n}\n\nfunc newMockRequest() *mockRequest {\n\treturn &mockRequest{\n\t\tReq: &protocol.Request{},\n\t}\n}\n\nfunc (m *mockRequest) SetRequestURI(uri string) *mockRequest {\n\tm.Req.SetRequestURI(uri)\n\treturn m\n}\n\nfunc (m *mockRequest) SetFile(param, fileName string) *mockRequest {\n\tm.Req.SetFile(param, fileName)\n\treturn m\n}\n\nfunc (m *mockRequest) SetHeader(key, value string) *mockRequest {\n\tm.Req.Header.Set(key, value)\n\treturn m\n}\n\nfunc (m *mockRequest) SetHeaders(key, value string) *mockRequest {\n\tm.Req.Header.Set(key, value)\n\treturn m\n}\n\nfunc (m *mockRequest) SetPostArg(key, value string) *mockRequest {\n\tm.Req.PostArgs().Add(key, value)\n\treturn m\n}\n\nfunc (m *mockRequest) SetUrlEncodeContentType() *mockRequest {\n\tm.Req.Header.SetContentTypeBytes([]byte(\"application/x-www-form-urlencoded\"))\n\treturn m\n}\n\nfunc (m *mockRequest) SetJSONContentType() *mockRequest {\n\tm.Req.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationJSON))\n\treturn m\n}\n\nfunc (m *mockRequest) SetProtobufContentType() *mockRequest {\n\tm.Req.Header.SetContentTypeBytes([]byte(consts.MIMEPROTOBUF))\n\treturn m\n}\n\nfunc (m *mockRequest) SetBody(data []byte) *mockRequest {\n\tm.Req.SetBody(data)\n\tm.Req.Header.SetContentLength(len(data))\n\treturn m\n}\n\nfunc TestBind_BaseType(t *testing.T) {\n\ttype Req struct {\n\t\tVersion int    `path:\"v\"`\n\t\tID      int    `query:\"id\"`\n\t\tHeader  string `header:\"H\"`\n\t\tForm    string `form:\"f\"`\n\t}\n\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?id=12\").\n\t\tSetHeaders(\"H\", \"header\").\n\t\tSetPostArg(\"f\", \"form\").\n\t\tSetUrlEncodeContentType()\n\tvar params param.Params\n\tparams = append(params, param.Param{\n\t\tKey:   \"v\",\n\t\tValue: \"1\",\n\t})\n\n\tvar result Req\n\n\terr := DefaultBinder().Bind(req.Req, &result, params)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 1, result.Version)\n\tassert.DeepEqual(t, 12, result.ID)\n\tassert.DeepEqual(t, \"header\", result.Header)\n\tassert.DeepEqual(t, \"form\", result.Form)\n}\n\nfunc TestBind_SliceType(t *testing.T) {\n\ttype Req struct {\n\t\tID   *[]int    `query:\"id\"`\n\t\tStr  [3]string `query:\"str\"`\n\t\tByte []byte    `query:\"b\"`\n\t\tHH   []string  `header:\"h\"`\n\t}\n\tIDs := []int{11, 12, 13}\n\tStrs := [3]string{\"qwe\", \"asd\", \"zxc\"}\n\tBytes := []byte(\"123\")\n\tHeaders := []string{\"header\"}\n\n\treq := newMockRequest().\n\t\tSetHeaders(\"H\", Headers[0]).\n\t\tSetRequestURI(fmt.Sprintf(\"http://foobar.com?id=%d&id=%d&id=%d&str=%s&str=%s&str=%s&b=%d&b=%d&b=%d\", IDs[0], IDs[1], IDs[2], Strs[0], Strs[1], Strs[2], Bytes[0], Bytes[1], Bytes[2]))\n\n\tvar result Req\n\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 3, len(*result.ID))\n\tfor idx, val := range IDs {\n\t\tassert.DeepEqual(t, val, (*result.ID)[idx])\n\t}\n\tassert.DeepEqual(t, 3, len(result.Str))\n\tfor idx, val := range Strs {\n\t\tassert.DeepEqual(t, val, result.Str[idx])\n\t}\n\tassert.DeepEqual(t, 3, len(result.Byte))\n\tfor idx, val := range Bytes {\n\t\tassert.DeepEqual(t, val, result.Byte[idx])\n\t}\n\tassert.DeepEqual(t, Headers, result.HH)\n}\n\nfunc TestBind_StructType(t *testing.T) {\n\ttype FFF struct {\n\t\tF1 string `query:\"F1\"`\n\t}\n\n\ttype TTT struct {\n\t\tT1 string `query:\"F1\"`\n\t\tT2 FFF\n\t}\n\n\ttype Foo struct {\n\t\tF1 string `query:\"F1\"`\n\t\tF2 string `header:\"f2\"`\n\t\tF3 TTT\n\t}\n\n\ttype Bar struct {\n\t\tB1 string `query:\"B1\"`\n\t\tB2 Foo    `query:\"B2\"`\n\t}\n\n\tvar result Bar\n\n\treq := newMockRequest().SetRequestURI(\"http://foobar.com?F1=f1&B1=b1\").SetHeader(\"f2\", \"f2\")\n\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tassert.DeepEqual(t, \"b1\", result.B1)\n\tassert.DeepEqual(t, \"f1\", result.B2.F1)\n\tassert.DeepEqual(t, \"f2\", result.B2.F2)\n\tassert.DeepEqual(t, \"f1\", result.B2.F3.T1)\n\tassert.DeepEqual(t, \"f1\", result.B2.F3.T2.F1)\n}\n\nfunc TestBind_PointerType(t *testing.T) {\n\ttype TT struct {\n\t\tT1 string `query:\"F1\"`\n\t}\n\n\ttype Foo struct {\n\t\tF1 *TT                       `query:\"F1\"`\n\t\tF2 *******************string `query:\"F1\"`\n\t}\n\n\ttype Bar struct {\n\t\tB1 ***string `query:\"B1\"`\n\t\tB2 ****Foo   `query:\"B2\"`\n\t\tB3 []*string `query:\"B3\"`\n\t\tB4 [2]*int   `query:\"B4\"`\n\t}\n\n\tresult := Bar{}\n\n\tF1 := \"f1\"\n\tB1 := \"b1\"\n\tB2 := \"b2\"\n\tB3s := []string{\"b31\", \"b32\"}\n\tB4s := [2]int{0, 1}\n\n\treq := newMockRequest().SetRequestURI(fmt.Sprintf(\"http://foobar.com?F1=%s&B1=%s&B2=%s&B3=%s&B3=%s&B4=%d&B4=%d\", F1, B1, B2, B3s[0], B3s[1], B4s[0], B4s[1])).\n\t\tSetHeader(\"f2\", \"f2\")\n\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, B1, ***result.B1)\n\tassert.DeepEqual(t, F1, (*(****result.B2).F1).T1)\n\tassert.DeepEqual(t, F1, *******************(****result.B2).F2)\n\tassert.DeepEqual(t, len(B3s), len(result.B3))\n\tfor idx, val := range B3s {\n\t\tassert.DeepEqual(t, val, *result.B3[idx])\n\t}\n\tassert.DeepEqual(t, len(B4s), len(result.B4))\n\tfor idx, val := range B4s {\n\t\tassert.DeepEqual(t, val, *result.B4[idx])\n\t}\n}\n\nfunc TestBind_NestedStruct(t *testing.T) {\n\ttype Foo struct {\n\t\tF1 string `query:\"F1\"`\n\t}\n\n\ttype Bar struct {\n\t\tFoo\n\t\tNested struct {\n\t\t\tN1 string `query:\"F1\"`\n\t\t}\n\t}\n\n\tresult := Bar{}\n\n\treq := newMockRequest().SetRequestURI(\"http://foobar.com?F1=qwe\")\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"qwe\", result.Foo.F1)\n\tassert.DeepEqual(t, \"qwe\", result.Nested.N1)\n}\n\nfunc TestBind_SliceStruct(t *testing.T) {\n\ttype Foo struct {\n\t\tF1 string `json:\"f1\"`\n\t}\n\n\ttype Bar struct {\n\t\tB1 []Foo `query:\"F1\"`\n\t}\n\n\tresult := Bar{}\n\tB1s := []string{\"1\", \"2\", \"3\"}\n\n\treq := newMockRequest().SetRequestURI(fmt.Sprintf(\"http://foobar.com?F1={\\\"f1\\\":\\\"%s\\\"}&F1={\\\"f1\\\":\\\"%s\\\"}&F1={\\\"f1\\\":\\\"%s\\\"}\", B1s[0], B1s[1], B1s[2]))\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, len(result.B1), len(B1s))\n\tfor idx, val := range B1s {\n\t\tassert.DeepEqual(t, B1s[idx], val)\n\t}\n}\n\nfunc TestBind_MapType(t *testing.T) {\n\tvar result map[string]string\n\treq := newMockRequest().\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(`{\"j1\":\"j1\", \"j2\":\"j2\"}`))\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, 2, len(result))\n\tassert.DeepEqual(t, \"j1\", result[\"j1\"])\n\tassert.DeepEqual(t, \"j2\", result[\"j2\"])\n}\n\nfunc TestBind_MapFieldType(t *testing.T) {\n\ttype Foo struct {\n\t\tF1 ***map[string]string `query:\"f1\" json:\"f1\"`\n\t}\n\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?f1={\\\"f1\\\":\\\"f1\\\"}\").\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(`{\"j1\":\"j1\", \"j2\":\"j2\"}`))\n\tresult := Foo{}\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, 1, len(***result.F1))\n\tassert.DeepEqual(t, \"f1\", (***result.F1)[\"f1\"])\n\n\ttype Foo2 struct {\n\t\tF1 map[string]string `query:\"f1\" json:\"f1\"`\n\t}\n\tresult2 := Foo2{}\n\terr = DefaultBinder().Bind(req.Req, &result2, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, 1, len(result2.F1))\n\tassert.DeepEqual(t, \"f1\", result2.F1[\"f1\"])\n\treq = newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?f1={\\\"f1\\\":\\\"f1\\\"\")\n\tresult2 = Foo2{}\n\terr = DefaultBinder().Bind(req.Req, &result2, nil)\n\tif err == nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestBind_UnexportedField(t *testing.T) {\n\tvar s struct {\n\t\tA int `query:\"a\"`\n\t\tb int `query:\"b\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?a=1&b=2\")\n\terr := DefaultBinder().Bind(req.Req, &s, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, 1, s.A)\n\tassert.DeepEqual(t, 0, s.b)\n}\n\nfunc TestBind_NoTagField(t *testing.T) {\n\tvar s struct {\n\t\tA string\n\t\tB string\n\t\tC string\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?B=b1&C=c1\").\n\t\tSetHeader(\"A\", \"a2\")\n\n\tvar params param.Params\n\tparams = append(params, param.Param{\n\t\tKey:   \"B\",\n\t\tValue: \"b2\",\n\t})\n\n\terr := DefaultBinder().Bind(req.Req, &s, params)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, \"a2\", s.A)\n\tassert.DeepEqual(t, \"b2\", s.B)\n\tassert.DeepEqual(t, \"c1\", s.C)\n}\n\nfunc TestBind_ZeroValueBind(t *testing.T) {\n\tvar s struct {\n\t\tA int     `query:\"a\"`\n\t\tB float64 `query:\"b\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?a=&b\")\n\n\tbindConfig := &BindConfig{}\n\tbindConfig.LooseZeroMode = true\n\tbinder := NewDefaultBinder(bindConfig)\n\terr := binder.Bind(req.Req, &s, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, 0, s.A)\n\tassert.DeepEqual(t, float64(0), s.B)\n}\n\nfunc TestBind_DefaultValueBind(t *testing.T) {\n\tvar s struct {\n\t\tA int       `default:\"15\"`\n\t\tB float64   `query:\"b\" default:\"17\"`\n\t\tC []int     `default:\"[15]\"`\n\t\tD []string  `default:\"['qwe','asd']\"`\n\t\tF [2]string `default:\"['qwe','asd','zxc']\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\")\n\n\terr := DefaultBinder().Bind(req.Req, &s, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, 15, s.A)\n\tassert.DeepEqual(t, float64(17), s.B)\n\tassert.DeepEqual(t, 15, s.C[0])\n\tassert.DeepEqual(t, 2, len(s.D))\n\tassert.DeepEqual(t, \"qwe\", s.D[0])\n\tassert.DeepEqual(t, \"asd\", s.D[1])\n\tassert.DeepEqual(t, 2, len(s.F))\n\tassert.DeepEqual(t, \"qwe\", s.F[0])\n\tassert.DeepEqual(t, \"asd\", s.F[1])\n\n\tvar s2 struct {\n\t\tF [2]string `default:\"['qwe']\"`\n\t}\n\terr = DefaultBinder().Bind(req.Req, &s2, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, 2, len(s2.F))\n\tassert.DeepEqual(t, \"qwe\", s2.F[0])\n\tassert.DeepEqual(t, \"\", s2.F[1])\n\n\tvar d struct {\n\t\tD [2]string `default:\"qwe\"`\n\t}\n\n\terr = DefaultBinder().Bind(req.Req, &d, nil)\n\tif err == nil {\n\t\tt.Fatal(\"expected err\")\n\t}\n}\n\nfunc TestBind_RequiredBind(t *testing.T) {\n\tvar s struct {\n\t\tA int `query:\"a,required\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\")\n\terr := DefaultBinder().Bind(req.Req, &s, nil)\n\tassert.DeepEqual(t, \"'a' field is a 'required' parameter, but the request does not have this parameter\", err.Error())\n\n\treq = newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetHeader(\"A\", \"1\")\n\terr = DefaultBinder().Bind(req.Req, &s, nil)\n\tif err == nil {\n\t\tt.Fatal(\"expected error\")\n\t}\n\n\tvar d struct {\n\t\tA int `query:\"a,required\" header:\"A\"`\n\t}\n\terr = DefaultBinder().Bind(req.Req, &d, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, 1, d.A)\n}\n\nfunc TestBind_TypedefType(t *testing.T) {\n\ttype Foo string\n\ttype Bar *int\n\ttype T struct {\n\t\tT1 string `query:\"a\"`\n\t}\n\ttype TT T\n\n\tvar s struct {\n\t\tA  Foo `query:\"a\"`\n\t\tB  Bar `query:\"b\"`\n\t\tT1 TT\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?a=1&b=2\")\n\terr := DefaultBinder().Bind(req.Req, &s, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, Foo(\"1\"), s.A)\n\tassert.DeepEqual(t, 2, *s.B)\n\tassert.DeepEqual(t, \"1\", s.T1.T1)\n}\n\ntype EnumType int64\n\nconst (\n\tEnumType_TWEET   EnumType = 0\n\tEnumType_RETWEET EnumType = 2\n)\n\nfunc (p EnumType) String() string {\n\tswitch p {\n\tcase EnumType_TWEET:\n\t\treturn \"TWEET\"\n\tcase EnumType_RETWEET:\n\t\treturn \"RETWEET\"\n\t}\n\treturn \"<UNSET>\"\n}\n\nfunc TestBind_EnumBind(t *testing.T) {\n\tvar s struct {\n\t\tA EnumType `query:\"a\"`\n\t\tB EnumType `query:\"b\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?a=0&b=2\")\n\terr := DefaultBinder().Bind(req.Req, &s, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\ntype CustomizedDecode struct {\n\tA string\n}\n\nfunc TestBind_CustomizedTypeDecode(t *testing.T) {\n\ttype Foo struct {\n\t\tF ***CustomizedDecode `query:\"a\"`\n\t}\n\n\tbindConfig := &BindConfig{}\n\terr := bindConfig.RegTypeUnmarshal(reflect.TypeOf(CustomizedDecode{}), func(req *protocol.Request, params param.Params, text string) (reflect.Value, error) {\n\t\tq1 := req.URI().QueryArgs().Peek(\"a\")\n\t\tif len(q1) == 0 {\n\t\t\treturn reflect.Value{}, fmt.Errorf(\"can be nil\")\n\t\t}\n\t\tval := CustomizedDecode{\n\t\t\tA: string(q1),\n\t\t}\n\t\treturn reflect.ValueOf(val), nil\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tbinder := NewDefaultBinder(bindConfig)\n\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?a=1&b=2\")\n\tresult := Foo{}\n\terr = binder.Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, \"1\", (***result.F).A)\n\n\ttype Bar struct {\n\t\tB *Foo\n\t}\n\n\tresult2 := Bar{}\n\terr = binder.Bind(req.Req, &result2, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"1\", (***(*result2.B).F).A)\n}\n\nfunc TestBind_CustomizedTypeDecodeForPanic(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"expect a panic, but get nil\")\n\t\t}\n\t}()\n\n\tbindConfig := &BindConfig{}\n\tbindConfig.MustRegTypeUnmarshal(reflect.TypeOf(string(\"\")), func(req *protocol.Request, params param.Params, text string) (reflect.Value, error) {\n\t\treturn reflect.Value{}, nil\n\t})\n}\n\nfunc TestBind_JSON(t *testing.T) {\n\ttype Req struct {\n\t\tJ1 string    `json:\"j1\"`\n\t\tJ2 int       `json:\"j2\" query:\"j2\"` // 1. json unmarshal 2. query binding cover\n\t\tJ3 []byte    `json:\"j3\"`\n\t\tJ4 [2]string `json:\"j4\"`\n\t}\n\tJ3s := []byte(\"12\")\n\tJ4s := [2]string{\"qwe\", \"asd\"}\n\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?j2=13\").\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(fmt.Sprintf(`{\"j1\":\"j1\", \"j2\":12, \"j3\":[%d, %d], \"j4\":[\"%s\", \"%s\"]}`, J3s[0], J3s[1], J4s[0], J4s[1])))\n\tvar result Req\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"j1\", result.J1)\n\tassert.DeepEqual(t, 13, result.J2)\n\tfor idx, val := range J3s {\n\t\tassert.DeepEqual(t, val, result.J3[idx])\n\t}\n\tfor idx, val := range J4s {\n\t\tassert.DeepEqual(t, val, result.J4[idx])\n\t}\n}\n\nfunc TestBind_ResetJSONUnmarshal(t *testing.T) {\n\tbindConfig := &BindConfig{}\n\tbindConfig.UseStdJSONUnmarshaler()\n\tbinder := NewDefaultBinder(bindConfig)\n\ttype Req struct {\n\t\tJ1 string    `json:\"j1\"`\n\t\tJ2 int       `json:\"j2\"`\n\t\tJ3 []byte    `json:\"j3\"`\n\t\tJ4 [2]string `json:\"j4\"`\n\t}\n\tJ3s := []byte(\"12\")\n\tJ4s := [2]string{\"qwe\", \"asd\"}\n\n\treq := newMockRequest().\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(fmt.Sprintf(`{\"j1\":\"j1\", \"j2\":12, \"j3\":[%d, %d], \"j4\":[\"%s\", \"%s\"]}`, J3s[0], J3s[1], J4s[0], J4s[1])))\n\tvar result Req\n\terr := binder.Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"j1\", result.J1)\n\tassert.DeepEqual(t, 12, result.J2)\n\tfor idx, val := range J3s {\n\t\tassert.DeepEqual(t, val, result.J3[idx])\n\t}\n\tfor idx, val := range J4s {\n\t\tassert.DeepEqual(t, val, result.J4[idx])\n\t}\n}\n\nfunc TestBind_FileBind(t *testing.T) {\n\ttype Nest struct {\n\t\tN multipart.FileHeader `file_name:\"d\"`\n\t}\n\n\tvar s struct {\n\t\tA *multipart.FileHeader `file_name:\"a\"`\n\t\tB *multipart.FileHeader `form:\"b\"`\n\t\tC multipart.FileHeader\n\t\tD **Nest `file_name:\"d\"`\n\t}\n\tfileName := \"binder_test.go\"\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetFile(\"a\", fileName).\n\t\tSetFile(\"b\", fileName).\n\t\tSetFile(\"C\", fileName).\n\t\tSetFile(\"d\", fileName)\n\t// to parse multipart files\n\treq2 := req2.GetHTTP1Request(req.Req)\n\t_ = req2.String()\n\terr := DefaultBinder().Bind(req.Req, &s, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, fileName, s.A.Filename)\n\tassert.DeepEqual(t, fileName, s.B.Filename)\n\tassert.DeepEqual(t, fileName, s.C.Filename)\n\tassert.DeepEqual(t, fileName, (**s.D).N.Filename)\n}\n\nfunc TestBind_FileBindWithNoFile(t *testing.T) {\n\tvar s struct {\n\t\tA *multipart.FileHeader `file_name:\"a\"`\n\t\tB *multipart.FileHeader `form:\"b\"`\n\t\tC *multipart.FileHeader\n\t}\n\tfileName := \"binder_test.go\"\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetFile(\"a\", fileName).\n\t\tSetFile(\"b\", fileName)\n\t// to parse multipart files\n\treq2 := req2.GetHTTP1Request(req.Req)\n\t_ = req2.String()\n\terr := DefaultBinder().Bind(req.Req, &s, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected err: %v\", err)\n\t}\n\tassert.DeepEqual(t, fileName, s.A.Filename)\n\tassert.DeepEqual(t, fileName, s.B.Filename)\n\tif s.C != nil {\n\t\tt.Fatalf(\"expected a nil for s.C\")\n\t}\n}\n\nfunc TestBind_FileSliceBind(t *testing.T) {\n\ttype Nest struct {\n\t\tN *[]*multipart.FileHeader `form:\"b\"`\n\t}\n\tvar s struct {\n\t\tA []multipart.FileHeader  `form:\"a\"`\n\t\tB [3]multipart.FileHeader `form:\"b\"`\n\t\tC []*multipart.FileHeader `form:\"b\"`\n\t\tD Nest\n\t}\n\tfileName := \"binder_test.go\"\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetFile(\"a\", fileName).\n\t\tSetFile(\"a\", fileName).\n\t\tSetFile(\"a\", fileName).\n\t\tSetFile(\"b\", fileName).\n\t\tSetFile(\"b\", fileName).\n\t\tSetFile(\"b\", fileName)\n\t// to parse multipart files\n\treq2 := req2.GetHTTP1Request(req.Req)\n\t_ = req2.String()\n\terr := DefaultBinder().Bind(req.Req, &s, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, 3, len(s.A))\n\tfor _, file := range s.A {\n\t\tassert.DeepEqual(t, fileName, file.Filename)\n\t}\n\tassert.DeepEqual(t, 3, len(s.B))\n\tfor _, file := range s.B {\n\t\tassert.DeepEqual(t, fileName, file.Filename)\n\t}\n\tassert.DeepEqual(t, 3, len(s.C))\n\tfor _, file := range s.C {\n\t\tassert.DeepEqual(t, fileName, file.Filename)\n\t}\n\tassert.DeepEqual(t, 3, len(*s.D.N))\n\tfor _, file := range *s.D.N {\n\t\tassert.DeepEqual(t, fileName, file.Filename)\n\t}\n}\n\nfunc TestBind_AnonymousField(t *testing.T) {\n\ttype nest struct {\n\t\tn1     string       `query:\"n1\"` // bind default value\n\t\tN2     ***string    `query:\"n2\"` // bind n2 value\n\t\tstring `query:\"n3\"` // bind default value\n\t}\n\n\tvar s struct {\n\t\ts1  int          `query:\"s1\"` // bind default value\n\t\tint `query:\"s2\"` // bind default value\n\t\tnest\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?s1=1&s2=2&n1=1&n2=2&n3=3\")\n\terr := DefaultBinder().Bind(req.Req, &s, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, 0, s.s1)\n\tassert.DeepEqual(t, 0, s.int)\n\tassert.DeepEqual(t, \"\", s.nest.n1)\n\tassert.DeepEqual(t, \"2\", ***s.nest.N2)\n\tassert.DeepEqual(t, \"\", s.nest.string)\n}\n\nfunc TestBind_IgnoreField(t *testing.T) {\n\ttype Req struct {\n\t\tVersion int    `path:\"-\"`\n\t\tID      int    `query:\"-\"`\n\t\tHeader  string `header:\"-\"`\n\t\tForm    string `form:\"-\"`\n\t}\n\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?ID=12\").\n\t\tSetHeaders(\"Header\", \"header\").\n\t\tSetPostArg(\"Form\", \"form\").\n\t\tSetUrlEncodeContentType()\n\tvar params param.Params\n\tparams = append(params, param.Param{\n\t\tKey:   \"Version\",\n\t\tValue: \"1\",\n\t})\n\n\tvar result Req\n\n\terr := DefaultBinder().Bind(req.Req, &result, params)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 0, result.Version)\n\tassert.DeepEqual(t, 0, result.ID)\n\tassert.DeepEqual(t, \"\", result.Header)\n\tassert.DeepEqual(t, \"\", result.Form)\n}\n\nfunc TestBind_DefaultTag(t *testing.T) {\n\ttype Req struct {\n\t\tVersion int\n\t\tID      int\n\t\tHeader  string\n\t\tForm    string\n\t}\n\ttype Req2 struct {\n\t\tVersion int\n\t\tID      int\n\t\tHeader  string\n\t\tForm    string\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?ID=12\").\n\t\tSetHeaders(\"Header\", \"header\").\n\t\tSetPostArg(\"Form\", \"form\").\n\t\tSetUrlEncodeContentType()\n\tvar params param.Params\n\tparams = append(params, param.Param{\n\t\tKey:   \"Version\",\n\t\tValue: \"1\",\n\t})\n\tvar result Req\n\terr := DefaultBinder().Bind(req.Req, &result, params)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 1, result.Version)\n\tassert.DeepEqual(t, 12, result.ID)\n\tassert.DeepEqual(t, \"header\", result.Header)\n\tassert.DeepEqual(t, \"form\", result.Form)\n\n\tbindConfig := &BindConfig{}\n\tbindConfig.DisableDefaultTag = true\n\tbinder := NewDefaultBinder(bindConfig)\n\tresult2 := Req2{}\n\terr = binder.Bind(req.Req, &result2, params)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 0, result2.Version)\n\tassert.DeepEqual(t, 0, result2.ID)\n\tassert.DeepEqual(t, \"\", result2.Header)\n\tassert.DeepEqual(t, \"\", result2.Form)\n}\n\nfunc TestBind_StructFieldResolve(t *testing.T) {\n\ttype Nested struct {\n\t\tA int `query:\"a\" json:\"a\"`\n\t\tB int `query:\"b\" json:\"b\"`\n\t}\n\ttype Req struct {\n\t\tN Nested `query:\"n\"`\n\t}\n\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?n={\\\"a\\\":1,\\\"b\\\":2}\").\n\t\tSetHeaders(\"Header\", \"header\").\n\t\tSetPostArg(\"Form\", \"form\").\n\t\tSetUrlEncodeContentType()\n\tvar result Req\n\tbindConfig := &BindConfig{}\n\tbindConfig.DisableStructFieldResolve = false\n\tbinder := NewDefaultBinder(bindConfig)\n\terr := binder.Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 1, result.N.A)\n\tassert.DeepEqual(t, 2, result.N.B)\n\n\treq = newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?n={\\\"a\\\":1,\\\"b\\\":2}&a=11&b=22\").\n\t\tSetHeaders(\"Header\", \"header\").\n\t\tSetPostArg(\"Form\", \"form\").\n\t\tSetUrlEncodeContentType()\n\terr = DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 11, result.N.A)\n\tassert.DeepEqual(t, 22, result.N.B)\n}\n\nfunc TestBind_JSONRequiredField(t *testing.T) {\n\ttype Nested2 struct {\n\t\tC int `json:\"c,required\"`\n\t\tD int `json:\"dd,required\"`\n\t}\n\ttype Nested struct {\n\t\tA  int     `json:\"a,required\"`\n\t\tB  int     `json:\"b,required\"`\n\t\tN2 Nested2 `json:\"n2\"`\n\t}\n\ttype Req struct {\n\t\tN Nested `json:\"n,required\"`\n\t}\n\tbodyBytes := []byte(`{\n    \"n\": {\n        \"a\": 1,\n        \"b\": 2,\n        \"n2\": {\n             \"dd\": 4\n        }\n    }\n}`)\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?j2=13\").\n\t\tSetJSONContentType().\n\t\tSetBody(bodyBytes)\n\tvar result Req\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err == nil {\n\t\tt.Errorf(\"expected an error, but get nil\")\n\t}\n\tassert.DeepEqual(t, \"'c' field is a 'required' parameter, but the request body does not have this parameter 'n.n2.c'\", err.Error())\n\tassert.DeepEqual(t, 1, result.N.A)\n\tassert.DeepEqual(t, 2, result.N.B)\n\tassert.DeepEqual(t, 0, result.N.N2.C)\n\tassert.DeepEqual(t, 4, result.N.N2.D)\n\n\tbodyBytes = []byte(`{\n    \"n\": {\n        \"a\": 1,\n        \"b\": 2\n    }\n}`)\n\treq = newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?j2=13\").\n\t\tSetJSONContentType().\n\t\tSetBody(bodyBytes)\n\tvar result2 Req\n\terr = DefaultBinder().Bind(req.Req, &result2, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 1, result2.N.A)\n\tassert.DeepEqual(t, 2, result2.N.B)\n\tassert.DeepEqual(t, 0, result2.N.N2.C)\n\tassert.DeepEqual(t, 0, result2.N.N2.D)\n}\n\nfunc TestValidate_MultipleValidate(t *testing.T) {\n\ttype Test1 struct {\n\t\tA int `query:\"a\" vd:\"$>10\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?a=9\")\n\tvar result Test1\n\terr := BindAndValidate(req.Req, &result, nil)\n\tif err == nil {\n\t\tt.Fatalf(\"expected an error, but get nil\")\n\t}\n}\n\nfunc TestBind_BindQuery(t *testing.T) {\n\ttype Req struct {\n\t\tQ1 int `query:\"q1\"`\n\t\tQ2 int\n\t\tQ3 string\n\t\tQ4 string\n\t\tQ5 []int\n\t}\n\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?q1=1&Q2=2&Q3=3&Q4=4&Q5=51&Q5=52\")\n\n\tvar result Req\n\n\terr := DefaultBinder().BindQuery(req.Req, &result)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 1, result.Q1)\n\tassert.DeepEqual(t, 2, result.Q2)\n\tassert.DeepEqual(t, \"3\", result.Q3)\n\tassert.DeepEqual(t, \"4\", result.Q4)\n\tassert.DeepEqual(t, 51, result.Q5[0])\n\tassert.DeepEqual(t, 52, result.Q5[1])\n}\n\nfunc TestBind_LooseMode(t *testing.T) {\n\tbindConfig := &BindConfig{}\n\tbindConfig.LooseZeroMode = false\n\tbinder := NewDefaultBinder(bindConfig)\n\ttype Req struct {\n\t\tID int `query:\"id\"`\n\t}\n\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?id=\")\n\n\tvar result Req\n\n\terr := binder.Bind(req.Req, &result, nil)\n\tif err == nil {\n\t\tt.Fatal(\"expected err\")\n\t}\n\tassert.DeepEqual(t, 0, result.ID)\n\n\tbindConfig.LooseZeroMode = true\n\tbinder = NewDefaultBinder(bindConfig)\n\tvar result2 Req\n\n\terr = binder.Bind(req.Req, &result2, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 0, result.ID)\n}\n\nfunc TestBind_NonStruct(t *testing.T) {\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?id=1&id=2\")\n\tvar id interface{}\n\terr := DefaultBinder().Bind(req.Req, &id, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\terr = BindAndValidate(req.Req, &id, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestBind_BindTag(t *testing.T) {\n\ttype Req struct {\n\t\tQuery  string\n\t\tHeader string\n\t\tPath   string\n\t\tForm   string\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?Query=query\").\n\t\tSetHeader(\"Header\", \"header\").\n\t\tSetPostArg(\"Form\", \"form\")\n\tvar params param.Params\n\tparams = append(params, param.Param{\n\t\tKey:   \"Path\",\n\t\tValue: \"path\",\n\t})\n\tresult := Req{}\n\n\t// test query tag\n\terr := DefaultBinder().BindQuery(req.Req, &result)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"query\", result.Query)\n\n\t// test header tag\n\tresult = Req{}\n\terr = DefaultBinder().BindHeader(req.Req, &result)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"header\", result.Header)\n\n\t// test form tag\n\tresult = Req{}\n\terr = DefaultBinder().BindForm(req.Req, &result)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"form\", result.Form)\n\n\t// test path tag\n\tresult = Req{}\n\terr = DefaultBinder().BindPath(req.Req, &result, params)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"path\", result.Path)\n\n\t// test json tag\n\treq = newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(\"{\\n    \\\"Query\\\": \\\"query\\\",\\n    \\\"Path\\\": \\\"path\\\",\\n    \\\"Header\\\": \\\"header\\\",\\n    \\\"Form\\\": \\\"form\\\"\\n}\"))\n\tresult = Req{}\n\terr = DefaultBinder().BindJSON(req.Req, &result)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"form\", result.Form)\n\tassert.DeepEqual(t, \"query\", result.Query)\n\tassert.DeepEqual(t, \"header\", result.Header)\n\tassert.DeepEqual(t, \"path\", result.Path)\n}\n\nfunc TestBind_BindAndValidate(t *testing.T) {\n\ttype Req struct {\n\t\tID int `query:\"id\" vd:\"$>10\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?id=12\")\n\n\t// test bindAndValidate\n\tvar result Req\n\terr := BindAndValidate(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 12, result.ID)\n\n\t// test bind\n\tresult = Req{}\n\terr = Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 12, result.ID)\n\n\t// test validate\n\treq = newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?id=9\")\n\tresult = Req{}\n\terr = Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = Validate(result)\n\tif err == nil {\n\t\tt.Errorf(\"expect an error, but get nil\")\n\t}\n\tassert.DeepEqual(t, 9, result.ID)\n}\n\nfunc TestBind_FastPath(t *testing.T) {\n\ttype Req struct {\n\t\tID int `query:\"id\" vd:\"$>10\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?id=12\")\n\n\t// test bindAndValidate\n\tvar result Req\n\terr := BindAndValidate(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 12, result.ID)\n\t// execute multiple times, test cache\n\tfor i := 0; i < 10; i++ {\n\t\tresult = Req{}\n\t\terr := BindAndValidate(req.Req, &result, nil)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tassert.DeepEqual(t, 12, result.ID)\n\t}\n}\n\nfunc TestBind_NonPointer(t *testing.T) {\n\ttype Req struct {\n\t\tID int `query:\"id\" vd:\"$>10\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?id=12\")\n\n\t// test bindAndValidate\n\tvar result Req\n\terr := BindAndValidate(req.Req, result, nil)\n\tif err == nil {\n\t\tt.Error(\"expect an error, but get nil\")\n\t}\n\n\terr = Bind(req.Req, result, nil)\n\tif err == nil {\n\t\tt.Error(\"expect an error, but get nil\")\n\t}\n}\n\nfunc TestBind_PreBind(t *testing.T) {\n\ttype Req struct {\n\t\tQuery  string\n\t\tHeader string\n\t\tPath   string\n\t\tForm   string\n\t}\n\t// test json tag\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(\"\\n    \\\"Query\\\": \\\"query\\\",\\n    \\\"Path\\\": \\\"path\\\",\\n    \\\"Header\\\": \\\"header\\\",\\n    \\\"Form\\\": \\\"form\\\"\\n}\"))\n\tresult := Req{}\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err == nil {\n\t\tt.Error(\"expect an error, but get nil\")\n\t}\n\terr = BindAndValidate(req.Req, &result, nil)\n\tif err == nil {\n\t\tt.Error(\"expect an error, but get nil\")\n\t}\n}\n\nfunc TestBind_BindProtobuf(t *testing.T) {\n\tdata := testdata.HertzReq{Name: \"hertz\"}\n\tbody, err := proto.Marshal(&data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetProtobufContentType().\n\t\tSetBody(body)\n\n\tresult := testdata.HertzReq{}\n\terr = BindAndValidate(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"hertz\", result.Name)\n\n\tresult = testdata.HertzReq{}\n\terr = DefaultBinder().BindProtobuf(req.Req, &result)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"hertz\", result.Name)\n}\n\nfunc TestBind_PointerStruct(t *testing.T) {\n\tbindConfig := &BindConfig{}\n\tbindConfig.DisableStructFieldResolve = false\n\tbinder := NewDefaultBinder(bindConfig)\n\ttype Foo struct {\n\t\tF1 string `query:\"F1\"`\n\t}\n\ttype Bar struct {\n\t\tB1 **Foo `query:\"B1,required\"`\n\t}\n\tquery := make(url.Values)\n\tquery.Add(\"B1\", \"{\\n    \\\"F1\\\": \\\"111\\\"\\n}\")\n\n\tvar result Bar\n\treq := newMockRequest().\n\t\tSetRequestURI(fmt.Sprintf(\"http://foobar.com?%s\", query.Encode()))\n\n\terr := binder.Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"111\", (**result.B1).F1)\n\n\tresult = Bar{}\n\treq = newMockRequest().\n\t\tSetRequestURI(fmt.Sprintf(\"http://foobar.com?%s&F1=222\", query.Encode()))\n\terr = binder.Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"222\", (**result.B1).F1)\n}\n\nfunc TestBind_StructRequired(t *testing.T) {\n\tbindConfig := &BindConfig{}\n\tbindConfig.DisableStructFieldResolve = false\n\tbinder := NewDefaultBinder(bindConfig)\n\ttype Foo struct {\n\t\tF1 string `query:\"F1\"`\n\t}\n\ttype Bar struct {\n\t\tB1 **Foo `query:\"B1,required\"`\n\t}\n\n\tvar result Bar\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\")\n\n\terr := binder.Bind(req.Req, &result, nil)\n\tif err == nil {\n\t\tt.Error(\"expect an error, but get nil\")\n\t}\n\n\ttype Bar2 struct {\n\t\tB1 **Foo `query:\"B1\"`\n\t}\n\tvar result2 Bar2\n\treq = newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\")\n\n\terr = binder.Bind(req.Req, &result2, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestBind_StructErrorToWarn(t *testing.T) {\n\tbindConfig := &BindConfig{}\n\tbindConfig.DisableStructFieldResolve = false\n\tbinder := NewDefaultBinder(bindConfig)\n\ttype Foo struct {\n\t\tF1 string `query:\"F1\"`\n\t}\n\ttype Bar struct {\n\t\tB1 **Foo `query:\"B1,required\"`\n\t}\n\n\tvar result Bar\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?B1=111&F1=222\")\n\n\terr := binder.Bind(req.Req, &result, nil)\n\t// transfer 'unmarsahl err' to 'warn'\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"222\", (**result.B1).F1)\n\n\ttype Bar2 struct {\n\t\tB1 Foo `query:\"B1,required\"`\n\t}\n\tvar result2 Bar2\n\terr = binder.Bind(req.Req, &result2, nil)\n\t// transfer 'unmarsahl err' to 'warn'\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"222\", result2.B1.F1)\n}\n\nfunc TestBind_DisallowUnknownFieldsConfig(t *testing.T) {\n\tbindConfig := &BindConfig{}\n\tbindConfig.EnableDecoderDisallowUnknownFields = true\n\tbinder := NewDefaultBinder(bindConfig)\n\ttype FooStructUseNumber struct {\n\t\tFoo interface{} `json:\"foo\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(`{\"foo\": 123,\"bar\": \"456\"}`))\n\tvar result FooStructUseNumber\n\n\terr := binder.BindJSON(req.Req, &result)\n\tif err == nil {\n\t\tt.Errorf(\"expected an error, but get nil\")\n\t}\n}\n\nfunc TestBind_UseNumberConfig(t *testing.T) {\n\tbindConfig := &BindConfig{}\n\tbindConfig.EnableDecoderUseNumber = true\n\tbinder := NewDefaultBinder(bindConfig)\n\ttype FooStructUseNumber struct {\n\t\tFoo interface{} `json:\"foo\"`\n\t}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(`{\"foo\": 123}`))\n\tvar result FooStructUseNumber\n\n\terr := binder.BindJSON(req.Req, &result)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tv, err := result.Foo.(json.Number).Int64()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, int64(123), v)\n}\n\nfunc TestBind_InterfaceType(t *testing.T) {\n\ttype Bar struct {\n\t\tB1 interface{} `query:\"B1\"`\n\t}\n\n\tvar result Bar\n\tquery := make(url.Values)\n\tquery.Add(\"B1\", `{\"B1\":\"111\"}`)\n\treq := newMockRequest().\n\t\tSetRequestURI(fmt.Sprintf(\"http://foobar.com?%s\", query.Encode()))\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\ttype Bar2 struct {\n\t\tB2 *interface{} `query:\"B1\"`\n\t}\n\n\tvar result2 Bar2\n\terr = DefaultBinder().Bind(req.Req, &result2, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc Test_BindHeaderNormalize(t *testing.T) {\n\ttype Req struct {\n\t\tHeader string `header:\"h\"`\n\t}\n\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetHeaders(\"h\", \"header\")\n\tvar result Req\n\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"header\", result.Header)\n\treq = newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetHeaders(\"H\", \"header\")\n\terr = DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"header\", result.Header)\n\n\ttype Req2 struct {\n\t\tHeader string `header:\"H\"`\n\t}\n\n\treq2 := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetHeaders(\"h\", \"header\")\n\tvar result2 Req2\n\n\terr2 := DefaultBinder().Bind(req2.Req, &result2, nil)\n\tif err != nil {\n\t\tt.Error(err2)\n\t}\n\tassert.DeepEqual(t, \"header\", result2.Header)\n\treq2 = newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\").\n\t\tSetHeaders(\"H\", \"header\")\n\terr2 = DefaultBinder().Bind(req2.Req, &result2, nil)\n\tif err2 != nil {\n\t\tt.Error(err2)\n\t}\n\tassert.DeepEqual(t, \"header\", result2.Header)\n\n\ttype Req3 struct {\n\t\tHeader string `header:\"h\"`\n\t}\n\n\t// without normalize, the header key & tag key need to be consistent\n\treq3 := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\")\n\treq3.Req.Header.DisableNormalizing()\n\treq3.SetHeaders(\"h\", \"header\")\n\tvar result3 Req3\n\terr3 := DefaultBinder().Bind(req3.Req, &result3, nil)\n\tif err3 != nil {\n\t\tt.Error(err3)\n\t}\n\tassert.DeepEqual(t, \"header\", result3.Header)\n\treq3 = newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com\")\n\treq3.Req.Header.DisableNormalizing()\n\treq3.SetHeaders(\"H\", \"header\")\n\tresult3 = Req3{}\n\terr3 = DefaultBinder().Bind(req3.Req, &result3, nil)\n\tif err3 != nil {\n\t\tt.Error(err3)\n\t}\n\tassert.DeepEqual(t, \"\", result3.Header)\n}\n\ntype ValidateError struct {\n\tErrType, FailField, Msg string\n}\n\n// Error implements error interface.\nfunc (e *ValidateError) Error() string {\n\tif e.Msg != \"\" {\n\t\treturn e.ErrType + \": expr_path=\" + e.FailField + \", cause=\" + e.Msg\n\t}\n\treturn e.ErrType + \": expr_path=\" + e.FailField + \", cause=invalid\"\n}\n\nfunc Test_ValidatorErrorFactory(t *testing.T) {\n\ttype TestBind struct {\n\t\tA string `query:\"a,required\"`\n\t}\n\n\tr := protocol.NewRequest(\"GET\", \"/foo\", nil)\n\tr.SetRequestURI(\"/foo/bar?b=20\")\n\tCustomValidateErrFunc := func(failField, msg string) error {\n\t\terr := ValidateError{\n\t\t\tErrType:   \"validateErr\",\n\t\t\tFailField: \"[validateFailField]: \" + failField,\n\t\t\tMsg:       \"[validateErrMsg]: \" + msg,\n\t\t}\n\n\t\treturn &err\n\t}\n\n\tvalidateConfig := NewValidateConfig()\n\tvalidateConfig.SetValidatorErrorFactory(CustomValidateErrFunc)\n\tvalidator := NewValidator(validateConfig)\n\n\tvar req TestBind\n\terr := Bind(r, &req, nil)\n\tif err == nil {\n\t\tt.Fatalf(\"unexpected nil, expected an error\")\n\t}\n\tassert.DeepEqual(t, \"'a' field is a 'required' parameter, but the request does not have this parameter\", err.Error())\n\n\ttype TestValidate struct {\n\t\tB int `query:\"b\" vd:\"$>100\"`\n\t}\n\n\tvar reqValidate TestValidate\n\terr = Bind(r, &reqValidate, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\terr = validator.ValidateStruct(&reqValidate)\n\tif err == nil {\n\t\tt.Fatalf(\"unexpected nil, expected an error\")\n\t}\n\tassert.DeepEqual(t, \"validateErr: expr_path=[validateFailField]: B, cause=[validateErrMsg]: \", err.Error())\n}\n\n// Test_Issue964 used to the cover issue for time.Time\nfunc Test_Issue964(t *testing.T) {\n\ttype CreateReq struct {\n\t\tStartAt *time.Time `json:\"startAt\"`\n\t}\n\tr := newMockRequest().SetBody([]byte(\"{\\n  \\\"startAt\\\": \\\"2006-01-02T15:04:05+07:00\\\"\\n}\")).SetJSONContentType()\n\tvar req CreateReq\n\terr := BindAndValidate(r.Req, &req, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"2006-01-02 15:04:05 +0700 +0700\", req.StartAt.String())\n\tr = newMockRequest()\n\treq = CreateReq{}\n\terr = BindAndValidate(r.Req, &req, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif req.StartAt != nil {\n\t\tt.Error(\"expected nil\")\n\t}\n}\n\ntype reqSameType struct {\n\tParent   *reqSameType  `json:\"parent\"`\n\tChildren []reqSameType `json:\"children\"`\n\tFoo1     reqSameType2  `json:\"foo1\"`\n\tA        string        `json:\"a\"`\n}\n\ntype reqSameType2 struct {\n\tFoo1 *reqSameType `json:\"foo1\"`\n}\n\nfunc TestBind_Issue1015(t *testing.T) {\n\treq := newMockRequest().\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(`{\"parent\":{\"parent\":{}, \"children\":[{},{}], \"foo1\":{\"foo1\":{}}}, \"children\":[{},{}], \"a\":\"asd\"}`))\n\n\tvar result reqSameType\n\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.NotNil(t, result.Parent)\n\tassert.NotNil(t, result.Parent.Parent)\n\tassert.Nil(t, result.Parent.Parent.Parent)\n\tassert.NotNil(t, result.Parent.Children)\n\tassert.DeepEqual(t, 2, len(result.Parent.Children))\n\tassert.NotNil(t, result.Parent.Foo1.Foo1)\n\tassert.DeepEqual(t, \"\", result.Parent.A)\n\tassert.DeepEqual(t, 2, len(result.Children))\n\tassert.Nil(t, result.Foo1.Foo1)\n\tassert.DeepEqual(t, \"asd\", result.A)\n}\n\nfunc TestBind_JSONWithDefault(t *testing.T) {\n\ttype Req struct {\n\t\tJ1 string `json:\"j1\" default:\"j1default\"`\n\t}\n\n\treq := newMockRequest().\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(`{\"j1\":\"j1\"}`))\n\tvar result Req\n\terr := DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"j1\", result.J1)\n\n\tresult = Req{}\n\treq = newMockRequest().\n\t\tSetJSONContentType().\n\t\tSetBody([]byte(`{\"j2\":\"j2\"}`))\n\terr = DefaultBinder().Bind(req.Req, &result, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"j1default\", result.J1)\n}\n\nfunc TestBind_WithoutPreBindForTag(t *testing.T) {\n\ttype BaseQuery struct {\n\t\tAction  string `query:\"Action\" binding:\"required\"`\n\t\tVersion string `query:\"Version\" binding:\"required\"`\n\t}\n\n\treq := newMockRequest().\n\t\tSetJSONContentType().\n\t\tSetRequestURI(\"http://foobar.com/?Action=action&Version=version\").\n\t\tSetBody([]byte(``))\n\n\tvar result BaseQuery\n\n\terr := DefaultBinder().BindQuery(req.Req, &result)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"action\", result.Action)\n\tassert.DeepEqual(t, \"version\", result.Version)\n}\n\nfunc TestBind_NormalizeContentType(t *testing.T) {\n\ttype BaseQuery struct {\n\t\tAction  string `json:\"action\" binding:\"required\"`\n\t\tVersion string `json:\"version\" binding:\"required\"`\n\t}\n\n\treq := newMockRequest().\n\t\tSetHeader(\"Content-Type\", \"ApplicAtion/json\").\n\t\tSetRequestURI(\"http://foobar.com/?Action=action&Version=version\").\n\t\tSetBody([]byte(`{\"action\":\"action\", \"version\":\"version\"}`))\n\n\tvar result BaseQuery\n\n\terr := DefaultBinder().BindQuery(req.Req, &result)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"action\", result.Action)\n\tassert.DeepEqual(t, \"version\", result.Version)\n}\n\ntype TestEnumType int32\n\nvar _ encoding.TextUnmarshaler = (*TestEnumType)(nil)\n\nfunc (p *TestEnumType) UnmarshalText(v []byte) error {\n\tswitch string(v) {\n\tcase \"one\":\n\t\t*p = 1\n\tcase \"two\":\n\t\t*p = 2\n\tdefault:\n\t\treturn errors.New(\"invalid\")\n\t}\n\treturn nil\n}\n\nfunc TestBind_TextUnmarshaler(t *testing.T) {\n\ttype Query struct {\n\t\tA TestEnumType  `query:\"a\"`\n\t\tB TestEnumType  `query:\"b\"`\n\t\tC *TestEnumType `query:\"c\"`\n\t\tD *TestEnumType `query:\"d\"`\n\t}\n\tq := &Query{}\n\treq := newMockRequest().SetRequestURI(\"http://example.com?a=1&b=one&c=2&d=two\")\n\terr := DefaultBinder().BindQuery(req.Req, q)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, TestEnumType(1), q.A)\n\tassert.DeepEqual(t, TestEnumType(1), q.B)\n\tassert.NotNil(t, q.C)\n\tassert.NotNil(t, q.D)\n\tassert.DeepEqual(t, TestEnumType(2), *q.C)\n\tassert.DeepEqual(t, TestEnumType(2), *q.D)\n}\n\nfunc Benchmark_Binding(b *testing.B) {\n\ttype Req struct {\n\t\tVersion string `path:\"v\"`\n\t\tID      int    `query:\"id\"`\n\t\tHeader  string `header:\"h\"`\n\t\tForm    string `form:\"f\"`\n\t}\n\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?id=12\").\n\t\tSetHeaders(\"H\", \"header\").\n\t\tSetPostArg(\"f\", \"form\").\n\t\tSetUrlEncodeContentType()\n\n\tvar params param.Params\n\tparams = append(params, param.Param{\n\t\tKey:   \"v\",\n\t\tValue: \"1\",\n\t})\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tvar result Req\n\t\terr := DefaultBinder().Bind(req.Req, &result, params)\n\t\tif err != nil {\n\t\t\tb.Error(err)\n\t\t}\n\t\tif result.ID != 12 {\n\t\t\tb.Error(\"Id failed\")\n\t\t}\n\t\tif result.Form != \"form\" {\n\t\t\tb.Error(\"form failed\")\n\t\t}\n\t\tif result.Header != \"header\" {\n\t\t\tb.Error(\"header failed\")\n\t\t}\n\t\tif result.Version != \"1\" {\n\t\t\tb.Error(\"path failed\")\n\t\t}\n\t}\n}\n\n// TestBind_AnonymousFieldWithDefaultTag tests that default tag values don't override\n// JSON-provided values when using anonymous struct embedding with multiple tags\nfunc TestBind_AnonymousFieldWithDefaultTag(t *testing.T) {\n\ttype PageInfo struct {\n\t\tPage  int `json:\"page\" form:\"page\" query:\"page\" default:\"1\"`\n\t\tLimit int `json:\"limit\" form:\"limit\" query:\"limit\" default:\"15\"`\n\t}\n\ttype Req struct {\n\t\tKeyword string `json:\"keyword\"`\n\t\tPageInfo\n\t}\n\n\t// Test 1: JSON values should override defaults\n\treq := protocol.NewRequest(\"POST\", \"/search\", nil)\n\treq.SetBody([]byte(`{\"keyword\":\"test\",\"page\":2,\"limit\":5}`))\n\treq.Header.SetContentTypeBytes([]byte(\"application/json\"))\n\treq.Header.SetContentLength(37)\n\n\tvar r Req\n\terr := DefaultBinder().Bind(req, &r, nil)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, \"test\", r.Keyword)\n\tassert.DeepEqual(t, 2, r.Page)  // Should use JSON value, not default\n\tassert.DeepEqual(t, 5, r.Limit) // Should use JSON value, not default\n\n\t// Test 2: Empty JSON should use defaults\n\treq2 := protocol.NewRequest(\"POST\", \"/search\", nil)\n\treq2.SetBody([]byte(`{\"keyword\":\"test\"}`))\n\treq2.Header.SetContentTypeBytes([]byte(\"application/json\"))\n\treq2.Header.SetContentLength(20)\n\n\tvar r2 Req\n\terr2 := DefaultBinder().Bind(req2, &r2, nil)\n\tassert.Nil(t, err2)\n\tassert.DeepEqual(t, \"test\", r2.Keyword)\n\tassert.DeepEqual(t, 1, r2.Page)   // Should use default value\n\tassert.DeepEqual(t, 15, r2.Limit) // Should use default value\n\n\t// Test 3: Query values should work\n\treq3 := protocol.NewRequest(\"POST\", \"/search?page=3&limit=4\", nil)\n\treq3.Header.SetContentTypeBytes([]byte(\"application/x-www-form-urlencoded\"))\n\n\tvar r3 Req\n\terr3 := DefaultBinder().Bind(req3, &r3, nil)\n\tassert.Nil(t, err3)\n\tassert.DeepEqual(t, 3, r3.Page)  // Should use query value\n\tassert.DeepEqual(t, 4, r3.Limit) // Should use query value\n\n\t// Test 4: Nested anonymous structs (multi-level)\n\ttype BaseInfo struct {\n\t\tPage int `json:\"page\" form:\"page\" default:\"1\"`\n\t}\n\ttype ExtInfo struct {\n\t\tBaseInfo\n\t\tLimit int `json:\"limit\" form:\"limit\" default:\"20\"`\n\t}\n\ttype Req2 struct {\n\t\tKeyword string `json:\"keyword\"`\n\t\tExtInfo\n\t}\n\n\treq4 := protocol.NewRequest(\"POST\", \"/search\", nil)\n\treq4.SetBody([]byte(`{\"keyword\":\"nested\",\"page\":10,\"limit\":30}`))\n\treq4.Header.SetContentTypeBytes([]byte(\"application/json\"))\n\treq4.Header.SetContentLength(44)\n\n\tvar r4 Req2\n\terr4 := DefaultBinder().Bind(req4, &r4, nil)\n\tassert.Nil(t, err4)\n\tassert.DeepEqual(t, \"nested\", r4.Keyword)\n\tassert.DeepEqual(t, 10, r4.Page)  // Should use JSON value from nested struct\n\tassert.DeepEqual(t, 30, r4.Limit) // Should use JSON value, not default\n}\n"
  },
  {
    "path": "pkg/app/server/binding/config.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage binding\n\nimport (\n\tstdJson \"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n\n\texprValidator \"github.com/cloudwego/hertz/internal/tagexpr/validator\"\n\tinDecoder \"github.com/cloudwego/hertz/pkg/app/server/binding/internal/decoder\"\n\thJson \"github.com/cloudwego/hertz/pkg/common/json\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\n// BindConfig contains options for default bind behavior.\ntype BindConfig struct {\n\t// LooseZeroMode if set to true,\n\t// the empty string request parameter is bound to the zero value of parameter.\n\t// NOTE:\n\t//\tThe default is false.\n\t//\tSuitable for these parameter types: query/header/cookie/form .\n\tLooseZeroMode bool\n\t// DisableDefaultTag is used to add default tags to a field when it has no tag\n\t// If is false, the field with no tag will be added default tags, for more automated binding. But there may be additional overhead.\n\t// NOTE:\n\t// The default is false.\n\tDisableDefaultTag bool\n\t// DisableStructFieldResolve is used to generate a separate decoder for a struct.\n\t// If is false, the 'struct' field will get a single inDecoder.structTypeFieldTextDecoder, and use json.Unmarshal for decode it.\n\t// It usually used to add json string to query parameter.\n\t// NOTE:\n\t// The default is false.\n\tDisableStructFieldResolve bool\n\t// EnableDecoderUseNumber is used to call the UseNumber method on the JSON\n\t// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an\n\t// interface{} as a Number instead of as a float64.\n\t// NOTE:\n\t// The default is false.\n\t// It is used for BindJSON().\n\tEnableDecoderUseNumber bool\n\t// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method\n\t// on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to\n\t// return an error when the destination is a struct and the input contains object\n\t// keys which do not match any non-ignored, exported fields in the destination.\n\t// NOTE:\n\t// The default is false.\n\t// It is used for BindJSON().\n\tEnableDecoderDisallowUnknownFields bool\n\t// TypeUnmarshalFuncs registers customized type unmarshaler.\n\t// NOTE:\n\t// time.Time is registered by default\n\tTypeUnmarshalFuncs map[reflect.Type]inDecoder.CustomizeDecodeFunc\n\n\t// Validator is used to validate for BindAndValidate()\n\t//\n\t// Deprecated: use ValidatorFunc instead. You can create a ValidatorFunc\n\t// from a StructValidator using MakeValidatorFunc()\n\tValidator StructValidator\n\n\t// ValidatorFunc is used to validate structs with custom validation logic.\n\t// It replaces the deprecated Validator field and provides request context.\n\t// NOTE:\n\t//   The default is nil. If set, this takes precedence over the Validator field.\n\t//   The function signature allows access to the request for context-aware validation.\n\tValidatorFunc func(req *protocol.Request, v any) error\n}\n\nfunc NewBindConfig() *BindConfig {\n\treturn &BindConfig{\n\t\tLooseZeroMode:                      false,\n\t\tDisableDefaultTag:                  false,\n\t\tDisableStructFieldResolve:          false,\n\t\tEnableDecoderUseNumber:             false,\n\t\tEnableDecoderDisallowUnknownFields: false,\n\t\tTypeUnmarshalFuncs:                 make(map[reflect.Type]inDecoder.CustomizeDecodeFunc),\n\t\tValidator:                          defaultValidate,\n\t}\n}\n\n// RegTypeUnmarshal registers customized type unmarshaler.\nfunc (config *BindConfig) RegTypeUnmarshal(t reflect.Type, fn inDecoder.CustomizeDecodeFunc) error {\n\t// check\n\tswitch t.Kind() {\n\tcase reflect.String, reflect.Bool,\n\t\treflect.Float32, reflect.Float64,\n\t\treflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8,\n\t\treflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:\n\t\treturn fmt.Errorf(\"registration type cannot be a basic type\")\n\tcase reflect.Ptr:\n\t\treturn fmt.Errorf(\"registration type cannot be a pointer type\")\n\t}\n\tif config.TypeUnmarshalFuncs == nil {\n\t\tconfig.TypeUnmarshalFuncs = make(map[reflect.Type]inDecoder.CustomizeDecodeFunc)\n\t}\n\tconfig.TypeUnmarshalFuncs[t] = fn\n\treturn nil\n}\n\n// MustRegTypeUnmarshal registers customized type unmarshaler. It will panic if exist error.\nfunc (config *BindConfig) MustRegTypeUnmarshal(t reflect.Type, fn func(req *protocol.Request, params param.Params, text string) (reflect.Value, error)) {\n\terr := config.RegTypeUnmarshal(t, fn)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc (config *BindConfig) initTypeUnmarshal() {\n\tconfig.MustRegTypeUnmarshal(reflect.TypeOf(time.Time{}), func(req *protocol.Request, params param.Params, text string) (reflect.Value, error) {\n\t\tif text == \"\" {\n\t\t\treturn reflect.ValueOf(time.Time{}), nil\n\t\t}\n\t\tt, err := time.Parse(time.RFC3339, text)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\t\treturn reflect.ValueOf(t), nil\n\t})\n}\n\n// UseThirdPartyJSONUnmarshaler uses third-party json library for binding\n// NOTE:\n//\n//\tUseThirdPartyJSONUnmarshaler will remain in effect once it has been called.\nfunc (config *BindConfig) UseThirdPartyJSONUnmarshaler(fn func(data []byte, v interface{}) error) {\n\thJson.Unmarshal = fn\n}\n\n// UseStdJSONUnmarshaler uses encoding/json as json library\n// NOTE:\n//\n//\tThe current version uses encoding/json by default.\n//\tUseStdJSONUnmarshaler will remain in effect once it has been called.\nfunc (config *BindConfig) UseStdJSONUnmarshaler() {\n\tconfig.UseThirdPartyJSONUnmarshaler(stdJson.Unmarshal)\n}\n\n// ValidateErrFactory defines the factory function for creating validation errors.\n//\n// Deprecated: Use WithCustomValidatorFunc with a custom validation function instead.\ntype ValidateErrFactory func(fieldSelector, msg string) error\n\n// ValidateConfig configures validation behavior for the built-in StructValidator.\n//\n// Deprecated: Use WithCustomValidatorFunc with a custom validation function instead.\ntype ValidateConfig struct {\n\tValidateTag string\n\tErrFactory  ValidateErrFactory\n}\n\n// NewValidateConfig creates a new ValidateConfig.\n//\n// Deprecated: Use WithCustomValidatorFunc with a custom validation function instead.\nfunc NewValidateConfig() *ValidateConfig {\n\treturn &ValidateConfig{}\n}\n\n// MustRegValidateFunc registers validator function expression.\n// NOTE:\n//\n//\tIf force=true, allow to cover the existed same funcName.\n//\tMustRegValidateFunc will remain in effect once it has been called.\n//\n// Deprecated: Use WithCustomValidatorFunc with a custom validation function instead.\nfunc (config *ValidateConfig) MustRegValidateFunc(funcName string, fn func(args ...interface{}) error, force ...bool) {\n\texprValidator.MustRegFunc(funcName, fn, force...)\n}\n\n// SetValidatorErrorFactory customizes the factory of validation error.\n//\n// Deprecated: Use WithCustomValidatorFunc with a custom validation function instead.\nfunc (config *ValidateConfig) SetValidatorErrorFactory(errFactory ValidateErrFactory) {\n\tconfig.ErrFactory = errFactory\n}\n\n// SetValidatorTag customizes the validation tag.\n//\n// Deprecated: Use WithCustomValidatorFunc with a custom validation function instead.\nfunc (config *ValidateConfig) SetValidatorTag(tag string) {\n\tconfig.ValidateTag = tag\n}\n"
  },
  {
    "path": "pkg/app/server/binding/default.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage binding\n\nimport (\n\t\"bytes\"\n\tstdJson \"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\texprValidator \"github.com/cloudwego/hertz/internal/tagexpr/validator\"\n\tinDecoder \"github.com/cloudwego/hertz/pkg/app/server/binding/internal/decoder\"\n\thJson \"github.com/cloudwego/hertz/pkg/common/json\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nconst (\n\tqueryTag           = \"query\"\n\theaderTag          = \"header\"\n\tformTag            = \"form\"\n\tpathTag            = \"path\"\n\tdefaultValidateTag = \"vd\"\n)\n\ntype decoderInfo struct {\n\tdecoder inDecoder.Decoder\n}\n\nvar defaultBind = (NewDefaultBinder(nil).(*defaultBinder))\n\nfunc DefaultBinder() Binder {\n\treturn defaultBind\n}\n\ntype defaultBinder struct {\n\tconfig             *BindConfig\n\tdecoderCache       sync.Map\n\tqueryDecoderCache  sync.Map\n\tformDecoderCache   sync.Map\n\theaderDecoderCache sync.Map\n\tpathDecoderCache   sync.Map\n}\n\nfunc NewDefaultBinder(config *BindConfig) Binder {\n\tif config == nil {\n\t\tconfig = NewBindConfig()\n\t}\n\tconfig.initTypeUnmarshal()\n\tif config.Validator == nil {\n\t\tconfig.Validator = DefaultValidator()\n\t}\n\t// Initialize ValidatorFunc if not set, using the legacy Validator\n\tif config.ValidatorFunc == nil {\n\t\tconfig.ValidatorFunc = MakeValidatorFunc(config.Validator)\n\t}\n\treturn &defaultBinder{\n\t\tconfig: config,\n\t}\n}\n\n// BindAndValidate binds data from *protocol.Request to obj and validates them if needed.\n// NOTE:\n//\n//\tobj should be a pointer.\nfunc BindAndValidate(req *protocol.Request, obj interface{}, pathParams param.Params) error {\n\tif err := defaultBind.Bind(req, obj, pathParams); err != nil {\n\t\treturn err\n\t}\n\treturn defaultBind.Validate(req, obj)\n}\n\n// Bind binds data from *protocol.Request to obj.\n// NOTE:\n//\n//\tobj should be a pointer.\nfunc Bind(req *protocol.Request, obj interface{}, pathParams param.Params) error {\n\treturn defaultBind.Bind(req, obj, pathParams)\n}\n\n// Validate validates obj with \"vd\" tag\n// NOTE:\n//\n//\tobj should be a pointer.\n//\tValidate should be called after Bind.\nfunc Validate(obj interface{}) error {\n\treturn defaultBind.Validate(nil, obj)\n}\n\nfunc (b *defaultBinder) tagCache(tag string) *sync.Map {\n\tswitch tag {\n\tcase queryTag:\n\t\treturn &b.queryDecoderCache\n\tcase headerTag:\n\t\treturn &b.headerDecoderCache\n\tcase formTag:\n\t\treturn &b.formDecoderCache\n\tcase pathTag:\n\t\treturn &b.pathDecoderCache\n\tdefault:\n\t\treturn &b.decoderCache\n\t}\n}\n\nfunc (b *defaultBinder) bindTag(req *protocol.Request, v interface{}, params param.Params, tag string) error {\n\trv, typeID := valueAndTypeID(v)\n\tif err := checkPointer(rv); err != nil {\n\t\treturn err\n\t}\n\trt := dereferenceType(rv.Type())\n\tif rt.Kind() != reflect.Struct {\n\t\treturn b.bindNonStruct(req, v)\n\t}\n\n\tif len(tag) == 0 {\n\t\terr := b.preBindBody(req, v)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"bind body failed, err=%v\", err)\n\t\t}\n\t}\n\tcache := b.tagCache(tag)\n\tcached, ok := cache.Load(typeID)\n\tif ok {\n\t\t// cached fieldDecoder, fast path\n\t\tdecoder := cached.(decoderInfo)\n\t\treturn decoder.decoder(req, params, rv.Elem())\n\t}\n\tdecodeConfig := &inDecoder.DecodeConfig{\n\t\tLooseZeroMode:                      b.config.LooseZeroMode,\n\t\tDisableDefaultTag:                  b.config.DisableDefaultTag,\n\t\tDisableStructFieldResolve:          b.config.DisableStructFieldResolve,\n\t\tEnableDecoderUseNumber:             b.config.EnableDecoderUseNumber,\n\t\tEnableDecoderDisallowUnknownFields: b.config.EnableDecoderDisallowUnknownFields,\n\t\tTypeUnmarshalFuncs:                 b.config.TypeUnmarshalFuncs,\n\t}\n\tdecoder, err := inDecoder.GetReqDecoder(rv.Type(), tag, decodeConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcache.Store(typeID, decoderInfo{decoder: decoder})\n\treturn decoder(req, params, rv.Elem())\n}\n\nfunc (b *defaultBinder) BindQuery(req *protocol.Request, v interface{}) error {\n\treturn b.bindTag(req, v, nil, queryTag)\n}\n\nfunc (b *defaultBinder) BindHeader(req *protocol.Request, v interface{}) error {\n\treturn b.bindTag(req, v, nil, headerTag)\n}\n\nfunc (b *defaultBinder) BindPath(req *protocol.Request, v interface{}, params param.Params) error {\n\treturn b.bindTag(req, v, params, pathTag)\n}\n\nfunc (b *defaultBinder) BindForm(req *protocol.Request, v interface{}) error {\n\treturn b.bindTag(req, v, nil, formTag)\n}\n\nfunc (b *defaultBinder) BindJSON(req *protocol.Request, v interface{}) error {\n\treturn b.decodeJSON(bytes.NewReader(req.Body()), v)\n}\n\nfunc (b *defaultBinder) decodeJSON(r io.Reader, obj interface{}) error {\n\tdecoder := hJson.NewDecoder(r)\n\tif b.config.EnableDecoderUseNumber {\n\t\tdecoder.UseNumber()\n\t}\n\tif b.config.EnableDecoderDisallowUnknownFields {\n\t\tdecoder.DisallowUnknownFields()\n\t}\n\n\treturn decoder.Decode(obj)\n}\n\nfunc (b *defaultBinder) BindProtobuf(req *protocol.Request, v interface{}) error {\n\tmsg, ok := v.(proto.Message)\n\tif !ok {\n\t\treturn fmt.Errorf(\"%s does not implement 'proto.Message'\", v)\n\t}\n\treturn proto.Unmarshal(req.Body(), msg)\n}\n\nfunc (b *defaultBinder) Name() string {\n\treturn \"hertz\"\n}\n\nfunc (b *defaultBinder) Bind(req *protocol.Request, v interface{}, params param.Params) error {\n\treturn b.bindTag(req, v, params, \"\")\n}\n\nfunc (b *defaultBinder) Validate(req *protocol.Request, v interface{}) error {\n\treturn b.config.ValidatorFunc(req, v)\n}\n\n// best effort binding\nfunc (b *defaultBinder) preBindBody(req *protocol.Request, v interface{}) error {\n\tif req.Header.ContentLength() <= 0 {\n\t\treturn nil\n\t}\n\tct := bytesconv.B2s(req.Header.ContentType())\n\tswitch strings.ToLower(utils.FilterContentType(ct)) {\n\tcase consts.MIMEApplicationJSON:\n\t\treturn hJson.Unmarshal(req.Body(), v)\n\tcase consts.MIMEPROTOBUF:\n\t\tmsg, ok := v.(proto.Message)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"%s can not implement 'proto.Message'\", v)\n\t\t}\n\t\treturn proto.Unmarshal(req.Body(), msg)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (b *defaultBinder) bindNonStruct(req *protocol.Request, v interface{}) (err error) {\n\tct := bytesconv.B2s(req.Header.ContentType())\n\tswitch strings.ToLower(utils.FilterContentType(ct)) {\n\tcase consts.MIMEApplicationJSON:\n\t\terr = hJson.Unmarshal(req.Body(), v)\n\tcase consts.MIMEPROTOBUF:\n\t\tmsg, ok := v.(proto.Message)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"%s can not implement 'proto.Message'\", v)\n\t\t}\n\t\terr = proto.Unmarshal(req.Body(), msg)\n\tcase consts.MIMEMultipartPOSTForm:\n\t\tform := make(url.Values)\n\t\tmf, err1 := req.MultipartForm()\n\t\tif err1 == nil && mf.Value != nil {\n\t\t\tfor k, v := range mf.Value {\n\t\t\t\tfor _, vv := range v {\n\t\t\t\t\tform.Add(k, vv)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tb, _ := stdJson.Marshal(form)\n\t\terr = hJson.Unmarshal(b, v)\n\tcase consts.MIMEApplicationHTMLForm:\n\t\tform := make(url.Values)\n\t\treq.PostArgs().VisitAll(func(formKey, value []byte) {\n\t\t\tform.Add(string(formKey), string(value))\n\t\t})\n\t\tb, _ := stdJson.Marshal(form)\n\t\terr = hJson.Unmarshal(b, v)\n\tdefault:\n\t\t// using query to decode\n\t\tquery := make(url.Values)\n\t\treq.URI().QueryArgs().VisitAll(func(queryKey, value []byte) {\n\t\t\tquery.Add(string(queryKey), string(value))\n\t\t})\n\t\tb, _ := stdJson.Marshal(query)\n\t\terr = hJson.Unmarshal(b, v)\n\t}\n\treturn\n}\n\nvar _ StructValidator = (*validator)(nil)\n\ntype validator struct {\n\tvalidateTag string\n\tvalidate    *exprValidator.Validator\n}\n\n// NewValidator creates a new StructValidator with the given configuration.\n//\n// Deprecated: Use WithCustomValidatorFunc with a custom validation function instead.\n// You can convert the returned StructValidator to a ValidatorFunc using MakeValidatorFunc().\nfunc NewValidator(config *ValidateConfig) StructValidator {\n\tvalidateTag := defaultValidateTag\n\tif config != nil && len(config.ValidateTag) != 0 {\n\t\tvalidateTag = config.ValidateTag\n\t}\n\tvd := exprValidator.New(validateTag).SetErrorFactory(defaultValidateErrorFactory)\n\tif config != nil && config.ErrFactory != nil {\n\t\tvd.SetErrorFactory(config.ErrFactory)\n\t}\n\treturn &validator{\n\t\tvalidateTag: validateTag,\n\t\tvalidate:    vd,\n\t}\n}\n\n// Error validate error\ntype validateError struct {\n\tFailPath, Msg string\n}\n\n// Error implements error interface.\nfunc (e *validateError) Error() string {\n\tif e.Msg != \"\" {\n\t\treturn e.Msg\n\t}\n\treturn \"invalid parameter: \" + e.FailPath\n}\n\nfunc defaultValidateErrorFactory(failPath, msg string) error {\n\treturn &validateError{\n\t\tFailPath: failPath,\n\t\tMsg:      msg,\n\t}\n}\n\n// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.\nfunc (v *validator) ValidateStruct(obj interface{}) error {\n\tif obj == nil {\n\t\treturn nil\n\t}\n\treturn v.validate.Validate(obj)\n}\n\n// Engine returns the underlying validator\nfunc (v *validator) Engine() interface{} {\n\treturn v.validate\n}\n\nfunc (v *validator) ValidateTag() string {\n\treturn v.validateTag\n}\n\nvar defaultValidate = NewValidator(NewValidateConfig())\n\n// DefaultValidator returns the default StructValidator instance that uses tagexpr validation.\n// The validator uses the \"vd\" tag for validation expressions and provides comprehensive\n// struct field validation capabilities.\n//\n// Deprecated: Use WithCustomValidatorFunc with a custom validation function instead.\n// For migration: convert this StructValidator to a ValidatorFunc using MakeValidatorFunc().\n//\n// Example migration:\n//\n//\t// Old way (deprecated)\n//\tvalidator := binding.DefaultValidator()\n//\n//\t// New way (recommended)\n//\tvalidatorFunc := binding.MakeValidatorFunc(binding.DefaultValidator())\n//\tserver.WithCustomValidatorFunc(validatorFunc)\nfunc DefaultValidator() StructValidator {\n\treturn defaultValidate\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/base_type_decoder.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage decoder\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype fieldInfo struct {\n\tindex       int\n\tparentIndex []int\n\tfieldName   string\n\ttagInfos    []TagInfo\n\tfieldType   reflect.Type\n\tconfig      *DecodeConfig\n}\n\ntype baseTypeFieldTextDecoder struct {\n\tfieldInfo\n\tdecoder TextDecoder\n}\n\nfunc (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Params, reqValue reflect.Value) error {\n\tvar err error\n\tvar text string\n\tvar exist bool\n\tvar defaultValue string\n\tfor _, tagInfo := range d.tagInfos {\n\t\tif tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {\n\t\t\tif tagInfo.Key == jsonTag {\n\t\t\t\tdefaultValue = tagInfo.Default\n\t\t\t\tfound := checkRequireJSON(req, tagInfo)\n\t\t\t\tif found {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"'%s' field is a 'required' parameter, but the request body does not have this parameter '%s'\", tagInfo.Value, tagInfo.JSONName)\n\t\t\t\t}\n\t\t\t\tif len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {\n\t\t\t\t\tdefaultValue = \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\ttext, exist = tagInfo.Getter(req, params, tagInfo.Value)\n\t\tdefaultValue = tagInfo.Default\n\t\tif exist {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t\tif tagInfo.Required {\n\t\t\terr = fmt.Errorf(\"'%s' field is a 'required' parameter, but the request does not have this parameter\", tagInfo.Value)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(text) == 0 && len(defaultValue) != 0 {\n\t\ttext = toDefaultValue(d.fieldType, defaultValue)\n\t}\n\tif !exist && len(text) == 0 {\n\t\treturn nil\n\t}\n\n\t// get the non-nil value for the parent field\n\treqValue = GetFieldValue(reqValue, d.parentIndex)\n\tfield := reqValue.Field(d.index)\n\tif field.Kind() == reflect.Ptr {\n\t\tt := field.Type()\n\t\tvar ptrDepth int\n\t\tfor t.Kind() == reflect.Ptr {\n\t\t\tt = t.Elem()\n\t\t\tptrDepth++\n\t\t}\n\t\tvar vv reflect.Value\n\t\tvv, err := stringToValue(t, text, req, params, d.config)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfield.Set(ReferenceValue(vv, ptrDepth))\n\t\treturn nil\n\t}\n\n\t// Non-pointer elems\n\tif field.CanAddr() {\n\t\tif tryTextUnmarshaler(field.Addr(), text) {\n\t\t\treturn nil\n\t\t}\n\t}\n\terr = d.decoder.UnmarshalString(text, field, d.config.LooseZeroMode)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to decode '%s' as %s: %w\", text, d.fieldType.Name(), err)\n\t}\n\n\treturn nil\n}\n\nfunc getBaseTypeTextDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int, config *DecodeConfig) ([]fieldDecoder, error) {\n\tfor idx, tagInfo := range tagInfos {\n\t\tswitch tagInfo.Key {\n\t\tcase pathTag:\n\t\t\ttagInfos[idx].SliceGetter = pathSlice\n\t\t\ttagInfos[idx].Getter = path\n\t\tcase formTag:\n\t\t\ttagInfos[idx].SliceGetter = postFormSlice\n\t\t\ttagInfos[idx].Getter = postForm\n\t\tcase queryTag:\n\t\t\ttagInfos[idx].SliceGetter = querySlice\n\t\t\ttagInfos[idx].Getter = query\n\t\tcase cookieTag:\n\t\t\ttagInfos[idx].SliceGetter = cookieSlice\n\t\t\ttagInfos[idx].Getter = cookie\n\t\tcase headerTag:\n\t\t\ttagInfos[idx].SliceGetter = headerSlice\n\t\t\ttagInfos[idx].Getter = header\n\t\tcase jsonTag:\n\t\t\t// do nothing\n\t\tcase rawBodyTag:\n\t\t\ttagInfos[idx].SliceGetter = rawBodySlice\n\t\t\ttagInfos[idx].Getter = rawBody\n\t\tcase fileNameTag:\n\t\t\t// do nothing\n\t\tdefault:\n\t\t}\n\t}\n\n\tfieldType := field.Type\n\tfor field.Type.Kind() == reflect.Ptr {\n\t\tfieldType = field.Type.Elem()\n\t}\n\n\ttextDecoder, err := SelectTextDecoder(fieldType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn []fieldDecoder{&baseTypeFieldTextDecoder{\n\t\tfieldInfo: fieldInfo{\n\t\t\tindex:       index,\n\t\t\tparentIndex: parentIdx,\n\t\t\tfieldName:   field.Name,\n\t\t\ttagInfos:    tagInfos,\n\t\t\tfieldType:   fieldType,\n\t\t\tconfig:      config,\n\t\t},\n\t\tdecoder: textDecoder,\n\t}}, nil\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/customized_type_decoder.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage decoder\n\nimport (\n\t\"reflect\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype CustomizeDecodeFunc func(req *protocol.Request, params param.Params, text string) (reflect.Value, error)\n\ntype customizedFieldTextDecoder struct {\n\tfieldInfo\n\tdecodeFunc CustomizeDecodeFunc\n}\n\nfunc (d *customizedFieldTextDecoder) Decode(req *protocol.Request, params param.Params, reqValue reflect.Value) error {\n\tvar text string\n\tvar exist bool\n\tvar defaultValue string\n\tfor _, tagInfo := range d.tagInfos {\n\t\tif tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {\n\t\t\tif tagInfo.Key == jsonTag {\n\t\t\t\tdefaultValue = tagInfo.Default\n\t\t\t\tif len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {\n\t\t\t\t\tdefaultValue = \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\ttext, exist = tagInfo.Getter(req, params, tagInfo.Value)\n\t\tdefaultValue = tagInfo.Default\n\t\tif exist {\n\t\t\tbreak\n\t\t}\n\t}\n\tif !exist {\n\t\treturn nil\n\t}\n\tif len(text) == 0 && len(defaultValue) != 0 {\n\t\ttext = toDefaultValue(d.fieldType, defaultValue)\n\t}\n\n\tv, err := d.decodeFunc(req, params, text)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !v.IsValid() {\n\t\treturn nil\n\t}\n\n\treqValue = GetFieldValue(reqValue, d.parentIndex)\n\tfield := reqValue.Field(d.index)\n\tif field.Kind() == reflect.Ptr {\n\t\tt := field.Type()\n\t\tvar ptrDepth int\n\t\tfor t.Kind() == reflect.Ptr {\n\t\t\tt = t.Elem()\n\t\t\tptrDepth++\n\t\t}\n\t\tfield.Set(ReferenceValue(v, ptrDepth))\n\t\treturn nil\n\t}\n\n\tfield.Set(v)\n\treturn nil\n}\n\nfunc getCustomizedFieldDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int, decodeFunc CustomizeDecodeFunc, config *DecodeConfig) ([]fieldDecoder, error) {\n\tfor idx, tagInfo := range tagInfos {\n\t\tswitch tagInfo.Key {\n\t\tcase pathTag:\n\t\t\ttagInfos[idx].SliceGetter = pathSlice\n\t\t\ttagInfos[idx].Getter = path\n\t\tcase formTag:\n\t\t\ttagInfos[idx].SliceGetter = postFormSlice\n\t\t\ttagInfos[idx].Getter = postForm\n\t\tcase queryTag:\n\t\t\ttagInfos[idx].SliceGetter = querySlice\n\t\t\ttagInfos[idx].Getter = query\n\t\tcase cookieTag:\n\t\t\ttagInfos[idx].SliceGetter = cookieSlice\n\t\t\ttagInfos[idx].Getter = cookie\n\t\tcase headerTag:\n\t\t\ttagInfos[idx].SliceGetter = headerSlice\n\t\t\ttagInfos[idx].Getter = header\n\t\tcase jsonTag:\n\t\t\t// do nothing\n\t\tcase rawBodyTag:\n\t\t\ttagInfos[idx].SliceGetter = rawBodySlice\n\t\t\ttagInfos[idx].Getter = rawBody\n\t\tcase fileNameTag:\n\t\t\t// do nothing\n\t\tdefault:\n\t\t}\n\t}\n\tfieldType := field.Type\n\tfor field.Type.Kind() == reflect.Ptr {\n\t\tfieldType = field.Type.Elem()\n\t}\n\treturn []fieldDecoder{&customizedFieldTextDecoder{\n\t\tfieldInfo: fieldInfo{\n\t\t\tindex:       index,\n\t\t\tparentIndex: parentIdx,\n\t\t\tfieldName:   field.Name,\n\t\t\ttagInfos:    tagInfos,\n\t\t\tfieldType:   fieldType,\n\t\t\tconfig:      config,\n\t\t},\n\t\tdecodeFunc: decodeFunc,\n\t}}, nil\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/decoder.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage decoder\n\nimport (\n\t\"fmt\"\n\t\"mime/multipart\"\n\t\"reflect\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype fieldDecoder interface {\n\tDecode(req *protocol.Request, params param.Params, reqValue reflect.Value) error\n}\n\ntype Decoder func(req *protocol.Request, params param.Params, rv reflect.Value) error\n\ntype DecodeConfig struct {\n\tLooseZeroMode                      bool\n\tDisableDefaultTag                  bool\n\tDisableStructFieldResolve          bool\n\tEnableDecoderUseNumber             bool\n\tEnableDecoderDisallowUnknownFields bool\n\tTypeUnmarshalFuncs                 map[reflect.Type]CustomizeDecodeFunc\n}\n\nfunc GetReqDecoder(rt reflect.Type, byTag string, config *DecodeConfig) (Decoder, error) {\n\tvar decoders []fieldDecoder\n\n\tel := rt.Elem()\n\tif el.Kind() != reflect.Struct {\n\t\treturn nil, fmt.Errorf(\"unsupported \\\"%s\\\" type binding\", rt.String())\n\t}\n\n\tfor i := 0; i < el.NumField(); i++ {\n\t\tif el.Field(i).PkgPath != \"\" && !el.Field(i).Anonymous {\n\t\t\t// ignore unexported field\n\t\t\tcontinue\n\t\t}\n\n\t\tdec, err := getFieldDecoder(parentInfos{[]reflect.Type{el}, []int{}, \"\"}, el.Field(i), i, byTag, config)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif dec != nil {\n\t\t\tdecoders = append(decoders, dec...)\n\t\t}\n\t}\n\n\treturn func(req *protocol.Request, params param.Params, rv reflect.Value) error {\n\t\tfor _, decoder := range decoders {\n\t\t\terr := decoder.Decode(req, params, rv)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}, nil\n}\n\ntype parentInfos struct {\n\tTypes    []reflect.Type\n\tIndexes  []int\n\tJSONName string\n}\n\nfunc getFieldDecoder(pInfo parentInfos, field reflect.StructField, index int, byTag string, config *DecodeConfig) ([]fieldDecoder, error) {\n\tfor field.Type.Kind() == reflect.Ptr {\n\t\tfield.Type = field.Type.Elem()\n\t}\n\t// skip anonymous definitions, like:\n\t// type A struct {\n\t// \t\tstring\n\t// }\n\tif field.Type.Kind() != reflect.Struct && field.Anonymous {\n\t\treturn nil, nil\n\t}\n\n\t// JSONName is like 'a.b.c' for 'required validate'\n\tfieldTagInfos, newParentJSONName := lookupFieldTags(field, pInfo.JSONName, config)\n\tif len(fieldTagInfos) == 0 && !config.DisableDefaultTag {\n\t\tfieldTagInfos, newParentJSONName = getDefaultFieldTags(field, pInfo.JSONName)\n\t}\n\tif len(byTag) != 0 {\n\t\tfieldTagInfos = getFieldTagInfoByTag(field, byTag)\n\t}\n\n\t// customized type decoder has the highest priority\n\tif customizedFunc, exist := config.TypeUnmarshalFuncs[field.Type]; exist {\n\t\tdec, err := getCustomizedFieldDecoder(field, index, fieldTagInfos, pInfo.Indexes, customizedFunc, config)\n\t\treturn dec, err\n\t}\n\n\t// slice/array field decoder\n\tif field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array {\n\t\tdec, err := getSliceFieldDecoder(field, index, fieldTagInfos, pInfo.Indexes, config)\n\t\treturn dec, err\n\t}\n\n\t// map filed decoder\n\tif field.Type.Kind() == reflect.Map {\n\t\tdec, err := getMapTypeTextDecoder(field, index, fieldTagInfos, pInfo.Indexes, config)\n\t\treturn dec, err\n\t}\n\n\t// struct field will be resolved recursively\n\tif field.Type.Kind() == reflect.Struct {\n\t\tvar decoders []fieldDecoder\n\t\tel := field.Type\n\t\t// todo: more built-in common struct binding, ex. time...\n\t\tswitch el {\n\t\tcase reflect.TypeOf(multipart.FileHeader{}): // file binding\n\t\t\tdec, err := getMultipartFileDecoder(field, index, fieldTagInfos, pInfo.Indexes, config)\n\t\t\treturn dec, err\n\t\t}\n\t\tif !config.DisableStructFieldResolve { // decode struct type separately\n\t\t\tstructFieldDecoder, err := getStructTypeFieldDecoder(field, index, fieldTagInfos, pInfo.Indexes, config)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif structFieldDecoder != nil {\n\t\t\t\tdecoders = append(decoders, structFieldDecoder...)\n\t\t\t}\n\t\t}\n\n\t\t// prevent infinite recursion when struct field with the same name as a struct\n\t\tif hasSameType(pInfo.Types, el) {\n\t\t\treturn decoders, nil\n\t\t}\n\n\t\tpIdx := pInfo.Indexes\n\t\tif !field.Anonymous {\n\t\t\tpInfo.JSONName = newParentJSONName\n\t\t}\n\t\tfor i := 0; i < el.NumField(); i++ {\n\t\t\tif el.Field(i).PkgPath != \"\" && !el.Field(i).Anonymous {\n\t\t\t\t// ignore unexported field\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar idxes []int\n\t\t\tif len(pInfo.Indexes) > 0 {\n\t\t\t\tidxes = append(idxes, pIdx...)\n\t\t\t}\n\t\t\tidxes = append(idxes, index)\n\t\t\tpInfo.Indexes = idxes\n\t\t\tpInfo.Types = append(pInfo.Types, el)\n\t\t\tdec, err := getFieldDecoder(pInfo, el.Field(i), i, byTag, config)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif dec != nil {\n\t\t\t\tdecoders = append(decoders, dec...)\n\t\t\t}\n\t\t}\n\n\t\treturn decoders, nil\n\t}\n\n\t// base type decoder\n\tdec, err := getBaseTypeTextDecoder(field, index, fieldTagInfos, pInfo.Indexes, config)\n\treturn dec, err\n}\n\n// hasSameType determine if the same type is present in the parent-child relationship\nfunc hasSameType(pts []reflect.Type, ft reflect.Type) bool {\n\tfor _, pt := range pts {\n\t\tif reflect.DeepEqual(getElemType(pt), getElemType(ft)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/getter.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage decoder\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype getter func(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret string, exist bool)\n\nfunc path(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret string, exist bool) {\n\tif params != nil {\n\t\tret, exist = params.Get(key)\n\t}\n\n\tif len(ret) == 0 && len(defaultValue) != 0 {\n\t\tret = defaultValue[0]\n\t}\n\treturn ret, exist\n}\n\nfunc postForm(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret string, exist bool) {\n\tif ret, exist = req.PostArgs().PeekExists(key); exist {\n\t\treturn\n\t}\n\n\tmf, err := req.MultipartForm()\n\tif err == nil && mf.Value != nil {\n\t\tfor k, v := range mf.Value {\n\t\t\tif k == key && len(v) > 0 {\n\t\t\t\tret = v[0]\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(ret) != 0 {\n\t\treturn ret, true\n\t}\n\tif ret, exist = req.URI().QueryArgs().PeekExists(key); exist {\n\t\treturn\n\t}\n\n\tif len(ret) == 0 && len(defaultValue) != 0 {\n\t\tret = defaultValue[0]\n\t}\n\n\treturn ret, false\n}\n\nfunc query(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret string, exist bool) {\n\tif ret, exist = req.URI().QueryArgs().PeekExists(key); exist {\n\t\treturn\n\t}\n\n\tif len(ret) == 0 && len(defaultValue) != 0 {\n\t\tret = defaultValue[0]\n\t}\n\n\treturn\n}\n\nfunc cookie(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret string, exist bool) {\n\tif val := req.Header.Cookie(key); val != nil {\n\t\tret = string(val)\n\t\treturn ret, true\n\t}\n\n\tif len(ret) == 0 && len(defaultValue) != 0 {\n\t\tret = defaultValue[0]\n\t}\n\n\treturn ret, false\n}\n\nfunc header(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret string, exist bool) {\n\tif val := req.Header.Peek(key); val != nil {\n\t\tret = string(val)\n\t\treturn ret, true\n\t}\n\n\tif len(ret) == 0 && len(defaultValue) != 0 {\n\t\tret = defaultValue[0]\n\t}\n\n\treturn ret, false\n}\n\nfunc rawBody(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret string, exist bool) {\n\texist = false\n\tif req.Header.ContentLength() > 0 {\n\t\tret = string(req.Body())\n\t\texist = true\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/gjson_required.go",
    "content": "// Copyright 2023 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build gjson || !(amd64 && (linux || windows || darwin))\n\npackage decoder\n\nimport (\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/tidwall/gjson\"\n)\n\nfunc checkRequireJSON(req *protocol.Request, tagInfo TagInfo) bool {\n\tif !tagInfo.Required {\n\t\treturn true\n\t}\n\tct := bytesconv.B2s(req.Header.ContentType())\n\tif !strings.EqualFold(utils.FilterContentType(ct), consts.MIMEApplicationJSON) {\n\t\treturn false\n\t}\n\tresult := gjson.GetBytes(req.Body(), tagInfo.JSONName)\n\tif !result.Exists() {\n\t\tidx := strings.LastIndex(tagInfo.JSONName, \".\")\n\t\t// There should be a superior if it is empty, it will report 'true' for required\n\t\tif idx > 0 && !gjson.GetBytes(req.Body(), tagInfo.JSONName[:idx]).Exists() {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc keyExist(req *protocol.Request, tagInfo TagInfo) bool {\n\tct := bytesconv.B2s(req.Header.ContentType())\n\tif utils.FilterContentType(ct) != consts.MIMEApplicationJSON {\n\t\treturn false\n\t}\n\tresult := gjson.GetBytes(req.Body(), tagInfo.JSONName)\n\treturn result.Exists()\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/map_type_decoder.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage decoder\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\thJson \"github.com/cloudwego/hertz/pkg/common/json\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype mapTypeFieldTextDecoder struct {\n\tfieldInfo\n}\n\nfunc (d *mapTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Params, reqValue reflect.Value) error {\n\tvar err error\n\tvar text string\n\tvar exist bool\n\tvar defaultValue string\n\tfor _, tagInfo := range d.tagInfos {\n\t\tif tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {\n\t\t\tif tagInfo.Key == jsonTag {\n\t\t\t\tdefaultValue = tagInfo.Default\n\t\t\t\tfound := checkRequireJSON(req, tagInfo)\n\t\t\t\tif found {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"'%s' field is a 'required' parameter, but the request does not have this parameter\", tagInfo.Value)\n\t\t\t\t}\n\t\t\t\tif len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {\n\t\t\t\t\tdefaultValue = \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\ttext, exist = tagInfo.Getter(req, params, tagInfo.Value)\n\t\tdefaultValue = tagInfo.Default\n\t\tif exist {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t\tif tagInfo.Required {\n\t\t\terr = fmt.Errorf(\"'%s' field is a 'required' parameter, but the request does not have this parameter\", tagInfo.Value)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(text) == 0 && len(defaultValue) != 0 {\n\t\ttext = toDefaultValue(d.fieldType, defaultValue)\n\t}\n\tif !exist && len(text) == 0 {\n\t\treturn nil\n\t}\n\n\treqValue = GetFieldValue(reqValue, d.parentIndex)\n\tfield := reqValue.Field(d.index)\n\tif field.Kind() == reflect.Ptr {\n\t\tt := field.Type()\n\t\tvar ptrDepth int\n\t\tfor t.Kind() == reflect.Ptr {\n\t\t\tt = t.Elem()\n\t\t\tptrDepth++\n\t\t}\n\t\tvar vv reflect.Value\n\t\tvv, err := stringToValue(t, text, req, params, d.config)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to decode '%s' as %s: %w\", text, d.fieldType.Name(), err)\n\t\t}\n\t\tfield.Set(ReferenceValue(vv, ptrDepth))\n\t\treturn nil\n\t}\n\n\terr = hJson.Unmarshal(bytesconv.S2b(text), field.Addr().Interface())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to decode '%s' as %s: %w\", text, d.fieldType.Name(), err)\n\t}\n\n\treturn nil\n}\n\nfunc getMapTypeTextDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int, config *DecodeConfig) ([]fieldDecoder, error) {\n\tfor idx, tagInfo := range tagInfos {\n\t\tswitch tagInfo.Key {\n\t\tcase pathTag:\n\t\t\ttagInfos[idx].SliceGetter = pathSlice\n\t\t\ttagInfos[idx].Getter = path\n\t\tcase formTag:\n\t\t\ttagInfos[idx].SliceGetter = postFormSlice\n\t\t\ttagInfos[idx].Getter = postForm\n\t\tcase queryTag:\n\t\t\ttagInfos[idx].SliceGetter = querySlice\n\t\t\ttagInfos[idx].Getter = query\n\t\tcase cookieTag:\n\t\t\ttagInfos[idx].SliceGetter = cookieSlice\n\t\t\ttagInfos[idx].Getter = cookie\n\t\tcase headerTag:\n\t\t\ttagInfos[idx].SliceGetter = headerSlice\n\t\t\ttagInfos[idx].Getter = header\n\t\tcase jsonTag:\n\t\t\t// do nothing\n\t\tcase rawBodyTag:\n\t\t\ttagInfos[idx].SliceGetter = rawBodySlice\n\t\t\ttagInfos[idx].Getter = rawBody\n\t\tcase fileNameTag:\n\t\t\t// do nothing\n\t\tdefault:\n\t\t}\n\t}\n\n\tfieldType := field.Type\n\tfor field.Type.Kind() == reflect.Ptr {\n\t\tfieldType = field.Type.Elem()\n\t}\n\n\treturn []fieldDecoder{&mapTypeFieldTextDecoder{\n\t\tfieldInfo: fieldInfo{\n\t\t\tindex:       index,\n\t\t\tparentIndex: parentIdx,\n\t\t\tfieldName:   field.Name,\n\t\t\ttagInfos:    tagInfos,\n\t\t\tfieldType:   fieldType,\n\t\t\tconfig:      config,\n\t\t},\n\t}}, nil\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/multipart_file_decoder.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage decoder\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype fileTypeDecoder struct {\n\tfieldInfo\n\tisRepeated bool\n}\n\nfunc (d *fileTypeDecoder) Decode(req *protocol.Request, params param.Params, reqValue reflect.Value) error {\n\tfieldValue := GetFieldValue(reqValue, d.parentIndex)\n\tfield := fieldValue.Field(d.index)\n\n\tif d.isRepeated {\n\t\treturn d.fileSliceDecode(req, params, reqValue)\n\t}\n\tvar fileName string\n\t// file_name > form > fieldName\n\tfor _, tagInfo := range d.tagInfos {\n\t\tif tagInfo.Key == fileNameTag {\n\t\t\tfileName = tagInfo.Value\n\t\t\tbreak\n\t\t}\n\t\tif tagInfo.Key == formTag {\n\t\t\tfileName = tagInfo.Value\n\t\t}\n\t}\n\tif len(fileName) == 0 {\n\t\tfileName = d.fieldName\n\t}\n\tfile, err := req.FormFile(fileName)\n\tif err != nil {\n\t\thlog.SystemLogger().Warnf(\"can not get file '%s' form request, reason: %v, so skip '%s' field binding\", fileName, err, d.fieldName)\n\t\treturn nil\n\t}\n\tif field.Kind() == reflect.Ptr {\n\t\tt := field.Type()\n\t\tvar ptrDepth int\n\t\tfor t.Kind() == reflect.Ptr {\n\t\t\tt = t.Elem()\n\t\t\tptrDepth++\n\t\t}\n\t\tv := reflect.New(t).Elem()\n\t\tv.Set(reflect.ValueOf(*file))\n\t\tfield.Set(ReferenceValue(v, ptrDepth))\n\t\treturn nil\n\t}\n\n\t// Non-pointer elems\n\tfield.Set(reflect.ValueOf(*file))\n\n\treturn nil\n}\n\nfunc (d *fileTypeDecoder) fileSliceDecode(req *protocol.Request, params param.Params, reqValue reflect.Value) error {\n\tfieldValue := GetFieldValue(reqValue, d.parentIndex)\n\tfield := fieldValue.Field(d.index)\n\t// 如果没值，需要为其建一个值\n\tif field.Kind() == reflect.Ptr {\n\t\tif field.IsNil() {\n\t\t\tnonNilVal, ptrDepth := GetNonNilReferenceValue(field)\n\t\t\tfield.Set(ReferenceValue(nonNilVal, ptrDepth))\n\t\t}\n\t}\n\tvar parentPtrDepth int\n\tfor field.Kind() == reflect.Ptr {\n\t\tfield = field.Elem()\n\t\tparentPtrDepth++\n\t}\n\n\tvar fileName string\n\t// file_name > form > fieldName\n\tfor _, tagInfo := range d.tagInfos {\n\t\tif tagInfo.Key == fileNameTag {\n\t\t\tfileName = tagInfo.Value\n\t\t\tbreak\n\t\t}\n\t\tif tagInfo.Key == formTag {\n\t\t\tfileName = tagInfo.Value\n\t\t}\n\t}\n\tif len(fileName) == 0 {\n\t\tfileName = d.fieldName\n\t}\n\tmultipartForm, err := req.MultipartForm()\n\tif err != nil {\n\t\thlog.SystemLogger().Warnf(\"can not get MultipartForm from request, reason: %v, so skip '%s' field binding\", fileName, err, d.fieldName)\n\t\treturn nil\n\t}\n\tfiles, exist := multipartForm.File[fileName]\n\tif !exist {\n\t\thlog.SystemLogger().Warnf(\"the file '%s' is not existed in request, so skip '%s' field binding\", fileName, d.fieldName)\n\t\treturn nil\n\t}\n\n\tif field.Kind() == reflect.Array {\n\t\tif len(files) != field.Len() {\n\t\t\treturn fmt.Errorf(\"the numbers(%d) of file '%s' does not match the length(%d) of %s\", len(files), fileName, field.Len(), field.Type().String())\n\t\t}\n\t} else {\n\t\t// slice need creating enough capacity\n\t\tfield = reflect.MakeSlice(field.Type(), len(files), len(files))\n\t}\n\n\t// handle multiple pointer\n\tvar ptrDepth int\n\tt := d.fieldType.Elem()\n\telemKind := t.Kind()\n\tfor elemKind == reflect.Ptr {\n\t\tt = t.Elem()\n\t\telemKind = t.Kind()\n\t\tptrDepth++\n\t}\n\n\tfor idx, file := range files {\n\t\tv := reflect.New(t).Elem()\n\t\tv.Set(reflect.ValueOf(*file))\n\t\tfield.Index(idx).Set(ReferenceValue(v, ptrDepth))\n\t}\n\tfieldValue.Field(d.index).Set(ReferenceValue(field, parentPtrDepth))\n\n\treturn nil\n}\n\nfunc getMultipartFileDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int, config *DecodeConfig) ([]fieldDecoder, error) {\n\tfieldType := field.Type\n\tfor field.Type.Kind() == reflect.Ptr {\n\t\tfieldType = field.Type.Elem()\n\t}\n\tisRepeated := false\n\tif fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice {\n\t\tisRepeated = true\n\t}\n\n\treturn []fieldDecoder{&fileTypeDecoder{\n\t\tfieldInfo: fieldInfo{\n\t\t\tindex:       index,\n\t\t\tparentIndex: parentIdx,\n\t\t\tfieldName:   field.Name,\n\t\t\ttagInfos:    tagInfos,\n\t\t\tfieldType:   fieldType,\n\t\t\tconfig:      config,\n\t\t},\n\t\tisRepeated: isRepeated,\n\t}}, nil\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/reflect.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage decoder\n\nimport (\n\t\"reflect\"\n)\n\n// ReferenceValue convert T to *T, the ptrDepth is the count of '*'.\nfunc ReferenceValue(v reflect.Value, ptrDepth int) reflect.Value {\n\tswitch {\n\tcase ptrDepth > 0:\n\t\tfor ; ptrDepth > 0; ptrDepth-- {\n\t\t\tvv := reflect.New(v.Type())\n\t\t\tvv.Elem().Set(v)\n\t\t\tv = vv\n\t\t}\n\tcase ptrDepth < 0:\n\t\tfor ; ptrDepth < 0 && v.Kind() == reflect.Ptr; ptrDepth++ {\n\t\t\tv = v.Elem()\n\t\t}\n\t}\n\treturn v\n}\n\nfunc GetNonNilReferenceValue(v reflect.Value) (reflect.Value, int) {\n\tvar ptrDepth int\n\tt := v.Type()\n\telemKind := t.Kind()\n\tfor elemKind == reflect.Ptr {\n\t\tt = t.Elem()\n\t\telemKind = t.Kind()\n\t\tptrDepth++\n\t}\n\tval := reflect.New(t).Elem()\n\treturn val, ptrDepth\n}\n\nfunc GetFieldValue(reqValue reflect.Value, parentIndex []int) reflect.Value {\n\t// reqValue -> (***bar)(nil) need new a default\n\tif reqValue.Kind() == reflect.Ptr && reqValue.IsNil() {\n\t\tnonNilVal, ptrDepth := GetNonNilReferenceValue(reqValue)\n\t\treqValue = ReferenceValue(nonNilVal, ptrDepth)\n\t}\n\tfor _, idx := range parentIndex {\n\t\tif reqValue.Kind() == reflect.Ptr && reqValue.IsNil() {\n\t\t\tnonNilVal, ptrDepth := GetNonNilReferenceValue(reqValue)\n\t\t\treqValue.Set(ReferenceValue(nonNilVal, ptrDepth))\n\t\t}\n\t\tfor reqValue.Kind() == reflect.Ptr {\n\t\t\treqValue = reqValue.Elem()\n\t\t}\n\t\treqValue = reqValue.Field(idx)\n\t}\n\n\t// It is possible that the parent struct is also a pointer,\n\t// so need to create a non-nil reflect.Value for it at runtime.\n\tfor reqValue.Kind() == reflect.Ptr {\n\t\tif reqValue.IsNil() {\n\t\t\tnonNilVal, ptrDepth := GetNonNilReferenceValue(reqValue)\n\t\t\treqValue.Set(ReferenceValue(nonNilVal, ptrDepth))\n\t\t}\n\t\treqValue = reqValue.Elem()\n\t}\n\n\treturn reqValue\n}\n\nfunc getElemType(t reflect.Type) reflect.Type {\n\tfor t.Kind() == reflect.Ptr {\n\t\tt = t.Elem()\n\t}\n\n\treturn t\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/slice_getter.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage decoder\n\nimport (\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype sliceGetter func(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret []string)\n\nfunc pathSlice(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret []string) {\n\tvar value string\n\tif params != nil {\n\t\tvalue, _ = params.Get(key)\n\t}\n\n\tif len(value) == 0 && len(defaultValue) != 0 {\n\t\tvalue = defaultValue[0]\n\t}\n\tif len(value) != 0 {\n\t\tret = append(ret, value)\n\t}\n\n\treturn\n}\n\nfunc postFormSlice(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret []string) {\n\treq.PostArgs().VisitAll(func(formKey, value []byte) {\n\t\tif bytesconv.B2s(formKey) == key {\n\t\t\tret = append(ret, string(value))\n\t\t}\n\t})\n\tif len(ret) > 0 {\n\t\treturn\n\t}\n\n\tmf, err := req.MultipartForm()\n\tif err == nil && mf.Value != nil {\n\t\tfor k, v := range mf.Value {\n\t\t\tif k == key && len(v) > 0 {\n\t\t\t\tret = append(ret, v...)\n\t\t\t}\n\t\t}\n\t}\n\tif len(ret) > 0 {\n\t\treturn\n\t}\n\n\tif len(ret) == 0 && len(defaultValue) != 0 {\n\t\tret = append(ret, defaultValue...)\n\t}\n\n\treturn\n}\n\nfunc querySlice(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret []string) {\n\treq.URI().QueryArgs().VisitAll(func(queryKey, value []byte) {\n\t\tif key == bytesconv.B2s(queryKey) {\n\t\t\tret = append(ret, string(value))\n\t\t}\n\t})\n\n\tif len(ret) == 0 && len(defaultValue) != 0 {\n\t\tret = append(ret, defaultValue...)\n\t}\n\n\treturn\n}\n\nfunc cookieSlice(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret []string) {\n\treq.Header.VisitAllCookie(func(cookieKey, value []byte) {\n\t\tif bytesconv.B2s(cookieKey) == key {\n\t\t\tret = append(ret, string(value))\n\t\t}\n\t})\n\n\tif len(ret) == 0 && len(defaultValue) != 0 {\n\t\tret = append(ret, defaultValue...)\n\t}\n\n\treturn\n}\n\nfunc headerSlice(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret []string) {\n\tret = defaultValue\n\tif vv := req.Header.GetAll(key); len(vv) > 0 {\n\t\tret = vv\n\t}\n\treturn\n}\n\nfunc rawBodySlice(req *protocol.Request, params param.Params, key string, defaultValue ...string) (ret []string) {\n\tif req.Header.ContentLength() > 0 {\n\t\tret = append(ret, string(req.Body()))\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/slice_type_decoder.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage decoder\n\nimport (\n\t\"fmt\"\n\t\"mime/multipart\"\n\t\"reflect\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\thJson \"github.com/cloudwego/hertz/pkg/common/json\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype sliceTypeFieldTextDecoder struct {\n\tfieldInfo\n\tisArray bool\n}\n\nfunc (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Params, reqValue reflect.Value) error {\n\tvar err error\n\tvar texts []string\n\tvar defaultValue string\n\tvar bindRawBody bool\n\tvar isDefault bool\n\tfor _, tagInfo := range d.tagInfos {\n\t\tif tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {\n\t\t\tif tagInfo.Key == jsonTag {\n\t\t\t\tdefaultValue = tagInfo.Default\n\t\t\t\tfound := checkRequireJSON(req, tagInfo)\n\t\t\t\tif found {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"'%s' field is a 'required' parameter, but the request does not have this parameter\", tagInfo.Value)\n\t\t\t\t}\n\t\t\t\tif len(tagInfo.Default) != 0 && keyExist(req, tagInfo) { //\n\t\t\t\t\tdefaultValue = \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif tagInfo.Key == rawBodyTag {\n\t\t\tbindRawBody = true\n\t\t}\n\t\ttexts = tagInfo.SliceGetter(req, params, tagInfo.Value)\n\t\tdefaultValue = tagInfo.Default\n\t\tif len(texts) != 0 {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t\tif tagInfo.Required {\n\t\t\terr = fmt.Errorf(\"'%s' field is a 'required' parameter, but the request does not have this parameter\", tagInfo.Value)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(texts) == 0 && len(defaultValue) != 0 {\n\t\tdefaultValue = toDefaultValue(d.fieldType, defaultValue)\n\t\ttexts = append(texts, defaultValue)\n\t\tisDefault = true\n\t}\n\tif len(texts) == 0 {\n\t\treturn nil\n\t}\n\n\treqValue = GetFieldValue(reqValue, d.parentIndex)\n\tfield := reqValue.Field(d.index)\n\t// **[]**int\n\tif field.Kind() == reflect.Ptr {\n\t\tif field.IsNil() {\n\t\t\tnonNilVal, ptrDepth := GetNonNilReferenceValue(field)\n\t\t\tfield.Set(ReferenceValue(nonNilVal, ptrDepth))\n\t\t}\n\t}\n\tvar parentPtrDepth int\n\tfor field.Kind() == reflect.Ptr {\n\t\tfield = field.Elem()\n\t\tparentPtrDepth++\n\t}\n\n\tif d.isArray {\n\t\tif len(texts) != field.Len() && !isDefault {\n\t\t\treturn fmt.Errorf(\"%q is not valid value for %s\", texts, field.Type().String())\n\t\t}\n\t} else {\n\t\t// slice need creating enough capacity\n\t\tfield = reflect.MakeSlice(field.Type(), len(texts), len(texts))\n\t}\n\t// raw_body && []byte binding\n\tif bindRawBody && field.Type().Elem().Kind() == reflect.Uint8 {\n\t\treqValue.Field(d.index).Set(reflect.ValueOf(req.Body()))\n\t\treturn nil\n\t}\n\n\t// handle internal multiple pointer, []**int\n\tvar ptrDepth int\n\tt := d.fieldType.Elem() // d.fieldType is non-pointer type for the field\n\telemKind := t.Kind()\n\tfor elemKind == reflect.Ptr {\n\t\tt = t.Elem()\n\t\telemKind = t.Kind()\n\t\tptrDepth++\n\t}\n\tif isDefault {\n\t\terr = hJson.Unmarshal(bytesconv.S2b(texts[0]), reqValue.Field(d.index).Addr().Interface())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"using '%s' to unmarshal field '%s: %s' failed, %v\", texts[0], d.fieldName, d.fieldType.String(), err)\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor idx, text := range texts {\n\t\tvar vv reflect.Value\n\t\tvv, err = stringToValue(t, text, req, params, d.config)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tfield.Index(idx).Set(ReferenceValue(vv, ptrDepth))\n\t}\n\tif err != nil {\n\t\tif !reqValue.Field(d.index).CanAddr() {\n\t\t\treturn err\n\t\t}\n\t\t// text[0] can be a complete json content for []Type.\n\t\terr = hJson.Unmarshal(bytesconv.S2b(texts[0]), reqValue.Field(d.index).Addr().Interface())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"using '%s' to unmarshal field '%s: %s' failed, %v\", texts[0], d.fieldName, d.fieldType.String(), err)\n\t\t}\n\t} else {\n\t\treqValue.Field(d.index).Set(ReferenceValue(field, parentPtrDepth))\n\t}\n\n\treturn nil\n}\n\nfunc getSliceFieldDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int, config *DecodeConfig) ([]fieldDecoder, error) {\n\tif !(field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array) {\n\t\treturn nil, fmt.Errorf(\"unexpected type %s, expected slice or array\", field.Type.String())\n\t}\n\tisArray := false\n\tif field.Type.Kind() == reflect.Array {\n\t\tisArray = true\n\t}\n\tfor idx, tagInfo := range tagInfos {\n\t\tswitch tagInfo.Key {\n\t\tcase pathTag:\n\t\t\ttagInfos[idx].SliceGetter = pathSlice\n\t\t\ttagInfos[idx].Getter = path\n\t\tcase formTag:\n\t\t\ttagInfos[idx].SliceGetter = postFormSlice\n\t\t\ttagInfos[idx].Getter = postForm\n\t\tcase queryTag:\n\t\t\ttagInfos[idx].SliceGetter = querySlice\n\t\t\ttagInfos[idx].Getter = query\n\t\tcase cookieTag:\n\t\t\ttagInfos[idx].SliceGetter = cookieSlice\n\t\t\ttagInfos[idx].Getter = cookie\n\t\tcase headerTag:\n\t\t\ttagInfos[idx].SliceGetter = headerSlice\n\t\t\ttagInfos[idx].Getter = header\n\t\tcase jsonTag:\n\t\t\t// do nothing\n\t\tcase rawBodyTag:\n\t\t\ttagInfos[idx].SliceGetter = rawBodySlice\n\t\t\ttagInfos[idx].Getter = rawBody\n\t\tcase fileNameTag:\n\t\t\t// do nothing\n\t\tdefault:\n\t\t}\n\t}\n\n\tfieldType := field.Type\n\tfor field.Type.Kind() == reflect.Ptr {\n\t\tfieldType = field.Type.Elem()\n\t}\n\t// fieldType.Elem() is the type for array/slice elem\n\tt := getElemType(fieldType.Elem())\n\tif t == reflect.TypeOf(multipart.FileHeader{}) {\n\t\treturn getMultipartFileDecoder(field, index, tagInfos, parentIdx, config)\n\t}\n\n\treturn []fieldDecoder{&sliceTypeFieldTextDecoder{\n\t\tfieldInfo: fieldInfo{\n\t\t\tindex:       index,\n\t\t\tparentIndex: parentIdx,\n\t\t\tfieldName:   field.Name,\n\t\t\ttagInfos:    tagInfos,\n\t\t\tfieldType:   fieldType,\n\t\t\tconfig:      config,\n\t\t},\n\t\tisArray: isArray,\n\t}}, nil\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/sonic_required.go",
    "content": "// Copyright 2023 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build (linux || windows || darwin) && amd64 && !gjson\n\npackage decoder\n\nimport (\n\t\"strings\"\n\n\t\"github.com/bytedance/sonic\"\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc checkRequireJSON(req *protocol.Request, tagInfo TagInfo) bool {\n\tif !tagInfo.Required {\n\t\treturn true\n\t}\n\tct := bytesconv.B2s(req.Header.ContentType())\n\tif !strings.EqualFold(utils.FilterContentType(ct), consts.MIMEApplicationJSON) {\n\t\treturn false\n\t}\n\tnode, _ := sonic.Get(req.Body(), stringSliceForInterface(tagInfo.JSONName)...)\n\tif !node.Exists() {\n\t\tidx := strings.LastIndex(tagInfo.JSONName, \".\")\n\t\tif idx > 0 {\n\t\t\t// There should be a superior if it is empty, it will report 'true' for required\n\t\t\tnode, _ := sonic.Get(req.Body(), stringSliceForInterface(tagInfo.JSONName[:idx])...)\n\t\t\tif !node.Exists() {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc stringSliceForInterface(s string) (ret []interface{}) {\n\tx := strings.Split(s, \".\")\n\tfor _, val := range x {\n\t\tret = append(ret, val)\n\t}\n\treturn\n}\n\nfunc keyExist(req *protocol.Request, tagInfo TagInfo) bool {\n\tct := bytesconv.B2s(req.Header.ContentType())\n\tif utils.FilterContentType(ct) != consts.MIMEApplicationJSON {\n\t\treturn false\n\t}\n\tnode, _ := sonic.Get(req.Body(), stringSliceForInterface(tagInfo.JSONName)...)\n\treturn node.Exists()\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/struct_type_decoder.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage decoder\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\thjson \"github.com/cloudwego/hertz/pkg/common/json\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype structTypeFieldTextDecoder struct {\n\tfieldInfo\n}\n\nfunc (d *structTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Params, reqValue reflect.Value) error {\n\tvar err error\n\tvar text string\n\tvar exist bool\n\tvar defaultValue string\n\tfor _, tagInfo := range d.tagInfos {\n\t\tif tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {\n\t\t\tif tagInfo.Key == jsonTag {\n\t\t\t\tdefaultValue = tagInfo.Default\n\t\t\t\tfound := checkRequireJSON(req, tagInfo)\n\t\t\t\tif found {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"'%s' field is a 'required' parameter, but the request does not have this parameter\", tagInfo.Value)\n\t\t\t\t}\n\t\t\t\tif len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {\n\t\t\t\t\tdefaultValue = \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\ttext, exist = tagInfo.Getter(req, params, tagInfo.Value)\n\t\tdefaultValue = tagInfo.Default\n\t\tif exist {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t\tif tagInfo.Required {\n\t\t\terr = fmt.Errorf(\"'%s' field is a 'required' parameter, but the request does not have this parameter\", tagInfo.Value)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(text) == 0 && len(defaultValue) != 0 {\n\t\ttext = toDefaultValue(d.fieldType, defaultValue)\n\t}\n\tif !exist && len(text) == 0 {\n\t\treturn nil\n\t}\n\treqValue = GetFieldValue(reqValue, d.parentIndex)\n\tfield := reqValue.Field(d.index)\n\tif field.Kind() == reflect.Ptr {\n\t\tt := field.Type()\n\t\tvar ptrDepth int\n\t\tfor t.Kind() == reflect.Ptr {\n\t\t\tt = t.Elem()\n\t\t\tptrDepth++\n\t\t}\n\t\tvar vv reflect.Value\n\t\tvv, err := stringToValue(t, text, req, params, d.config)\n\t\tif err != nil {\n\t\t\thlog.SystemLogger().Infof(\"unable to decode '%s' as %s: %v, but it may not affect correctness, so skip it\", text, d.fieldType.Name(), err)\n\t\t\treturn nil\n\t\t}\n\t\tfield.Set(ReferenceValue(vv, ptrDepth))\n\t\treturn nil\n\t}\n\n\terr = hjson.Unmarshal(bytesconv.S2b(text), field.Addr().Interface())\n\tif err != nil {\n\t\thlog.SystemLogger().Infof(\"unable to decode '%s' as %s: %v, but it may not affect correctness, so skip it\", text, d.fieldType.Name(), err)\n\t}\n\n\treturn nil\n}\n\nfunc getStructTypeFieldDecoder(field reflect.StructField, index int, tagInfos []TagInfo, parentIdx []int, config *DecodeConfig) ([]fieldDecoder, error) {\n\tfor idx, tagInfo := range tagInfos {\n\t\tswitch tagInfo.Key {\n\t\tcase pathTag:\n\t\t\ttagInfos[idx].SliceGetter = pathSlice\n\t\t\ttagInfos[idx].Getter = path\n\t\tcase formTag:\n\t\t\ttagInfos[idx].SliceGetter = postFormSlice\n\t\t\ttagInfos[idx].Getter = postForm\n\t\tcase queryTag:\n\t\t\ttagInfos[idx].SliceGetter = querySlice\n\t\t\ttagInfos[idx].Getter = query\n\t\tcase cookieTag:\n\t\t\ttagInfos[idx].SliceGetter = cookieSlice\n\t\t\ttagInfos[idx].Getter = cookie\n\t\tcase headerTag:\n\t\t\ttagInfos[idx].SliceGetter = headerSlice\n\t\t\ttagInfos[idx].Getter = header\n\t\tcase jsonTag:\n\t\t\t// do nothing\n\t\tcase rawBodyTag:\n\t\t\ttagInfos[idx].SliceGetter = rawBodySlice\n\t\t\ttagInfos[idx].Getter = rawBody\n\t\tcase fileNameTag:\n\t\t\t// do nothing\n\t\tdefault:\n\t\t}\n\t}\n\n\tfieldType := field.Type\n\tfor field.Type.Kind() == reflect.Ptr {\n\t\tfieldType = field.Type.Elem()\n\t}\n\n\treturn []fieldDecoder{&structTypeFieldTextDecoder{\n\t\tfieldInfo: fieldInfo{\n\t\t\tindex:       index,\n\t\t\tparentIndex: parentIdx,\n\t\t\tfieldName:   field.Name,\n\t\t\ttagInfos:    tagInfos,\n\t\t\tfieldType:   fieldType,\n\t\t\tconfig:      config,\n\t\t},\n\t}}, nil\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/tag.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage decoder\n\nimport (\n\t\"reflect\"\n\t\"strings\"\n)\n\nconst (\n\tpathTag     = \"path\"\n\tformTag     = \"form\"\n\tqueryTag    = \"query\"\n\tcookieTag   = \"cookie\"\n\theaderTag   = \"header\"\n\tjsonTag     = \"json\"\n\trawBodyTag  = \"raw_body\"\n\tfileNameTag = \"file_name\"\n)\n\nconst (\n\tdefaultTag = \"default\"\n)\n\nconst (\n\trequiredTagOpt = \"required\"\n)\n\ntype TagInfo struct {\n\tKey         string\n\tValue       string\n\tJSONName    string\n\tRequired    bool\n\tSkip        bool\n\tDefault     string\n\tOptions     []string\n\tGetter      getter\n\tSliceGetter sliceGetter\n}\n\nfunc head(str, sep string) (head, tail string) {\n\tidx := strings.Index(str, sep)\n\tif idx < 0 {\n\t\treturn str, \"\"\n\t}\n\treturn str[:idx], str[idx+len(sep):]\n}\n\nfunc lookupFieldTags(field reflect.StructField, parentJSONName string, config *DecodeConfig) ([]TagInfo, string) {\n\tvar ret []string\n\ttags := []string{pathTag, formTag, queryTag, cookieTag, headerTag, jsonTag, rawBodyTag, fileNameTag}\n\tfor _, tag := range tags {\n\t\tif _, ok := field.Tag.Lookup(tag); ok {\n\t\t\tret = append(ret, tag)\n\t\t}\n\t}\n\n\tdefaultVal := \"\"\n\tif val, ok := field.Tag.Lookup(defaultTag); ok {\n\t\tdefaultVal = val\n\t}\n\n\tvar tagInfos []TagInfo\n\tvar newParentJSONName string\n\tfor _, tag := range ret {\n\t\ttagContent := field.Tag.Get(tag)\n\t\ttagValue, opts := head(tagContent, \",\")\n\t\tif len(tagValue) == 0 {\n\t\t\ttagValue = field.Name\n\t\t}\n\t\tskip := false\n\t\tjsonName := parentJSONName + \".\" + field.Name\n\t\tif tag == jsonTag {\n\t\t\tjsonName = parentJSONName + \".\" + tagValue\n\t\t}\n\t\tif tagValue == \"-\" {\n\t\t\tskip = true\n\t\t\tif tag == jsonTag {\n\t\t\t\tjsonName = parentJSONName + \".\" + field.Name\n\t\t\t}\n\t\t}\n\t\tif jsonName != \"\" {\n\t\t\tjsonName = strings.TrimPrefix(jsonName, \".\")\n\t\t\tnewParentJSONName = jsonName\n\t\t}\n\t\tvar options []string\n\t\tvar opt string\n\t\tvar required bool\n\t\tfor len(opts) > 0 {\n\t\t\topt, opts = head(opts, \",\")\n\t\t\toptions = append(options, opt)\n\t\t\tif opt == requiredTagOpt {\n\t\t\t\trequired = true\n\t\t\t}\n\t\t}\n\t\ttagInfos = append(tagInfos, TagInfo{Key: tag, Value: tagValue, JSONName: jsonName, Options: options, Required: required, Default: defaultVal, Skip: skip})\n\t}\n\tif len(newParentJSONName) == 0 {\n\t\tnewParentJSONName = strings.TrimPrefix(parentJSONName+\".\"+field.Name, \".\")\n\t}\n\n\treturn tagInfos, newParentJSONName\n}\n\nfunc getDefaultFieldTags(field reflect.StructField, parentJSONName string) (tagInfos []TagInfo, newParentJSONName string) {\n\tdefaultVal := \"\"\n\tif val, ok := field.Tag.Lookup(defaultTag); ok {\n\t\tdefaultVal = val\n\t}\n\n\ttags := []string{pathTag, formTag, queryTag, cookieTag, headerTag, jsonTag, fileNameTag}\n\tfor _, tag := range tags {\n\t\tjsonName := strings.TrimPrefix(parentJSONName+\".\"+field.Name, \".\")\n\t\ttagInfos = append(tagInfos, TagInfo{Key: tag, Value: field.Name, Default: defaultVal, JSONName: jsonName})\n\t}\n\tnewParentJSONName = strings.TrimPrefix(parentJSONName+\".\"+field.Name, \".\")\n\n\treturn\n}\n\nfunc getFieldTagInfoByTag(field reflect.StructField, tag string) []TagInfo {\n\tvar tagInfos []TagInfo\n\tif content, ok := field.Tag.Lookup(tag); ok {\n\t\ttagValue, opts := head(content, \",\")\n\t\tif len(tagValue) == 0 {\n\t\t\ttagValue = field.Name\n\t\t}\n\t\tskip := false\n\t\tif tagValue == \"-\" {\n\t\t\tskip = true\n\t\t}\n\t\tdefaultVal := \"\"\n\t\tif val, ok := field.Tag.Lookup(defaultTag); ok {\n\t\t\tdefaultVal = val\n\t\t}\n\t\tvar options []string\n\t\tvar opt string\n\t\tvar required bool\n\t\tfor len(opts) > 0 {\n\t\t\topt, opts = head(opts, \",\")\n\t\t\toptions = append(options, opt)\n\t\t\tif opt == requiredTagOpt {\n\t\t\t\trequired = true\n\t\t\t}\n\t\t}\n\t\ttagInfos = append(tagInfos, TagInfo{Key: tag, Value: tagValue, Options: options, Required: required, Default: defaultVal, Skip: skip})\n\t} else {\n\t\ttagInfos = append(tagInfos, TagInfo{Key: tag, Value: field.Name})\n\t}\n\n\treturn tagInfos\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/text_decoder.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage decoder\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\thJson \"github.com/cloudwego/hertz/pkg/common/json\"\n)\n\ntype TextDecoder interface {\n\tUnmarshalString(s string, fieldValue reflect.Value, looseZeroMode bool) error\n}\n\nfunc SelectTextDecoder(rt reflect.Type) (TextDecoder, error) {\n\tswitch rt.Kind() {\n\tcase reflect.Bool:\n\t\treturn &boolDecoder{}, nil\n\tcase reflect.Uint8:\n\t\treturn &uintDecoder{bitSize: 8}, nil\n\tcase reflect.Uint16:\n\t\treturn &uintDecoder{bitSize: 16}, nil\n\tcase reflect.Uint32:\n\t\treturn &uintDecoder{bitSize: 32}, nil\n\tcase reflect.Uint64:\n\t\treturn &uintDecoder{bitSize: 64}, nil\n\tcase reflect.Uint:\n\t\treturn &uintDecoder{}, nil\n\tcase reflect.Int8:\n\t\treturn &intDecoder{bitSize: 8}, nil\n\tcase reflect.Int16:\n\t\treturn &intDecoder{bitSize: 16}, nil\n\tcase reflect.Int32:\n\t\treturn &intDecoder{bitSize: 32}, nil\n\tcase reflect.Int64:\n\t\treturn &intDecoder{bitSize: 64}, nil\n\tcase reflect.Int:\n\t\treturn &intDecoder{}, nil\n\tcase reflect.String:\n\t\treturn &stringDecoder{}, nil\n\tcase reflect.Float32:\n\t\treturn &floatDecoder{bitSize: 32}, nil\n\tcase reflect.Float64:\n\t\treturn &floatDecoder{bitSize: 64}, nil\n\tcase reflect.Interface:\n\t\treturn &interfaceDecoder{}, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"unsupported type %s\", rt.String())\n}\n\ntype boolDecoder struct{}\n\nfunc (d *boolDecoder) UnmarshalString(s string, fieldValue reflect.Value, looseZeroMode bool) error {\n\tif s == \"\" && looseZeroMode {\n\t\ts = \"false\"\n\t}\n\tv, err := strconv.ParseBool(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfieldValue.SetBool(v)\n\treturn nil\n}\n\ntype floatDecoder struct {\n\tbitSize int\n}\n\nfunc (d *floatDecoder) UnmarshalString(s string, fieldValue reflect.Value, looseZeroMode bool) error {\n\tif s == \"\" && looseZeroMode {\n\t\ts = \"0.0\"\n\t}\n\tv, err := strconv.ParseFloat(s, d.bitSize)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfieldValue.SetFloat(v)\n\treturn nil\n}\n\ntype intDecoder struct {\n\tbitSize int\n}\n\nfunc (d *intDecoder) UnmarshalString(s string, fieldValue reflect.Value, looseZeroMode bool) error {\n\tif s == \"\" && looseZeroMode {\n\t\ts = \"0\"\n\t}\n\tv, err := strconv.ParseInt(s, 10, d.bitSize)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfieldValue.SetInt(v)\n\treturn nil\n}\n\ntype stringDecoder struct{}\n\nfunc (d *stringDecoder) UnmarshalString(s string, fieldValue reflect.Value, looseZeroMode bool) error {\n\tfieldValue.SetString(s)\n\treturn nil\n}\n\ntype uintDecoder struct {\n\tbitSize int\n}\n\nfunc (d *uintDecoder) UnmarshalString(s string, fieldValue reflect.Value, looseZeroMode bool) error {\n\tif s == \"\" && looseZeroMode {\n\t\ts = \"0\"\n\t}\n\tv, err := strconv.ParseUint(s, 10, d.bitSize)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfieldValue.SetUint(v)\n\treturn nil\n}\n\ntype interfaceDecoder struct{}\n\nfunc (d *interfaceDecoder) UnmarshalString(s string, fieldValue reflect.Value, looseZeroMode bool) error {\n\tif s == \"\" && looseZeroMode {\n\t\ts = \"0\"\n\t}\n\treturn hJson.Unmarshal(bytesconv.S2b(s), fieldValue.Addr().Interface())\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/util.go",
    "content": "/*\n * Copyright 2024 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage decoder\n\nimport (\n\t\"encoding\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\thJson \"github.com/cloudwego/hertz/pkg/common/json\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\nconst (\n\tspecialChar = \"\\x07\"\n)\n\n// toDefaultValue will preprocess the default value and transfer it to be standard format\nfunc toDefaultValue(typ reflect.Type, defaultValue string) string {\n\tswitch typ.Kind() {\n\tcase reflect.Slice, reflect.Array, reflect.Map, reflect.Struct:\n\t\t// escape single quote and double quote, replace single quote with double quote\n\t\tdefaultValue = strings.Replace(defaultValue, `\"`, `\\\"`, -1)\n\t\tdefaultValue = strings.Replace(defaultValue, `\\'`, specialChar, -1)\n\t\tdefaultValue = strings.Replace(defaultValue, `'`, `\"`, -1)\n\t\tdefaultValue = strings.Replace(defaultValue, specialChar, `'`, -1)\n\t}\n\treturn defaultValue\n}\n\n// stringToValue is used to dynamically create reflect.Value for 'text'\nfunc stringToValue(elemType reflect.Type, text string, req *protocol.Request, params param.Params, config *DecodeConfig) (reflect.Value, error) {\n\tif customizedFunc, exist := config.TypeUnmarshalFuncs[elemType]; exist {\n\t\tval, err := customizedFunc(req, params, text)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\t\treturn val, nil\n\t}\n\tv := reflect.New(elemType)\n\tif tryTextUnmarshaler(v, text) {\n\t\treturn v.Elem(), nil\n\t}\n\tswitch elemType.Kind() {\n\tcase reflect.Struct, reflect.Map:\n\t\tif err := hJson.Unmarshal(bytesconv.S2b(text), v.Interface()); err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\t\treturn v.Elem(), nil\n\n\tcase reflect.Array, reflect.Slice:\n\t\t// do nothing\n\t\treturn v.Elem(), nil\n\n\tdefault:\n\t\tdecoder, err := SelectTextDecoder(elemType)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, err\n\t\t}\n\t\tv = v.Elem()\n\t\terr = decoder.UnmarshalString(text, v, config.LooseZeroMode)\n\t\tif err != nil {\n\t\t\treturn reflect.Value{}, fmt.Errorf(\"unable to decode '%s' as %s: %w\", text, elemType.String(), err)\n\t\t}\n\t\treturn v, nil\n\t}\n}\n\nfunc tryTextUnmarshaler(v reflect.Value, s string) bool {\n\tenc, ok := v.Interface().(encoding.TextUnmarshaler)\n\tif ok {\n\t\tif err := enc.UnmarshalText(bytesconv.S2b(s)); err == nil {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/app/server/binding/internal/decoder/util_test.go",
    "content": "/*\n * Copyright 2024 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage decoder\n\nimport (\n\t\"encoding\"\n\t\"errors\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype testTextUnmarshaler struct {\n\tValue string\n}\n\nfunc (t *testTextUnmarshaler) UnmarshalText(text []byte) error {\n\tt.Value = string(text)\n\treturn nil\n}\n\nvar _ encoding.TextUnmarshaler = (*testTextUnmarshaler)(nil)\n\nfunc TestStringToValue(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\telemType    reflect.Type\n\t\ttext        string\n\t\tconfig      *DecodeConfig\n\t\texpectValue interface{}\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname:        \"string type\",\n\t\t\telemType:    reflect.TypeOf(\"\"),\n\t\t\ttext:        \"test string\",\n\t\t\texpectValue: \"test string\",\n\t\t},\n\t\t{\n\t\t\tname:        \"int type\",\n\t\t\telemType:    reflect.TypeOf(0),\n\t\t\ttext:        \"42\",\n\t\t\texpectValue: 42,\n\t\t},\n\t\t{\n\t\t\tname:        \"bool type\",\n\t\t\telemType:    reflect.TypeOf(false),\n\t\t\ttext:        \"true\",\n\t\t\texpectValue: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"float type\",\n\t\t\telemType:    reflect.TypeOf(0.0),\n\t\t\ttext:        \"3.14\",\n\t\t\texpectValue: 3.14,\n\t\t},\n\t\t{\n\t\t\tname:        \"text unmarshaler\",\n\t\t\telemType:    reflect.TypeOf(testTextUnmarshaler{}),\n\t\t\ttext:        \"custom text\",\n\t\t\texpectValue: testTextUnmarshaler{Value: \"custom text\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"invalid int\",\n\t\t\telemType:    reflect.TypeOf(0),\n\t\t\ttext:        \"not an int\",\n\t\t\texpectError: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"struct type\",\n\t\t\telemType:    reflect.TypeOf(struct{ Name string }{}),\n\t\t\ttext:        `{\"Name\":\"test\"}`,\n\t\t\texpectValue: struct{ Name string }{Name: \"test\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"struct type err\",\n\t\t\telemType:    reflect.TypeOf(struct{ Name string }{}),\n\t\t\ttext:        `{\"Name\":1}`,\n\t\t\texpectError: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"list type\",\n\t\t\telemType:    reflect.TypeOf([]int{}),\n\t\t\texpectValue: *new([]int),\n\t\t},\n\t\t{\n\t\t\tname:        \"map type\",\n\t\t\telemType:    reflect.TypeOf(map[string]interface{}{}),\n\t\t\ttext:        `{\"key\":\"value\"}`,\n\t\t\texpectValue: map[string]interface{}{\"key\": \"value\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"unsupported type\",\n\t\t\telemType:    reflect.TypeOf(complex64(0)),\n\t\t\texpectError: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"custom type unmarshal func\",\n\t\t\telemType: reflect.TypeOf(testTextUnmarshaler{}),\n\t\t\ttext:     \"custom func\",\n\t\t\tconfig: &DecodeConfig{\n\t\t\t\tTypeUnmarshalFuncs: map[reflect.Type]CustomizeDecodeFunc{\n\t\t\t\t\treflect.TypeOf(testTextUnmarshaler{}): func(req *protocol.Request, params param.Params, text string) (reflect.Value, error) {\n\t\t\t\t\t\treturn reflect.ValueOf(testTextUnmarshaler{Value: \"from custom func\"}), nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectValue: testTextUnmarshaler{Value: \"from custom func\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"custom type unmarshal func err\",\n\t\t\telemType: reflect.TypeOf(testTextUnmarshaler{}),\n\t\t\tconfig: &DecodeConfig{\n\t\t\t\tTypeUnmarshalFuncs: map[reflect.Type]CustomizeDecodeFunc{\n\t\t\t\t\treflect.TypeOf(testTextUnmarshaler{}): func(req *protocol.Request, params param.Params, text string) (reflect.Value, error) {\n\t\t\t\t\t\treturn reflect.Value{}, errors.New(\"err\")\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\treq := &protocol.Request{}\n\t\t\tparams := param.Params{}\n\t\t\tconfig := tt.config\n\t\t\tif config == nil {\n\t\t\t\tconfig = &DecodeConfig{}\n\t\t\t}\n\t\t\tval, err := stringToValue(tt.elemType, tt.text, req, params, config)\n\t\t\tif tt.expectError {\n\t\t\t\tassert.NotNil(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.DeepEqual(t, tt.expectValue, val.Interface())\n\t\t})\n\t}\n}\n\nfunc TestTryTextUnmarshaler(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tvalue    interface{}\n\t\ttext     string\n\t\texpected bool\n\t}{\n\t\t{\n\t\t\tname:     \"text unmarshaler\",\n\t\t\tvalue:    &testTextUnmarshaler{},\n\t\t\ttext:     \"test text\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"non text unmarshaler\",\n\t\t\tvalue:    &struct{}{},\n\t\t\ttext:     \"test text\",\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname:     \"nil value\",\n\t\t\tvalue:    nil,\n\t\t\ttext:     \"test text\",\n\t\t\texpected: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar v reflect.Value\n\t\t\tif tt.value != nil {\n\t\t\t\tv = reflect.ValueOf(tt.value)\n\t\t\t} else {\n\t\t\t\tv = reflect.ValueOf(&tt.value).Elem()\n\t\t\t}\n\n\t\t\tresult := tryTextUnmarshaler(v, tt.text)\n\t\t\tassert.DeepEqual(t, tt.expected, result)\n\n\t\t\tif tt.expected && tt.value != nil {\n\t\t\t\t// Verify the value was actually set\n\t\t\t\tunmarshaler := tt.value.(*testTextUnmarshaler)\n\t\t\t\tassert.DeepEqual(t, tt.text, unmarshaler.Value)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/app/server/binding/reflect.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright (c) 2019-present Fenny and Contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage binding\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"unsafe\"\n)\n\nfunc valueAndTypeID(v interface{}) (reflect.Value, uintptr) {\n\theader := (*emptyInterface)(unsafe.Pointer(&v))\n\trv := reflect.ValueOf(v)\n\treturn rv, header.typeID\n}\n\ntype emptyInterface struct {\n\ttypeID  uintptr\n\tdataPtr unsafe.Pointer\n}\n\nfunc checkPointer(rv reflect.Value) error {\n\tif rv.Kind() != reflect.Ptr || rv.IsNil() {\n\t\treturn fmt.Errorf(\"receiver must be a non-nil pointer\")\n\t}\n\treturn nil\n}\n\n// dereferenceType recursively dereferences pointer types to get the underlying type.\nfunc dereferenceType(t reflect.Type) reflect.Type {\n\tfor t.Kind() == reflect.Pointer {\n\t\tt = t.Elem()\n\t}\n\treturn t\n}\n"
  },
  {
    "path": "pkg/app/server/binding/reflect_internal_test.go",
    "content": "// Copyright 2023 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\npackage binding\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/server/binding/internal/decoder\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\ntype foo2 struct {\n\tF1 string\n}\n\ntype fooq struct {\n\tF1 **string\n}\n\nfunc Test_ReferenceValue(t *testing.T) {\n\tfoo1 := foo2{F1: \"f1\"}\n\tfoo1Val := reflect.ValueOf(foo1)\n\tfoo1PointerVal := decoder.ReferenceValue(foo1Val, 5)\n\tassert.DeepEqual(t, \"f1\", foo1.F1)\n\tassert.DeepEqual(t, \"f1\", foo1Val.Field(0).Interface().(string))\n\tif foo1PointerVal.Kind() != reflect.Ptr {\n\t\tt.Errorf(\"expect a pointer, but get nil\")\n\t}\n\tassert.DeepEqual(t, \"*****binding.foo2\", foo1PointerVal.Type().String())\n\n\tdeFoo1PointerVal := decoder.ReferenceValue(foo1PointerVal, -5)\n\tif deFoo1PointerVal.Kind() == reflect.Ptr {\n\t\tt.Errorf(\"expect a non-pointer, but get a pointer\")\n\t}\n\tassert.DeepEqual(t, \"f1\", deFoo1PointerVal.Field(0).Interface().(string))\n}\n\nfunc Test_GetNonNilReferenceValue(t *testing.T) {\n\tfoo1 := (****foo)(nil)\n\tfoo1Val := reflect.ValueOf(foo1)\n\tfoo1ValNonNil, ptrDepth := decoder.GetNonNilReferenceValue(foo1Val)\n\tif !foo1ValNonNil.IsValid() {\n\t\tt.Errorf(\"expect a valid value, but get nil\")\n\t}\n\tif !foo1ValNonNil.CanSet() {\n\t\tt.Errorf(\"expect can set value, but not\")\n\t}\n\n\tfoo1ReferPointer := decoder.ReferenceValue(foo1ValNonNil, ptrDepth)\n\tif foo1ReferPointer.Kind() != reflect.Ptr {\n\t\tt.Errorf(\"expect a pointer, but get nil\")\n\t}\n}\n\nfunc Test_GetFieldValue(t *testing.T) {\n\ttype bar struct {\n\t\tB1 **fooq\n\t}\n\tbar1 := (***bar)(nil)\n\tparentIdx := []int{0}\n\tidx := 0\n\n\tbar1Val := reflect.ValueOf(bar1)\n\tparentFieldVal := decoder.GetFieldValue(bar1Val, parentIdx)\n\tif parentFieldVal.Kind() == reflect.Ptr {\n\t\tt.Errorf(\"expect a non-pointer, but get a pointer\")\n\t}\n\tif !parentFieldVal.CanSet() {\n\t\tt.Errorf(\"expect can set value, but not\")\n\t}\n\tfooFieldVal := parentFieldVal.Field(idx)\n\tassert.DeepEqual(t, \"**string\", fooFieldVal.Type().String())\n\tif !fooFieldVal.CanSet() {\n\t\tt.Errorf(\"expect can set value, but not\")\n\t}\n}\n"
  },
  {
    "path": "pkg/app/server/binding/reflect_test.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage binding\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\ntype foo struct {\n\tf1 string\n}\n\nfunc TestReflect_TypeID(t *testing.T) {\n\t_, intType := valueAndTypeID(int(1))\n\t_, uintType := valueAndTypeID(uint(1))\n\t_, shouldBeIntType := valueAndTypeID(int(1))\n\tassert.DeepEqual(t, intType, shouldBeIntType)\n\tassert.NotEqual(t, intType, uintType)\n\n\tfoo1 := foo{f1: \"1\"}\n\tfoo2 := foo{f1: \"2\"}\n\t_, foo1Type := valueAndTypeID(foo1)\n\t_, foo2Type := valueAndTypeID(foo2)\n\t_, foo2PointerType := valueAndTypeID(&foo2)\n\t_, foo1PointerType := valueAndTypeID(&foo1)\n\tassert.DeepEqual(t, foo1Type, foo2Type)\n\tassert.NotEqual(t, foo1Type, foo2PointerType)\n\tassert.DeepEqual(t, foo1PointerType, foo2PointerType)\n}\n\nfunc TestReflect_CheckPointer(t *testing.T) {\n\tfoo1 := foo{}\n\tfoo1Val := reflect.ValueOf(foo1)\n\terr := checkPointer(foo1Val)\n\tif err == nil {\n\t\tt.Errorf(\"expect an err, but get nil\")\n\t}\n\n\tfoo2 := &foo{}\n\tfoo2Val := reflect.ValueOf(foo2)\n\terr = checkPointer(foo2Val)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tfoo3 := (*foo)(nil)\n\tfoo3Val := reflect.ValueOf(foo3)\n\terr = checkPointer(foo3Val)\n\tif err == nil {\n\t\tt.Errorf(\"expect an err, but get nil\")\n\t}\n}\n\nfunc TestReflect_DereferenceType(t *testing.T) {\n\tvar foo1 ****foo\n\tfoo1Val := reflect.ValueOf(foo1)\n\trt := dereferenceType(foo1Val.Type())\n\tassert.DeepEqual(t, \"foo\", rt.Name())\n\n\tvar foo2 foo\n\tfoo2Val := reflect.ValueOf(foo2)\n\trt2 := dereferenceType(foo2Val.Type())\n\tassert.DeepEqual(t, \"foo\", rt2.Name())\n}\n"
  },
  {
    "path": "pkg/app/server/binding/tagexpr_bind_test.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * MIT License\n *\n * Copyright 2019 Bytedance Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage binding\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc TestRawBody(t *testing.T) {\n\ttype Recv struct {\n\t\tS []byte   `raw_body:\"\"`\n\t\tF **string `raw_body:\"\"`\n\t}\n\tbodyBytes := []byte(\"raw_body.............\")\n\treq := newRequest(\"\", nil, nil, bytes.NewReader(bodyBytes))\n\trecv := new(Recv)\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\n\tassert.DeepEqual(t, bodyBytes, recv.S)\n\tassert.DeepEqual(t, string(bodyBytes), **recv.F)\n}\n\nfunc TestQueryString(t *testing.T) {\n\ttype metric string\n\ttype count int32\n\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA []string  `query:\"a\"`\n\t\t\tB string    `query:\"b\"`\n\t\t\tC *[]string `query:\"c,required\"`\n\t\t\tD *string   `query:\"d\"`\n\t\t\tE *[]***int `query:\"e\"`\n\t\t\tF metric    `query:\"f\"`\n\t\t\tG []count   `query:\"g\"`\n\t\t}\n\t\tY string  `query:\"y,required\"`\n\t\tZ *string `query:\"z\"`\n\t}\n\treq := newRequest(\"http://localhost:8080/?a=a1&a=a2&b=b1&c=c1&c=c2&d=d1&d=d&f=qps&g=1002&g=1003&e=&e=2&y=y1\", nil, nil, nil)\n\trecv := new(Recv)\n\tbindConfig := &BindConfig{}\n\tbindConfig.LooseZeroMode = true\n\tbinder := NewDefaultBinder(bindConfig)\n\terr := binder.Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 0, ***(*(**recv.X).E)[0])\n\tassert.DeepEqual(t, 2, ***(*(**recv.X).E)[1])\n\tassert.DeepEqual(t, []string{\"a1\", \"a2\"}, (**recv.X).A)\n\tassert.DeepEqual(t, \"b1\", (**recv.X).B)\n\tassert.DeepEqual(t, []string{\"c1\", \"c2\"}, *(**recv.X).C)\n\tassert.DeepEqual(t, \"d1\", *(**recv.X).D)\n\tassert.DeepEqual(t, metric(\"qps\"), (**recv.X).F)\n\tassert.DeepEqual(t, []count{1002, 1003}, (**recv.X).G)\n\tassert.DeepEqual(t, \"y1\", recv.Y)\n\tassert.DeepEqual(t, (*string)(nil), recv.Z)\n}\n\nfunc TestGetBody(t *testing.T) {\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tE string `json:\"e,required\" query:\"e,required\"`\n\t\t}\n\t}\n\treq := newRequest(\"http://localhost:8080/\", nil, nil, nil)\n\trecv := new(Recv)\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err == nil {\n\t\tt.Fatalf(\"expected an error, but get nil\")\n\t}\n\tassert.DeepEqual(t, err.Error(), \"'e' field is a 'required' parameter, but the request body does not have this parameter 'X.e'\")\n}\n\nfunc TestQueryNum(t *testing.T) {\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA []int     `query:\"a\"`\n\t\t\tB int32     `query:\"b\"`\n\t\t\tC *[]uint16 `query:\"c,required\"`\n\t\t\tD *float32  `query:\"d\"`\n\t\t}\n\t\tY bool   `query:\"y,required\"`\n\t\tZ *int64 `query:\"z\"`\n\t}\n\treq := newRequest(\"http://localhost:8080/?a=11&a=12&b=21&c=31&c=32&d=41&d=42&y=true\", nil, nil, nil)\n\trecv := new(Recv)\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\tassert.DeepEqual(t, []int{11, 12}, (**recv.X).A)\n\tassert.DeepEqual(t, int32(21), (**recv.X).B)\n\tassert.DeepEqual(t, &[]uint16{31, 32}, (**recv.X).C)\n\tassert.DeepEqual(t, float32(41), *(**recv.X).D)\n\tassert.DeepEqual(t, true, recv.Y)\n\tassert.DeepEqual(t, (*int64)(nil), recv.Z)\n}\n\nfunc TestHeaderString(t *testing.T) {\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA []string  `header:\"X-A\"`\n\t\t\tB string    `header:\"X-B\"`\n\t\t\tC *[]string `header:\"X-C,required\"`\n\t\t\tD *string   `header:\"X-D\"`\n\t\t}\n\t\tY string  `header:\"X-Y,required\"`\n\t\tZ *string `header:\"X-Z\"`\n\t}\n\theader := make(http.Header)\n\theader.Add(\"X-A\", \"a1\")\n\theader.Add(\"X-A\", \"a2\")\n\theader.Add(\"X-B\", \"b1\")\n\theader.Add(\"X-C\", \"c1\")\n\theader.Add(\"X-C\", \"c2\")\n\theader.Add(\"X-D\", \"d1\")\n\theader.Add(\"X-D\", \"d2\")\n\theader.Add(\"X-Y\", \"y1\")\n\treq := newRequest(\"\", header, nil, nil)\n\trecv := new(Recv)\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\tassert.DeepEqual(t, []string{\"a1\", \"a2\"}, (**recv.X).A)\n\tassert.DeepEqual(t, \"b1\", (**recv.X).B)\n\tassert.DeepEqual(t, []string{\"c1\", \"c2\"}, *(**recv.X).C)\n\tassert.DeepEqual(t, \"d1\", *(**recv.X).D)\n\tassert.DeepEqual(t, \"y1\", recv.Y)\n\tassert.DeepEqual(t, (*string)(nil), recv.Z)\n}\n\nfunc TestHeaderNum(t *testing.T) {\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA []int     `header:\"X-A\"`\n\t\t\tB int32     `header:\"X-B\"`\n\t\t\tC *[]uint16 `header:\"X-C,required\"`\n\t\t\tD *float32  `header:\"X-D\"`\n\t\t}\n\t\tY bool   `header:\"X-Y,required\"`\n\t\tZ *int64 `header:\"X-Z\"`\n\t}\n\theader := make(http.Header)\n\theader.Add(\"X-A\", \"11\")\n\theader.Add(\"X-A\", \"12\")\n\theader.Add(\"X-B\", \"21\")\n\theader.Add(\"X-C\", \"31\")\n\theader.Add(\"X-C\", \"32\")\n\theader.Add(\"X-D\", \"41\")\n\theader.Add(\"X-D\", \"42\")\n\theader.Add(\"X-Y\", \"true\")\n\treq := newRequest(\"\", header, nil, nil)\n\trecv := new(Recv)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, []int{11, 12}, (**recv.X).A)\n\tassert.DeepEqual(t, int32(21), (**recv.X).B)\n\tassert.DeepEqual(t, &[]uint16{31, 32}, (**recv.X).C)\n\tassert.DeepEqual(t, float32(41), *(**recv.X).D)\n\tassert.DeepEqual(t, true, recv.Y)\n\tassert.DeepEqual(t, (*int64)(nil), recv.Z)\n}\n\nfunc TestCookieString(t *testing.T) {\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA []string  `cookie:\"a\"`\n\t\t\tB string    `cookie:\"b\"`\n\t\t\tC *[]string `cookie:\"c,required\"`\n\t\t\tD *string   `cookie:\"d\"`\n\t\t}\n\t\tY string  `cookie:\"y,required\"`\n\t\tZ *string `cookie:\"z\"`\n\t}\n\tcookies := []*http.Cookie{\n\t\t{Name: \"a\", Value: \"a1\"},\n\t\t{Name: \"a\", Value: \"a2\"},\n\t\t{Name: \"b\", Value: \"b1\"},\n\t\t{Name: \"c\", Value: \"c1\"},\n\t\t{Name: \"c\", Value: \"c2\"},\n\t\t{Name: \"d\", Value: \"d1\"},\n\t\t{Name: \"d\", Value: \"d2\"},\n\t\t{Name: \"y\", Value: \"y1\"},\n\t}\n\treq := newRequest(\"\", nil, cookies, nil)\n\trecv := new(Recv)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, []string{\"a2\"}, (**recv.X).A)\n\tassert.DeepEqual(t, \"b1\", (**recv.X).B)\n\tassert.DeepEqual(t, []string{\"c2\"}, *(**recv.X).C)\n\tassert.DeepEqual(t, \"d2\", *(**recv.X).D)\n\tassert.DeepEqual(t, \"y1\", recv.Y)\n\tassert.DeepEqual(t, (*string)(nil), recv.Z)\n}\n\nfunc TestCookieNum(t *testing.T) {\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA []int     `cookie:\"a\"`\n\t\t\tB int32     `cookie:\"b\"`\n\t\t\tC *[]uint16 `cookie:\"c,required\"`\n\t\t\tD *float32  `cookie:\"d\"`\n\t\t}\n\t\tY bool   `cookie:\"y,required\"`\n\t\tZ *int64 `cookie:\"z\"`\n\t}\n\tcookies := []*http.Cookie{\n\t\t{Name: \"a\", Value: \"11\"},\n\t\t{Name: \"b\", Value: \"21\"},\n\t\t{Name: \"c\", Value: \"31\"},\n\t\t{Name: \"d\", Value: \"41\"},\n\t\t{Name: \"y\", Value: \"t\"},\n\t}\n\treq := newRequest(\"\", nil, cookies, nil)\n\trecv := new(Recv)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, []int{11}, (**recv.X).A)\n\tassert.DeepEqual(t, int32(21), (**recv.X).B)\n\tassert.DeepEqual(t, &[]uint16{31}, (**recv.X).C)\n\tassert.DeepEqual(t, float32(41), *(**recv.X).D)\n\tassert.DeepEqual(t, true, recv.Y)\n\tassert.DeepEqual(t, (*int64)(nil), recv.Z)\n}\n\nfunc TestFormString(t *testing.T) {\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA []string  `form:\"a\"`\n\t\t\tB string    `form:\"b\"`\n\t\t\tC *[]string `form:\"c,required\"`\n\t\t\tD *string   `form:\"d\"`\n\t\t}\n\t\tY   string                `form:\"y,required\"`\n\t\tZ   *string               `form:\"z\"`\n\t\tF   *multipart.FileHeader `form:\"F1\"`\n\t\tF1  multipart.FileHeader\n\t\tFs  []multipart.FileHeader  `form:\"F1\"`\n\t\tFs1 []*multipart.FileHeader `form:\"F1\"`\n\t}\n\tvalues := make(url.Values)\n\tvalues.Add(\"a\", \"a1\")\n\tvalues.Add(\"a\", \"a2\")\n\tvalues.Add(\"b\", \"b1\")\n\tvalues.Add(\"c\", \"c1\")\n\tvalues.Add(\"c\", \"c2\")\n\tvalues.Add(\"d\", \"d1\")\n\tvalues.Add(\"d\", \"d2\")\n\tvalues.Add(\"y\", \"y1\")\n\tfor i, f := range []files{{\n\t\t\"F1\": []file{\n\t\t\tnewFile(\"txt\", strings.NewReader(\"0123\")),\n\t\t},\n\t}} {\n\t\tcontentType, bodyReader := newFormBody2(values, f)\n\t\theader := make(http.Header)\n\t\theader.Set(\"Content-Type\", contentType)\n\t\treq := newRequest(\"\", header, nil, bodyReader)\n\t\trecv := new(Recv)\n\t\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tassert.DeepEqual(t, []string{\"a1\", \"a2\"}, (**recv.X).A)\n\t\tassert.DeepEqual(t, \"b1\", (**recv.X).B)\n\t\tassert.DeepEqual(t, []string{\"c1\", \"c2\"}, *(**recv.X).C)\n\t\tassert.DeepEqual(t, \"d1\", *(**recv.X).D)\n\t\tassert.DeepEqual(t, \"y1\", recv.Y)\n\t\tassert.DeepEqual(t, (*string)(nil), recv.Z)\n\t\tt.Logf(\"[%d] F: %#v\", i, recv.F)\n\t\tt.Logf(\"[%d] F1: %#v\", i, recv.F1)\n\t\tt.Logf(\"[%d] Fs: %#v\", i, recv.Fs)\n\t\tt.Logf(\"[%d] Fs1: %#v\", i, recv.Fs1)\n\t\tif len(recv.Fs1) > 0 {\n\t\t\tt.Logf(\"[%d] Fs1[0]: %#v\", i, recv.Fs1[0])\n\t\t}\n\t}\n}\n\nfunc TestFormNum(t *testing.T) {\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA []int     `form:\"a\"`\n\t\t\tB int32     `form:\"b\"`\n\t\t\tC *[]uint16 `form:\"c,required\"`\n\t\t\tD *float32  `form:\"d\"`\n\t\t}\n\t\tY bool   `form:\"y,required\"`\n\t\tZ *int64 `form:\"z\"`\n\t}\n\tvalues := make(url.Values)\n\tvalues.Add(\"a\", \"11\")\n\tvalues.Add(\"a\", \"12\")\n\tvalues.Add(\"b\", \"-21\")\n\tvalues.Add(\"c\", \"31\")\n\tvalues.Add(\"c\", \"32\")\n\tvalues.Add(\"d\", \"41\")\n\tvalues.Add(\"d\", \"42\")\n\tvalues.Add(\"y\", \"1\")\n\tfor _, f := range []files{nil, {\n\t\t\"f1\": []file{\n\t\t\tnewFile(\"txt\", strings.NewReader(\"f11 text.\")),\n\t\t},\n\t}} {\n\t\tcontentType, bodyReader := newFormBody2(values, f)\n\t\theader := make(http.Header)\n\t\theader.Set(\"Content-Type\", contentType)\n\t\treq := newRequest(\"\", header, nil, bodyReader)\n\t\trecv := new(Recv)\n\n\t\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tassert.DeepEqual(t, []int{11, 12}, (**recv.X).A)\n\t\tassert.DeepEqual(t, int32(-21), (**recv.X).B)\n\t\tassert.DeepEqual(t, &[]uint16{31, 32}, (**recv.X).C)\n\t\tassert.DeepEqual(t, float32(41), *(**recv.X).D)\n\t\tassert.DeepEqual(t, true, recv.Y)\n\t\tassert.DeepEqual(t, (*int64)(nil), recv.Z)\n\t}\n}\n\nfunc TestJSON(t *testing.T) {\n\ttype metric string\n\ttype count int32\n\ttype ZS struct {\n\t\tZ *int64\n\t}\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA []string          `json:\"a\"`\n\t\t\tB int32             `json:\"\"`\n\t\t\tC *[]uint16         `json:\",required\"`\n\t\t\tD *float32          `json:\"d\"`\n\t\t\tE metric            `json:\"e\"`\n\t\t\tF count             `json:\"f\"`\n\t\t\tM map[string]string `json:\"m\"`\n\t\t}\n\t\tY string `json:\"y,required\"`\n\t\tZS\n\t}\n\n\tbodyReader := strings.NewReader(`{\n\t\t\"X\": {\n\t\t\t\"a\": [\"a1\",\"a2\"],\n\t\t\t\"B\": 21,\n\t\t\t\"C\": [31,32],\n\t\t\t\"d\": 41,\n\t\t\t\"e\": \"qps\",\n\t\t\t\"f\": 100,\n\t\t\t\"m\": {\"a\":\"x\"}\n\t\t},\n\t\t\"Z\": 6\n\t}`)\n\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", consts.MIMEApplicationJSON)\n\treq := newRequest(\"\", header, nil, bodyReader)\n\trecv := new(Recv)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err == nil {\n\t\tt.Error(\"expected an error, but get nil\")\n\t}\n\tassert.DeepEqual(t, err.Error(), \"'y' field is a 'required' parameter, but the request body does not have this parameter 'y'\")\n\tassert.DeepEqual(t, []string{\"a1\", \"a2\"}, (**recv.X).A)\n\tassert.DeepEqual(t, int32(21), (**recv.X).B)\n\tassert.DeepEqual(t, &[]uint16{31, 32}, (**recv.X).C)\n\tassert.DeepEqual(t, float32(41), *(**recv.X).D)\n\tassert.DeepEqual(t, metric(\"qps\"), (**recv.X).E)\n\tassert.DeepEqual(t, count(100), (**recv.X).F)\n\tassert.DeepEqual(t, map[string]string{\"a\": \"x\"}, (**recv.X).M)\n\tassert.DeepEqual(t, \"\", recv.Y)\n\tassert.DeepEqual(t, (int64)(6), *recv.Z)\n}\n\nfunc TestNonstruct(t *testing.T) {\n\tbodyReader := strings.NewReader(`{\n\t\t\"X\": {\n\t\t\t\"a\": [\"a1\",\"a2\"],\n\t\t\t\"B\": 21,\n\t\t\t\"C\": [31,32],\n\t\t\t\"d\": 41,\n\t\t\t\"e\": \"qps\",\n\t\t\t\"f\": 100\n\t\t},\n\t\t\"Z\": 6\n\t}`)\n\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", \"application/json\")\n\treq := newRequest(\"\", header, nil, bodyReader)\n\tvar recv interface{}\n\terr := DefaultBinder().Bind(req.Req, &recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tb, err := json.Marshal(recv)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tt.Logf(\"%s\", b)\n\n\tbodyReader = strings.NewReader(\"b=334ddddd&token=mymocktoken&type=url_verification\")\n\theader.Set(\"Content-Type\", \"application/x-www-form-urlencoded; charset=utf-8\")\n\treq = newRequest(\"\", header, nil, bodyReader)\n\trecv = nil\n\terr = DefaultBinder().Bind(req.Req, &recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tb, err = json.Marshal(recv)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tt.Logf(\"%s\", b)\n}\n\nfunc TestPath(t *testing.T) {\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA []string  `path:\"a\"`\n\t\t\tB int32     `path:\"b\"`\n\t\t\tC *[]uint16 `path:\"c,required\"`\n\t\t\tD *float32  `path:\"d\"`\n\t\t}\n\t\tY string `path:\"y,required\"`\n\t\tZ *int64\n\t}\n\n\treq := newRequest(\"\", nil, nil, nil)\n\trecv := new(Recv)\n\n\tparams := param.Params{\n\t\t{\n\t\t\tKey:   \"a\",\n\t\t\tValue: \"a1\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"b\",\n\t\t\tValue: \"-21\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"c\",\n\t\t\tValue: \"31\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"d\",\n\t\t\tValue: \"41\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"y\",\n\t\t\tValue: \"y1\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"name\",\n\t\t\tValue: \"henrylee2cn\",\n\t\t},\n\t}\n\n\terr := DefaultBinder().Bind(req.Req, recv, params)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, []string{\"a1\"}, (**recv.X).A)\n\tassert.DeepEqual(t, int32(-21), (**recv.X).B)\n\tassert.DeepEqual(t, &[]uint16{31}, (**recv.X).C)\n\tassert.DeepEqual(t, float32(41), *(**recv.X).D)\n\tassert.DeepEqual(t, \"y1\", recv.Y)\n\tassert.DeepEqual(t, (*int64)(nil), recv.Z)\n}\n\nfunc TestDefault(t *testing.T) {\n\ttype S struct {\n\t\tSS string `json:\"ss\"`\n\t}\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tA          []string           `path:\"a\" json:\"a\"`\n\t\t\tB          int32              `path:\"b\" default:\"32\"`\n\t\t\tC          bool               `json:\"c\" default:\"true\"`\n\t\t\tD          *float32           `default:\"123.4\"`\n\t\t\tE          *[]string          `default:\"['a','b','c','d,e,f']\"`\n\t\t\tF          map[string]string  `default:\"{'a':'\\\"\\\\'1','\\\"b':'c','c':'2'}\"`\n\t\t\tG          map[string]int64   `default:\"{'a':1,'b':2,'c':3}\"`\n\t\t\tH          map[string]float64 `default:\"{'a':0.1,'b':1.2,'c':2.3}\"`\n\t\t\tI          map[string]float64 `default:\"{'\\\"a\\\"':0.1,'b':1.2,'c':2.3}\"`\n\t\t\tEmpty      string             `default:\"\"`\n\t\t\tNull       string             `default:\"\"`\n\t\t\tCommaSpace string             `default:\",a:c \"`\n\t\t\tDash       string             `default:\"-\"`\n\t\t\t// InvalidInt int                `default:\"abc\"`\n\t\t\t// InvalidMap map[string]string  `default:\"abc\"`\n\t\t}\n\t\tY       string `json:\"y\" default:\"y1\"`\n\t\tZ       int64\n\t\tW       string                          `json:\"w\"`\n\t\tV       []int64                         `json:\"v\" default:\"[1,2,3]\"`\n\t\tU       []float32                       `json:\"u\" default:\"[1.1,2,3]\"`\n\t\tT       *string                         `json:\"t\" default:\"t1\"`\n\t\tS       S                               `default:\"{'ss':'test'}\"`\n\t\tO       *S                              `default:\"{'ss':'test2'}\"`\n\t\tComplex map[string][]map[string][]int64 `default:\"{'a':[{'aa':[1,2,3], 'bb':[4,5]}],'b':[{}]}\"`\n\t}\n\n\tbodyReader := strings.NewReader(`{\n\t\t\"X\": {\n\t\t\t\"a\": [\"a1\",\"a2\"]\n\t\t},\n\t\t\"Z\": 6\n\t}`)\n\n\t// var nilMap map[string]string\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", consts.MIMEApplicationJSON)\n\treq := newRequest(\"\", header, nil, bodyReader)\n\trecv := new(Recv)\n\n\tparam2 := param.Params{\n\t\t{\n\t\t\tKey:   \"e\",\n\t\t\tValue: \"123\",\n\t\t},\n\t}\n\n\terr := DefaultBinder().Bind(req.Req, recv, param2)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, []string{\"a1\", \"a2\"}, (**recv.X).A)\n\tassert.DeepEqual(t, int32(32), (**recv.X).B)\n\tassert.DeepEqual(t, true, (**recv.X).C)\n\tassert.DeepEqual(t, float32(123.4), *(**recv.X).D)\n\tassert.DeepEqual(t, []string{\"a\", \"b\", \"c\", \"d,e,f\"}, *(**recv.X).E)\n\tassert.DeepEqual(t, map[string]string{\"a\": \"\\\"'1\", \"\\\"b\": \"c\", \"c\": \"2\"}, (**recv.X).F)\n\tassert.DeepEqual(t, map[string]int64{\"a\": 1, \"b\": 2, \"c\": 3}, (**recv.X).G)\n\tassert.DeepEqual(t, map[string]float64{\"a\": 0.1, \"b\": 1.2, \"c\": 2.3}, (**recv.X).H)\n\tassert.DeepEqual(t, map[string]float64{\"\\\"a\\\"\": 0.1, \"b\": 1.2, \"c\": 2.3}, (**recv.X).I)\n\tassert.DeepEqual(t, \"\", (**recv.X).Empty)\n\tassert.DeepEqual(t, \"\", (**recv.X).Null)\n\tassert.DeepEqual(t, \",a:c \", (**recv.X).CommaSpace)\n\tassert.DeepEqual(t, \"-\", (**recv.X).Dash)\n\t// assert.DeepEqual(t, 0, (**recv.X).InvalidInt)\n\t// assert.DeepEqual(t, nilMap, (**recv.X).InvalidMap)\n\tassert.DeepEqual(t, \"y1\", recv.Y)\n\tassert.DeepEqual(t, \"t1\", *recv.T)\n\tassert.DeepEqual(t, int64(6), recv.Z)\n\tassert.DeepEqual(t, []int64{1, 2, 3}, recv.V)\n\tassert.DeepEqual(t, []float32{1.1, 2, 3}, recv.U)\n\tassert.DeepEqual(t, S{SS: \"test\"}, recv.S)\n\tassert.DeepEqual(t, &S{SS: \"test2\"}, recv.O)\n\tassert.DeepEqual(t, map[string][]map[string][]int64{\"a\": {{\"aa\": {1, 2, 3}, \"bb\": []int64{4, 5}}}, \"b\": {map[string][]int64{}}}, recv.Complex)\n}\n\nfunc TestAuto(t *testing.T) {\n\ttype Recv struct {\n\t\tA string\n\t\tB string\n\t\tC string\n\t\tD string `query:\"D,required\" form:\"D,required\"`\n\t\tE string `cookie:\"e\" json:\"e\"`\n\t}\n\tquery := make(url.Values)\n\tquery.Add(\"A\", \"a\")\n\tquery.Add(\"B\", \"b\")\n\tquery.Add(\"C\", \"c\")\n\tquery.Add(\"D\", \"d-from-query\")\n\tcontentType, bodyReader, err := newJSONBody(map[string]string{\"e\": \"e-from-jsonbody\"})\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", contentType)\n\treq := newRequest(\"http://localhost/?\"+query.Encode(), header, []*http.Cookie{\n\t\t{Name: \"e\", Value: \"e-from-cookie\"},\n\t}, bodyReader)\n\trecv := new(Recv)\n\n\terr = DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"a\", recv.A)\n\tassert.DeepEqual(t, \"b\", recv.B)\n\tassert.DeepEqual(t, \"c\", recv.C)\n\tassert.DeepEqual(t, \"d-from-query\", recv.D)\n\tassert.DeepEqual(t, \"e-from-cookie\", recv.E)\n\n\tquery = make(url.Values)\n\tquery.Add(\"D\", \"d-from-query\")\n\tform := make(url.Values)\n\tform.Add(\"B\", \"b\")\n\tform.Add(\"C\", \"c\")\n\tform.Add(\"D\", \"d-from-form\")\n\tcontentType, bodyReader = newFormBody2(form, nil)\n\theader = make(http.Header)\n\theader.Set(\"Content-Type\", contentType)\n\treq = newRequest(\"http://localhost/?\"+query.Encode(), header, nil, bodyReader)\n\trecv = new(Recv)\n\terr = DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"\", recv.A)\n\tassert.DeepEqual(t, \"b\", recv.B)\n\tassert.DeepEqual(t, \"c\", recv.C)\n\tassert.DeepEqual(t, \"d-from-form\", recv.D)\n}\n\nfunc TestTypeUnmarshal(t *testing.T) {\n\ttype Recv struct {\n\t\tA time.Time   `form:\"t1\"`\n\t\tB *time.Time  `query:\"t2\"`\n\t\tC []time.Time `query:\"t2\"`\n\t}\n\tquery := make(url.Values)\n\tquery.Add(\"t2\", \"2019-09-04T14:05:24+08:00\")\n\tquery.Add(\"t2\", \"2019-09-04T18:05:24+08:00\")\n\tform := make(url.Values)\n\tform.Add(\"t1\", \"2019-09-03T18:05:24+08:00\")\n\tcontentType, bodyReader := newFormBody2(form, nil)\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", contentType)\n\treq := newRequest(\"http://localhost/?\"+query.Encode(), header, nil, bodyReader)\n\trecv := new(Recv)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tt1, err := time.Parse(time.RFC3339, \"2019-09-03T18:05:24+08:00\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, t1, recv.A)\n\tt21, err := time.Parse(time.RFC3339, \"2019-09-04T14:05:24+08:00\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, t21, *recv.B)\n\tt22, err := time.Parse(time.RFC3339, \"2019-09-04T18:05:24+08:00\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, []time.Time{t21, t22}, recv.C)\n\tt.Logf(\"%v\", recv)\n}\n\n// test: required\nfunc TestOption(t *testing.T) {\n\ttype Recv struct {\n\t\tX *struct {\n\t\t\tC int `json:\"c,required\"`\n\t\t\tD int `json:\"d\"`\n\t\t} `json:\"X\"`\n\t\tY string `json:\"y\"`\n\t}\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", consts.MIMEApplicationJSON)\n\n\tbodyReader := strings.NewReader(`{\n\t\t\t\"X\": {\n\t\t\t\t\"c\": 21,\n\t\t\t\t\"d\": 41\n\t\t\t},\n\t\t\t\"y\": \"y1\"\n\t\t}`)\n\treq := newRequest(\"\", header, nil, bodyReader)\n\trecv := new(Recv)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 21, recv.X.C)\n\tassert.DeepEqual(t, 41, recv.X.D)\n\tassert.DeepEqual(t, \"y1\", recv.Y)\n\n\tbodyReader = strings.NewReader(`{\n\t\t\t\"X\": {\n\t\t\t},\n\t\t\t\"y\": \"y1\"\n\t\t}`)\n\treq = newRequest(\"\", header, nil, bodyReader)\n\trecv = new(Recv)\n\terr = DefaultBinder().Bind(req.Req, recv, nil)\n\tassert.DeepEqual(t, err.Error(), \"'c' field is a 'required' parameter, but the request body does not have this parameter 'X.c'\")\n\tassert.DeepEqual(t, 0, recv.X.C)\n\tassert.DeepEqual(t, 0, recv.X.D)\n\tassert.DeepEqual(t, \"y1\", recv.Y)\n\n\tbodyReader = strings.NewReader(`{\n\t\t\t\"y\": \"y1\"\n\t\t}`)\n\treq = newRequest(\"\", header, nil, bodyReader)\n\trecv = new(Recv)\n\terr = DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.True(t, recv.X == nil)\n\tassert.DeepEqual(t, \"y1\", recv.Y)\n\n\ttype Recv2 struct {\n\t\tX *struct {\n\t\t\tC int `json:\"c,required\"`\n\t\t\tD int `json:\"d\"`\n\t\t} `json:\"X,required\"`\n\t\tY string `json:\"y\"`\n\t}\n\tbodyReader = strings.NewReader(`{\n\t\t\t\"y\": \"y1\"\n\t\t}`)\n\treq = newRequest(\"\", header, nil, bodyReader)\n\trecv2 := new(Recv2)\n\tbindConfig := &BindConfig{}\n\tbindConfig.DisableStructFieldResolve = false\n\tbinder := NewDefaultBinder(bindConfig)\n\terr = binder.Bind(req.Req, recv2, nil)\n\tassert.DeepEqual(t, err.Error(), \"'X' field is a 'required' parameter, but the request does not have this parameter\")\n\tassert.True(t, recv2.X == nil)\n\tassert.DeepEqual(t, \"y1\", recv2.Y)\n}\n\nfunc newRequest(u string, header http.Header, cookies []*http.Cookie, bodyReader io.Reader) *mockRequest {\n\tif header == nil {\n\t\theader = make(http.Header)\n\t}\n\tmethod := \"GET\"\n\tvar body []byte\n\tif bodyReader != nil {\n\t\tbody, _ = ioutil.ReadAll(bodyReader)\n\t\tmethod = \"POST\"\n\t}\n\tif u == \"\" {\n\t\tu = \"http://localhost\"\n\t}\n\treq := newMockRequest()\n\treq.SetRequestURI(u)\n\tfor k, v := range header {\n\t\tfor _, val := range v {\n\t\t\treq.Req.Header.Add(k, val)\n\t\t}\n\t}\n\tif len(body) != 0 {\n\t\treq.SetBody(body)\n\t\treq.Req.Header.SetContentLength(len(body))\n\t}\n\treq.Req.SetMethod(method)\n\tfor _, c := range cookies {\n\t\treq.Req.Header.SetCookie(c.Name, c.Value)\n\t}\n\treturn req\n}\n\nfunc TestQueryStringIssue(t *testing.T) {\n\ttype Timestamp struct {\n\t\ttime.Time\n\t}\n\ttype Recv struct {\n\t\tName *string    `query:\"name\"`\n\t\tT    *Timestamp `query:\"t\"`\n\t}\n\treq := newRequest(\"http://localhost:8080/?name=test\", nil, nil, nil)\n\trecv := new(Recv)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"test\", *recv.Name)\n\t// DIFF: the type with customized decoder must be a non-nil value\n\t// assert.DeepEqual(t, (*Timestamp)(nil), recv.T)\n}\n\nfunc TestQueryTypes(t *testing.T) {\n\ttype metric string\n\ttype count int32\n\ttype metrics []string\n\ttype filter struct {\n\t\tCol1 string\n\t}\n\n\ttype Recv struct {\n\t\tA metric\n\t\tB count\n\t\tC *count\n\t\tD metrics `query:\"D,required\" form:\"D,required\"`\n\t\tE metric  `cookie:\"e\" json:\"e\"`\n\t\tF filter  `json:\"f\"`\n\t}\n\tquery := make(url.Values)\n\tquery.Add(\"A\", \"qps\")\n\tquery.Add(\"B\", \"123\")\n\tquery.Add(\"C\", \"321\")\n\tquery.Add(\"D\", \"dau\")\n\tquery.Add(\"D\", \"dnu\")\n\tcontentType, bodyReader, err := newJSONBody(\n\t\tmap[string]interface{}{\n\t\t\t\"e\": \"e-from-jsonbody\",\n\t\t\t\"f\": filter{Col1: \"abc\"},\n\t\t},\n\t)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", contentType)\n\treq := newRequest(\"http://localhost/?\"+query.Encode(), header, []*http.Cookie{\n\t\t{Name: \"e\", Value: \"e-from-cookie\"},\n\t}, bodyReader)\n\trecv := new(Recv)\n\n\terr = DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, metric(\"qps\"), recv.A)\n\tassert.DeepEqual(t, count(123), recv.B)\n\tassert.DeepEqual(t, count(321), *recv.C)\n\tassert.DeepEqual(t, metrics{\"dau\", \"dnu\"}, recv.D)\n\tassert.DeepEqual(t, metric(\"e-from-cookie\"), recv.E)\n\tassert.DeepEqual(t, filter{Col1: \"abc\"}, recv.F)\n}\n\nfunc TestNoTagIssue(t *testing.T) {\n\ttype x int\n\ttype T struct {\n\t\tx\n\t\tx2 x\n\t\ta  int\n\t\tB  int\n\t}\n\treq := newRequest(\"http://localhost:8080/?x=11&x2=12&a=1&B=2\", nil, nil, nil)\n\trecv := new(T)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, x(0), recv.x)\n\tassert.DeepEqual(t, x(0), recv.x2)\n\tassert.DeepEqual(t, 0, recv.a)\n\tassert.DeepEqual(t, 2, recv.B)\n}\n\nfunc TestRegTypeUnmarshal(t *testing.T) {\n\ttype Q struct {\n\t\tA int\n\t\tB string\n\t}\n\ttype T struct {\n\t\tQ   Q       `query:\"q\"`\n\t\tQs  []*Q    `query:\"qs\"`\n\t\tQs2 ***[]*Q `query:\"qs\"`\n\t}\n\tvalues := url.Values{}\n\tb, err := json.Marshal(Q{A: 2, B: \"y\"})\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tvalues.Add(\"q\", string(b))\n\tbs, _ := json.Marshal([]Q{{A: 1, B: \"x\"}, {A: 2, B: \"y\"}})\n\tvalues.Add(\"qs\", string(bs))\n\treq := newRequest(\"http://localhost:8080/?\"+values.Encode(), nil, nil, nil)\n\trecv := new(T)\n\n\tbindConfig := &BindConfig{}\n\tbindConfig.DisableStructFieldResolve = false\n\tbinder := NewDefaultBinder(bindConfig)\n\terr = binder.Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, 2, recv.Q.A)\n\tassert.DeepEqual(t, \"y\", recv.Q.B)\n\tassert.DeepEqual(t, 1, recv.Qs[0].A)\n\tassert.DeepEqual(t, \"x\", recv.Qs[0].B)\n\tassert.DeepEqual(t, 2, recv.Qs[1].A)\n\tassert.DeepEqual(t, \"y\", recv.Qs[1].B)\n\tassert.DeepEqual(t, 1, (***recv.Qs2)[0].A)\n\tassert.DeepEqual(t, \"x\", (***recv.Qs2)[0].B)\n\tassert.DeepEqual(t, 2, (***recv.Qs2)[1].A)\n\tassert.DeepEqual(t, \"y\", (***recv.Qs2)[1].B)\n}\n\nfunc TestPathnameBUG(t *testing.T) {\n\ttype Currency struct {\n\t\tCurrencyName   *string `form:\"currency_name,required\" json:\"currency_name,required\" protobuf:\"bytes,1,req,name=currency_name,json=currencyName\" query:\"currency_name,required\"`\n\t\tCurrencySymbol *string `form:\"currency_symbol,required\" json:\"currency_symbol,required\" protobuf:\"bytes,2,req,name=currency_symbol,json=currencySymbol\" query:\"currency_symbol,required\"`\n\t\tSymbolPosition *int32  `form:\"symbol_position,required\" json:\"symbol_position,required\" protobuf:\"varint,3,req,name=symbol_position,json=symbolPosition\" query:\"symbol_position,required\"`\n\t\tDecimalPlaces  *int32  `form:\"decimal_places,required\" json:\"decimal_places,required\" protobuf:\"varint,4,req,name=decimal_places,json=decimalPlaces\" query:\"decimal_places,required\"` // 56x56\n\t\tDecimalSymbol  *string `form:\"decimal_symbol,required\" json:\"decimal_symbol,required\" protobuf:\"bytes,5,req,name=decimal_symbol,json=decimalSymbol\" query:\"decimal_symbol,required\"`\n\t\tSeparator      *string `form:\"separator,required\" json:\"separator,required\" protobuf:\"bytes,6,req,name=separator\" query:\"separator,required\"`\n\t\tSeparatorIndex *string `form:\"separator_index,required\" json:\"separator_index,required\" protobuf:\"bytes,7,req,name=separator_index,json=separatorIndex\" query:\"separator_index,required\"`\n\t\tBetween        *string `form:\"between,required\" json:\"between,required\" protobuf:\"bytes,8,req,name=between\" query:\"between,required\"`\n\t\tMinPrice       *string `form:\"min_price\" json:\"min_price,omitempty\" protobuf:\"bytes,9,opt,name=min_price,json=minPrice\" query:\"min_price\"`\n\t\tMaxPrice       *string `form:\"max_price\" json:\"max_price,omitempty\" protobuf:\"bytes,10,opt,name=max_price,json=maxPrice\" query:\"max_price\"`\n\t}\n\n\ttype CurrencyData struct {\n\t\tAmount   *string   `form:\"amount,required\" json:\"amount,required\" protobuf:\"bytes,1,req,name=amount\" query:\"amount,required\"`\n\t\tCurrency *Currency `form:\"currency,required\" json:\"currency,required\" protobuf:\"bytes,2,req,name=currency\" query:\"currency,required\"`\n\t}\n\n\ttype ExchangeCurrencyRequest struct {\n\t\tPromotionRegion *string       `form:\"promotion_region,required\" json:\"promotion_region,required\" protobuf:\"bytes,1,req,name=promotion_region,json=promotionRegion\" query:\"promotion_region,required\"`\n\t\tCurrency        *CurrencyData `form:\"currency,required\" json:\"currency,required\" protobuf:\"bytes,2,req,name=currency\" query:\"currency,required\"`\n\t\tVersion         *int32        `json:\"version,omitempty\" path:\"version\" protobuf:\"varint,100,opt,name=version\"`\n\t}\n\n\tz := new(ExchangeCurrencyRequest)\n\tz.Currency = new(CurrencyData)\n\tz.Currency.Currency = new(Currency)\n\tz.PromotionRegion = proto.String(\"?\")\n\tz.Version = proto.Int32(-32)\n\tz.Currency.Amount = proto.String(\"?\")\n\tz.Currency.Currency.CurrencyName = proto.String(\"?\")\n\tz.Currency.Currency.CurrencySymbol = proto.String(\"?\")\n\tz.Currency.Currency.SymbolPosition = proto.Int32(-32)\n\tz.Currency.Currency.DecimalPlaces = proto.Int32(-32)\n\tz.Currency.Currency.DecimalSymbol = proto.String(\"?\")\n\tz.Currency.Currency.Separator = proto.String(\"?\")\n\tz.Currency.Currency.Between = proto.String(\"?\")\n\tz.Currency.Currency.MinPrice = proto.String(\"?\")\n\tz.Currency.Currency.MaxPrice = proto.String(\"?\")\n\n\tb, err := json.MarshalIndent(z, \"\", \"  \")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", \"application/json;charset=utf-8\")\n\treq := newRequest(\"http://localhost\", header, nil, bytes.NewReader(b))\n\trecv := new(ExchangeCurrencyRequest)\n\n\terr = DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\n// test: required\nfunc TestPathnameBUG2(t *testing.T) {\n\ttype CurrencyData struct {\n\t\tAmount *string `form:\"amount,required\" json:\"amount,required\" protobuf:\"bytes,1,req,name=amount\" query:\"amount,required\"`\n\t\tName   *string `form:\"name,required\" json:\"name,required\" protobuf:\"bytes,2,req,name=name\" query:\"name,required\"`\n\t\tSymbol *string `form:\"symbol\" json:\"symbol,omitempty\" protobuf:\"bytes,3,opt,name=symbol\" query:\"symbol\"`\n\t}\n\ttype TimeRange struct {\n\t\tStartTime *int64 `form:\"start_time,required\" json:\"start_time,required\" protobuf:\"varint,1,req,name=start_time,json=startTime\" query:\"start_time,required\"`\n\t\tEndTime   *int64 `form:\"end_time,required\" json:\"end_time,required\" protobuf:\"varint,2,req,name=end_time,json=endTime\" query:\"end_time,required\"`\n\t}\n\ttype CreateFreeShippingRequest struct {\n\t\tPromotionName    *string       `form:\"promotion_name,required\" json:\"promotion_name,required\" protobuf:\"bytes,1,req,name=promotion_name,json=promotionName\" query:\"promotion_name,required\"`\n\t\tPromotionRegion  *string       `form:\"promotion_region,required\" json:\"promotion_region,required\" protobuf:\"bytes,2,req,name=promotion_region,json=promotionRegion\" query:\"promotion_region,required\"`\n\t\tTimeRange        *TimeRange    `form:\"time_range,required\" json:\"time_range,required\" protobuf:\"bytes,3,req,name=time_range,json=timeRange\" query:\"time_range,required\"`\n\t\tPromotionBudget  *CurrencyData `form:\"promotion_budget,required\" json:\"promotion_budget,required\" protobuf:\"bytes,4,req,name=promotion_budget,json=promotionBudget\" query:\"promotion_budget,required\"`\n\t\tLoaded_SellerIds []string      `form:\"loaded_Seller_ids\" json:\"loaded_Seller_ids,omitempty\" protobuf:\"bytes,5,rep,name=loaded_Seller_ids,json=loadedSellerIds\" query:\"loaded_Seller_ids\"`\n\t\tVersion          *int32        `json:\"version,omitempty\" path:\"version\" protobuf:\"varint,100,opt,name=version\"`\n\t}\n\n\t// z := &CreateFreeShippingRequest{}\n\t// v := ameda.InitSampleValue(reflect.TypeOf(z), 10).Interface().(*CreateFreeShippingRequest)\n\t// b, err := json.MarshalIndent(v, \"\", \"  \")\n\t// t.Log(string(b))\n\tb := []byte(`{\n    \"promotion_name\": \"mu\",\n    \"promotion_region\": \"ID\",\n    \"time_range\": {\n        \"start_time\": 1616420139,\n        \"end_time\": 1616520139\n    },\n    \"promotion_budget\": {\n        \"amount\":\"10000000\",\n        \"name\":\"USD\",\n        \"symbol\":\"$\"\n    },\n    \"loaded_Seller_ids\": [\n        \"7493989780026655762\",\"11111\",\"111212121\"\n    ]\n}`)\n\tv := new(CreateFreeShippingRequest)\n\terr := json.Unmarshal(b, v)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", \"application/json;charset=utf-8\")\n\treq := newRequest(\"http://localhost\", header, nil, bytes.NewReader(b))\n\trecv := new(CreateFreeShippingRequest)\n\n\terr = DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tassert.DeepEqual(t, v, recv)\n}\n\nfunc TestRequiredBUG(t *testing.T) {\n\ttype Currency struct {\n\t\t// currencyName   *string `form:\"currency_name,required\" json:\"currency_name,required\" protobuf:\"bytes,1,req,name=currency_name,json=currencyName\" query:\"currency_name,required\"`\n\t\tCurrencySymbol *string `form:\"currency_symbol,required\" json:\"currency_symbol,required\" protobuf:\"bytes,2,req,name=currency_symbol,json=currencySymbol\" query:\"currency_symbol,required\"`\n\t}\n\n\ttype CurrencyData struct {\n\t\tAmount *string              `form:\"amount,required\" json:\"amount,required\" protobuf:\"bytes,1,req,name=amount\" query:\"amount,required\"`\n\t\tSlice  []*Currency          `form:\"slice,required\" json:\"slice,required\" protobuf:\"bytes,2,req,name=slice\" query:\"slice,required\"`\n\t\tMap    map[string]*Currency `form:\"map,required\" json:\"map,required\" protobuf:\"bytes,2,req,name=map\" query:\"map,required\"`\n\t}\n\n\ttype ExchangeCurrencyRequest struct {\n\t\tPromotionRegion *string       `form:\"promotion_region,required\" json:\"promotion_region,required\" protobuf:\"bytes,1,req,name=promotion_region,json=promotionRegion\" query:\"promotion_region,required\"`\n\t\tCurrency        *CurrencyData `form:\"currency,required\" json:\"currency,required\" protobuf:\"bytes,2,req,name=currency\" query:\"currency,required\"`\n\t}\n\n\tz := &ExchangeCurrencyRequest{}\n\tb := []byte(`{\n          \"promotion_region\": \"?\",\n          \"currency\": {\n            \"amount\": \"?\",\n            \"slice\": [\n              {\n                \"currency_symbol\": \"?\"\n              }\n            ],\n            \"map\": {\n              \"?\": {\n                \"currency_name\": \"?\"\n              }\n            }\n          }\n        }`)\n\tjson.Unmarshal(b, z)\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", \"application/json;charset=utf-8\")\n\treq := newRequest(\"http://localhost\", header, nil, bytes.NewReader(b))\n\trecv := new(ExchangeCurrencyRequest)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\t// no need for validate\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, z, recv)\n}\n\nfunc TestIssue25(t *testing.T) {\n\ttype Recv struct {\n\t\tA string\n\t}\n\theader := make(http.Header)\n\theader.Set(\"A\", \"from header\")\n\tcookies := []*http.Cookie{\n\t\t{Name: \"A\", Value: \"from cookie\"},\n\t}\n\treq := newRequest(\"/1\", header, cookies, nil)\n\trecv := new(Recv)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\t// assert.DeepEqual(t, \"from cookie\", recv.A)\n\n\theader2 := make(http.Header)\n\theader2.Set(\"A\", \"from header\")\n\tcookies2 := []*http.Cookie{}\n\treq2 := newRequest(\"/2\", header2, cookies2, nil)\n\trecv2 := new(Recv)\n\terr2 := DefaultBinder().Bind(req2.Req, recv2, nil)\n\tif err2 != nil {\n\t\tt.Error(err2)\n\t}\n\tassert.DeepEqual(t, \"from header\", recv2.A)\n}\n\nfunc TestIssue26(t *testing.T) {\n\ttype Recv struct {\n\t\tType            string `json:\"type,required\" vd:\"($=='update_target_threshold' && (TargetThreshold)$!='-1') || ($=='update_status' && (Status)$!='-1')\"`\n\t\tRuleName        string `json:\"rule_name,required\" vd:\"regexp('^rule[0-9]+$')\"`\n\t\tTargetThreshold string `json:\"target_threshold\" vd:\"regexp('^-?[0-9]+(\\\\.[0-9]+)?$')\"`\n\t\tStatus          string `json:\"status\" vd:\"$=='0' || $=='1'\"`\n\t\tOperator        string `json:\"operator,required\" vd:\"len($)>0\"`\n\t}\n\n\tb := []byte(`{\n    \"status\": \"1\",\n    \"adv\": \"11520\",\n    \"target_deep_external_action\": \"39\",\n    \"package\": \"test.bytedance.com\",\n    \"previous_target_threshold\": \"0.6\",\n    \"deep_external_action\": \"675\",\n    \"rule_name\": \"rule2\",\n    \"deep_bid_type\": \"54\",\n    \"modify_time\": \"2021-08-24:14:35:20\",\n    \"aid\": \"111\",\n    \"operator\": \"yanghaoze\",\n    \"external_action\": \"76\",\n    \"target_threshold\": \"0.1\",\n    \"type\": \"update_status\"\n}`)\n\n\trecv := new(Recv)\n\terr := json.Unmarshal(b, recv)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", consts.MIMEApplicationJSON)\n\theader.Set(\"A\", \"from header\")\n\tcookies := []*http.Cookie{\n\t\t{Name: \"A\", Value: \"from cookie\"},\n\t}\n\n\treq := newRequest(\"/1\", header, cookies, bytes.NewReader(b))\n\n\trecv2 := new(Recv)\n\terr = DefaultBinder().Bind(req.Req, recv2, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, recv, recv2)\n}\n\n// BUGFIX: after 'json unmarshal', the default value will change it\nfunc TestDefault2(t *testing.T) {\n\ttype Recv struct {\n\t\tX **struct {\n\t\t\tDash string `default:\"xxxx\"`\n\t\t}\n\t}\n\tbodyReader := strings.NewReader(`{\n\t\t\"X\": {\n\t\t\t\"Dash\": \"hello Dash\"\n\t\t}\n\t}`)\n\theader := make(http.Header)\n\theader.Set(\"Content-Type\", consts.MIMEApplicationJSON)\n\treq := newRequest(\"\", header, nil, bodyReader)\n\trecv := new(Recv)\n\n\terr := DefaultBinder().Bind(req.Req, recv, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.DeepEqual(t, \"hello Dash\", (**recv.X).Dash)\n}\n\ntype (\n\tfiles map[string][]file\n\tfile  interface {\n\t\tName() string\n\t\tRead(p []byte) (n int, err error)\n\t}\n)\n\nfunc newFormBody2(values url.Values, files files) (contentType string, bodyReader io.Reader) {\n\tif len(files) == 0 {\n\t\treturn \"application/x-www-form-urlencoded\", strings.NewReader(values.Encode())\n\t}\n\tpr, pw := io.Pipe()\n\tbodyWriter := multipart.NewWriter(pw)\n\tvar fileWriter io.Writer\n\tbuf := make([]byte, 32*1024)\n\tgo func() {\n\t\tfor fieldName, postfiles := range files {\n\t\t\tfor _, file := range postfiles {\n\t\t\t\tfileWriter, _ = bodyWriter.CreateFormFile(fieldName, file.Name())\n\t\t\t\tio.CopyBuffer(fileWriter, file, buf)\n\t\t\t}\n\t\t}\n\t\tfor k, v := range values {\n\t\t\tfor _, vv := range v {\n\t\t\t\tbodyWriter.WriteField(k, vv)\n\t\t\t}\n\t\t}\n\t\tbodyWriter.Close()\n\t\tpw.Close()\n\t}()\n\treturn bodyWriter.FormDataContentType(), pr\n}\n\nfunc newFile(name string, bodyReader io.Reader) file {\n\treturn &fileReader{name, bodyReader}\n}\n\n// fileReader file name and bytes.\ntype fileReader struct {\n\tname       string\n\tbodyReader io.Reader\n}\n\nfunc (f *fileReader) Name() string {\n\treturn f.name\n}\n\nfunc (f *fileReader) Read(p []byte) (int, error) {\n\treturn f.bodyReader.Read(p)\n}\n\nfunc newJSONBody(v interface{}) (contentType string, bodyReader io.Reader, err error) {\n\tb, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn\n\t}\n\treturn \"application/json;charset=utf-8\", bytes.NewReader(b), nil\n}\n"
  },
  {
    "path": "pkg/app/server/binding/testdata/hello.pb.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.30.0\n// \tprotoc        v3.21.12\n// source: hello.proto\n\npackage testdata\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype HertzReq struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName string `protobuf:\"bytes,1,opt,name=Name,proto3\" json:\"Name,omitempty\"`\n}\n\nfunc (x *HertzReq) Reset() {\n\t*x = HertzReq{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_hello_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *HertzReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HertzReq) ProtoMessage() {}\n\nfunc (x *HertzReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_hello_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use HertzReq.ProtoReflect.Descriptor instead.\nfunc (*HertzReq) Descriptor() ([]byte, []int) {\n\treturn file_hello_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *HertzReq) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nvar File_hello_proto protoreflect.FileDescriptor\n\nvar file_hello_proto_rawDesc = []byte{\n\t0x0a, 0x0b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x68,\n\t0x65, 0x72, 0x74, 0x7a, 0x22, 0x1e, 0x0a, 0x08, 0x48, 0x65, 0x72, 0x74, 0x7a, 0x52, 0x65, 0x71,\n\t0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x4e, 0x61, 0x6d, 0x65, 0x42, 0x0d, 0x5a, 0x0b, 0x68, 0x65, 0x72, 0x74, 0x7a, 0x2f, 0x68, 0x65,\n\t0x6c, 0x6c, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_hello_proto_rawDescOnce sync.Once\n\tfile_hello_proto_rawDescData = file_hello_proto_rawDesc\n)\n\nfunc file_hello_proto_rawDescGZIP() []byte {\n\tfile_hello_proto_rawDescOnce.Do(func() {\n\t\tfile_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_hello_proto_rawDescData)\n\t})\n\treturn file_hello_proto_rawDescData\n}\n\nvar file_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_hello_proto_goTypes = []interface{}{\n\t(*HertzReq)(nil), // 0: hertz.HertzReq\n}\nvar file_hello_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_hello_proto_init() }\nfunc file_hello_proto_init() {\n\tif File_hello_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*HertzReq); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_hello_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_hello_proto_goTypes,\n\t\tDependencyIndexes: file_hello_proto_depIdxs,\n\t\tMessageInfos:      file_hello_proto_msgTypes,\n\t}.Build()\n\tFile_hello_proto = out.File\n\tfile_hello_proto_rawDesc = nil\n\tfile_hello_proto_goTypes = nil\n\tfile_hello_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/app/server/binding/testdata/hello.proto",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nsyntax = \"proto3\";\npackage hertz;\noption go_package = \"hertz/hello\";\n\nmessage HertzReq {\n  string Name = 1;\n}\n\n"
  },
  {
    "path": "pkg/app/server/binding/validator.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2023 CloudWeGo Authors\n */\n\npackage binding\n\nimport (\n\t\"reflect\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\n// ValidatorFunc defines a validation function that can access request context.\n// It takes a request and the object to validate, returning an error if validation fails.\ntype ValidatorFunc func(*protocol.Request, interface{}) error\n\n// StructValidator defines the interface for struct validation.\n//\n// Deprecated: Use ValidatorFunc in BindConfig instead. You can create a ValidatorFunc\n// from a StructValidator using MakeValidatorFunc().\ntype StructValidator interface {\n\tValidateStruct(interface{}) error\n\tEngine() interface{}\n\tValidateTag() string\n}\n\n// hasValidateTagCache caches whether a type has validation tags to avoid\n// redundant reflection-based tag analysis on repeated validations\nvar hasValidateTagCache sync.Map\n\n// MakeValidatorFunc creates a validation function from a StructValidator.\n// It optimizes validation by caching tag analysis results and skipping\n// validation entirely for types that don't have validation tags.\nfunc MakeValidatorFunc(s StructValidator) ValidatorFunc {\n\tif s == nil {\n\t\treturn nil\n\t}\n\treturn func(_ *protocol.Request, v any) error {\n\t\trv, typeID := valueAndTypeID(v)\n\t\tc, ok := hasValidateTagCache.Load(typeID)\n\t\tif ok {\n\t\t\tif !c.(bool) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn s.ValidateStruct(rv)\n\t\t}\n\t\ttag := s.ValidateTag()\n\t\tif tag == \"\" {\n\t\t\ttag = defaultValidateTag\n\t\t}\n\t\thasTag := containsStructTag(rv.Type(), tag, nil)\n\t\thasValidateTagCache.Store(typeID, hasTag)\n\t\tif !hasTag {\n\t\t\treturn nil\n\t\t}\n\t\treturn s.ValidateStruct(rv)\n\t}\n}\n\n// containsStructTag recursively checks if a struct type contains any field with the specified tag.\n// It uses a checking map to prevent infinite recursion in self-referential struct types.\nfunc containsStructTag(rt reflect.Type, tag string, checking map[reflect.Type]bool) bool {\n\trt = dereferenceType(rt)\n\tif rt.Kind() != reflect.Struct {\n\t\treturn false\n\t}\n\tif checking == nil {\n\t\tchecking = map[reflect.Type]bool{}\n\t}\n\tchecking[rt] = true\n\tfor i := 0; i < rt.NumField(); i++ {\n\t\tf := rt.Field(i)\n\t\t_, ok := f.Tag.Lookup(tag)\n\t\tif ok {\n\t\t\treturn true\n\t\t}\n\t\tft := dereferenceType(f.Type)\n\t\tif checking[ft] {\n\t\t\tcontinue\n\t\t}\n\t\tif containsStructTag(ft, tag, checking) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/app/server/binding/validator_test.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage binding\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc Test_ValidateStruct(t *testing.T) {\n\ttype User struct {\n\t\tAge int `vd:\"$>=0&&$<=130\"`\n\t}\n\n\tuser := &User{\n\t\tAge: 135,\n\t}\n\terr := DefaultValidator().ValidateStruct(user)\n\tif err == nil {\n\t\tt.Fatalf(\"expected an error, but got nil\")\n\t}\n}\n\nfunc Test_ValidateTag(t *testing.T) {\n\ttype User struct {\n\t\tAge int `query:\"age\" vt:\"$>=0&&$<=130\"`\n\t}\n\n\tuser := &User{\n\t\tAge: 135,\n\t}\n\tvalidateConfig := NewValidateConfig()\n\tvalidateConfig.ValidateTag = \"vt\"\n\tvd := NewValidator(validateConfig)\n\terr := vd.ValidateStruct(user)\n\tif err == nil {\n\t\tt.Fatalf(\"expected an error, but got nil\")\n\t}\n\n\tbindConfig := NewBindConfig()\n\tbindConfig.Validator = vd\n\tbinder := NewDefaultBinder(bindConfig)\n\tuser = &User{}\n\treq := newMockRequest().\n\t\tSetRequestURI(\"http://foobar.com?age=135\").\n\t\tSetHeaders(\"h\", \"header\")\n\terr = binder.Bind(req.Req, user, nil)\n\tassert.Nil(t, err)\n\terr = binder.Validate(req.Req, user)\n\tassert.NotNil(t, err)\n}\n"
  },
  {
    "path": "pkg/app/server/hertz.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage server\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/middlewares/server/recovery\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/route\"\n)\n\n// Hertz is the core struct of hertz.\ntype Hertz struct {\n\t*route.Engine\n\tsignalWaiter func(err chan error) error\n}\n\n// New creates a hertz instance without any default config.\nfunc New(opts ...config.Option) *Hertz {\n\toptions := config.NewOptions(opts)\n\th := &Hertz{\n\t\tEngine: route.NewEngine(options),\n\t}\n\treturn h\n}\n\n// Default creates a hertz instance with default middlewares.\nfunc Default(opts ...config.Option) *Hertz {\n\th := New(opts...)\n\th.Use(recovery.Recovery())\n\n\treturn h\n}\n\n// Spin runs the server until catching os.Signal or error returned by h.Run().\nfunc (h *Hertz) Spin() {\n\terrCh := make(chan error)\n\th.initOnRunHooks(errCh)\n\tgo func() {\n\t\terrCh <- h.Run()\n\t}()\n\n\tsignalWaiter := waitSignal\n\tif h.signalWaiter != nil {\n\t\tsignalWaiter = h.signalWaiter\n\t}\n\n\tif err := signalWaiter(errCh); err != nil {\n\t\thlog.SystemLogger().Errorf(\"Receive close signal: error=%v\", err)\n\t\tif err := h.Engine.Close(); err != nil {\n\t\t\thlog.SystemLogger().Errorf(\"Close error=%v\", err)\n\t\t}\n\t\treturn\n\t}\n\n\tif err := h.Shutdown(context.Background()); err != nil {\n\t\thlog.SystemLogger().Errorf(\"Shutdown error=%v\", err)\n\t}\n}\n\n// SetCustomSignalWaiter sets the signal waiter function.\n// If Default one is not met the requirement, set this function to customize.\n// Hertz will exit immediately if f returns an error, otherwise it will exit gracefully.\nfunc (h *Hertz) SetCustomSignalWaiter(f func(err chan error) error) {\n\th.signalWaiter = f\n}\n\n// Default implementation for signal waiter.\n// SIGHUP|SIGINT|SIGTERM triggers graceful shutdown.\nfunc waitSignal(errCh chan error) error {\n\tsignalToNotify := []os.Signal{syscall.SIGINT, syscall.SIGHUP, syscall.SIGTERM}\n\tif signal.Ignored(syscall.SIGHUP) {\n\t\tsignalToNotify = []os.Signal{syscall.SIGINT, syscall.SIGTERM}\n\t}\n\n\tsignals := make(chan os.Signal, 1)\n\tsignal.Notify(signals, signalToNotify...)\n\n\tselect {\n\tcase sig := <-signals:\n\t\tswitch sig {\n\t\tcase syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM:\n\t\t\thlog.SystemLogger().Infof(\"Received signal: %s\\n\", sig)\n\t\t\t// graceful shutdown\n\t\t\treturn nil\n\t\t}\n\tcase err := <-errCh:\n\t\t// error occurs, exit immediately\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (h *Hertz) initOnRunHooks(errChan chan error) {\n\t// add register func to runHooks\n\topt := h.GetOptions()\n\th.OnRun = append(h.OnRun, func(ctx context.Context) error {\n\t\tgo func() {\n\t\t\t// delay register 1s\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tif err := opt.Registry.Register(opt.RegistryInfo); err != nil {\n\t\t\t\thlog.SystemLogger().Errorf(\"Register error=%v\", err)\n\t\t\t\t// pass err to errChan\n\t\t\t\terrChan <- err\n\t\t\t}\n\t\t}()\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "pkg/app/server/hertz_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"path\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/test/mock/binder\"\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\tc \"github.com/cloudwego/hertz/pkg/app/client\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/binding\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/registry\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/req\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n)\n\ntype routeEngine interface {\n\tIsRunning() bool\n}\n\nfunc waitEngineRunning(e routeEngine) {\n\ttestutils.WaitEngineRunning(e)\n}\n\nfunc fullURL(ln net.Listener, p string) string {\n\treturn \"http://\" + path.Join(ln.Addr().String(), p)\n}\n\nfunc TestHertz_Run(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\thertz := Default(WithListener(ln))\n\thertz.GET(\"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t\ttime.Sleep(time.Second)\n\t\tpath := ctx.Request.URI().PathOriginal()\n\t\tctx.SetBodyString(string(path))\n\t})\n\n\ttestint := uint32(0)\n\thertz.Engine.OnShutdown = append(hertz.OnShutdown, func(ctx context.Context) {\n\t\tatomic.StoreUint32(&testint, 1)\n\t})\n\n\tassert.Assert(t, len(hertz.Handlers) == 1)\n\n\tgo hertz.Spin()\n\twaitEngineRunning(hertz)\n\n\thertz.Close()\n\ttime.Sleep(10 * time.Millisecond)\n\t// Close will not call OnShutdown\n\tassert.DeepEqual(t, uint32(0), atomic.LoadUint32(&testint))\n}\n\nfunc TestHertz_GracefulShutdown(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\thandling := make(chan struct{})\n\tclosing := make(chan struct{})\n\tengine := New(WithListener(ln))\n\tengine.GET(\"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t\tclose(handling)\n\t\t<-closing\n\t\tpath := ctx.Request.URI().PathOriginal()\n\t\tctx.SetBodyString(string(path))\n\t})\n\tengine.GET(\"/test2\", func(c context.Context, ctx *app.RequestContext) {})\n\n\ttestint := uint32(0)\n\ttestint2 := uint32(0)\n\ttestint3 := uint32(0)\n\tengine.Engine.OnShutdown = append(engine.OnShutdown, func(ctx context.Context) {\n\t\tatomic.StoreUint32(&testint, 1)\n\t})\n\tengine.Engine.OnShutdown = append(engine.OnShutdown, func(ctx context.Context) {\n\t\tatomic.StoreUint32(&testint2, 2)\n\t})\n\tengine.Engine.OnShutdown = append(engine.OnShutdown, func(ctx context.Context) {\n\t\tatomic.StoreUint32(&testint3, 3)\n\t})\n\n\tgo engine.Spin()\n\twaitEngineRunning(engine)\n\n\thc := http.Client{Timeout: time.Second}\n\tvar err error\n\tvar resp *http.Response\n\tch := make(chan struct{})\n\tch2 := make(chan struct{})\n\tgo func() {\n\t\tticker := time.NewTicker(10 * time.Millisecond)\n\t\tdefer ticker.Stop()\n\t\tfor range ticker.C {\n\t\t\tt.Logf(\"[%v]begin listening\\n\", time.Now())\n\t\t\t_, err2 := hc.Get(fullURL(ln, \"/test2\"))\n\t\t\tif err2 != nil {\n\t\t\t\tt.Logf(\"[%v]listening closed: %v\", time.Now(), err2)\n\t\t\t\tch2 <- struct{}{}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\tgo func() {\n\t\tt.Logf(\"[%v]begin request\\n\", time.Now())\n\t\tresp, err = http.Get(fullURL(ln, \"/test\"))\n\t\tt.Logf(\"[%v]end request\\n\", time.Now())\n\t\tch <- struct{}{}\n\t}()\n\n\t<-handling\n\n\tstart := time.Now()\n\tctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)\n\tt.Logf(\"[%v]begin shutdown\\n\", start)\n\tengine.Shutdown(ctx)\n\tend := time.Now()\n\tt.Logf(\"[%v]end shutdown\\n\", end)\n\n\tclose(closing)\n\t<-ch\n\tassert.Nil(t, err)\n\tassert.NotNil(t, resp)\n\tassert.DeepEqual(t, true, resp.Close)\n\tassert.DeepEqual(t, uint32(1), atomic.LoadUint32(&testint))\n\tassert.DeepEqual(t, uint32(2), atomic.LoadUint32(&testint2))\n\tassert.DeepEqual(t, uint32(3), atomic.LoadUint32(&testint3))\n\n\t<-ch2\n\n\tcancel()\n}\n\nfunc TestLoadHTMLGlob(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tengine := New(WithMaxRequestBodySize(15), WithListener(ln))\n\tengine.Delims(\"{[{\", \"}]}\")\n\tengine.LoadHTMLGlob(\"../../common/testdata/template/index.tmpl\")\n\tengine.GET(\"/index\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.HTML(consts.StatusOK, \"index.tmpl\", utils.H{\n\t\t\t\"title\": \"Main website\",\n\t\t})\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tresp, _ := http.Get(fullURL(ln, \"/index\"))\n\tassert.DeepEqual(t, consts.StatusOK, resp.StatusCode)\n\tb := make([]byte, 100)\n\tn, _ := resp.Body.Read(b)\n\tconst expected = `<html><h1>Main website</h1></html>`\n\n\tassert.DeepEqual(t, expected, string(b[0:n]))\n}\n\nfunc TestLoadHTMLFiles(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tengine := New(WithMaxRequestBodySize(15), WithListener(ln))\n\tengine.Delims(\"{[{\", \"}]}\")\n\tengine.SetFuncMap(template.FuncMap{\n\t\t\"formatAsDate\": formatAsDate,\n\t})\n\tengine.LoadHTMLFiles(\"../../common/testdata/template/htmltemplate.html\", \"../../common/testdata/template/index.tmpl\")\n\n\tengine.GET(\"/raw\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.HTML(consts.StatusOK, \"htmltemplate.html\", map[string]interface{}{\n\t\t\t\"now\": time.Date(2017, 0o7, 0o1, 0, 0, 0, 0, time.UTC),\n\t\t})\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tresp, _ := http.Get(fullURL(ln, \"/raw\"))\n\tassert.DeepEqual(t, consts.StatusOK, resp.StatusCode)\n\tb := make([]byte, 100)\n\tn, _ := resp.Body.Read(b)\n\tassert.DeepEqual(t, \"<h1>Date: 2017/07/01</h1>\", string(b[0:n]))\n}\n\nfunc formatAsDate(t time.Time) string {\n\tyear, month, day := t.Date()\n\treturn fmt.Sprintf(\"%d/%02d/%02d\", year, month, day)\n}\n\n// copied from router\nvar (\n\tdefault400Body   = []byte(\"Bad Request\")\n\trequiredHostBody = []byte(\"missing required Host header\")\n)\n\nfunc TestServer_Use(t *testing.T) {\n\trouter := New()\n\trouter.Use(func(c context.Context, ctx *app.RequestContext) {})\n\tassert.DeepEqual(t, 1, len(router.Handlers))\n\trouter.Use(func(c context.Context, ctx *app.RequestContext) {})\n\tassert.DeepEqual(t, 2, len(router.Handlers))\n}\n\nfunc Test_getServerName(t *testing.T) {\n\tengine := New()\n\tassert.DeepEqual(t, []byte(\"hertz\"), engine.GetServerName())\n\tss := New()\n\tss.Name = \"test_name\"\n\tassert.DeepEqual(t, []byte(\"test_name\"), ss.GetServerName())\n}\n\nfunc TestServer_Run(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\thertz := New(WithListener(ln))\n\thertz.GET(\"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t\tpath := ctx.Request.URI().PathOriginal()\n\t\tctx.SetBodyString(string(path))\n\t})\n\thertz.POST(\"/redirect\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Redirect(consts.StatusMovedPermanently, []byte(fullURL(ln, \"/test\")))\n\t})\n\tgo hertz.Run()\n\twaitEngineRunning(hertz)\n\n\tresp, err := http.Get(fullURL(ln, \"/test\"))\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, consts.StatusOK, resp.StatusCode)\n\tb := make([]byte, 5)\n\tresp.Body.Read(b)\n\tassert.DeepEqual(t, \"/test\", string(b))\n\n\tresp, err = http.Get(fullURL(ln, \"/foo\"))\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, consts.StatusNotFound, resp.StatusCode)\n\n\tresp, err = http.Post(fullURL(ln, \"/redirect\"), \"\", nil)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, consts.StatusOK, resp.StatusCode)\n\tb = make([]byte, 5)\n\tresp.Body.Read(b)\n\tassert.DeepEqual(t, \"/test\", string(b))\n\n\tctx, cancel := context.WithTimeout(context.Background(), 0)\n\tdefer cancel()\n\t_ = hertz.Shutdown(ctx)\n}\n\nfunc TestNotAbsolutePath(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tengine := New(WithListener(ln))\n\tengine.POST(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Write(ctx.Request.Body())\n\t})\n\tengine.POST(\"/a\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Write(ctx.Request.Body())\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\ts := \"POST ?a=b HTTP/1.1\\r\\nHost: a.b.c\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tzr := mock.NewZeroCopyReader(s)\n\n\tctx := app.NewContext(0)\n\tif err := req.Read(&ctx.Request, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tengine.ServeHTTP(context.Background(), ctx)\n\tassert.DeepEqual(t, consts.StatusOK, ctx.Response.StatusCode())\n\tassert.DeepEqual(t, ctx.Request.Body(), ctx.Response.Body())\n\n\ts = \"POST a?a=b HTTP/1.1\\r\\nHost: a.b.c\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tzr = mock.NewZeroCopyReader(s)\n\n\tctx = app.NewContext(0)\n\tif err := req.Read(&ctx.Request, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tengine.ServeHTTP(context.Background(), ctx)\n\tassert.DeepEqual(t, consts.StatusOK, ctx.Response.StatusCode())\n\tassert.DeepEqual(t, ctx.Request.Body(), ctx.Response.Body())\n}\n\nfunc TestNotAbsolutePathWithRawPath(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tengine := New(WithListener(ln), WithUseRawPath(true))\n\tconst (\n\t\tMiddlewareKey   = \"middleware_key\"\n\t\tMiddlewareValue = \"middleware_value\"\n\t)\n\tengine.Use(func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Response.Header.Set(MiddlewareKey, MiddlewareValue)\n\t})\n\tengine.POST(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t})\n\tengine.POST(\"/a\", func(c context.Context, ctx *app.RequestContext) {\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\ts := \"POST ?a=b HTTP/1.1\\r\\nHost: a.b.c\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tzr := mock.NewZeroCopyReader(s)\n\n\tctx := app.NewContext(0)\n\tif err := req.Read(&ctx.Request, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tengine.ServeHTTP(context.Background(), ctx)\n\tassert.DeepEqual(t, consts.StatusBadRequest, ctx.Response.StatusCode())\n\tassert.DeepEqual(t, default400Body, ctx.Response.Body())\n\tgh := ctx.Response.Header.Get(MiddlewareKey)\n\tassert.DeepEqual(t, MiddlewareValue, gh)\n\n\ts = \"POST a?a=b HTTP/1.1\\r\\nHost: a.b.c\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tzr = mock.NewZeroCopyReader(s)\n\n\tctx = app.NewContext(0)\n\tif err := req.Read(&ctx.Request, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tengine.ServeHTTP(context.Background(), ctx)\n\tassert.DeepEqual(t, consts.StatusBadRequest, ctx.Response.StatusCode())\n\tassert.DeepEqual(t, default400Body, ctx.Response.Body())\n\tgh = ctx.Response.Header.Get(MiddlewareKey)\n\tassert.DeepEqual(t, MiddlewareValue, gh)\n}\n\nfunc TestNotValidHost(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tengine := New(WithListener(ln))\n\tconst (\n\t\tMiddlewareKey   = \"middleware_key\"\n\t\tMiddlewareValue = \"middleware_value\"\n\t)\n\tengine.Use(func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Response.Header.Set(MiddlewareKey, MiddlewareValue)\n\t})\n\tengine.POST(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t})\n\tengine.POST(\"/a\", func(c context.Context, ctx *app.RequestContext) {\n\t})\n\n\ts := \"POST ?a=b HTTP/1.1\\r\\nHost: \\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tzr := mock.NewZeroCopyReader(s)\n\n\tctx := app.NewContext(0)\n\tif err := req.Read(&ctx.Request, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tengine.ServeHTTP(context.Background(), ctx)\n\tassert.DeepEqual(t, consts.StatusBadRequest, ctx.Response.StatusCode())\n\tassert.DeepEqual(t, requiredHostBody, ctx.Response.Body())\n\tgh := ctx.Response.Header.Get(MiddlewareKey)\n\tassert.DeepEqual(t, MiddlewareValue, gh)\n\n\ts = \"POST a?a=b HTTP/1.1\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tzr = mock.NewZeroCopyReader(s)\n\n\tctx = app.NewContext(0)\n\tif err := req.Read(&ctx.Request, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tengine.ServeHTTP(context.Background(), ctx)\n\tassert.DeepEqual(t, consts.StatusBadRequest, ctx.Response.StatusCode())\n\tassert.DeepEqual(t, requiredHostBody, ctx.Response.Body())\n\tgh = ctx.Response.Header.Get(MiddlewareKey)\n\tassert.DeepEqual(t, MiddlewareValue, gh)\n}\n\nfunc TestWithBasePath(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tengine := New(WithBasePath(\"/hertz\"), WithListener(ln))\n\tengine.POST(\"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tvar r http.Request\n\tr.ParseForm()\n\tr.Form.Add(\"xxxxxx\", \"xxx\")\n\tbody := strings.NewReader(r.Form.Encode())\n\tresp, err := http.Post(fullURL(ln, \"/hertz/test\"), \"application/x-www-form-urlencoded\", body)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, consts.StatusOK, resp.StatusCode)\n}\n\nfunc TestNotEnoughBodySize(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tengine := New(WithMaxRequestBodySize(5), WithListener(ln))\n\tengine.POST(\"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tvar r http.Request\n\tr.ParseForm()\n\tr.Form.Add(\"xxxxxx\", \"xxx\")\n\tbody := strings.NewReader(r.Form.Encode())\n\tresp, err := http.Post(fullURL(ln, \"/test\"), \"application/x-www-form-urlencoded\", body)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, 413, resp.StatusCode)\n\tbodyBytes, _ := ioutil.ReadAll(resp.Body)\n\tassert.DeepEqual(t, \"Request Entity Too Large\", string(bodyBytes))\n}\n\nfunc TestEnoughBodySize(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tengine := New(WithMaxRequestBodySize(15), WithListener(ln))\n\tengine.POST(\"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t})\n\tgo engine.Run()\n\tdefer func() {\n\t\tengine.Close()\n\t}()\n\twaitEngineRunning(engine)\n\n\tvar r http.Request\n\tr.ParseForm()\n\tr.Form.Add(\"xxxxxx\", \"xxx\")\n\tbody := strings.NewReader(r.Form.Encode())\n\tresp, _ := http.Post(fullURL(ln, \"/test\"), \"application/x-www-form-urlencoded\", body)\n\tassert.DeepEqual(t, consts.StatusOK, resp.StatusCode)\n}\n\nfunc TestRequestCtxHijack(t *testing.T) {\n\thijackStartCh := make(chan struct{})\n\thijackStopCh := make(chan struct{})\n\tengine := New()\n\tengine.Init()\n\n\tengine.GET(\"/foo\", func(c context.Context, ctx *app.RequestContext) {\n\t\tif ctx.Hijacked() {\n\t\t\tt.Error(\"connection mustn't be hijacked\")\n\t\t}\n\t\tctx.Hijack(func(c network.Conn) {\n\t\t\t<-hijackStartCh\n\n\t\t\tb := make([]byte, 1)\n\t\t\t// ping-pong echo via hijacked conn\n\t\t\tfor {\n\t\t\t\tn, err := c.Read(b)\n\t\t\t\tif n != 1 {\n\t\t\t\t\tif err == io.EOF {\n\t\t\t\t\t\tclose(hijackStopCh)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\tt.Errorf(\"unexpected number of bytes read: %d. Expecting 1\", n)\n\t\t\t\t}\n\t\t\t\tif _, err = c.Write(b); err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error when writing data: %s\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\tif !ctx.Hijacked() {\n\t\t\tt.Error(\"connection must be hijacked\")\n\t\t}\n\t\tctx.Data(consts.StatusOK, \"foo/bar\", []byte(\"hijack it!\"))\n\t})\n\n\thijackedString := \"foobar baz hijacked!!!\"\n\n\tc := mock.NewConn(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\" + hijackedString)\n\n\tch := make(chan error)\n\tgo func() {\n\t\tch <- engine.Serve(context.Background(), c)\n\t}()\n\n\ttime.Sleep(100 * time.Millisecond)\n\n\tclose(hijackStartCh)\n\n\tif err := <-ch; err != nil {\n\t\tif !errors.Is(err, errs.ErrHijacked) {\n\t\t\tt.Fatalf(\"Unexpected error from serveConn: %s\", err)\n\t\t}\n\t}\n\tverifyResponse(t, c.WriterRecorder(), consts.StatusOK, \"foo/bar\", \"hijack it!\")\n\n\tselect {\n\tcase <-hijackStopCh:\n\tcase <-time.After(100 * time.Millisecond):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tzw := c.WriterRecorder()\n\tdata, err := zw.ReadBinary(zw.Len())\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading remaining data: %s\", err)\n\t}\n\tif string(data) != hijackedString {\n\t\tt.Fatalf(\"Unexpected data read after the first response %q. Expecting %q\", data, hijackedString)\n\t}\n}\n\nfunc verifyResponse(t *testing.T, zr network.Reader, expectedStatusCode int, expectedContentType, expectedBody string) {\n\tvar r protocol.Response\n\tif err := resp.Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing response: %s\", err)\n\t}\n\n\tif !bytes.Equal(r.Body(), []byte(expectedBody)) {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", r.Body(), []byte(expectedBody))\n\t}\n\tverifyResponseHeader(t, &r.Header, expectedStatusCode, len(r.Body()), expectedContentType, \"\")\n}\n\nfunc verifyResponseHeader(t *testing.T, h *protocol.ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType, expectedContentEncoding string) {\n\tif h.StatusCode() != expectedStatusCode {\n\t\tt.Fatalf(\"Unexpected status code %d. Expected %d\", h.StatusCode(), expectedStatusCode)\n\t}\n\tif h.ContentLength() != expectedContentLength {\n\t\tt.Fatalf(\"Unexpected content length %d. Expected %d\", h.ContentLength(), expectedContentLength)\n\t}\n\tif string(h.ContentType()) != expectedContentType {\n\t\tt.Fatalf(\"Unexpected content type %q. Expected %q\", h.ContentType(), expectedContentType)\n\t}\n\tif string(h.ContentEncoding()) != expectedContentEncoding {\n\t\tt.Fatalf(\"Unexpected content encoding %q. Expected %q\", h.ContentEncoding(), expectedContentEncoding)\n\t}\n}\n\nfunc TestParamInconsist(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tmapS := sync.Map{}\n\th := New(WithListener(ln))\n\th.GET(\"/:label\", func(c context.Context, ctx *app.RequestContext) {\n\t\tlabel := ctx.Param(\"label\")\n\t\tx, _ := mapS.LoadOrStore(label, label)\n\t\tlabelString := x.(string)\n\t\tif label != labelString {\n\t\t\tt.Errorf(\"unexpected label: %s, expected return label: %s\", label, labelString)\n\t\t}\n\t})\n\tgo h.Run()\n\twaitEngineRunning(h)\n\n\tclient, _ := c.NewClient()\n\twg := sync.WaitGroup{}\n\ttr := func() {\n\t\tdefer wg.Done()\n\t\tfor i := 0; i < 500; i++ {\n\t\t\tclient.Get(context.Background(), nil, fullURL(ln, \"/test1\"))\n\t\t}\n\t}\n\tti := func() {\n\t\tdefer wg.Done()\n\t\tfor i := 0; i < 500; i++ {\n\t\t\tclient.Get(context.Background(), nil, fullURL(ln, \"/test2\"))\n\t\t}\n\t}\n\n\tfor i := 0; i < 30; i++ {\n\t\tgo tr()\n\t\tgo ti()\n\t\twg.Add(2)\n\t}\n\twg.Wait()\n}\n\nfunc TestDuplicateReleaseBodyStream(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(WithStreamBody(true), WithListener(ln))\n\th.POST(\"/test\", func(ctx context.Context, c *app.RequestContext) {\n\t\tstream := c.RequestBodyStream()\n\t\tc.Response.SetBodyStream(stream, -1)\n\t})\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\tclient, _ := c.NewClient(c.WithMaxConnsPerHost(1000000), c.WithDialTimeout(time.Minute))\n\tbodyBytes := make([]byte, 102388)\n\tindex := 0\n\tletterBytes := \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\tfor i := 0; i < 102388; i++ {\n\t\tbodyBytes[i] = letterBytes[index]\n\t\tif i%1969 == 0 && i != 0 {\n\t\t\tindex = index + 1\n\t\t}\n\t}\n\tbody := string(bodyBytes)\n\n\twg := sync.WaitGroup{}\n\ttestFunc := func() {\n\t\tdefer wg.Done()\n\t\tr := protocol.NewRequest(\"POST\", fullURL(ln, \"/test\"), nil)\n\t\tr.SetBodyString(body)\n\t\tresp := protocol.AcquireResponse()\n\t\terr := client.Do(context.Background(), r, resp)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %s\", err.Error())\n\t\t}\n\t\tif body != string(resp.Body()) {\n\t\t\tt.Errorf(\"unequal body\")\n\t\t}\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\twg.Add(1)\n\t\tgo testFunc()\n\t}\n\twg.Wait()\n}\n\nfunc TestServiceRegisterFailed(t *testing.T) {\n\tt.Parallel() // slow test, make it parallel\n\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tmockRegErr := errors.New(\"mock register error\")\n\tvar rCount int32\n\tvar drCount int32\n\tmockRegistry := MockRegistry{\n\t\tRegisterFunc: func(info *registry.Info) error {\n\t\t\tatomic.AddInt32(&rCount, 1)\n\t\t\treturn mockRegErr\n\t\t},\n\t\tDeregisterFunc: func(info *registry.Info) error {\n\t\t\tatomic.AddInt32(&drCount, 1)\n\t\t\treturn nil\n\t\t},\n\t}\n\tvar opts []config.Option\n\topts = append(opts, WithRegistry(mockRegistry, nil))\n\topts = append(opts, WithListener(ln))\n\tsrv := New(opts...)\n\tsrv.Spin()\n\tassert.Assert(t, atomic.LoadInt32(&rCount) == 1)\n}\n\nfunc TestServiceDeregisterFailed(t *testing.T) {\n\tt.Parallel() // slow test, make it parallel\n\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tmockDeregErr := errors.New(\"mock deregister error\")\n\n\tvar wg sync.WaitGroup\n\twg.Add(2) // RegisterFunc && DeregisterFunc\n\tvar rCount int32\n\tvar drCount int32\n\tmockRegistry := MockRegistry{\n\t\tRegisterFunc: func(info *registry.Info) error {\n\t\t\tdefer wg.Done()\n\t\t\tatomic.AddInt32(&rCount, 1)\n\t\t\treturn nil\n\t\t},\n\t\tDeregisterFunc: func(info *registry.Info) error {\n\t\t\tdefer wg.Done()\n\t\t\tatomic.AddInt32(&drCount, 1)\n\t\t\treturn mockDeregErr\n\t\t},\n\t}\n\n\tvar opts []config.Option\n\topts = append(opts, WithRegistry(mockRegistry, nil))\n\topts = append(opts, WithListener(ln))\n\tsrv := New(opts...)\n\tgo srv.Spin()\n\twaitEngineRunning(srv)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\t_ = srv.Shutdown(ctx)\n\n\twg.Wait()\n\tassert.Assert(t, atomic.LoadInt32(&rCount) == 1)\n\tassert.Assert(t, atomic.LoadInt32(&drCount) == 1)\n}\n\nfunc TestServiceRegistryInfo(t *testing.T) {\n\tt.Parallel() // slow test, make it parallel\n\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tregistryInfo := &registry.Info{\n\t\tWeight:      100,\n\t\tTags:        map[string]string{\"aa\": \"bb\"},\n\t\tServiceName: \"hertz.api.test\",\n\t}\n\tcheckInfo := func(info *registry.Info) {\n\t\tassert.Assert(t, info.Weight == registryInfo.Weight)\n\t\tassert.Assert(t, info.ServiceName == \"hertz.api.test\")\n\t\tassert.Assert(t, len(info.Tags) == len(registryInfo.Tags), info.Tags)\n\t\tassert.Assert(t, info.Tags[\"aa\"] == registryInfo.Tags[\"aa\"], info.Tags)\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(2) // RegisterFunc && DeregisterFunc\n\tvar rCount int32\n\tvar drCount int32\n\tmockRegistry := MockRegistry{\n\t\tRegisterFunc: func(info *registry.Info) error {\n\t\t\tdefer wg.Done()\n\t\t\tcheckInfo(info)\n\t\t\tatomic.AddInt32(&rCount, 1)\n\t\t\treturn nil\n\t\t},\n\t\tDeregisterFunc: func(info *registry.Info) error {\n\t\t\tdefer wg.Done()\n\t\t\tcheckInfo(info)\n\t\t\tatomic.AddInt32(&drCount, 1)\n\t\t\treturn nil\n\t\t},\n\t}\n\tvar opts []config.Option\n\topts = append(opts, WithRegistry(mockRegistry, registryInfo))\n\topts = append(opts, WithListener(ln))\n\tsrv := New(opts...)\n\tgo srv.Spin()\n\twaitEngineRunning(srv)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 0)\n\tdefer cancel()\n\t_ = srv.Shutdown(ctx)\n\twg.Wait()\n\tassert.Assert(t, atomic.LoadInt32(&rCount) == 1)\n\tassert.Assert(t, atomic.LoadInt32(&drCount) == 1)\n}\n\nfunc TestServiceRegistryNoInitInfo(t *testing.T) {\n\tt.Parallel() // slow test, make it parallel\n\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tcheckInfo := func(info *registry.Info) {\n\t\tassert.Assert(t, info == nil)\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(2) // RegisterFunc && DeregisterFunc\n\tvar rCount int32\n\tvar drCount int32\n\tmockRegistry := MockRegistry{\n\t\tRegisterFunc: func(info *registry.Info) error {\n\t\t\tdefer wg.Done()\n\t\t\tcheckInfo(info)\n\t\t\tatomic.AddInt32(&rCount, 1)\n\t\t\treturn nil\n\t\t},\n\t\tDeregisterFunc: func(info *registry.Info) error {\n\t\t\tdefer wg.Done()\n\t\t\tcheckInfo(info)\n\t\t\tatomic.AddInt32(&drCount, 1)\n\t\t\treturn nil\n\t\t},\n\t}\n\tvar opts []config.Option\n\topts = append(opts, WithRegistry(mockRegistry, nil))\n\topts = append(opts, WithListener(ln))\n\tsrv := New(opts...)\n\tgo srv.Spin()\n\twaitEngineRunning(srv)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 0)\n\tdefer cancel()\n\t_ = srv.Shutdown(ctx)\n\twg.Wait()\n\tassert.Assert(t, atomic.LoadInt32(&rCount) == 1)\n\tassert.Assert(t, atomic.LoadInt32(&drCount) == 1)\n}\n\ntype testTracer struct{}\n\nfunc (t testTracer) Start(ctx context.Context, c *app.RequestContext) context.Context {\n\tvalue := 0\n\tif v := ctx.Value(\"testKey\"); v != nil {\n\t\tvalue = v.(int)\n\t\tvalue++\n\t}\n\t//nolint:staticcheck // SA1029 no built-in type string as key\n\treturn context.WithValue(ctx, \"testKey\", value)\n}\n\nfunc (t testTracer) Finish(ctx context.Context, c *app.RequestContext) {}\n\nfunc TestReuseCtx(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(WithTracer(testTracer{}), WithListener(ln))\n\th.GET(\"/ping\", func(ctx context.Context, c *app.RequestContext) {\n\t\tassert.DeepEqual(t, 0, ctx.Value(\"testKey\").(int))\n\t})\n\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\tfor i := 0; i < 1000; i++ {\n\t\t_, _, err := c.Get(context.Background(), nil, fullURL(ln, \"/ping\"))\n\t\tassert.Nil(t, err)\n\t}\n}\n\nfunc TestOnprepare(t *testing.T) {\n\tln1 := testutils.NewTestListener(t)\n\tdefer ln1.Close()\n\th1 := New(\n\t\tWithListener(ln1),\n\t\tWithOnConnect(func(ctx context.Context, conn network.Conn) context.Context {\n\t\t\tb, err := conn.Peek(3)\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.DeepEqual(t, string(b), \"GET\")\n\t\t\tconn.Close()\n\t\t\treturn ctx\n\t\t}))\n\th1.GET(\"/ping\", func(ctx context.Context, c *app.RequestContext) {\n\t\tc.JSON(consts.StatusOK, utils.H{\"ping\": \"pong\"})\n\t})\n\n\tgo h1.Spin()\n\twaitEngineRunning(h1)\n\n\t_, _, err := c.Get(context.Background(), nil, fullURL(ln1, \"/ping\"))\n\tassert.DeepEqual(t, \"the server closed connection before returning the first response byte. Make sure the server returns 'Connection: close' response header before closing the connection\", err.Error())\n\n\tln2 := testutils.NewTestListener(t)\n\tdefer ln2.Close()\n\th2 := New(\n\t\tWithOnAccept(func(conn net.Conn) context.Context {\n\t\t\tconn.Close()\n\t\t\treturn context.Background()\n\t\t}),\n\t\tWithListener(ln2))\n\th2.GET(\"/ping\", func(ctx context.Context, c *app.RequestContext) {\n\t\tc.JSON(consts.StatusOK, utils.H{\"ping\": \"pong\"})\n\t})\n\tgo h2.Spin()\n\twaitEngineRunning(h2)\n\n\t_, _, err = c.Get(context.Background(), nil, fullURL(ln2, \"/ping\"))\n\tif err == nil {\n\t\tt.Fatalf(\"err should not be nil\")\n\t}\n\n\tln3 := testutils.NewTestListener(t)\n\tdefer ln3.Close()\n\tvar h3 *Hertz\n\th3 = New(\n\t\tWithOnAccept(func(conn net.Conn) context.Context {\n\t\t\tassert.DeepEqual(t, conn.LocalAddr().String(), ln3.Addr().String())\n\t\t\treturn context.Background()\n\t\t}),\n\t\tWithListener(ln3),\n\t\tWithTransport(standard.NewTransporter))\n\th3.GET(\"/ping\", func(ctx context.Context, c *app.RequestContext) {\n\t\tc.JSON(consts.StatusOK, utils.H{\"ping\": \"pong\"})\n\t})\n\tgo h3.Spin()\n\twaitEngineRunning(h3)\n\n\tc.Get(context.Background(), nil, fullURL(ln3, \"/ping\"))\n}\n\ntype lockBuffer struct {\n\tsync.Mutex\n\tb bytes.Buffer\n}\n\nfunc (l *lockBuffer) Write(p []byte) (int, error) {\n\tl.Lock()\n\tdefer l.Unlock()\n\treturn l.b.Write(p)\n}\n\nfunc (l *lockBuffer) String() string {\n\tl.Lock()\n\tdefer l.Unlock()\n\treturn l.b.String()\n}\n\nfunc TestHertzDisableHeaderNamesNormalizing(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(\n\t\tWithListener(ln),\n\t\tWithDisableHeaderNamesNormalizing(true),\n\t)\n\theaderName := \"CASE-senSITive-HEAder-NAME\"\n\theaderValue := \"foobar-baz\"\n\tsucceed := false\n\th.GET(\"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.VisitAllHeaders(func(key, value []byte) {\n\t\t\tif string(key) == headerName && string(value) == headerValue {\n\t\t\t\tsucceed = true\n\t\t\t\treturn\n\t\t\t}\n\t\t})\n\t\tif !succeed {\n\t\t\tt.Fatalf(\"DisableHeaderNamesNormalizing failed\")\n\t\t} else {\n\t\t\tctx.Header(headerName, headerValue)\n\t\t}\n\t})\n\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\tcli, _ := c.NewClient(c.WithDisableHeaderNamesNormalizing(true))\n\n\tr := protocol.NewRequest(\"GET\", fullURL(ln, \"/test\"), nil)\n\tr.Header.DisableNormalizing()\n\tr.Header.Set(headerName, headerValue)\n\tres := protocol.AcquireResponse()\n\terr := cli.Do(context.Background(), r, res)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, headerValue, res.Header.Get(headerName))\n}\n\nfunc TestBindConfig(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\"`\n\t}\n\tbindConfig := binding.NewBindConfig()\n\tbindConfig.LooseZeroMode = true\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(\n\t\tWithListener(ln),\n\t\tWithBindConfig(bindConfig))\n\th.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error\")\n\t\t}\n\t})\n\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\thc := http.Client{Timeout: time.Second}\n\t_, err := hc.Get(fullURL(ln, \"/bind?a=\"))\n\tassert.Nil(t, err)\n\n\tbindConfig = binding.NewBindConfig()\n\tbindConfig.LooseZeroMode = false\n\tln2 := testutils.NewTestListener(t)\n\tdefer ln2.Close()\n\th2 := New(\n\t\tWithListener(ln2),\n\t\tWithBindConfig(bindConfig))\n\th2.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expect an error\")\n\t\t}\n\t})\n\n\tgo h2.Spin()\n\twaitEngineRunning(h2)\n\n\t_, err = hc.Get(fullURL(ln2, \"/bind?a=\"))\n\tassert.Nil(t, err)\n\ttime.Sleep(100 * time.Millisecond)\n}\n\nfunc TestCustomBinder(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\"`\n\t}\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(\n\t\tWithListener(ln),\n\t\tWithCustomBinder(binder.NewBinderWithValidateError(errors.New(\"test binder\"))))\n\th.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expect an error\")\n\t\t}\n\t\tassert.DeepEqual(t, \"test binder\", err.Error())\n\t})\n\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\thc := http.Client{Timeout: time.Second}\n\t_, err := hc.Get(fullURL(ln, \"/bind?a=\"))\n\tassert.Nil(t, err)\n\ttime.Sleep(100 * time.Millisecond)\n}\n\nfunc TestValidateConfigRegValidateFunc(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\" vd:\"f($)\"`\n\t}\n\tvalidateConfig := &binding.ValidateConfig{}\n\tvalidateConfig.MustRegValidateFunc(\"f\", func(args ...interface{}) error {\n\t\treturn fmt.Errorf(\"test validator\")\n\t})\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(WithListener(ln))\n\th.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expect an error\")\n\t\t}\n\t\tassert.DeepEqual(t, \"test validator\", err.Error())\n\t})\n\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\thc := http.Client{Timeout: time.Second}\n\t_, err := hc.Get(fullURL(ln, \"/bind?a=2\"))\n\tassert.Nil(t, err)\n\ttime.Sleep(100 * time.Millisecond)\n}\n\nfunc TestCustomValidator(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\" vd:\"f($)\"`\n\t}\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(\n\t\tWithListener(ln),\n\t\tWithCustomValidatorFunc(func(_ *protocol.Request, _ interface{}) error {\n\t\t\treturn errors.New(\"test mock validator\")\n\t\t}))\n\th.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expect an error\")\n\t\t}\n\t\tassert.DeepEqual(t, \"test mock validator\", err.Error())\n\t})\n\n\tgo h.Spin()\n\ttime.Sleep(100 * time.Millisecond)\n\thc := http.Client{Timeout: time.Second}\n\t_, err := hc.Get(fullURL(ln, \"/bind?a=2\"))\n\tassert.Nil(t, err)\n\ttime.Sleep(100 * time.Millisecond)\n}\n\ntype ValidateError struct {\n\tErrType, FailField, Msg string\n}\n\n// Error implements error interface.\nfunc (e *ValidateError) Error() string {\n\tif e.Msg != \"\" {\n\t\treturn e.ErrType + \": expr_path=\" + e.FailField + \", cause=\" + e.Msg\n\t}\n\treturn e.ErrType + \": expr_path=\" + e.FailField + \", cause=invalid\"\n}\n\nfunc TestValidateConfigSetSetErrorFactory(t *testing.T) {\n\ttype TestValidate struct {\n\t\tB int `query:\"b\" vd:\"$>100\"`\n\t}\n\tCustomValidateErrFunc := func(failField, msg string) error {\n\t\terr := ValidateError{\n\t\t\tErrType:   \"validateErr\",\n\t\t\tFailField: \"[validateFailField]: \" + failField,\n\t\t\tMsg:       \"[validateErrMsg]: \" + msg,\n\t\t}\n\n\t\treturn &err\n\t}\n\tvalidateConfig := binding.NewValidateConfig()\n\tvalidateConfig.SetValidatorErrorFactory(CustomValidateErrFunc)\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(\n\t\tWithListener(ln),\n\t\tWithValidateConfig(validateConfig))\n\th.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req TestValidate\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expect an error\")\n\t\t}\n\t\tassert.DeepEqual(t, \"validateErr: expr_path=[validateFailField]: B, cause=[validateErrMsg]: \", err.Error())\n\t})\n\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\thc := http.Client{Timeout: time.Second}\n\t_, err := hc.Get(fullURL(ln, \"/bind?b=1\"))\n\tassert.Nil(t, err)\n\ttime.Sleep(100 * time.Millisecond)\n}\n\nfunc TestValidateConfigAndBindConfig(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\" vt:\"$>=0&&$<=130\"`\n\t}\n\tvalidateConfig := binding.NewValidateConfig()\n\tvalidateConfig.ValidateTag = \"vt\"\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(\n\t\tWithListener(ln),\n\t\tWithValidateConfig(validateConfig))\n\th.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expect an error\")\n\t\t}\n\t\tt.Log(err)\n\t})\n\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\thc := http.Client{Timeout: time.Second}\n\t_, err := hc.Get(fullURL(ln, \"/bind?a=135\"))\n\tassert.Nil(t, err)\n\ttime.Sleep(100 * time.Millisecond)\n}\n\nfunc TestWithDisableDefaultDate(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(\n\t\tWithListener(ln),\n\t\tWithDisableDefaultDate(true),\n\t)\n\th.GET(\"/\", func(_ context.Context, c *app.RequestContext) {})\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\thc := http.Client{Timeout: time.Second}\n\tr, _ := hc.Get(fullURL(ln, \"\")) //nolint:errcheck\n\tassert.DeepEqual(t, \"\", r.Header.Get(\"Date\"))\n}\n\nfunc TestWithDisableDefaultContentType(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := New(\n\t\tWithListener(ln),\n\t\tWithDisableDefaultContentType(true),\n\t)\n\th.GET(\"/\", func(_ context.Context, c *app.RequestContext) {})\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\thc := http.Client{Timeout: time.Second}\n\tr, _ := hc.Get(fullURL(ln, \"\")) //nolint:errcheck\n\tassert.DeepEqual(t, \"\", r.Header.Get(\"Content-Type\"))\n}\n\nfunc TestServerReturns413And431OnSizeLimits(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\th := Default(WithListener(ln), WithMaxHeaderBytes(500), WithMaxRequestBodySize(1000))\n\n\th.GET(\"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(consts.StatusOK, \"success\")\n\t})\n\th.POST(\"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(consts.StatusOK, \"success\")\n\t})\n\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\tdefer h.Shutdown(context.Background())\n\n\taddr := ln.Addr().String()\n\tclient := &http.Client{Timeout: 2 * time.Second}\n\n\t// Test 431 - Request Header Fields Too Large\n\treq, _ := http.NewRequest(\"GET\", fmt.Sprintf(\"http://%s/test\", addr), nil)\n\treq.Header.Set(\"Large-Header\", strings.Repeat(\"x\", 501)) // Exceeds 500 byte limit\n\n\tresp, err := client.Do(req)\n\tassert.Nil(t, err)\n\tresp.Body.Close()\n\n\t// If we get a response, it should be 431\n\tassert.DeepEqual(t, resp.StatusCode, 431)\n\n\t// Test 413 - Request Entity Too Large\n\tlargeBody := strings.NewReader(strings.Repeat(\"x\", 1001)) // Exceeds 1000 byte limit\n\treq2, _ := http.NewRequest(\"POST\", fmt.Sprintf(\"http://%s/test\", addr), largeBody)\n\n\tresp2, err2 := client.Do(req2)\n\tassert.Nil(t, err2)\n\tresp2.Body.Close()\n\n\t// Should return 413\n\tassert.DeepEqual(t, resp2.StatusCode, 413)\n}\n"
  },
  {
    "path": "pkg/app/server/hertz_unix_test.go",
    "content": "// Copyright 2023 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris\n\npackage server\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\tc \"github.com/cloudwego/hertz/pkg/app/client\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc TestReusePorts(t *testing.T) {\n\tcfg := &net.ListenConfig{Control: func(network, address string, c syscall.RawConn) error {\n\t\treturn c.Control(func(fd uintptr) {\n\t\t\tsyscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEADDR, 1)\n\t\t\tsyscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)\n\t\t})\n\t}}\n\tha := New(WithHostPorts(\"localhost:10093\"), WithListenConfig(cfg), WithTransport(standard.NewTransporter))\n\thb := New(WithHostPorts(\"localhost:10093\"), WithListenConfig(cfg), WithTransport(standard.NewTransporter))\n\thc := New(WithHostPorts(\"localhost:10093\"), WithListenConfig(cfg))\n\thd := New(WithHostPorts(\"localhost:10093\"), WithListenConfig(cfg))\n\tha.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.JSON(consts.StatusOK, utils.H{\"ping\": \"pong\"})\n\t})\n\thc.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.JSON(consts.StatusOK, utils.H{\"ping\": \"pong\"})\n\t})\n\thd.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.JSON(consts.StatusOK, utils.H{\"ping\": \"pong\"})\n\t})\n\thb.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.JSON(consts.StatusOK, utils.H{\"ping\": \"pong\"})\n\t})\n\tgo ha.Run()\n\tgo hb.Run()\n\tgo hc.Run()\n\tgo hd.Run()\n\twaitEngineRunning(ha)\n\twaitEngineRunning(hb)\n\twaitEngineRunning(hc)\n\twaitEngineRunning(hd)\n\n\tclient, _ := c.NewClient()\n\tfor i := 0; i < 1000; i++ {\n\t\tstatusCode, body, err := client.Get(context.Background(), nil, \"http://localhost:10093/ping\")\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, consts.StatusOK, statusCode)\n\t\tassert.DeepEqual(t, \"{\\\"ping\\\":\\\"pong\\\"}\", string(body))\n\t}\n}\n\nfunc TestHertz_Spin(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\tengine := New(WithListener(ln))\n\tengine.GET(\"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t\ttime.Sleep(40 * time.Millisecond)\n\t\tpath := ctx.Request.URI().PathOriginal()\n\t\tctx.SetBodyString(string(path))\n\t})\n\tengine.GET(\"/test2\", func(c context.Context, ctx *app.RequestContext) {})\n\n\ttestint := uint32(0)\n\tengine.Engine.OnShutdown = append(engine.OnShutdown, func(ctx context.Context) {\n\t\tatomic.StoreUint32(&testint, 1)\n\t})\n\n\tgo engine.Spin()\n\twaitEngineRunning(engine)\n\n\thc := http.Client{Timeout: time.Second}\n\tvar err error\n\tvar resp *http.Response\n\tch := make(chan struct{})\n\tch2 := make(chan struct{})\n\tgo func() {\n\t\tticker := time.NewTicker(10 * time.Millisecond)\n\t\tdefer ticker.Stop()\n\t\tfor range ticker.C {\n\t\t\t_, err := hc.Get(fullURL(ln, \"/test2\"))\n\t\t\tt.Logf(\"[%v]begin listening\\n\", time.Now())\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"[%v]listening closed: %v\", time.Now(), err)\n\t\t\t\tch2 <- struct{}{}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\tgo func() {\n\t\tt.Logf(\"[%v]begin request\\n\", time.Now())\n\t\tresp, err = http.Get(fullURL(ln, \"/test\"))\n\t\tt.Logf(\"[%v]end request\\n\", time.Now())\n\t\tch <- struct{}{}\n\t}()\n\n\ttime.Sleep(20 * time.Millisecond)\n\tpid := strconv.Itoa(os.Getpid())\n\tcmd := exec.Command(\"kill\", \"-SIGHUP\", pid)\n\tt.Logf(\"[%v]begin SIGHUP\\n\", time.Now())\n\tif err := cmd.Run(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Logf(\"[%v]end SIGHUP\\n\", time.Now())\n\t<-ch\n\tassert.Nil(t, err)\n\tassert.NotNil(t, resp)\n\n\t<-ch2\n\tassert.DeepEqual(t, uint32(1), atomic.LoadUint32(&testint))\n}\n\nfunc TestWithSenseClientDisconnection(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tvar closeFlag int32\n\th := New(WithListener(ln), WithSenseClientDisconnection(true))\n\th.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\tassert.DeepEqual(t, \"aa\", string(ctx.Host()))\n\t\tch := make(chan struct{})\n\t\tselect {\n\t\tcase <-c.Done():\n\t\t\tatomic.StoreInt32(&closeFlag, 1)\n\t\t\tassert.DeepEqual(t, context.Canceled, c.Err())\n\t\tcase <-ch:\n\t\t}\n\t})\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\tcon, err := net.Dial(\"tcp\", ln.Addr().String())\n\tassert.Nil(t, err)\n\t_, err = con.Write([]byte(\"GET /ping HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\"))\n\tassert.Nil(t, err)\n\ttime.Sleep(20 * time.Millisecond)\n\tassert.DeepEqual(t, atomic.LoadInt32(&closeFlag), int32(0))\n\tassert.Nil(t, con.Close())\n\ttime.Sleep(20 * time.Millisecond)\n\tassert.DeepEqual(t, atomic.LoadInt32(&closeFlag), int32(1))\n}\n\nfunc TestWithSenseClientDisconnectionAndWithOnConnect(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tvar closeFlag int32\n\th := New(WithListener(ln), WithSenseClientDisconnection(true), WithOnConnect(func(ctx context.Context, conn network.Conn) context.Context {\n\t\treturn ctx\n\t}))\n\th.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\tassert.DeepEqual(t, \"aa\", string(ctx.Host()))\n\t\tch := make(chan struct{})\n\t\tselect {\n\t\tcase <-c.Done():\n\t\t\tatomic.StoreInt32(&closeFlag, 1)\n\t\t\tassert.DeepEqual(t, context.Canceled, c.Err())\n\t\tcase <-ch:\n\t\t}\n\t})\n\tgo h.Spin()\n\twaitEngineRunning(h)\n\n\tcon, err := net.Dial(\"tcp\", ln.Addr().String())\n\tassert.Nil(t, err)\n\t_, err = con.Write([]byte(\"GET /ping HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\"))\n\tassert.Nil(t, err)\n\ttime.Sleep(20 * time.Millisecond)\n\tassert.DeepEqual(t, atomic.LoadInt32(&closeFlag), int32(0))\n\tassert.Nil(t, con.Close())\n\ttime.Sleep(20 * time.Millisecond)\n\tassert.DeepEqual(t, atomic.LoadInt32(&closeFlag), int32(1))\n}\n"
  },
  {
    "path": "pkg/app/server/mocks_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage server\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/app/server/registry\"\n)\n\nvar _ registry.Registry = (*MockRegistry)(nil)\n\n// MockRegistry is the mock implementation of registry.Registry interface.\ntype MockRegistry struct {\n\tRegisterFunc   func(info *registry.Info) error\n\tDeregisterFunc func(info *registry.Info) error\n}\n\n// Register is the mock implementation of registry.Registry interface.\nfunc (m MockRegistry) Register(info *registry.Info) error {\n\tif m.RegisterFunc != nil {\n\t\treturn m.RegisterFunc(info)\n\t}\n\treturn nil\n}\n\n// Deregister is the mock implementation of registry.Registry interface.\nfunc (m MockRegistry) Deregister(info *registry.Info) error {\n\tif m.DeregisterFunc != nil {\n\t\treturn m.DeregisterFunc(info)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/app/server/option.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/server/binding\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/registry\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/stats\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n)\n\n// WithKeepAliveTimeout sets keep-alive timeout.\n//\n// In most cases, there is no need to care about this option.\nfunc WithKeepAliveTimeout(t time.Duration) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.KeepAliveTimeout = t\n\t}}\n}\n\n// WithReadTimeout sets read timeout.\n//\n// Close the connection when read request timeout.\nfunc WithReadTimeout(t time.Duration) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.ReadTimeout = t\n\t}}\n}\n\n// WithWriteTimeout sets write timeout.\n//\n// Connection will be closed when write request timeout.\nfunc WithWriteTimeout(t time.Duration) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.WriteTimeout = t\n\t}}\n}\n\n// WithIdleTimeout sets idle timeout.\n//\n// Close the connection when the successive request timeout (in keepalive mode).\n// Set this to protect server from misbehavior clients.\nfunc WithIdleTimeout(t time.Duration) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.IdleTimeout = t\n\t}}\n}\n\n// WithRedirectTrailingSlash sets redirectTrailingSlash.\n//\n// Enables automatic redirection if the current route can't be matched but a\n// handler for the path with (without) the trailing slash exists.\n// For example if /foo/ is requested but a route only exists for /foo, the\n// client is redirected to /foo with http status code 301 for GET requests\n// and 307 for all other request methods.\nfunc WithRedirectTrailingSlash(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.RedirectTrailingSlash = b\n\t}}\n}\n\n// WithRedirectFixedPath sets redirectFixedPath.\n//\n// If enabled, the router tries to fix the current request path, if no\n// handle is registered for it.\n// First superfluous path elements like ../ or // are removed.\n// Afterwards the router does a case-insensitive lookup of the cleaned path.\n// If a handle can be found for this route, the router makes a redirection\n// to the corrected path with status code 301 for GET requests and 308 for\n// all other request methods.\n// For example /FOO and /..//Foo could be redirected to /foo.\n// RedirectTrailingSlash is independent of this option.\nfunc WithRedirectFixedPath(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.RedirectFixedPath = b\n\t}}\n}\n\n// WithHandleMethodNotAllowed sets handleMethodNotAllowed.\n//\n// If enabled, the router checks if another method is allowed for the\n// current route, if the current request can not be routed.\n// If this is the case, the request is answered with 'Method Not Allowed'\n// and HTTP status code 405.\n// If no other Method is allowed, the request is delegated to the NotFound\n// handler.\nfunc WithHandleMethodNotAllowed(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.HandleMethodNotAllowed = b\n\t}}\n}\n\n// WithUseRawPath sets useRawPath.\n//\n// If enabled, the url.RawPath will be used to find parameters.\nfunc WithUseRawPath(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.UseRawPath = b\n\t}}\n}\n\n// WithRemoveExtraSlash sets removeExtraSlash.\n//\n// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.\n// If UseRawPath is false (by default), the RemoveExtraSlash effectively is true,\n// as url.Path gonna be used, which is already cleaned.\nfunc WithRemoveExtraSlash(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.RemoveExtraSlash = b\n\t}}\n}\n\n// WithUnescapePathValues sets unescapePathValues.\n//\n// If true, the path value will be unescaped.\n// If UseRawPath is false (by default), the UnescapePathValues effectively is true,\n// as url.Path gonna be used, which is already unescaped.\nfunc WithUnescapePathValues(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.UnescapePathValues = b\n\t}}\n}\n\n// WithDisablePreParseMultipartForm sets disablePreParseMultipartForm.\n//\n// This option is useful for servers that desire to treat\n// multipart form data as a binary blob, or choose when to parse the data.\n// Server pre parses multipart form data by default.\nfunc WithDisablePreParseMultipartForm(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.DisablePreParseMultipartForm = b\n\t}}\n}\n\n// WithHostPorts sets listening address.\nfunc WithHostPorts(hp string) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.Addr = hp\n\t}}\n}\n\n// WithListener sets the listener to use.\n//\n// If set, the server will use this listener instead of creating a new one.\n// This is useful for custom listener implementations or testing.\n// Note: This will update Network and Addr based on the listener's address,\n// and reset ListenConfig since it's not needed when a listener is provided.\n//\n// WARNING: Custom net.Listener implementations may not be supported by cloudwego/netpoll.\n// If your custom listener doesn't support netpoll, you need to explicitly set the transporter to the standard library:\n//\n//\tWithListener(customListener), WithTransport(standard.NewTransporter)\nfunc WithListener(ln net.Listener) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.Listener = ln\n\t\to.Network = ln.Addr().Network()\n\t\to.Addr = ln.Addr().String()\n\t\to.ListenConfig = nil\n\t}}\n}\n\n// WithBasePath sets basePath.Must be \"/\" prefix and suffix,If not the default concatenate \"/\"\nfunc WithBasePath(basePath string) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\t// Must be \"/\" prefix and suffix,If not the default concatenate \"/\"\n\t\tif !strings.HasPrefix(basePath, \"/\") {\n\t\t\tbasePath = \"/\" + basePath\n\t\t}\n\t\tif !strings.HasSuffix(basePath, \"/\") {\n\t\t\tbasePath = basePath + \"/\"\n\t\t}\n\t\to.BasePath = basePath\n\t}}\n}\n\n// WithMaxRequestBodySize sets the limitation of request body size. Unit: byte\n//\n// Body buffer which larger than this size will be put back into buffer poll.\nfunc WithMaxRequestBodySize(bs int) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.MaxRequestBodySize = bs\n\t}}\n}\n\n// WithMaxHeaderBytes sets the limitation of request header size. Unit: byte\n//\n// If the header size exceeds this value, an ErrHeaderTooLarge error will be returned\n// and the server will respond with HTTP 431 Request Header Fields Too Large.\n//\n// Default: 1MB (1 << 20 bytes)\nfunc WithMaxHeaderBytes(size int) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.MaxHeaderBytes = size\n\t}}\n}\n\n// WithMaxKeepBodySize sets max size of request/response body to keep when recycled. Unit: byte\n//\n// Body buffer which larger than this size will be put back into buffer poll.\n// Note: If memory pressure is high, try setting the value to 0.\nfunc WithMaxKeepBodySize(bs int) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.MaxKeepBodySize = bs\n\t}}\n}\n\n// WithGetOnly sets whether accept GET request only. Default: false\nfunc WithGetOnly(isOnly bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.GetOnly = isOnly\n\t}}\n}\n\n// WithKeepAlive sets Whether use long connection. Default: true\nfunc WithKeepAlive(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.DisableKeepalive = !b\n\t}}\n}\n\n// WithStreamBody determines whether read body in stream or not.\n//\n// StreamRequestBody enables streaming request body,\n// and calls the handler sooner when given body is\n// larger than the current limit.\nfunc WithStreamBody(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.StreamRequestBody = b\n\t}}\n}\n\n// WithNetwork sets network. Support \"tcp\", \"udp\", \"unix\"(unix domain socket).\nfunc WithNetwork(nw string) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.Network = nw\n\t}}\n}\n\n// WithExitWaitTime sets timeout for graceful shutdown.\n//\n// The server may exit ahead after all connections closed.\n// All responses after shutdown will be added 'Connection: close' header.\nfunc WithExitWaitTime(timeout time.Duration) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.ExitWaitTimeout = timeout\n\t}}\n}\n\n// WithTLS sets TLS config to start a tls server.\n//\n// NOTE: If a tls server is started, it won't accept non-tls request.\nfunc WithTLS(cfg *tls.Config) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\t// If there is no explicit transporter, change it to standard one. Netpoll do not support tls yet.\n\t\tif o.TransporterNewer == nil {\n\t\t\to.TransporterNewer = standard.NewTransporter\n\t\t}\n\t\to.TLS = cfg\n\t}}\n}\n\n// WithListenConfig sets listener config.\nfunc WithListenConfig(l *net.ListenConfig) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.ListenConfig = l\n\t}}\n}\n\n// WithTransport sets which network library to use.\nfunc WithTransport(transporter func(options *config.Options) network.Transporter) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.TransporterNewer = transporter\n\t}}\n}\n\n// WithAltTransport sets which network library to use as an alternative transporter(need to be implemented by specific transporter).\nfunc WithAltTransport(transporter func(options *config.Options) network.Transporter) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.AltTransporterNewer = transporter\n\t}}\n}\n\n// WithH2C sets whether enable H2C.\nfunc WithH2C(enable bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.H2C = enable\n\t}}\n}\n\n// WithReadBufferSize sets the size of each read buffer node in standard transport.\n// NOTE: this cannot limit the header size.\nfunc WithReadBufferSize(size int) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.ReadBufferSize = size\n\t}}\n}\n\n// WithALPN sets whether enable ALPN.\nfunc WithALPN(enable bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.ALPN = enable\n\t}}\n}\n\n// WithTracer adds tracer to server.\nfunc WithTracer(t tracer.Tracer) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.Tracers = append(o.Tracers, t)\n\t}}\n}\n\n// WithTraceLevel sets the level trace.\nfunc WithTraceLevel(level stats.Level) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.TraceLevel = level\n\t}}\n}\n\n// WithRegistry sets the registry and registry's info\nfunc WithRegistry(r registry.Registry, info *registry.Info) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.Registry = r\n\t\to.RegistryInfo = info\n\t}}\n}\n\n// WithAutoReloadRender sets the config of auto reload render.\n// If auto reload render is enabled:\n// 1. interval = 0 means reload render according to file watch mechanism.(recommended)\n// 2. interval > 0 means reload render every interval.\nfunc WithAutoReloadRender(b bool, interval time.Duration) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.AutoReloadRender = b\n\t\to.AutoReloadInterval = interval\n\t}}\n}\n\n// WithDisablePrintRoute sets whether disable debugPrintRoute\n// If we don't set it, it will default to false\nfunc WithDisablePrintRoute(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.DisablePrintRoute = b\n\t}}\n}\n\n// WithOnAccept sets the callback function when a new connection is accepted but cannot\n// receive data in netpoll. In go net, it will be called before converting tls connection\nfunc WithOnAccept(fn func(conn net.Conn) context.Context) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.OnAccept = fn\n\t}}\n}\n\n// WithOnConnect sets the onConnect function. It can received data from connection in netpoll.\n// In go net, it will be called after converting tls connection.\nfunc WithOnConnect(fn func(ctx context.Context, conn network.Conn) context.Context) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.OnConnect = fn\n\t}}\n}\n\n// WithBindConfig sets bind config.\nfunc WithBindConfig(bc *binding.BindConfig) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.BindConfig = bc\n\t}}\n}\n\n// WithValidateConfig sets validate config.\n//\n// Deprecated: Use WithCustomValidatorFunc with a custom validation function instead.\nfunc WithValidateConfig(vc *binding.ValidateConfig) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.ValidateConfig = vc\n\t}}\n}\n\n// WithCustomBinder sets customized Binder.\n//\n// Priority: CustomBinder has the highest priority and will override any BindConfig\n// and CustomValidator settings when present. If CustomBinder is set, both the default\n// binder initialization and validator initialization are completely bypassed.\n//\n// Priority order (highest to lowest):\n//  1. CustomBinder (this option) - completely overrides all binding and validation logic\n//  2. CustomValidator/WithCustomValidatorFunc - sets custom validation for default binder\n//  3. BindConfig - configures the default binder behavior\n//  4. ValidateConfig - legacy validation configuration (deprecated)\n//  5. Default binding and validation behavior\n//\n// Note: When CustomBinder is used, validation logic must be implemented within the\n// custom binder's BindAndValidate method, as CustomValidator is ignored.\nfunc WithCustomBinder(b binding.Binder) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.CustomBinder = b\n\t}}\n}\n\n// WithCustomValidator sets customized StructValidator.\n//\n// Deprecated: Use WithCustomValidatorFunc instead.\nfunc WithCustomValidator(b binding.StructValidator) config.Option {\n\treturn WithCustomValidatorFunc(binding.MakeValidatorFunc(b))\n}\n\n// WithCustomValidatorFunc sets customized validator function.\nfunc WithCustomValidatorFunc(vf binding.ValidatorFunc) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.CustomValidator = vf\n\t}}\n}\n\n// WithDisableHeaderNamesNormalizing is used to set whether disable header names normalizing.\nfunc WithDisableHeaderNamesNormalizing(disable bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.DisableHeaderNamesNormalizing = disable\n\t}}\n}\n\nfunc WithDisableDefaultDate(disable bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.NoDefaultDate = disable\n\t}}\n}\n\nfunc WithDisableDefaultContentType(disable bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.NoDefaultContentType = disable\n\t}}\n}\n\n// WithSenseClientDisconnection sets the ability to sense client disconnections.\n// If we don't set it, it will default to false.\n// There are two issues to note when using this option:\n// 1. Warning: It only applies to netpoll.\n// 2. After opening, the context.Context in the request will be cancelled.\n//\n//\tExample:\n//\tserver.Default(\n//\tserver.WithSenseClientDisconnection(true),\n//\t)\nfunc WithSenseClientDisconnection(b bool) config.Option {\n\treturn config.Option{F: func(o *config.Options) {\n\t\to.SenseClientDisconnection = b\n\t}}\n}\n"
  },
  {
    "path": "pkg/app/server/option_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage server\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"reflect\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/registry\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/stats\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\nfunc TestOptions(t *testing.T) {\n\tinfo := &registry.Info{\n\t\tServiceName: \"hertz.test.api\",\n\t\tWeight:      10,\n\t\tAddr:        utils.NewNetAddr(\"tcp\", \":8888\"),\n\t}\n\tcfg := &net.ListenConfig{Control: func(network, address string, c syscall.RawConn) error {\n\t\treturn c.Control(func(fd uintptr) {})\n\t}}\n\ttransporter := func(options *config.Options) network.Transporter {\n\t\treturn &mockTransporter{}\n\t}\n\topt := config.NewOptions([]config.Option{\n\t\tWithReadTimeout(time.Second),\n\t\tWithWriteTimeout(time.Second),\n\t\tWithIdleTimeout(time.Second),\n\t\tWithKeepAliveTimeout(time.Second),\n\t\tWithRedirectTrailingSlash(false),\n\t\tWithRedirectFixedPath(true),\n\t\tWithHandleMethodNotAllowed(true),\n\t\tWithUseRawPath(true),\n\t\tWithRemoveExtraSlash(true),\n\t\tWithUnescapePathValues(false),\n\t\tWithDisablePreParseMultipartForm(true),\n\t\tWithStreamBody(false),\n\t\tWithHostPorts(\":8888\"),\n\t\tWithBasePath(\"/\"),\n\t\tWithMaxRequestBodySize(2),\n\t\tWithDisablePrintRoute(true),\n\t\tWithSenseClientDisconnection(true),\n\t\tWithNetwork(\"unix\"),\n\t\tWithExitWaitTime(time.Second),\n\t\tWithMaxKeepBodySize(500),\n\t\tWithGetOnly(true),\n\t\tWithKeepAlive(false),\n\t\tWithTLS(nil),\n\t\tWithH2C(true),\n\t\tWithReadBufferSize(100),\n\t\tWithALPN(true),\n\t\tWithTraceLevel(stats.LevelDisabled),\n\t\tWithRegistry(nil, info),\n\t\tWithAutoReloadRender(true, 5*time.Second),\n\t\tWithListenConfig(cfg),\n\t\tWithAltTransport(transporter),\n\t\tWithDisableHeaderNamesNormalizing(true),\n\t\tWithMaxHeaderBytes(1024),\n\t})\n\tassert.DeepEqual(t, opt.ReadTimeout, time.Second)\n\tassert.DeepEqual(t, opt.WriteTimeout, time.Second)\n\tassert.DeepEqual(t, opt.IdleTimeout, time.Second)\n\tassert.DeepEqual(t, opt.KeepAliveTimeout, time.Second)\n\tassert.DeepEqual(t, opt.RedirectTrailingSlash, false)\n\tassert.DeepEqual(t, opt.RedirectFixedPath, true)\n\tassert.DeepEqual(t, opt.HandleMethodNotAllowed, true)\n\tassert.DeepEqual(t, opt.UseRawPath, true)\n\tassert.DeepEqual(t, opt.RemoveExtraSlash, true)\n\tassert.DeepEqual(t, opt.UnescapePathValues, false)\n\tassert.DeepEqual(t, opt.DisablePreParseMultipartForm, true)\n\tassert.DeepEqual(t, opt.StreamRequestBody, false)\n\tassert.DeepEqual(t, opt.Addr, \":8888\")\n\tassert.DeepEqual(t, opt.BasePath, \"/\")\n\tassert.DeepEqual(t, opt.MaxRequestBodySize, 2)\n\tassert.DeepEqual(t, opt.DisablePrintRoute, true)\n\tassert.DeepEqual(t, opt.SenseClientDisconnection, true)\n\tassert.DeepEqual(t, opt.Network, \"unix\")\n\tassert.DeepEqual(t, opt.ExitWaitTimeout, time.Second)\n\tassert.DeepEqual(t, opt.MaxKeepBodySize, 500)\n\tassert.DeepEqual(t, opt.GetOnly, true)\n\tassert.DeepEqual(t, opt.DisableKeepalive, true)\n\tassert.DeepEqual(t, opt.H2C, true)\n\tassert.DeepEqual(t, opt.ReadBufferSize, 100)\n\tassert.DeepEqual(t, opt.ALPN, true)\n\tassert.DeepEqual(t, opt.TraceLevel, stats.LevelDisabled)\n\tassert.DeepEqual(t, opt.RegistryInfo, info)\n\tassert.DeepEqual(t, opt.Registry, nil)\n\tassert.DeepEqual(t, opt.AutoReloadRender, true)\n\tassert.DeepEqual(t, opt.AutoReloadInterval, 5*time.Second)\n\tassert.DeepEqual(t, opt.ListenConfig, cfg)\n\tassert.Assert(t, reflect.TypeOf(opt.AltTransporterNewer) == reflect.TypeOf(transporter))\n\tassert.DeepEqual(t, opt.DisableHeaderNamesNormalizing, true)\n\tassert.DeepEqual(t, opt.MaxHeaderBytes, 1024)\n}\n\nfunc TestDefaultOptions(t *testing.T) {\n\topt := config.NewOptions([]config.Option{})\n\tassert.DeepEqual(t, opt.ReadTimeout, time.Minute*3)\n\tassert.DeepEqual(t, opt.IdleTimeout, time.Minute*3)\n\tassert.DeepEqual(t, opt.KeepAliveTimeout, time.Minute)\n\tassert.DeepEqual(t, opt.RedirectTrailingSlash, true)\n\tassert.DeepEqual(t, opt.RedirectFixedPath, false)\n\tassert.DeepEqual(t, opt.HandleMethodNotAllowed, false)\n\tassert.DeepEqual(t, opt.UseRawPath, false)\n\tassert.DeepEqual(t, opt.RemoveExtraSlash, false)\n\tassert.DeepEqual(t, opt.UnescapePathValues, true)\n\tassert.DeepEqual(t, opt.DisablePreParseMultipartForm, false)\n\tassert.DeepEqual(t, opt.StreamRequestBody, false)\n\tassert.DeepEqual(t, opt.Addr, \":8888\")\n\tassert.DeepEqual(t, opt.BasePath, \"/\")\n\tassert.DeepEqual(t, opt.MaxRequestBodySize, 4*1024*1024)\n\tassert.DeepEqual(t, opt.GetOnly, false)\n\tassert.DeepEqual(t, opt.DisableKeepalive, false)\n\tassert.DeepEqual(t, opt.DisablePrintRoute, false)\n\tassert.DeepEqual(t, opt.SenseClientDisconnection, false)\n\tassert.DeepEqual(t, opt.Network, \"tcp\")\n\tassert.DeepEqual(t, opt.ExitWaitTimeout, time.Second*5)\n\tassert.DeepEqual(t, opt.MaxKeepBodySize, 4*1024*1024)\n\tassert.DeepEqual(t, opt.H2C, false)\n\tassert.DeepEqual(t, opt.ReadBufferSize, 4096)\n\tassert.DeepEqual(t, opt.ALPN, false)\n\tassert.DeepEqual(t, opt.Registry, registry.NoopRegistry)\n\tassert.DeepEqual(t, opt.AutoReloadRender, false)\n\tassert.Assert(t, opt.RegistryInfo == nil)\n\tassert.DeepEqual(t, opt.AutoReloadRender, false)\n\tassert.DeepEqual(t, opt.AutoReloadInterval, time.Duration(0))\n\tassert.DeepEqual(t, opt.DisableHeaderNamesNormalizing, false)\n\tassert.DeepEqual(t, opt.MaxHeaderBytes, 1<<20)\n}\n\nfunc TestWithListener(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\tcfg := &net.ListenConfig{}\n\topt := config.NewOptions([]config.Option{\n\t\tWithHostPorts(\"127.0.0.1:8888\"),\n\t\tWithNetwork(\"udp\"),\n\t\tWithListenConfig(cfg),\n\t\tWithListener(ln),\n\t})\n\n\t// Listener should be set\n\tassert.DeepEqual(t, opt.Listener, ln)\n\n\t// Network and Addr should be updated from listener\n\tassert.DeepEqual(t, opt.Network, ln.Addr().Network())\n\tassert.DeepEqual(t, opt.Addr, ln.Addr().String())\n\n\t// ListenConfig should be reset\n\tassert.Assert(t, opt.ListenConfig == nil)\n}\n\ntype mockTransporter struct{}\n\nfunc (m *mockTransporter) ListenAndServe(onData network.OnData) (err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockTransporter) Close() error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockTransporter) Shutdown(ctx context.Context) error {\n\tpanic(\"implement me\")\n}\n"
  },
  {
    "path": "pkg/app/server/registry/registry.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage registry\n\nimport \"net\"\n\nconst (\n\tDefaultWeight = 10\n)\n\n// Registry is extension interface of service registry.\ntype Registry interface {\n\tRegister(info *Info) error\n\tDeregister(info *Info) error\n}\n\n// Info is used for registry.\n// The fields are just suggested, which is used depends on design.\ntype Info struct {\n\t// ServiceName will be set in hertz by default\n\tServiceName string\n\t// Addr will be set in hertz by default\n\tAddr net.Addr\n\t// Weight will be set in hertz by default\n\tWeight int\n\t// extend other infos with Tags.\n\tTags map[string]string\n}\n\n// NoopRegistry is an empty implement of Registry\nvar NoopRegistry Registry = &noopRegistry{}\n\n// NoopRegistry\ntype noopRegistry struct{}\n\nfunc (e noopRegistry) Register(*Info) error {\n\treturn nil\n}\n\nfunc (e noopRegistry) Deregister(*Info) error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/app/server/registry/registry_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage registry\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestNoopRegistry(t *testing.T) {\n\treg := noopRegistry{}\n\tassert.Nil(t, reg.Deregister(&Info{}))\n\tassert.Nil(t, reg.Register(&Info{}))\n}\n"
  },
  {
    "path": "pkg/app/server/render/data.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage render\n\nimport \"github.com/cloudwego/hertz/pkg/protocol\"\n\n// Data contains ContentType and bytes data.\ntype Data struct {\n\tContentType string\n\tData        []byte\n}\n\n// Render (Data) writes data with custom ContentType.\nfunc (r Data) Render(resp *protocol.Response) (err error) {\n\tr.WriteContentType(resp)\n\tresp.AppendBody(r.Data)\n\treturn\n}\n\n// WriteContentType (Data) writes custom ContentType.\nfunc (r Data) WriteContentType(resp *protocol.Response) {\n\twriteContentType(resp, r.ContentType)\n}\n"
  },
  {
    "path": "pkg/app/server/render/doc.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The files in render package are forked from gin[github.com/gin-gonic/gin],\n// and we keep the original Copyright[Copyright 2014 gin authors] and License of gin for those files.\n// We also need to modify as we need, the modifications are Copyright of 2022 CloudWeGo Authors.\n// Thanks for gin authors! Below is the source code information:\n// \t\tRepo: github.com/gin-gonic/gin\n//\t\tForked Version: v1.7.7\n\npackage render\n"
  },
  {
    "path": "pkg/app/server/render/html.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage render\n\nimport (\n\t\"html/template\"\n\t\"log\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/fsnotify/fsnotify\"\n)\n\n// Delims represents a set of Left and Right delimiters for HTML template rendering.\ntype Delims struct {\n\t// Left delimiter, defaults to {{.\n\tLeft string\n\t// Right delimiter, defaults to }}.\n\tRight string\n}\n\n// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.\ntype HTMLRender interface {\n\t// Instance returns an HTML instance.\n\tInstance(string, interface{}) Render\n\tClose() error\n}\n\n// HTMLProduction contains template reference and its delims.\ntype HTMLProduction struct {\n\tTemplate *template.Template\n}\n\n// HTML contains template reference and its name with given interface object.\ntype HTML struct {\n\tTemplate *template.Template\n\tName     string\n\tData     interface{}\n}\n\nvar htmlContentType = \"text/html; charset=utf-8\"\n\n// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.\nfunc (r HTMLProduction) Instance(name string, data interface{}) Render {\n\treturn HTML{\n\t\tTemplate: r.Template,\n\t\tName:     name,\n\t\tData:     data,\n\t}\n}\n\nfunc (r HTMLProduction) Close() error {\n\treturn nil\n}\n\n// Render (HTML) executes template and writes its result with custom ContentType for response.\nfunc (r HTML) Render(resp *protocol.Response) error {\n\tr.WriteContentType(resp)\n\n\tif r.Name == \"\" {\n\t\treturn r.Template.Execute(resp.BodyWriter(), r.Data)\n\t}\n\treturn r.Template.ExecuteTemplate(resp.BodyWriter(), r.Name, r.Data)\n}\n\n// WriteContentType (HTML) writes HTML ContentType.\nfunc (r HTML) WriteContentType(resp *protocol.Response) {\n\twriteContentType(resp, htmlContentType)\n}\n\ntype HTMLDebug struct {\n\tsync.Once\n\tTemplate        *template.Template\n\tRefreshInterval time.Duration\n\n\tFiles   []string\n\tFuncMap template.FuncMap\n\tDelims  Delims\n\n\treloadCh chan struct{}\n\twatcher  *fsnotify.Watcher\n}\n\nfunc (h *HTMLDebug) Instance(name string, data interface{}) Render {\n\th.Do(func() {\n\t\th.startChecker()\n\t})\n\n\tselect {\n\tcase <-h.reloadCh:\n\t\th.reload()\n\tdefault:\n\t}\n\n\treturn HTML{\n\t\tTemplate: h.Template,\n\t\tName:     name,\n\t\tData:     data,\n\t}\n}\n\nfunc (h *HTMLDebug) Close() error {\n\tif h.watcher == nil {\n\t\treturn nil\n\t}\n\treturn h.watcher.Close()\n}\n\nfunc (h *HTMLDebug) reload() {\n\th.Template = template.Must(template.New(\"\").\n\t\tDelims(h.Delims.Left, h.Delims.Right).\n\t\tFuncs(h.FuncMap).\n\t\tParseFiles(h.Files...))\n}\n\nfunc (h *HTMLDebug) startChecker() {\n\th.reloadCh = make(chan struct{})\n\n\tif h.RefreshInterval > 0 {\n\t\tgo func() {\n\t\t\thlog.SystemLogger().Debugf(\"[HTMLDebug] HTML template reloader started with interval %v\", h.RefreshInterval)\n\t\t\tfor range time.Tick(h.RefreshInterval) {\n\t\t\t\thlog.SystemLogger().Debugf(\"[HTMLDebug] triggering HTML template reloader\")\n\t\t\t\th.reloadCh <- struct{}{}\n\t\t\t\thlog.SystemLogger().Debugf(\"[HTMLDebug] HTML template has been reloaded, next reload in %v\", h.RefreshInterval)\n\t\t\t}\n\t\t}()\n\t\treturn\n\t}\n\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\th.watcher = watcher\n\tfor _, f := range h.Files {\n\t\terr := watcher.Add(f)\n\t\thlog.SystemLogger().Debugf(\"[HTMLDebug] watching file: %s\", f)\n\t\tif err != nil {\n\t\t\thlog.SystemLogger().Errorf(\"[HTMLDebug] add watching file: %s, error happened: %v\", f, err)\n\t\t}\n\n\t}\n\n\tgo func() {\n\t\thlog.SystemLogger().Debugf(\"[HTMLDebug] HTML template reloader started with file watcher\")\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase event, ok := <-watcher.Events:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif event.Op&fsnotify.Write == fsnotify.Write {\n\t\t\t\t\thlog.SystemLogger().Debugf(\"[HTMLDebug] modified file: %s, html render template will be reloaded at the next rendering\", event.Name)\n\t\t\t\t\th.reloadCh <- struct{}{}\n\t\t\t\t\thlog.SystemLogger().Debugf(\"[HTMLDebug] HTML template has been reloaded\")\n\t\t\t\t}\n\t\t\tcase err, ok := <-watcher.Errors:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\thlog.SystemLogger().Errorf(\"error happened when watching the rendering files: %v\", err)\n\t\t\t}\n\t\t}\n\t}()\n}\n"
  },
  {
    "path": "pkg/app/server/render/html_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage render\n\nimport (\n\t\"html/template\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\nfunc TestHTMLDebug_StartChecker_timer(t *testing.T) {\n\trender := &HTMLDebug{\n\t\tRefreshInterval: time.Second,\n\t\tDelims:          Delims{Left: \"{[{\", Right: \"}]}\"},\n\t\tFuncMap:         template.FuncMap{},\n\t\tFiles:           []string{\"../../../common/testdata/template/index.tmpl\"},\n\t}\n\tselect {\n\tcase <-render.reloadCh:\n\t\tt.Fatalf(\"should not be triggered\")\n\tdefault:\n\t}\n\trender.startChecker()\n\tselect {\n\tcase <-time.After(render.RefreshInterval + 500*time.Millisecond):\n\t\tt.Fatalf(\"should be triggered in 1.5 second\")\n\tcase <-render.reloadCh:\n\t\trender.reload()\n\t}\n}\n\nfunc TestHTMLDebug_StartChecker_fs_watcher(t *testing.T) {\n\tf, _ := ioutil.TempFile(\"./\", \"test.tmpl\")\n\tdefer func() {\n\t\tf.Close()\n\t\tos.Remove(f.Name())\n\t}()\n\trender := &HTMLDebug{Files: []string{f.Name()}}\n\tselect {\n\tcase <-render.reloadCh:\n\t\tt.Fatalf(\"should not be triggered\")\n\tdefault:\n\t}\n\trender.startChecker()\n\tf.Write([]byte(\"hello\"))\n\tf.Sync()\n\tselect {\n\tcase <-time.After(50 * time.Millisecond):\n\t\tt.Fatalf(\"should be triggered immediately\")\n\tcase <-render.reloadCh:\n\t}\n\tselect {\n\tcase <-render.reloadCh:\n\t\tt.Fatalf(\"should not be triggered\")\n\tdefault:\n\t}\n}\n\nfunc TestRenderHTML(t *testing.T) {\n\tresp := &protocol.Response{}\n\n\ttmpl := template.Must(template.New(\"\").\n\t\tDelims(\"{[{\", \"}]}\").\n\t\tFuncs(template.FuncMap{}).\n\t\tParseFiles(\"../../../common/testdata/template/index.tmpl\"))\n\n\tr := &HTMLProduction{Template: tmpl}\n\n\thtml := r.Instance(\"index.tmpl\", utils.H{\n\t\t\"title\": \"Main website\",\n\t})\n\n\terr := r.Close()\n\tassert.Nil(t, err)\n\n\thtml.WriteContentType(resp)\n\tassert.DeepEqual(t, []byte(\"text/html; charset=utf-8\"), resp.Header.Peek(\"Content-Type\"))\n\n\terr = html.Render(resp)\n\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"text/html; charset=utf-8\"), resp.Header.Peek(\"Content-Type\"))\n\tassert.DeepEqual(t, []byte(\"<html><h1>Main website</h1></html>\"), resp.Body())\n\n\trespDebug := &protocol.Response{}\n\n\trDebug := &HTMLDebug{\n\t\tTemplate: tmpl,\n\t\tDelims:   Delims{Left: \"{[{\", Right: \"}]}\"},\n\t\tFuncMap:  template.FuncMap{},\n\t\tFiles:    []string{\"../../../common/testdata/template/index.tmpl\"},\n\t}\n\n\thtmlDebug := rDebug.Instance(\"index.tmpl\", utils.H{\n\t\t\"title\": \"Main website\",\n\t})\n\n\terr = rDebug.Close()\n\tassert.Nil(t, err)\n\n\thtmlDebug.WriteContentType(respDebug)\n\tassert.DeepEqual(t, []byte(\"text/html; charset=utf-8\"), respDebug.Header.Peek(\"Content-Type\"))\n\n\terr = htmlDebug.Render(respDebug)\n\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"text/html; charset=utf-8\"), respDebug.Header.Peek(\"Content-Type\"))\n\tassert.DeepEqual(t, []byte(\"<html><h1>Main website</h1></html>\"), respDebug.Body())\n}\n"
  },
  {
    "path": "pkg/app/server/render/json.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage render\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\n\thjson \"github.com/cloudwego/hertz/pkg/common/json\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\n// JSONMarshaler customize json.Marshal as you like\ntype JSONMarshaler func(v interface{}) ([]byte, error)\n\nvar jsonMarshalFunc JSONMarshaler\n\nfunc init() {\n\tResetJSONMarshal(hjson.Marshal)\n}\n\nfunc ResetJSONMarshal(fn JSONMarshaler) {\n\tjsonMarshalFunc = fn\n}\n\nfunc ResetStdJSONMarshal() {\n\tResetJSONMarshal(json.Marshal)\n}\n\n// JSONRender JSON contains the given interface object.\ntype JSONRender struct {\n\tData interface{}\n}\n\nvar jsonContentType = \"application/json; charset=utf-8\"\n\n// Render (JSON) writes data with custom ContentType.\nfunc (r JSONRender) Render(resp *protocol.Response) error {\n\twriteContentType(resp, jsonContentType)\n\tjsonBytes, err := jsonMarshalFunc(r.Data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresp.AppendBody(jsonBytes)\n\treturn nil\n}\n\n// WriteContentType (JSON) writes JSON ContentType.\nfunc (r JSONRender) WriteContentType(resp *protocol.Response) {\n\twriteContentType(resp, jsonContentType)\n}\n\n// PureJSON contains the given interface object.\ntype PureJSON struct {\n\tData interface{}\n}\n\n// Render (JSON) writes data with custom ContentType.\nfunc (r PureJSON) Render(resp *protocol.Response) (err error) {\n\tr.WriteContentType(resp)\n\tbuffer := new(bytes.Buffer)\n\tencoder := json.NewEncoder(buffer)\n\tencoder.SetEscapeHTML(false)\n\terr = encoder.Encode(r.Data)\n\tif err != nil {\n\t\treturn\n\t}\n\tresp.AppendBody(buffer.Bytes())\n\treturn\n}\n\n// WriteContentType (JSON) writes JSON ContentType.\nfunc (r PureJSON) WriteContentType(resp *protocol.Response) {\n\twriteContentType(resp, jsonContentType)\n}\n\n// IndentedJSON contains the given interface object.\ntype IndentedJSON struct {\n\tData interface{}\n}\n\n// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.\nfunc (r IndentedJSON) Render(resp *protocol.Response) (err error) {\n\twriteContentType(resp, jsonContentType)\n\tjsonBytes, err := jsonMarshalFunc(r.Data)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar buf bytes.Buffer\n\terr = json.Indent(&buf, jsonBytes, \"\", \"    \")\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp.AppendBody(buf.Bytes())\n\treturn nil\n}\n\n// WriteContentType (JSON) writes JSON ContentType.\nfunc (r IndentedJSON) WriteContentType(resp *protocol.Response) {\n\twriteContentType(resp, jsonContentType)\n}\n"
  },
  {
    "path": "pkg/app/server/render/json_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage render\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc Test_ResetStdJSONMarshal(t *testing.T) {\n\ttable := map[string]string{\n\t\t\"testA\": \"hello\",\n\t\t\"B\":     \"world\",\n\t}\n\tResetStdJSONMarshal()\n\tjsonBytes, err := jsonMarshalFunc(table)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !strings.Contains(string(jsonBytes), \"\\\"B\\\":\\\"world\\\"\") || !strings.Contains(string(jsonBytes), \"\\\"testA\\\":\\\"hello\\\"\") {\n\t\tt.Fatal(\"marshal struct is not equal to the string\")\n\t}\n}\n\nfunc Test_DefaultJSONMarshal(t *testing.T) {\n\ttable := map[string]string{\n\t\t\"testA\": \"hello\",\n\t\t\"B\":     \"world\",\n\t}\n\tjsonBytes, err := jsonMarshalFunc(table)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !strings.Contains(string(jsonBytes), \"\\\"B\\\":\\\"world\\\"\") || !strings.Contains(string(jsonBytes), \"\\\"testA\\\":\\\"hello\\\"\") {\n\t\tt.Fatal(\"marshal struct is not equal to the string\")\n\t}\n}\n"
  },
  {
    "path": "pkg/app/server/render/protobuf.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage render\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// ProtoBuf contains the given interface object.\ntype ProtoBuf struct {\n\tData interface{}\n}\n\nvar protobufContentType = \"application/x-protobuf\"\n\n// Render (ProtoBuf) marshals the given interface object and writes data with custom ContentType.\nfunc (r ProtoBuf) Render(resp *protocol.Response) error {\n\tr.WriteContentType(resp)\n\tbytes, err := proto.Marshal(r.Data.(proto.Message))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresp.AppendBody(bytes)\n\treturn nil\n}\n\n// WriteContentType (ProtoBuf) writes ProtoBuf ContentType.\nfunc (r ProtoBuf) WriteContentType(resp *protocol.Response) {\n\twriteContentType(resp, protobufContentType)\n}\n"
  },
  {
    "path": "pkg/app/server/render/render.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage render\n\nimport \"github.com/cloudwego/hertz/pkg/protocol\"\n\n// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.\ntype Render interface {\n\t// Render writes data with custom ContentType.\n\t// Do not panic inside, RequestContext will handle it.\n\tRender(resp *protocol.Response) error\n\t// WriteContentType writes custom ContentType.\n\tWriteContentType(resp *protocol.Response)\n}\n\nvar (\n\t_ Render = JSONRender{}\n\t_ Render = String{}\n\t_ Render = Data{}\n)\n\nfunc writeContentType(resp *protocol.Response, value string) {\n\tresp.Header.SetContentType(value)\n}\n"
  },
  {
    "path": "pkg/app/server/render/render_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage render\n\nimport (\n\t\"encoding/xml\"\n\t\"testing\"\n\n\t\"github.com/bytedance/sonic\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/testdata/proto\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\ntype xmlmap map[string]interface{}\n\n// Allows type H to be used with xml.Marshal\nfunc (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {\n\tstart.Name = xml.Name{\n\t\tSpace: \"\",\n\t\tLocal: \"map\",\n\t}\n\tif err := e.EncodeToken(start); err != nil {\n\t\treturn err\n\t}\n\tfor key, value := range h {\n\t\telem := xml.StartElement{\n\t\t\tName: xml.Name{Space: \"\", Local: key},\n\t\t\tAttr: []xml.Attr{},\n\t\t}\n\t\tif err := e.EncodeElement(value, elem); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn e.EncodeToken(xml.EndElement{Name: start.Name})\n}\n\nfunc TestRenderJSON(t *testing.T) {\n\tresp := &protocol.Response{}\n\tdata := map[string]interface{}{\n\t\t\"foo\":  \"bar\",\n\t\t\"html\": \"<b>\",\n\t}\n\n\t(JSONRender{data}).WriteContentType(resp)\n\tassert.DeepEqual(t, []byte(consts.MIMEApplicationJSONUTF8), resp.Header.Peek(\"Content-Type\"))\n\n\terr := (JSONRender{data}).Render(resp)\n\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"{\\\"foo\\\":\\\"bar\\\",\\\"html\\\":\\\"\\\\u003cb\\\\u003e\\\"}\"), resp.Body())\n\tassert.DeepEqual(t, []byte(consts.MIMEApplicationJSONUTF8), resp.Header.Peek(\"Content-Type\"))\n}\n\nfunc TestRenderJSONError(t *testing.T) {\n\tresp := &protocol.Response{}\n\tdata := make(chan int)\n\n\terr := (JSONRender{data}).Render(resp)\n\t// json: unsupported type: chan int\n\tassert.NotNil(t, err)\n}\n\nfunc TestRenderPureJSON(t *testing.T) {\n\tresp := &protocol.Response{}\n\tdata := map[string]interface{}{\n\t\t\"foo\":  \"bar\",\n\t\t\"html\": \"<b>\",\n\t}\n\n\t(PureJSON{data}).WriteContentType(resp)\n\tassert.DeepEqual(t, []byte(consts.MIMEApplicationJSONUTF8), resp.Header.Peek(\"Content-Type\"))\n\n\terr := (PureJSON{data}).Render(resp)\n\n\tassert.Nil(t, err)\n\n\tassert.DeepEqual(t, []byte(\"{\\\"foo\\\":\\\"bar\\\",\\\"html\\\":\\\"<b>\\\"}\\n\"), resp.Body())\n\tassert.DeepEqual(t, []byte(consts.MIMEApplicationJSONUTF8), resp.Header.Peek(\"Content-Type\"))\n}\n\nfunc TestRenderPureJSONError(t *testing.T) {\n\tresp := &protocol.Response{}\n\tdata := make(chan int)\n\n\terr := (PureJSON{data}).Render(resp)\n\t// json: unsupported type: chan int\n\tassert.NotNil(t, err)\n}\n\nfunc TestRenderProtobuf(t *testing.T) {\n\tresp := &protocol.Response{}\n\tdata := proto.TestStruct{Body: []byte(\"Hello World\")}\n\n\t(ProtoBuf{&data}).WriteContentType(resp)\n\tassert.DeepEqual(t, []byte(\"application/x-protobuf\"), resp.Header.Peek(\"Content-Type\"))\n\n\terr := (ProtoBuf{&data}).Render(resp)\n\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"\\n\\vHello World\"), resp.Body())\n\tassert.DeepEqual(t, []byte(\"application/x-protobuf\"), resp.Header.Peek(\"Content-Type\"))\n}\n\nfunc TestRenderProtobufError(t *testing.T) {\n\tresp := &protocol.Response{}\n\tdata := proto.Test{}\n\n\terr := (ProtoBuf{&data}).Render(resp)\n\n\tassert.NotNil(t, err)\n}\n\nfunc TestRenderString(t *testing.T) {\n\tresp := &protocol.Response{}\n\n\t(String{\n\t\tFormat: \"hello %s %d\",\n\t\tData:   []interface{}{},\n\t}).WriteContentType(resp)\n\tassert.DeepEqual(t, []byte(consts.MIMETextPlainUTF8), resp.Header.Peek(\"Content-Type\"))\n\n\terr := (String{\n\t\tFormat: \"hola %s %d\",\n\t\tData:   []interface{}{\"manu\", 2},\n\t}).Render(resp)\n\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"hola manu 2\"), resp.Body())\n\tassert.DeepEqual(t, []byte(consts.MIMETextPlainUTF8), resp.Header.Peek(\"Content-Type\"))\n}\n\nfunc TestRenderStringLenZero(t *testing.T) {\n\tresp := &protocol.Response{}\n\n\terr := (String{\n\t\tFormat: \"hola %s %d\",\n\t\tData:   []interface{}{},\n\t}).Render(resp)\n\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"hola %s %d\"), resp.Body())\n\tassert.DeepEqual(t, []byte(consts.MIMETextPlainUTF8), resp.Header.Peek(\"Content-Type\"))\n}\n\nfunc TestRenderData(t *testing.T) {\n\tresp := &protocol.Response{}\n\tdata := []byte(\"#!PNG some raw data\")\n\n\terr := (Data{\n\t\tContentType: \"image/png\",\n\t\tData:        data,\n\t}).Render(resp)\n\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"#!PNG some raw data\"), resp.Body())\n\tassert.DeepEqual(t, []byte(consts.MIMEImagePNG), resp.Header.Peek(\"Content-Type\"))\n}\n\nfunc TestRenderXML(t *testing.T) {\n\tresp := &protocol.Response{}\n\tdata := xmlmap{\n\t\t\"foo\": \"bar\",\n\t}\n\n\t(XML{data}).WriteContentType(resp)\n\tassert.DeepEqual(t, []byte(consts.MIMEApplicationXMLUTF8), resp.Header.Peek(\"Content-Type\"))\n\n\terr := (XML{data}).Render(resp)\n\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"<map><foo>bar</foo></map>\"), resp.Body())\n\tassert.DeepEqual(t, []byte(consts.MIMEApplicationXMLUTF8), resp.Header.Peek(\"Content-Type\"))\n}\n\nfunc TestRenderXMLError(t *testing.T) {\n\tresp := &protocol.Response{}\n\tdata := make(chan int)\n\n\terr := (XML{data}).Render(resp)\n\n\tassert.NotNil(t, err)\n}\n\nfunc TestRenderIndentedJSON(t *testing.T) {\n\tdata := map[string]interface{}{\n\t\t\"foo\":  \"bar\",\n\t\t\"html\": \"h1\",\n\t}\n\tt.Run(\"TestHeader\", func(t *testing.T) {\n\t\tresp := &protocol.Response{}\n\t\t(IndentedJSON{data}).WriteContentType(resp)\n\t\tassert.DeepEqual(t, []byte(consts.MIMEApplicationJSONUTF8), resp.Header.Peek(\"Content-Type\"))\n\t})\n\tt.Run(\"TestBody\", func(t *testing.T) {\n\t\tResetStdJSONMarshal()\n\t\tresp := &protocol.Response{}\n\t\terr := (IndentedJSON{data}).Render(resp)\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, []byte(\"{\\n    \\\"foo\\\": \\\"bar\\\",\\n    \\\"html\\\": \\\"h1\\\"\\n}\"), resp.Body())\n\t\tassert.DeepEqual(t, []byte(consts.MIMEApplicationJSONUTF8), resp.Header.Peek(\"Content-Type\"))\n\t\tResetJSONMarshal(sonic.Marshal)\n\t})\n\tt.Run(\"TestError\", func(t *testing.T) {\n\t\tresp := &protocol.Response{}\n\t\tch := make(chan int)\n\t\terr := (IndentedJSON{ch}).Render(resp)\n\t\tassert.NotNil(t, err)\n\t})\n}\n"
  },
  {
    "path": "pkg/app/server/render/text.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage render\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\n// String contains the given interface object slice and its format.\ntype String struct {\n\tFormat string\n\tData   []interface{}\n}\n\nvar plainContentType = \"text/plain; charset=utf-8\"\n\n// Render (String) writes data with custom ContentType.\nfunc (r String) Render(resp *protocol.Response) error {\n\twriteContentType(resp, plainContentType)\n\toutput := r.Format\n\tif len(r.Data) > 0 {\n\t\toutput = fmt.Sprintf(r.Format, r.Data...)\n\t}\n\tresp.AppendBodyString(output)\n\treturn nil\n}\n\n// WriteContentType (String) writes Plain ContentType.\nfunc (r String) WriteContentType(resp *protocol.Response) {\n\twriteContentType(resp, plainContentType)\n}\n"
  },
  {
    "path": "pkg/app/server/render/xml.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage render\n\nimport (\n\t\"encoding/xml\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\n// XML contains the given interface object.\ntype XML struct {\n\tData interface{}\n}\n\nvar xmlContentType = \"application/xml; charset=utf-8\"\n\n// Render (XML) encodes the given interface object and writes data with custom ContentType.\nfunc (r XML) Render(resp *protocol.Response) error {\n\twriteContentType(resp, xmlContentType)\n\txmlBytes, err := xml.Marshal(r.Data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresp.AppendBody(xmlBytes)\n\treturn nil\n}\n\n// WriteContentType (XML) writes XML ContentType for response.\nfunc (r XML) WriteContentType(w *protocol.Response) {\n\twriteContentType(w, xmlContentType)\n}\n"
  },
  {
    "path": "pkg/app/server/server_bench_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage server\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n)\n\nfunc BenchmarkServerHelloWorld(b *testing.B) {\n\tln := testutils.NewTestListener(b)\n\tdefer ln.Close()\n\n\th := Default(WithListener(ln), WithTransport(standard.NewTransporter))\n\th.GET(\"/hello\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.SetBodyString(\"hello world\")\n\t})\n\n\tgo h.Run()\n\twaitEngineRunning(h)\n\tdefer h.Engine.Close()\n\n\taddr := ln.Addr().String()\n\n\t// Pre-create connection pool with keep-alive\n\tconst poolSize = 10\n\tconnPool := make([]net.Conn, poolSize)\n\treaderPool := make([]*bufio.Reader, poolSize)\n\tfor i := 0; i < poolSize; i++ {\n\t\tconn, err := net.Dial(\"tcp\", addr)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"failed to dial: %s\", err)\n\t\t}\n\t\tconnPool[i] = conn\n\t\treaderPool[i] = bufio.NewReader(conn)\n\t\tdefer conn.Close()\n\t}\n\n\trequest := []byte(\"GET /hello HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tconn := connPool[i%poolSize]\n\t\treader := readerPool[i%poolSize]\n\t\t_, err := conn.Write(request)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"write error: %s\", err)\n\t\t}\n\t\t_, err = reader.Peek(1)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\t_, err = reader.Discard(reader.Buffered())\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/common/adaptor/handler.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage adaptor\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n)\n\n// HertzHandler converts a http.Handler to an app.HandlerFunc.\nfunc HertzHandler(h http.Handler) app.HandlerFunc {\n\treturn func(ctx context.Context, rc *app.RequestContext) {\n\t\t// creating http.Request\n\t\tr := &rc.Request\n\t\treq, err := http.NewRequestWithContext(ctx, methodstr(r.Method()), r.URI().String(), nil)\n\t\tif err != nil {\n\t\t\trc.SetStatusCode(consts.StatusInternalServerError)\n\t\t\trc.SetBodyString(err.Error())\n\t\t\treturn\n\t\t}\n\n\t\t// request Body & ContentLength\n\t\tstream := r.IsBodyStream()\n\t\tif stream && r.HasMultipartForm() && len(r.BodyBytes()) > 0 {\n\t\t\t// in this case, r.MultipartForm() called before this handler then we can not rely on stream body.\n\t\t\t// coz both r.Body() and r.BodyStream() will return nothing due to EOF of stream body.\n\t\t\t// we fix it by calling r.CloseBodyStream(), then r.Body() correctly returns bytes.\n\t\t\t// FIXME: This should be taken care of by `protocol.Request`\n\t\t\tr.CloseBodyStream()\n\t\t\tstream = false\n\t\t}\n\t\tif stream {\n\t\t\t// use BodyStream if possible to avoid OOM issue.\n\t\t\treq.Body = reader2closer(r.BodyStream())\n\t\t\treq.ContentLength = int64(r.Header.ContentLength())\n\t\t} else {\n\t\t\t// fast path for default cases with StreamRequestBody=false\n\t\t\tb := r.Body()\n\t\t\treq.Body = newBytesRWCloser(b)\n\t\t\treq.ContentLength = int64(len(b))\n\t\t}\n\n\t\t// request Header\n\t\tr.Header.VisitAll(func(k, v []byte) {\n\t\t\treq.Header[string(k)] = append(req.Header[string(k)], string(v))\n\t\t})\n\n\t\t// request other properties\n\t\tif s := r.Header.GetProtocol(); s != \"\" {\n\t\t\treq.Proto = s\n\t\t\treq.ProtoMajor, req.ProtoMinor, _ = parseHTTPVersion(s)\n\t\t}\n\t\treq.Close = r.ConnectionClose()\n\t\treq.RemoteAddr = rc.RemoteAddr().String()\n\t\treq.RequestURI = string(r.RequestURI())\n\t\tif tlsconn, ok := rc.GetConn().(network.ConnTLSer); ok {\n\t\t\tstate := tlsconn.ConnectionState()\n\t\t\treq.TLS = &state\n\t\t}\n\n\t\t// creating http.ResponseWriter\n\t\t//\n\t\t// coz it's server response\n\t\t// no need to copy anything from hertz Response\n\t\tw := &httpResponseWriter{rc: rc}\n\n\t\th.ServeHTTP(w, req)\n\t\tif w.hijacked != nil {\n\t\t\t// wait for hijacked conn to close before returning,\n\t\t\t// otherwise either hertz will close the conn\n\t\t\t// or netpoll may reuse the conn for next request.\n\t\t\t<-w.hijacked\n\t\t}\n\t}\n}\n\ntype httpResponseWriter struct {\n\trc     *app.RequestContext\n\theader http.Header\n\n\terr error\n\n\twroteHeader bool\n\tskipBody    bool\n\n\thijacked chan struct{} // != nil if hijacked\n}\n\nvar errConnHijacked = errors.New(\"hertz net/http adaptor: conn hijacked\")\n\nfunc (p *httpResponseWriter) Header() http.Header {\n\tif p.header != nil {\n\t\treturn p.header\n\t}\n\tp.header = make(map[string][]string)\n\treturn p.header\n}\n\n// Write implements http.ResponseWriter.Write\nfunc (p *httpResponseWriter) Write(b []byte) (n int, err error) {\n\tif p.hijacked != nil {\n\t\treturn 0, errConnHijacked\n\t}\n\tif !p.wroteHeader {\n\t\tp.WriteHeader(consts.StatusOK)\n\t}\n\tif p.err != nil {\n\t\treturn 0, p.err\n\t}\n\tif p.skipBody {\n\t\treturn len(b), nil\n\t}\n\tn, p.err = p.rc.Response.GetHijackWriter().Write(b)\n\treturn n, p.err\n}\n\n// WriteHeader implements http.ResponseWriter.WriteHeader\nfunc (p *httpResponseWriter) WriteHeader(statusCode int) {\n\tif p.wroteHeader || p.hijacked != nil {\n\t\treturn\n\t}\n\tp.wroteHeader = true\n\tr := &p.rc.Response\n\t// reset and check if user updates Content-Length\n\t// if we have no Content-Length, can only use chunked Transfer-Encoding\n\tr.Header.InitContentLengthWithValue(-1)\n\tfor k, vv := range p.header {\n\t\tfor _, v := range vv {\n\t\t\tr.Header.Add(k, v)\n\t\t}\n\t}\n\tw := p.rc.GetWriter()\n\tr.Header.SetStatusCode(statusCode)\n\tp.skipBody = r.Header.MustSkipContentLength() ||\n\t\tstring(p.rc.Request.Method()) == consts.MethodHead\n\tif p.skipBody {\n\t\t// set Content-Length: 0\n\t\tr.Header.SetCanonical(bytestr.StrContentLength, []byte(\"0\"))\n\t\t// skip all further writes,\n\t\t// must be set for hertz request loop or it would write header and body after handler returns\n\t\tr.HijackWriter(noopWriter{})\n\t\tp.err = resp.WriteHeader(&r.Header, w)\n\t} else if r.Header.ContentLength() < 0 {\n\t\t// For chunked encoding, write headers immediately\n\t\tcw := resp.NewChunkedBodyWriter(r, w)\n\t\tr.HijackWriter(cw)\n\t\ttype chunkedBodyWriter interface {\n\t\t\tWriteHeader() error\n\t\t}\n\t\tp.err = cw.(chunkedBodyWriter).WriteHeader()\n\t} else {\n\t\t// use Writer directly instead of keep buffering data in resp.BodyBuffer()\n\t\t// you never know how much data would be written to response\n\t\tr.HijackWriter(writer2writerExt(w))\n\t\tp.err = resp.WriteHeader(&r.Header, w)\n\t}\n}\n\nvar _ http.Flusher = (*httpResponseWriter)(nil)\n\n// Flush implements http.Flusher and captures any flush errors\nfunc (p *httpResponseWriter) Flush() {\n\tif p.err == nil {\n\t\tp.err = p.rc.GetWriter().Flush()\n\t}\n}\n\nvar _ http.Hijacker = (*httpResponseWriter)(nil)\n\n// Hijack implements http.Hijacker\nfunc (p *httpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\tif p.hijacked != nil {\n\t\treturn nil, nil, errConnHijacked\n\t}\n\tif p.err != nil {\n\t\treturn nil, nil, p.err\n\t}\n\t// If headers were already written, flush the buffer to avoid losing\n\t// any pending data before hijacking the connection\n\tif p.wroteHeader {\n\t\tif p.err = p.rc.GetWriter().Flush(); p.err != nil {\n\t\t\treturn nil, nil, p.err\n\t\t}\n\t}\n\tconn := newHijackedConn(p.rc.GetConn())\n\tp.hijacked = conn.closeCh\n\n\t// reset timeout if any\n\t_ = conn.SetReadTimeout(0)\n\t_ = conn.SetWriteTimeout(0)\n\n\t// make sure after handler returns:\n\t// * hertz won't reuse the conn\n\t// * hertz won't write any extra bytes to underlying conn\n\tp.rc.Response.Header.SetConnectionClose(true)\n\tp.rc.Response.HijackWriter(noopHijackWriter{})\n\n\treturn conn, bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)), nil\n}\n\ntype noopHijackWriter struct{}\n\nvar _ network.ExtWriter = noopHijackWriter{}\n\nfunc (noopHijackWriter) Write(b []byte) (int, error) {\n\treturn 0, errConnHijacked\n}\nfunc (noopHijackWriter) Flush() error    { return errConnHijacked }\nfunc (noopHijackWriter) Finalize() error { return nil }\n\ntype noopWriter struct{}\n\nvar _ network.ExtWriter = noopWriter{}\n\nfunc (noopWriter) Write(b []byte) (int, error) { return len(b), nil }\nfunc (noopWriter) Flush() error                { return nil }\nfunc (noopWriter) Finalize() error             { return nil }\n\ntype hijackedConn struct {\n\tnetwork.Conn\n\n\tcloseOnce sync.Once\n\tcloseCh   chan struct{}\n}\n\nfunc newHijackedConn(conn network.Conn) *hijackedConn {\n\tc := &hijackedConn{Conn: conn, closeCh: make(chan struct{})}\n\truntime.SetFinalizer(c, hijackedConnFinalizer)\n\treturn c\n}\n\nfunc hijackedConnFinalizer(c *hijackedConn) {\n\t_ = c.Close()\n}\n\nfunc (c *hijackedConn) Close() error {\n\truntime.SetFinalizer(c, nil)\n\terr := c.Conn.Close()\n\tc.closeOnce.Do(func() {\n\t\tclose(c.closeCh)\n\t})\n\treturn err\n}\n"
  },
  {
    "path": "pkg/common/adaptor/handler_test.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage adaptor\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"embed\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/route\"\n)\n\n//go:embed *\nvar adaptorFiles embed.FS\n\nfunc runEngine(onCreate func(*route.Engine)) (string, *route.Engine) {\n\tln := testutils.NewTestListener(&testing.T{})\n\topt := config.NewOptions(nil)\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tonCreate(engine)\n\tgo engine.Run()\n\ttestutils.WaitEngineRunning(engine)\n\treturn ln.Addr().String(), engine\n}\n\nfunc TestHertzHandler_BodyStream(t *testing.T) {\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\th := HertzHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer wg.Done()\n\t\tb := make([]byte, 100)\n\t\tfor i := 0; i < 3; i++ { // reading chunked data\n\t\t\tn, err := r.Body.Read(b)\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Assert(t, n == 5, n)\n\t\t\tassert.Assert(t, string(b[:n]) == \"hello\")\n\t\t}\n\t\tn, err := r.Body.Read(b)\n\t\tassert.Assert(t, err == io.EOF)\n\t\tassert.Assert(t, n == 0)\n\t}))\n\taddr, e := runEngine(func(e *route.Engine) {\n\t\te.GetOptions().StreamRequestBody = true\n\t\te.POST(\"/test\", h)\n\t})\n\tdefer e.Close()\n\n\tr, w := io.Pipe() // for sending chunked data\n\treq, err := http.NewRequest(\"POST\", \"http://\"+addr+\"/test\", r)\n\tassert.Nil(t, err)\n\tcli := &http.Client{}\n\tgo cli.Do(req)\n\tfor i := 0; i < 3; i++ {\n\t\tw.Write([]byte(\"hello\"))\n\t\ttime.Sleep(50 * time.Millisecond)\n\t}\n\tw.Close()\n\twg.Wait()\n}\n\nfunc TestHertzHandler_Chunked(t *testing.T) {\n\th := HertzHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tf := w.(http.Flusher)\n\t\tw.Header().Set(\"Transfer-Encoding\", \"chunked\")\n\t\tfor i := 0; i < 5; i++ {\n\t\t\tchunk := fmt.Sprintf(\"data:%d\", i)\n\t\t\t_, err := w.Write([]byte(chunk))\n\t\t\tassert.Nil(t, err)\n\t\t\tf.Flush()\n\t\t\ttime.Sleep(20 * time.Millisecond)\n\t\t}\n\t}))\n\taddr, e := runEngine(func(e *route.Engine) {\n\t\te.GET(\"/test\", h)\n\t})\n\tdefer e.Close()\n\n\tresp, err := http.Get(\"http://\" + addr + \"/test\")\n\tassert.Nil(t, err)\n\tdefer resp.Body.Close()\n\tassert.Assert(t, len(resp.TransferEncoding) == 1 && resp.TransferEncoding[0] == \"chunked\")\n\tfor i := 0; i < 5; i++ {\n\t\tb := make([]byte, 10)\n\t\tn, err := resp.Body.Read(b)\n\t\tassert.Nil(t, err)\n\t\tassert.Assert(t, string(b[:n]) == fmt.Sprintf(\"data:%d\", i))\n\t}\n}\n\nfunc TestHertzHandler_WriteHeader(t *testing.T) {\n\th := HertzHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Length\", \"0\")\n\t\tw.WriteHeader(500)\n\t\tw.(http.Flusher).Flush()\n\t\ttime.Sleep(time.Second) // Simulate long-running handler\n\t}))\n\taddr, e := runEngine(func(e *route.Engine) {\n\t\te.GET(\"/test\", h)\n\t})\n\tdefer e.Close()\n\n\tconn, err := net.Dial(\"tcp\", addr)\n\tassert.Nil(t, err)\n\tdefer conn.Close()\n\n\t_, err = conn.Write([]byte(\"GET /test HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\"))\n\tassert.Nil(t, err)\n\n\t// Set a short read deadline to verify headers arrive quickly after WriteHeader()\n\tconn.SetReadDeadline(time.Now().Add(50 * time.Millisecond))\n\tb := make([]byte, 200)\n\tn, err := conn.Read(b)\n\tassert.Nil(t, err)\n\tassert.Assert(t, strings.HasPrefix(string(b[:n]), \"HTTP/1.1 500 \"))\n}\n\nfunc TestHertzHandler_Hijack(t *testing.T) {\n\th := HertzHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tconn, rw, err := w.(http.Hijacker).Hijack()\n\t\tassert.Nil(t, err)\n\t\t_, _, err = w.(http.Hijacker).Hijack() // hijacked\n\t\tassert.NotNil(t, err)\n\n\t\tw.WriteHeader(500) // hijacked, noop\n\n\t\tgo func() {\n\t\t\tdefer conn.Close()\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\t\t\t_, err = w.Write([]byte(\"hello\"))\n\t\t\tassert.Assert(t, err == errConnHijacked)\n\n\t\t\t_, err = rw.Write([]byte(\"hello\"))\n\t\t\tassert.Nil(t, err)\n\t\t\terr = rw.Flush()\n\t\t\tassert.Nil(t, err)\n\t\t\tb := make([]byte, 10)\n\t\t\tn, err := rw.Read(b)\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Assert(t, string(b[:n]) == \"world\")\n\t\t}()\n\t}))\n\taddr, e := runEngine(func(e *route.Engine) {\n\t\te.GET(\"/test\", h)\n\t})\n\tdefer e.Close()\n\n\tconn, err := net.Dial(\"tcp\", addr)\n\tassert.Nil(t, err)\n\tdefer conn.Close()\n\tconn.Write([]byte(\"GET /test HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\"))\n\tb := make([]byte, 100)\n\tn, err := conn.Read(b)\n\tassert.Nil(t, err)\n\tassert.Assert(t, string(b[:n]) == \"hello\", string(b[:n]))\n\t_, err = conn.Write([]byte(\"world\"))\n\tassert.Nil(t, err)\n\tn, err = conn.Read(b) // Keep-Alive will not work if hijacked\n\tassert.Assert(t, err == io.EOF)\n\tassert.Assert(t, n == 0)\n}\n\n// TestHertzHandler_HijackGC tests that hijacked conn is closed by GC finalizer\n// when user forgets to call Close()\nfunc TestHertzHandler_HijackGC(t *testing.T) {\n\th := HertzHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _, err := w.(http.Hijacker).Hijack()\n\t\tassert.Nil(t, err)\n\t\t// intentionally not closing conn, let GC handle it\n\t\truntime.GC()\n\t\truntime.GC() // make sure the net.Conn is closed by GC\n\t}))\n\taddr, e := runEngine(func(e *route.Engine) {\n\t\te.GET(\"/test\", h)\n\t})\n\tdefer e.Close()\n\n\tconn, err := net.Dial(\"tcp\", addr)\n\tassert.Nil(t, err)\n\tdefer conn.Close()\n\tconn.Write([]byte(\"GET /test HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\"))\n\n\tb := make([]byte, 100)\n\tn, err := conn.Read(b) // conn should be closed by finalizer\n\tassert.Assert(t, err == io.EOF, err)\n\tassert.Assert(t, n == 0)\n}\n\n// TestHertzHandler_WriteHeader_Hijack verifies that headers are properly flushed\n// before hijacking the connection. This test ensures that when WriteHeader is called\n// before Hijack, the headers are correctly sent and the connection can be taken over.\nfunc TestHertzHandler_WriteHeader_Hijack(t *testing.T) {\n\th := HertzHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"X-Custom-Header\", \"test-value\")\n\t\tw.WriteHeader(200)\n\n\t\tconn, rw, err := w.(http.Hijacker).Hijack()\n\t\tassert.Nil(t, err)\n\t\tdefer conn.Close()\n\n\t\t_, err = rw.WriteString(\"hijacked response body\")\n\t\tassert.Nil(t, err)\n\t\tassert.Nil(t, rw.Flush())\n\t}))\n\taddr, e := runEngine(func(e *route.Engine) {\n\t\te.GET(\"/test\", h)\n\t})\n\tdefer e.Close()\n\n\tconn, err := net.Dial(\"tcp\", addr)\n\tassert.Nil(t, err)\n\tdefer conn.Close()\n\n\t_, err = conn.Write([]byte(\"GET /test HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\"))\n\tassert.Nil(t, err)\n\n\t// Wait briefly for server to process and send response\n\ttime.Sleep(50 * time.Millisecond)\n\tconn.SetReadDeadline(time.Now().Add(50 * time.Millisecond))\n\n\tb := make([]byte, 1024)\n\tn, err := conn.Read(b)\n\tassert.Nil(t, err)\n\n\tresponse := string(b[:n])\n\tt.Logf(\"Response: %q\", response)\n\tassert.Assert(t, strings.Contains(response, \"HTTP/1.1 200 OK\"), response)\n\tassert.Assert(t, strings.Contains(response, \"X-Custom-Header: test-value\"), response)\n\tassert.Assert(t, strings.Contains(response, \"hijacked response body\"), response)\n}\n\nfunc TestHertzHandler_FSEmbed(t *testing.T) {\n\taddr, e := runEngine(func(e *route.Engine) {\n\t\th := HertzHandler(http.FileServer(http.FS(adaptorFiles)))\n\t\te.GET(\"/*filepath\", h)\n\t\te.HEAD(\"/*filepath\", h)\n\t})\n\tdefer e.Close()\n\n\tresp, err := http.Get(\"http://\" + addr + \"/handler_test.go\")\n\tassert.Nil(t, err)\n\n\texpect := \"hello, I'm handler_test.go\"\n\n\tb, err := io.ReadAll(resp.Body)\n\ts := string(b)\n\tassert.Nil(t, err)\n\tassert.Assert(t, strings.Contains(s, expect), s)\n}\n\nfunc TestHertzHandler_Multipart(t *testing.T) {\n\tkvs := map[string]string{\n\t\t\"name\":  \"Alice\",\n\t\t\"email\": \"alice@example.com\",\n\t}\n\th := HertzHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfor k, expectv := range kvs {\n\t\t\tv := r.FormValue(k)\n\t\t\tassert.Assert(t, v == expectv, v)\n\t\t}\n\t\tw.WriteHeader(204)\n\t\tw.Write([]byte(\"hello\"))\n\t}))\n\taddr, e := runEngine(func(e *route.Engine) {\n\t\topts := e.GetOptions()\n\t\topts.StreamRequestBody = true\n\t\topts.DisablePreParseMultipartForm = true\n\t\te.POST(\"/test\", func(ctx context.Context, rc *app.RequestContext) {\n\t\t\t_, err := rc.MultipartForm() // call rc.MultipartForm before HertzHandler\n\t\t\tassert.Nil(t, err)\n\t\t\th(ctx, rc)\n\t\t})\n\t})\n\tdefer e.Close()\n\n\tbody, ct := createMultipartBody(kvs)\n\treq, err := http.NewRequest(\"POST\", \"http://\"+addr+\"/test\", bytes.NewReader(body.Bytes()))\n\tassert.Nil(t, err)\n\treq.Header.Set(\"Content-Type\", ct)\n\n\tclient := &http.Client{}\n\tresp, err := client.Do(req)\n\tassert.Nil(t, err)\n\tassert.Assert(t, resp.StatusCode == 204, resp.StatusCode)\n\tresp.Body.Close()\n}\n\nfunc createMultipartBody(kvs map[string]string) (*bytes.Buffer, string) {\n\tbuf := &bytes.Buffer{}\n\tw := multipart.NewWriter(buf)\n\tfor k, v := range kvs {\n\t\t_ = w.WriteField(k, v)\n\t}\n\t_ = w.Close()\n\treturn buf, w.FormDataContentType()\n}\n\nfunc TestNoopHijackWriter(t *testing.T) {\n\twriter := noopHijackWriter{}\n\n\t// Test Write method\n\tn, err := writer.Write([]byte(\"test\"))\n\tassert.Assert(t, n == 0, n)\n\tassert.Assert(t, err == errConnHijacked, err)\n\n\t// Test Flush method\n\terr = writer.Flush()\n\tassert.Assert(t, err == errConnHijacked, err)\n\n\t// Test Finalize method\n\terr = writer.Finalize()\n\tassert.Nil(t, err)\n}\n\nfunc TestNoopWriter(t *testing.T) {\n\twriter := noopWriter{}\n\n\t// Test Write method\n\ttestData := []byte(\"test data\")\n\tn, err := writer.Write(testData)\n\tassert.Assert(t, n == len(testData), n)\n\tassert.Nil(t, err)\n\n\t// Test Flush method\n\terr = writer.Flush()\n\tassert.Nil(t, err)\n\n\t// Test Finalize method\n\terr = writer.Finalize()\n\tassert.Nil(t, err)\n}\n"
  },
  {
    "path": "pkg/common/adaptor/request.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage adaptor\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\n// GetCompatRequest only support basic function of Request, not for all.\n//\n// Deprecated: use HertzHandler instead\nfunc GetCompatRequest(req *protocol.Request) (*http.Request, error) {\n\tr, err := http.NewRequest(string(req.Method()), req.URI().String(), bytes.NewReader(req.Body()))\n\tif err != nil {\n\t\treturn r, err\n\t}\n\n\th := make(map[string][]string)\n\treq.Header.VisitAll(func(k, v []byte) {\n\t\th[string(k)] = append(h[string(k)], string(v))\n\t})\n\n\tr.Header = h\n\treturn r, nil\n}\n\n// CopyToHertzRequest copy uri, host, method, protocol, header, but share body reader from http.Request to protocol.Request.\nfunc CopyToHertzRequest(req *http.Request, hreq *protocol.Request) error {\n\threq.Header.SetRequestURI(req.RequestURI)\n\threq.Header.SetHost(req.Host)\n\threq.Header.SetMethod(req.Method)\n\threq.Header.SetProtocol(req.Proto)\n\tfor k, v := range req.Header {\n\t\tfor _, vv := range v {\n\t\t\threq.Header.Add(k, vv)\n\t\t}\n\t}\n\tif req.Body != nil {\n\t\threq.SetBodyStream(req.Body, hreq.Header.ContentLength())\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/common/adaptor/request_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage adaptor\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/app/server\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc fullURL(ln net.Listener, p string) string {\n\treturn \"http://\" + path.Join(ln.Addr().String(), p)\n}\n\nfunc TestCompatResponse_WriteHeader(t *testing.T) {\n\tvar testHeader http.Header\n\tvar testBody string\n\ttestStatusCode := 299\n\ttestCookieValue := \"cookie\"\n\n\ttestHeader = make(map[string][]string)\n\ttestHeader[\"Key1\"] = []string{\"value1\"}\n\ttestHeader[\"Key2\"] = []string{\"value2\", \"value22\"}\n\ttestHeader[\"Key3\"] = []string{\"value3\", \"value33\", \"value333\"}\n\ttestHeader[consts.HeaderSetCookie] = []string{testCookieValue}\n\n\ttestBody = \"test body\"\n\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\th := server.New(server.WithListener(ln))\n\th.POST(\"/test1\", func(c context.Context, ctx *app.RequestContext) {\n\t\treq, _ := GetCompatRequest(&ctx.Request)\n\t\tresp := GetCompatResponseWriter(&ctx.Response)\n\t\thandlerAndCheck(t, resp, req, testHeader, testBody, testStatusCode)\n\t})\n\n\th.POST(\"/test2\", func(c context.Context, ctx *app.RequestContext) {\n\t\treq, _ := GetCompatRequest(&ctx.Request)\n\t\tresp := GetCompatResponseWriter(&ctx.Response)\n\t\thandlerAndCheck(t, resp, req, testHeader, testBody)\n\t})\n\n\tgo h.Spin()\n\ttime.Sleep(100 * time.Millisecond)\n\n\ttestUrl1 := fullURL(ln, \"/test1\")\n\ttestUrl2 := fullURL(ln, \"/test2\")\n\tmakeACall(t, http.MethodPost, testUrl1, testHeader, testBody, testStatusCode, []byte(testCookieValue))\n\tmakeACall(t, http.MethodPost, testUrl2, testHeader, testBody, consts.StatusOK, []byte(testCookieValue))\n}\n\nfunc makeACall(t *testing.T, method, url string, header http.Header, body string, expectStatusCode int, expectCookieValue []byte) {\n\tclient := http.Client{}\n\treq, _ := http.NewRequest(method, url, strings.NewReader(body))\n\treq.Header = header\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tt.Fatalf(\"make a call error: %s\", err)\n\t}\n\n\trespHeader := resp.Header\n\n\tfor k, v := range header {\n\t\tfor i := 0; i < len(v); i++ {\n\t\t\tif respHeader[k][i] != v[i] {\n\t\t\t\tt.Fatalf(\"Header error: want %s=%s, got %s=%s\", respHeader[k], respHeader[k][i], respHeader[k], v[i])\n\t\t\t}\n\t\t}\n\t}\n\n\tb, err := ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatalf(\"Read body error: %s\", err)\n\t}\n\tassert.DeepEqual(t, body, string(b))\n\tassert.DeepEqual(t, expectStatusCode, resp.StatusCode)\n\n\t// Parse out the cookie to verify it is correct\n\tcookie := protocol.Cookie{}\n\t_ = cookie.Parse(header[consts.HeaderSetCookie][0])\n\tassert.DeepEqual(t, expectCookieValue, cookie.Value())\n}\n\n// handlerAndCheck is designed to handle the program and check the header\n//\n// \"...\" is used in the type of statusCode, which is a syntactic sugar in Go.\n// In this way, the statusCode can be made an optional parameter,\n// and there is no need to pass in some meaningless numbers to judge some special cases.\nfunc handlerAndCheck(t *testing.T, writer http.ResponseWriter, request *http.Request, wantHeader http.Header, wantBody string, statusCode ...int) {\n\treqHeader := request.Header\n\tfor k, v := range wantHeader {\n\t\tif reqHeader[k] == nil {\n\t\t\tt.Fatalf(\"Header error: want %s=%s, got %s=nil\", reqHeader[k], reqHeader[k][0], reqHeader[k])\n\t\t}\n\t\tif reqHeader[k][0] != v[0] {\n\t\t\tt.Fatalf(\"Header error: want %s=%s, got %s=%s\", reqHeader[k], reqHeader[k][0], reqHeader[k], v[0])\n\t\t}\n\t}\n\n\tbody, err := ioutil.ReadAll(request.Body)\n\tif err != nil {\n\t\tt.Fatalf(\"Read body error: %s\", err)\n\t}\n\tassert.DeepEqual(t, wantBody, string(body))\n\n\trespHeader := writer.Header()\n\tfor k, v := range reqHeader {\n\t\trespHeader[k] = v\n\t}\n\n\t// When the incoming status code is nil, the execution of this code is skipped\n\t// and the status code is set to 200\n\tif statusCode != nil {\n\t\twriter.WriteHeader(statusCode[0])\n\t}\n\n\t_, err = writer.Write([]byte(\"test\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Write body error: %s\", err)\n\t}\n\t_, err = writer.Write([]byte(\" body\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Write body error: %s\", err)\n\t}\n}\n\nfunc TestCopyToHertzRequest(t *testing.T) {\n\treq := http.Request{\n\t\tMethod:     \"GET\",\n\t\tRequestURI: \"/test\",\n\t\tURL: &url.URL{\n\t\t\tScheme: \"http\",\n\t\t\tHost:   \"test.com\",\n\t\t},\n\t\tProto:  \"HTTP/1.1\",\n\t\tHeader: http.Header{},\n\t}\n\treq.Header.Set(\"key1\", \"value1\")\n\treq.Header.Add(\"key2\", \"value2\")\n\treq.Header.Add(\"key2\", \"value22\")\n\thertzReq := protocol.Request{}\n\terr := CopyToHertzRequest(&req, &hertzReq)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, req.Method, string(hertzReq.Method()))\n\tassert.DeepEqual(t, req.RequestURI, string(hertzReq.Path()))\n\tassert.DeepEqual(t, req.Proto, hertzReq.Header.GetProtocol())\n\tassert.DeepEqual(t, req.Header.Get(\"key1\"), hertzReq.Header.Get(\"key1\"))\n\tvalueSlice := make([]string, 0, 2)\n\thertzReq.Header.VisitAllCustomHeader(func(key, value []byte) {\n\t\tif strings.ToLower(string(key)) == \"key2\" {\n\t\t\tvalueSlice = append(valueSlice, string(value))\n\t\t}\n\t})\n\n\tassert.DeepEqual(t, req.Header.Values(\"key2\"), valueSlice)\n\n\tassert.DeepEqual(t, 3, hertzReq.Header.Len())\n}\n"
  },
  {
    "path": "pkg/common/adaptor/response.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage adaptor\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\ntype compatResponse struct {\n\th           *protocol.Response\n\theader      http.Header\n\twriteHeader bool\n}\n\nfunc (c *compatResponse) Header() http.Header {\n\tif c.header != nil {\n\t\treturn c.header\n\t}\n\tc.header = make(map[string][]string)\n\treturn c.header\n}\n\nfunc (c *compatResponse) Write(b []byte) (int, error) {\n\tif !c.writeHeader {\n\t\tc.WriteHeader(consts.StatusOK)\n\t}\n\n\treturn c.h.BodyWriter().Write(b)\n}\n\nfunc (c *compatResponse) WriteHeader(statusCode int) {\n\tif !c.writeHeader {\n\t\tfor k, v := range c.header {\n\t\t\tfor _, vv := range v {\n\t\t\t\tif k == consts.HeaderContentLength {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif k == consts.HeaderSetCookie {\n\t\t\t\t\tcookie := protocol.AcquireCookie()\n\t\t\t\t\t_ = cookie.Parse(vv)\n\t\t\t\t\tc.h.Header.SetCookie(cookie)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tc.h.Header.Add(k, vv)\n\t\t\t}\n\t\t}\n\t\tc.writeHeader = true\n\t}\n\n\tc.h.Header.SetStatusCode(statusCode)\n}\n\n// GetCompatResponseWriter only support basic function of ResponseWriter, not for all.\n//\n// Deprecated: use HertzHandler instead\nfunc GetCompatResponseWriter(resp *protocol.Response) http.ResponseWriter {\n\tc := &compatResponse{\n\t\th: resp,\n\t}\n\tc.h.Header.SetNoDefaultContentType(true)\n\n\th := make(map[string][]string)\n\ttmpKey := make([][]byte, 0, c.h.Header.Len())\n\tc.h.Header.VisitAll(func(k, v []byte) {\n\t\th[string(k)] = append(h[string(k)], string(v))\n\t\ttmpKey = append(tmpKey, k)\n\t})\n\n\tfor _, k := range tmpKey {\n\t\tc.h.Header.DelBytes(k)\n\t}\n\n\tc.header = h\n\treturn c\n}\n"
  },
  {
    "path": "pkg/common/adaptor/utils.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage adaptor\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unsafe\"\n\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\n// methodstr tries to return consts without allocation\nfunc methodstr(m []byte) string {\n\tif len(m) == 0 {\n\t\treturn \"GET\"\n\t}\n\tswitch m[0] {\n\tcase 'G':\n\t\tif string(m) == consts.MethodGet {\n\t\t\treturn consts.MethodGet\n\t\t}\n\tcase 'P':\n\t\tswitch string(m) {\n\t\tcase consts.MethodPost:\n\t\t\treturn consts.MethodPost\n\n\t\tcase consts.MethodPut:\n\t\t\treturn consts.MethodPut\n\n\t\tcase consts.MethodPatch:\n\t\t\treturn consts.MethodPatch\n\t\t}\n\tcase 'H':\n\t\tif string(m) == consts.MethodHead {\n\t\t\treturn consts.MethodHead\n\t\t}\n\tcase 'D':\n\t\tif string(m) == consts.MethodDelete {\n\t\t\treturn consts.MethodDelete\n\t\t}\n\t}\n\treturn string(m)\n}\n\ntype bytesRWCloser struct {\n\tbytes.Reader\n}\n\n// newBytesRWCloser adds noop Close method to bytes.Reader without allocation\nfunc newBytesRWCloser(b []byte) io.ReadCloser {\n\trd := bytes.NewReader(b)\n\treturn (*bytesRWCloser)(unsafe.Pointer(rd))\n}\n\n// Close implements the [io.Closer] interface.\nfunc (bytesRWCloser) Close() error { return nil }\n\nfunc writer2writerExt(w network.Writer) network.ExtWriter {\n\treturn extWriter{w}\n}\n\ntype extWriter struct {\n\tnetwork.Writer\n}\n\nfunc (w extWriter) Write(b []byte) (int, error) {\n\tbuf, err := w.Writer.Malloc(len(b))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn copy(buf, b), nil\n}\n\nfunc (w extWriter) Finalize() error {\n\treturn w.Flush()\n}\n\nfunc reader2closer(r io.Reader) io.ReadCloser {\n\trc, ok := r.(io.ReadCloser)\n\tif ok {\n\t\treturn rc\n\t}\n\treturn io.NopCloser(r)\n}\n\nfunc parseHTTPVersion(s string) (major, minor int, _ error) {\n\tv := strings.TrimPrefix(s, \"HTTP/\")\n\tif len(v) == len(s) {\n\t\treturn 1, 1, fmt.Errorf(\"invalid http version: %q\", s)\n\t}\n\tswitch v {\n\tcase \"1.0\":\n\t\treturn 1, 0, nil\n\n\tcase \"1.1\":\n\t\treturn 1, 1, nil\n\n\tdefault:\n\t\ta, b, ok := strings.Cut(v, \".\")\n\t\tif ok {\n\t\t\tmajor, err1 := strconv.Atoi(a)\n\t\t\tminor, err2 := strconv.Atoi(b)\n\t\t\tif err1 == nil && err2 == nil {\n\t\t\t\treturn major, minor, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn 1, 1, fmt.Errorf(\"invalid http version: %q\", s)\n}\n"
  },
  {
    "path": "pkg/common/adaptor/utils_test.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage adaptor\n\nimport (\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestMethodStr(t *testing.T) {\n\tvar m0, m1 runtime.MemStats\n\tassertEqual := func(got, expect string) {\n\t\tif got != expect {\n\t\t\tt.Helper()\n\t\t\tt.Fatal(got)\n\t\t}\n\t}\n\truntime.ReadMemStats(&m0)\n\tfor i := 0; i < 1000; i++ {\n\t\tassertEqual(methodstr([]byte(nil)), \"GET\")\n\t\tassertEqual(methodstr(bytestr.StrGet), \"GET\")\n\t\tassertEqual(methodstr(bytestr.StrHead), \"HEAD\")\n\t\tassertEqual(methodstr(bytestr.StrPost), \"POST\")\n\t\tassertEqual(methodstr(bytestr.StrPut), \"PUT\")\n\t\tassertEqual(methodstr(bytestr.StrDelete), \"DELETE\")\n\t\tassertEqual(methodstr(bytestr.StrPatch), \"PATCH\")\n\t}\n\truntime.ReadMemStats(&m1)\n\n\t// should be zero, but in case of other background task running\n\tdiff := m1.Mallocs - m0.Mallocs\n\tassert.Assert(t, diff < 50, diff)\n}\n\nfunc TestBytesRWCloser(t *testing.T) {\n\t// Test data\n\ttestData := []byte(\"test bytesRWCloser\")\n\n\t// Create a new bytesRWCloser\n\trc := newBytesRWCloser(testData)\n\n\t// Read from the reader\n\tbuf := make([]byte, len(testData))\n\tn, err := rc.Read(buf)\n\n\t// Verify read was successful\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, n, len(testData))\n\tassert.DeepEqual(t, buf, testData)\n\n\t// Test that Close returns nil\n\terr = rc.Close()\n\tassert.Nil(t, err)\n}\n\nfunc TestParseHTTPVersion(t *testing.T) {\n\t// Test HTTP/1.0\n\tmajor, minor, err := parseHTTPVersion(\"HTTP/1.0\")\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, major, 1)\n\tassert.DeepEqual(t, minor, 0)\n\n\t// Test HTTP/1.1\n\tmajor, minor, err = parseHTTPVersion(\"HTTP/1.1\")\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, major, 1)\n\tassert.DeepEqual(t, minor, 1)\n\n\t// Test HTTP/2.0\n\tmajor, minor, err = parseHTTPVersion(\"HTTP/2.0\")\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, major, 2)\n\tassert.DeepEqual(t, minor, 0)\n\n\t// Test HTTP/3.1\n\tmajor, minor, err = parseHTTPVersion(\"HTTP/3.1\")\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, major, 3)\n\tassert.DeepEqual(t, minor, 1)\n\n\t// Test missing HTTP prefix\n\tmajor, minor, err = parseHTTPVersion(\"1.1\")\n\tassert.NotNil(t, err)\n\tassert.DeepEqual(t, major, 1)\n\tassert.DeepEqual(t, minor, 1)\n\n\t// Test missing dot separator\n\tmajor, minor, err = parseHTTPVersion(\"HTTP/11\")\n\tassert.NotNil(t, err)\n\tassert.DeepEqual(t, major, 1)\n\tassert.DeepEqual(t, minor, 1)\n\n\t// Test empty string\n\tmajor, minor, err = parseHTTPVersion(\"\")\n\tassert.NotNil(t, err)\n\tassert.DeepEqual(t, major, 1)\n\tassert.DeepEqual(t, minor, 1)\n}\n"
  },
  {
    "path": "pkg/common/bytebufferpool/bytebuffer.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytebufferpool\n\nimport \"io\"\n\n// ByteBuffer provides byte buffer, which can be used for minimizing\n// memory allocations.\n//\n// ByteBuffer may be used with functions appending data to the given []byte\n// slice. See example code for details.\n//\n// Use Get for obtaining an empty byte buffer.\ntype ByteBuffer struct {\n\t// B is a byte buffer to use in append-like workloads.\n\t// See example code for details.\n\tB []byte\n}\n\n// Len returns the size of the byte buffer.\nfunc (b *ByteBuffer) Len() int {\n\treturn len(b.B)\n}\n\nfunc (b *ByteBuffer) Cap() int {\n\treturn cap(b.B)\n}\n\n// ReadFrom implements io.ReaderFrom.\n//\n// The function appends all the data read from r to b.\nfunc (b *ByteBuffer) ReadFrom(r io.Reader) (int64, error) {\n\tp := b.B\n\tnStart := int64(len(p))\n\tnMax := int64(cap(p))\n\tn := nStart\n\tif nMax == 0 {\n\t\tnMax = 64\n\t\tp = make([]byte, nMax)\n\t} else {\n\t\tp = p[:nMax]\n\t}\n\tfor {\n\t\tif n == nMax {\n\t\t\tnMax *= 2\n\t\t\tbNew := make([]byte, nMax)\n\t\t\tcopy(bNew, p)\n\t\t\tp = bNew\n\t\t}\n\t\tnn, err := r.Read(p[n:])\n\t\tn += int64(nn)\n\t\tif err != nil {\n\t\t\tb.B = p[:n]\n\t\t\tn -= nStart\n\t\t\tif err == io.EOF {\n\t\t\t\treturn n, nil\n\t\t\t}\n\t\t\treturn n, err\n\t\t}\n\t}\n}\n\n// WriteTo implements io.WriterTo.\nfunc (b *ByteBuffer) WriteTo(w io.Writer) (int64, error) {\n\tn, err := w.Write(b.B)\n\treturn int64(n), err\n}\n\n// Bytes returns b.B, i.e. all the bytes accumulated in the buffer.\n//\n// The purpose of this function is bytes.Buffer compatibility.\nfunc (b *ByteBuffer) Bytes() []byte {\n\treturn b.B\n}\n\n// Write implements io.Writer - it appends p to ByteBuffer.B\nfunc (b *ByteBuffer) Write(p []byte) (int, error) {\n\tb.B = append(b.B, p...)\n\treturn len(p), nil\n}\n\n// WriteByte appends the byte c to the buffer.\n//\n// The purpose of this function is bytes.Buffer compatibility.\n//\n// The function always returns nil.\nfunc (b *ByteBuffer) WriteByte(c byte) error {\n\tb.B = append(b.B, c)\n\treturn nil\n}\n\n// WriteString appends s to ByteBuffer.B.\nfunc (b *ByteBuffer) WriteString(s string) (int, error) {\n\tb.B = append(b.B, s...)\n\treturn len(s), nil\n}\n\n// Set sets ByteBuffer.B to p.\nfunc (b *ByteBuffer) Set(p []byte) {\n\tb.B = append(b.B[:0], p...)\n}\n\n// SetString sets ByteBuffer.B to s.\nfunc (b *ByteBuffer) SetString(s string) {\n\tb.B = append(b.B[:0], s...)\n}\n\n// String returns string representation of ByteBuffer.B.\nfunc (b *ByteBuffer) String() string {\n\treturn string(b.B)\n}\n\n// Reset makes ByteBuffer.B empty.\nfunc (b *ByteBuffer) Reset() {\n\tb.B = b.B[:0]\n}\n"
  },
  {
    "path": "pkg/common/bytebufferpool/bytebuffer_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytebufferpool\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestByteBufferReadFrom(t *testing.T) {\n\tprefix := \"foobar\"\n\texpectedS := \"asadfsdafsadfasdfisdsdfa\"\n\tprefixLen := int64(len(prefix))\n\texpectedN := int64(len(expectedS))\n\n\tvar bb ByteBuffer\n\tbb.WriteString(prefix)\n\n\trf := (io.ReaderFrom)(&bb)\n\tfor i := 0; i < 20; i++ {\n\t\tr := bytes.NewBufferString(expectedS)\n\t\tn, err := rf.ReadFrom(r)\n\t\tif n != expectedN {\n\t\t\tt.Fatalf(\"unexpected n=%d. Expecting %d. iteration %d\", n, expectedN, i)\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tbbLen := int64(bb.Len())\n\t\texpectedLen := prefixLen + int64(i+1)*expectedN\n\t\tif bbLen != expectedLen {\n\t\t\tt.Fatalf(\"unexpected byteBuffer length: %d. Expecting %d\", bbLen, expectedLen)\n\t\t}\n\t\tassert.True(t, bb.Cap() >= int(expectedLen))\n\t\tfor j := 0; j < i; j++ {\n\t\t\tstart := prefixLen + int64(j)*expectedN\n\t\t\tb := bb.B[start : start+expectedN]\n\t\t\tif string(b) != expectedS {\n\t\t\t\tt.Fatalf(\"unexpected byteBuffer contents: %q. Expecting %q\", b, expectedS)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestByteBufferWriteTo(t *testing.T) {\n\texpectedS := \"foobarbaz\"\n\tvar bb ByteBuffer\n\tbb.WriteString(expectedS[:3])\n\tbb.WriteString(expectedS[3:])\n\n\twt := (io.WriterTo)(&bb)\n\tvar w bytes.Buffer\n\tfor i := 0; i < 10; i++ {\n\t\tn, err := wt.WriteTo(&w)\n\t\tif n != int64(len(expectedS)) {\n\t\t\tt.Fatalf(\"unexpected n returned from WriteTo: %d. Expecting %d\", n, len(expectedS))\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\ts := w.String()\n\t\tif s != expectedS {\n\t\t\tt.Fatalf(\"unexpected string written %q. Expecting %q\", s, expectedS)\n\t\t}\n\t\tw.Reset()\n\t\tassert.True(t, bb.Cap() >= len(expectedS))\n\t}\n}\n\nfunc TestByteBufferGetPutSerial(t *testing.T) {\n\ttestByteBufferGetPut(t)\n}\n\nfunc TestByteBufferGetPutConcurrent(t *testing.T) {\n\tconcurrency := 10\n\tch := make(chan struct{}, concurrency)\n\tfor i := 0; i < concurrency; i++ {\n\t\tgo func() {\n\t\t\ttestByteBufferGetPut(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor i := 0; i < concurrency; i++ {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout!\")\n\t\t}\n\t}\n}\n\nfunc testByteBufferGetPut(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\texpectedS := fmt.Sprintf(\"num %d\", i)\n\t\tb := Get()\n\t\tb.B = append(b.B, \"num \"...)\n\t\tb.B = append(b.B, fmt.Sprintf(\"%d\", i)...)\n\t\tif string(b.B) != expectedS {\n\t\t\tt.Fatalf(\"unexpected result: %q. Expecting %q\", b.B, expectedS)\n\t\t}\n\t\tPut(b)\n\t}\n}\n\nfunc testByteBufferGetString(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\texpectedS := fmt.Sprintf(\"num %d\", i)\n\t\tb := Get()\n\t\tb.SetString(expectedS)\n\t\tif b.String() != expectedS {\n\t\t\tt.Fatalf(\"unexpected result: %q. Expecting %q\", b.B, expectedS)\n\t\t}\n\t\tPut(b)\n\t}\n}\n\nfunc TestByteBufferGetStringSerial(t *testing.T) {\n\ttestByteBufferGetString(t)\n}\n\nfunc TestByteBufferGetStringConcurrent(t *testing.T) {\n\tconcurrency := 10\n\tch := make(chan struct{}, concurrency)\n\tfor i := 0; i < concurrency; i++ {\n\t\tgo func() {\n\t\t\ttestByteBufferGetString(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor i := 0; i < concurrency; i++ {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout!\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/common/bytebufferpool/doc.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The files in bytebufferpool package are forked from fasthttp[github.com/valyala/fasthttp],\n// and we keep the original Copyright[Copyright 2015 fasthttp authors] and License of fasthttp for those files.\n// We also need to modify as we need, the modifications are Copyright of 2022 CloudWeGo Authors.\n// Thanks for fasthttp authors! Below is the source code information:\n// \t\tRepo: github.com/valyala/fasthttp\n//\t\tForked Version: v1.36.0\n\npackage bytebufferpool\n"
  },
  {
    "path": "pkg/common/bytebufferpool/pool.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytebufferpool\n\nimport (\n\t\"sort\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\nconst (\n\tminBitSize = 6 // 2**6=64 is a CPU cache line size\n\tsteps      = 20\n\n\tminSize = 1 << minBitSize\n\tmaxSize = 1 << (minBitSize + steps - 1)\n\n\tcalibrateCallsThreshold = 42000\n\tmaxPercentile           = 0.95\n)\n\n// Pool represents byte buffer pool.\n//\n// Distinct pools may be used for distinct types of byte buffers.\n// Properly determined byte buffer types with their own pools may help reducing\n// memory waste.\ntype Pool struct {\n\tcalls       [steps]uint64\n\tcalibrating uint64\n\n\tdefaultSize uint64\n\tmaxSize     uint64\n\n\tpool sync.Pool\n}\n\nvar defaultPool Pool\n\n// Get returns an empty byte buffer from the pool.\n//\n// Got byte buffer may be returned to the pool via Put call.\n// This reduces the number of memory allocations required for byte buffer\n// management.\nfunc Get() *ByteBuffer { return defaultPool.Get() }\n\n// Get returns new byte buffer with zero length.\n//\n// The byte buffer may be returned to the pool via Put after the use\n// in order to minimize GC overhead.\nfunc (p *Pool) Get() *ByteBuffer {\n\tv := p.pool.Get()\n\tif v != nil {\n\t\treturn v.(*ByteBuffer)\n\t}\n\treturn &ByteBuffer{\n\t\tB: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)),\n\t}\n}\n\n// Put returns byte buffer to the pool.\n//\n// ByteBuffer.B mustn't be touched after returning it to the pool.\n// Otherwise data races will occur.\nfunc Put(b *ByteBuffer) { defaultPool.Put(b) }\n\n// Put releases byte buffer obtained via Get to the pool.\n//\n// The buffer mustn't be accessed after returning to the pool.\nfunc (p *Pool) Put(b *ByteBuffer) {\n\tidx := index(len(b.B))\n\n\tif atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {\n\t\tp.calibrate()\n\t}\n\n\tmaxSize := int(atomic.LoadUint64(&p.maxSize))\n\tif maxSize == 0 || cap(b.B) <= maxSize {\n\t\tb.Reset()\n\t\tp.pool.Put(b)\n\t}\n}\n\nfunc (p *Pool) calibrate() {\n\tif !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {\n\t\treturn\n\t}\n\n\ta := make(callSizes, 0, steps)\n\tvar callsSum uint64\n\tfor i := uint64(0); i < steps; i++ {\n\t\tcalls := atomic.SwapUint64(&p.calls[i], 0)\n\t\tcallsSum += calls\n\t\ta = append(a, callSize{\n\t\t\tcalls: calls,\n\t\t\tsize:  minSize << i,\n\t\t})\n\t}\n\tsort.Sort(a)\n\n\tdefaultSize := a[0].size\n\tmaxSize := defaultSize\n\n\tmaxSum := uint64(float64(callsSum) * maxPercentile)\n\tcallsSum = 0\n\tfor i := 0; i < steps; i++ {\n\t\tif callsSum > maxSum {\n\t\t\tbreak\n\t\t}\n\t\tcallsSum += a[i].calls\n\t\tsize := a[i].size\n\t\tif size > maxSize {\n\t\t\tmaxSize = size\n\t\t}\n\t}\n\n\tatomic.StoreUint64(&p.defaultSize, defaultSize)\n\tatomic.StoreUint64(&p.maxSize, maxSize)\n\n\tatomic.StoreUint64(&p.calibrating, 0)\n}\n\ntype callSize struct {\n\tcalls uint64\n\tsize  uint64\n}\n\ntype callSizes []callSize\n\nfunc (ci callSizes) Len() int {\n\treturn len(ci)\n}\n\nfunc (ci callSizes) Less(i, j int) bool {\n\treturn ci[i].calls > ci[j].calls\n}\n\nfunc (ci callSizes) Swap(i, j int) {\n\tci[i], ci[j] = ci[j], ci[i]\n}\n\nfunc index(n int) int {\n\tn--\n\tn >>= minBitSize\n\tidx := 0\n\tfor n > 0 {\n\t\tn >>= 1\n\t\tidx++\n\t}\n\tif idx >= steps {\n\t\tidx = steps - 1\n\t}\n\treturn idx\n}\n"
  },
  {
    "path": "pkg/common/bytebufferpool/pool_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage bytebufferpool\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestIndex(t *testing.T) {\n\ttestIndex(t, 0, 0)\n\ttestIndex(t, 1, 0)\n\n\ttestIndex(t, minSize-1, 0)\n\ttestIndex(t, minSize, 0)\n\ttestIndex(t, minSize+1, 1)\n\n\ttestIndex(t, 2*minSize-1, 1)\n\ttestIndex(t, 2*minSize, 1)\n\ttestIndex(t, 2*minSize+1, 2)\n\n\ttestIndex(t, maxSize-1, steps-1)\n\ttestIndex(t, maxSize, steps-1)\n\ttestIndex(t, maxSize+1, steps-1)\n}\n\nfunc testIndex(t *testing.T, n, expectedIdx int) {\n\tidx := index(n)\n\tif idx != expectedIdx {\n\t\tt.Fatalf(\"unexpected idx for n=%d: %d. Expecting %d\", n, idx, expectedIdx)\n\t}\n}\n\nfunc TestPoolCalibrate(t *testing.T) {\n\tfor i := 0; i < steps*calibrateCallsThreshold; i++ {\n\t\tn := 1004\n\t\tif i%15 == 0 {\n\t\t\tn = rand.Intn(15234)\n\t\t}\n\t\ttestGetPut(t, n)\n\t}\n}\n\nfunc TestPoolVariousSizesSerial(t *testing.T) {\n\ttestPoolVariousSizes(t)\n}\n\nfunc TestPoolVariousSizesConcurrent(t *testing.T) {\n\tconcurrency := 5\n\tch := make(chan struct{})\n\tfor i := 0; i < concurrency; i++ {\n\t\tgo func() {\n\t\t\ttestPoolVariousSizes(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\tfor i := 0; i < concurrency; i++ {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(10 * time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc testPoolVariousSizes(t *testing.T) {\n\tfor i := 0; i < steps+1; i++ {\n\t\tn := 1 << uint32(i)\n\n\t\ttestGetPut(t, n)\n\t\ttestGetPut(t, n+1)\n\t\ttestGetPut(t, n-1)\n\n\t\tfor j := 0; j < 10; j++ {\n\t\t\ttestGetPut(t, j+n)\n\t\t}\n\t}\n}\n\nfunc testGetPut(t *testing.T, n int) {\n\tbb := Get()\n\tif len(bb.B) > 0 {\n\t\tt.Fatalf(\"non-empty byte buffer returned from acquire\")\n\t}\n\tbb.B = allocNBytes(bb.B, n)\n\tPut(bb)\n}\n\nfunc allocNBytes(dst []byte, n int) []byte {\n\tdiff := n - cap(dst)\n\tif diff <= 0 {\n\t\treturn dst[:n]\n\t}\n\treturn append(dst, make([]byte, diff)...)\n}\n"
  },
  {
    "path": "pkg/common/compress/compress.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage compress\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/common/stackless\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\nconst CompressDefaultCompression = 6 // flate.DefaultCompression\n\nvar gzipReaderPool sync.Pool\n\nvar (\n\tstacklessGzipWriterPoolMap = newCompressWriterPoolMap()\n\trealGzipWriterPoolMap      = newCompressWriterPoolMap()\n)\n\nfunc newCompressWriterPoolMap() []*sync.Pool {\n\t// Initialize pools for all the compression levels defined\n\t// in https://golang.org/pkg/compress/flate/#pkg-constants .\n\t// Compression levels are normalized with normalizeCompressLevel,\n\t// so the fit [0..11].\n\tvar m []*sync.Pool\n\tfor i := 0; i < 12; i++ {\n\t\tm = append(m, &sync.Pool{})\n\t}\n\treturn m\n}\n\ntype compressCtx struct {\n\tw     io.Writer\n\tp     []byte\n\tlevel int\n}\n\n// AppendGunzipBytes appends gunzipped src to dst and returns the resulting dst.\nfunc AppendGunzipBytes(dst, src []byte) ([]byte, error) {\n\tw := &byteSliceWriter{dst}\n\t_, err := WriteGunzip(w, src)\n\treturn w.b, err\n}\n\ntype byteSliceWriter struct {\n\tb []byte\n}\n\nfunc (w *byteSliceWriter) Write(p []byte) (int, error) {\n\tw.b = append(w.b, p...)\n\treturn len(p), nil\n}\n\n// WriteGunzip writes gunzipped p to w and returns the number of uncompressed\n// bytes written to w.\nfunc WriteGunzip(w io.Writer, p []byte) (int, error) {\n\tr := &byteSliceReader{p}\n\tzr, err := AcquireGzipReader(r)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tzw := network.NewWriter(w)\n\tn, err := utils.CopyZeroAlloc(zw, zr)\n\tReleaseGzipReader(zr)\n\tnn := int(n)\n\tif int64(nn) != n {\n\t\treturn 0, fmt.Errorf(\"too much data gunzipped: %d\", n)\n\t}\n\treturn nn, err\n}\n\ntype byteSliceReader struct {\n\tb []byte\n}\n\nfunc (r *byteSliceReader) Read(p []byte) (int, error) {\n\tif len(r.b) == 0 {\n\t\treturn 0, io.EOF\n\t}\n\tn := copy(p, r.b)\n\tr.b = r.b[n:]\n\treturn n, nil\n}\n\nfunc AcquireGzipReader(r io.Reader) (*gzip.Reader, error) {\n\tv := gzipReaderPool.Get()\n\tif v == nil {\n\t\treturn gzip.NewReader(r)\n\t}\n\tzr := v.(*gzip.Reader)\n\tif err := zr.Reset(r); err != nil {\n\t\treturn nil, err\n\t}\n\treturn zr, nil\n}\n\nfunc ReleaseGzipReader(zr *gzip.Reader) {\n\tzr.Close()\n\tgzipReaderPool.Put(zr)\n}\n\n// AppendGzipBytes appends gzipped src to dst and returns the resulting dst.\nfunc AppendGzipBytes(dst, src []byte) []byte {\n\treturn AppendGzipBytesLevel(dst, src, CompressDefaultCompression)\n}\n\n// AppendGzipBytesLevel appends gzipped src to dst using the given\n// compression level and returns the resulting dst.\n//\n// Supported compression levels are:\n//\n//   - CompressNoCompression\n//   - CompressBestSpeed\n//   - CompressBestCompression\n//   - CompressDefaultCompression\n//   - CompressHuffmanOnly\nfunc AppendGzipBytesLevel(dst, src []byte, level int) []byte {\n\tw := &byteSliceWriter{dst}\n\tWriteGzipLevel(w, src, level) //nolint:errcheck\n\treturn w.b\n}\n\nvar stacklessWriteGzip = stackless.NewFunc(nonblockingWriteGzip)\n\nfunc nonblockingWriteGzip(ctxv interface{}) {\n\tctx := ctxv.(*compressCtx)\n\tzw := acquireRealGzipWriter(ctx.w, ctx.level)\n\n\t_, err := zw.Write(ctx.p)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"BUG: gzip.Writer.Write for len(p)=%d returned unexpected error: %s\", len(ctx.p), err))\n\t}\n\n\treleaseRealGzipWriter(zw, ctx.level)\n}\n\nfunc releaseRealGzipWriter(zw *gzip.Writer, level int) {\n\tzw.Close()\n\tnLevel := normalizeCompressLevel(level)\n\tp := realGzipWriterPoolMap[nLevel]\n\tp.Put(zw)\n}\n\nfunc acquireRealGzipWriter(w io.Writer, level int) *gzip.Writer {\n\tnLevel := normalizeCompressLevel(level)\n\tp := realGzipWriterPoolMap[nLevel]\n\tv := p.Get()\n\tif v == nil {\n\t\tzw, err := gzip.NewWriterLevel(w, level)\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"BUG: unexpected error from gzip.NewWriterLevel(%d): %s\", level, err))\n\t\t}\n\t\treturn zw\n\t}\n\tzw := v.(*gzip.Writer)\n\tzw.Reset(w)\n\treturn zw\n}\n\n// normalizes compression level into [0..11], so it could be used as an index\n// in *PoolMap.\nfunc normalizeCompressLevel(level int) int {\n\t// -2 is the lowest compression level - CompressHuffmanOnly\n\t// 9 is the highest compression level - CompressBestCompression\n\tif level < -2 || level > 9 {\n\t\tlevel = CompressDefaultCompression\n\t}\n\treturn level + 2\n}\n\n// WriteGzipLevel writes gzipped p to w using the given compression level\n// and returns the number of compressed bytes written to w.\n//\n// Supported compression levels are:\n//\n//   - CompressNoCompression\n//   - CompressBestSpeed\n//   - CompressBestCompression\n//   - CompressDefaultCompression\n//   - CompressHuffmanOnly\nfunc WriteGzipLevel(w io.Writer, p []byte, level int) (int, error) {\n\tswitch w.(type) {\n\tcase *byteSliceWriter,\n\t\t*bytes.Buffer,\n\t\t*bytebufferpool.ByteBuffer:\n\t\t// These writers don't block, so we can just use stacklessWriteGzip\n\t\tctx := &compressCtx{\n\t\t\tw:     w,\n\t\t\tp:     p,\n\t\t\tlevel: level,\n\t\t}\n\t\tstacklessWriteGzip(ctx)\n\t\treturn len(p), nil\n\tdefault:\n\t\tzw := AcquireStacklessGzipWriter(w, level)\n\t\tn, err := zw.Write(p)\n\t\tReleaseStacklessGzipWriter(zw, level)\n\t\treturn n, err\n\t}\n}\n\nfunc AcquireStacklessGzipWriter(w io.Writer, level int) stackless.Writer {\n\tnLevel := normalizeCompressLevel(level)\n\tp := stacklessGzipWriterPoolMap[nLevel]\n\tv := p.Get()\n\tif v == nil {\n\t\treturn stackless.NewWriter(w, func(w io.Writer) stackless.Writer {\n\t\t\treturn acquireRealGzipWriter(w, level)\n\t\t})\n\t}\n\tsw := v.(stackless.Writer)\n\tsw.Reset(w)\n\treturn sw\n}\n\nfunc ReleaseStacklessGzipWriter(sw stackless.Writer, level int) {\n\tsw.Close()\n\tnLevel := normalizeCompressLevel(level)\n\tp := stacklessGzipWriterPoolMap[nLevel]\n\tp.Put(sw)\n}\n"
  },
  {
    "path": "pkg/common/compress/compress_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage compress\n\nimport (\n\t\"io\"\n\t\"testing\"\n)\n\nfunc TestCompressNewCompressWriterPoolMap(t *testing.T) {\n\tpool := newCompressWriterPoolMap()\n\tif len(pool) != 12 {\n\t\tt.Fatalf(\"Unexpected number for WriterPoolMap: %d. Expecting 12\", len(pool))\n\t}\n}\n\nfunc TestCompressAppendGunzipBytes(t *testing.T) {\n\tdst1 := []byte(\"\")\n\t// src unzip -> \"hello\". The src must the string that has been gunzipped.\n\tsrc1 := []byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 202, 72, 205, 201, 201, 7, 0, 0, 0, 255, 255}\n\texpectedRes1 := \"hello\"\n\tres1, err1 := AppendGunzipBytes(dst1, src1)\n\t// gzip will wrap io.EOF to io.ErrUnexpectedEOF\n\t// just ignore in this case\n\tif err1 != io.ErrUnexpectedEOF {\n\t\tt.Fatalf(\"Unexpected error: %s\", err1)\n\t}\n\tif string(res1) != expectedRes1 {\n\t\tt.Fatalf(\"Unexpected : %s. Expecting : %s\", res1, expectedRes1)\n\t}\n\n\tdst2 := []byte(\"!!!\")\n\tsrc2 := []byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 202, 72, 205, 201, 201, 7, 0, 0, 0, 255, 255}\n\texpectedRes2 := \"!!!hello\"\n\tres2, err2 := AppendGunzipBytes(dst2, src2)\n\tif err2 != io.ErrUnexpectedEOF {\n\t\tt.Fatalf(\"Unexpected error: %s\", err2)\n\t}\n\tif string(res2) != expectedRes2 {\n\t\tt.Fatalf(\"Unexpected : %s. Expecting : %s\", res2, expectedRes2)\n\t}\n\n\tdst3 := []byte(\"!!!\")\n\tsrc3 := []byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 255}\n\texpectedRes3 := \"!!!\"\n\tres3, err3 := AppendGunzipBytes(dst3, src3)\n\tif err3 != io.ErrUnexpectedEOF {\n\t\tt.Fatalf(\"Unexpected error: %s\", err3)\n\t}\n\tif string(res3) != expectedRes3 {\n\t\tt.Fatalf(\"Unexpected : %s. Expecting : %s\", res3, expectedRes3)\n\t}\n}\n\nfunc TestCompressAppendGzipBytesLevel(t *testing.T) {\n\t// test the byteSliceWriter case for WriteGzipLevel\n\tdst1 := []byte(\"\")\n\tsrc1 := []byte(\"hello\")\n\tres1 := AppendGzipBytesLevel(dst1, src1, 5)\n\texpectedRes1 := []byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 202, 72, 205, 201, 201, 7, 4, 0, 0, 255, 255, 134, 166, 16, 54, 5, 0, 0, 0}\n\tif string(res1) != string(expectedRes1) {\n\t\tt.Fatalf(\"Unexpected : %s. Expecting : %s\", res1, expectedRes1)\n\t}\n}\n\nfunc TestCompressWriteGzipLevel(t *testing.T) {\n\t// test default case for WriteGzipLevel\n\tvar w defaultByteWriter\n\tp := []byte(\"hello\")\n\texpectedW := []byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 202, 72, 205, 201, 201, 7, 4, 0, 0, 255, 255, 134, 166, 16, 54, 5, 0, 0, 0}\n\tnum, err := WriteGzipLevel(&w, p, 5)\n\tif string(expectedW) != string(w.b) {\n\t\tt.Fatalf(\"Unexpected : %s. Expecting: %s.\", w.b, expectedW)\n\t}\n\tif num != len(p) {\n\t\tt.Fatalf(\"Unexpected number of compressed bytes: %d\", num)\n\t}\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n}\n\ntype defaultByteWriter struct {\n\tb []byte\n}\n\nfunc (w *defaultByteWriter) Write(p []byte) (int, error) {\n\tw.b = append(w.b, p...)\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "pkg/common/compress/doc.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The files in compress package are forked from fasthttp[github.com/valyala/fasthttp],\n// and we keep the original Copyright[Copyright 2015 fasthttp authors] and License of fasthttp for those files.\n// We also need to modify as we need, the modifications are Copyright of 2022 CloudWeGo Authors.\n// Thanks for fasthttp authors! Below is the source code information:\n// \t\tRepo: github.com/valyala/fasthttp\n//\t\tForked Version: v1.36.0\n\npackage compress\n"
  },
  {
    "path": "pkg/common/config/client_option.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage config\n\nimport (\n\t\"crypto/tls\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/retry\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\ntype ConnPoolState struct {\n\t// The conn num of conn pool. These conns are idle connections.\n\tPoolConnNum int\n\t// Total conn num.\n\tTotalConnNum int\n\t// Number of pending connections\n\tWaitConnNum int\n\t// HostClient Addr\n\tAddr string\n\t// Maximum number of connections, <= 0 means no limit.\n\tMaxConns int\n}\n\ntype HostClientState interface {\n\tConnPoolState() ConnPoolState\n}\n\ntype HostClientStateFunc func(HostClientState)\n\n// ClientOption is the only struct that can be used to set ClientOptions.\ntype ClientOption struct {\n\tF func(o *ClientOptions)\n}\n\ntype ClientOptions struct {\n\t// Timeout for establishing a connection to server\n\tDialTimeout time.Duration\n\t// The max connection nums for each host, <= 0 means no limit\n\tMaxConnsPerHost int\n\n\tMaxIdleConnDuration time.Duration\n\tMaxConnDuration     time.Duration\n\tMaxConnWaitTimeout  time.Duration\n\tKeepAlive           bool\n\tReadTimeout         time.Duration\n\tTLSConfig           *tls.Config\n\tResponseBodyStream  bool\n\n\t// Client name. Used in User-Agent request header.\n\t//\n\t// Default client name is used if not set.\n\tName string\n\n\t// NoDefaultUserAgentHeader when set to true, causes the default\n\t// User-Agent header to be excluded from the Request.\n\tNoDefaultUserAgentHeader bool\n\n\t// Dialer is the custom dialer used to establish connection.\n\t// Default Dialer is used if not set.\n\tDialer network.Dialer\n\n\t// Attempt to connect to both ipv4 and ipv6 addresses if set to true.\n\t//\n\t// This option is used only if default TCP dialer is used,\n\t// i.e. if Dialer is blank.\n\t//\n\t// By default client connects only to ipv4 addresses,\n\t// since unfortunately ipv6 remains broken in many networks worldwide :)\n\tDialDualStack bool\n\n\t// Maximum duration for full request writing (including body).\n\t//\n\t// By default request write timeout is unlimited.\n\tWriteTimeout time.Duration\n\n\t// Maximum response body size.\n\t//\n\t// The client returns ErrBodyTooLarge if this limit is greater than 0\n\t// and response body is greater than the limit.\n\t//\n\t// By default response body size is unlimited.\n\tMaxResponseBodySize int\n\n\t// Header names are passed as-is without normalization\n\t// if this option is set.\n\t//\n\t// Disabled header names' normalization may be useful only for proxying\n\t// responses to other clients expecting case-sensitive header names.\n\t//\n\t// By default request and response header names are normalized, i.e.\n\t// The first letter and the first letters following dashes\n\t// are uppercased, while all the other letters are lowercased.\n\t// Examples:\n\t//\n\t//     * HOST -> Host\n\t//     * content-type -> Content-Type\n\t//     * cONTENT-lenGTH -> Content-Length\n\tDisableHeaderNamesNormalizing bool\n\n\t// Path values are sent as-is without normalization\n\t//\n\t// Disabled path normalization may be useful for proxying incoming requests\n\t// to servers that are expecting paths to be forwarded as-is.\n\t//\n\t// By default path values are normalized, i.e.\n\t// extra slashes are removed, special characters are encoded.\n\tDisablePathNormalizing bool\n\n\t// all configurations related to retry\n\tRetryConfig *retry.Config\n\n\tHostClientStateObserve HostClientStateFunc\n\n\t// StateObserve execution interval\n\tObservationInterval time.Duration\n\n\t// Callback hook for re-configuring host client\n\t// If an error is returned, the request will be terminated.\n\tHostClientConfigHook func(hc interface{}) error\n}\n\nfunc NewClientOptions(opts []ClientOption) *ClientOptions {\n\toptions := &ClientOptions{\n\t\tDialTimeout:         consts.DefaultDialTimeout,\n\t\tMaxIdleConnDuration: consts.DefaultMaxIdleConnDuration,\n\t\tKeepAlive:           true,\n\t\tObservationInterval: time.Second * 5,\n\t}\n\toptions.Apply(opts)\n\n\treturn options\n}\n\nfunc (o *ClientOptions) Apply(opts []ClientOption) {\n\tfor _, op := range opts {\n\t\top.F(o)\n\t}\n}\n"
  },
  {
    "path": "pkg/common/config/client_option_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage config\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\n// TestDefaultClientOptions test client options with default values\nfunc TestDefaultClientOptions(t *testing.T) {\n\toptions := NewClientOptions([]ClientOption{})\n\n\tassert.DeepEqual(t, consts.DefaultDialTimeout, options.DialTimeout)\n\tassert.DeepEqual(t, 0, options.MaxConnsPerHost)\n\tassert.DeepEqual(t, consts.DefaultMaxIdleConnDuration, options.MaxIdleConnDuration)\n\tassert.DeepEqual(t, true, options.KeepAlive)\n}\n\n// TestCustomClientOptions test client options with custom values\nfunc TestCustomClientOptions(t *testing.T) {\n\toptions := NewClientOptions([]ClientOption{})\n\n\toptions.Apply([]ClientOption{\n\t\t{\n\t\t\tF: func(o *ClientOptions) {\n\t\t\t\to.DialTimeout = 2 * time.Second\n\t\t\t},\n\t\t},\n\t})\n\tassert.DeepEqual(t, 2*time.Second, options.DialTimeout)\n}\n"
  },
  {
    "path": "pkg/common/config/option.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage config\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/server/registry\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\n// Option is the only struct that can be used to set Options.\ntype Option struct {\n\tF func(o *Options)\n}\n\nconst (\n\tdefaultKeepAliveTimeout   = 1 * time.Minute\n\tdefaultReadTimeout        = 3 * time.Minute\n\tdefaultAddr               = \":8888\"\n\tdefaultNetwork            = \"tcp\"\n\tdefaultBasePath           = \"/\"\n\tdefaultMaxRequestBodySize = 4 << 20 // 4MB\n\tdefaultMaxHeaderBytes     = 1 << 20 // 1MB\n\tdefaultWaitExitTimeout    = time.Second * 5\n\tdefaultReadBufferSize     = 4 * 1024\n)\n\ntype Options struct {\n\tKeepAliveTimeout             time.Duration\n\tReadTimeout                  time.Duration\n\tWriteTimeout                 time.Duration\n\tIdleTimeout                  time.Duration\n\tRedirectTrailingSlash        bool\n\tMaxRequestBodySize           int\n\tMaxHeaderBytes               int\n\tMaxKeepBodySize              int\n\tGetOnly                      bool\n\tDisableKeepalive             bool\n\tRedirectFixedPath            bool\n\tHandleMethodNotAllowed       bool\n\tUseRawPath                   bool\n\tRemoveExtraSlash             bool\n\tUnescapePathValues           bool\n\tDisablePreParseMultipartForm bool\n\tNoDefaultDate                bool\n\tNoDefaultContentType         bool\n\tStreamRequestBody            bool\n\tNoDefaultServerHeader        bool\n\tDisablePrintRoute            bool\n\tSenseClientDisconnection     bool\n\tNetwork                      string\n\tAddr                         string\n\tBasePath                     string\n\tExitWaitTimeout              time.Duration\n\tTLS                          *tls.Config\n\tH2C                          bool\n\tReadBufferSize               int\n\tALPN                         bool\n\tTracers                      []interface{}\n\tTraceLevel                   interface{}\n\tListener                     net.Listener\n\tListenConfig                 *net.ListenConfig\n\tBindConfig                   interface{}\n\tCustomBinder                 interface{}\n\tCustomValidator              interface{}\n\n\t// Deprecated: Use CustomValidator with a ValidatorFunc instead\n\tValidateConfig interface{}\n\n\t// TransporterNewer is the function to create a transporter.\n\tTransporterNewer    func(opt *Options) network.Transporter\n\tAltTransporterNewer func(opt *Options) network.Transporter\n\n\t// In netpoll library, OnAccept is called after connection accepted\n\t// but before adding it to epoll. OnConnect is called after adding it to epoll.\n\t// The difference is that onConnect can get data but OnAccept cannot.\n\t// If you'd like to check whether the peer IP is in the blacklist, you can use OnAccept.\n\t// In go net, OnAccept is executed after connection accepted but before establishing\n\t// tls connection. OnConnect is executed after establishing tls connection.\n\tOnAccept  func(conn net.Conn) context.Context\n\tOnConnect func(ctx context.Context, conn network.Conn) context.Context\n\n\t// Registry is used for service registry.\n\tRegistry registry.Registry\n\t// RegistryInfo is base info used for service registry.\n\tRegistryInfo *registry.Info\n\t// Enable automatically HTML template reloading mechanism.\n\n\tAutoReloadRender bool\n\t// If AutoReloadInterval is set to 0(default).\n\t// The HTML template will reload according to files' changing event\n\t// otherwise it will reload after AutoReloadInterval.\n\tAutoReloadInterval time.Duration\n\n\t// Header names are passed as-is without normalization\n\t// if this option is set.\n\t//\n\t// Disabled header names' normalization may be useful only for proxying\n\t// responses to other clients expecting case-sensitive header names.\n\t//\n\t// By default, request and response header names are normalized, i.e.\n\t// The first letter and the first letters following dashes\n\t// are uppercased, while all the other letters are lowercased.\n\t// Examples:\n\t//\n\t//     * HOST -> Host\n\t//     * content-type -> Content-Type\n\t//     * cONTENT-lenGTH -> Content-Length\n\tDisableHeaderNamesNormalizing bool\n}\n\nfunc (o *Options) Apply(opts []Option) {\n\tfor _, op := range opts {\n\t\top.F(o)\n\t}\n}\n\nfunc NewOptions(opts []Option) *Options {\n\toptions := &Options{\n\t\t// Keep-alive timeout. When idle connection exceeds this time,\n\t\t// server will send keep-alive packets to ensure it's a validated\n\t\t// connection.\n\t\t//\n\t\t// NOTE: Usually there is no need to care about this value, just\n\t\t// care about IdleTimeout.\n\t\tKeepAliveTimeout: defaultKeepAliveTimeout,\n\n\t\t// the timeout of reading from low-level library\n\t\tReadTimeout: defaultReadTimeout,\n\n\t\t// When there is no request during the idleTimeout, the connection\n\t\t// will be closed by server.\n\t\t// Default to ReadTimeout. Zero means no timeout.\n\t\tIdleTimeout: defaultReadTimeout,\n\n\t\t// Enables automatic redirection if the current route can't be matched but a\n\t\t// handler for the path with (without) the trailing slash exists.\n\t\t// For example if /foo/ is requested but a route only exists for /foo, the\n\t\t// client is redirected to /foo with http status code 301 for GET requests\n\t\t// and 308 for all other request methods.\n\t\tRedirectTrailingSlash: true,\n\n\t\t// If enabled, the router tries to fix the current request path, if no\n\t\t// handle is registered for it.\n\t\t// First superfluous path elements like ../ or // are removed.\n\t\t// Afterwards the router does a case-insensitive lookup of the cleaned path.\n\t\t// If a handle can be found for this route, the router makes a redirection\n\t\t// to the corrected path with status code 301 for GET requests and 308 for\n\t\t// all other request methods.\n\t\t// For example /FOO and /..//Foo could be redirected to /foo.\n\t\t// RedirectTrailingSlash is independent of this option.\n\t\tRedirectFixedPath: false,\n\n\t\t// If enabled, the router checks if another method is allowed for the\n\t\t// current route, if the current request can not be routed.\n\t\t// If this is the case, the request is answered with 'Method Not Allowed'\n\t\t// and HTTP status code 405.\n\t\t// If no other Method is allowed, the request is delegated to the NotFound\n\t\t// handler.\n\t\tHandleMethodNotAllowed: false,\n\n\t\t// If enabled, the url.RawPath will be used to find parameters.\n\t\tUseRawPath: false,\n\n\t\t// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.\n\t\tRemoveExtraSlash: false,\n\n\t\t// If true, the path value will be unescaped.\n\t\t// If UseRawPath is false (by default), the UnescapePathValues effectively is true,\n\t\t// as url.Path gonna be used, which is already unescaped.\n\t\tUnescapePathValues: true,\n\n\t\t// ContinueHandler is called after receiving the Expect 100 Continue Header\n\t\t//\n\t\t// https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3\n\t\t// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.1.1\n\t\t// Using ContinueHandler a server can make decisioning on whether or not\n\t\t// to read a potentially large request body based on the headers\n\t\t//\n\t\t// The default is to automatically read request bodies of Expect 100 Continue requests\n\t\t// like they are normal requests\n\t\tDisablePreParseMultipartForm: false,\n\n\t\t// When set to true, causes the default Content-Type header to be excluded from the response.\n\t\tNoDefaultContentType: false,\n\n\t\t// When set to true, causes the default date header to be excluded from the response.\n\t\tNoDefaultDate: false,\n\n\t\t// Routes info printing is not disabled by default\n\t\t// Disabled when set to True\n\t\tDisablePrintRoute: false,\n\n\t\t// The ability to sense client disconnection is disabled by default\n\t\tSenseClientDisconnection: false,\n\n\t\t// \"tcp\", \"udp\", \"unix\"(unix domain socket)\n\t\tNetwork: defaultNetwork,\n\n\t\t// listen address\n\t\tAddr: defaultAddr,\n\n\t\t// basePath\n\t\tBasePath: defaultBasePath,\n\n\t\t// Define the max request body size. If the body Size exceeds this value,\n\t\t// an error will be returned\n\t\tMaxRequestBodySize: defaultMaxRequestBodySize,\n\n\t\t// Define the max request header size. If the header size exceeds this value,\n\t\t// an error will be returned\n\t\tMaxHeaderBytes: defaultMaxHeaderBytes,\n\n\t\t// max reserved body buffer size when reset Request & Response\n\t\t// If the body size exceeds this value, then the buffer will be put to\n\t\t// sync.Pool instead of hold by Request/Response directly.\n\t\tMaxKeepBodySize: defaultMaxRequestBodySize,\n\n\t\t// only accept GET request\n\t\tGetOnly: false,\n\n\t\tDisableKeepalive: false,\n\n\t\t// request body stream switch\n\t\tStreamRequestBody: false,\n\n\t\tNoDefaultServerHeader: false,\n\n\t\t// graceful shutdown wait time\n\t\tExitWaitTimeout: defaultWaitExitTimeout,\n\n\t\t// tls config\n\t\tTLS: nil,\n\n\t\t// Set init read buffer size. Usually there is no need to set it.\n\t\tReadBufferSize: defaultReadBufferSize,\n\n\t\t// ALPN switch\n\t\tALPN: false,\n\n\t\t// H2C switch\n\t\tH2C: false,\n\n\t\t// tracers\n\t\tTracers: []interface{}{},\n\n\t\t// trace level, default LevelDetailed\n\t\tTraceLevel: new(interface{}),\n\n\t\tRegistry: registry.NoopRegistry,\n\n\t\t// Disabled header names' normalization, default false\n\t\tDisableHeaderNamesNormalizing: false,\n\t}\n\toptions.Apply(opts)\n\treturn options\n}\n"
  },
  {
    "path": "pkg/common/config/option_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage config\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/server/registry\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\n// TestDefaultOptions test options with default values\nfunc TestDefaultOptions(t *testing.T) {\n\toptions := NewOptions([]Option{})\n\n\tassert.DeepEqual(t, defaultKeepAliveTimeout, options.KeepAliveTimeout)\n\tassert.DeepEqual(t, defaultReadTimeout, options.ReadTimeout)\n\tassert.DeepEqual(t, defaultReadTimeout, options.IdleTimeout)\n\tassert.DeepEqual(t, time.Duration(0), options.WriteTimeout)\n\tassert.True(t, options.RedirectTrailingSlash)\n\tassert.True(t, options.RedirectTrailingSlash)\n\tassert.False(t, options.HandleMethodNotAllowed)\n\tassert.False(t, options.UseRawPath)\n\tassert.False(t, options.RemoveExtraSlash)\n\tassert.True(t, options.UnescapePathValues)\n\tassert.False(t, options.DisablePreParseMultipartForm)\n\tassert.False(t, options.SenseClientDisconnection)\n\tassert.DeepEqual(t, defaultNetwork, options.Network)\n\tassert.DeepEqual(t, defaultAddr, options.Addr)\n\tassert.DeepEqual(t, defaultMaxRequestBodySize, options.MaxRequestBodySize)\n\tassert.False(t, options.GetOnly)\n\tassert.False(t, options.DisableKeepalive)\n\tassert.False(t, options.NoDefaultServerHeader)\n\tassert.DeepEqual(t, defaultWaitExitTimeout, options.ExitWaitTimeout)\n\tassert.Nil(t, options.TLS)\n\tassert.DeepEqual(t, defaultReadBufferSize, options.ReadBufferSize)\n\tassert.False(t, options.ALPN)\n\tassert.False(t, options.H2C)\n\tassert.DeepEqual(t, []interface{}{}, options.Tracers)\n\tassert.DeepEqual(t, new(interface{}), options.TraceLevel)\n\tassert.DeepEqual(t, registry.NoopRegistry, options.Registry)\n\tassert.Nil(t, options.BindConfig)\n\tassert.Nil(t, options.ValidateConfig)\n\tassert.Nil(t, options.CustomBinder)\n\tassert.Nil(t, options.CustomValidator)\n\tassert.DeepEqual(t, false, options.DisableHeaderNamesNormalizing)\n}\n\n// TestApplyCustomOptions test apply options with custom values after init\nfunc TestApplyCustomOptions(t *testing.T) {\n\toptions := NewOptions([]Option{})\n\toptions.Apply([]Option{\n\t\t{F: func(o *Options) {\n\t\t\to.Network = \"unix\"\n\t\t}},\n\t})\n\tassert.DeepEqual(t, \"unix\", options.Network)\n}\n"
  },
  {
    "path": "pkg/common/config/request_option.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage config\n\nimport \"time\"\n\nvar preDefinedOpts []RequestOption\n\ntype RequestOptions struct {\n\ttags map[string]string\n\tisSD bool\n\n\tdialTimeout  time.Duration\n\treadTimeout  time.Duration\n\twriteTimeout time.Duration\n\t// Request timeout. Usually set by DoDeadline or DoTimeout\n\t// if <= 0, means not set\n\trequestTimeout time.Duration\n\tstart          time.Time\n}\n\n// RequestOption is the only struct to set request-level options.\ntype RequestOption struct {\n\tF func(o *RequestOptions)\n}\n\n// NewRequestOptions create a *RequestOptions according to the given opts.\nfunc NewRequestOptions(opts []RequestOption) *RequestOptions {\n\toptions := &RequestOptions{\n\t\ttags: make(map[string]string),\n\t\tisSD: false,\n\t}\n\tif preDefinedOpts != nil {\n\t\toptions.Apply(preDefinedOpts)\n\t}\n\toptions.Apply(opts)\n\treturn options\n}\n\n// WithTag set tag in RequestOptions.\nfunc WithTag(k, v string) RequestOption {\n\treturn RequestOption{F: func(o *RequestOptions) {\n\t\to.tags[k] = v\n\t}}\n}\n\n// WithSD set isSD in RequestOptions.\nfunc WithSD(b bool) RequestOption {\n\treturn RequestOption{F: func(o *RequestOptions) {\n\t\to.isSD = b\n\t}}\n}\n\n// WithDialTimeout sets dial timeout.\n//\n// This is the request level configuration. It has a higher\n// priority than the client level configuration\n// Note: it won't take effect in the case of the number of\n// connections in the connection pool exceeds the maximum\n// number of connections and needs to establish a connection\n// while waiting.\nfunc WithDialTimeout(t time.Duration) RequestOption {\n\treturn RequestOption{F: func(o *RequestOptions) {\n\t\to.dialTimeout = t\n\t}}\n}\n\n// WithReadTimeout sets read timeout.\n//\n// This is the request level configuration. It has a higher\n// priority than the client level configuration\nfunc WithReadTimeout(t time.Duration) RequestOption {\n\treturn RequestOption{F: func(o *RequestOptions) {\n\t\to.readTimeout = t\n\t}}\n}\n\n// WithWriteTimeout sets write timeout.\n//\n// This is the request level configuration. It has a higher\n// priority than the client level configuration\nfunc WithWriteTimeout(t time.Duration) RequestOption {\n\treturn RequestOption{F: func(o *RequestOptions) {\n\t\to.writeTimeout = t\n\t}}\n}\n\n// WithRequestTimeout sets whole request timeout. If it reaches timeout,\n// the client will return.\n//\n// This is the request level configuration.\nfunc WithRequestTimeout(t time.Duration) RequestOption {\n\treturn RequestOption{F: func(o *RequestOptions) {\n\t\to.requestTimeout = t\n\t}}\n}\n\nfunc (o *RequestOptions) Apply(opts []RequestOption) {\n\tfor _, op := range opts {\n\t\top.F(o)\n\t}\n}\n\nfunc (o *RequestOptions) Tag(k string) string {\n\treturn o.tags[k]\n}\n\nfunc (o *RequestOptions) Tags() map[string]string {\n\treturn o.tags\n}\n\nfunc (o *RequestOptions) IsSD() bool {\n\treturn o.isSD\n}\n\nfunc (o *RequestOptions) DialTimeout() time.Duration {\n\treturn o.dialTimeout\n}\n\nfunc (o *RequestOptions) ReadTimeout() time.Duration {\n\treturn o.readTimeout\n}\n\nfunc (o *RequestOptions) WriteTimeout() time.Duration {\n\treturn o.writeTimeout\n}\n\nfunc (o *RequestOptions) RequestTimeout() time.Duration {\n\treturn o.requestTimeout\n}\n\n// StartRequest records the start time of the request.\n//\n// Note: Users should not call this method.\nfunc (o *RequestOptions) StartRequest() {\n\tif o.requestTimeout > 0 {\n\t\to.start = time.Now()\n\t}\n}\n\nfunc (o *RequestOptions) StartTime() time.Time {\n\treturn o.start\n}\n\nfunc (o *RequestOptions) CopyTo(dst *RequestOptions) {\n\tif dst.tags == nil {\n\t\tdst.tags = make(map[string]string)\n\t}\n\n\tfor k, v := range o.tags {\n\t\tdst.tags[k] = v\n\t}\n\n\tdst.isSD = o.isSD\n\tdst.readTimeout = o.readTimeout\n\tdst.writeTimeout = o.writeTimeout\n\tdst.dialTimeout = o.dialTimeout\n\tdst.requestTimeout = o.requestTimeout\n\tdst.start = o.start\n}\n\n// SetPreDefinedOpts Pre define some RequestOption here\nfunc SetPreDefinedOpts(opts ...RequestOption) {\n\tpreDefinedOpts = nil\n\tpreDefinedOpts = append(preDefinedOpts, opts...)\n}\n"
  },
  {
    "path": "pkg/common/config/request_option_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage config\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\n// TestRequestOptions test request options with custom values\nfunc TestRequestOptions(t *testing.T) {\n\topt := NewRequestOptions([]RequestOption{\n\t\tWithTag(\"a\", \"b\"),\n\t\tWithTag(\"c\", \"d\"),\n\t\tWithTag(\"e\", \"f\"),\n\t\tWithSD(true),\n\t\tWithDialTimeout(time.Second),\n\t\tWithReadTimeout(time.Second),\n\t\tWithWriteTimeout(time.Second),\n\t})\n\tassert.DeepEqual(t, \"b\", opt.Tag(\"a\"))\n\tassert.DeepEqual(t, \"d\", opt.Tag(\"c\"))\n\tassert.DeepEqual(t, \"f\", opt.Tag(\"e\"))\n\tassert.DeepEqual(t, time.Second, opt.DialTimeout())\n\tassert.DeepEqual(t, time.Second, opt.ReadTimeout())\n\tassert.DeepEqual(t, time.Second, opt.WriteTimeout())\n\tassert.True(t, opt.IsSD())\n}\n\n// TestRequestOptionsWithDefaultOpts test request options with default values\nfunc TestRequestOptionsWithDefaultOpts(t *testing.T) {\n\tSetPreDefinedOpts(WithTag(\"pre-defined\", \"blablabla\"), WithTag(\"a\", \"default-value\"), WithSD(true))\n\topt := NewRequestOptions([]RequestOption{\n\t\tWithTag(\"a\", \"b\"),\n\t\tWithSD(false),\n\t})\n\tassert.DeepEqual(t, \"b\", opt.Tag(\"a\"))\n\tassert.DeepEqual(t, \"blablabla\", opt.Tag(\"pre-defined\"))\n\tassert.DeepEqual(t, map[string]string{\n\t\t\"a\":           \"b\",\n\t\t\"pre-defined\": \"blablabla\",\n\t}, opt.Tags())\n\tassert.False(t, opt.IsSD())\n\tSetPreDefinedOpts()\n\tassert.Nil(t, preDefinedOpts)\n\tassert.DeepEqual(t, time.Duration(0), opt.WriteTimeout())\n\tassert.DeepEqual(t, time.Duration(0), opt.ReadTimeout())\n\tassert.DeepEqual(t, time.Duration(0), opt.DialTimeout())\n}\n\n// TestRequestOptions_CopyTo test request options copy to another one\nfunc TestRequestOptions_CopyTo(t *testing.T) {\n\topt := NewRequestOptions([]RequestOption{\n\t\tWithTag(\"a\", \"b\"),\n\t\tWithSD(false),\n\t})\n\tvar copyOpt RequestOptions\n\topt.CopyTo(&copyOpt)\n\tassert.DeepEqual(t, opt.Tags(), copyOpt.Tags())\n\tassert.DeepEqual(t, opt.IsSD(), copyOpt.IsSD())\n}\n"
  },
  {
    "path": "pkg/common/errors/errors.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage errors\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n)\n\nvar (\n\t// These errors are the base error, which are used for checking in errors.Is()\n\tErrNeedMore           = errors.New(\"need more data\")\n\tErrChunkedStream      = errors.New(\"chunked stream\")\n\tErrBodyTooLarge       = errors.New(\"body size exceeds the given limit\")\n\tErrHeaderTooLarge     = errors.New(\"header size exceeds the given limit\")\n\tErrHijacked           = errors.New(\"connection has been hijacked\")\n\tErrTimeout            = errors.New(\"timeout\")\n\tErrIdleTimeout        = errors.New(\"idle timeout\")\n\tErrNothingRead        = errors.New(\"nothing read\")\n\tErrShortConnection    = errors.New(\"short connection\")\n\tErrConnectionClosed   = errors.New(\"connection closed\")\n\tErrNotSupportProtocol = errors.New(\"not support protocol\")\n\tErrNoMultipartForm    = errors.New(\"request has no multipart/form-data Content-Type\")\n\tErrBadPoolConn        = errors.New(\"connection is closed by peer while being in the connection pool\")\n\n\t// ErrNoFreeConns is returned by the HTTP client when MaxConnsPerHost (or\n\t// HostClient.MaxConns) is set to a positive value and all connections to\n\t// the target host are in use.\n\t// If MaxConnWaitTimeout is also set, the client waits for a free connection\n\t// up to that duration before returning this error.\n\t//\n\t// Before v0.10.3, MaxConnsPerHost defaulted to consts.DefaultMaxConnsPerHost\n\t// (512), so this error could be returned even when no limit was intended.\n\t// Since v0.10.3, MaxConnsPerHost defaults to 0 (no limit), meaning this\n\t// error is only returned when MaxConnsPerHost is explicitly configured.\n\tErrNoFreeConns = errors.New(\"no free connections available to host\")\n)\n\n// ErrorType is an unsigned 64-bit error code as defined in the hertz spec.\ntype ErrorType uint64\n\ntype Error struct {\n\tErr  error\n\tType ErrorType\n\tMeta interface{}\n}\n\nconst (\n\t// ErrorTypeBind is used when Context.Bind() fails.\n\tErrorTypeBind ErrorType = 1 << iota\n\t// ErrorTypeRender is used when Context.Render() fails.\n\tErrorTypeRender\n\t// ErrorTypePrivate indicates a private error.\n\tErrorTypePrivate\n\t// ErrorTypePublic indicates a public error.\n\tErrorTypePublic\n\t// ErrorTypeAny indicates any other error.\n\tErrorTypeAny\n)\n\ntype ErrorChain []*Error\n\nvar _ error = (*Error)(nil)\n\n// SetType sets the error's type.\nfunc (msg *Error) SetType(flags ErrorType) *Error {\n\tmsg.Type = flags\n\treturn msg\n}\n\n// AbortWithMsg implements the error interface.\nfunc (msg *Error) Error() string {\n\treturn msg.Err.Error()\n}\n\nfunc (a ErrorChain) String() string {\n\tif len(a) == 0 {\n\t\treturn \"\"\n\t}\n\tvar buffer strings.Builder\n\tfor i, msg := range a {\n\t\tfmt.Fprintf(&buffer, \"Error #%02d: %s\\n\", i+1, msg.Err)\n\t\tif msg.Meta != nil {\n\t\t\tfmt.Fprintf(&buffer, \"     Meta: %v\\n\", msg.Meta)\n\t\t}\n\t}\n\treturn buffer.String()\n}\n\nfunc (msg *Error) Unwrap() error {\n\treturn msg.Err\n}\n\n// SetMeta sets the error's meta data.\nfunc (msg *Error) SetMeta(data interface{}) *Error {\n\tmsg.Meta = data\n\treturn msg\n}\n\n// IsType judges one error.\nfunc (msg *Error) IsType(flags ErrorType) bool {\n\treturn (msg.Type & flags) > 0\n}\n\n// JSON creates a properly formatted JSON\nfunc (msg *Error) JSON() interface{} {\n\tjsonData := make(map[string]interface{})\n\tif msg.Meta != nil {\n\t\tvalue := reflect.ValueOf(msg.Meta)\n\t\tswitch value.Kind() {\n\t\tcase reflect.Struct:\n\t\t\treturn msg.Meta\n\t\tcase reflect.Map:\n\t\t\tfor _, key := range value.MapKeys() {\n\t\t\t\tjsonData[key.String()] = value.MapIndex(key).Interface()\n\t\t\t}\n\t\tdefault:\n\t\t\tjsonData[\"meta\"] = msg.Meta\n\t\t}\n\t}\n\tif _, ok := jsonData[\"error\"]; !ok {\n\t\tjsonData[\"error\"] = msg.Error()\n\t}\n\treturn jsonData\n}\n\n// Errors returns an array will all the error messages.\n// Example:\n//\n//\tc.Error(errors.New(\"first\"))\n//\tc.Error(errors.New(\"second\"))\n//\tc.Error(errors.New(\"third\"))\n//\tc.Errors.Errors() // == []string{\"first\", \"second\", \"third\"}\nfunc (a ErrorChain) Errors() []string {\n\tif len(a) == 0 {\n\t\treturn nil\n\t}\n\terrorStrings := make([]string, len(a))\n\tfor i, err := range a {\n\t\terrorStrings[i] = err.Error()\n\t}\n\treturn errorStrings\n}\n\n// ByType returns a readonly copy filtered the byte.\n// ie ByType(hertz.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic.\nfunc (a ErrorChain) ByType(typ ErrorType) ErrorChain {\n\tif len(a) == 0 {\n\t\treturn nil\n\t}\n\tif typ == ErrorTypeAny {\n\t\treturn a\n\t}\n\tvar result ErrorChain\n\tfor _, msg := range a {\n\t\tif msg.IsType(typ) {\n\t\t\tresult = append(result, msg)\n\t\t}\n\t}\n\treturn result\n}\n\n// Last returns the last error in the slice. It returns nil if the array is empty.\n// Shortcut for errors[len(errors)-1].\nfunc (a ErrorChain) Last() *Error {\n\tif length := len(a); length > 0 {\n\t\treturn a[length-1]\n\t}\n\treturn nil\n}\n\nfunc (a ErrorChain) JSON() interface{} {\n\tswitch length := len(a); length {\n\tcase 0:\n\t\treturn nil\n\tcase 1:\n\t\treturn a.Last().JSON()\n\tdefault:\n\t\tjsonData := make([]interface{}, length)\n\t\tfor i, err := range a {\n\t\t\tjsonData[i] = err.JSON()\n\t\t}\n\t\treturn jsonData\n\t}\n}\n\nfunc New(err error, t ErrorType, meta interface{}) *Error {\n\treturn &Error{\n\t\tErr:  err,\n\t\tType: t,\n\t\tMeta: meta,\n\t}\n}\n\n// shortcut for creating a public *Error from string\nfunc NewPublic(err string) *Error {\n\treturn New(errors.New(err), ErrorTypePublic, nil)\n}\n\nfunc NewPrivate(err string) *Error {\n\treturn New(errors.New(err), ErrorTypePrivate, nil)\n}\n\nfunc Newf(t ErrorType, meta interface{}, format string, v ...interface{}) *Error {\n\treturn New(fmt.Errorf(format, v...), t, meta)\n}\n\nfunc NewPublicf(format string, v ...interface{}) *Error {\n\treturn New(fmt.Errorf(format, v...), ErrorTypePublic, nil)\n}\n\nfunc NewPrivatef(format string, v ...interface{}) *Error {\n\treturn New(fmt.Errorf(format, v...), ErrorTypePrivate, nil)\n}\n"
  },
  {
    "path": "pkg/common/errors/errors_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage errors\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestError(t *testing.T) {\n\tbaseError := errors.New(\"test error\")\n\terr := &Error{\n\t\tErr:  baseError,\n\t\tType: ErrorTypePrivate,\n\t}\n\tassert.DeepEqual(t, err.Error(), baseError.Error())\n\tassert.DeepEqual(t, map[string]interface{}{\"error\": baseError.Error()}, err.JSON())\n\n\tassert.DeepEqual(t, err.SetType(ErrorTypePublic), err)\n\tassert.DeepEqual(t, ErrorTypePublic, err.Type)\n\n\tassert.DeepEqual(t, err.SetMeta(\"some data\"), err)\n\tassert.DeepEqual(t, \"some data\", err.Meta)\n\tassert.DeepEqual(t, map[string]interface{}{\n\t\t\"error\": baseError.Error(),\n\t\t\"meta\":  \"some data\",\n\t}, err.JSON())\n\n\terr.SetMeta(map[string]interface{}{ // nolint: errcheck\n\t\t\"status\": \"200\",\n\t\t\"data\":   \"some data\",\n\t})\n\tassert.DeepEqual(t, map[string]interface{}{\n\t\t\"error\":  baseError.Error(),\n\t\t\"status\": \"200\",\n\t\t\"data\":   \"some data\",\n\t}, err.JSON())\n\n\terr.SetMeta(map[string]interface{}{ // nolint: errcheck\n\t\t\"error\":  \"custom error\",\n\t\t\"status\": \"200\",\n\t\t\"data\":   \"some data\",\n\t})\n\tassert.DeepEqual(t, map[string]interface{}{\n\t\t\"error\":  \"custom error\",\n\t\t\"status\": \"200\",\n\t\t\"data\":   \"some data\",\n\t}, err.JSON())\n\n\ttype customError struct {\n\t\tstatus string\n\t\tdata   string\n\t}\n\terr.SetMeta(customError{status: \"200\", data: \"other data\"}) // nolint: errcheck\n\tassert.DeepEqual(t, customError{status: \"200\", data: \"other data\"}, err.JSON())\n}\n\nfunc TestErrorSlice(t *testing.T) {\n\terrs := ErrorChain{\n\t\t{Err: errors.New(\"first\"), Type: ErrorTypePrivate},\n\t\t{Err: errors.New(\"second\"), Type: ErrorTypePrivate, Meta: \"some data\"},\n\t\t{Err: errors.New(\"third\"), Type: ErrorTypePublic, Meta: map[string]interface{}{\"status\": \"400\"}},\n\t}\n\n\tassert.DeepEqual(t, errs, errs.ByType(ErrorTypeAny))\n\tassert.DeepEqual(t, \"third\", errs.Last().Error())\n\tassert.DeepEqual(t, []string{\"first\", \"second\", \"third\"}, errs.Errors())\n\tassert.DeepEqual(t, []string{\"third\"}, errs.ByType(ErrorTypePublic).Errors())\n\tassert.DeepEqual(t, []string{\"first\", \"second\"}, errs.ByType(ErrorTypePrivate).Errors())\n\tassert.DeepEqual(t, []string{\"first\", \"second\", \"third\"}, errs.ByType(ErrorTypePublic|ErrorTypePrivate).Errors())\n\tassert.DeepEqual(t, \"\", errs.ByType(ErrorTypeBind).String())\n\tassert.DeepEqual(t, `Error #01: first\nError #02: second\n     Meta: some data\nError #03: third\n     Meta: map[status:400]\n`, errs.String())\n\tassert.DeepEqual(t, []interface{}{\n\t\tmap[string]interface{}{\"error\": \"first\"},\n\t\tmap[string]interface{}{\"error\": \"second\", \"meta\": \"some data\"},\n\t\tmap[string]interface{}{\"error\": \"third\", \"status\": \"400\"},\n\t}, errs.JSON())\n\terrs = ErrorChain{\n\t\t{Err: errors.New(\"first\"), Type: ErrorTypePrivate},\n\t}\n\n\tassert.DeepEqual(t, map[string]interface{}{\"error\": \"first\"}, errs.JSON())\n\terrs = ErrorChain{}\n\tassert.DeepEqual(t, true, errs.Last() == nil)\n\tassert.Nil(t, errs.JSON())\n\tassert.DeepEqual(t, \"\", errs.String())\n}\n\nfunc TestErrorFormat(t *testing.T) {\n\terr := Newf(ErrorTypeAny, nil, \"caused by %s\", \"reason\")\n\tassert.DeepEqual(t, New(errors.New(\"caused by reason\"), ErrorTypeAny, nil), err)\n\tpublicErr := NewPublicf(\"caused by %s\", \"reason\")\n\tassert.DeepEqual(t, New(errors.New(\"caused by reason\"), ErrorTypePublic, nil), publicErr)\n\tprivateErr := NewPrivatef(\"caused by %s\", \"reason\")\n\tassert.DeepEqual(t, New(errors.New(\"caused by reason\"), ErrorTypePrivate, nil), privateErr)\n}\n"
  },
  {
    "path": "pkg/common/hlog/consts.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hlog\n\nconst (\n\tsystemLogPrefix = \"HERTZ: \"\n\n\tEngineErrorFormat = \"Error=%s, remoteAddr=%s\"\n)\n"
  },
  {
    "path": "pkg/common/hlog/default.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hlog\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n)\n\n// Fatal calls the default logger's Fatal method and then os.Exit(1).\nfunc Fatal(v ...interface{}) {\n\tlogger.Fatal(v...)\n}\n\n// Error calls the default logger's Error method.\nfunc Error(v ...interface{}) {\n\tlogger.Error(v...)\n}\n\n// Warn calls the default logger's Warn method.\nfunc Warn(v ...interface{}) {\n\tlogger.Warn(v...)\n}\n\n// Notice calls the default logger's Notice method.\nfunc Notice(v ...interface{}) {\n\tlogger.Notice(v...)\n}\n\n// Info calls the default logger's Info method.\nfunc Info(v ...interface{}) {\n\tlogger.Info(v...)\n}\n\n// Debug calls the default logger's Debug method.\nfunc Debug(v ...interface{}) {\n\tlogger.Debug(v...)\n}\n\n// Trace calls the default logger's Trace method.\nfunc Trace(v ...interface{}) {\n\tlogger.Trace(v...)\n}\n\n// Fatalf calls the default logger's Fatalf method and then os.Exit(1).\nfunc Fatalf(format string, v ...interface{}) {\n\tlogger.Fatalf(format, v...)\n}\n\n// Errorf calls the default logger's Errorf method.\nfunc Errorf(format string, v ...interface{}) {\n\tlogger.Errorf(format, v...)\n}\n\n// Warnf calls the default logger's Warnf method.\nfunc Warnf(format string, v ...interface{}) {\n\tlogger.Warnf(format, v...)\n}\n\n// Noticef calls the default logger's Noticef method.\nfunc Noticef(format string, v ...interface{}) {\n\tlogger.Noticef(format, v...)\n}\n\n// Infof calls the default logger's Infof method.\nfunc Infof(format string, v ...interface{}) {\n\tlogger.Infof(format, v...)\n}\n\n// Debugf calls the default logger's Debugf method.\nfunc Debugf(format string, v ...interface{}) {\n\tlogger.Debugf(format, v...)\n}\n\n// Tracef calls the default logger's Tracef method.\nfunc Tracef(format string, v ...interface{}) {\n\tlogger.Tracef(format, v...)\n}\n\n// CtxFatalf calls the default logger's CtxFatalf method and then os.Exit(1).\nfunc CtxFatalf(ctx context.Context, format string, v ...interface{}) {\n\tlogger.CtxFatalf(ctx, format, v...)\n}\n\n// CtxErrorf calls the default logger's CtxErrorf method.\nfunc CtxErrorf(ctx context.Context, format string, v ...interface{}) {\n\tlogger.CtxErrorf(ctx, format, v...)\n}\n\n// CtxWarnf calls the default logger's CtxWarnf method.\nfunc CtxWarnf(ctx context.Context, format string, v ...interface{}) {\n\tlogger.CtxWarnf(ctx, format, v...)\n}\n\n// CtxNoticef calls the default logger's CtxNoticef method.\nfunc CtxNoticef(ctx context.Context, format string, v ...interface{}) {\n\tlogger.CtxNoticef(ctx, format, v...)\n}\n\n// CtxInfof calls the default logger's CtxInfof method.\nfunc CtxInfof(ctx context.Context, format string, v ...interface{}) {\n\tlogger.CtxInfof(ctx, format, v...)\n}\n\n// CtxDebugf calls the default logger's CtxDebugf method.\nfunc CtxDebugf(ctx context.Context, format string, v ...interface{}) {\n\tlogger.CtxDebugf(ctx, format, v...)\n}\n\n// CtxTracef calls the default logger's CtxTracef method.\nfunc CtxTracef(ctx context.Context, format string, v ...interface{}) {\n\tlogger.CtxTracef(ctx, format, v...)\n}\n\ntype defaultLogger struct {\n\tstdlog *log.Logger\n\tlevel  Level\n\tdepth  int\n}\n\nfunc (ll *defaultLogger) SetOutput(w io.Writer) {\n\tll.stdlog.SetOutput(w)\n}\n\nfunc (ll *defaultLogger) SetLevel(lv Level) {\n\tll.level = lv\n}\n\nfunc (ll *defaultLogger) logf(lv Level, format *string, v ...interface{}) {\n\tif ll.level > lv {\n\t\treturn\n\t}\n\tmsg := lv.toString()\n\tif format != nil {\n\t\tif len(v) > 0 {\n\t\t\tmsg += fmt.Sprintf(*format, v...)\n\t\t} else {\n\t\t\tmsg += *format\n\t\t}\n\t} else {\n\t\tmsg += fmt.Sprint(v...)\n\t}\n\n\tll.stdlog.Output(ll.depth, msg)\n\tif lv == LevelFatal {\n\t\tos.Exit(1)\n\t}\n}\n\nfunc (ll *defaultLogger) Fatal(v ...interface{}) {\n\tll.logf(LevelFatal, nil, v...)\n}\n\nfunc (ll *defaultLogger) Error(v ...interface{}) {\n\tll.logf(LevelError, nil, v...)\n}\n\nfunc (ll *defaultLogger) Warn(v ...interface{}) {\n\tll.logf(LevelWarn, nil, v...)\n}\n\nfunc (ll *defaultLogger) Notice(v ...interface{}) {\n\tll.logf(LevelNotice, nil, v...)\n}\n\nfunc (ll *defaultLogger) Info(v ...interface{}) {\n\tll.logf(LevelInfo, nil, v...)\n}\n\nfunc (ll *defaultLogger) Debug(v ...interface{}) {\n\tll.logf(LevelDebug, nil, v...)\n}\n\nfunc (ll *defaultLogger) Trace(v ...interface{}) {\n\tll.logf(LevelTrace, nil, v...)\n}\n\nfunc (ll *defaultLogger) Fatalf(format string, v ...interface{}) {\n\tll.logf(LevelFatal, &format, v...)\n}\n\nfunc (ll *defaultLogger) Errorf(format string, v ...interface{}) {\n\tll.logf(LevelError, &format, v...)\n}\n\nfunc (ll *defaultLogger) Warnf(format string, v ...interface{}) {\n\tll.logf(LevelWarn, &format, v...)\n}\n\nfunc (ll *defaultLogger) Noticef(format string, v ...interface{}) {\n\tll.logf(LevelNotice, &format, v...)\n}\n\nfunc (ll *defaultLogger) Infof(format string, v ...interface{}) {\n\tll.logf(LevelInfo, &format, v...)\n}\n\nfunc (ll *defaultLogger) Debugf(format string, v ...interface{}) {\n\tll.logf(LevelDebug, &format, v...)\n}\n\nfunc (ll *defaultLogger) Tracef(format string, v ...interface{}) {\n\tll.logf(LevelTrace, &format, v...)\n}\n\nfunc (ll *defaultLogger) CtxFatalf(ctx context.Context, format string, v ...interface{}) {\n\tll.logf(LevelFatal, &format, v...)\n}\n\nfunc (ll *defaultLogger) CtxErrorf(ctx context.Context, format string, v ...interface{}) {\n\tll.logf(LevelError, &format, v...)\n}\n\nfunc (ll *defaultLogger) CtxWarnf(ctx context.Context, format string, v ...interface{}) {\n\tll.logf(LevelWarn, &format, v...)\n}\n\nfunc (ll *defaultLogger) CtxNoticef(ctx context.Context, format string, v ...interface{}) {\n\tll.logf(LevelNotice, &format, v...)\n}\n\nfunc (ll *defaultLogger) CtxInfof(ctx context.Context, format string, v ...interface{}) {\n\tll.logf(LevelInfo, &format, v...)\n}\n\nfunc (ll *defaultLogger) CtxDebugf(ctx context.Context, format string, v ...interface{}) {\n\tll.logf(LevelDebug, &format, v...)\n}\n\nfunc (ll *defaultLogger) CtxTracef(ctx context.Context, format string, v ...interface{}) {\n\tll.logf(LevelTrace, &format, v...)\n}\n"
  },
  {
    "path": "pkg/common/hlog/default_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hlog\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc initTestLogger() {\n\tlogger = &defaultLogger{\n\t\tstdlog: log.New(os.Stderr, \"\", 0),\n\t\tdepth:  4,\n\t}\n}\n\ntype byteSliceWriter struct {\n\tb []byte\n}\n\nfunc (w *byteSliceWriter) Write(p []byte) (int, error) {\n\tw.b = append(w.b, p...)\n\treturn len(p), nil\n}\n\nfunc TestDefaultLogger(t *testing.T) {\n\tinitTestLogger()\n\n\tvar w byteSliceWriter\n\tSetOutput(&w)\n\n\tTrace(\"trace work\")\n\tDebug(\"received work order\")\n\tInfo(\"starting work\")\n\tNotice(\"something happens in work\")\n\tWarn(\"work may fail\")\n\tError(\"work failed\")\n\n\tassert.DeepEqual(t, \"[Trace] trace work\\n\"+\n\t\t\"[Debug] received work order\\n\"+\n\t\t\"[Info] starting work\\n\"+\n\t\t\"[Notice] something happens in work\\n\"+\n\t\t\"[Warn] work may fail\\n\"+\n\t\t\"[Error] work failed\\n\", string(w.b))\n}\n\nfunc TestDefaultFormatLogger(t *testing.T) {\n\tinitTestLogger()\n\n\tvar w byteSliceWriter\n\tSetOutput(&w)\n\n\twork := \"work\"\n\tTracef(\"trace %s\", work)\n\tDebugf(\"received %s order\", work)\n\tInfof(\"starting %s\", work)\n\tNoticef(\"something happens in %s\", work)\n\tWarnf(\"%s may fail\", work)\n\tErrorf(\"%s failed\", work)\n\n\tassert.DeepEqual(t, \"[Trace] trace work\\n\"+\n\t\t\"[Debug] received work order\\n\"+\n\t\t\"[Info] starting work\\n\"+\n\t\t\"[Notice] something happens in work\\n\"+\n\t\t\"[Warn] work may fail\\n\"+\n\t\t\"[Error] work failed\\n\", string(w.b))\n}\n\nfunc TestCtxLogger(t *testing.T) {\n\tinitTestLogger()\n\n\tvar w byteSliceWriter\n\tSetOutput(&w)\n\n\tctx := context.Background()\n\twork := \"work\"\n\tCtxTracef(ctx, \"trace %s\", work)\n\tCtxDebugf(ctx, \"received %s order\", work)\n\tCtxInfof(ctx, \"starting %s\", work)\n\tCtxNoticef(ctx, \"something happens in %s\", work)\n\tCtxWarnf(ctx, \"%s may fail\", work)\n\tCtxErrorf(ctx, \"%s failed\", work)\n\n\tassert.DeepEqual(t, \"[Trace] trace work\\n\"+\n\t\t\"[Debug] received work order\\n\"+\n\t\t\"[Info] starting work\\n\"+\n\t\t\"[Notice] something happens in work\\n\"+\n\t\t\"[Warn] work may fail\\n\"+\n\t\t\"[Error] work failed\\n\", string(w.b))\n}\n\nfunc TestFormatLoggerWithEscapedCharacters(t *testing.T) {\n\tinitTestLogger()\n\n\tvar w byteSliceWriter\n\tSetOutput(&w)\n\n\tInfof(\"http://localhost:8080/ping?f=http://localhost:3000/hello?c=%E5%A4%A7hi%E5%93%A6%E5%95%8A%E8%AF%B4%E5%BE%97%E5%A5%BD\")\n\tassert.DeepEqual(t, \"[Info] http://localhost:8080/ping?f=http://localhost:3000/hello?c=%E5%A4%A7hi%E5%93%A6%E5%95%8A%E8%AF%B4%E5%BE%97%E5%A5%BD\\n\", string(w.b))\n}\n\nfunc TestSetLevel(t *testing.T) {\n\tsetLogger := &defaultLogger{\n\t\tstdlog: log.New(os.Stderr, \"\", log.LstdFlags|log.Lshortfile|log.Lmicroseconds),\n\t\tdepth:  4,\n\t}\n\n\tsetLogger.SetLevel(LevelTrace)\n\tassert.DeepEqual(t, LevelTrace, setLogger.level)\n\tassert.DeepEqual(t, LevelTrace.toString(), setLogger.level.toString())\n\n\tsetLogger.SetLevel(LevelDebug)\n\tassert.DeepEqual(t, LevelDebug, setLogger.level)\n\tassert.DeepEqual(t, LevelDebug.toString(), setLogger.level.toString())\n\n\tsetLogger.SetLevel(LevelInfo)\n\tassert.DeepEqual(t, LevelInfo, setLogger.level)\n\tassert.DeepEqual(t, LevelInfo.toString(), setLogger.level.toString())\n\n\tsetLogger.SetLevel(LevelNotice)\n\tassert.DeepEqual(t, LevelNotice, setLogger.level)\n\tassert.DeepEqual(t, LevelNotice.toString(), setLogger.level.toString())\n\n\tsetLogger.SetLevel(LevelWarn)\n\tassert.DeepEqual(t, LevelWarn, setLogger.level)\n\tassert.DeepEqual(t, LevelWarn.toString(), setLogger.level.toString())\n\n\tsetLogger.SetLevel(LevelError)\n\tassert.DeepEqual(t, LevelError, setLogger.level)\n\tassert.DeepEqual(t, LevelError.toString(), setLogger.level.toString())\n\n\tsetLogger.SetLevel(LevelFatal)\n\tassert.DeepEqual(t, LevelFatal, setLogger.level)\n\tassert.DeepEqual(t, LevelFatal.toString(), setLogger.level.toString())\n\n\tsetLogger.SetLevel(7)\n\tassert.DeepEqual(t, 7, int(setLogger.level))\n\tassert.DeepEqual(t, \"[?7] \", setLogger.level.toString())\n}\n"
  },
  {
    "path": "pkg/common/hlog/hlog.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hlog\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"os\"\n)\n\nvar (\n\t// Provide default logger for users to use\n\tlogger FullLogger = &defaultLogger{\n\t\tstdlog: log.New(os.Stderr, \"\", log.LstdFlags|log.Lshortfile|log.Lmicroseconds),\n\t\tdepth:  4,\n\t}\n\n\t// Provide system logger for print system log\n\tsysLogger FullLogger = &systemLogger{\n\t\t&defaultLogger{\n\t\t\tstdlog: log.New(os.Stderr, \"\", log.LstdFlags|log.Lshortfile|log.Lmicroseconds),\n\t\t\tdepth:  4,\n\t\t},\n\t\tsystemLogPrefix,\n\t}\n)\n\n// SetOutput sets the output of default logger and system logger. By default, it is stderr.\nfunc SetOutput(w io.Writer) {\n\tlogger.SetOutput(w)\n\tsysLogger.SetOutput(w)\n}\n\n// SetLevel sets the level of logs below which logs will not be output.\n// The default logger and system logger level is LevelTrace.\n// Note that this method is not concurrent-safe.\nfunc SetLevel(lv Level) {\n\tlogger.SetLevel(lv)\n\tsysLogger.SetLevel(lv)\n}\n\n// DefaultLogger return the default logger for hertz.\nfunc DefaultLogger() FullLogger {\n\treturn logger\n}\n\n// SystemLogger return the system logger for hertz to print system log.\n// This function is not recommended for users to use.\nfunc SystemLogger() FullLogger {\n\treturn sysLogger\n}\n\n// SetSystemLogger sets the system logger.\n// Note that this method is not concurrent-safe and must not be called\n// This function is not recommended for users to use.\nfunc SetSystemLogger(v FullLogger) {\n\tsysLogger = &systemLogger{v, systemLogPrefix}\n}\n\n// SetLogger sets the default logger and the system logger.\n// Note that this method is not concurrent-safe and must not be called\n// after the use of DefaultLogger and global functions in this package.\nfunc SetLogger(v FullLogger) {\n\tlogger = v\n\tSetSystemLogger(v)\n}\n"
  },
  {
    "path": "pkg/common/hlog/hlog_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hlog\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestDefaultAndSysLogger(t *testing.T) {\n\tdefaultLog := DefaultLogger()\n\tsystemLog := SystemLogger()\n\n\tassert.DeepEqual(t, logger, defaultLog)\n\tassert.DeepEqual(t, sysLogger, systemLog)\n\tassert.NotEqual(t, logger, systemLog)\n\tassert.NotEqual(t, sysLogger, defaultLog)\n}\n\nfunc TestSetLogger(t *testing.T) {\n\tsetLog := &defaultLogger{\n\t\tstdlog: log.New(os.Stderr, \"\", log.LstdFlags|log.Lshortfile|log.Lmicroseconds),\n\t\tdepth:  6,\n\t}\n\tsetSysLog := &systemLogger{\n\t\tsetLog,\n\t\tsystemLogPrefix,\n\t}\n\n\tassert.NotEqual(t, logger, setLog)\n\tassert.NotEqual(t, sysLogger, setSysLog)\n\tSetLogger(setLog)\n\tassert.DeepEqual(t, logger, setLog)\n\tassert.DeepEqual(t, sysLogger, setSysLog)\n}\n"
  },
  {
    "path": "pkg/common/hlog/log.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hlog\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// FormatLogger is a logger interface that output logs with a format.\ntype FormatLogger interface {\n\tTracef(format string, v ...interface{})\n\tDebugf(format string, v ...interface{})\n\tInfof(format string, v ...interface{})\n\tNoticef(format string, v ...interface{})\n\tWarnf(format string, v ...interface{})\n\tErrorf(format string, v ...interface{})\n\tFatalf(format string, v ...interface{})\n}\n\n// Logger is a logger interface that provides logging function with levels.\ntype Logger interface {\n\tTrace(v ...interface{})\n\tDebug(v ...interface{})\n\tInfo(v ...interface{})\n\tNotice(v ...interface{})\n\tWarn(v ...interface{})\n\tError(v ...interface{})\n\tFatal(v ...interface{})\n}\n\n// CtxLogger is a logger interface that accepts a context argument and output\n// logs with a format.\ntype CtxLogger interface {\n\tCtxTracef(ctx context.Context, format string, v ...interface{})\n\tCtxDebugf(ctx context.Context, format string, v ...interface{})\n\tCtxInfof(ctx context.Context, format string, v ...interface{})\n\tCtxNoticef(ctx context.Context, format string, v ...interface{})\n\tCtxWarnf(ctx context.Context, format string, v ...interface{})\n\tCtxErrorf(ctx context.Context, format string, v ...interface{})\n\tCtxFatalf(ctx context.Context, format string, v ...interface{})\n}\n\n// Control provides methods to config a logger.\ntype Control interface {\n\tSetLevel(Level)\n\tSetOutput(io.Writer)\n}\n\n// FullLogger is the combination of Logger, FormatLogger, CtxLogger and Control.\ntype FullLogger interface {\n\tLogger\n\tFormatLogger\n\tCtxLogger\n\tControl\n}\n\n// Level defines the priority of a log message.\n// When a logger is configured with a level, any log message with a lower\n// log level (smaller by integer comparison) will not be output.\ntype Level int\n\n// The levels of logs.\nconst (\n\tLevelTrace Level = iota\n\tLevelDebug\n\tLevelInfo\n\tLevelNotice\n\tLevelWarn\n\tLevelError\n\tLevelFatal\n)\n\nvar strs = []string{\n\t\"[Trace] \",\n\t\"[Debug] \",\n\t\"[Info] \",\n\t\"[Notice] \",\n\t\"[Warn] \",\n\t\"[Error] \",\n\t\"[Fatal] \",\n}\n\nfunc (lv Level) toString() string {\n\tif lv >= LevelTrace && lv <= LevelFatal {\n\t\treturn strs[lv]\n\t}\n\treturn fmt.Sprintf(\"[?%d] \", lv)\n}\n"
  },
  {
    "path": "pkg/common/hlog/system.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hlog\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n)\n\nvar silentMode = false\n\n// SetSilentMode is used to mute engine error log,\n// for example: error when reading request headers.\n// If true, hertz engine will mute it.\nfunc SetSilentMode(s bool) {\n\tsilentMode = s\n}\n\nvar builderPool = sync.Pool{New: func() interface{} {\n\treturn &strings.Builder{} // nolint:SA6002\n}}\n\ntype systemLogger struct {\n\tlogger FullLogger\n\tprefix string\n}\n\nfunc (ll *systemLogger) SetOutput(w io.Writer) {\n\tll.logger.SetOutput(w)\n}\n\nfunc (ll *systemLogger) SetLevel(lv Level) {\n\tll.logger.SetLevel(lv)\n}\n\nfunc (ll *systemLogger) Fatal(v ...interface{}) {\n\tv = append([]interface{}{ll.prefix}, v...)\n\tll.logger.Fatal(v...)\n}\n\nfunc (ll *systemLogger) Error(v ...interface{}) {\n\tv = append([]interface{}{ll.prefix}, v...)\n\tll.logger.Error(v...)\n}\n\nfunc (ll *systemLogger) Warn(v ...interface{}) {\n\tv = append([]interface{}{ll.prefix}, v...)\n\tll.logger.Warn(v...)\n}\n\nfunc (ll *systemLogger) Notice(v ...interface{}) {\n\tv = append([]interface{}{ll.prefix}, v...)\n\tll.logger.Notice(v...)\n}\n\nfunc (ll *systemLogger) Info(v ...interface{}) {\n\tv = append([]interface{}{ll.prefix}, v...)\n\tll.logger.Info(v...)\n}\n\nfunc (ll *systemLogger) Debug(v ...interface{}) {\n\tv = append([]interface{}{ll.prefix}, v...)\n\tll.logger.Debug(v...)\n}\n\nfunc (ll *systemLogger) Trace(v ...interface{}) {\n\tv = append([]interface{}{ll.prefix}, v...)\n\tll.logger.Trace(v...)\n}\n\nfunc (ll *systemLogger) Fatalf(format string, v ...interface{}) {\n\tll.logger.Fatalf(ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) Errorf(format string, v ...interface{}) {\n\tif silentMode && format == EngineErrorFormat {\n\t\treturn\n\t}\n\tll.logger.Errorf(ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) Warnf(format string, v ...interface{}) {\n\tll.logger.Warnf(ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) Noticef(format string, v ...interface{}) {\n\tll.logger.Noticef(ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) Infof(format string, v ...interface{}) {\n\tll.logger.Infof(ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) Debugf(format string, v ...interface{}) {\n\tll.logger.Debugf(ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) Tracef(format string, v ...interface{}) {\n\tll.logger.Tracef(ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) CtxFatalf(ctx context.Context, format string, v ...interface{}) {\n\tll.logger.CtxFatalf(ctx, ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) CtxErrorf(ctx context.Context, format string, v ...interface{}) {\n\tll.logger.CtxErrorf(ctx, ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) CtxWarnf(ctx context.Context, format string, v ...interface{}) {\n\tll.logger.CtxWarnf(ctx, ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) CtxNoticef(ctx context.Context, format string, v ...interface{}) {\n\tll.logger.CtxNoticef(ctx, ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) CtxInfof(ctx context.Context, format string, v ...interface{}) {\n\tll.logger.CtxInfof(ctx, ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) CtxDebugf(ctx context.Context, format string, v ...interface{}) {\n\tll.logger.CtxDebugf(ctx, ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) CtxTracef(ctx context.Context, format string, v ...interface{}) {\n\tll.logger.CtxTracef(ctx, ll.addPrefix(format), v...)\n}\n\nfunc (ll *systemLogger) addPrefix(format string) string {\n\tbuilder := builderPool.Get().(*strings.Builder)\n\tbuilder.Grow(len(format) + len(ll.prefix))\n\tbuilder.WriteString(ll.prefix)\n\tbuilder.WriteString(format)\n\ts := builder.String()\n\tbuilder.Reset()\n\tbuilderPool.Put(builder) // nolint:SA6002\n\treturn s\n}\n"
  },
  {
    "path": "pkg/common/hlog/system_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hlog\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc initTestSysLogger() {\n\tsysLogger = &systemLogger{\n\t\t&defaultLogger{\n\t\t\tstdlog: log.New(os.Stderr, \"\", 0),\n\t\t\tdepth:  4,\n\t\t},\n\t\tsystemLogPrefix,\n\t}\n}\n\nfunc TestSysLogger(t *testing.T) {\n\tinitTestSysLogger()\n\tvar w byteSliceWriter\n\tSetOutput(&w)\n\n\tsysLogger.Trace(\"trace work\")\n\tsysLogger.Debug(\"received work order\")\n\tsysLogger.Info(\"starting work\")\n\tsysLogger.Notice(\"something happens in work\")\n\tsysLogger.Warn(\"work may fail\")\n\tsysLogger.Error(\"work failed\")\n\n\tassert.DeepEqual(t, \"[Trace] HERTZ: trace work\\n\"+\n\t\t\"[Debug] HERTZ: received work order\\n\"+\n\t\t\"[Info] HERTZ: starting work\\n\"+\n\t\t\"[Notice] HERTZ: something happens in work\\n\"+\n\t\t\"[Warn] HERTZ: work may fail\\n\"+\n\t\t\"[Error] HERTZ: work failed\\n\", string(w.b))\n}\n\nfunc TestSysFormatLogger(t *testing.T) {\n\tinitTestSysLogger()\n\tvar w byteSliceWriter\n\tSetOutput(&w)\n\n\twork := \"work\"\n\tsysLogger.Tracef(\"trace %s\", work)\n\tsysLogger.Debugf(\"received %s order\", work)\n\tsysLogger.Infof(\"starting %s\", work)\n\tsysLogger.Noticef(\"something happens in %s\", work)\n\tsysLogger.Warnf(\"%s may fail\", work)\n\tsysLogger.Errorf(\"%s failed\", work)\n\n\tassert.DeepEqual(t, \"[Trace] HERTZ: trace work\\n\"+\n\t\t\"[Debug] HERTZ: received work order\\n\"+\n\t\t\"[Info] HERTZ: starting work\\n\"+\n\t\t\"[Notice] HERTZ: something happens in work\\n\"+\n\t\t\"[Warn] HERTZ: work may fail\\n\"+\n\t\t\"[Error] HERTZ: work failed\\n\", string(w.b))\n}\n\nfunc TestSysCtxLogger(t *testing.T) {\n\tinitTestSysLogger()\n\tvar w byteSliceWriter\n\tSetOutput(&w)\n\n\tctx := context.Background()\n\twork := \"work\"\n\tsysLogger.CtxTracef(ctx, \"trace %s\", work)\n\tsysLogger.CtxDebugf(ctx, \"received %s order\", work)\n\tsysLogger.CtxInfof(ctx, \"starting %s\", work)\n\tsysLogger.CtxNoticef(ctx, \"something happens in %s\", work)\n\tsysLogger.CtxWarnf(ctx, \"%s may fail\", work)\n\tsysLogger.CtxErrorf(ctx, \"%s failed\", work)\n\n\tassert.DeepEqual(t, \"[Trace] HERTZ: trace work\\n\"+\n\t\t\"[Debug] HERTZ: received work order\\n\"+\n\t\t\"[Info] HERTZ: starting work\\n\"+\n\t\t\"[Notice] HERTZ: something happens in work\\n\"+\n\t\t\"[Warn] HERTZ: work may fail\\n\"+\n\t\t\"[Error] HERTZ: work failed\\n\", string(w.b))\n}\n"
  },
  {
    "path": "pkg/common/json/sonic.go",
    "content": "// Copyright 2022 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build (amd64 || arm64) && !stdjson\n\npackage json\n\nimport \"github.com/bytedance/sonic\"\n\n// Name is the name of the effective json package.\nconst Name = \"sonic\"\n\nvar (\n\tjson = sonic.ConfigStd\n\t// Marshal is sonic implementation exported by hertz which is used by rendering.\n\tMarshal = json.Marshal\n\t// Unmarshal is sonic implementation exported by hertz which is used by binding.\n\tUnmarshal = json.Unmarshal\n\t// MarshalIndent is sonic implementation exported by hertz.\n\tMarshalIndent = json.MarshalIndent\n\t// NewDecoder is sonic implementation exported by hertz.\n\tNewDecoder = json.NewDecoder\n\t// NewEncoder is sonic implementation exported by hertz.\n\tNewEncoder = json.NewEncoder\n)\n"
  },
  {
    "path": "pkg/common/json/std.go",
    "content": "// Copyright 2022 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build !(amd64 || arm64) || stdjson\n\npackage json\n\nimport \"encoding/json\"\n\n// Name is the name of the effective json package.\nconst Name = \"encoding/json\"\n\nvar (\n\t// Marshal is standard implementation exported by hertz which is used by rendering.\n\tMarshal = json.Marshal\n\t// Unmarshal is standard implementation exported by hertz which is used by binding.\n\tUnmarshal = json.Unmarshal\n\t// MarshalIndent is standard implementation exported by hertz.\n\tMarshalIndent = json.MarshalIndent\n\t// NewDecoder is standard implementation exported by hertz.\n\tNewDecoder = json.NewDecoder\n\t// NewEncoder is standard implementation exported by hertz.\n\tNewEncoder = json.NewEncoder\n)\n"
  },
  {
    "path": "pkg/common/stackless/doc.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The files in stackless package are forked from fasthttp[github.com/valyala/fasthttp],\n// and we keep the original Copyright[Copyright 2015 fasthttp authors] and License of fasthttp for those files.\n// We also need to modify as we need, the modifications are Copyright of 2022 CloudWeGo Authors.\n// Thanks for fasthttp authors! Below is the source code information:\n// \t\tRepo: github.com/valyala/fasthttp\n//\t\tForked Version: v1.36.0\n\npackage stackless\n"
  },
  {
    "path": "pkg/common/stackless/func.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage stackless\n\nimport (\n\t\"runtime\"\n\t\"sync\"\n)\n\n// NewFunc returns stackless wrapper for the function f.\n//\n// Unlike f, the returned stackless wrapper doesn't use stack space\n// on the goroutine that calls it.\n// The wrapper may save a lot of stack space if the following conditions\n// are met:\n//\n//   - f doesn't contain blocking calls on network, I/O or channels;\n//   - f uses a lot of stack space;\n//   - the wrapper is called from high number of concurrent goroutines.\n//\n// The stackless wrapper returns false if the call cannot be processed\n// at the moment due to high load.\nfunc NewFunc(f func(ctx interface{})) func(ctx interface{}) bool {\n\tif f == nil {\n\t\tpanic(\"BUG: f cannot be nil\")\n\t}\n\n\tfuncWorkCh := make(chan *funcWork, runtime.GOMAXPROCS(-1)*2048)\n\tonceInit := func() {\n\t\tn := runtime.GOMAXPROCS(-1)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tgo funcWorker(funcWorkCh, f)\n\t\t}\n\t}\n\tvar once sync.Once\n\n\treturn func(ctx interface{}) bool {\n\t\tonce.Do(onceInit)\n\t\tfw := getFuncWork()\n\t\tfw.ctx = ctx\n\n\t\tselect {\n\t\tcase funcWorkCh <- fw:\n\t\tdefault:\n\t\t\tputFuncWork(fw)\n\t\t\treturn false\n\t\t}\n\t\t<-fw.done\n\t\tputFuncWork(fw)\n\t\treturn true\n\t}\n}\n\nfunc funcWorker(funcWorkCh <-chan *funcWork, f func(ctx interface{})) {\n\tfor fw := range funcWorkCh {\n\t\tf(fw.ctx)\n\t\tfw.done <- struct{}{}\n\t}\n}\n\nfunc getFuncWork() *funcWork {\n\tv := funcWorkPool.Get()\n\tif v == nil {\n\t\tv = &funcWork{\n\t\t\tdone: make(chan struct{}, 1),\n\t\t}\n\t}\n\treturn v.(*funcWork)\n}\n\nfunc putFuncWork(fw *funcWork) {\n\tfw.ctx = nil\n\tfuncWorkPool.Put(fw)\n}\n\nvar funcWorkPool sync.Pool\n\ntype funcWork struct {\n\tctx  interface{}\n\tdone chan struct{}\n}\n"
  },
  {
    "path": "pkg/common/stackless/func_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage stackless\n\nimport (\n\t\"fmt\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestNewFuncSimple(t *testing.T) {\n\tvar n uint64\n\tf := NewFunc(func(ctx interface{}) {\n\t\tatomic.AddUint64(&n, uint64(ctx.(int)))\n\t})\n\n\titerations := 4 * 1024\n\tfor i := 0; i < iterations; i++ {\n\t\tif !f(2) {\n\t\t\tt.Fatalf(\"f mustn't return false\")\n\t\t}\n\t}\n\tif n != uint64(2*iterations) {\n\t\tt.Fatalf(\"Unexpected n: %d. Expecting %d\", n, 2*iterations)\n\t}\n}\n\nfunc TestNewFuncMulti(t *testing.T) {\n\tvar n1, n2 uint64\n\tf1 := NewFunc(func(ctx interface{}) {\n\t\tatomic.AddUint64(&n1, uint64(ctx.(int)))\n\t})\n\tf2 := NewFunc(func(ctx interface{}) {\n\t\tatomic.AddUint64(&n2, uint64(ctx.(int)))\n\t})\n\n\titerations := 4 * 1024\n\n\tf1Done := make(chan error, 1)\n\tgo func() {\n\t\tvar err error\n\t\tfor i := 0; i < iterations; i++ {\n\t\t\tif !f1(3) {\n\t\t\t\terr = fmt.Errorf(\"f1 mustn't return false\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tf1Done <- err\n\t}()\n\n\tf2Done := make(chan error, 1)\n\tgo func() {\n\t\tvar err error\n\t\tfor i := 0; i < iterations; i++ {\n\t\t\tif !f2(5) {\n\t\t\t\terr = fmt.Errorf(\"f2 mustn't return false\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tf2Done <- err\n\t}()\n\n\tselect {\n\tcase err := <-f1Done:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tselect {\n\tcase err := <-f2Done:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tif n1 != uint64(3*iterations) {\n\t\tt.Fatalf(\"unexpected n1: %d. Expecting %d\", n1, 3*iterations)\n\t}\n\tif n2 != uint64(5*iterations) {\n\t\tt.Fatalf(\"unexpected n2: %d. Expecting %d\", n2, 5*iterations)\n\t}\n}\n"
  },
  {
    "path": "pkg/common/stackless/func_timing_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage stackless\n\nimport (\n\t\"sync/atomic\"\n\t\"testing\"\n)\n\nfunc BenchmarkFuncOverhead(b *testing.B) {\n\tvar n uint64\n\tf := NewFunc(func(ctx interface{}) {\n\t\tatomic.AddUint64(&n, *(ctx.(*uint64)))\n\t})\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tx := uint64(1)\n\t\tfor pb.Next() {\n\t\t\tif !f(&x) {\n\t\t\t\tb.Fatalf(\"f mustn't return false\")\n\t\t\t}\n\t\t}\n\t})\n\tif n != uint64(b.N) {\n\t\tb.Fatalf(\"unexpected n: %d. Expecting %d\", n, b.N)\n\t}\n}\n\nfunc BenchmarkFuncPure(b *testing.B) {\n\tvar n uint64\n\tf := func(x *uint64) {\n\t\tatomic.AddUint64(&n, *x)\n\t}\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tx := uint64(1)\n\t\tfor pb.Next() {\n\t\t\tf(&x)\n\t\t}\n\t})\n\tif n != uint64(b.N) {\n\t\tb.Fatalf(\"unexpected n: %d. Expecting %d\", n, b.N)\n\t}\n}\n"
  },
  {
    "path": "pkg/common/stackless/writer.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage stackless\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n)\n\n// Writer is an interface stackless writer must conform to.\n//\n// The interface contains common subset for Writers from compress/* packages.\ntype Writer interface {\n\tWrite(p []byte) (int, error)\n\tFlush() error\n\tClose() error\n\tReset(w io.Writer)\n}\n\n// NewWriterFunc must return new writer that will be wrapped into\n// stackless writer.\ntype NewWriterFunc func(w io.Writer) Writer\n\n// NewWriter creates a stackless writer around a writer returned\n// from newWriter.\n//\n// The returned writer writes data to dstW.\n//\n// Writers that use a lot of stack space may be wrapped into stackless writer,\n// thus saving stack space for high number of concurrently running goroutines.\nfunc NewWriter(dstW io.Writer, newWriter NewWriterFunc) Writer {\n\tw := &writer{\n\t\tdstW: dstW,\n\t}\n\tw.zw = newWriter(&w.xw)\n\treturn w\n}\n\ntype writer struct {\n\tdstW io.Writer\n\tzw   Writer\n\txw   xWriter\n\n\terr error\n\tn   int\n\n\tp  []byte\n\top op\n}\n\ntype op int\n\nconst (\n\topWrite op = iota\n\topFlush\n\topClose\n\topReset\n)\n\nfunc (w *writer) Write(p []byte) (int, error) {\n\tw.p = p\n\terr := w.do(opWrite)\n\tw.p = nil\n\treturn w.n, err\n}\n\nfunc (w *writer) Flush() error {\n\treturn w.do(opFlush)\n}\n\nfunc (w *writer) Close() error {\n\treturn w.do(opClose)\n}\n\nfunc (w *writer) Reset(dstW io.Writer) {\n\tw.xw.Reset()\n\tw.do(opReset) //nolint:errcheck\n\tw.dstW = dstW\n}\n\nfunc (w *writer) do(op op) error {\n\tw.op = op\n\tif !stacklessWriterFunc(w) {\n\t\treturn errHighLoad\n\t}\n\terr := w.err\n\tif err != nil {\n\t\treturn err\n\t}\n\tif w.xw.bb != nil && len(w.xw.bb.B) > 0 {\n\t\t_, err = w.dstW.Write(w.xw.bb.B)\n\t}\n\tw.xw.Reset()\n\n\treturn err\n}\n\nvar errHighLoad = errors.NewPublic(\"cannot compress data due to high load\")\n\nvar stacklessWriterFunc = NewFunc(writerFunc)\n\nfunc writerFunc(ctx interface{}) {\n\tw := ctx.(*writer)\n\tswitch w.op {\n\tcase opWrite:\n\t\tw.n, w.err = w.zw.Write(w.p)\n\tcase opFlush:\n\t\tw.err = w.zw.Flush()\n\tcase opClose:\n\t\tw.err = w.zw.Close()\n\tcase opReset:\n\t\tw.zw.Reset(&w.xw)\n\t\tw.err = nil\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"BUG: unexpected op: %d\", w.op))\n\t}\n}\n\ntype xWriter struct {\n\tbb *bytebufferpool.ByteBuffer\n}\n\nfunc (w *xWriter) Write(p []byte) (int, error) {\n\tif w.bb == nil {\n\t\tw.bb = bufferPool.Get()\n\t}\n\treturn w.bb.Write(p)\n}\n\nfunc (w *xWriter) Reset() {\n\tif w.bb != nil {\n\t\tbufferPool.Put(w.bb)\n\t\tw.bb = nil\n\t}\n}\n\nvar bufferPool bytebufferpool.Pool\n"
  },
  {
    "path": "pkg/common/stackless/writer_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage stackless\n\nimport (\n\t\"bytes\"\n\t\"compress/flate\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestCompressFlateSerial(t *testing.T) {\n\tif err := testCompressFlate(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n}\n\nfunc TestCompressFlateConcurrent(t *testing.T) {\n\tif err := testConcurrent(testCompressFlate, 10); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n}\n\nfunc testCompressFlate() error {\n\treturn testWriter(func(w io.Writer) Writer {\n\t\tzw, err := flate.NewWriter(w, flate.DefaultCompression)\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"BUG: unexpected error: %s\", err))\n\t\t}\n\t\treturn zw\n\t}, func(r io.Reader) io.Reader {\n\t\treturn flate.NewReader(r)\n\t})\n}\n\nfunc TestCompressGzipSerial(t *testing.T) {\n\tif err := testCompressGzip(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n}\n\nfunc TestCompressGzipConcurrent(t *testing.T) {\n\tif err := testConcurrent(testCompressGzip, 10); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n}\n\nfunc testCompressGzip() error {\n\treturn testWriter(func(w io.Writer) Writer {\n\t\treturn gzip.NewWriter(w)\n\t}, func(r io.Reader) io.Reader {\n\t\tzr, err := gzip.NewReader(r)\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"BUG: cannot create gzip reader: %s\", err))\n\t\t}\n\t\treturn zr\n\t})\n}\n\nfunc testWriter(newWriter NewWriterFunc, newReader func(io.Reader) io.Reader) error {\n\tdstW := &bytes.Buffer{}\n\tw := NewWriter(dstW, newWriter)\n\n\tfor i := 0; i < 5; i++ {\n\t\tif err := testWriterReuse(w, dstW, newReader); err != nil {\n\t\t\treturn fmt.Errorf(\"unexpected error when re-using writer on iteration %d: %s\", i, err)\n\t\t}\n\t\tdstW = &bytes.Buffer{}\n\t\tw.Reset(dstW)\n\t}\n\n\treturn nil\n}\n\nfunc testWriterReuse(w Writer, r io.Reader, newReader func(io.Reader) io.Reader) error {\n\twantW := &bytes.Buffer{}\n\tmw := io.MultiWriter(w, wantW)\n\tfor i := 0; i < 30; i++ {\n\t\tfmt.Fprintf(mw, \"foobar %d\\n\", i)\n\t\tif i%13 == 0 {\n\t\t\tif err := w.Flush(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error on flush: %s\", err)\n\t\t\t}\n\t\t}\n\t}\n\tw.Close()\n\n\tzr := newReader(r)\n\tdata, err := ioutil.ReadAll(zr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %s, data=%q\", err, data)\n\t}\n\n\twantData := wantW.Bytes()\n\tif !bytes.Equal(data, wantData) {\n\t\treturn fmt.Errorf(\"unexpected data: %q. Expecting %q\", data, wantData)\n\t}\n\n\treturn nil\n}\n\nfunc testConcurrent(testFunc func() error, concurrency int) error {\n\tch := make(chan error, concurrency)\n\tfor i := 0; i < concurrency; i++ {\n\t\tgo func() {\n\t\t\tch <- testFunc()\n\t\t}()\n\t}\n\tfor i := 0; i < concurrency; i++ {\n\t\tselect {\n\t\tcase err := <-ch:\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"unexpected error on goroutine %d: %s\", i, err)\n\t\t\t}\n\t\tcase <-time.After(time.Second):\n\t\t\treturn fmt.Errorf(\"timeout on goroutine %d\", i)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/common/test/assert/assert.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage assert\n\nimport (\n\t\"reflect\"\n)\n\ntype testingT interface {\n\tHelper()\n\tFatal(args ...any)\n\tFatalf(format string, args ...any)\n}\n\n// Assert .\nfunc Assert(t testingT, cond bool, val ...interface{}) {\n\tt.Helper()\n\tif !cond {\n\t\tif len(val) > 0 {\n\t\t\tval = append([]interface{}{\"assertion failed:\"}, val...)\n\t\t\tt.Fatal(val...)\n\t\t} else {\n\t\t\tt.Fatal(\"assertion failed\")\n\t\t}\n\t}\n}\n\n// Assertf .\nfunc Assertf(t testingT, cond bool, format string, val ...interface{}) {\n\tt.Helper()\n\tif !cond {\n\t\tt.Fatalf(format, val...)\n\t}\n}\n\n// DeepEqual .\nfunc DeepEqual(t testingT, expected, actual interface{}) {\n\tt.Helper()\n\tif !reflect.DeepEqual(actual, expected) {\n\t\tt.Fatalf(\"assertion failed, unexpected: %v, expected: %v\", actual, expected)\n\t}\n}\n\nfunc isNil(rv reflect.Value) bool {\n\tswitch rv.Kind() {\n\tcase reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer,\n\t\treflect.UnsafePointer, reflect.Interface, reflect.Slice:\n\t\tif rv.IsNil() {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc Nil(t testingT, data interface{}) {\n\tt.Helper()\n\tif data == nil || isNil(reflect.ValueOf(data)) {\n\t\treturn\n\t}\n\tt.Fatalf(\"assertion failed, unexpected: %v, expected: nil\", data)\n}\n\nfunc NotNil(t testingT, data interface{}) {\n\tt.Helper()\n\tif data == nil || isNil(reflect.ValueOf(data)) {\n\t\tt.Fatalf(\"assertion failed, unexpected: %v, expected: not nil\", data)\n\t}\n}\n\n// NotEqual .\nfunc NotEqual(t testingT, expected, actual interface{}) {\n\tt.Helper()\n\tif expected == nil || actual == nil {\n\t\tif expected == actual {\n\t\t\tt.Fatalf(\"assertion failed: %v == %v\", actual, expected)\n\t\t}\n\t}\n\n\tif reflect.DeepEqual(actual, expected) {\n\t\tt.Fatalf(\"assertion failed: %v == %v\", actual, expected)\n\t}\n}\n\nfunc True(t testingT, obj interface{}) {\n\tt.Helper()\n\tDeepEqual(t, true, obj)\n}\n\nfunc False(t testingT, obj interface{}) {\n\tt.Helper()\n\tDeepEqual(t, false, obj)\n}\n\n// Panic .\nfunc Panic(t testingT, fn func()) {\n\tt.Helper()\n\tdefer func() {\n\t\tif err := recover(); err == nil {\n\t\t\tt.Fatal(\"assertion failed: did not panic\")\n\t\t}\n\t}()\n\tfn()\n}\n\n// NotPanic .\nfunc NotPanic(t testingT, fn func()) {\n\tt.Helper()\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tt.Fatal(\"assertion failed: panicked\")\n\t\t}\n\t}()\n\tfn()\n}\n"
  },
  {
    "path": "pkg/common/test/assert/assert_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage assert\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\ntype mockTestingT struct {\n\tfatalstr string\n}\n\nfunc (mockTestingT) Helper() {}\n\nfunc (m *mockTestingT) Reset() {\n\tm.fatalstr = \"\"\n}\n\nfunc (m *mockTestingT) Fatal(args ...any) {\n\tm.fatalstr = fmt.Sprintln(args...)\n}\n\nfunc (m *mockTestingT) Fatalf(fm string, args ...any) {\n\tm.fatalstr = fmt.Sprintf(fm, args...)\n}\n\nfunc (m *mockTestingT) String() string {\n\treturn m.fatalstr\n}\n\nfunc (m *mockTestingT) Expect(t *testing.T, s string) {\n\tt.Helper()\n\tgot := strings.TrimSpace(m.fatalstr)\n\tif got != s {\n\t\tt.Fatalf(\"got: %q expect: %q\", got, s)\n\t}\n\tm.Reset()\n}\n\nfunc TestAssert(t *testing.T) {\n\tm := &mockTestingT{}\n\tAssert(m, true)\n\tm.Expect(t, \"\")\n\n\tAssert(m, false)\n\tm.Expect(t, \"assertion failed\")\n\n\tAssert(m, false, \"hello\")\n\tm.Expect(t, \"assertion failed: hello\")\n\n\tAssertf(m, true, \"hello %s\", \"world\")\n\tm.Expect(t, \"\")\n\tAssertf(m, false, \"hello %s\", \"world\")\n\tm.Expect(t, \"hello world\")\n}\n\nfunc TestNil(t *testing.T) {\n\tm := &mockTestingT{}\n\n\tNil(m, nil)\n\tm.Expect(t, \"\")\n\tNil(m, (*testing.T)(nil))\n\tm.Expect(t, \"\")\n\n\tNil(m, 1)\n\tm.Expect(t, \"assertion failed, unexpected: 1, expected: nil\")\n\n\tNil(m, \"hello\")\n\tm.Expect(t, \"assertion failed, unexpected: hello, expected: nil\")\n\n\tNotNil(m, 1)\n\tm.Expect(t, \"\")\n\n\tNotNil(m, \"hello\")\n\tm.Expect(t, \"\")\n\n\tNotNil(m, struct {\n\t\thello string\n\t}{})\n\tm.Expect(t, \"\")\n\n\tNotNil(m, (*testing.T)(nil))\n\tm.Expect(t, `assertion failed, unexpected: <nil>, expected: not nil`)\n\n}\n\nfunc TestDeepEqual(t *testing.T) {\n\tm := &mockTestingT{}\n\n\tDeepEqual(m, 1, 1)\n\tm.Expect(t, \"\")\n\n\tDeepEqual(m, 1, 2)\n\tm.Expect(t, `assertion failed, unexpected: 2, expected: 1`)\n\n}\n\nfunc TestNotEqual(t *testing.T) {\n\tm := &mockTestingT{}\n\n\tNotEqual(m, 1, 2)\n\tm.Expect(t, \"\")\n\n\tNotEqual(m, nil, nil)\n\tm.Expect(t, `assertion failed: <nil> == <nil>`)\n}\n\nfunc TestTrueFalse(t *testing.T) {\n\tm := &mockTestingT{}\n\n\tTrue(m, true)\n\tm.Expect(t, \"\")\n\n\tFalse(m, false)\n\tm.Expect(t, \"\")\n}\n\nfunc TestPanic(t *testing.T) {\n\tm := &mockTestingT{}\n\n\tPanic(m, func() {\n\t\tpanic(\"hello\")\n\t})\n\tm.Expect(t, \"\")\n\tPanic(m, func() {\n\t})\n\tm.Expect(t, `assertion failed: did not panic`)\n\n\tNotPanic(m, func() {\n\t})\n\tm.Expect(t, \"\")\n\n\tNotPanic(m, func() {\n\t\tpanic(\"hello\")\n\t})\n\tm.Expect(t, `assertion failed: panicked`)\n\n}\n"
  },
  {
    "path": "pkg/common/test/mock/body_data.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage mock\n\nimport \"fmt\"\n\nfunc CreateFixedBody(bodySize int) []byte {\n\tvar b []byte\n\tfor i := 0; i < bodySize; i++ {\n\t\tb = append(b, byte(i%10)+'0')\n\t}\n\treturn b\n}\n\nfunc CreateChunkedBody(body []byte, trailer map[string]string, hasTrailer bool) []byte {\n\tvar b []byte\n\tchunkSize := 1\n\tfor len(body) > 0 {\n\t\tif chunkSize > len(body) {\n\t\t\tchunkSize = len(body)\n\t\t}\n\t\tb = append(b, []byte(fmt.Sprintf(\"%x\\r\\n\", chunkSize))...)\n\t\tb = append(b, body[:chunkSize]...)\n\t\tb = append(b, []byte(\"\\r\\n\")...)\n\t\tbody = body[chunkSize:]\n\t\tchunkSize++\n\t}\n\tif hasTrailer {\n\t\tb = append(b, \"0\\r\\n\"...)\n\t\tfor k, v := range trailer {\n\t\t\tb = append(b, k...)\n\t\t\tb = append(b, \": \"...)\n\t\t\tb = append(b, v...)\n\t\t\tb = append(b, \"\\r\\n\"...)\n\t\t}\n\t\tb = append(b, \"\\r\\n\"...)\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "pkg/common/test/mock/body_data_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage mock\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestGenerateCreateFixedBody(t *testing.T) {\n\tbodySize := 10\n\tresFixedBody := \"0123456789\"\n\tb := CreateFixedBody(bodySize)\n\tif string(b) != resFixedBody {\n\t\tt.Fatalf(\"Unexpected %s. Expecting %s.\", b, resFixedBody)\n\t}\n\n\tnilFixedBody := CreateFixedBody(0)\n\tif nilFixedBody != nil {\n\t\tt.Fatalf(\"Unexpected %s. Expecting a nil\", nilFixedBody)\n\t}\n}\n\nfunc TestGenerateCreateChunkedBody(t *testing.T) {\n\tbodySize := 10\n\tb := CreateFixedBody(bodySize)\n\ttrailer := map[string]string{\"Foo\": \"chunked shit\"}\n\texpectCb := \"1\\r\\n0\\r\\n2\\r\\n12\\r\\n3\\r\\n345\\r\\n4\\r\\n6789\\r\\n0\\r\\nFoo: chunked shit\\r\\n\\r\\n\"\n\n\tcb := CreateChunkedBody(b, trailer, true)\n\tassert.DeepEqual(t, expectCb, string(cb))\n}\n"
  },
  {
    "path": "pkg/common/test/mock/network.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage mock\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\nvar (\n\tErrReadTimeout  = errs.New(errs.ErrTimeout, errs.ErrorTypePublic, \"read timeout\")\n\tErrWriteTimeout = errs.New(errs.ErrTimeout, errs.ErrorTypePublic, \"write timeout\")\n)\n\ntype Conn struct {\n\tzr       network.Reader\n\tzw       network.ReadWriter\n\twroteLen int\n\n\trtimeout time.Duration\n\twtimeout time.Duration\n}\n\ntype Recorder interface {\n\tnetwork.Reader\n\tWroteLen() int\n}\n\nfunc (m *Conn) SetWriteTimeout(t time.Duration) error {\n\t// TODO implement me\n\treturn nil\n}\n\ntype SlowReadConn struct {\n\t*Conn\n}\n\nfunc (m *SlowReadConn) SetWriteTimeout(t time.Duration) error {\n\treturn nil\n}\n\nfunc (m *SlowReadConn) SetReadTimeout(t time.Duration) error {\n\tm.Conn.rtimeout = t\n\treturn nil\n}\n\nfunc SlowReadDialer(addr string) (network.Conn, error) {\n\treturn NewSlowReadConn(\"\"), nil\n}\n\nfunc SlowWriteDialer(addr string) (network.Conn, error) {\n\treturn NewSlowWriteConn(\"\"), nil\n}\n\nfunc (m *Conn) ReadBinary(n int) (p []byte, err error) {\n\treturn m.zr.(netpoll.Reader).ReadBinary(n)\n}\n\nfunc (m *Conn) Read(b []byte) (n int, err error) {\n\treturn netpoll.NewIOReader(m.zr.(netpoll.Reader)).Read(b)\n}\n\nfunc (m *Conn) Write(b []byte) (n int, err error) {\n\treturn netpoll.NewIOWriter(m.zw.(netpoll.ReadWriter)).Write(b)\n}\n\nfunc (m *Conn) Release() error {\n\treturn nil\n}\n\nfunc (m *Conn) Peek(i int) ([]byte, error) {\n\tb, err := m.zr.Peek(i)\n\tif err != nil || len(b) != i {\n\t\tif m.rtimeout <= 0 {\n\t\t\t// simulate timeout forever\n\t\t\tselect {}\n\t\t}\n\t\ttime.Sleep(m.rtimeout)\n\t\treturn nil, errs.ErrTimeout\n\t}\n\treturn b, err\n}\n\nfunc (m *Conn) Skip(n int) error {\n\treturn m.zr.Skip(n)\n}\n\nfunc (m *Conn) ReadByte() (byte, error) {\n\treturn m.zr.ReadByte()\n}\n\nfunc (m *Conn) Len() int {\n\treturn m.zr.Len()\n}\n\nfunc (m *Conn) Malloc(n int) (buf []byte, err error) {\n\tm.wroteLen += n\n\treturn m.zw.Malloc(n)\n}\n\nfunc (m *Conn) WriteBinary(b []byte) (n int, err error) {\n\tn, err = m.zw.WriteBinary(b)\n\tm.wroteLen += n\n\treturn n, err\n}\n\nfunc (m *Conn) Flush() error {\n\treturn m.zw.Flush()\n}\n\nfunc (m *Conn) WriterRecorder() Recorder {\n\treturn &recorder{c: m, Reader: m.zw}\n}\n\nfunc (m *Conn) GetReadTimeout() time.Duration {\n\treturn m.rtimeout\n}\n\nfunc (m *Conn) GetWriteTimeout() time.Duration {\n\treturn m.wtimeout\n}\n\ntype recorder struct {\n\tc *Conn\n\tnetwork.Reader\n}\n\nfunc (r *recorder) WroteLen() int {\n\treturn r.c.wroteLen\n}\n\nfunc (m *SlowReadConn) Peek(i int) ([]byte, error) {\n\tb, err := m.zr.Peek(i)\n\tif m.rtimeout > 0 {\n\t\ttime.Sleep(m.rtimeout)\n\t} else {\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\tif err != nil || len(b) != i {\n\t\treturn nil, ErrReadTimeout\n\t}\n\treturn b, err\n}\n\nfunc NewConn(source string) *Conn {\n\tzr := netpoll.NewReader(strings.NewReader(source))\n\tzw := netpoll.NewReadWriter(&bytes.Buffer{})\n\n\treturn &Conn{\n\t\tzr: zr,\n\t\tzw: zw,\n\t}\n}\n\ntype BrokenConn struct {\n\t*Conn\n}\n\nfunc (o *BrokenConn) Peek(i int) ([]byte, error) {\n\treturn nil, io.ErrUnexpectedEOF\n}\n\nfunc (o *BrokenConn) Read(b []byte) (n int, err error) {\n\treturn 0, io.ErrUnexpectedEOF\n}\n\nfunc (o *BrokenConn) Flush() error {\n\treturn errs.ErrConnectionClosed\n}\n\nfunc NewBrokenConn(source string) *BrokenConn {\n\treturn &BrokenConn{Conn: NewConn(source)}\n}\n\ntype OneTimeConn struct {\n\tisRead        bool\n\tisFlushed     bool\n\tcontentLength int\n\t*Conn\n}\n\nfunc (o *OneTimeConn) Peek(n int) ([]byte, error) {\n\tif o.isRead {\n\t\treturn nil, io.EOF\n\t}\n\treturn o.Conn.Peek(n)\n}\n\nfunc (o *OneTimeConn) Skip(n int) error {\n\tif o.isRead {\n\t\treturn io.EOF\n\t}\n\to.contentLength -= n\n\n\tif o.contentLength == 0 {\n\t\to.isRead = true\n\t}\n\n\treturn o.Conn.Skip(n)\n}\n\nfunc (o *OneTimeConn) Flush() error {\n\tif o.isFlushed {\n\t\treturn errs.ErrConnectionClosed\n\t}\n\to.isFlushed = true\n\treturn o.Conn.Flush()\n}\n\nfunc NewOneTimeConn(source string) *OneTimeConn {\n\treturn &OneTimeConn{isRead: false, isFlushed: false, Conn: NewConn(source), contentLength: len(source)}\n}\n\nfunc NewSlowReadConn(source string) *SlowReadConn {\n\treturn &SlowReadConn{Conn: NewConn(source)}\n}\n\ntype ErrorReadConn struct {\n\t*Conn\n\terrorToReturn error\n}\n\nfunc NewErrorReadConn(err error) *ErrorReadConn {\n\treturn &ErrorReadConn{\n\t\tConn:          NewConn(\"\"),\n\t\terrorToReturn: err,\n\t}\n}\n\nfunc (er *ErrorReadConn) Peek(n int) ([]byte, error) {\n\treturn nil, er.errorToReturn\n}\n\ntype SlowWriteConn struct {\n\t*Conn\n\twriteTimeout time.Duration\n}\n\nfunc (m *SlowWriteConn) SetWriteTimeout(t time.Duration) error {\n\tm.writeTimeout = t\n\treturn nil\n}\n\nfunc NewSlowWriteConn(source string) *SlowWriteConn {\n\treturn &SlowWriteConn{NewConn(source), 0}\n}\n\nfunc (m *SlowWriteConn) Flush() error {\n\terr := m.zw.Flush()\n\tif err == nil {\n\t\ttime.Sleep(m.writeTimeout)\n\t\treturn ErrWriteTimeout\n\t}\n\treturn err\n}\n\nfunc (m *Conn) Close() error {\n\treturn nil\n}\n\nfunc (m *Conn) LocalAddr() net.Addr {\n\treturn nil\n}\n\nfunc (m *Conn) RemoteAddr() net.Addr {\n\treturn nil\n}\n\nfunc (m *Conn) SetDeadline(t time.Time) error {\n\tm.rtimeout = -time.Since(t)\n\tm.wtimeout = m.rtimeout\n\treturn nil\n}\n\nfunc (m *Conn) SetReadDeadline(t time.Time) error {\n\tm.rtimeout = -time.Since(t)\n\treturn nil\n}\n\nfunc (m *Conn) SetWriteDeadline(t time.Time) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *Conn) Reader() network.Reader {\n\treturn m.zr\n}\n\nfunc (m *Conn) Writer() network.Writer {\n\treturn m.zw\n}\n\nfunc (m *Conn) IsActive() bool {\n\tpanic(\"implement me\")\n}\n\nfunc (m *Conn) SetIdleTimeout(timeout time.Duration) error {\n\treturn nil\n}\n\nfunc (m *Conn) SetReadTimeout(t time.Duration) error {\n\tm.rtimeout = t\n\treturn nil\n}\n\nfunc (m *Conn) SetOnRequest(on netpoll.OnRequest) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *Conn) AddCloseCallback(callback netpoll.CloseCallback) error {\n\tpanic(\"implement me\")\n}\n\ntype StreamConn struct {\n\tHasReleased bool\n\tData        []byte\n}\n\nfunc NewStreamConn() *StreamConn {\n\treturn &StreamConn{\n\t\tData: make([]byte, 1<<15, 1<<16),\n\t}\n}\n\nfunc (m *StreamConn) Peek(n int) ([]byte, error) {\n\tif len(m.Data) >= n {\n\t\treturn m.Data[:n], nil\n\t}\n\tif n == 1 {\n\t\tm.Data = m.Data[:cap(m.Data)]\n\t\treturn m.Data[:1], nil\n\t}\n\treturn nil, errs.NewPublic(\"not enough data\")\n}\n\nfunc (m *StreamConn) Skip(n int) error {\n\tif len(m.Data) >= n {\n\t\tm.Data = m.Data[n:]\n\t\treturn nil\n\t}\n\treturn errs.NewPublic(\"not enough data\")\n}\n\nfunc (m *StreamConn) Release() error {\n\tm.HasReleased = true\n\treturn nil\n}\n\nfunc (m *StreamConn) Len() int {\n\treturn len(m.Data)\n}\n\nfunc (m *StreamConn) ReadByte() (byte, error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *StreamConn) ReadBinary(n int) (p []byte, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc DialerFun(addr string) (network.Conn, error) {\n\treturn NewConn(\"\"), nil\n}\n\ntype MockWriter struct {\n\tw network.Writer\n\n\tMockMalloc      func(n int) (buf []byte, err error)\n\tMockWriteBinary func(b []byte) (n int, err error)\n\tMockFlush       func() error\n}\n\nfunc NewMockWriter(w network.Writer) *MockWriter {\n\treturn &MockWriter{w: w}\n}\n\nfunc (m *MockWriter) Malloc(n int) (buf []byte, err error) {\n\tif m.MockMalloc != nil {\n\t\treturn m.MockMalloc(n)\n\t}\n\treturn m.w.Malloc(n)\n}\n\nfunc (m *MockWriter) WriteBinary(b []byte) (n int, err error) {\n\tif m.MockWriteBinary != nil {\n\t\treturn m.MockWriteBinary(b)\n\t}\n\treturn m.w.WriteBinary(b)\n}\n\nfunc (m *MockWriter) Flush() error {\n\tif m.MockFlush != nil {\n\t\treturn m.MockFlush()\n\t}\n\treturn m.w.Flush()\n}\n\ntype TLSConn struct {\n\tnetwork.Conn\n\n\tHandshakeErr error\n}\n\nvar _ network.ConnTLSer = (*TLSConn)(nil)\n\nfunc (c *TLSConn) Handshake() error {\n\treturn c.HandshakeErr\n}\n\nfunc (c *TLSConn) ConnectionState() tls.ConnectionState {\n\treturn tls.ConnectionState{}\n}\n\nfunc NewTLSConn(conn network.Conn) *TLSConn {\n\treturn &TLSConn{Conn: conn}\n}\n"
  },
  {
    "path": "pkg/common/test/mock/network_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage mock\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\nfunc TestConn(t *testing.T) {\n\tt.Run(\"TestReader\", func(t *testing.T) {\n\t\ts1 := \"abcdef4343\"\n\t\tconn1 := NewConn(s1)\n\t\tassert.Nil(t, conn1.SetWriteTimeout(1))\n\t\terr := conn1.SetReadDeadline(time.Now().Add(time.Millisecond * 100))\n\t\tassert.DeepEqual(t, nil, err)\n\t\terr = conn1.SetReadTimeout(time.Millisecond * 100)\n\t\tassert.DeepEqual(t, nil, err)\n\t\tassert.DeepEqual(t, time.Millisecond*100, conn1.GetReadTimeout())\n\n\t\t// Peek Skip Read\n\t\tb, _ := conn1.Peek(1)\n\t\tassert.DeepEqual(t, []byte{'a'}, b)\n\t\tconn1.Skip(1)\n\t\treadByte, _ := conn1.ReadByte()\n\t\tassert.DeepEqual(t, byte('b'), readByte)\n\n\t\tp := make([]byte, 100)\n\t\tn, err := conn1.Read(p)\n\t\tassert.DeepEqual(t, nil, err)\n\t\tassert.DeepEqual(t, s1[2:], string(p[:n]))\n\n\t\t_, err = conn1.Peek(1)\n\t\tassert.DeepEqual(t, errs.ErrTimeout, err)\n\n\t\tconn2 := NewConn(s1)\n\t\tp, _ = conn2.ReadBinary(len(s1))\n\t\tassert.DeepEqual(t, s1, string(p))\n\t\tassert.DeepEqual(t, 0, conn2.Len())\n\t\t// Reader\n\t\tassert.DeepEqual(t, conn2.zr, conn2.Reader())\n\t})\n\n\tt.Run(\"TestReadWriter\", func(t *testing.T) {\n\t\ts1 := \"abcdef4343\"\n\t\tconn := NewConn(s1)\n\t\tp, err := conn.ReadBinary(len(s1))\n\t\tassert.DeepEqual(t, nil, err)\n\t\tassert.DeepEqual(t, s1, string(p))\n\n\t\twr := conn.WriterRecorder()\n\t\ts2 := \"efghljk\"\n\t\t// WriteBinary\n\t\tn, err := conn.WriteBinary([]byte(s2))\n\t\tassert.DeepEqual(t, nil, err)\n\t\tassert.DeepEqual(t, len(s2), n)\n\t\tassert.DeepEqual(t, len(s2), wr.WroteLen())\n\n\t\t// Flush\n\t\tp, _ = wr.ReadBinary(len(s2))\n\t\tassert.DeepEqual(t, len(p), 0)\n\n\t\tconn.Flush()\n\t\tp, _ = wr.ReadBinary(len(s2))\n\t\tassert.DeepEqual(t, s2, string(p))\n\n\t\t// Write\n\t\ts3 := \"foobarbaz\"\n\t\tn, err = conn.Write([]byte(s3))\n\t\tassert.DeepEqual(t, nil, err)\n\t\tassert.DeepEqual(t, len(s3), n)\n\t\tp, _ = wr.ReadBinary(len(s3))\n\t\tassert.DeepEqual(t, s3, string(p))\n\n\t\t// Malloc\n\t\tbuf, _ := conn.Malloc(10)\n\t\tassert.DeepEqual(t, 10, len(buf))\n\t\t// Writer\n\t\tassert.DeepEqual(t, conn.zw, conn.Writer())\n\n\t\t_, err = DialerFun(\"\")\n\t\tassert.DeepEqual(t, nil, err)\n\t})\n\n\tt.Run(\"TestNotImplement\", func(t *testing.T) {\n\t\tconn := NewConn(\"\")\n\t\tt1 := time.Now().Add(time.Millisecond)\n\t\tdu1 := time.Second\n\t\tassert.DeepEqual(t, nil, conn.Release())\n\t\tassert.DeepEqual(t, nil, conn.Close())\n\t\tassert.DeepEqual(t, nil, conn.LocalAddr())\n\t\tassert.DeepEqual(t, nil, conn.RemoteAddr())\n\t\tassert.DeepEqual(t, nil, conn.SetIdleTimeout(du1))\n\t\tassert.Panic(t, func() {\n\t\t\tconn.SetWriteDeadline(t1)\n\t\t})\n\t\tassert.Panic(t, func() {\n\t\t\tconn.IsActive()\n\t\t})\n\t\tassert.Panic(t, func() {\n\t\t\tconn.SetOnRequest(func(ctx context.Context, connection netpoll.Connection) error {\n\t\t\t\treturn nil\n\t\t\t})\n\t\t})\n\t\tassert.Panic(t, func() {\n\t\t\tconn.AddCloseCallback(func(connection netpoll.Connection) error {\n\t\t\t\treturn nil\n\t\t\t})\n\t\t})\n\t})\n}\n\nfunc TestSlowConn(t *testing.T) {\n\tt.Run(\"TestSlowReadConn\", func(t *testing.T) {\n\t\ts1 := \"abcdefg\"\n\t\tconn := NewSlowReadConn(s1)\n\t\tassert.Nil(t, conn.SetWriteTimeout(1))\n\t\tassert.Nil(t, conn.SetReadTimeout(1))\n\t\tassert.DeepEqual(t, time.Duration(1), conn.GetReadTimeout())\n\n\t\tb, err := conn.Peek(4)\n\t\tassert.DeepEqual(t, nil, err)\n\t\tassert.DeepEqual(t, s1[:4], string(b))\n\t\tconn.Skip(len(s1))\n\t\t_, err = conn.Peek(1)\n\t\tassert.DeepEqual(t, ErrReadTimeout, err)\n\t\t_, err = SlowReadDialer(\"\")\n\t\tassert.DeepEqual(t, nil, err)\n\t})\n\n\tt.Run(\"TestSlowWriteConn\", func(t *testing.T) {\n\t\tconn, err := SlowWriteDialer(\"\")\n\t\tassert.DeepEqual(t, nil, err)\n\t\tconn.SetWriteTimeout(time.Millisecond * 100)\n\t\terr = conn.Flush()\n\t\tassert.DeepEqual(t, ErrWriteTimeout, err)\n\t})\n}\n\nfunc TestStreamConn(t *testing.T) {\n\tt.Run(\"TestStreamConn\", func(t *testing.T) {\n\t\tconn := NewStreamConn()\n\t\t_, err := conn.Peek(10)\n\t\tassert.DeepEqual(t, nil, err)\n\t\tconn.Skip(conn.Len())\n\t\tassert.DeepEqual(t, 0, conn.Len())\n\t\t_, err = conn.Peek(10)\n\t\tassert.DeepEqual(t, \"not enough data\", err.Error())\n\t\t_, err = conn.Peek(1)\n\t\tassert.DeepEqual(t, nil, err)\n\t\tassert.DeepEqual(t, cap(conn.Data), conn.Len())\n\t\terr = conn.Skip(conn.Len() + 1)\n\t\tassert.DeepEqual(t, \"not enough data\", err.Error())\n\t\terr = conn.Release()\n\t\tassert.DeepEqual(t, nil, err)\n\t\tassert.DeepEqual(t, true, conn.HasReleased)\n\t})\n\n\tt.Run(\"TestNotImplement\", func(t *testing.T) {\n\t\tconn := NewStreamConn()\n\t\tassert.Panic(t, func() {\n\t\t\tconn.ReadByte()\n\t\t})\n\t\tassert.Panic(t, func() {\n\t\t\tconn.ReadBinary(10)\n\t\t})\n\t})\n}\n\nfunc TestBrokenConn_Flush(t *testing.T) {\n\tconn := NewBrokenConn(\"\")\n\tn, err := conn.Writer().WriteBinary([]byte(\"Foo\"))\n\tassert.DeepEqual(t, 3, n)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, errs.ErrConnectionClosed, conn.Flush())\n}\n\nfunc TestBrokenConn_Peek(t *testing.T) {\n\tconn := NewBrokenConn(\"Foo\")\n\tbuf, err := conn.Peek(3)\n\tassert.Nil(t, buf)\n\tassert.DeepEqual(t, io.ErrUnexpectedEOF, err)\n}\n\nfunc TestOneTimeConn_Flush(t *testing.T) {\n\tconn := NewOneTimeConn(\"\")\n\tn, err := conn.Writer().WriteBinary([]byte(\"Foo\"))\n\tassert.DeepEqual(t, 3, n)\n\tassert.Nil(t, err)\n\tassert.Nil(t, conn.Flush())\n\tn, err = conn.Writer().WriteBinary([]byte(\"Bar\"))\n\tassert.DeepEqual(t, 3, n)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, errs.ErrConnectionClosed, conn.Flush())\n}\n\nfunc TestOneTimeConn_Skip(t *testing.T) {\n\tconn := NewOneTimeConn(\"FooBar\")\n\tbuf, err := conn.Peek(3)\n\tassert.DeepEqual(t, \"Foo\", string(buf))\n\tassert.Nil(t, err)\n\tassert.Nil(t, conn.Skip(3))\n\tassert.DeepEqual(t, 3, conn.contentLength)\n\n\tbuf, err = conn.Peek(3)\n\tassert.DeepEqual(t, \"Bar\", string(buf))\n\tassert.Nil(t, err)\n\tassert.Nil(t, conn.Skip(3))\n\tassert.DeepEqual(t, 0, conn.contentLength)\n\n\tbuf, err = conn.Peek(3)\n\tassert.DeepEqual(t, 0, len(buf))\n\tassert.DeepEqual(t, io.EOF, err)\n\tassert.DeepEqual(t, io.EOF, conn.Skip(3))\n\tassert.DeepEqual(t, 0, conn.contentLength)\n}\n"
  },
  {
    "path": "pkg/common/test/mock/reader.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage mock\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n)\n\n// ZeroCopyReader is used to create ZeroCopyReader for testing.\n//\n// NOTE: In principle, ut should use the zcReader created by netpoll.NewReader() for mock testing,\n// but because zcReader does not implement the io.Reader interface, the test requirements of\n// io.Reader involved are replaced with MockZeroCopyReader\ntype ZeroCopyReader struct {\n\t*bufio.Reader\n}\n\nfunc (m ZeroCopyReader) Peek(n int) ([]byte, error) {\n\tb, err := m.Reader.Peek(n)\n\t// if n is bigger than the buffer in m.Reader,\n\t// it will only return bufio.ErrBufferFull even if the underline reader return io.EOF.\n\t// so we make another Peek to get the real error.\n\t// for more info: https://github.com/golang/go/issues/50569\n\tif err == bufio.ErrBufferFull && len(b) == 0 {\n\t\treturn m.Reader.Peek(1)\n\t}\n\treturn b, err\n}\n\nfunc (m ZeroCopyReader) Skip(n int) (err error) {\n\t_, err = m.Reader.Discard(n)\n\treturn\n}\n\nfunc (m ZeroCopyReader) Release() (err error) {\n\treturn nil\n}\n\nfunc (m ZeroCopyReader) Len() (length int) {\n\treturn m.Reader.Buffered()\n}\n\nfunc (m ZeroCopyReader) ReadBinary(n int) (p []byte, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc NewZeroCopyReader(r string) ZeroCopyReader {\n\tbr := bufio.NewReaderSize(bytes.NewBufferString(r), len(r))\n\treturn ZeroCopyReader{br}\n}\n\nfunc NewLimitReader(r *bytes.Buffer) io.LimitedReader {\n\treturn io.LimitedReader{\n\t\tR: r,\n\t\tN: int64(r.Len()),\n\t}\n}\n\ntype EOFReader struct{}\n\nfunc (e *EOFReader) Peek(n int) ([]byte, error) {\n\treturn []byte{}, io.EOF\n}\n\nfunc (e *EOFReader) Skip(n int) error {\n\treturn nil\n}\n\nfunc (e *EOFReader) Release() error {\n\treturn nil\n}\n\nfunc (e *EOFReader) Len() int {\n\treturn 0\n}\n\nfunc (e *EOFReader) ReadByte() (byte, error) {\n\treturn ' ', io.EOF\n}\n\nfunc (e *EOFReader) ReadBinary(n int) (p []byte, err error) {\n\treturn p, io.EOF\n}\n\nfunc (e *EOFReader) Read(p []byte) (n int, err error) {\n\treturn 0, io.EOF\n}\n"
  },
  {
    "path": "pkg/common/test/mock/reader_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage mock\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"testing\"\n\t\"testing/iotest\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestZeroCopyReader(t *testing.T) {\n\t// raw\n\tr := \"abcdef4343\"\n\tzr := NewZeroCopyReader(r)\n\trs := readBytes(zr.Reader)\n\tassert.DeepEqual(t, rs, r)\n\n\t// peek\n\tzr = NewZeroCopyReader(r)\n\ts, err := zr.Peek(1)\n\tassert.DeepEqual(t, nil, err)\n\tassert.DeepEqual(t, \"a\", string(s))\n\n\ts, err = zr.Peek(4)\n\tassert.DeepEqual(t, nil, err)\n\tassert.DeepEqual(t, \"abcd\", string(s))\n\n\t// https://github.com/golang/go/issues/50569\n\tezr := NewZeroCopyReader(\"\")\n\ts, err = ezr.Peek(32)\n\tassert.DeepEqual(t, io.EOF, err)\n\tassert.DeepEqual(t, \"\", string(s))\n\n\t// skip\n\terr = zr.Skip(1)\n\tassert.DeepEqual(t, nil, err)\n\n\ts, err = zr.Peek(4)\n\tassert.DeepEqual(t, nil, err)\n\tassert.DeepEqual(t, \"bcde\", string(s))\n\n\t// len\n\tassert.DeepEqual(t, len(r)-1, zr.Len())\n\tassert.DeepEqual(t, nil, ezr.Release())\n\tassert.Panic(t, func() { // not implement\n\t\tzr.ReadBinary(10)\n\t})\n}\n\nfunc TestEOFReader(t *testing.T) {\n\tr := &EOFReader{}\n\ts, err := r.Peek(1)\n\tassert.DeepEqual(t, io.EOF, err)\n\tassert.DeepEqual(t, \"\", string(s))\n\tassert.DeepEqual(t, nil, r.Skip(1))\n\tassert.DeepEqual(t, 0, r.Len())\n\n\t_, err = r.ReadByte()\n\tassert.DeepEqual(t, io.EOF, err)\n\t_, err = r.ReadBinary(10)\n\tassert.DeepEqual(t, io.EOF, err)\n\t_, err = r.Read(s)\n\tassert.DeepEqual(t, io.EOF, err)\n\tassert.DeepEqual(t, nil, r.Release())\n}\n\nfunc readBytes(buf *bufio.Reader) string {\n\tvar b [1000]byte\n\tnb := 0\n\tfor {\n\t\tc, err := buf.ReadByte()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err == nil {\n\t\t\tb[nb] = c\n\t\t\tnb++\n\t\t} else if err != iotest.ErrTimeout {\n\t\t\tpanic(\"Data: \" + err.Error())\n\t\t}\n\t}\n\treturn string(b[0:nb])\n}\n"
  },
  {
    "path": "pkg/common/test/mock/writer.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage mock\n\nimport \"bytes\"\n\ntype ExtWriter struct {\n\ttmp     []byte\n\tBuf     *bytes.Buffer\n\tIsFinal *bool\n}\n\nfunc (m *ExtWriter) Write(p []byte) (n int, err error) {\n\tm.tmp = p\n\treturn len(p), nil\n}\n\nfunc (m *ExtWriter) Flush() error {\n\t_, err := m.Buf.Write(m.tmp)\n\treturn err\n}\n\nfunc (m *ExtWriter) Finalize() error {\n\tif !*m.IsFinal {\n\t\t*m.IsFinal = true\n\t}\n\treturn nil\n}\n\nfunc (m *ExtWriter) SetBody(body []byte) {\n\tm.Buf.Reset()\n\tm.tmp = body\n}\n"
  },
  {
    "path": "pkg/common/test/mock/writer_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage mock\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestExtWriter(t *testing.T) {\n\tb1 := []byte(\"abcdef4343\")\n\tbuf := new(bytes.Buffer)\n\tisFinal := false\n\tw := &ExtWriter{\n\t\tBuf:     buf,\n\t\tIsFinal: &isFinal,\n\t}\n\n\t// write\n\tn, err := w.Write(b1)\n\tassert.DeepEqual(t, nil, err)\n\tassert.DeepEqual(t, len(b1), n)\n\n\t// flush\n\terr = w.Flush()\n\tassert.DeepEqual(t, nil, err)\n\tassert.DeepEqual(t, b1, w.Buf.Bytes())\n\n\t// setbody\n\tb2 := []byte(\"abc\")\n\tw.SetBody(b2)\n\terr = w.Flush()\n\tassert.DeepEqual(t, nil, err)\n\tassert.DeepEqual(t, b2, w.Buf.Bytes())\n\n\tw.Finalize()\n\tassert.DeepEqual(t, true, *(w.IsFinal))\n}\n"
  },
  {
    "path": "pkg/common/testdata/conf/p_s_m.yaml",
    "content": "Develop:\n  ServicePort: \"6789\"\n  DebugPort: \"6790\"\n  EnablePprof: true\n  LogLevel: \"debug\"\n  LogInterval: \"hour\"\n  EnableMetrics: false\n  ConsoleLog: true\n  AgentLog: true\n  FileLog: true\n\nProduct:\n  ServicePort: \"6789\"\n  DebugPort: \"6790\"\n  EnablePprof: true\n  LogLevel: \"info\"\n  LogInterval: \"hour\"\n  EnableMetrics: true\n  ConsoleLog: false\n  AgentLog: true\n  FileLog: true\n"
  },
  {
    "path": "pkg/common/testdata/proto/test.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.28.0\n// \tprotoc        v3.19.3\n// source: test.proto\n\npackage proto\n\nimport (\n\treflect \"reflect\"\n\tsync \"sync\"\n\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype FOO int32\n\nconst (\n\tFOO_X FOO = 17\n)\n\n// Enum value maps for FOO.\nvar (\n\tFOO_name = map[int32]string{\n\t\t17: \"X\",\n\t}\n\tFOO_value = map[string]int32{\n\t\t\"X\": 17,\n\t}\n)\n\nfunc (x FOO) Enum() *FOO {\n\tp := new(FOO)\n\t*p = x\n\treturn p\n}\n\nfunc (x FOO) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (FOO) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_test_proto_enumTypes[0].Descriptor()\n}\n\nfunc (FOO) Type() protoreflect.EnumType {\n\treturn &file_test_proto_enumTypes[0]\n}\n\nfunc (x FOO) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Do not use.\nfunc (x *FOO) UnmarshalJSON(b []byte) error {\n\tnum, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*x = FOO(num)\n\treturn nil\n}\n\n// Deprecated: Use FOO.Descriptor instead.\nfunc (FOO) EnumDescriptor() ([]byte, []int) {\n\treturn file_test_proto_rawDescGZIP(), []int{0}\n}\n\ntype Test struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLabel         *string             `protobuf:\"bytes,1,req,name=label\" json:\"label,omitempty\"`\n\tType          *int32              `protobuf:\"varint,2,opt,name=type,def=77\" json:\"type,omitempty\"`\n\tReps          []int64             `protobuf:\"varint,3,rep,name=reps\" json:\"reps,omitempty\"`\n\tOptionalgroup *Test_OptionalGroup `protobuf:\"group,4,opt,name=OptionalGroup,json=optionalgroup\" json:\"optionalgroup,omitempty\"`\n}\n\n// Default values for Test fields.\nconst (\n\tDefault_Test_Type = int32(77)\n)\n\nfunc (x *Test) Reset() {\n\t*x = Test{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_test_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Test) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Test) ProtoMessage() {}\n\nfunc (x *Test) ProtoReflect() protoreflect.Message {\n\tmi := &file_test_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Test.ProtoReflect.Descriptor instead.\nfunc (*Test) Descriptor() ([]byte, []int) {\n\treturn file_test_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Test) GetLabel() string {\n\tif x != nil && x.Label != nil {\n\t\treturn *x.Label\n\t}\n\treturn \"\"\n}\n\nfunc (x *Test) GetType() int32 {\n\tif x != nil && x.Type != nil {\n\t\treturn *x.Type\n\t}\n\treturn Default_Test_Type\n}\n\nfunc (x *Test) GetReps() []int64 {\n\tif x != nil {\n\t\treturn x.Reps\n\t}\n\treturn nil\n}\n\nfunc (x *Test) GetOptionalgroup() *Test_OptionalGroup {\n\tif x != nil {\n\t\treturn x.Optionalgroup\n\t}\n\treturn nil\n}\n\ntype TestStruct struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tBody []byte `protobuf:\"bytes,1,opt,name=body\" json:\"body,omitempty\"`\n}\n\nfunc (x *TestStruct) Reset() {\n\t*x = TestStruct{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_test_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TestStruct) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TestStruct) ProtoMessage() {}\n\nfunc (x *TestStruct) ProtoReflect() protoreflect.Message {\n\tmi := &file_test_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TestStruct.ProtoReflect.Descriptor instead.\nfunc (*TestStruct) Descriptor() ([]byte, []int) {\n\treturn file_test_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *TestStruct) GetBody() []byte {\n\tif x != nil {\n\t\treturn x.Body\n\t}\n\treturn nil\n}\n\ntype Test_OptionalGroup struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRequiredField *string `protobuf:\"bytes,5,req,name=RequiredField\" json:\"RequiredField,omitempty\"`\n}\n\nfunc (x *Test_OptionalGroup) Reset() {\n\t*x = Test_OptionalGroup{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_test_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Test_OptionalGroup) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Test_OptionalGroup) ProtoMessage() {}\n\nfunc (x *Test_OptionalGroup) ProtoReflect() protoreflect.Message {\n\tmi := &file_test_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Test_OptionalGroup.ProtoReflect.Descriptor instead.\nfunc (*Test_OptionalGroup) Descriptor() ([]byte, []int) {\n\treturn file_test_proto_rawDescGZIP(), []int{0, 0}\n}\n\nfunc (x *Test_OptionalGroup) GetRequiredField() string {\n\tif x != nil && x.RequiredField != nil {\n\t\treturn *x.RequiredField\n\t}\n\treturn \"\"\n}\n\nvar File_test_proto protoreflect.FileDescriptor\n\nvar file_test_proto_rawDesc = []byte{\n\t0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x22, 0xc7, 0x01, 0x0a, 0x04, 0x54,\n\t0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x02,\n\t0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x04, 0x74, 0x79, 0x70,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x3a, 0x02, 0x37, 0x37, 0x52, 0x04, 0x74, 0x79, 0x70,\n\t0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52,\n\t0x04, 0x72, 0x65, 0x70, 0x73, 0x12, 0x46, 0x0a, 0x0d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61,\n\t0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0a, 0x32, 0x20, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74,\n\t0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0d,\n\t0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x35, 0x0a,\n\t0x0d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x24,\n\t0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18,\n\t0x05, 0x20, 0x02, 0x28, 0x09, 0x52, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46,\n\t0x69, 0x65, 0x6c, 0x64, 0x22, 0x20, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75,\n\t0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,\n\t0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x2a, 0x0c, 0x0a, 0x03, 0x46, 0x4f, 0x4f, 0x12, 0x05, 0x0a,\n\t0x01, 0x58, 0x10, 0x11, 0x42, 0x10, 0x5a, 0x0e, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65,\n\t0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,\n}\n\nvar (\n\tfile_test_proto_rawDescOnce sync.Once\n\tfile_test_proto_rawDescData = file_test_proto_rawDesc\n)\n\nfunc file_test_proto_rawDescGZIP() []byte {\n\tfile_test_proto_rawDescOnce.Do(func() {\n\t\tfile_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)\n\t})\n\treturn file_test_proto_rawDescData\n}\n\nvar (\n\tfile_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\n\tfile_test_proto_msgTypes  = make([]protoimpl.MessageInfo, 3)\n\tfile_test_proto_goTypes   = []interface{}{\n\t\t(FOO)(0),                   // 0: proto.FOO\n\t\t(*Test)(nil),               // 1: proto.Test\n\t\t(*TestStruct)(nil),         // 2: proto.TestStruct\n\t\t(*Test_OptionalGroup)(nil), // 3: proto.Test.OptionalGroup\n\t}\n)\n\nvar file_test_proto_depIdxs = []int32{\n\t3, // 0: proto.Test.optionalgroup:type_name -> proto.Test.OptionalGroup\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_test_proto_init() }\nfunc file_test_proto_init() {\n\tif File_test_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Test); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TestStruct); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_test_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Test_OptionalGroup); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_test_proto_rawDesc,\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_test_proto_goTypes,\n\t\tDependencyIndexes: file_test_proto_depIdxs,\n\t\tEnumInfos:         file_test_proto_enumTypes,\n\t\tMessageInfos:      file_test_proto_msgTypes,\n\t}.Build()\n\tFile_test_proto = out.File\n\tfile_test_proto_rawDesc = nil\n\tfile_test_proto_goTypes = nil\n\tfile_test_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/common/testdata/proto/test.proto",
    "content": "syntax = \"proto2\";\npackage protoexample;\noption go_package = \"./proto\";\n\nenum FOO {X=17;};\n\nmessage Test {\n   required string label = 1;\n   optional int32 type = 2[default=77];\n   repeated int64 reps = 3;\n   optional group OptionalGroup = 4{\n      required string RequiredField = 5;\n   }\n}\n\nmessage TestStruct {\n   optional bytes body = 1;\n}\n"
  },
  {
    "path": "pkg/common/testdata/template/htmltemplate.html",
    "content": "<h1>Date: {[{.now | formatAsDate}]}</h1>"
  },
  {
    "path": "pkg/common/testdata/template/index.tmpl",
    "content": "<html><h1>{[{ .title }]}</h1></html>"
  },
  {
    "path": "pkg/common/testdata/test.txt",
    "content": "hello world!"
  },
  {
    "path": "pkg/common/timer/doc.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The files in timer package are forked from fasthttp[github.com/valyala/fasthttp],\n// and we keep the original Copyright[Copyright 2015 fasthttp authors] and License of fasthttp for those files.\n// We also need to modify as we need, the modifications are Copyright of 2022 CloudWeGo Authors.\n// Thanks for fasthttp authors! Below is the source code information:\n// \t\tRepo: github.com/valyala/fasthttp\n//\t\tForked Version: v1.36.0\n\npackage timer\n"
  },
  {
    "path": "pkg/common/timer/timer.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage timer\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\nfunc initTimer(t *time.Timer, timeout time.Duration) *time.Timer {\n\tif t == nil {\n\t\treturn time.NewTimer(timeout)\n\t}\n\tif t.Reset(timeout) {\n\t\tpanic(\"BUG: active timer trapped into initTimer()\")\n\t}\n\treturn t\n}\n\nfunc stopTimer(t *time.Timer) {\n\tif !t.Stop() {\n\t\t// Collect possibly added time from the channel\n\t\t// if timer has been stopped and nobody collected its value.\n\t\tselect {\n\t\tcase <-t.C:\n\t\tdefault:\n\t\t}\n\t}\n}\n\n// AcquireTimer returns a time.Timer from the pool and updates it to\n// send the current time on its channel after at least timeout.\n//\n// The returned Timer may be returned to the pool with ReleaseTimer\n// when no longer needed. This allows reducing GC load.\nfunc AcquireTimer(timeout time.Duration) *time.Timer {\n\tv := timerPool.Get()\n\tif v == nil {\n\t\treturn time.NewTimer(timeout)\n\t}\n\tt := v.(*time.Timer)\n\tinitTimer(t, timeout)\n\treturn t\n}\n\n// ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool\n// and prevents the Timer from firing.\n//\n// Do not access the released time.Timer or read from its channel otherwise\n// data races may occur.\nfunc ReleaseTimer(t *time.Timer) {\n\tstopTimer(t)\n\ttimerPool.Put(t)\n}\n\nvar timerPool sync.Pool\n"
  },
  {
    "path": "pkg/common/timer/timer_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage timer\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\n// test initTimer function\nfunc TestTimerInitTimer(t *testing.T) {\n\t// test nil Timer\n\tvar nilTimer *time.Timer\n\tresNilTime := initTimer(nilTimer, 2*time.Second)\n\tif resNilTime == nil {\n\t\tt.Fatalf(\"Unexpected a nil. Expecting a Timer.\")\n\t}\n\n\t// test the panic\n\tpanicTimer := time.NewTimer(1 * time.Second)\n\tresPanicTimer := wrapInitTimer(panicTimer, 2*time.Second)\n\tif resPanicTimer != -1 {\n\t\tt.Fatalf(\"Expecting a panic for Timer, but nil\")\n\t}\n\t// sleep enough time to test next timer\n\ttime.Sleep(3 * time.Second)\n}\n\nfunc wrapInitTimer(t *time.Timer, timeout time.Duration) (ret int) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tret = -1\n\t\t}\n\t}()\n\tres := initTimer(t, timeout)\n\tif res != nil {\n\t\tret = 1\n\t}\n\treturn ret\n}\n\nfunc TestTimerStopTimer(t *testing.T) {\n\tnormalTimer := time.NewTimer(3 * time.Second)\n\tstopTimer(normalTimer)\n\tif normalTimer.Stop() {\n\t\tt.Fatalf(\"Expecting timer stopped, but it doesn't\")\n\t}\n}\n\nfunc TestTimerAcquireTimer(t *testing.T) {\n\tnormalTimer := AcquireTimer(2 * time.Second)\n\tif normalTimer == nil {\n\t\tt.Fatalf(\"Unexpected nil, expecting a timer\")\n\t}\n\tReleaseTimer(normalTimer)\n}\n\nfunc TestTimerReleaseTimer(t *testing.T) {\n\tnormalTimer := AcquireTimer(2 * time.Second)\n\tReleaseTimer(normalTimer)\n\tif normalTimer.Stop() {\n\t\tt.Fatalf(\"Expecting the timer is released.\")\n\t}\n}\n"
  },
  {
    "path": "pkg/common/tracer/stats/event.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage stats\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n)\n\n// EventIndex indicates a unique event.\ntype EventIndex int\n\n// Level sets the record level.\ntype Level int\n\n// Event levels.\nconst (\n\tLevelDisabled Level = iota\n\tLevelBase\n\tLevelDetailed\n)\n\n// Event is used to indicate a specific event.\ntype Event interface {\n\tIndex() EventIndex\n\tLevel() Level\n}\n\ntype event struct {\n\tidx   EventIndex\n\tlevel Level\n}\n\n// Index implements the Event interface.\nfunc (e event) Index() EventIndex {\n\treturn e.idx\n}\n\n// Level implements the Event interface.\nfunc (e event) Level() Level {\n\treturn e.level\n}\n\nconst (\n\t_ EventIndex = iota\n\tserverHandleStart\n\tserverHandleFinish\n\thttpStart\n\thttpFinish\n\treadHeaderStart\n\treadHeaderFinish\n\treadBodyStart\n\treadBodyFinish\n\twriteStart\n\twriteFinish\n\tpredefinedEventNum\n)\n\n// Predefined events.\nvar (\n\tHTTPStart  = newEvent(httpStart, LevelBase)\n\tHTTPFinish = newEvent(httpFinish, LevelBase)\n\n\tServerHandleStart  = newEvent(serverHandleStart, LevelDetailed)\n\tServerHandleFinish = newEvent(serverHandleFinish, LevelDetailed)\n\tReadHeaderStart    = newEvent(readHeaderStart, LevelDetailed)\n\tReadHeaderFinish   = newEvent(readHeaderFinish, LevelDetailed)\n\tReadBodyStart      = newEvent(readBodyStart, LevelDetailed)\n\tReadBodyFinish     = newEvent(readBodyFinish, LevelDetailed)\n\tWriteStart         = newEvent(writeStart, LevelDetailed)\n\tWriteFinish        = newEvent(writeFinish, LevelDetailed)\n)\n\n// errors\nvar (\n\tErrNotAllowed = errors.NewPublic(\"event definition is not allowed after initialization\")\n\tErrDuplicated = errors.NewPublic(\"event name is already defined\")\n)\n\nvar (\n\tlock        sync.RWMutex\n\tinited      int32\n\tuserDefined = make(map[string]Event)\n\tmaxEventNum = int(predefinedEventNum)\n)\n\n// FinishInitialization freezes all events defined and prevents further definitions to be added.\nfunc FinishInitialization() {\n\tatomic.StoreInt32(&inited, 1)\n}\n\n// DefineNewEvent allows user to add event definitions during program initialization.\nfunc DefineNewEvent(name string, level Level) (Event, error) {\n\tif atomic.LoadInt32(&inited) == 1 {\n\t\treturn nil, ErrNotAllowed\n\t}\n\tlock.Lock()\n\tdefer lock.Unlock()\n\tevt, exist := userDefined[name]\n\tif exist {\n\t\treturn evt, ErrDuplicated\n\t}\n\tuserDefined[name] = newEvent(EventIndex(maxEventNum), level)\n\tmaxEventNum++\n\treturn userDefined[name], nil\n}\n\n// MaxEventNum returns the number of event defined.\nfunc MaxEventNum() int {\n\tlock.RLock()\n\tdefer lock.RUnlock()\n\treturn maxEventNum\n}\n\nfunc newEvent(idx EventIndex, level Level) Event {\n\treturn event{\n\t\tidx:   idx,\n\t\tlevel: level,\n\t}\n}\n"
  },
  {
    "path": "pkg/common/tracer/stats/event_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage stats\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestDefineNewEvent(t *testing.T) {\n\tnum0 := MaxEventNum()\n\n\tevent1, err1 := DefineNewEvent(\"myevent\", LevelDetailed)\n\tnum1 := MaxEventNum()\n\n\tassert.Assert(t, err1 == nil)\n\tassert.Assert(t, event1 != nil)\n\tassert.Assert(t, num1 == num0+1)\n\tassert.Assert(t, event1.Level() == LevelDetailed)\n\n\tevent2, err2 := DefineNewEvent(\"myevent\", LevelBase)\n\tnum2 := MaxEventNum()\n\tassert.Assert(t, err2 == ErrDuplicated)\n\tassert.Assert(t, event2 == event1)\n\tassert.Assert(t, num2 == num1)\n\tassert.Assert(t, event2.Level() == LevelDetailed)\n\n\tFinishInitialization()\n\n\tevent3, err3 := DefineNewEvent(\"another\", LevelDetailed)\n\tnum3 := MaxEventNum()\n\tassert.Assert(t, err3 == ErrNotAllowed)\n\tassert.Assert(t, event3 == nil)\n\tassert.Assert(t, num3 == num1)\n}\n"
  },
  {
    "path": "pkg/common/tracer/stats/status.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage stats\n\n// Status is used to indicate the status of an event.\ntype Status int8\n\n// Predefined status.\nconst (\n\tStatusInfo  Status = 1\n\tStatusWarn  Status = 2\n\tStatusError Status = 3\n)\n"
  },
  {
    "path": "pkg/common/tracer/traceinfo/httpstats.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage traceinfo\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/stats\"\n)\n\nvar _ HTTPStats = (*httpStats)(nil)\n\nvar (\n\teventPool   sync.Pool\n\tonce        sync.Once\n\tmaxEventNum int\n)\n\ntype event struct {\n\tevent  stats.Event\n\tstatus stats.Status\n\tinfo   string\n\ttime   time.Time\n}\n\n// Event implements the Event interface.\nfunc (e *event) Event() stats.Event {\n\treturn e.event\n}\n\n// Status implements the Event interface.\nfunc (e *event) Status() stats.Status {\n\treturn e.status\n}\n\n// Info implements the Event interface.\nfunc (e *event) Info() string {\n\treturn e.info\n}\n\n// Time implements the Event interface.\nfunc (e *event) Time() time.Time {\n\treturn e.time\n}\n\n// IsNil implements the Event interface.\nfunc (e *event) IsNil() bool {\n\treturn e == nil\n}\n\nfunc newEvent() interface{} {\n\treturn &event{}\n}\n\nfunc (e *event) zero() {\n\te.event = nil\n\te.status = 0\n\te.info = \"\"\n\te.time = time.Time{}\n}\n\n// Recycle reuses the event.\nfunc (e *event) Recycle() {\n\te.zero()\n\teventPool.Put(e)\n}\n\ntype httpStats struct {\n\tsync.RWMutex\n\tlevel stats.Level\n\n\teventMap []Event\n\n\tsendSize int\n\trecvSize int\n\n\terr      error\n\tpanicErr interface{}\n}\n\nfunc init() {\n\teventPool.New = newEvent\n}\n\n// Record implements the HTTPStats interface.\nfunc (h *httpStats) Record(e stats.Event, status stats.Status, info string) {\n\tif e.Level() > h.level {\n\t\treturn\n\t}\n\teve := eventPool.Get().(*event)\n\teve.event = e\n\teve.status = status\n\teve.info = info\n\teve.time = time.Now()\n\n\tidx := e.Index()\n\th.Lock()\n\th.eventMap[idx] = eve\n\th.Unlock()\n}\n\n// SendSize implements the HTTPStats interface.\nfunc (h *httpStats) SendSize() int {\n\treturn h.sendSize\n}\n\n// RecvSize implements the HTTPStats interface.\nfunc (h *httpStats) RecvSize() int {\n\treturn h.recvSize\n}\n\n// Error implements the HTTPStats interface.\nfunc (h *httpStats) Error() error {\n\treturn h.err\n}\n\n// Panicked implements the HTTPStats interface.\nfunc (h *httpStats) Panicked() (bool, interface{}) {\n\treturn h.panicErr != nil, h.panicErr\n}\n\n// GetEvent implements the HTTPStats interface.\nfunc (h *httpStats) GetEvent(e stats.Event) Event {\n\tidx := e.Index()\n\th.RLock()\n\tevt := h.eventMap[idx]\n\th.RUnlock()\n\tif evt == nil || evt.IsNil() {\n\t\treturn nil\n\t}\n\treturn evt\n}\n\n// Level implements the HTTPStats interface.\nfunc (h *httpStats) Level() stats.Level {\n\treturn h.level\n}\n\n// SetSendSize sets send size.\nfunc (h *httpStats) SetSendSize(size int) {\n\th.sendSize = size\n}\n\n// SetRecvSize sets recv size.\nfunc (h *httpStats) SetRecvSize(size int) {\n\th.recvSize = size\n}\n\n// SetError sets error.\nfunc (h *httpStats) SetError(err error) {\n\th.err = err\n}\n\n// SetPanicked sets if panicked.\nfunc (h *httpStats) SetPanicked(x interface{}) {\n\th.panicErr = x\n}\n\n// SetLevel sets the level.\nfunc (h *httpStats) SetLevel(level stats.Level) {\n\th.level = level\n}\n\n// Reset resets the stats.\nfunc (h *httpStats) Reset() {\n\th.err = nil\n\th.panicErr = nil\n\th.recvSize = 0\n\th.sendSize = 0\n\tfor i := range h.eventMap {\n\t\tif h.eventMap[i] != nil {\n\t\t\th.eventMap[i].(*event).Recycle()\n\t\t\th.eventMap[i] = nil\n\t\t}\n\t}\n}\n\n// ImmutableView restricts the httpStats into a read-only traceinfo.HTTPStats.\nfunc (h *httpStats) ImmutableView() HTTPStats {\n\treturn h\n}\n\n// NewHTTPStats creates a new HTTPStats.\nfunc NewHTTPStats() HTTPStats {\n\tonce.Do(func() {\n\t\tstats.FinishInitialization()\n\t\tmaxEventNum = stats.MaxEventNum()\n\t})\n\treturn &httpStats{\n\t\teventMap: make([]Event, maxEventNum),\n\t}\n}\n"
  },
  {
    "path": "pkg/common/tracer/traceinfo/interface.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage traceinfo\n\nimport (\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/stats\"\n)\n\n// HTTPStats is used to collect statistics about the HTTP.\ntype HTTPStats interface {\n\tRecord(event stats.Event, status stats.Status, info string)\n\tGetEvent(event stats.Event) Event\n\tSendSize() int\n\tSetSendSize(size int)\n\tRecvSize() int\n\tSetRecvSize(size int)\n\tError() error\n\tSetError(err error)\n\tPanicked() (bool, interface{})\n\tSetPanicked(x interface{})\n\tLevel() stats.Level\n\tSetLevel(level stats.Level)\n\tReset()\n}\n\n// Event is the abstraction of an event happened at a specific time.\ntype Event interface {\n\tEvent() stats.Event\n\tStatus() stats.Status\n\tInfo() string\n\tTime() time.Time\n\tIsNil() bool\n}\n\n// TraceInfo contains the trace message in Hertz.\ntype TraceInfo interface {\n\tStats() HTTPStats\n\tReset()\n}\n"
  },
  {
    "path": "pkg/common/tracer/traceinfo/traceinfo.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage traceinfo\n\ntype traceInfo struct {\n\tstats HTTPStats\n}\n\n// Stats implements the HTTPInfo interface.\nfunc (r *traceInfo) Stats() HTTPStats { return r.stats }\n\n// Reset reuses the traceInfo.\nfunc (r *traceInfo) Reset() {\n\tr.stats.Reset()\n}\n\n// NewTraceInfo creates a new traceInfoImpl using the given information.\nfunc NewTraceInfo() TraceInfo {\n\treturn &traceInfo{stats: NewHTTPStats()}\n}\n"
  },
  {
    "path": "pkg/common/tracer/tracer.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage tracer\n\nimport (\n\t\"context\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n)\n\n// Tracer is executed at the start and finish of an HTTP.\ntype Tracer interface {\n\tStart(ctx context.Context, c *app.RequestContext) context.Context\n\tFinish(ctx context.Context, c *app.RequestContext)\n}\n\ntype Controller interface {\n\tAppend(col Tracer)\n\tDoStart(ctx context.Context, c *app.RequestContext) context.Context\n\tDoFinish(ctx context.Context, c *app.RequestContext, err error)\n\tHasTracer() bool\n}\n"
  },
  {
    "path": "pkg/common/ut/context.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ut\n\nimport (\n\t\"io\"\n\t\"io/ioutil\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route\"\n)\n\n// CreateUtRequestContext returns an app.RequestContext for testing purposes\nfunc CreateUtRequestContext(method, url string, body *Body, headers ...Header) *app.RequestContext {\n\tengine := route.NewEngine(config.NewOptions([]config.Option{}))\n\treturn createUtRequestContext(engine, method, url, body, headers...)\n}\n\nfunc createUtRequestContext(engine *route.Engine, method, url string, body *Body, headers ...Header) *app.RequestContext {\n\tctx := engine.NewContext()\n\n\tvar r *protocol.Request\n\tif body != nil && body.Body != nil {\n\t\tr = protocol.NewRequest(method, url, body.Body)\n\t\tr.CopyTo(&ctx.Request)\n\t\tif engine.IsStreamRequestBody() || body.Len == -1 {\n\t\t\tctx.Request.SetBodyStream(body.Body, body.Len)\n\t\t} else {\n\t\t\tbuf, err := ioutil.ReadAll(&io.LimitedReader{R: body.Body, N: int64(body.Len)})\n\t\t\tctx.Request.SetBody(buf)\n\t\t\tif err != nil && err != io.EOF {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tr = protocol.NewRequest(method, url, nil)\n\t\tr.CopyTo(&ctx.Request)\n\t}\n\n\tfor _, v := range headers {\n\t\tif ctx.Request.Header.Get(v.Key) != \"\" {\n\t\t\tctx.Request.Header.Add(v.Key, v.Value)\n\t\t} else {\n\t\t\tctx.Request.Header.Set(v.Key, v.Value)\n\t\t}\n\t}\n\n\treturn ctx\n}\n"
  },
  {
    "path": "pkg/common/ut/context_test.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ut\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestCreateUtRequestContext(t *testing.T) {\n\tbody := \"1\"\n\tmethod := \"PUT\"\n\tpath := \"/hey/dy\"\n\theaderKey := \"Connection\"\n\theaderValue := \"close\"\n\tctx := CreateUtRequestContext(method, path, &Body{bytes.NewBufferString(body), len(body)},\n\t\tHeader{headerKey, headerValue})\n\n\tassert.DeepEqual(t, method, string(ctx.Method()))\n\tassert.DeepEqual(t, path, string(ctx.Path()))\n\tbody1, err := ctx.Body()\n\tassert.DeepEqual(t, nil, err)\n\tassert.DeepEqual(t, body, string(body1))\n\tassert.DeepEqual(t, headerValue, string(ctx.GetHeader(headerKey)))\n}\n"
  },
  {
    "path": "pkg/common/ut/request.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package ut provides a convenient way to write unit test for the business logic.\npackage ut\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/cloudwego/hertz/pkg/route\"\n)\n\n// Header is a key-value pair indicating one http header\ntype Header struct {\n\tKey   string\n\tValue string\n}\n\n// Body is for setting Request.Body\ntype Body struct {\n\tBody io.Reader\n\tLen  int\n}\n\n// PerformRequest send a constructed request to given engine without network transporting\n//\n// # Url can be a standard relative URI or a simple absolute path\n//\n// If engine.streamRequestBody is true, it sets body as bodyStream\n// if not, it sets body as bodyBytes\n//\n// ResponseRecorder returned are flushed, which means its StatusCode is always set (default 200)\n//\n// See ./request_test.go for more examples\nfunc PerformRequest(engine *route.Engine, method, url string, body *Body, headers ...Header) *ResponseRecorder {\n\tctx := createUtRequestContext(engine, method, url, body, headers...)\n\tengine.ServeHTTP(context.Background(), ctx)\n\n\tw := NewRecorder()\n\th := w.Header()\n\tctx.Response.Header.CopyTo(h)\n\n\tw.WriteHeader(ctx.Response.StatusCode())\n\tw.Write(ctx.Response.Body())\n\tw.Flush()\n\treturn w\n}\n"
  },
  {
    "path": "pkg/common/ut/request_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ut\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/route\"\n)\n\nfunc newTestEngine() *route.Engine {\n\topt := config.NewOptions([]config.Option{})\n\treturn route.NewEngine(opt)\n}\n\nfunc TestPerformRequest(t *testing.T) {\n\trouter := newTestEngine()\n\trouter.PUT(\"/hey/:user\", func(ctx context.Context, c *app.RequestContext) {\n\t\tuser := c.Param(\"user\")\n\t\tif string(c.Request.Body()) == \"1\" {\n\t\t\tassert.DeepEqual(t, \"close\", c.Request.Header.Get(\"Connection\"))\n\t\t\tc.Response.SetConnectionClose()\n\t\t\tc.JSON(consts.StatusCreated, map[string]string{\"hi\": user})\n\t\t} else if string(c.Request.Body()) == \"\" {\n\t\t\tc.AbortWithMsg(\"unauthorized\", consts.StatusUnauthorized)\n\t\t} else {\n\t\t\tassert.DeepEqual(t, \"PUT /hey/dy HTTP/1.1\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n\", string(c.Request.Header.Header()))\n\t\t\tc.String(consts.StatusAccepted, \"body:%v\", string(c.Request.Body()))\n\t\t}\n\t})\n\trouter.GET(\"/her/header\", func(ctx context.Context, c *app.RequestContext) {\n\t\tassert.DeepEqual(t, \"application/json\", string(c.GetHeader(\"Content-Type\")))\n\t\tassert.DeepEqual(t, 1, c.Request.Header.ContentLength())\n\t\tassert.DeepEqual(t, \"a\", c.Request.Header.Get(\"dummy\"))\n\t})\n\n\t// valid user\n\tw := PerformRequest(router, \"PUT\", \"/hey/dy\", &Body{bytes.NewBufferString(\"1\"), 1},\n\t\tHeader{\"Connection\", \"close\"})\n\tresp := w.Result()\n\tassert.DeepEqual(t, consts.StatusCreated, resp.StatusCode())\n\tassert.DeepEqual(t, \"{\\\"hi\\\":\\\"dy\\\"}\", string(resp.Body()))\n\tassert.DeepEqual(t, \"application/json; charset=utf-8\", string(resp.Header.ContentType()))\n\tassert.DeepEqual(t, true, resp.Header.ConnectionClose())\n\n\t// unauthorized user\n\tw = PerformRequest(router, \"PUT\", \"/hey/dy\", nil)\n\t_ = w.Result()\n\tresp = w.Result()\n\tassert.DeepEqual(t, consts.StatusUnauthorized, resp.StatusCode())\n\tassert.DeepEqual(t, \"unauthorized\", string(resp.Body()))\n\tassert.DeepEqual(t, \"text/plain; charset=utf-8\", string(resp.Header.ContentType()))\n\tassert.DeepEqual(t, 12, resp.Header.ContentLength())\n\n\t// special header\n\tPerformRequest(router, \"GET\", \"/hey/header\", nil,\n\t\tHeader{\"content-type\", \"application/json\"},\n\t\tHeader{\"content-length\", \"1\"},\n\t\tHeader{\"dummy\", \"a\"},\n\t\tHeader{\"dummy\", \"b\"},\n\t)\n\n\t// not found\n\tw = PerformRequest(router, \"GET\", \"/hey\", nil)\n\tresp = w.Result()\n\tassert.DeepEqual(t, consts.StatusNotFound, resp.StatusCode())\n\n\t// fake body\n\tw = PerformRequest(router, \"GET\", \"/hey\", nil)\n\t_, err := w.WriteString(\", faker\")\n\tresp = w.Result()\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, consts.StatusNotFound, resp.StatusCode())\n\tassert.DeepEqual(t, \"Not Found, faker\", string(resp.Body()))\n\n\t// chunked body\n\tbody := bytes.NewReader(createChunkedBody([]byte(\"hello world!\")))\n\tw = PerformRequest(router, \"PUT\", \"/hey/dy\", &Body{body, -1})\n\tresp = w.Result()\n\tassert.DeepEqual(t, consts.StatusAccepted, resp.StatusCode())\n\tassert.DeepEqual(t, \"body:1\\r\\nh\\r\\n2\\r\\nel\\r\\n3\\r\\nlo \\r\\n4\\r\\nworl\\r\\n2\\r\\nd!\\r\\n0\\r\\n\\r\\n\", string(resp.Body()))\n}\n\nfunc createChunkedBody(body []byte) []byte {\n\tvar b []byte\n\tchunkSize := 1\n\tfor len(body) > 0 {\n\t\tif chunkSize > len(body) {\n\t\t\tchunkSize = len(body)\n\t\t}\n\t\tb = append(b, []byte(fmt.Sprintf(\"%x\\r\\n\", chunkSize))...)\n\t\tb = append(b, body[:chunkSize]...)\n\t\tb = append(b, []byte(\"\\r\\n\")...)\n\t\tbody = body[chunkSize:]\n\t\tchunkSize++\n\t}\n\treturn append(b, []byte(\"0\\r\\n\\r\\n\")...)\n}\n"
  },
  {
    "path": "pkg/common/ut/response.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ut\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\n// ResponseRecorder records handler's response for later test\ntype ResponseRecorder struct {\n\t// Code is the HTTP response code set by WriteHeader.\n\t//\n\t// Note that if a Handler never calls WriteHeader or Write,\n\t// this might end up being 0, rather than the implicit\n\t// http.StatusOK. To get the implicit value, use the Result\n\t// method.\n\tCode int\n\n\t// header contains the headers explicitly set by the Handler.\n\t// It is an internal detail.\n\theader *protocol.ResponseHeader\n\n\t// Body is the buffer to which the Handler's Write calls are sent.\n\t// If nil, the Writes are silently discarded.\n\tBody *bytes.Buffer\n\n\t// Flushed is whether the Handler called Flush.\n\tFlushed bool\n\n\tresult      *protocol.Response // cache of Result's return value\n\twroteHeader bool\n}\n\n// NewRecorder returns an initialized ResponseRecorder.\nfunc NewRecorder() *ResponseRecorder {\n\treturn &ResponseRecorder{\n\t\theader: new(protocol.ResponseHeader),\n\t\tBody:   new(bytes.Buffer),\n\t\tCode:   consts.StatusOK,\n\t}\n}\n\n// Header returns the response headers to mutate within a handler.\n// To test the headers that were written after a handler completes,\n// use the Result method and see the returned Response value's Header.\nfunc (rw *ResponseRecorder) Header() *protocol.ResponseHeader {\n\tm := rw.header\n\tif m == nil {\n\t\tm = new(protocol.ResponseHeader)\n\t\trw.header = m\n\t}\n\treturn m\n}\n\n// Write implements io.Writer. The data in buf is written to\n// rw.Body, if not nil.\nfunc (rw *ResponseRecorder) Write(buf []byte) (int, error) {\n\tif !rw.wroteHeader {\n\t\trw.WriteHeader(consts.StatusOK)\n\t}\n\tif rw.Body != nil {\n\t\trw.Body.Write(buf)\n\t}\n\treturn len(buf), nil\n}\n\n// WriteString implements io.StringWriter. The data in str is written\n// to rw.Body, if not nil.\nfunc (rw *ResponseRecorder) WriteString(str string) (int, error) {\n\tif !rw.wroteHeader {\n\t\trw.WriteHeader(consts.StatusOK)\n\t}\n\tif rw.Body != nil {\n\t\trw.Body.WriteString(str)\n\t}\n\treturn len(str), nil\n}\n\n// WriteHeader sends an HTTP response header with the provided\n// status code.\nfunc (rw *ResponseRecorder) WriteHeader(code int) {\n\tif rw.wroteHeader {\n\t\treturn\n\t}\n\tif rw.header == nil {\n\t\trw.header = new(protocol.ResponseHeader)\n\t}\n\trw.header.SetStatusCode(code)\n\trw.Code = code\n\trw.wroteHeader = true\n}\n\n// Flush implements http.Flusher. To test whether Flush was\n// called, see rw.Flushed.\nfunc (rw *ResponseRecorder) Flush() {\n\tif !rw.wroteHeader {\n\t\trw.WriteHeader(consts.StatusOK)\n\t}\n\trw.Flushed = true\n}\n\n// Result returns the response generated by the handler.\n//\n// The returned Response will have at least its StatusCode,\n// Header, Body, and optionally Trailer populated.\n// More fields may be populated in the future, so callers should\n// not DeepEqual the result in tests.\n//\n// The Response.Header is a snapshot of the headers at the time of the\n// first write call, or at the time of this call, if the handler never\n// did a write.\n//\n// The Response.Body is guaranteed to be non-nil and Body.Read call is\n// guaranteed to not return any error other than io.EOF.\n//\n// Result must only be called after the handler has finished running.\nfunc (rw *ResponseRecorder) Result() *protocol.Response {\n\tif rw.result != nil {\n\t\treturn rw.result\n\t}\n\n\tres := new(protocol.Response)\n\th := rw.Header()\n\th.CopyTo(&res.Header)\n\tif rw.Body != nil {\n\t\tb := rw.Body.Bytes()\n\t\tres.SetBody(b)\n\t\tres.Header.SetContentLength(len(b))\n\t}\n\n\trw.result = res\n\treturn res\n}\n"
  },
  {
    "path": "pkg/common/ut/response_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ut\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc TestResult(t *testing.T) {\n\tr := new(ResponseRecorder)\n\tret := r.Result()\n\tassert.DeepEqual(t, consts.StatusOK, ret.StatusCode())\n}\n\nfunc TestFlush(t *testing.T) {\n\tr := new(ResponseRecorder)\n\tr.Flush()\n\tret := r.Result()\n\tassert.DeepEqual(t, consts.StatusOK, ret.StatusCode())\n}\n\nfunc TestWriterHeader(t *testing.T) {\n\tr := NewRecorder()\n\tr.WriteHeader(consts.StatusCreated)\n\tr.WriteHeader(consts.StatusOK)\n\tret := r.Result()\n\tassert.DeepEqual(t, consts.StatusCreated, ret.StatusCode())\n}\n\nfunc TestWriteString(t *testing.T) {\n\tr := NewRecorder()\n\tr.WriteString(\"hello\")\n\tret := r.Result()\n\tassert.DeepEqual(t, \"hello\", string(ret.Body()))\n}\n\nfunc TestWrite(t *testing.T) {\n\tr := NewRecorder()\n\tr.Write([]byte(\"hello\"))\n\tret := r.Result()\n\tassert.DeepEqual(t, \"hello\", string(ret.Body()))\n}\n"
  },
  {
    "path": "pkg/common/utils/bufpool.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport \"sync\"\n\nvar CopyBufPool = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn make([]byte, 4096)\n\t},\n}\n"
  },
  {
    "path": "pkg/common/utils/chunk.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\nvar errBrokenChunk = errors.NewPublic(\"cannot find crlf at the end of chunk\")\n\nfunc ParseChunkSize(r network.Reader) (int, error) {\n\tn, err := bytesconv.ReadHexInt(r)\n\tif err != nil {\n\t\tif err == io.EOF {\n\t\t\terr = io.ErrUnexpectedEOF\n\t\t}\n\t\treturn -1, err\n\t}\n\tfor {\n\t\tc, err := r.ReadByte()\n\t\tif err != nil {\n\t\t\treturn -1, errors.NewPublic(fmt.Sprintf(\"cannot read '\\r' char at the end of chunk size: %s\", err))\n\t\t}\n\t\t// Skip any trailing whitespace after chunk size.\n\t\tif c == ' ' {\n\t\t\tcontinue\n\t\t}\n\t\tif c != '\\r' {\n\t\t\treturn -1, errors.NewPublic(\n\t\t\t\tfmt.Sprintf(\"unexpected char %q at the end of chunk size. Expected %q\", c, '\\r'),\n\t\t\t)\n\t\t}\n\t\tbreak\n\t}\n\tc, err := r.ReadByte()\n\tif err != nil {\n\t\treturn -1, errors.NewPublic(fmt.Sprintf(\"cannot read '\\n' char at the end of chunk size: %s\", err))\n\t}\n\tif c != '\\n' {\n\t\treturn -1, errors.NewPublic(\n\t\t\tfmt.Sprintf(\"unexpected char %q at the end of chunk size. Expected %q\", c, '\\n'),\n\t\t)\n\t}\n\treturn n, nil\n}\n\n// SkipCRLF will only skip the next CRLF(\"\\r\\n\"), otherwise, error will be returned.\nfunc SkipCRLF(reader network.Reader) error {\n\tp, err := reader.Peek(len(bytestr.StrCRLF))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !bytes.Equal(p, bytestr.StrCRLF) {\n\t\treturn errBrokenChunk\n\t}\n\n\treader.Skip(len(p)) // nolint: errcheck\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/common/utils/chunk_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n)\n\nfunc TestChunkParseChunkSizeGetCorrect(t *testing.T) {\n\t// iterate the hexMap, and judge the difference between dec and ParseChunkSize\n\thexMap := map[int]string{0: \"0\", 10: \"a\", 100: \"64\", 1000: \"3e8\"}\n\tfor dec, hex := range hexMap {\n\t\tchunkSizeBody := hex + \"\\r\\n\"\n\t\tzr := mock.NewZeroCopyReader(chunkSizeBody)\n\t\tchunkSize, err := ParseChunkSize(zr)\n\t\tassert.DeepEqual(t, nil, err)\n\t\tassert.DeepEqual(t, chunkSize, dec)\n\t}\n}\n\nfunc TestChunkParseChunkSizeGetError(t *testing.T) {\n\t// test err from -----n, err := bytesconv.ReadHexInt(r)-----\n\tchunkSizeBody := \"\"\n\tzr := mock.NewZeroCopyReader(chunkSizeBody)\n\tchunkSize, err := ParseChunkSize(zr)\n\tassert.NotNil(t, err)\n\tassert.DeepEqual(t, -1, chunkSize)\n\t// test err from -----c, err := r.ReadByte()-----\n\tchunkSizeBody = \"0\"\n\tzr = mock.NewZeroCopyReader(chunkSizeBody)\n\tchunkSize, err = ParseChunkSize(zr)\n\tassert.NotNil(t, err)\n\tassert.DeepEqual(t, -1, chunkSize)\n\t// test err from -----c, err := r.ReadByte()-----\n\tchunkSizeBody = \"0\" + \"\\r\"\n\tzr = mock.NewZeroCopyReader(chunkSizeBody)\n\tchunkSize, err = ParseChunkSize(zr)\n\tassert.NotNil(t, err)\n\tassert.DeepEqual(t, -1, chunkSize)\n\t// test err from -----c, err := r.ReadByte()-----\n\tchunkSizeBody = \"0\" + \"\\r\" + \"\\r\"\n\tzr = mock.NewZeroCopyReader(chunkSizeBody)\n\tchunkSize, err = ParseChunkSize(zr)\n\tassert.NotNil(t, err)\n\tassert.DeepEqual(t, -1, chunkSize)\n}\n\nfunc TestChunkParseChunkSizeCorrectWhiteSpace(t *testing.T) {\n\t// test the whitespace\n\twhiteSpace := \"\"\n\tfor i := 0; i < 10; i++ {\n\t\twhiteSpace += \" \"\n\t\tchunkSizeBody := \"0\" + whiteSpace + \"\\r\\n\"\n\t\tzr := mock.NewZeroCopyReader(chunkSizeBody)\n\t\tchunkSize, err := ParseChunkSize(zr)\n\t\tassert.DeepEqual(t, nil, err)\n\t\tassert.DeepEqual(t, 0, chunkSize)\n\t}\n}\n\nfunc TestChunkParseChunkSizeNonCRLF(t *testing.T) {\n\t// test non-\"\\r\\n\"\n\tchunkSizeBody := \"0\" + \"\\n\\r\"\n\tzr := mock.NewZeroCopyReader(chunkSizeBody)\n\tchunkSize, err := ParseChunkSize(zr)\n\tassert.DeepEqual(t, true, err != nil)\n\tassert.DeepEqual(t, -1, chunkSize)\n}\n\nfunc TestChunkReadTrueCRLF(t *testing.T) {\n\tCRLF := \"\\r\\n\"\n\tzr := mock.NewZeroCopyReader(CRLF)\n\terr := SkipCRLF(zr)\n\tassert.DeepEqual(t, nil, err)\n}\n\nfunc TestChunkReadFalseCRLF(t *testing.T) {\n\tCRLF := \"\\n\\r\"\n\tzr := mock.NewZeroCopyReader(CRLF)\n\terr := SkipCRLF(zr)\n\tassert.DeepEqual(t, errBrokenChunk, err)\n}\n"
  },
  {
    "path": "pkg/common/utils/env.go",
    "content": "/*\n * Copyright 2024 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n)\n\n// Get bool from env\nfunc GetBoolFromEnv(key string) (bool, error) {\n\tvalue, isExist := os.LookupEnv(key)\n\tif !isExist {\n\t\treturn false, errors.NewPublic(\"env not exist\")\n\t}\n\n\tvalue = strings.TrimSpace(value)\n\treturn strconv.ParseBool(value)\n}\n"
  },
  {
    "path": "pkg/common/utils/ioutil.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"io\"\n\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\nfunc CopyBuffer(dst network.Writer, src io.Reader, buf []byte) (written int64, err error) {\n\tif buf != nil && len(buf) == 0 {\n\t\tpanic(\"empty buffer in io.CopyBuffer\")\n\t}\n\treturn copyBuffer(dst, src, buf)\n}\n\n// copyBuffer is the actual implementation of Copy and CopyBuffer.\n// If buf is nil, one is allocated.\nfunc copyBuffer(dst network.Writer, src io.Reader, buf []byte) (written int64, err error) {\n\tif wt, ok := src.(io.WriterTo); ok {\n\t\tif w, ok := dst.(io.Writer); ok {\n\t\t\treturn wt.WriteTo(w)\n\t\t}\n\t}\n\n\t// Sendfile impl\n\tif rf, ok := dst.(io.ReaderFrom); ok {\n\t\treturn rf.ReadFrom(src)\n\t}\n\n\tif buf == nil {\n\t\tsize := 32 * 1024\n\t\tif l, ok := src.(*io.LimitedReader); ok && int64(size) > l.N {\n\t\t\tif l.N < 1 {\n\t\t\t\tsize = 1\n\t\t\t} else {\n\t\t\t\tsize = int(l.N)\n\t\t\t}\n\t\t}\n\t\tbuf = make([]byte, size)\n\t}\n\tfor {\n\t\tnr, er := src.Read(buf)\n\t\tif nr > 0 {\n\t\t\tnw, eb := dst.WriteBinary(buf[:nr])\n\t\t\tif eb != nil {\n\t\t\t\terr = eb\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif nw > 0 {\n\t\t\t\twritten += int64(nw)\n\t\t\t}\n\t\t\tif nr != nw {\n\t\t\t\terr = io.ErrShortWrite\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err = dst.Flush(); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif er != nil {\n\t\t\tif er != io.EOF {\n\t\t\t\terr = er\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn\n}\n\nfunc CopyZeroAlloc(w network.Writer, r io.Reader) (int64, error) {\n\tvbuf := CopyBufPool.Get()\n\tbuf := vbuf.([]byte)\n\tn, err := CopyBuffer(w, r, buf)\n\tCopyBufPool.Put(vbuf)\n\treturn n, err\n}\n"
  },
  {
    "path": "pkg/common/utils/ioutil_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\ntype writeReadTest interface {\n\tWrite(p []byte) (n int, err error)\n\tMalloc(n int) (buf []byte, err error)\n\tWriteBinary(b []byte) (n int, err error)\n\tFlush() error\n}\n\ntype readerTest interface {\n\tReadFrom(r io.Reader) (n int64, err error)\n\tMalloc(n int) (buf []byte, err error)\n\tWriteBinary(b []byte) (n int, err error)\n\tFlush() error\n}\n\ntype testWriter struct {\n\tw io.Writer\n}\n\nfunc (t testWriter) Write(p []byte) (n int, err error) {\n\treturn\n}\n\nfunc (t testWriter) Malloc(n int) (buf []byte, err error) {\n\treturn\n}\n\nfunc (t testWriter) WriteBinary(b []byte) (n int, err error) {\n\treturn\n}\n\nfunc (t testWriter) Flush() error {\n\treturn nil\n}\n\ntype testReader struct {\n\tr io.ReaderFrom\n}\n\nfunc (t testReader) ReadFrom(r io.Reader) (n int64, err error) {\n\treturn\n}\n\nfunc (t testReader) Malloc(n int) (buf []byte, err error) {\n\treturn\n}\n\nfunc (t testReader) WriteBinary(b []byte) (n int, err error) {\n\treturn\n}\n\nfunc (t testReader) Flush() error {\n\treturn nil\n}\n\nfunc newTestWriter(w io.Writer) writeReadTest {\n\treturn &testWriter{\n\t\tw: w,\n\t}\n}\n\nfunc newTestReaderForm(r io.ReaderFrom) readerTest {\n\treturn &testReader{\n\t\tr: r,\n\t}\n}\n\nfunc TestIoutilCopyBuffer(t *testing.T) {\n\tvar writeBuffer bytes.Buffer\n\tstr := string(\"hertz is very good!!!\")\n\tsrc := bytes.NewBufferString(str)\n\tdst := network.NewWriter(&writeBuffer)\n\tvar buf []byte\n\t// src.Len() will change, when use src.read(p []byte)\n\tsrcLen := int64(src.Len())\n\twritten, err := CopyBuffer(dst, src, buf)\n\n\tassert.DeepEqual(t, written, srcLen)\n\tassert.DeepEqual(t, err, nil)\n\tassert.DeepEqual(t, []byte(str), writeBuffer.Bytes())\n\n\t// Test when no data is readable\n\twriteBuffer.Reset()\n\temptySrc := bytes.NewBufferString(\"\")\n\twritten, err = CopyBuffer(dst, emptySrc, buf)\n\tassert.DeepEqual(t, written, int64(0))\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"\"), writeBuffer.Bytes())\n\n\t// Test a LimitedReader\n\twriteBuffer.Reset()\n\tlimit := int64(5)\n\tlimitedSrc := io.LimitedReader{R: bytes.NewBufferString(str), N: limit}\n\twritten, err = CopyBuffer(dst, &limitedSrc, buf)\n\tassert.DeepEqual(t, written, limit)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(str[:limit]), writeBuffer.Bytes())\n}\n\nfunc TestIoutilCopyBufferWithIoWriter(t *testing.T) {\n\tvar writeBuffer bytes.Buffer\n\tstr := \"hertz is very good!!!\"\n\tvar buf []byte\n\tsrc := bytes.NewBuffer([]byte(str))\n\tioWriter := newTestWriter(&writeBuffer)\n\t// to show example about -----w, ok := dst.(io.Writer)-----\n\t_, ok := ioWriter.(io.Writer)\n\tassert.DeepEqual(t, true, ok)\n\twritten, err := CopyBuffer(ioWriter, src, buf)\n\tassert.DeepEqual(t, written, int64(0))\n\tassert.NotNil(t, err)\n\tassert.DeepEqual(t, []byte(nil), writeBuffer.Bytes())\n}\n\nfunc TestIoutilCopyBufferWithIoReaderFrom(t *testing.T) {\n\tvar writeBuffer bytes.Buffer\n\tstr := \"hertz is very good!!!\"\n\tvar buf []byte\n\tsrc := bytes.NewBufferString(str)\n\tioReaderFrom := newTestReaderForm(&writeBuffer)\n\t// to show example about -----rf, ok := dst.(io.ReaderFrom)-----\n\t_, ok := ioReaderFrom.(io.Writer)\n\tassert.DeepEqual(t, false, ok)\n\t_, ok = ioReaderFrom.(io.ReaderFrom)\n\tassert.DeepEqual(t, true, ok)\n\twritten, err := CopyBuffer(ioReaderFrom, src, buf)\n\tassert.DeepEqual(t, written, int64(0))\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(nil), writeBuffer.Bytes())\n}\n\nfunc TestIoutilCopyBufferWithPanic(t *testing.T) {\n\tvar writeBuffer bytes.Buffer\n\tstr := \"hertz is very good!!!\"\n\tvar buf []byte\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tassert.DeepEqual(t, \"empty buffer in io.CopyBuffer\", r)\n\t\t}\n\t}()\n\tsrc := bytes.NewBufferString(str)\n\tdst := network.NewWriter(&writeBuffer)\n\tbuf = make([]byte, 0)\n\t_, _ = CopyBuffer(dst, src, buf)\n}\n\nfunc TestIoutilCopyBufferWithNilBuffer(t *testing.T) {\n\tvar writeBuffer bytes.Buffer\n\tstr := string(\"hertz is very good!!!\")\n\tsrc := bytes.NewBufferString(str)\n\tdst := network.NewWriter(&writeBuffer)\n\t// src.Len() will change, when use src.read(p []byte)\n\tsrcLen := int64(src.Len())\n\twritten, err := CopyBuffer(dst, src, nil)\n\n\tassert.DeepEqual(t, written, srcLen)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(str), writeBuffer.Bytes())\n}\n\nfunc TestIoutilCopyBufferWithNilBufferAndIoLimitedReader(t *testing.T) {\n\tvar writeBuffer bytes.Buffer\n\tstr := \"hertz is very good!!!\"\n\tsrc := bytes.NewBufferString(str)\n\treader := mock.NewLimitReader(src)\n\tdst := network.NewWriter(&writeBuffer)\n\tsrcLen := int64(src.Len())\n\twritten, err := CopyBuffer(dst, &reader, nil)\n\n\tassert.DeepEqual(t, written, srcLen)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(str), writeBuffer.Bytes())\n\n\t// test l.N < 1\n\twriteBuffer.Reset()\n\tstr = \"\"\n\tsrc = bytes.NewBufferString(str)\n\treader = mock.NewLimitReader(src)\n\tdst = network.NewWriter(&writeBuffer)\n\tsrcLen = int64(src.Len())\n\twritten, err = CopyBuffer(dst, &reader, nil)\n\n\tassert.DeepEqual(t, written, srcLen)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(str), writeBuffer.Bytes())\n}\n\nfunc TestIoutilCopyZeroAlloc(t *testing.T) {\n\tvar writeBuffer bytes.Buffer\n\tstr := \"hertz is very good!!!\"\n\tsrc := bytes.NewBufferString(str)\n\tdst := network.NewWriter(&writeBuffer)\n\tsrcLen := int64(src.Len())\n\twritten, err := CopyZeroAlloc(dst, src)\n\n\tassert.DeepEqual(t, written, srcLen)\n\tassert.DeepEqual(t, err, nil)\n\tassert.DeepEqual(t, []byte(str), writeBuffer.Bytes())\n\n\t// Test when no data is readable\n\twriteBuffer.Reset()\n\temptySrc := bytes.NewBufferString(\"\")\n\twritten, err = CopyZeroAlloc(dst, emptySrc)\n\tassert.DeepEqual(t, written, int64(0))\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"\"), writeBuffer.Bytes())\n}\n\nfunc TestIoutilCopyBufferWithEmptyBuffer(t *testing.T) {\n\tvar writeBuffer bytes.Buffer\n\tstr := \"hertz is very good!!!\"\n\tsrc := bytes.NewBufferString(str)\n\tdst := network.NewWriter(&writeBuffer)\n\t// Use a non-empty buffer of length 0\n\temptyBuf := make([]byte, 0)\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tassert.DeepEqual(t, \"empty buffer in io.CopyBuffer\", r)\n\t\t\t}\n\t\t}()\n\n\t\twritten, err := CopyBuffer(dst, src, emptyBuf)\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, written, int64(len(str)))\n\t\tassert.DeepEqual(t, []byte(str), writeBuffer.Bytes())\n\t}()\n}\n\nfunc TestIoutilCopyBufferWithLimitedReader(t *testing.T) {\n\tvar writeBuffer bytes.Buffer\n\tstr := \"hertz is very good!!!\"\n\tsrc := bytes.NewBufferString(str)\n\tlimit := int64(5)\n\tlimitedSrc := io.LimitedReader{R: src, N: limit}\n\tdst := network.NewWriter(&writeBuffer)\n\tvar buf []byte\n\n\t// Test LimitedReader status\n\twritten, err := CopyBuffer(dst, &limitedSrc, buf)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, written, limit)\n\tassert.DeepEqual(t, []byte(str[:limit]), writeBuffer.Bytes())\n}\n"
  },
  {
    "path": "pkg/common/utils/netaddr.go",
    "content": "/*\n * Copyright 2021 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport \"net\"\n\nvar _ net.Addr = (*NetAddr)(nil)\n\n// NetAddr implements the net.Addr interface.\ntype NetAddr struct {\n\tnetwork string\n\taddress string\n}\n\n// NewNetAddr creates a new NetAddr object with the network and address provided.\nfunc NewNetAddr(network, address string) net.Addr {\n\treturn &NetAddr{network, address}\n}\n\n// Network implements the net.Addr interface.\nfunc (na *NetAddr) Network() string {\n\treturn na.network\n}\n\n// String implements the net.Addr interface.\nfunc (na *NetAddr) String() string {\n\treturn na.address\n}\n"
  },
  {
    "path": "pkg/common/utils/netaddr_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestNetAddr(t *testing.T) {\n\tnetworkAddr := NewNetAddr(\"127.0.0.1\", \"192.168.1.1\")\n\n\tassert.DeepEqual(t, networkAddr.Network(), \"127.0.0.1\")\n\tassert.DeepEqual(t, networkAddr.String(), \"192.168.1.1\")\n}\n"
  },
  {
    "path": "pkg/common/utils/network.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport \"net\"\n\nconst (\n\tUNKNOWN_IP_ADDR = \"-\"\n)\n\nvar localIP string\n\n// LocalIP returns host's ip\nfunc LocalIP() string {\n\treturn localIP\n}\n\n// getLocalIp enumerates local net interfaces to find local ip, it should only be called in init phase\nfunc getLocalIp() string {\n\tinters, err := net.Interfaces()\n\tif err != nil {\n\t\treturn UNKNOWN_IP_ADDR\n\t}\n\tfor _, inter := range inters {\n\t\tif inter.Flags&net.FlagLoopback != net.FlagLoopback &&\n\t\t\tinter.Flags&net.FlagUp != 0 {\n\t\t\taddrs, err := inter.Addrs()\n\t\t\tif err != nil {\n\t\t\t\treturn UNKNOWN_IP_ADDR\n\t\t\t}\n\t\t\tfor _, addr := range addrs {\n\t\t\t\tif ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {\n\t\t\t\t\treturn ipnet.IP.String()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn UNKNOWN_IP_ADDR\n}\n\nfunc init() {\n\tlocalIP = getLocalIp()\n}\n\n// TLSRecordHeaderLooksLikeHTTP reports whether a TLS record header\n// looks like it might've been a misdirected plaintext HTTP request.\nfunc TLSRecordHeaderLooksLikeHTTP(hdr [5]byte) bool {\n\tswitch string(hdr[:]) {\n\tcase \"GET /\", \"HEAD \", \"POST \", \"PUT /\", \"OPTIO\":\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/common/utils/network_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestTLSRecordHeaderLooksLikeHTTP(t *testing.T) {\n\tHeaderValueAndExpectedResult := [][]interface{}{\n\t\t{[5]byte{'G', 'E', 'T', ' ', '/'}, true},\n\t\t{[5]byte{'H', 'E', 'A', 'D', ' '}, true},\n\t\t{[5]byte{'P', 'O', 'S', 'T', ' '}, true},\n\t\t{[5]byte{'P', 'U', 'T', ' ', '/'}, true},\n\t\t{[5]byte{'O', 'P', 'T', 'I', 'O'}, true},\n\t\t{[5]byte{'G', 'E', 'T', '/', ' '}, false},\n\t\t{[5]byte{' ', 'H', 'E', 'A', 'D'}, false},\n\t\t{[5]byte{' ', 'P', 'O', 'S', 'T'}, false},\n\t\t{[5]byte{'P', 'U', 'T', '/', ' '}, false},\n\t\t{[5]byte{'H', 'E', 'R', 'T', 'Z'}, false},\n\t}\n\n\tfor _, testCase := range HeaderValueAndExpectedResult {\n\t\tvalue, expectedResult := testCase[0].([5]byte), testCase[1].(bool)\n\t\tassert.DeepEqual(t, expectedResult, TLSRecordHeaderLooksLikeHTTP(value))\n\t}\n}\n\nfunc TestLocalIP(t *testing.T) {\n\t// Mock the localIP variable for testing purposes.\n\tlocalIP = \"192.168.0.1\"\n\n\t// Ensure that LocalIP() returns the expected local IP.\n\texpectedIP := \"192.168.0.1\"\n\tif got := LocalIP(); got != expectedIP {\n\t\tassert.DeepEqual(t, got, expectedIP)\n\t}\n}\n"
  },
  {
    "path": "pkg/common/utils/path.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage utils\n\nimport \"strings\"\n\n// CleanPath is the URL version of path.Clean, it returns a canonical URL path\n// for p, eliminating . and .. elements.\n//\n// The following rules are applied iteratively until no further processing can\n// be done:\n//  1. Replace multiple slashes with a single slash.\n//  2. Eliminate each . path name element (the current directory).\n//  3. Eliminate each inner .. path name element (the parent directory)\n//     along with the non-.. element that precedes it.\n//  4. Eliminate .. elements that begin a rooted path:\n//     that is, replace \"/..\" by \"/\" at the beginning of a path.\n//\n// If the result of this process is an empty string, \"/\" is returned\nfunc CleanPath(p string) string {\n\tconst stackBufSize = 128\n\n\t// Turn empty string into \"/\"\n\tif p == \"\" {\n\t\treturn \"/\"\n\t}\n\n\t// Reasonably sized buffer on stack to avoid allocations in the common case.\n\t// If a larger buffer is required, it gets allocated dynamically.\n\tbuf := make([]byte, 0, stackBufSize)\n\n\tn := len(p)\n\n\t// Invariants:\n\t//      reading from path; r is index of next byte to process.\n\t//      writing to buf; w is index of next byte to write.\n\n\t// path must start with '/'\n\tr := 1\n\tw := 1\n\n\tif p[0] != '/' {\n\t\tr = 0\n\n\t\tif n+1 > stackBufSize {\n\t\t\tbuf = make([]byte, n+1)\n\t\t} else {\n\t\t\tbuf = buf[:n+1]\n\t\t}\n\t\tbuf[0] = '/'\n\t}\n\n\ttrailing := n > 1 && p[n-1] == '/'\n\n\t// A bit more clunky without a 'lazybuf' like the path package, but the loop\n\t// gets completely inlined (bufApp calls).\n\t// So in contrast to the path package this loop has no expensive function\n\t// calls (except make, if needed).\n\n\tfor r < n {\n\t\tswitch {\n\t\tcase p[r] == '/':\n\t\t\t// empty path element, trailing slash is added after the end\n\t\t\tr++\n\n\t\tcase p[r] == '.' && r+1 == n:\n\t\t\ttrailing = true\n\t\t\tr++\n\n\t\tcase p[r] == '.' && p[r+1] == '/':\n\t\t\t// . element\n\t\t\tr += 2\n\n\t\tcase p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):\n\t\t\t// .. element: remove to last /\n\t\t\tr += 3\n\n\t\t\tif w > 1 {\n\t\t\t\t// can backtrack\n\t\t\t\tw--\n\n\t\t\t\tif len(buf) == 0 {\n\t\t\t\t\tfor w > 1 && p[w] != '/' {\n\t\t\t\t\t\tw--\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfor w > 1 && buf[w] != '/' {\n\t\t\t\t\t\tw--\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\tdefault:\n\t\t\t// Real path element.\n\t\t\t// Add slash if needed\n\t\t\tif w > 1 {\n\t\t\t\tbufApp(&buf, p, w, '/')\n\t\t\t\tw++\n\t\t\t}\n\n\t\t\t// Copy element\n\t\t\tfor r < n && p[r] != '/' {\n\t\t\t\tbufApp(&buf, p, w, p[r])\n\t\t\t\tw++\n\t\t\t\tr++\n\t\t\t}\n\t\t}\n\t}\n\n\t// Re-append trailing slash\n\tif trailing && w > 1 {\n\t\tbufApp(&buf, p, w, '/')\n\t\tw++\n\t}\n\n\t// If the original string was not modified (or only shortened at the end),\n\t// return the respective substring of the original string.\n\t// Otherwise return a new string from the buffer.\n\tif len(buf) == 0 {\n\t\treturn p[:w]\n\t}\n\treturn string(buf[:w])\n}\n\n// Internal helper to lazily create a buffer if necessary.\n// Calls to this function get inlined.\nfunc bufApp(buf *[]byte, s string, w int, c byte) {\n\tb := *buf\n\tif len(b) == 0 {\n\t\t// No modification of the original string so far.\n\t\t// If the next character is the same as in the original string, we do\n\t\t// not yet have to allocate a buffer.\n\t\tif s[w] == c {\n\t\t\treturn\n\t\t}\n\n\t\t// Otherwise use either the stack buffer, if it is large enough, or\n\t\t// allocate a new buffer on the heap, and copy all previous characters.\n\t\tif l := len(s); l > cap(b) {\n\t\t\t*buf = make([]byte, len(s))\n\t\t} else {\n\t\t\t*buf = (*buf)[:l]\n\t\t}\n\t\tb = *buf\n\n\t\tcopy(b, s[:w])\n\t}\n\tb[w] = c\n}\n\n// AddMissingPort adds a port to a host if it is missing.\n// A literal IPv6 address in hostport must be enclosed in square\n// brackets, as in \"[::1]:80\", \"[::1%lo0]:80\".\nfunc AddMissingPort(addr string, isTLS bool) string {\n\tif strings.IndexByte(addr, ':') >= 0 {\n\t\tendOfV6 := strings.IndexByte(addr, ']')\n\t\t// we do not care about the validity of the address, just check if it has more bytes after ']'\n\t\tif endOfV6 < len(addr)-1 {\n\t\t\treturn addr\n\t\t}\n\t}\n\tif !isTLS {\n\t\treturn addr + \":80\"\n\t}\n\treturn addr + \":443\"\n}\n"
  },
  {
    "path": "pkg/common/utils/path_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestPathCleanPath(t *testing.T) {\n\tnormalPath := \"/Foo/Bar/go/src/github.com/cloudwego/hertz/pkg/common/utils/path_test.go\"\n\texpectedNormalPath := \"/Foo/Bar/go/src/github.com/cloudwego/hertz/pkg/common/utils/path_test.go\"\n\tcleanNormalPath := CleanPath(normalPath)\n\tassert.DeepEqual(t, expectedNormalPath, cleanNormalPath)\n\n\tsingleDotPath := \"/Foo/Bar/./././go/src\"\n\texpectedSingleDotPath := \"/Foo/Bar/go/src\"\n\tcleanSingleDotPath := CleanPath(singleDotPath)\n\tassert.DeepEqual(t, expectedSingleDotPath, cleanSingleDotPath)\n\n\tdoubleDotPath := \"../../..\"\n\texpectedDoubleDotPath := \"/\"\n\tcleanDoublePotPath := CleanPath(doubleDotPath)\n\tassert.DeepEqual(t, expectedDoubleDotPath, cleanDoublePotPath)\n\n\t// MultiDot can be treated as a file name\n\tmultiDotPath := \"/../....\"\n\texpectedMultiDotPath := \"/....\"\n\tcleanMultiDotPath := CleanPath(multiDotPath)\n\tassert.DeepEqual(t, expectedMultiDotPath, cleanMultiDotPath)\n\n\tnullPath := \"\"\n\texpectedNullPath := \"/\"\n\tcleanNullPath := CleanPath(nullPath)\n\tassert.DeepEqual(t, expectedNullPath, cleanNullPath)\n\n\trelativePath := \"/Foo/Bar/../go/src/../../github.com/cloudwego/hertz\"\n\texpectedRelativePath := \"/Foo/github.com/cloudwego/hertz\"\n\tcleanRelativePath := CleanPath(relativePath)\n\tassert.DeepEqual(t, expectedRelativePath, cleanRelativePath)\n\n\tmultiSlashPath := \"///////Foo//Bar////go//src/github.com/cloudwego/hertz//..\"\n\texpectedMultiSlashPath := \"/Foo/Bar/go/src/github.com/cloudwego\"\n\tcleanMultiSlashPath := CleanPath(multiSlashPath)\n\tassert.DeepEqual(t, expectedMultiSlashPath, cleanMultiSlashPath)\n\n\tinputPath := \"/Foo/Bar/go/src/github.com/cloudwego/hertz/pkg/common/utils/path_test.go/.\"\n\texpectedPath := \"/Foo/Bar/go/src/github.com/cloudwego/hertz/pkg/common/utils/path_test.go/\"\n\tcleanedPath := CleanPath(inputPath)\n\tassert.DeepEqual(t, expectedPath, cleanedPath)\n}\n\n// The Function AddMissingPort can only add the missed port, don't consider the other error case.\nfunc TestPathAddMissingPort(t *testing.T) {\n\tipList := []string{\"127.0.0.1\", \"111.111.1.1\", \"[0:0:0:0:0:ffff:192.1.56.10]\", \"[0:0:0:0:0:ffff:c0a8:101]\", \"www.foobar.com\"}\n\tfor _, ip := range ipList {\n\t\tassert.DeepEqual(t, ip+\":443\", AddMissingPort(ip, true))\n\t\tassert.DeepEqual(t, ip+\":80\", AddMissingPort(ip, false))\n\t\tcustomizedPort := \":8080\"\n\t\tassert.DeepEqual(t, ip+customizedPort, AddMissingPort(ip+customizedPort, true))\n\t\tassert.DeepEqual(t, ip+customizedPort, AddMissingPort(ip+customizedPort, false))\n\t}\n}\n"
  },
  {
    "path": "pkg/common/utils/utils.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage utils\n\nimport (\n\t\"bytes\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n)\n\nvar errNeedMore = errs.New(errs.ErrNeedMore, errs.ErrorTypePublic, \"cannot find trailing lf\")\n\nfunc Assert(guard bool, text string) {\n\tif !guard {\n\t\tpanic(text)\n\t}\n}\n\n// H is a shortcut for map[string]interface{}\ntype H map[string]interface{}\n\nfunc IsTrueString(str string) bool {\n\treturn strings.ToLower(str) == \"true\"\n}\n\nfunc NameOfFunction(f interface{}) string {\n\treturn runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()\n}\n\nfunc CaseInsensitiveCompare(a, b []byte) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := 0; i < len(a); i++ {\n\t\tif a[i]|0x20 != b[i]|0x20 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc NormalizeHeaderKey(b []byte, disableNormalizing bool) {\n\tif disableNormalizing {\n\t\treturn\n\t}\n\n\tn := len(b)\n\tif n == 0 {\n\t\treturn\n\t}\n\n\tb[0] = bytesconv.ToUpperTable[b[0]]\n\tfor i := 1; i < n; i++ {\n\t\tp := &b[i]\n\t\tif *p == '-' {\n\t\t\ti++\n\t\t\tif i < n {\n\t\t\t\tb[i] = bytesconv.ToUpperTable[b[i]]\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\t*p = bytesconv.ToLowerTable[*p]\n\t}\n}\n\nfunc NextLine(b []byte) ([]byte, []byte, error) {\n\tnNext := bytes.IndexByte(b, '\\n')\n\tif nNext < 0 {\n\t\treturn nil, nil, errNeedMore\n\t}\n\tn := nNext\n\tif n > 0 && b[n-1] == '\\r' {\n\t\tn--\n\t}\n\treturn b[:n], b[nNext+1:], nil\n}\n\nfunc FilterContentType(content string) string {\n\tfor i, char := range content {\n\t\tif char == ' ' || char == ';' {\n\t\t\treturn content[:i]\n\t\t}\n\t}\n\treturn content\n}\n"
  },
  {
    "path": "pkg/common/utils/utils_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\n// test assert func\nfunc TestUtilsAssert(t *testing.T) {\n\tassertPanic := func() (panicked bool) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tpanicked = true\n\t\t\t}\n\t\t}()\n\t\tAssert(false, \"should panic\")\n\t\treturn false\n\t}\n\n\t// Checking if the assertPanic function results in a panic as expected.\n\t// We expect a true value because it should panic.\n\tassert.DeepEqual(t, true, assertPanic())\n\n\t// Checking if a true assertion does not result in a panic.\n\t// We create a wrapper around Assert to capture if it panics when it should not.\n\tnoPanic := func() (panicked bool) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tpanicked = true\n\t\t\t}\n\t\t}()\n\t\tAssert(true, \"should not panic\")\n\t\treturn false\n\t}\n\n\t// We expect a false value because it should not panic.\n\tassert.DeepEqual(t, false, noPanic())\n}\n\nfunc TestUtilsIsTrueString(t *testing.T) {\n\tnormalTrueStr := \"true\"\n\tupperTrueStr := \"TRUE\"\n\totherStr := \"hertz\"\n\n\tassert.DeepEqual(t, true, IsTrueString(normalTrueStr))\n\tassert.DeepEqual(t, true, IsTrueString(upperTrueStr))\n\tassert.DeepEqual(t, false, IsTrueString(otherStr))\n}\n\n// used for TestUtilsNameOfFunction\nfunc testName(a int) {\n}\n\n// return the relative path for the function\nfunc TestUtilsNameOfFunction(t *testing.T) {\n\tpathOfTestName := \"github.com/cloudwego/hertz/pkg/common/utils.testName\"\n\tpathOfIsTrueString := \"github.com/cloudwego/hertz/pkg/common/utils.IsTrueString\"\n\tnameOfTestName := NameOfFunction(testName)\n\tnameOfIsTrueString := NameOfFunction(IsTrueString)\n\n\tassert.DeepEqual(t, pathOfTestName, nameOfTestName)\n\tassert.DeepEqual(t, pathOfIsTrueString, nameOfIsTrueString)\n}\n\nfunc TestUtilsCaseInsensitiveCompare(t *testing.T) {\n\tlowerStr := []byte(\"content-length\")\n\tupperStr := []byte(\"Content-Length\")\n\tassert.DeepEqual(t, true, CaseInsensitiveCompare(lowerStr, upperStr))\n\n\tlessStr := []byte(\"content-type\")\n\tmoreStr := []byte(\"content-length\")\n\tassert.DeepEqual(t, false, CaseInsensitiveCompare(lessStr, moreStr))\n\n\tfirstStr := []byte(\"content-type\")\n\tsecondStr := []byte(\"content0type\")\n\tassert.DeepEqual(t, false, CaseInsensitiveCompare(firstStr, secondStr))\n}\n\n// NormalizeHeaderKey can upper the first letter and lower the other letter in\n// HTTP header, invervaled by '-'.\n// Example: \"content-type\" -> \"Content-Type\"\nfunc TestUtilsNormalizeHeaderKey(t *testing.T) {\n\tcontentTypeStr := []byte(\"Content-Type\")\n\tlowerContentTypeStr := []byte(\"content-type\")\n\tmixedContentTypeStr := []byte(\"conTENt-tYpE\")\n\tmixedContertTypeStrWithoutNormalizing := []byte(\"Content-type\")\n\tNormalizeHeaderKey(contentTypeStr, false)\n\tNormalizeHeaderKey(lowerContentTypeStr, false)\n\tNormalizeHeaderKey(mixedContentTypeStr, false)\n\tNormalizeHeaderKey(lowerContentTypeStr, true)\n\n\tassert.DeepEqual(t, \"Content-Type\", string(contentTypeStr))\n\tassert.DeepEqual(t, \"Content-Type\", string(lowerContentTypeStr))\n\tassert.DeepEqual(t, \"Content-Type\", string(mixedContentTypeStr))\n\tassert.DeepEqual(t, \"Content-type\", string(mixedContertTypeStrWithoutNormalizing))\n}\n\n// Cutting up the header Type.\n// Example: \"Content-Type: application/x-www-form-urlencoded\\r\\nDate: Fri, 6 Aug 2021 11:00:31 GMT\"\n// ->\"Content-Type: application/x-www-form-urlencoded\" and \"Date: Fri, 6 Aug 2021 11:00:31 GMT\"\nfunc TestUtilsNextLine(t *testing.T) {\n\tmultiHeaderStr := []byte(\"Content-Type: application/x-www-form-urlencoded\\r\\nDate: Fri, 6 Aug 2021 11:00:31 GMT\")\n\tcontentTypeStr, dateStr, hErr := NextLine(multiHeaderStr)\n\tassert.DeepEqual(t, nil, hErr)\n\tassert.DeepEqual(t, \"Content-Type: application/x-www-form-urlencoded\", string(contentTypeStr))\n\tassert.DeepEqual(t, \"Date: Fri, 6 Aug 2021 11:00:31 GMT\", string(dateStr))\n\n\tmultiHeaderStrWithoutReturn := []byte(\"Content-Type: application/x-www-form-urlencoded\\nDate: Fri, 6 Aug 2021 11:00:31 GMT\")\n\tcontentTypeStr, dateStr, hErr = NextLine(multiHeaderStrWithoutReturn)\n\tassert.DeepEqual(t, nil, hErr)\n\tassert.DeepEqual(t, \"Content-Type: application/x-www-form-urlencoded\", string(contentTypeStr))\n\tassert.DeepEqual(t, \"Date: Fri, 6 Aug 2021 11:00:31 GMT\", string(dateStr))\n\n\tsingleHeaderStrWithFirstNewLine := []byte(\"\\nContent-Type: application/x-www-form-urlencoded\")\n\tfirstStr, secondStr, sErr := NextLine(singleHeaderStrWithFirstNewLine)\n\tassert.DeepEqual(t, nil, sErr)\n\tassert.DeepEqual(t, string(\"\"), string(firstStr))\n\tassert.DeepEqual(t, \"Content-Type: application/x-www-form-urlencoded\", string(secondStr))\n\n\tsingleHeaderStr := []byte(\"Content-Type: application/x-www-form-urlencoded\")\n\t_, _, sErr = NextLine(singleHeaderStr)\n\tassert.DeepEqual(t, errNeedMore, sErr)\n}\n\nfunc TestFilterContentType(t *testing.T) {\n\tcontentType := \"text/plain; charset=utf-8\"\n\tcontentType = FilterContentType(contentType)\n\tassert.DeepEqual(t, \"text/plain\", contentType)\n}\n\nfunc TestNormalizeHeaderKeyEdgeCases(t *testing.T) {\n\tempty := []byte(\"\")\n\tNormalizeHeaderKey(empty, false)\n\tassert.DeepEqual(t, []byte(\"\"), empty)\n\tNormalizeHeaderKey(empty, true)\n\tassert.DeepEqual(t, []byte(\"\"), empty)\n}\n\nfunc TestFilterContentTypeEdgeCases(t *testing.T) {\n\tsimpleContentType := \"text/plain\"\n\tassert.DeepEqual(t, \"text/plain\", FilterContentType(simpleContentType))\n\tcomplexContentType := \"text/html; charset=utf-8; format=flowed\"\n\tassert.DeepEqual(t, \"text/html\", FilterContentType(complexContentType))\n}\n"
  },
  {
    "path": "pkg/network/connection.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage network\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n// Reader is for buffered Reader\ntype Reader interface {\n\t// Peek returns the next n bytes without advancing the reader.\n\tPeek(n int) ([]byte, error)\n\n\t// Skip discards the next n bytes.\n\tSkip(n int) error\n\n\t// Release the memory space occupied by all read slices. This method needs to be executed actively to\n\t// recycle the memory after confirming that the previously read data is no longer in use.\n\t// After invoking Release, the slices obtained by the method such as Peek will\n\t// become an invalid address and cannot be used anymore.\n\tRelease() error\n\n\t// Len returns the total length of the readable data in the reader.\n\tLen() int\n\n\t// ReadByte is used to read one byte with advancing the read pointer.\n\tReadByte() (byte, error)\n\n\t// ReadBinary is used to read next n byte with copy, and the read pointer will be advanced.\n\tReadBinary(n int) (p []byte, err error)\n}\n\ntype Writer interface {\n\t// Malloc will provide a n bytes buffer to send data.\n\tMalloc(n int) (buf []byte, err error)\n\n\t// WriteBinary will use the user buffer to flush.\n\t// NOTE: Before flush successfully, the buffer b should be valid.\n\tWriteBinary(b []byte) (n int, err error)\n\n\t// Flush will send data to the peer end.\n\tFlush() error\n}\n\ntype ReadWriter interface {\n\tReader\n\tWriter\n}\n\ntype Conn interface {\n\tnet.Conn\n\tReader\n\tWriter\n\n\t// SetReadTimeout should work for every Read process\n\tSetReadTimeout(t time.Duration) error\n\tSetWriteTimeout(t time.Duration) error\n}\n\ntype ConnTLSer interface {\n\tHandshake() error\n\tConnectionState() tls.ConnectionState\n}\n\ntype HandleSpecificError interface {\n\tHandleSpecificError(err error, rip string) (needIgnore bool)\n}\n\ntype ErrorNormalization interface {\n\tToHertzError(err error) error\n}\n\ntype DialFunc func(addr string) (Conn, error)\n\n/****************** Stream-based connection *******************/\n\n// StreamConn is interface for stream-based connection abstraction.\ntype StreamConn interface {\n\tGetRawConnection() interface{}\n\t// HandshakeComplete blocks until the handshake completes (or fails).\n\tHandshakeComplete() context.Context\n\t// GetVersion returns the version of the protocol used by the connection.\n\tGetVersion() uint32\n\t// CloseWithError closes the connection with an error.\n\t// The error string will be sent to the peer.\n\tCloseWithError(err ApplicationError, errMsg string) error\n\t// LocalAddr returns the local address.\n\tLocalAddr() net.Addr\n\t// RemoteAddr returns the address of the peer.\n\tRemoteAddr() net.Addr\n\t// The context is cancelled when the connection is closed.\n\tContext() context.Context\n\t// Streamer is the interface for stream operations.\n\tStreamer\n}\n\ntype Streamer interface {\n\t// AcceptStream returns the next stream opened by the peer, blocking until one is available.\n\t// If the connection was closed due to a timeout, the error satisfies\n\t// the net.Error interface, and Timeout() will be true.\n\tAcceptStream(context.Context) (Stream, error)\n\t// AcceptUniStream returns the next unidirectional stream opened by the peer, blocking until one is available.\n\t// If the connection was closed due to a timeout, the error satisfies\n\t// the net.Error interface, and Timeout() will be true.\n\tAcceptUniStream(context.Context) (ReceiveStream, error)\n\t// OpenStream opens a new bidirectional QUIC stream.\n\t// There is no signaling to the peer about new streams:\n\t// The peer can only accept the stream after data has been sent on the stream.\n\t// If the error is non-nil, it satisfies the net.Error interface.\n\t// When reaching the peer's stream limit, err.Temporary() will be true.\n\t// If the connection was closed due to a timeout, Timeout() will be true.\n\tOpenStream() (Stream, error)\n\t// OpenStreamSync opens a new bidirectional QUIC stream.\n\t// It blocks until a new stream can be opened.\n\t// If the error is non-nil, it satisfies the net.Error interface.\n\t// If the connection was closed due to a timeout, Timeout() will be true.\n\tOpenStreamSync(context.Context) (Stream, error)\n\t// OpenUniStream opens a new outgoing unidirectional QUIC stream.\n\t// If the error is non-nil, it satisfies the net.Error interface.\n\t// When reaching the peer's stream limit, Temporary() will be true.\n\t// If the connection was closed due to a timeout, Timeout() will be true.\n\tOpenUniStream() (SendStream, error)\n\t// OpenUniStreamSync opens a new outgoing unidirectional QUIC stream.\n\t// It blocks until a new stream can be opened.\n\t// If the error is non-nil, it satisfies the net.Error interface.\n\t// If the connection was closed due to a timeout, Timeout() will be true.\n\tOpenUniStreamSync(context.Context) (SendStream, error)\n}\n\ntype Stream interface {\n\tReceiveStream\n\tSendStream\n}\n\n// ReceiveStream is the interface for receiving data on a stream.\ntype ReceiveStream interface {\n\tStreamID() int64\n\tio.Reader\n\n\t// CancelRead aborts receiving on this stream.\n\t// It will ask the peer to stop transmitting stream data.\n\t// Read will unblock immediately, and future Read calls will fail.\n\t// When called multiple times or after reading the io.EOF it is a no-op.\n\tCancelRead(err ApplicationError)\n\n\t// SetReadDeadline sets the deadline for future Read calls and\n\t// any currently-blocked Read call.\n\t// A zero value for t means Read will not time out.\n\tSetReadDeadline(t time.Time) error\n}\n\n// SendStream is the interface for sending data on a stream.\ntype SendStream interface {\n\tStreamID() int64\n\t// Writer writes data to the stream.\n\t// Write can be made to time out and return a net.Error with Timeout() == true\n\t// after a fixed time limit; see SetDeadline and SetWriteDeadline.\n\t// If the stream was canceled by the peer, the error implements the StreamError\n\t// interface, and Canceled() == true.\n\t// If the connection was closed due to a timeout, the error satisfies\n\t// the net.Error interface, and Timeout() will be true.\n\tio.Writer\n\t// CancelWrite aborts sending on this stream.\n\t// Data already written, but not yet delivered to the peer is not guaranteed to be delivered reliably.\n\t// Write will unblock immediately, and future calls to Write will fail.\n\t// When called multiple times or after closing the stream it is a no-op.\n\tCancelWrite(err ApplicationError)\n\t// Closer closes the write-direction of the stream.\n\t// Future calls to Write are not permitted after calling Close.\n\t// It must not be called concurrently with Write.\n\t// It must not be called after calling CancelWrite.\n\tio.Closer\n\n\t// The Context is canceled as soon as the write-side of the stream is closed.\n\t// This happens when Close() or CancelWrite() is called, or when the peer\n\t// cancels the read-side of their stream.\n\tContext() context.Context\n\t// SetWriteDeadline sets the deadline for future Write calls\n\t// and any currently-blocked Write call.\n\t// Even if write times out, it may return n > 0, indicating that\n\t// some data was successfully written.\n\t// A zero value for t means Write will not time out.\n\tSetWriteDeadline(t time.Time) error\n}\n\ntype ApplicationError interface {\n\tErrCode() uint64\n\tfmt.Stringer\n}\n"
  },
  {
    "path": "pkg/network/dialer/dialer.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage dialer\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n)\n\n// will be netpoll.NewDialer() if available, see netpoll.go\nvar defaultDialer network.Dialer = standard.NewDialer()\n\n// SetDialer is used to set the global default dialer.\n// Deprecated: use WithDialer instead.\nfunc SetDialer(dialer network.Dialer) {\n\tdefaultDialer = dialer\n}\n\nfunc DefaultDialer() network.Dialer {\n\treturn defaultDialer\n}\n\nfunc DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {\n\treturn defaultDialer.DialConnection(network, address, timeout, tlsConfig)\n}\n\nfunc DialTimeout(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn net.Conn, err error) {\n\treturn defaultDialer.DialTimeout(network, address, timeout, tlsConfig)\n}\n\n// AddTLS is used to add tls to a persistent connection, i.e. negotiate a TLS session. If conn is already a TLS\n// tunnel, this function establishes a nested TLS session inside the encrypted channel.\nfunc AddTLS(conn network.Conn, tlsConfig *tls.Config) (network.Conn, error) {\n\treturn defaultDialer.AddTLS(conn, tlsConfig)\n}\n"
  },
  {
    "path": "pkg/network/dialer/dialer_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage dialer\n\nimport (\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\nfunc TestDialer(t *testing.T) {\n\tSetDialer(&mockDialer{})\n\tdialer := DefaultDialer()\n\tassert.DeepEqual(t, &mockDialer{}, dialer)\n\n\t_, err := AddTLS(nil, nil)\n\tassert.NotNil(t, err)\n\n\t_, err = DialConnection(\"\", \"\", 0, nil)\n\tassert.NotNil(t, err)\n\n\t_, err = DialTimeout(\"\", \"\", 0, nil)\n\tassert.NotNil(t, err)\n}\n\ntype mockDialer struct{}\n\nfunc (m *mockDialer) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {\n\treturn nil, errors.New(\"method not implement\")\n}\n\nfunc (m *mockDialer) DialTimeout(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn net.Conn, err error) {\n\treturn nil, errors.New(\"method not implement\")\n}\n\nfunc (m *mockDialer) AddTLS(conn network.Conn, tlsConfig *tls.Config) (network.Conn, error) {\n\treturn nil, errors.New(\"method not implement\")\n}\n"
  },
  {
    "path": "pkg/network/dialer/netpoll.go",
    "content": "//go:build (amd64 || arm64) && (linux || darwin)\n\n/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage dialer\n\nimport (\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/cloudwego/hertz/pkg/network/netpoll\"\n)\n\nfunc init() {\n\tif v, _ := strconv.ParseBool(os.Getenv(\"HERTZ_NO_NETPOLL\")); !v {\n\t\tdefaultDialer = netpoll.NewDialer()\n\t}\n}\n"
  },
  {
    "path": "pkg/network/dialer.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage network\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\t\"time\"\n)\n\ntype Dialer interface {\n\t// DialConnection is used to dial the peer end.\n\tDialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn Conn, err error)\n\n\t// DialTimeout is used to dial the peer end with a timeout.\n\t//\n\t// NOTE: Not recommended to use this function. Just for compatibility.\n\tDialTimeout(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn net.Conn, err error)\n\n\t// AddTLS will transfer a common connection to a tls connection.\n\tAddTLS(conn Conn, tlsConfig *tls.Config) (Conn, error)\n}\n"
  },
  {
    "path": "pkg/network/netpoll/connection.go",
    "content": "// Copyright 2022 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\npackage netpoll\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"syscall\"\n\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\ntype Conn struct {\n\tnetwork.Conn\n}\n\nfunc (c *Conn) ToHertzError(err error) error {\n\tif errors.Is(err, netpoll.ErrConnClosed) || errors.Is(err, syscall.EPIPE) {\n\t\treturn errs.ErrConnectionClosed\n\t}\n\n\t// only unify read timeout for now\n\tif errors.Is(err, netpoll.ErrReadTimeout) {\n\t\treturn errs.ErrTimeout\n\t}\n\treturn err\n}\n\nfunc (c *Conn) Peek(n int) (b []byte, err error) {\n\tb, err = c.Conn.Peek(n)\n\terr = normalizeErr(err)\n\treturn\n}\n\nfunc (c *Conn) Read(p []byte) (int, error) {\n\tn, err := c.Conn.Read(p)\n\terr = normalizeErr(err)\n\treturn n, err\n}\n\nfunc (c *Conn) Skip(n int) error {\n\treturn c.Conn.Skip(n)\n}\n\nfunc (c *Conn) Release() error {\n\treturn c.Conn.Release()\n}\n\nfunc (c *Conn) Len() int {\n\treturn c.Conn.Len()\n}\n\nfunc (c *Conn) ReadByte() (b byte, err error) {\n\tb, err = c.Conn.ReadByte()\n\terr = normalizeErr(err)\n\treturn\n}\n\nfunc (c *Conn) ReadBinary(n int) (b []byte, err error) {\n\tb, err = c.Conn.ReadBinary(n)\n\terr = normalizeErr(err)\n\treturn\n}\n\nfunc (c *Conn) Malloc(n int) (buf []byte, err error) {\n\treturn c.Conn.Malloc(n)\n}\n\nfunc (c *Conn) WriteBinary(b []byte) (n int, err error) {\n\treturn c.Conn.WriteBinary(b)\n}\n\nfunc (c *Conn) Flush() error {\n\treturn c.Conn.Flush()\n}\n\nfunc (c *Conn) HandleSpecificError(err error, rip string) (needIgnore bool) {\n\tif errors.Is(err, netpoll.ErrConnClosed) || errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) {\n\t\t// ignore flushing error when connection is closed or reset\n\t\tif strings.Contains(err.Error(), \"when flush\") {\n\t\t\treturn true\n\t\t}\n\t\thlog.SystemLogger().Debugf(\"Netpoll error=%s, remoteAddr=%s\", err.Error(), rip)\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc normalizeErr(err error) error {\n\tif errors.Is(err, netpoll.ErrEOF) {\n\t\treturn io.EOF\n\t}\n\n\treturn err\n}\n\nfunc newConn(c netpoll.Connection) network.Conn {\n\treturn &Conn{Conn: c.(network.Conn)}\n}\n"
  },
  {
    "path": "pkg/network/netpoll/connection_test.go",
    "content": "// Copyright 2022 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build !windows\n\npackage netpoll\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\nfunc TestReadBytes(t *testing.T) {\n\tc := &mockConn{[]byte(\"a\"), nil, 0}\n\tconn := newConn(c)\n\tassert.DeepEqual(t, 1, conn.Len())\n\n\tb, _ := conn.Peek(1)\n\tassert.DeepEqual(t, []byte{'a'}, b)\n\n\treadByte, _ := conn.ReadByte()\n\tassert.DeepEqual(t, byte('a'), readByte)\n\n\t_, err := conn.ReadByte()\n\tassert.DeepEqual(t, errors.New(\"readByte error: index out of range\"), err)\n\n\tc = &mockConn{[]byte(\"bcd\"), nil, 0}\n\tconn = newConn(c)\n\n\treadBinary, _ := conn.ReadBinary(2)\n\tassert.DeepEqual(t, []byte{'b', 'c'}, readBinary)\n\n\t_, err = conn.ReadBinary(2)\n\tassert.DeepEqual(t, errors.New(\"readBinary error: index out of range\"), err)\n}\n\nfunc TestPeekRelease(t *testing.T) {\n\tc := &mockConn{[]byte(\"abcdefg\"), nil, 0}\n\tconn := newConn(c)\n\n\t// release the buf\n\tconn.Release()\n\t_, err := conn.Peek(1)\n\tassert.DeepEqual(t, errors.New(\"peek error\"), err)\n\n\tassert.DeepEqual(t, errors.New(\"skip error\"), conn.Skip(2))\n}\n\nfunc TestWriteLogin(t *testing.T) {\n\tc := &mockConn{nil, []byte(\"abcdefg\"), 0}\n\tconn := newConn(c)\n\tbuf, _ := conn.Malloc(10)\n\tassert.DeepEqual(t, 10, len(buf))\n\tn, _ := conn.WriteBinary([]byte(\"abcdefg\"))\n\tassert.DeepEqual(t, 7, n)\n\tassert.DeepEqual(t, errors.New(\"flush error\"), conn.Flush())\n}\n\nfunc TestHandleSpecificError(t *testing.T) {\n\tconn := &Conn{}\n\tassert.DeepEqual(t, false, conn.HandleSpecificError(nil, \"\"))\n\tassert.DeepEqual(t, true, conn.HandleSpecificError(netpoll.ErrConnClosed, \"\"))\n}\n\ntype mockConn struct {\n\treadBuf  []byte\n\twriteBuf []byte\n\t// index for the first readable byte in readBuf\n\toff int\n}\n\nfunc (m *mockConn) SetWriteTimeout(timeout time.Duration) error {\n\t// TODO implement me\n\tpanic(\"implement me\")\n}\n\n// mockConn's methods is simplified for unit test\n// Peek returns the next n bytes without advancing the reader\nfunc (m *mockConn) Peek(n int) (b []byte, err error) {\n\tif m.off+n-1 < len(m.readBuf) {\n\t\treturn m.readBuf[m.off : m.off+n], nil\n\t}\n\treturn nil, errors.New(\"peek error\")\n}\n\n// Skip discards the next n bytes\nfunc (m *mockConn) Skip(n int) error {\n\tif m.off+n < len(m.readBuf) {\n\t\tm.off += n\n\t\treturn nil\n\t}\n\treturn errors.New(\"skip error\")\n}\n\n// Release the memory space occupied by all read slices\nfunc (m *mockConn) Release() error {\n\tm.readBuf = nil\n\tm.off = 0\n\treturn nil\n}\n\n// Len returns the total length of the readable data in the reader\nfunc (m *mockConn) Len() int {\n\treturn len(m.readBuf) - m.off\n}\n\n// ReadByte is used to read one byte with advancing the read pointer\nfunc (m *mockConn) ReadByte() (byte, error) {\n\tif m.off < len(m.readBuf) {\n\t\tm.off++\n\t\treturn m.readBuf[m.off-1], nil\n\t}\n\treturn 0, errors.New(\"readByte error: index out of range\")\n}\n\n// ReadBinary is used to read next n byte with copy, and the read pointer will be advanced\nfunc (m *mockConn) ReadBinary(n int) (b []byte, err error) {\n\tif m.off+n < len(m.readBuf) {\n\t\tm.off += n\n\t\treturn m.readBuf[m.off-n : m.off], nil\n\t}\n\treturn nil, errors.New(\"readBinary error: index out of range\")\n}\n\n// Malloc will provide a n bytes buffer to send data\nfunc (m *mockConn) Malloc(n int) (buf []byte, err error) {\n\tm.writeBuf = make([]byte, n)\n\treturn m.writeBuf, nil\n}\n\n// WriteBinary will use the user buffer to flush\nfunc (m *mockConn) WriteBinary(b []byte) (n int, err error) {\n\treturn len(b), nil\n}\n\n// Flush will send data to the peer end\nfunc (m *mockConn) Flush() error {\n\treturn errors.New(\"flush error\")\n}\n\nfunc (m *mockConn) HandleSpecificError(err error, rip string) (needIgnore bool) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Read(b []byte) (n int, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Write(b []byte) (n int, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Close() error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) LocalAddr() net.Addr {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) RemoteAddr() net.Addr {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) SetDeadline(deadline time.Time) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) SetReadDeadline(deadline time.Time) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) SetWriteDeadline(deadline time.Time) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Reader() netpoll.Reader {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Writer() netpoll.Writer {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) IsActive() bool {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) SetReadTimeout(timeout time.Duration) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) SetIdleTimeout(timeout time.Duration) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) SetOnRequest(on netpoll.OnRequest) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) AddCloseCallback(callback netpoll.CloseCallback) error {\n\tpanic(\"implement me\")\n}\n"
  },
  {
    "path": "pkg/network/netpoll/dial.go",
    "content": "// Copyright 2022 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\npackage netpoll\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\nvar errNotSupportTLS = errors.NewPublic(\"not support tls\")\n\ntype dialer struct {\n\tnetpoll.Dialer\n}\n\nfunc (d dialer) DialConnection(n, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {\n\tif tlsConfig != nil {\n\t\t// https\n\t\treturn nil, errNotSupportTLS\n\t}\n\tc, err := d.Dialer.DialConnection(n, address, timeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn = newConn(c)\n\treturn\n}\n\nfunc (d dialer) DialTimeout(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn net.Conn, err error) {\n\tif tlsConfig != nil {\n\t\treturn nil, errNotSupportTLS\n\t}\n\tconn, err = d.Dialer.DialTimeout(network, address, timeout)\n\treturn\n}\n\nfunc (d dialer) AddTLS(conn network.Conn, tlsConfig *tls.Config) (network.Conn, error) {\n\treturn nil, errNotSupportTLS\n}\n\nfunc NewDialer() network.Dialer {\n\treturn dialer{Dialer: netpoll.NewDialer()}\n}\n"
  },
  {
    "path": "pkg/network/netpoll/dial_test.go",
    "content": "// Copyright 2023 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build !windows\n\npackage netpoll\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n)\n\nfunc TestDial(t *testing.T) {\n\tt.Run(\"NetpollDial\", func(t *testing.T) {\n\t\tln := testutils.NewTestListener(t)\n\t\tdefer ln.Close()\n\n\t\ttransporter := NewTransporter(&config.Options{\n\t\t\tListener: ln,\n\t\t})\n\t\tgo transporter.ListenAndServe(func(ctx context.Context, conn interface{}) error {\n\t\t\treturn nil\n\t\t})\n\t\tdefer transporter.Close()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\tdial := NewDialer()\n\t\taddr := ln.Addr().String()\n\t\tnw := ln.Addr().Network()\n\n\t\t// DialConnection\n\t\t_, err := dial.DialConnection(nw, \"localhost:10101\", time.Second, nil) // wrong addr\n\t\tassert.NotNil(t, err)\n\t\tnwConn, err := dial.DialConnection(nw, addr, time.Second, nil)\n\t\tassert.Nil(t, err)\n\t\tdefer nwConn.Close()\n\t\t_, err = nwConn.Write([]byte(\"abcdef\"))\n\t\tassert.Nil(t, err)\n\t\t// DialTimeout\n\t\tnConn, err := dial.DialTimeout(\"tcp\", addr, time.Second, nil)\n\t\tassert.Nil(t, err)\n\t\tdefer nConn.Close()\n\t})\n\n\tt.Run(\"NotSupportTLS\", func(t *testing.T) {\n\t\tdial := NewDialer()\n\t\t_, err := dial.AddTLS(mock.NewConn(\"\"), nil)\n\t\tassert.DeepEqual(t, errNotSupportTLS, err)\n\t\t_, err = dial.DialConnection(\"tcp\", \"localhost:10102\", time.Microsecond, &tls.Config{})\n\t\tassert.DeepEqual(t, errNotSupportTLS, err)\n\t\t_, err = dial.DialTimeout(\"tcp\", \"localhost:10102\", time.Microsecond, &tls.Config{})\n\t\tassert.DeepEqual(t, errNotSupportTLS, err)\n\t})\n}\n"
  },
  {
    "path": "pkg/network/netpoll/transport.go",
    "content": "// Copyright 2022 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build !windows\n\npackage netpoll\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\nfunc init() {\n\t// disable netpoll's log\n\tnetpoll.SetLoggerOutput(io.Discard)\n}\n\ntype ctxCancelKeyStruct struct{}\n\nvar ctxCancelKey = ctxCancelKeyStruct{}\n\nfunc cancelContext(ctx context.Context) context.Context {\n\tctx, cancel := context.WithCancel(ctx)\n\tctx = context.WithValue(ctx, ctxCancelKey, cancel)\n\treturn ctx\n}\n\ntype transporter struct {\n\tsenseClientDisconnection bool\n\tnetwork                  string\n\taddr                     string\n\tkeepAliveTimeout         time.Duration\n\treadTimeout              time.Duration\n\twriteTimeout             time.Duration\n\tlistenConfig             *net.ListenConfig\n\tOnAccept                 func(conn net.Conn) context.Context\n\tOnConnect                func(ctx context.Context, conn network.Conn) context.Context\n\n\tmu sync.RWMutex\n\tln net.Listener\n\tel netpoll.EventLoop\n}\n\n// For transporter switch\nfunc NewTransporter(options *config.Options) network.Transporter {\n\treturn &transporter{\n\t\tsenseClientDisconnection: options.SenseClientDisconnection,\n\t\tnetwork:                  options.Network,\n\t\taddr:                     options.Addr,\n\t\tkeepAliveTimeout:         options.KeepAliveTimeout,\n\t\treadTimeout:              options.ReadTimeout,\n\t\twriteTimeout:             options.WriteTimeout,\n\t\tln:                       options.Listener,\n\t\tlistenConfig:             options.ListenConfig,\n\t\tOnAccept:                 options.OnAccept,\n\t\tOnConnect:                options.OnConnect,\n\t}\n}\n\nfunc (t *transporter) Listener() net.Listener {\n\tt.mu.RLock()\n\tdefer t.mu.RUnlock()\n\treturn t.ln\n}\n\n// ListenAndServe binds listen address and keep serving, until an error occurs\n// or the transport shutdowns\nfunc (t *transporter) ListenAndServe(onReq network.OnData) (err error) {\n\tnetwork.UnlinkUdsFile(t.network, t.addr) //nolint:errcheck\n\n\tt.mu.Lock()\n\tif t.ln == nil {\n\t\tif t.listenConfig != nil {\n\t\t\tt.ln, err = t.listenConfig.Listen(context.Background(), t.network, t.addr)\n\t\t} else {\n\t\t\tt.ln, err = net.Listen(t.network, t.addr)\n\t\t}\n\t\tif err != nil {\n\t\t\tt.mu.Unlock()\n\t\t\tpanic(\"create netpoll listener fail: \" + err.Error())\n\t\t}\n\t}\n\tln := t.ln\n\tt.mu.Unlock()\n\n\t// Initialize custom option for EventLoop\n\topts := []netpoll.Option{\n\t\tnetpoll.WithIdleTimeout(t.keepAliveTimeout),\n\t\tnetpoll.WithOnPrepare(func(conn netpoll.Connection) context.Context {\n\t\t\tconn.SetReadTimeout(t.readTimeout) // nolint:errcheck\n\t\t\tif t.writeTimeout > 0 {\n\t\t\t\tconn.SetWriteTimeout(t.writeTimeout)\n\t\t\t}\n\t\t\tctx := context.Background()\n\t\t\tif t.OnAccept != nil {\n\t\t\t\tctx = t.OnAccept(newConn(conn))\n\t\t\t}\n\t\t\tif t.senseClientDisconnection {\n\t\t\t\tctx = cancelContext(ctx)\n\t\t\t}\n\t\t\treturn ctx\n\t\t}),\n\t}\n\n\tif t.OnConnect != nil {\n\t\topts = append(opts, netpoll.WithOnConnect(func(ctx context.Context, conn netpoll.Connection) context.Context {\n\t\t\treturn t.OnConnect(ctx, newConn(conn))\n\t\t}))\n\t}\n\n\tif t.senseClientDisconnection {\n\t\topts = append(opts, netpoll.WithOnDisconnect(func(ctx context.Context, connection netpoll.Connection) {\n\t\t\tcancelFunc, ok := ctx.Value(ctxCancelKey).(context.CancelFunc)\n\t\t\tif cancelFunc != nil && ok {\n\t\t\t\tcancelFunc()\n\t\t\t}\n\t\t}))\n\t}\n\n\t// Create EventLoop\n\tt.mu.Lock()\n\tt.el, err = netpoll.NewEventLoop(func(ctx context.Context, connection netpoll.Connection) error {\n\t\treturn onReq(ctx, newConn(connection))\n\t}, opts...)\n\teventLoop := t.el\n\tt.mu.Unlock()\n\tif err != nil {\n\t\tpanic(\"create netpoll event-loop fail\")\n\t}\n\n\t// Start Server\n\thlog.SystemLogger().Infof(\"HTTP server listening on address=%s\", ln.Addr().String())\n\terr = eventLoop.Serve(ln)\n\tif err != nil {\n\t\tpanic(\"netpoll server exit\")\n\t}\n\n\treturn nil\n}\n\n// Close forces transport to close immediately (no wait timeout)\nfunc (t *transporter) Close() error {\n\tctx, cancel := context.WithTimeout(context.Background(), 0)\n\tdefer cancel()\n\treturn t.Shutdown(ctx)\n}\n\n// Shutdown will trigger listener stop and graceful shutdown\n// It will wait all connections close until reaching ctx.Deadline()\nfunc (t *transporter) Shutdown(ctx context.Context) error {\n\tdefer func() {\n\t\tnetwork.UnlinkUdsFile(t.network, t.addr) //nolint:errcheck\n\t\tt.mu.RUnlock()\n\t}()\n\tt.mu.RLock()\n\tif t.el == nil {\n\t\treturn nil\n\t}\n\treturn t.el.Shutdown(ctx)\n}\n"
  },
  {
    "path": "pkg/network/netpoll/transport_test.go",
    "content": "// Copyright 2023 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build !windows\n\npackage netpoll\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc TestTransport(t *testing.T) {\n\tt.Run(\"TestDefault\", func(t *testing.T) {\n\t\tln := testutils.NewTestListener(t)\n\t\tdefer ln.Close()\n\n\t\tvar onConnFlag, onAcceptFlag, onDataFlag int32\n\t\ttransporter := NewTransporter(&config.Options{\n\t\t\tListener: ln,\n\t\t\tOnConnect: func(ctx context.Context, conn network.Conn) context.Context {\n\t\t\t\tatomic.StoreInt32(&onConnFlag, 1)\n\t\t\t\treturn ctx\n\t\t\t},\n\t\t\tWriteTimeout: time.Second,\n\t\t\tOnAccept: func(conn net.Conn) context.Context {\n\t\t\t\tatomic.StoreInt32(&onAcceptFlag, 1)\n\t\t\t\treturn context.Background()\n\t\t\t},\n\t\t})\n\t\tgo transporter.ListenAndServe(func(ctx context.Context, conn interface{}) error {\n\t\t\tatomic.StoreInt32(&onDataFlag, 1)\n\t\t\treturn nil\n\t\t})\n\t\tdefer transporter.Close()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\taddr := ln.Addr().String()\n\t\tnw := ln.Addr().Network()\n\n\t\tdial := NewDialer()\n\t\tconn, err := dial.DialConnection(nw, addr, time.Second, nil)\n\t\tassert.Nil(t, err)\n\t\t_, err = conn.Write([]byte(\"123\"))\n\t\tassert.Nil(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\tassert.Assert(t, atomic.LoadInt32(&onConnFlag) == 1)\n\t\tassert.Assert(t, atomic.LoadInt32(&onAcceptFlag) == 1)\n\t\tassert.Assert(t, atomic.LoadInt32(&onDataFlag) == 1)\n\t})\n\n\tt.Run(\"TestSenseClientDisconnection\", func(t *testing.T) {\n\t\tln := testutils.NewTestListener(t)\n\t\tdefer ln.Close()\n\n\t\tvar onReqFlag int32\n\t\ttransporter := NewTransporter(&config.Options{\n\t\t\tListener:                 ln,\n\t\t\tSenseClientDisconnection: true,\n\t\t})\n\n\t\tgo transporter.ListenAndServe(func(ctx context.Context, conn interface{}) error {\n\t\t\tatomic.StoreInt32(&onReqFlag, 1)\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\tassert.DeepEqual(t, context.Canceled, ctx.Err())\n\t\t\treturn nil\n\t\t})\n\t\tdefer transporter.Close()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\taddr := ln.Addr().String()\n\t\tnw := ln.Addr().Network()\n\n\t\tdial := NewDialer()\n\t\tconn, err := dial.DialConnection(nw, addr, time.Second, nil)\n\t\tassert.Nil(t, err)\n\t\t_, err = conn.Write([]byte(\"123\"))\n\t\tassert.Nil(t, err)\n\t\terr = conn.Close()\n\t\tassert.Nil(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\tassert.Assert(t, atomic.LoadInt32(&onReqFlag) == 1)\n\t})\n\n\tt.Run(\"TestListenConfig\", func(t *testing.T) {\n\t\tlistenCfg := &net.ListenConfig{Control: func(network, address string, c syscall.RawConn) error {\n\t\t\treturn c.Control(func(fd uintptr) {\n\t\t\t\tsyscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEADDR, 1)\n\t\t\t\tsyscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)\n\t\t\t})\n\t\t}}\n\t\ttransporter := NewTransporter(&config.Options{\n\t\t\tNetwork:      \"tcp\",\n\t\t\tAddr:         \"127.0.0.1:0\",\n\t\t\tListenConfig: listenCfg,\n\t\t})\n\t\tgo transporter.ListenAndServe(func(ctx context.Context, conn interface{}) error {\n\t\t\treturn nil\n\t\t})\n\t\tdefer transporter.Close()\n\t})\n\n\tt.Run(\"TestExceptionCase\", func(t *testing.T) {\n\t\tassert.Panic(t, func() { // listen err\n\t\t\ttransporter := NewTransporter(&config.Options{\n\t\t\t\tNetwork: \"unknown\",\n\t\t\t})\n\t\t\ttransporter.ListenAndServe(func(ctx context.Context, conn interface{}) error {\n\t\t\t\treturn nil\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"TestWithListener\", func(t *testing.T) {\n\t\tln := testutils.NewTestListener(t)\n\t\tdefer ln.Close()\n\n\t\tvar onDataFlag int32\n\t\ttrans := NewTransporter(&config.Options{\n\t\t\tListener: ln,\n\t\t}).(*transporter)\n\t\tgo trans.ListenAndServe(func(ctx context.Context, conn interface{}) error {\n\t\t\tatomic.StoreInt32(&onDataFlag, 1)\n\t\t\treturn nil\n\t\t})\n\t\tdefer trans.Close()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\t// Verify listener is used\n\t\tassert.DeepEqual(t, ln.Addr().String(), trans.Listener().Addr().String())\n\n\t\tnw := ln.Addr().Network()\n\n\t\t// Connect and send data\n\t\tdial := NewDialer()\n\t\tconn, err := dial.DialConnection(nw, ln.Addr().String(), time.Second, nil)\n\t\tassert.Nil(t, err)\n\t\t_, err = conn.Write([]byte(\"test\"))\n\t\tassert.Nil(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\tassert.Assert(t, atomic.LoadInt32(&onDataFlag) == 1)\n\t})\n}\n"
  },
  {
    "path": "pkg/network/standard/connection.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage standard\n\nimport (\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/cloudwego/gopkg/bufiox\"\n\t\"github.com/cloudwego/gopkg/net/connstate\"\n\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\ntype Conn struct {\n\tc      net.Conn\n\tbr     *bufiox.DefaultReader\n\tbw     *bufiox.DefaultWriter\n\tstater connstate.ConnStater\n\n\tbuf [8]byte\n}\n\nfunc (c *Conn) ToHertzError(err error) error {\n\tif errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ENOTCONN) {\n\t\treturn errs.ErrConnectionClosed\n\t}\n\tif netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {\n\t\treturn errs.ErrTimeout\n\t}\n\n\treturn err\n}\n\nfunc (c *Conn) SetWriteTimeout(t time.Duration) error {\n\tif t <= 0 {\n\t\treturn c.c.SetWriteDeadline(time.Time{})\n\t}\n\treturn c.c.SetWriteDeadline(time.Now().Add(t))\n}\n\nfunc (c *Conn) SetReadTimeout(t time.Duration) error {\n\tif t <= 0 {\n\t\treturn c.c.SetReadDeadline(time.Time{})\n\t}\n\treturn c.c.SetReadDeadline(time.Now().Add(t))\n}\n\ntype TLSConn struct {\n\tConn\n}\n\n// Peek returns the next n bytes without advancing the reader. If Peek returns\n// fewer than n bytes, it also returns an error explaining why the read is short.\nfunc (c *Conn) Peek(n int) ([]byte, error) {\n\tbuf, err := c.br.Peek(n)\n\t// bufiox readAtLeast converts partial-read+EOF to ErrUnexpectedEOF,\n\t// but hertz protocol code expects the original io.EOF.\n\tif err == io.ErrUnexpectedEOF {\n\t\terr = io.EOF\n\t}\n\treturn buf, err\n}\n\n// Skip discards the next n bytes.\nfunc (c *Conn) Skip(n int) error {\n\tif c.Len() < n {\n\t\treturn errs.NewPrivate(\"skip[\" + strconv.Itoa(n) + \"] not enough\")\n\t}\n\treturn c.br.Skip(n)\n}\n\n// Release frees internal read buffers.\nfunc (c *Conn) Release() error {\n\treturn c.br.Release(nil)\n}\n\n// Len returns the total length of the readable data in the reader.\nfunc (c *Conn) Len() int {\n\treturn c.br.Buffered()\n}\n\n// ReadByte is used to read one byte with advancing the read pointer.\nfunc (c *Conn) ReadByte() (b byte, err error) {\n\t// Use Read instead of Peek+Skip to avoid holding a ref to the underlying buffer.\n\t_, err = c.br.Read(c.buf[:1])\n\tif err == nil {\n\t\tb = c.buf[0]\n\t}\n\treturn\n}\n\n// ReadBinary is used to read next n byte with copy, and the read pointer will be advanced.\nfunc (c *Conn) ReadBinary(n int) ([]byte, error) {\n\tout := make([]byte, n)\n\t_, err := c.br.ReadBinary(out)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// Read implements io.Reader.\nfunc (c *Conn) Read(b []byte) (int, error) {\n\treturn c.br.Read(b)\n}\n\n// Write calls Write syscall directly to send data.\n// Will flush buffer immediately, for performance considerations use WriteBinary instead.\nfunc (c *Conn) Write(b []byte) (n int, err error) {\n\tif err = c.Flush(); err != nil {\n\t\treturn\n\t}\n\treturn c.c.Write(b)\n}\n\n// ReadFrom implements io.ReaderFrom. If the underlying writer\n// supports the ReadFrom method, this calls the underlying ReadFrom\n// without buffering.\nfunc (c *Conn) ReadFrom(r io.Reader) (n int64, err error) {\n\tif err = c.Flush(); err != nil {\n\t\treturn\n\t}\n\n\tif w, ok := c.c.(io.ReaderFrom); ok {\n\t\tn, err = w.ReadFrom(r)\n\t\treturn\n\t}\n\n\tvar buf [32 * 1024]byte\n\tfor {\n\t\tm, rerr := r.Read(buf[:])\n\t\tif m > 0 {\n\t\t\tdst, werr := c.bw.Malloc(m)\n\t\t\tif werr != nil {\n\t\t\t\treturn n, werr\n\t\t\t}\n\t\t\tcopy(dst, buf[:m])\n\t\t\tn += int64(m)\n\t\t}\n\t\tif rerr != nil {\n\t\t\tif rerr != io.EOF {\n\t\t\t\terr = rerr\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Close closes the connection\nfunc (c *Conn) Close() error {\n\t// Close stater first to stop epoll monitoring\n\tif c.stater != nil {\n\t\tc.stater.Close()\n\t\tc.stater = nil\n\t}\n\treturn c.c.Close()\n}\n\n// LocalAddr returns the local address of the connection.\nfunc (c *Conn) LocalAddr() net.Addr {\n\treturn c.c.LocalAddr()\n}\n\n// RemoteAddr returns the remote address of the connection.\nfunc (c *Conn) RemoteAddr() net.Addr {\n\treturn c.c.RemoteAddr()\n}\n\n// SetDeadline sets the connection deadline.\nfunc (c *Conn) SetDeadline(t time.Time) error {\n\treturn c.c.SetDeadline(t)\n}\n\n// SetReadDeadline sets the read deadline of the connection.\nfunc (c *Conn) SetReadDeadline(t time.Time) error {\n\treturn c.c.SetReadDeadline(t)\n}\n\n// SetWriteDeadline sets the write deadline of the connection.\nfunc (c *Conn) SetWriteDeadline(t time.Time) error {\n\treturn c.c.SetWriteDeadline(t)\n}\n\n// Malloc will provide a n bytes buffer to send data.\nfunc (c *Conn) Malloc(n int) ([]byte, error) {\n\treturn c.bw.Malloc(n)\n}\n\n// WriteBinary will use the user buffer to flush.\n// NOTE: Before flush successfully, the buffer b should be valid.\nfunc (c *Conn) WriteBinary(b []byte) (int, error) {\n\treturn c.bw.WriteBinary(b)\n}\n\n// Flush will send data to the peer end.\nfunc (c *Conn) Flush() error {\n\treturn c.bw.Flush()\n}\n\nfunc (c *Conn) HandleSpecificError(err error, rip string) (needIgnore bool) {\n\tif errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) {\n\t\thlog.SystemLogger().Debugf(\"Go net library error=%s, remoteAddr=%s\", err.Error(), rip)\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (c *TLSConn) Handshake() error {\n\treturn c.c.(network.ConnTLSer).Handshake()\n}\n\nfunc (c *TLSConn) ConnectionState() tls.ConnectionState {\n\treturn c.c.(network.ConnTLSer).ConnectionState()\n}\n\nfunc newConn(c net.Conn, size int) network.Conn {\n\treturn &Conn{\n\t\tc:  c,\n\t\tbr: bufiox.NewDefaultReaderSize(c, size),\n\t\tbw: bufiox.NewDefaultWriter(c),\n\t}\n}\n\nfunc newTLSConn(c net.Conn, size int) network.Conn {\n\treturn &TLSConn{\n\t\tConn{\n\t\t\tc:  c,\n\t\t\tbr: bufiox.NewDefaultReaderSize(c, size),\n\t\t\tbw: bufiox.NewDefaultWriter(c),\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/network/standard/connection_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage standard\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\n// --- helpers ---\n\nfunc mkConn(data []byte) (*bufConn, *Conn) {\n\tc := &bufConn{r: bytes.NewReader(data), w: &bytes.Buffer{}}\n\treturn c, newConn(c, 4096).(*Conn)\n}\n\ntype bufConn struct {\n\tmockConn\n\tr io.Reader\n\tw *bytes.Buffer\n}\n\nfunc (c *bufConn) Read(b []byte) (int, error)  { return c.r.Read(b) }\nfunc (c *bufConn) Write(b []byte) (int, error) { return c.w.Write(b) }\n\ntype mockConn struct {\n\tbuffer     bytes.Buffer\n\tlocalAddr  net.Addr\n\tremoteAddr net.Addr\n}\n\nfunc (m *mockConn) Handshake() error                     { return errors.New(\"not supported\") }\nfunc (m *mockConn) ConnectionState() tls.ConnectionState { return tls.ConnectionState{} }\nfunc (m mockConn) Read(b []byte) (int, error) {\n\tfor i := range b {\n\t\tb[i] = 0\n\t}\n\tn := len(b)\n\tif n > 8192 {\n\t\tn = 8192\n\t}\n\treturn n, nil\n}\nfunc (m *mockConn) Write(b []byte) (int, error)     { return m.buffer.Write(b) }\nfunc (m *mockConn) Close() error                    { return errors.New(\"not supported\") }\nfunc (m *mockConn) LocalAddr() net.Addr             { return m.localAddr }\nfunc (m *mockConn) RemoteAddr() net.Addr            { return m.remoteAddr }\nfunc (m *mockConn) SetDeadline(t time.Time) error   { return m.SetWriteDeadline(t) }\nfunc (m *mockConn) SetReadDeadline(time.Time) error { return errors.New(\"read deadline not supported\") }\nfunc (m *mockConn) SetWriteDeadline(time.Time) error {\n\treturn errors.New(\"write deadline not supported\")\n}\n\ntype errReader struct {\n\tdata []byte\n\terr  error\n\tdone bool\n}\n\nfunc (r *errReader) Read(b []byte) (int, error) {\n\tif r.done {\n\t\treturn 0, r.err\n\t}\n\tr.done = true\n\treturn copy(b, r.data), nil\n}\n\ntype readerFromConn struct {\n\tmockConn\n}\n\nfunc (c *readerFromConn) ReadFrom(r io.Reader) (int64, error) {\n\treturn io.Copy(&c.buffer, r)\n}\n\n// --- tests ---\n\nfunc TestRead(t *testing.T) {\n\tdata := bytes.Repeat([]byte{1}, 10000)\n\t_, conn := mkConn(data)\n\n\tb := make([]byte, 5)\n\tn, err := conn.Read(b)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, 5, n)\n\n\tb = make([]byte, 20000)\n\tn, err = conn.Read(b)\n\tassert.Nil(t, err)\n\tassert.True(t, n > 0)\n}\n\nfunc TestPeekSkipRelease(t *testing.T) {\n\tdata := bytes.Repeat([]byte{0}, 10000)\n\t_, conn := mkConn(data)\n\n\tb, err := conn.Peek(100)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, 100, len(b))\n\n\t// skip more than buffered fails\n\tassert.NotNil(t, conn.Skip(conn.Len()+1))\n\n\t// skip all succeeds\n\tassert.Nil(t, conn.Skip(conn.Len()))\n\tassert.DeepEqual(t, 0, conn.Len())\n\n\t// release then peek refills\n\tconn.Release()\n\tb, err = conn.Peek(1)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, 1, len(b))\n}\n\nfunc TestReadByteAndReadBinary(t *testing.T) {\n\t_, conn := mkConn([]byte(\"abcdef\"))\n\n\trb, err := conn.ReadBinary(3)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, []byte(\"abc\"), rb)\n\n\tby, err := conn.ReadByte()\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, byte('d'), by)\n}\n\nfunc TestWriteAndFlush(t *testing.T) {\n\tc := &mockConn{}\n\tconn := newConn(c, 4096).(*Conn)\n\n\tbuf, _ := conn.Malloc(5)\n\tcopy(buf, []byte(\"hello\"))\n\tconn.WriteBinary([]byte(\" world\"))\n\tassert.Nil(t, conn.Flush())\n\tassert.DeepEqual(t, \"hello world\", c.buffer.String())\n}\n\nfunc TestReadFrom(t *testing.T) {\n\tt.Run(\"fallback loop\", func(t *testing.T) {\n\t\tc := &mockConn{}\n\t\tconn := newConn(c, 4096)\n\t\tn, err := conn.(io.ReaderFrom).ReadFrom(strings.NewReader(\"hello\"))\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, int64(5), n)\n\t\tconn.Flush()\n\t\tassert.DeepEqual(t, \"hello\", c.buffer.String())\n\t})\n\n\tt.Run(\"reader error\", func(t *testing.T) {\n\t\treadErr := errors.New(\"disk failure\")\n\t\tc := &mockConn{}\n\t\tconn := newConn(c, 4096)\n\t\tn, err := conn.(io.ReaderFrom).ReadFrom(&errReader{data: []byte(\"partial\"), err: readErr})\n\t\tassert.DeepEqual(t, readErr, err)\n\t\tassert.DeepEqual(t, int64(7), n)\n\t})\n\n\tt.Run(\"underlying ReaderFrom\", func(t *testing.T) {\n\t\tc := &readerFromConn{}\n\t\tconn := newConn(c, 4096)\n\t\tn, err := conn.(io.ReaderFrom).ReadFrom(strings.NewReader(\"hello\"))\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, int64(5), n)\n\t\tassert.DeepEqual(t, \"hello\", c.buffer.String())\n\t})\n}\n\nfunc TestPeekPartialEOF(t *testing.T) {\n\t_, conn := mkConn(make([]byte, 100))\n\n\tb, err := conn.Peek(100)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, 100, len(b))\n\n\tconn.Skip(10)\n\n\tb, err = conn.Peek(100)\n\tassert.DeepEqual(t, io.EOF, err)\n\tassert.DeepEqual(t, 90, len(b))\n}\n\nfunc TestConnAddrs(t *testing.T) {\n\tlocal := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 80}\n\tremote := &net.TCPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 9000}\n\tc := &mockConn{localAddr: local, remoteAddr: remote}\n\tconn := newConn(c, 4096)\n\tassert.DeepEqual(t, local, conn.LocalAddr())\n\tassert.DeepEqual(t, remote, conn.RemoteAddr())\n}\n\nfunc TestSetDeadlines(t *testing.T) {\n\tc := &mockConn{}\n\tconn := newConn(c, 4096)\n\tassert.NotNil(t, conn.SetDeadline(time.Time{}))\n\tassert.NotNil(t, conn.SetReadDeadline(time.Time{}))\n\tassert.NotNil(t, conn.SetWriteDeadline(time.Time{}))\n}\n\nfunc TestSetTimeouts(t *testing.T) {\n\tc := &mockConn{}\n\tconn := newConn(c, 4096)\n\tassert.NotNil(t, conn.SetReadTimeout(time.Second))\n\tassert.NotNil(t, conn.SetReadTimeout(-1))\n\tassert.NotNil(t, conn.SetWriteTimeout(time.Second))\n\tassert.NotNil(t, conn.SetWriteTimeout(-1))\n}\n\nfunc TestToHertzError(t *testing.T) {\n\tconn := &Conn{}\n\tother := errors.New(\"other\")\n\n\tassert.DeepEqual(t, errs.ErrConnectionClosed, conn.ToHertzError(syscall.EPIPE))\n\tassert.DeepEqual(t, errs.ErrConnectionClosed, conn.ToHertzError(syscall.ENOTCONN))\n\tassert.DeepEqual(t, errs.ErrTimeout, conn.ToHertzError(&net.OpError{Op: \"read\", Err: &timeoutErr{}}))\n\tassert.DeepEqual(t, other, conn.ToHertzError(other))\n}\n\nfunc TestHandleSpecificError(t *testing.T) {\n\tconn := &Conn{}\n\tassert.DeepEqual(t, false, conn.HandleSpecificError(nil, \"\"))\n\tassert.DeepEqual(t, true, conn.HandleSpecificError(syscall.EPIPE, \"\"))\n}\n\nfunc TestTLSConn(t *testing.T) {\n\tc := &mockConn{}\n\ttc := newTLSConn(c, 4096).(*TLSConn)\n\tassert.NotNil(t, tc.Handshake())\n\tassert.DeepEqual(t, tls.ConnectionState{}, tc.ConnectionState())\n}\n\ntype mockAddr struct {\n\tnetwork string\n\taddress string\n}\n\nfunc (m *mockAddr) Network() string { return m.network }\nfunc (m *mockAddr) String() string  { return m.address }\n\ntype timeoutErr struct{}\n\nfunc (e *timeoutErr) Error() string   { return \"timeout\" }\nfunc (e *timeoutErr) Timeout() bool   { return true }\nfunc (e *timeoutErr) Temporary() bool { return true }\n"
  },
  {
    "path": "pkg/network/standard/dial.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage standard\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\ntype dialer struct{}\n\nfunc (d *dialer) DialConnection(n, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {\n\tc, err := net.DialTimeout(n, address, timeout)\n\tif tlsConfig != nil {\n\t\tcTLS := tls.Client(c, tlsConfig)\n\t\tconn = newTLSConn(cTLS, 0)\n\t\treturn\n\t}\n\tconn = newConn(c, 0)\n\treturn\n}\n\nfunc (d *dialer) DialTimeout(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn net.Conn, err error) {\n\tconn, err = net.DialTimeout(network, address, timeout)\n\treturn\n}\n\nfunc (d *dialer) AddTLS(conn network.Conn, tlsConfig *tls.Config) (network.Conn, error) {\n\tcTlS := tls.Client(conn, tlsConfig)\n\terr := cTlS.Handshake()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn = newTLSConn(cTlS, 0)\n\treturn conn, nil\n}\n\nfunc NewDialer() network.Dialer {\n\treturn &dialer{}\n}\n"
  },
  {
    "path": "pkg/network/standard/dial_test.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage standard\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"math/big\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestDial(t *testing.T) {\n\tconst nw = \"tcp\"\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\ttransporter := NewTransporter(&config.Options{\n\t\tListener: ln,\n\t\tNetwork:  nw,\n\t})\n\n\tgo transporter.ListenAndServe(func(ctx context.Context, conn interface{}) error {\n\t\treturn nil\n\t})\n\tdefer transporter.Close()\n\ttime.Sleep(time.Millisecond * 100)\n\n\taddr := ln.Addr().String()\n\n\tdial := NewDialer()\n\t_, err := dial.DialConnection(nw, addr, time.Second, nil)\n\tassert.Nil(t, err)\n\n\tnConn, err := dial.DialTimeout(nw, addr, time.Second, nil)\n\tassert.Nil(t, err)\n\tdefer nConn.Close()\n}\n\nfunc TestDialTLS(t *testing.T) {\n\tconst nw = \"tcp\"\n\taddr := \"127.0.0.1:0\"\n\tdata := []byte(\"abcdefg\")\n\tlistened := make(chan net.Listener)\n\tgo func() {\n\t\tmockTLSServe(nw, addr, func(conn net.Conn) {\n\t\t\tdefer conn.Close()\n\t\t\t_, err := conn.Write(data)\n\t\t\tassert.Nil(t, err)\n\t\t}, listened)\n\t}()\n\n\tselect {\n\tcase ln := <-listened:\n\t\taddr = ln.Addr().String()\n\tcase <-time.After(time.Second * 5):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tbuf := make([]byte, len(data))\n\n\tdial := NewDialer()\n\tconn, err := dial.DialConnection(nw, addr, time.Second, &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t})\n\tassert.Nil(t, err)\n\n\t_, err = conn.Read(buf)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, string(data), string(buf))\n\n\tconn, err = dial.DialConnection(nw, addr, time.Second, nil)\n\tassert.Nil(t, err)\n\tnConn, err := dial.AddTLS(conn, &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t})\n\tassert.Nil(t, err)\n\n\t_, err = nConn.Read(buf)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, string(data), string(buf))\n}\n\nfunc mockTLSServe(nw, addr string, handle func(conn net.Conn), listened chan net.Listener) (err error) {\n\tcertData, keyData, err := generateTestCertificate(\"\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcert, err := tls.X509KeyPair(certData, keyData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttlsConfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{cert},\n\t}\n\tln, err := tls.Listen(nw, addr, tlsConfig)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer ln.Close()\n\n\tlistened <- ln\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tgo handle(conn)\n\t}\n}\n\n// generateTestCertificate generates a test certificate and private key based on the given host.\nfunc generateTestCertificate(host string) ([]byte, []byte, error) {\n\tpriv, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)\n\tserialNumber, err := rand.Int(rand.Reader, serialNumberLimit)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tcert := &x509.Certificate{\n\t\tSerialNumber: serialNumber,\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"fasthttp test\"},\n\t\t},\n\t\tNotBefore:             time.Now(),\n\t\tNotAfter:              time.Now().Add(365 * 24 * time.Hour),\n\t\tKeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},\n\t\tSignatureAlgorithm:    x509.SHA256WithRSA,\n\t\tDNSNames:              []string{host},\n\t\tBasicConstraintsValid: true,\n\t\tIsCA:                  true,\n\t}\n\n\tcertBytes, err := x509.CreateCertificate(\n\t\trand.Reader, cert, cert, &priv.PublicKey, priv,\n\t)\n\n\tp := pem.EncodeToMemory(\n\t\t&pem.Block{\n\t\t\tType:  \"PRIVATE KEY\",\n\t\t\tBytes: x509.MarshalPKCS1PrivateKey(priv),\n\t\t},\n\t)\n\n\tb := pem.EncodeToMemory(\n\t\t&pem.Block{\n\t\t\tType:  \"CERTIFICATE\",\n\t\t\tBytes: certBytes,\n\t\t},\n\t)\n\n\treturn b, p, err\n}\n"
  },
  {
    "path": "pkg/network/standard/transport.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage standard\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/cloudwego/gopkg/net/connstate\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\ntype transport struct {\n\t// The underlying read buffer is a buffer node list, `readBufferSize` is the size of a single node.\n\t// `defaultReadBufferSize` (4KB) is used if not set.\n\treadBufferSize           int\n\tnetwork                  string\n\taddr                     string\n\tkeepAliveTimeout         time.Duration\n\tsenseClientDisconnection bool\n\treadTimeout              time.Duration\n\thandler                  network.OnData\n\ttls                      *tls.Config\n\tlistenConfig             *net.ListenConfig\n\tOnAccept                 func(conn net.Conn) context.Context\n\tOnConnect                func(ctx context.Context, conn network.Conn) context.Context\n\n\t// active connections. it +1 after accept and -1 after handler returns\n\tactive       int32\n\tshuttingDown int32\n\n\tmu sync.RWMutex\n\tln net.Listener\n}\n\nfunc (t *transport) Listener() net.Listener {\n\tt.mu.RLock()\n\tdefer t.mu.RUnlock()\n\treturn t.ln\n}\n\nfunc (t *transport) serve() (err error) {\n\tnetwork.UnlinkUdsFile(t.network, t.addr) //nolint:errcheck\n\tt.mu.Lock()\n\tif t.ln == nil {\n\t\tif t.listenConfig != nil {\n\t\t\tt.ln, err = t.listenConfig.Listen(context.Background(), t.network, t.addr)\n\t\t} else {\n\t\t\tt.ln, err = net.Listen(t.network, t.addr)\n\t\t}\n\t\tif err != nil {\n\t\t\tt.mu.Unlock()\n\t\t\treturn err\n\t\t}\n\t}\n\t// fix concurrency issue\n\t// normally listener must not be changed during serve()\n\tln := t.ln\n\tt.mu.Unlock()\n\thlog.SystemLogger().Infof(\"HTTP server listening on address=%s\", ln.Addr().String())\n\tfor {\n\t\tctx := context.Background()\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tif atomic.LoadInt32(&t.shuttingDown) > 0 {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif strings.Contains(err.Error(), \"closed\") {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\thlog.SystemLogger().Errorf(\"Accept err: %v\", err)\n\t\t\treturn err\n\t\t}\n\t\tt.updateActive(1)\n\n\t\tif t.OnAccept != nil {\n\t\t\tctx = t.OnAccept(conn)\n\t\t}\n\n\t\tvar c network.Conn\n\t\tif t.tls != nil {\n\t\t\tc = newTLSConn(tls.Server(conn, t.tls), t.readBufferSize)\n\t\t} else {\n\t\t\tc = newConn(conn, t.readBufferSize)\n\t\t}\n\n\t\tif t.OnConnect != nil {\n\t\t\tctx = t.OnConnect(ctx, c)\n\t\t}\n\n\t\tgo func(ctx context.Context, conn network.Conn) {\n\t\t\tif t.senseClientDisconnection {\n\t\t\t\t// Get the underlying net.Conn for connstate registration\n\t\t\t\tvar rawConn net.Conn\n\t\t\t\tvar stdConn *Conn\n\t\t\t\tswitch v := conn.(type) {\n\t\t\t\tcase *Conn:\n\t\t\t\t\tstdConn = v\n\t\t\t\t\trawConn = v.c\n\t\t\t\tcase *TLSConn:\n\t\t\t\t\t// TLSConn embeds Conn\n\t\t\t\t\tstdConn = &v.Conn\n\t\t\t\t\trawConn = v.c\n\t\t\t\tdefault:\n\t\t\t\t\t// Other connection types are not supported\n\t\t\t\t\tt.handler(ctx, conn)\n\t\t\t\t\tt.updateActive(-1)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Register connection close callback\n\t\t\t\tvar cancelCtx context.CancelFunc\n\t\t\t\tctx, cancelCtx = context.WithCancel(ctx)\n\t\t\t\tstater, err := connstate.ListenConnState(rawConn,\n\t\t\t\t\tconnstate.WithOnRemoteClosed(connstate.OnRemoteClosed(cancelCtx)),\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\thlog.SystemLogger().Errorf(\"ListenConnState failed: %v, connection close detection disabled\", err)\n\t\t\t\t} else {\n\t\t\t\t\t// Set stater to Conn, it will be cleaned up when Close is called\n\t\t\t\t\tstdConn.stater = stater\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tt.handler(ctx, conn)\n\t\t\tt.updateActive(-1)\n\t\t}(ctx, c)\n\t}\n}\n\nfunc (t *transport) updateActive(delta int32) int32 {\n\treturn atomic.AddInt32(&t.active, delta)\n}\n\nfunc (t *transport) ListenAndServe(onData network.OnData) (err error) {\n\tt.handler = onData\n\treturn t.serve()\n}\n\nfunc (t *transport) Close() error {\n\tctx, cancel := context.WithTimeout(context.Background(), 0)\n\tdefer cancel()\n\treturn t.Shutdown(ctx)\n}\n\nvar (\n\tshutdownTimeout = 30 * time.Second\n\tshutdownTicker  = 10 * time.Millisecond\n\n\terrShutdownTimeout = errors.New(\"shutdown timeout\")\n)\n\nfunc (t *transport) Shutdown(ctx context.Context) error {\n\tatomic.StoreInt32(&t.shuttingDown, 1)\n\n\tdefer func() {\n\t\tnetwork.UnlinkUdsFile(t.network, t.addr) //nolint:errcheck\n\t}()\n\tif ln := t.Listener(); ln != nil {\n\t\t_ = ln.Close()\n\t}\n\n\ttk := time.NewTicker(shutdownTicker)\n\tdefer tk.Stop()\n\n\t// make sure t.active is updated correctly under concurrency\n\t<-tk.C\n\n\t// luckily the server is idle, no more active connections\n\tif t.updateActive(0) <= 0 {\n\t\treturn nil\n\t}\n\n\t// check periodically to see if all connections closed\n\tt0 := time.Now()\n\tfor {\n\t\tselect {\n\t\tcase now := <-tk.C:\n\t\t\tif t.updateActive(0) <= 0 {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif now.Sub(t0) > shutdownTimeout {\n\t\t\t\treturn errShutdownTimeout\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\t}\n\t}\n}\n\n// For transporter switch\nfunc NewTransporter(options *config.Options) network.Transporter {\n\treturn &transport{\n\t\treadBufferSize:           options.ReadBufferSize,\n\t\tnetwork:                  options.Network,\n\t\taddr:                     options.Addr,\n\t\tkeepAliveTimeout:         options.KeepAliveTimeout,\n\t\treadTimeout:              options.ReadTimeout,\n\t\tsenseClientDisconnection: options.SenseClientDisconnection,\n\t\ttls:                      options.TLS,\n\t\tln:                       options.Listener,\n\t\tlistenConfig:             options.ListenConfig,\n\t\tOnAccept:                 options.OnAccept,\n\t\tOnConnect:                options.OnConnect,\n\t}\n}\n"
  },
  {
    "path": "pkg/network/standard/transport_test.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage standard\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\nfunc assertWriteRead(t *testing.T, c io.ReadWriter, w, r string) {\n\tt.Helper()\n\t_, err := c.Write([]byte(w))\n\tif err != nil {\n\t\tt.Fatal(\"write err\", err)\n\t}\n\tb := make([]byte, len(r))\n\t_, err = io.ReadFull(c, b)\n\tif err != nil {\n\t\tt.Fatal(\"read err\", err)\n\t}\n\tif s := string(b); s != r {\n\t\tt.Fatal(\"read\", r)\n\t}\n}\n\nfunc TestTransporter(t *testing.T) {\n\thandlerExit := make(chan struct{})\n\treq := \"hello\"\n\tresp := \"world\"\n\ttrans := NewTransporter(&config.Options{Network: \"tcp\", Addr: \"127.0.0.1:0\", SenseClientDisconnection: true}).(*transport)\n\tgo trans.ListenAndServe(func(ctx context.Context, conn interface{}) error {\n\t\t// SenseClientDisconnection is configured, connection close detection is handled by connstate\n\t\tc := conn.(network.Conn)\n\t\tdefer c.Close()\n\t\tassertWriteRead(t, c, resp, req)\n\t\t<-handlerExit\n\t\treturn nil\n\t})\n\tfor trans.Listener() == nil { // wait server up\n\t\ttime.Sleep(5 * time.Millisecond)\n\t}\n\n\t// dial and test\n\tc, err := net.Dial(\"tcp\", trans.Listener().Addr().String())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassertWriteRead(t, c, req, resp)\n\n\tcheckActiveConn := func(n int) {\n\t\tif v := trans.updateActive(0); v != int32(n) {\n\t\t\tt.Helper()\n\t\t\tt.Fatal(\"trans active conn\", v)\n\t\t}\n\t}\n\tcheckActiveConn(1)\n\n\t// make sure shutdownTimeout will be reset after this test\n\tdefer func(old time.Duration) { shutdownTimeout = old }(shutdownTimeout)\n\tdefer func(old time.Duration) { shutdownTicker = old }(shutdownTicker)\n\n\tshutdownTicker = time.Millisecond // shorter for saving test time\n\n\t// case: wait util shutdownTimeout\n\tshutdownTimeout = time.Millisecond\n\tif err := trans.Shutdown(context.Background()); err != errShutdownTimeout {\n\t\tt.Fatal(err)\n\t}\n\n\t// case: ctx done\n\tshutdownTimeout = time.Second // long enough\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\terr = trans.Shutdown(ctx)\n\tif err != ctx.Err() {\n\t\tt.Fatal(err)\n\t}\n\n\t// case: even after listener is closed, handler may still active\n\tcheckActiveConn(1)\n\n\t// case: Shutdown blocks at ticker and check periodically.\n\tshutdownTimeout = time.Second // long enough\n\tgo trans.Shutdown(context.Background())\n\ttime.Sleep(30 * time.Millisecond) // make sure Shutdown blocks at for loop\n\tclose(handlerExit)                // signal handler to return\n\ttime.Sleep(10 * time.Millisecond) // wait handler returns, and active conn to be updated.\n\tcheckActiveConn(0)\n}\n\nfunc TestAcceptError(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\n\ttrans := NewTransporter(&config.Options{Listener: ln}).(*transport)\n\terrCh := make(chan error, 1)\n\tgo func() { errCh <- trans.ListenAndServe(func(context.Context, interface{}) error { return nil }) }()\n\n\ttime.Sleep(10 * time.Millisecond) // Wait for serve to start\n\n\t// Close listener to trigger error\n\tln.Close()\n\n\t// Wait for serve to exit with error\n\tif err := <-errCh; err != nil {\n\t\tt.Fatal(\"expected nil after listener close\")\n\t}\n}\n"
  },
  {
    "path": "pkg/network/standard/unix_test.go",
    "content": "// Copyright 2026 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris\n\npackage standard\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/gopkg/net/connstate\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n)\n\n// TestConnCloseWithStater tests that Close properly closes the stater\nfunc TestConnCloseWithStater(t *testing.T) {\n\tc := &mockConn{\n\t\tlocalAddr: &mockAddr{\n\t\t\tnetwork: \"tcp\",\n\t\t\taddress: \"127.0.0.1:8080\",\n\t\t},\n\t\tremoteAddr: &mockAddr{\n\t\t\tnetwork: \"tcp\",\n\t\t\taddress: \"127.0.0.1:12345\",\n\t\t},\n\t}\n\tconn := newConn(c, 0).(*Conn)\n\n\t// Create a mock stater\n\tmockStater := &mockConnStater{}\n\tconn.stater = mockStater\n\n\t// Close the connection - mockConn.Close() returns error, but stater should still be closed\n\t_ = conn.Close()\n\n\t// After Close, stater should be nil\n\tif conn.stater != nil {\n\t\tt.Errorf(\"Stater should be nil after Close, got %v\", conn.stater)\n\t}\n\n\t// MockStater's Close method should have been called\n\tif !mockStater.closed {\n\t\tt.Error(\"MockStater's Close method was not called\")\n\t}\n}\n\n// TestConnstateConnectionCloseDetection tests the connstate-based connection close detection\nfunc TestConnstateConnectionCloseDetection(t *testing.T) {\n\t// Use net.Pipe to create a pair of connected connections\n\tcliConn, svrConn := net.Pipe()\n\tdefer cliConn.Close()\n\tdefer svrConn.Close()\n\n\t// Create a context that can be canceled\n\tctx, cancelCtx := context.WithCancel(context.Background())\n\tdefer cancelCtx()\n\n\t// Create server-side Conn\n\tsvrStdConn := newConn(svrConn, 0).(*Conn)\n\n\t// Register connstate callback\n\tstater, err := connstate.ListenConnState(svrConn,\n\t\tconnstate.WithOnRemoteClosed(func() {\n\t\t\tcancelCtx()\n\t\t}),\n\t)\n\tif err != nil {\n\t\t// If connstate is not supported on this platform, skip the test\n\t\tt.Skipf(\"connstate.ListenConnState not supported: %v\", err)\n\t\treturn\n\t}\n\tdefer stater.Close()\n\n\t// Set stater to Conn\n\tsvrStdConn.stater = stater\n\n\t// Test 1: Normal operation - context should not be canceled\n\tselect {\n\tcase <-ctx.Done():\n\t\tt.Fatal(\"Context should not be canceled in normal operation\")\n\tcase <-time.After(100 * time.Millisecond):\n\t\t// Expected - context not canceled\n\t}\n\n\t// Test 2: Close client connection - context should be canceled\n\tcliConn.Close()\n\n\t// Wait for context to be canceled (with timeout)\n\ttimeout := time.NewTimer(2 * time.Second)\n\tdefer timeout.Stop()\n\n\tselect {\n\tcase <-ctx.Done():\n\t\t// Expected - context canceled after client closes connection\n\tcase <-timeout.C:\n\t\tt.Fatal(\"Context was not canceled after client closed connection\")\n\t}\n\n\t// Close should properly clean up stater\n\terr = svrStdConn.Close()\n\tif err != nil {\n\t\tt.Errorf(\"Close failed: %v\", err)\n\t}\n}\n\n// TestSenseClientDisconnectionContextCancel tests that the context is canceled\n// when the client disconnects and SenseClientDisconnection is enabled\nfunc TestSenseClientDisconnectionContextCancel(t *testing.T) {\n\thandlerRunning := make(chan struct{})\n\thandlerExited := make(chan struct{})\n\n\ttrans := NewTransporter(&config.Options{\n\t\tNetwork:                  \"tcp\",\n\t\tAddr:                     \"127.0.0.1:0\",\n\t\tSenseClientDisconnection: true,\n\t}).(*transport)\n\n\tgo trans.ListenAndServe(func(ctx context.Context, conn interface{}) error {\n\t\tclose(handlerRunning)\n\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\t// Context was canceled as expected\n\t\tcase <-time.After(3 * time.Second):\n\t\t\tpanic(\"Context was not canceled after client disconnected\")\n\t\t}\n\n\t\tclose(handlerExited)\n\t\treturn nil\n\t})\n\n\tfor trans.Listener() == nil {\n\t\ttime.Sleep(2 * time.Millisecond)\n\t}\n\n\tclientConn, err := net.Dial(\"tcp\", trans.Listener().Addr().String())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Wait for handler to start running\n\t<-handlerRunning\n\n\t// Close client connection to trigger disconnection detection\n\tclientConn.Close()\n\n\t// Wait for handler to exit (context should be canceled)\n\tselect {\n\tcase <-handlerExited:\n\t\t// Handler exited as expected\n\tcase <-time.After(4 * time.Second):\n\t\tt.Fatal(\"Handler did not exit after client disconnected\")\n\t}\n}\n\n// TestSenseClientDisconnectionDisabled tests that context cancel behavior\n// when SenseClientDisconnection is disabled\nfunc TestSenseClientDisconnectionDisabled(t *testing.T) {\n\thandlerRunning := make(chan struct{})\n\thandlerExited := make(chan struct{})\n\n\ttrans := NewTransporter(&config.Options{\n\t\tNetwork:                  \"tcp\",\n\t\tAddr:                     \"127.0.0.1:0\",\n\t\tSenseClientDisconnection: false,\n\t}).(*transport)\n\n\tgo trans.ListenAndServe(func(ctx context.Context, conn interface{}) error {\n\t\tclose(handlerRunning)\n\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tpanic(\"Context was canceled after client disconnected\")\n\t\tcase <-time.After(200 * time.Millisecond):\n\t\t}\n\n\t\tclose(handlerExited)\n\t\treturn nil\n\t})\n\n\tfor trans.Listener() == nil {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t}\n\n\tclientConn, err := net.Dial(\"tcp\", trans.Listener().Addr().String())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Wait for handler to start running\n\t<-handlerRunning\n\n\t// Close client connection to trigger disconnection detection\n\tclientConn.Close()\n\n\t// Wait for handler to exit (context should be canceled)\n\tselect {\n\tcase <-handlerExited:\n\t\t// Handler exited as expected\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatal(\"Handler did not exit after client disconnected\")\n\t}\n}\n\n// mockConnStater is a mock implementation of connstate.ConnStater for testing\ntype mockConnStater struct {\n\tclosed bool\n\tstate  connstate.ConnState\n}\n\nfunc (m *mockConnStater) Close() error {\n\tm.closed = true\n\treturn nil\n}\n\nfunc (m *mockConnStater) State() connstate.ConnState {\n\treturn m.state\n}\n"
  },
  {
    "path": "pkg/network/transport.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage network\n\nimport (\n\t\"context\"\n)\n\ntype Transporter interface {\n\t// Close the transporter immediately\n\tClose() error\n\n\t// Graceful shutdown the transporter\n\tShutdown(ctx context.Context) error\n\n\t// Start listen and ready to accept connection\n\tListenAndServe(onData OnData) error\n}\n\n// Callback when data is ready on the connection\ntype OnData func(ctx context.Context, conn interface{}) error\n"
  },
  {
    "path": "pkg/network/utils.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage network\n\nimport \"syscall\"\n\nfunc UnlinkUdsFile(network, addr string) error {\n\tif network == \"unix\" {\n\t\treturn syscall.Unlink(addr)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/network/utils_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage network\n\nimport (\n\t\"os\"\n\t\"runtime\"\n\t\"testing\"\n)\n\nfunc TestUnlinkUdsFile(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.SkipNow()\n\t}\n\n\ttmp := \"tmpFile\"\n\tvar err error\n\n\terr = UnlinkUdsFile(\"unix\", tmp)\n\n\tif err == nil {\n\t\tt.Errorf(\"should have error when unlinking a nonexistent file\")\n\t}\n\n\tos.Create(tmp)\n\n\terr = UnlinkUdsFile(\"unix\", tmp)\n\tif err != nil {\n\t\tt.Errorf(\"unlink file failed: %s\", err.Error())\n\t}\n\n\tisExist, _ := pathExists(tmp)\n\n\tif isExist {\n\t\tt.Errorf(\"unlink file failed, file still exist\")\n\t}\n}\n\nfunc pathExists(path string) (bool, error) {\n\t_, err := os.Stat(path)\n\tif err == nil {\n\t\treturn true, nil\n\t}\n\tif os.IsNotExist(err) {\n\t\treturn false, nil\n\t}\n\treturn false, err\n}\n"
  },
  {
    "path": "pkg/network/version.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage network\n\nconst (\n\t// QUIC version codes\n\tVersionTLS     uint32 = 0x1\n\tVersionDraft29 uint32 = 0xff00001d\n\tVersion1       uint32 = 0x1\n\tVersion2       uint32 = 0x709a50c4\n)\n"
  },
  {
    "path": "pkg/network/writer.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage network\n\nimport (\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/bytedance/gopkg/lang/mcache\"\n)\n\nconst size4K = 1024 * 4\n\ntype node struct {\n\tdata     []byte\n\treadOnly bool\n}\n\nvar nodePool = sync.Pool{}\n\nfunc init() {\n\tnodePool.New = func() interface{} {\n\t\treturn &node{}\n\t}\n}\n\ntype networkWriter struct {\n\tcaches []*node\n\tw      io.Writer\n}\n\nfunc (w *networkWriter) release() {\n\tfor _, n := range w.caches {\n\t\tif !n.readOnly {\n\t\t\tmcache.Free(n.data)\n\t\t}\n\t\tn.data = nil\n\t\tn.readOnly = false\n\t\tnodePool.Put(n)\n\t}\n\tw.caches = w.caches[:0]\n}\n\nfunc (w *networkWriter) Malloc(length int) (buf []byte, err error) {\n\tidx := len(w.caches)\n\tif idx > 0 {\n\t\tidx -= 1\n\t\tinUse := len(w.caches[idx].data)\n\t\tif !w.caches[idx].readOnly && cap(w.caches[idx].data)-inUse >= length {\n\t\t\tend := inUse + length\n\t\t\tw.caches[idx].data = w.caches[idx].data[:end]\n\t\t\treturn w.caches[idx].data[inUse:end], nil\n\t\t}\n\t}\n\tbuf = mcache.Malloc(length)\n\tn := nodePool.Get().(*node)\n\tn.data = buf\n\tw.caches = append(w.caches, n)\n\treturn\n}\n\nfunc (w *networkWriter) WriteBinary(b []byte) (length int, err error) {\n\tlength = len(b)\n\tif length < size4K {\n\t\tbuf, _ := w.Malloc(length)\n\t\tcopy(buf, b)\n\t\treturn\n\t}\n\tnode := nodePool.Get().(*node)\n\tnode.readOnly = true\n\tnode.data = b\n\tw.caches = append(w.caches, node)\n\treturn\n}\n\nfunc (w *networkWriter) Flush() (err error) {\n\tfor _, c := range w.caches {\n\t\t_, err = w.w.Write(c.data)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\tw.release()\n\treturn\n}\n\nfunc NewWriter(w io.Writer) Writer {\n\treturn &networkWriter{\n\t\tw: w,\n\t}\n}\n\ntype ExtWriter interface {\n\tio.Writer\n\n\t// Flush sends data to peer immediately\n\tFlush() error\n\n\t// Finalize will be called by framework before the writer is released.\n\t// Implementations must guarantee that Finalize is safe for multiple calls.\n\tFinalize() error\n}\n"
  },
  {
    "path": "pkg/network/writer_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage network\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nconst (\n\tsize1K = 1024\n)\n\nfunc TestConvertNetworkWriter(t *testing.T) {\n\tiw := &mockIOWriter{}\n\tw := NewWriter(iw)\n\tnw, _ := w.(*networkWriter)\n\n\t// Test malloc\n\tbuf, _ := w.Malloc(size1K)\n\tassert.DeepEqual(t, len(buf), size1K)\n\tassert.DeepEqual(t, len(nw.caches), 1)\n\tassert.DeepEqual(t, len(nw.caches[0].data), size1K)\n\tassert.DeepEqual(t, cap(nw.caches[0].data), size1K)\n\terr := w.Flush()\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, size1K, iw.WriteNum)\n\tassert.DeepEqual(t, len(nw.caches), 0)\n\tassert.DeepEqual(t, cap(nw.caches), 1)\n\n\t// Test malloc left size\n\tbuf, _ = w.Malloc(size1K + 1)\n\tassert.DeepEqual(t, len(buf), size1K+1)\n\tassert.DeepEqual(t, len(nw.caches), 1)\n\tassert.DeepEqual(t, len(nw.caches[0].data), size1K+1)\n\tassert.DeepEqual(t, cap(nw.caches[0].data), size1K*2)\n\tbuf, _ = w.Malloc(size1K / 2)\n\tassert.DeepEqual(t, len(buf), size1K/2)\n\tassert.DeepEqual(t, len(nw.caches), 1)\n\tassert.DeepEqual(t, len(nw.caches[0].data), size1K+1+size1K/2)\n\tassert.DeepEqual(t, cap(nw.caches[0].data), size1K*2)\n\tbuf, _ = w.Malloc(size1K / 2)\n\tassert.DeepEqual(t, len(buf), size1K/2)\n\tassert.DeepEqual(t, len(nw.caches), 2)\n\tassert.DeepEqual(t, len(nw.caches[0].data), size1K+1+size1K/2)\n\tassert.DeepEqual(t, cap(nw.caches[0].data), size1K*2)\n\tassert.DeepEqual(t, len(nw.caches[1].data), size1K/2)\n\tassert.DeepEqual(t, cap(nw.caches[1].data), size1K/2)\n\terr = w.Flush()\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, size1K*3+1, iw.WriteNum)\n\tassert.DeepEqual(t, len(nw.caches), 0)\n\tassert.DeepEqual(t, cap(nw.caches), 2)\n\n\t// Test WriteBinary after Malloc\n\tbuf, _ = w.Malloc(size1K * 6)\n\tassert.DeepEqual(t, len(buf), size1K*6)\n\tassert.DeepEqual(t, len(nw.caches[0].data), size1K*6)\n\tb := make([]byte, size1K)\n\tw.WriteBinary(b)\n\tassert.DeepEqual(t, size1K*3+1, iw.WriteNum)\n\tassert.DeepEqual(t, len(nw.caches[0].data), size1K*7)\n\tassert.DeepEqual(t, cap(nw.caches[0].data), size1K*8)\n\n\tb = make([]byte, size1K*4)\n\tw.WriteBinary(b)\n\tassert.DeepEqual(t, len(nw.caches[1].data), size1K*4)\n\tassert.DeepEqual(t, cap(nw.caches[1].data), size1K*4)\n\tassert.DeepEqual(t, nw.caches[1].readOnly, true)\n\tw.Flush()\n\tassert.DeepEqual(t, size1K*14+1, iw.WriteNum)\n}\n\ntype mockIOWriter struct {\n\tWriteNum int\n}\n\nfunc (m *mockIOWriter) Write(p []byte) (n int, err error) {\n\tm.WriteNum += len(p)\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "pkg/protocol/args.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/nocopy\"\n)\n\nconst (\n\targsNoValue  = true\n\tArgsHasValue = false\n)\n\ntype argsScanner struct {\n\tb []byte\n}\n\ntype Args struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\targs []argsKV\n\tbuf  []byte\n}\n\n// Set sets 'key=value' argument.\nfunc (a *Args) Set(key, value string) {\n\ta.args = setArg(a.args, key, value, ArgsHasValue)\n}\n\n// Reset clears query args.\nfunc (a *Args) Reset() {\n\ta.args = a.args[:0]\n}\n\n// CopyTo copies all args to dst.\nfunc (a *Args) CopyTo(dst *Args) {\n\tdst.Reset()\n\tdst.args = copyArgs(dst.args, a.args)\n}\n\n// Del deletes argument with the given key from query args.\nfunc (a *Args) Del(key string) {\n\ta.args = delAllArgs(a.args, key)\n}\n\n// DelBytes deletes argument with the given key from query args.\nfunc (a *Args) DelBytes(key []byte) {\n\ta.args = delAllArgs(a.args, bytesconv.B2s(key))\n}\n\nfunc (s *argsScanner) next(kv *argsKV) bool {\n\tif len(s.b) == 0 {\n\t\treturn false\n\t}\n\tkv.noValue = ArgsHasValue\n\n\tisKey := true\n\tk := 0\n\tfor i, c := range s.b {\n\t\tswitch c {\n\t\tcase '=':\n\t\t\tif isKey {\n\t\t\t\tisKey = false\n\t\t\t\tkv.key = decodeArgAppend(kv.key[:0], s.b[:i])\n\t\t\t\tk = i + 1\n\t\t\t}\n\t\tcase '&':\n\t\t\tif isKey {\n\t\t\t\tkv.key = decodeArgAppend(kv.key[:0], s.b[:i])\n\t\t\t\tkv.value = kv.value[:0]\n\t\t\t\tkv.noValue = argsNoValue\n\t\t\t} else {\n\t\t\t\tkv.value = decodeArgAppend(kv.value[:0], s.b[k:i])\n\t\t\t}\n\t\t\ts.b = s.b[i+1:]\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif isKey {\n\t\tkv.key = decodeArgAppend(kv.key[:0], s.b)\n\t\tkv.value = kv.value[:0]\n\t\tkv.noValue = argsNoValue\n\t} else {\n\t\tkv.value = decodeArgAppend(kv.value[:0], s.b[k:])\n\t}\n\ts.b = s.b[len(s.b):]\n\treturn true\n}\n\nfunc decodeArgAppend(dst, src []byte) []byte {\n\tif bytes.IndexByte(src, '%') < 0 && bytes.IndexByte(src, '+') < 0 {\n\t\t// fast path: src doesn't contain encoded chars\n\t\treturn append(dst, src...)\n\t}\n\n\t// slow path\n\tfor i := 0; i < len(src); i++ {\n\t\tc := src[i]\n\t\tif c == '%' {\n\t\t\tif i+2 >= len(src) {\n\t\t\t\treturn append(dst, src[i:]...)\n\t\t\t}\n\t\t\tx2 := bytesconv.Hex2intTable[src[i+2]]\n\t\t\tx1 := bytesconv.Hex2intTable[src[i+1]]\n\t\t\tif x1 == 16 || x2 == 16 {\n\t\t\t\tdst = append(dst, '%')\n\t\t\t} else {\n\t\t\t\tdst = append(dst, x1<<4|x2)\n\t\t\t\ti += 2\n\t\t\t}\n\t\t} else if c == '+' {\n\t\t\tdst = append(dst, ' ')\n\t\t} else {\n\t\t\tdst = append(dst, c)\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc allocArg(h []argsKV) ([]argsKV, *argsKV) {\n\tn := len(h)\n\tif cap(h) > n {\n\t\th = h[:n+1]\n\t\tkv := &h[n]\n\t\tif kv.value == nil {\n\t\t\t// bytes in value would be reused, and it's not always nil\n\t\t\t// only set to empty when it's nil\n\t\t\tkv.value = []byte{}\n\t\t}\n\t\treturn h, kv\n\t}\n\th = append(h, argsKV{value: []byte{}})\n\treturn h, &h[n]\n}\n\nfunc releaseArg(h []argsKV) []argsKV {\n\treturn h[:len(h)-1]\n}\n\nfunc updateArgBytes(h []argsKV, key, value []byte) []argsKV {\n\tn := len(h)\n\tfor i := 0; i < n; i++ {\n\t\tkv := &h[i]\n\t\tif kv.noValue && bytes.Equal(key, kv.key) {\n\t\t\tkv.value = append(kv.value[:0], value...)\n\t\t\tkv.noValue = ArgsHasValue\n\t\t\treturn h\n\t\t}\n\t}\n\treturn h\n}\n\nfunc setArgBytes(h []argsKV, key, value []byte, noValue bool) []argsKV {\n\tn := len(h)\n\tfor i := 0; i < n; i++ {\n\t\tkv := &h[i]\n\t\tif bytes.Equal(key, kv.key) {\n\t\t\tif noValue {\n\t\t\t\tkv.value = kv.value[:0]\n\t\t\t} else {\n\t\t\t\tkv.value = append(kv.value[:0], value...)\n\t\t\t}\n\t\t\tkv.noValue = noValue\n\t\t\treturn h\n\t\t}\n\t}\n\treturn appendArgBytes(h, key, value, noValue)\n}\n\nfunc setArg(h []argsKV, key, value string, noValue bool) []argsKV {\n\tn := len(h)\n\tfor i := 0; i < n; i++ {\n\t\tkv := &h[i]\n\t\tif key == string(kv.key) {\n\t\t\tif noValue {\n\t\t\t\tkv.value = kv.value[:0]\n\t\t\t} else {\n\t\t\t\tkv.value = append(kv.value[:0], value...)\n\t\t\t}\n\t\t\tkv.noValue = noValue\n\t\t\treturn h\n\t\t}\n\t}\n\treturn appendArg(h, key, value, noValue)\n}\n\nfunc peekArgBytes(h []argsKV, k []byte) []byte {\n\tfor i, n := 0, len(h); i < n; i++ {\n\t\tkv := &h[i]\n\t\tif bytes.Equal(kv.key, k) {\n\t\t\treturn kv.value\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc peekAllArgBytesToDst(dst [][]byte, h []argsKV, k []byte) [][]byte {\n\tfor i, n := 0, len(h); i < n; i++ {\n\t\tkv := &h[i]\n\t\tif bytes.Equal(kv.key, k) {\n\t\t\tdst = append(dst, kv.value)\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc delAllArgsBytes(args []argsKV, key []byte) []argsKV {\n\treturn delAllArgs(args, bytesconv.B2s(key))\n}\n\nfunc delAllArgs(args []argsKV, key string) []argsKV {\n\tfor i, n := 0, len(args); i < n; i++ {\n\t\tkv := &args[i]\n\t\tif key == string(kv.key) {\n\t\t\ttmp := *kv\n\t\t\tcopy(args[i:], args[i+1:])\n\t\t\tn--\n\t\t\ti--\n\t\t\targs[n] = tmp\n\t\t\targs = args[:n]\n\t\t}\n\t}\n\treturn args\n}\n\n// Has returns true if the given key exists in Args.\nfunc (a *Args) Has(key string) bool {\n\treturn hasArg(a.args, key)\n}\n\nfunc hasArg(h []argsKV, key string) bool {\n\tfor i, n := 0, len(h); i < n; i++ {\n\t\tkv := &h[i]\n\t\tif key == string(kv.key) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// String returns string representation of query args.\nfunc (a *Args) String() string {\n\treturn string(a.QueryString())\n}\n\n// decodeArgAppendNoPlus is almost identical to decodeArgAppend, but it doesn't\n// substitute '+' with ' '.\n//\n// The function is copy-pasted from decodeArgAppend due to the performance\n// reasons only.\nfunc decodeArgAppendNoPlus(dst, src []byte) []byte {\n\tif bytes.IndexByte(src, '%') < 0 {\n\t\t// fast path: src doesn't contain encoded chars\n\t\treturn append(dst, src...)\n\t}\n\n\t// slow path\n\tfor i := 0; i < len(src); i++ {\n\t\tc := src[i]\n\t\tif c == '%' {\n\t\t\tif i+2 >= len(src) {\n\t\t\t\treturn append(dst, src[i:]...)\n\t\t\t}\n\t\t\tx2 := bytesconv.Hex2intTable[src[i+2]]\n\t\t\tx1 := bytesconv.Hex2intTable[src[i+1]]\n\t\t\tif x1 == 16 || x2 == 16 {\n\t\t\t\tdst = append(dst, '%')\n\t\t\t} else {\n\t\t\t\tdst = append(dst, x1<<4|x2)\n\t\t\t\ti += 2\n\t\t\t}\n\t\t} else {\n\t\t\tdst = append(dst, c)\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc peekArgStr(h []argsKV, k string) []byte {\n\tfor i, n := 0, len(h); i < n; i++ {\n\t\tkv := &h[i]\n\t\tif string(kv.key) == k {\n\t\t\treturn kv.value\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc peekArgStrExists(h []argsKV, k string) (string, bool) {\n\tfor i, n := 0, len(h); i < n; i++ {\n\t\tkv := &h[i]\n\t\tif string(kv.key) == k {\n\t\t\treturn string(kv.value), true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\n// QueryString returns query string for the args.\n//\n// The returned value is valid until the next call to Args methods.\nfunc (a *Args) QueryString() []byte {\n\ta.buf = a.AppendBytes(a.buf[:0])\n\treturn a.buf\n}\n\n// ParseBytes parses the given b containing query args.\nfunc (a *Args) ParseBytes(b []byte) {\n\ta.Reset()\n\n\tvar s argsScanner\n\ts.b = b\n\n\tvar kv *argsKV\n\ta.args, kv = allocArg(a.args)\n\tfor s.next(kv) {\n\t\tif len(kv.key) > 0 || len(kv.value) > 0 {\n\t\t\ta.args, kv = allocArg(a.args)\n\t\t}\n\t}\n\ta.args = releaseArg(a.args)\n\n\tif len(a.args) == 0 {\n\t\treturn\n\t}\n}\n\n// Peek returns query arg value for the given key.\n//\n// Returned value is valid until the next Args call.\nfunc (a *Args) Peek(key string) []byte {\n\treturn peekArgStr(a.args, key)\n}\n\nfunc (a *Args) PeekExists(key string) (string, bool) {\n\treturn peekArgStrExists(a.args, key)\n}\n\n// PeekAll returns all the arg values for the given key.\nfunc (a *Args) PeekAll(key string) [][]byte {\n\tvar values [][]byte\n\ta.VisitAll(func(k, v []byte) {\n\t\tif bytesconv.B2s(k) == key {\n\t\t\tvalues = append(values, v)\n\t\t}\n\t})\n\treturn values\n}\n\nfunc visitArgs(args []argsKV, f func(k, v []byte)) {\n\tfor i, n := 0, len(args); i < n; i++ {\n\t\tkv := &args[i]\n\t\tf(kv.key, kv.value)\n\t}\n}\n\n// Len returns the number of query args.\nfunc (a *Args) Len() int {\n\treturn len(a.args)\n}\n\n// AppendBytes appends query string to dst and returns the extended dst.\nfunc (a *Args) AppendBytes(dst []byte) []byte {\n\tfor i, n := 0, len(a.args); i < n; i++ {\n\t\tkv := &a.args[i]\n\t\tdst = bytesconv.AppendQuotedArg(dst, kv.key)\n\t\tif !kv.noValue {\n\t\t\tdst = append(dst, '=')\n\t\t\tif len(kv.value) > 0 {\n\t\t\t\tdst = bytesconv.AppendQuotedArg(dst, kv.value)\n\t\t\t}\n\t\t}\n\t\tif i+1 < n {\n\t\t\tdst = append(dst, '&')\n\t\t}\n\t}\n\treturn dst\n}\n\n// VisitAll calls f for each existing arg.\n//\n// f must not retain references to key and value after returning.\n// Make key and/or value copies if you need storing them after returning.\nfunc (a *Args) VisitAll(f func(key, value []byte)) {\n\tvisitArgs(a.args, f)\n}\n\n// WriteTo writes query string to w.\n//\n// WriteTo implements io.WriterTo interface.\nfunc (a *Args) WriteTo(w io.Writer) (int64, error) {\n\tn, err := w.Write(a.QueryString())\n\treturn int64(n), err\n}\n\n// Add adds 'key=value' argument.\n//\n// Multiple values for the same key may be added.\nfunc (a *Args) Add(key, value string) {\n\ta.args = appendArg(a.args, key, value, ArgsHasValue)\n}\n"
  },
  {
    "path": "pkg/protocol/args_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestArgsDeleteAll(t *testing.T) {\n\tt.Parallel()\n\tvar a Args\n\ta.Add(\"q1\", \"foo\")\n\ta.Add(\"q1\", \"bar\")\n\ta.Add(\"q1\", \"baz\")\n\ta.Add(\"q1\", \"quux\")\n\ta.Add(\"q2\", \"1234\")\n\ta.Del(\"q1\")\n\tif a.Len() != 1 || a.Has(\"q1\") {\n\t\tt.Fatalf(\"Expected q1 arg to be completely deleted. Current Args: %s\", a.String())\n\t}\n}\n\nfunc TestArgsBytesOperation(t *testing.T) {\n\tvar a Args\n\ta.Add(\"q1\", \"foo\")\n\ta.Add(\"q2\", \"bar\")\n\tsetArgBytes(a.args, a.args[0].key, a.args[0].value, false)\n\tassert.DeepEqual(t, []byte(\"foo\"), peekArgBytes(a.args, []byte(\"q1\")))\n\tsetArgBytes(a.args, a.args[1].key, a.args[1].value, true)\n\tassert.DeepEqual(t, []byte(\"\"), peekArgBytes(a.args, []byte(\"q2\")))\n}\n\nfunc TestArgsPeekExists(t *testing.T) {\n\tvar a Args\n\ta.Add(\"q1\", \"foo\")\n\ta.Add(\"\", \"\")\n\ta.Add(\"?\", \"=\")\n\tv1, b1 := a.PeekExists(\"q1\")\n\tassert.DeepEqual(t, []byte(\"foo\"), []byte(v1))\n\tassert.True(t, b1)\n\tv2, b2 := a.PeekExists(\"\")\n\tassert.DeepEqual(t, []byte(\"\"), []byte(v2))\n\tassert.True(t, b2)\n\tv3, b3 := a.PeekExists(\"q3\")\n\tassert.DeepEqual(t, \"\", v3)\n\tassert.False(t, b3)\n\tv4, b4 := a.PeekExists(\"?\")\n\tassert.DeepEqual(t, \"=\", v4)\n\tassert.True(t, b4)\n}\n\nfunc TestSetArg(t *testing.T) {\n\ta := Args{args: setArg(nil, \"q1\", \"foo\", true)}\n\ta.Add(\"\", \"\")\n\tsetArgBytes(a.args, []byte(\"q3\"), []byte(\"bar\"), false)\n\ts := a.String()\n\tassert.DeepEqual(t, []byte(\"q1&=\"), []byte(s))\n}\n\n// Test the encoding of special parameters\nfunc TestArgsParseBytes(t *testing.T) {\n\tvar ta1 Args\n\tta1.Add(\"q1\", \"foo\")\n\tta1.Add(\"q1\", \"bar\")\n\tta1.Add(\"q2\", \"123\")\n\tta1.Add(\"q3\", \"\")\n\tvar a1 Args\n\ta1.ParseBytes([]byte(\"q1=foo&q1=bar&q2=123&q3=\"))\n\tassert.DeepEqual(t, &ta1, &a1)\n\n\tvar ta2 Args\n\tta2.Add(\"?\", \"foo\")\n\tta2.Add(\"&\", \"bar\")\n\tta2.Add(\"&\", \"?\")\n\tta2.Add(\"=\", \"=\")\n\tvar a2 Args\n\ta2.ParseBytes([]byte(\"%3F=foo&%26=bar&%26=%3F&%3D=%3D\"))\n\tassert.DeepEqual(t, &ta2, &a2)\n}\n\nfunc TestArgsVisitAll(t *testing.T) {\n\tvar a Args\n\tvar s []string\n\ta.Add(\"cloudwego\", \"hertz\")\n\ta.Add(\"hello\", \"world\")\n\ta.VisitAll(func(key, value []byte) {\n\t\ts = append(s, string(key), string(value))\n\t})\n\tassert.DeepEqual(t, []string{\"cloudwego\", \"hertz\", \"hello\", \"world\"}, s)\n}\n\nfunc TestArgsCopyTo(t *testing.T) {\n\tvar a Args\n\ta.Add(\"cloudwego\", \"\")\n\ta.Add(\"hello\", \"world\")\n\n\tvar b Args\n\ta.CopyTo(&b)\n\tassert.Assert(t, b.Len() == 2)\n\tassert.Assert(t, a.Peek(\"cloudwego\") != nil && len(a.Peek(\"cloudwego\")) == 0)\n\tassert.Assert(t, string(a.Peek(\"hello\")) == \"world\")\n\tassert.Assert(t, a.Peek(\"key-not-exists\") == nil)\n}\n\nfunc TestArgsPeek(t *testing.T) {\n\tvar a Args\n\ta.Add(\"cloudwego\", \"\")\n\ta.Add(\"hello\", \"world\")\n\n\tassert.Assert(t, a.Peek(\"cloudwego\") != nil && len(a.Peek(\"cloudwego\")) == 0)\n\tassert.Assert(t, string(a.Peek(\"hello\")) == \"world\")\n\tassert.Assert(t, a.Peek(\"key-not-exists\") == nil)\n\n\t// reset and reuse\n\ta.Reset()\n\ta.Add(\"cloudwego\", \"\")\n\ta.Add(\"hello\", \"world\")\n\tassert.Assert(t, a.Peek(\"cloudwego\") != nil && len(a.Peek(\"cloudwego\")) == 0)\n\tassert.Assert(t, string(a.Peek(\"hello\")) == \"world\")\n\tassert.Assert(t, a.Peek(\"key-not-exists\") == nil)\n}\n\nfunc TestArgsPeekMulti(t *testing.T) {\n\tvar a Args\n\ta.Add(\"cloudwego\", \"hertz\")\n\ta.Add(\"cloudwego\", \"kitex\")\n\ta.Add(\"cloudwego\", \"\")\n\ta.Add(\"hello\", \"world\")\n\n\tvv := a.PeekAll(\"cloudwego\")\n\texpectedVV := [][]byte{\n\t\t[]byte(\"hertz\"),\n\t\t[]byte(\"kitex\"),\n\t\t[]byte{},\n\t}\n\tassert.DeepEqual(t, expectedVV, vv)\n\n\tvv = a.PeekAll(\"aaaa\")\n\tassert.DeepEqual(t, 0, len(vv))\n\n\tvv = a.PeekAll(\"hello\")\n\texpectedVV = [][]byte{[]byte(\"world\")}\n\tassert.DeepEqual(t, expectedVV, vv)\n}\n"
  },
  {
    "path": "pkg/protocol/client/client.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage client\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/timer\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nconst defaultMaxRedirectsCount = 16\n\nvar (\n\terrTimeout          = errors.New(errors.ErrTimeout, errors.ErrorTypePublic, \"host client\")\n\terrMissingLocation  = errors.NewPublic(\"missing Location header for http redirect\")\n\terrTooManyRedirects = errors.NewPublic(\"too many redirects detected when doing the request\")\n\n\tclientURLResponseChPool sync.Pool\n)\n\ntype HostClient interface {\n\tDoer\n\tSetDynamicConfig(dc *DynamicConfig)\n\tCloseIdleConnections()\n\tShouldRemove() bool\n\tConnectionCount() int\n}\n\ntype Doer interface {\n\tDo(ctx context.Context, req *protocol.Request, resp *protocol.Response) error\n}\n\n// DefaultRetryIf Default retry condition, mainly used for idempotent requests.\n// If this cannot be satisfied, you can implement your own retry condition.\nfunc DefaultRetryIf(req *protocol.Request, resp *protocol.Response, err error) bool {\n\t// cannot retry if the request body is not rewindable\n\tif req.IsBodyStream() {\n\t\treturn false\n\t}\n\n\tif isIdempotent(req, resp, err) {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc isIdempotent(req *protocol.Request, resp *protocol.Response, err error) bool {\n\treturn req.Header.IsGet() ||\n\t\treq.Header.IsHead() ||\n\t\treq.Header.IsPut() ||\n\t\treq.Header.IsDelete() ||\n\t\treq.Header.IsOptions() ||\n\t\treq.Header.IsTrace()\n}\n\n// DynamicConfig is config set which will be confirmed when starts a request.\ntype DynamicConfig struct {\n\tAddr     string\n\tProxyURI *protocol.URI\n\tIsTLS    bool\n}\n\n// RetryIfFunc signature of retry if function\n// Judge whether to retry by request,response or error , return true is retry\ntype RetryIfFunc func(req *protocol.Request, resp *protocol.Response, err error) bool\n\ntype clientURLResponse struct {\n\tstatusCode int\n\tbody       []byte\n\terr        error\n}\n\nfunc GetURL(ctx context.Context, dst []byte, url string, c Doer, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\treq := protocol.AcquireRequest()\n\treq.SetOptions(requestOptions...)\n\n\tstatusCode, body, err = doRequestFollowRedirectsBuffer(ctx, req, dst, url, c)\n\n\tprotocol.ReleaseRequest(req)\n\treturn statusCode, body, err\n}\n\nfunc GetURLTimeout(ctx context.Context, dst []byte, url string, timeout time.Duration, c Doer, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\tdeadline := time.Now().Add(timeout)\n\treturn GetURLDeadline(ctx, dst, url, deadline, c, requestOptions...)\n}\n\nfunc GetURLDeadline(ctx context.Context, dst []byte, url string, deadline time.Time, c Doer, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\ttimeout := -time.Since(deadline)\n\tif timeout <= 0 {\n\t\treturn 0, dst, errTimeout\n\t}\n\n\tvar ch chan clientURLResponse\n\tchv := clientURLResponseChPool.Get()\n\tif chv == nil {\n\t\tchv = make(chan clientURLResponse, 1)\n\t}\n\tch = chv.(chan clientURLResponse)\n\n\treq := protocol.AcquireRequest()\n\treq.SetOptions(requestOptions...)\n\n\t// Note that the request continues execution on errTimeout until\n\t// client-specific ReadTimeout exceeds. This helps to limit load\n\t// on slow hosts by MaxConns* concurrent requests.\n\t//\n\t// Without this 'hack' the load on slow host could exceed MaxConns*\n\t// concurrent requests, since timed out requests on client side\n\t// usually continue execution on the host.\n\tgo func() {\n\t\tstatusCodeCopy, bodyCopy, errCopy := doRequestFollowRedirectsBuffer(ctx, req, dst, url, c)\n\t\tch <- clientURLResponse{\n\t\t\tstatusCode: statusCodeCopy,\n\t\t\tbody:       bodyCopy,\n\t\t\terr:        errCopy,\n\t\t}\n\t}()\n\n\ttc := timer.AcquireTimer(timeout)\n\tselect {\n\tcase resp := <-ch:\n\t\tprotocol.ReleaseRequest(req)\n\t\tclientURLResponseChPool.Put(chv)\n\t\tstatusCode = resp.statusCode\n\t\tbody = resp.body\n\t\terr = resp.err\n\tcase <-tc.C:\n\t\tbody = dst\n\t\terr = errTimeout\n\t}\n\ttimer.ReleaseTimer(tc)\n\n\treturn statusCode, body, err\n}\n\nfunc PostURL(ctx context.Context, dst []byte, url string, postArgs *protocol.Args, c Doer, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {\n\treq := protocol.AcquireRequest()\n\treq.Header.SetMethodBytes(bytestr.StrPost)\n\treq.Header.SetContentTypeBytes(bytestr.MIMEPostForm)\n\treq.SetOptions(requestOptions...)\n\n\tif postArgs != nil {\n\t\tif _, err := postArgs.WriteTo(req.BodyWriter()); err != nil {\n\t\t\treturn 0, nil, err\n\t\t}\n\t}\n\n\tstatusCode, body, err = doRequestFollowRedirectsBuffer(ctx, req, dst, url, c)\n\n\tprotocol.ReleaseRequest(req)\n\treturn statusCode, body, err\n}\n\nfunc doRequestFollowRedirectsBuffer(ctx context.Context, req *protocol.Request, dst []byte, url string, c Doer) (statusCode int, body []byte, err error) {\n\tresp := protocol.AcquireResponse()\n\tbodyBuf := resp.BodyBuffer()\n\toldBody := bodyBuf.B\n\tbodyBuf.B = dst\n\n\tstatusCode, _, err = DoRequestFollowRedirects(ctx, req, resp, url, defaultMaxRedirectsCount, c)\n\n\t// In HTTP2 scenario, client use stream mode to create a request and its body is in body stream.\n\t// In HTTP1, only client recv body exceed max body size and client is in stream mode can trig it.\n\tbody = resp.Body()\n\tbodyBuf.B = oldBody\n\tprotocol.ReleaseResponse(resp)\n\n\treturn statusCode, body, err\n}\n\nfunc DoRequestFollowRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, url string, maxRedirectsCount int, c Doer) (statusCode int, body []byte, err error) {\n\tredirectsCount := 0\n\n\tfor {\n\t\treq.SetRequestURI(url)\n\t\treq.ParseURI()\n\n\t\tif err = c.Do(ctx, req, resp); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tstatusCode = resp.Header.StatusCode()\n\t\tif !StatusCodeIsRedirect(statusCode) {\n\t\t\tbreak\n\t\t}\n\n\t\tredirectsCount++\n\t\tif redirectsCount > maxRedirectsCount {\n\t\t\terr = errTooManyRedirects\n\t\t\tbreak\n\t\t}\n\t\tlocation := resp.Header.PeekLocation()\n\t\tif len(location) == 0 {\n\t\t\terr = errMissingLocation\n\t\t\tbreak\n\t\t}\n\t\turl = getRedirectURL(url, location)\n\n\t\t// Remove the former host header.\n\t\treq.Header.Del(consts.HeaderHost)\n\t}\n\n\treturn statusCode, body, err\n}\n\n// StatusCodeIsRedirect returns true if the status code indicates a redirect.\nfunc StatusCodeIsRedirect(statusCode int) bool {\n\treturn statusCode == consts.StatusMovedPermanently ||\n\t\tstatusCode == consts.StatusFound ||\n\t\tstatusCode == consts.StatusSeeOther ||\n\t\tstatusCode == consts.StatusTemporaryRedirect ||\n\t\tstatusCode == consts.StatusPermanentRedirect\n}\n\nfunc getRedirectURL(baseURL string, location []byte) string {\n\tu := protocol.AcquireURI()\n\tu.Update(baseURL)\n\tu.UpdateBytes(location)\n\tredirectURL := u.String()\n\tprotocol.ReleaseURI(u)\n\treturn redirectURL\n}\n\nfunc DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration, c Doer) error {\n\tif timeout <= 0 {\n\t\treturn errTimeout\n\t}\n\t// Note: it will overwrite the reqTimeout.\n\treq.SetOptions(config.WithRequestTimeout(timeout))\n\treturn c.Do(ctx, req, resp)\n}\n\nfunc DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time, c Doer) error {\n\ttimeout := time.Until(deadline)\n\tif timeout <= 0 {\n\t\treturn errTimeout\n\t}\n\t// Note: it will overwrite the reqTimeout.\n\treq.SetOptions(config.WithRequestTimeout(timeout))\n\treturn c.Do(ctx, req, resp)\n}\n"
  },
  {
    "path": "pkg/protocol/client/client_test.go",
    "content": "/*\n * Copyright 2024 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage client\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n)\n\nvar firstTime = true\n\ntype MockDoer struct {\n\tmock.Mock\n}\n\nfunc (m *MockDoer) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error {\n\t// this is the real logic in (c *HostClient) doNonNilReqResp method\n\tif len(req.Header.Host()) == 0 {\n\t\treq.Header.SetHostBytes(req.URI().Host())\n\t}\n\n\tif firstTime {\n\t\t// req.Header.Host() is the real host writing to the wire\n\t\tif string(req.Header.Host()) != \"example.com\" {\n\t\t\treturn errors.New(\"host not match\")\n\t\t}\n\t\t// this is the real logic in (c *HostClient) doNonNilReqResp method\n\t\tif len(req.Header.Host()) == 0 {\n\t\t\treq.Header.SetHostBytes(req.URI().Host())\n\t\t}\n\t\tresp.Header.SetCanonical(bytestr.StrLocation, []byte(\"https://a.b.c/foo\"))\n\t\tresp.SetStatusCode(301)\n\t\tfirstTime = false\n\t\treturn nil\n\t}\n\n\tif string(req.Header.Host()) != \"a.b.c\" {\n\t\tresp.SetStatusCode(400)\n\t\treturn errors.New(\"host not match\")\n\t}\n\n\tresp.SetStatusCode(200)\n\n\treturn nil\n}\n\nfunc TestDoRequestFollowRedirects(t *testing.T) {\n\tmockDoer := new(MockDoer)\n\tmockDoer.On(\"Do\", mock.Anything, mock.Anything, mock.Anything).Return(nil)\n\tstatusCode, _, err := DoRequestFollowRedirects(context.Background(), &protocol.Request{}, &protocol.Response{}, \"https://example.com\", defaultMaxRedirectsCount, mockDoer)\n\tassert.NoError(t, err)\n\tassert.Equal(t, 200, statusCode)\n}\n"
  },
  {
    "path": "pkg/protocol/consts/default.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage consts\n\nimport \"time\"\n\nconst (\n\t// *** Server default value ***\n\n\t// DefaultMaxInMemoryFileSize defines the in memory file size when parse\n\t// multipart_form. If the size exceeds, then hertz will write to disk.\n\tDefaultMaxInMemoryFileSize = 16 * 1024 * 1024\n\n\t// *** Client default value start from here ***\n\n\t// DefaultDialTimeout is timeout used by Dialer and DialDualStack\n\t// for establishing TCP connections.\n\tDefaultDialTimeout = time.Second\n\n\t// Deprecated: no longer used as a default. Previously, unconfigured clients\n\t// silently fell back to this value, capping connections even when no limit was\n\t// intended, causing ErrNoFreeConns when busy connections reached this cap.\n\t// Since v0.10.3: MaxConnsPerHost now defaults to 0 (no limit).\n\tDefaultMaxConnsPerHost = 512\n\n\t// DefaultMaxIdleConnDuration is the default duration before idle keep-alive\n\t// connection is closed.\n\tDefaultMaxIdleConnDuration = 10 * time.Second\n\n\t// DefaultMaxIdempotentCallAttempts is the default idempotent calls attempts count.\n\tDefaultMaxIdempotentCallAttempts = 1\n\n\t// DefaultMaxRetryTimes is the default call times of retry\n\tDefaultMaxRetryTimes = 1\n)\n"
  },
  {
    "path": "pkg/protocol/consts/fs.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage consts\n\nimport \"time\"\n\nconst (\n\t// files bigger than this size are sent with sendfile\n\tMaxSmallFileSize = 2 * 4096\n\n\t// FSHandlerCacheDuration is the default expiration duration for inactive\n\t// file handlers opened by FS.\n\tFSHandlerCacheDuration = 10 * time.Second\n\n\t// FSCompressedFileSuffix is the suffix FS adds to the original file names\n\t// when trying to store compressed file under the new file name.\n\t// See FS.Compress for details.\n\tFSCompressedFileSuffix    = \".hertz.gz\"\n\tFsMinCompressRatio        = 0.8\n\tFsMaxCompressibleFileSize = 8 * 1024 * 1024\n)\n"
  },
  {
    "path": "pkg/protocol/consts/headers.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage consts\n\nconst (\n\tHeaderDate = \"Date\"\n\n\tHeaderIfModifiedSince = \"If-Modified-Since\"\n\tHeaderLastModified    = \"Last-Modified\"\n\n\t// Redirects\n\tHeaderLocation = \"Location\"\n\n\t// Transfer coding\n\tHeaderTE               = \"TE\"\n\tHeaderTrailer          = \"Trailer\"\n\tHeaderTrailerLower     = \"trailer\"\n\tHeaderTransferEncoding = \"Transfer-Encoding\"\n\n\t// Controls\n\tHeaderCookie         = \"Cookie\"\n\tHeaderExpect         = \"Expect\"\n\tHeaderMaxForwards    = \"Max-Forwards\"\n\tHeaderSetCookie      = \"Set-Cookie\"\n\tHeaderSetCookieLower = \"set-cookie\"\n\n\t// Connection management\n\tHeaderConnection      = \"Connection\"\n\tHeaderKeepAlive       = \"Keep-Alive\"\n\tHeaderProxyConnection = \"Proxy-Connection\"\n\n\t// Authentication\n\tHeaderAuthorization      = \"Authorization\"\n\tHeaderProxyAuthenticate  = \"Proxy-Authenticate\"\n\tHeaderProxyAuthorization = \"Proxy-Authorization\"\n\tHeaderWWWAuthenticate    = \"WWW-Authenticate\"\n\n\t// Range requests\n\tHeaderAcceptRanges = \"Accept-Ranges\"\n\tHeaderContentRange = \"Content-Range\"\n\tHeaderIfRange      = \"If-Range\"\n\tHeaderRange        = \"Range\"\n\n\t// Response context\n\tHeaderAllow       = \"Allow\"\n\tHeaderServer      = \"Server\"\n\tHeaderServerLower = \"server\"\n\n\t// Request context\n\tHeaderFrom           = \"From\"\n\tHeaderHost           = \"Host\"\n\tHeaderReferer        = \"Referer\"\n\tHeaderReferrerPolicy = \"Referrer-Policy\"\n\tHeaderUserAgent      = \"User-Agent\"\n\n\t// Message body information\n\tHeaderContentEncoding = \"Content-Encoding\"\n\tHeaderContentLanguage = \"Content-Language\"\n\tHeaderContentLength   = \"Content-Length\"\n\tHeaderContentLocation = \"Content-Location\"\n\tHeaderContentType     = \"Content-Type\"\n\n\t// Content negotiation\n\tHeaderAccept         = \"Accept\"\n\tHeaderAcceptCharset  = \"Accept-Charset\"\n\tHeaderAcceptEncoding = \"Accept-Encoding\"\n\tHeaderAcceptLanguage = \"Accept-Language\"\n\tHeaderAltSvc         = \"Alt-Svc\"\n\n\t// Protocol\n\tHTTP11 = \"HTTP/1.1\"\n\tHTTP10 = \"HTTP/1.0\"\n\tHTTP20 = \"HTTP/2.0\"\n\n\t// MIME text\n\tMIMETextPlain             = \"text/plain\"\n\tMIMETextPlainUTF8         = \"text/plain; charset=utf-8\"\n\tMIMETextPlainISO88591     = \"text/plain; charset=iso-8859-1\"\n\tMIMETextPlainFormatFlowed = \"text/plain; format=flowed\"\n\tMIMETextPlainDelSpaceYes  = \"text/plain; delsp=yes\"\n\tMiMETextPlainDelSpaceNo   = \"text/plain; delsp=no\"\n\tMIMETextHtml              = \"text/html\"\n\tMIMETextCss               = \"text/css\"\n\tMIMETextJavascript        = \"text/javascript\"\n\tMIMEMultipartPOSTForm     = \"multipart/form-data\"\n\n\t// MIME application\n\tMIMEApplicationOctetStream  = \"application/octet-stream\"\n\tMIMEApplicationFlash        = \"application/x-shockwave-flash\"\n\tMIMEApplicationHTMLForm     = \"application/x-www-form-urlencoded\"\n\tMIMEApplicationHTMLFormUTF8 = \"application/x-www-form-urlencoded; charset=UTF-8\"\n\tMIMEApplicationTar          = \"application/x-tar\"\n\tMIMEApplicationGZip         = \"application/gzip\"\n\tMIMEApplicationXGZip        = \"application/x-gzip\"\n\tMIMEApplicationBZip2        = \"application/bzip2\"\n\tMIMEApplicationXBZip2       = \"application/x-bzip2\"\n\tMIMEApplicationShell        = \"application/x-sh\"\n\tMIMEApplicationDownload     = \"application/x-msdownload\"\n\tMIMEApplicationJSON         = \"application/json\"\n\tMIMEApplicationJSONUTF8     = \"application/json; charset=utf-8\"\n\tMIMEApplicationXML          = \"application/xml\"\n\tMIMEApplicationXMLUTF8      = \"application/xml; charset=utf-8\"\n\tMIMEApplicationZip          = \"application/zip\"\n\tMIMEApplicationPdf          = \"application/pdf\"\n\tMIMEApplicationWord         = \"application/msword\"\n\tMIMEApplicationExcel        = \"application/vnd.ms-excel\"\n\tMIMEApplicationPPT          = \"application/vnd.ms-powerpoint\"\n\tMIMEApplicationOpenXMLWord  = \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\n\tMIMEApplicationOpenXMLExcel = \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\"\n\tMIMEApplicationOpenXMLPPT   = \"application/vnd.openxmlformats-officedocument.presentationml.presentation\"\n\tMIMEPROTOBUF                = \"application/x-protobuf\"\n\n\t// MIME image\n\tMIMEImageJPEG         = \"image/jpeg\"\n\tMIMEImagePNG          = \"image/png\"\n\tMIMEImageGIF          = \"image/gif\"\n\tMIMEImageBitmap       = \"image/bmp\"\n\tMIMEImageWebP         = \"image/webp\"\n\tMIMEImageIco          = \"image/x-icon\"\n\tMIMEImageMicrosoftICO = \"image/vnd.microsoft.icon\"\n\tMIMEImageTIFF         = \"image/tiff\"\n\tMIMEImageSVG          = \"image/svg+xml\"\n\tMIMEImagePhotoshop    = \"image/vnd.adobe.photoshop\"\n\n\t// MIME audio\n\tMIMEAudioBasic     = \"audio/basic\"\n\tMIMEAudioL24       = \"audio/L24\"\n\tMIMEAudioMP3       = \"audio/mp3\"\n\tMIMEAudioMP4       = \"audio/mp4\"\n\tMIMEAudioMPEG      = \"audio/mpeg\"\n\tMIMEAudioOggVorbis = \"audio/ogg\"\n\tMIMEAudioWAVE      = \"audio/vnd.wave\"\n\tMIMEAudioWebM      = \"audio/webm\"\n\tMIMEAudioAAC       = \"audio/x-aac\"\n\tMIMEAudioAIFF      = \"audio/x-aiff\"\n\tMIMEAudioMIDI      = \"audio/x-midi\"\n\tMIMEAudioM3U       = \"audio/x-mpegurl\"\n\tMIMEAudioRealAudio = \"audio/x-pn-realaudio\"\n\n\t// MIME video\n\tMIMEVideoMPEG          = \"video/mpeg\"\n\tMIMEVideoOgg           = \"video/ogg\"\n\tMIMEVideoMP4           = \"video/mp4\"\n\tMIMEVideoQuickTime     = \"video/quicktime\"\n\tMIMEVideoWinMediaVideo = \"video/x-ms-wmv\"\n\tMIMEVideWebM           = \"video/webm\"\n\tMIMEVideoFlashVideo    = \"video/x-flv\"\n\tMIMEVideo3GPP          = \"video/3gpp\"\n\tMIMEVideoAVI           = \"video/x-msvideo\"\n\tMIMEVideoMatroska      = \"video/x-matroska\"\n)\n"
  },
  {
    "path": "pkg/protocol/consts/http2.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage consts\n\n// ClientPreface is the string that must be sent by new\n// connections from clients.\nconst ClientPreface = \"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\"\n"
  },
  {
    "path": "pkg/protocol/consts/methods.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage consts\n\n// HTTP methods were copied from net/http.\nconst (\n\tMethodGet     = \"GET\"     // RFC 7231, 4.3.1\n\tMethodHead    = \"HEAD\"    // RFC 7231, 4.3.2\n\tMethodPost    = \"POST\"    // RFC 7231, 4.3.3\n\tMethodPut     = \"PUT\"     // RFC 7231, 4.3.4\n\tMethodPatch   = \"PATCH\"   // RFC 5789\n\tMethodDelete  = \"DELETE\"  // RFC 7231, 4.3.5\n\tMethodConnect = \"CONNECT\" // RFC 7231, 4.3.6\n\tMethodOptions = \"OPTIONS\" // RFC 7231, 4.3.7\n\tMethodTrace   = \"TRACE\"   // RFC 7231, 4.3.8\n)\n"
  },
  {
    "path": "pkg/protocol/consts/status.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage consts\n\nimport (\n\t\"fmt\"\n\t\"sync/atomic\"\n)\n\nconst (\n\tstatusMessageMin = 100\n\tstatusMessageMax = 511\n)\n\n// HTTP status codes were stolen from net/http.\nconst (\n\tStatusContinue           = 100 // RFC 7231, 6.2.1\n\tStatusSwitchingProtocols = 101 // RFC 7231, 6.2.2\n\tStatusProcessing         = 102 // RFC 2518, 10.1\n\n\tStatusOK                   = 200 // RFC 7231, 6.3.1\n\tStatusCreated              = 201 // RFC 7231, 6.3.2\n\tStatusAccepted             = 202 // RFC 7231, 6.3.3\n\tStatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4\n\tStatusNoContent            = 204 // RFC 7231, 6.3.5\n\tStatusResetContent         = 205 // RFC 7231, 6.3.6\n\tStatusPartialContent       = 206 // RFC 7233, 4.1\n\tStatusMultiStatus          = 207 // RFC 4918, 11.1\n\tStatusAlreadyReported      = 208 // RFC 5842, 7.1\n\tStatusIMUsed               = 226 // RFC 3229, 10.4.1\n\n\tStatusMultipleChoices   = 300 // RFC 7231, 6.4.1\n\tStatusMovedPermanently  = 301 // RFC 7231, 6.4.2\n\tStatusFound             = 302 // RFC 7231, 6.4.3\n\tStatusSeeOther          = 303 // RFC 7231, 6.4.4\n\tStatusNotModified       = 304 // RFC 7232, 4.1\n\tStatusUseProxy          = 305 // RFC 7231, 6.4.5\n\t_                       = 306 // RFC 7231, 6.4.6 (Unused)\n\tStatusTemporaryRedirect = 307 // RFC 7231, 6.4.7\n\tStatusPermanentRedirect = 308 // RFC 7538, 3\n\n\tStatusBadRequest                   = 400 // RFC 7231, 6.5.1\n\tStatusUnauthorized                 = 401 // RFC 7235, 3.1\n\tStatusPaymentRequired              = 402 // RFC 7231, 6.5.2\n\tStatusForbidden                    = 403 // RFC 7231, 6.5.3\n\tStatusNotFound                     = 404 // RFC 7231, 6.5.4\n\tStatusMethodNotAllowed             = 405 // RFC 7231, 6.5.5\n\tStatusNotAcceptable                = 406 // RFC 7231, 6.5.6\n\tStatusProxyAuthRequired            = 407 // RFC 7235, 3.2\n\tStatusRequestTimeout               = 408 // RFC 7231, 6.5.7\n\tStatusConflict                     = 409 // RFC 7231, 6.5.8\n\tStatusGone                         = 410 // RFC 7231, 6.5.9\n\tStatusLengthRequired               = 411 // RFC 7231, 6.5.10\n\tStatusPreconditionFailed           = 412 // RFC 7232, 4.2\n\tStatusRequestEntityTooLarge        = 413 // RFC 7231, 6.5.11\n\tStatusRequestURITooLong            = 414 // RFC 7231, 6.5.12\n\tStatusUnsupportedMediaType         = 415 // RFC 7231, 6.5.13\n\tStatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4\n\tStatusExpectationFailed            = 417 // RFC 7231, 6.5.14\n\tStatusTeapot                       = 418 // RFC 7168, 2.3.3\n\tStatusUnprocessableEntity          = 422 // RFC 4918, 11.2\n\tStatusLocked                       = 423 // RFC 4918, 11.3\n\tStatusFailedDependency             = 424 // RFC 4918, 11.4\n\tStatusUpgradeRequired              = 426 // RFC 7231, 6.5.15\n\tStatusPreconditionRequired         = 428 // RFC 6585, 3\n\tStatusTooManyRequests              = 429 // RFC 6585, 4\n\tStatusRequestHeaderFieldsTooLarge  = 431 // RFC 6585, 5\n\tStatusUnavailableForLegalReasons   = 451 // RFC 7725, 3\n\n\tStatusInternalServerError           = 500 // RFC 7231, 6.6.1\n\tStatusNotImplemented                = 501 // RFC 7231, 6.6.2\n\tStatusBadGateway                    = 502 // RFC 7231, 6.6.3\n\tStatusServiceUnavailable            = 503 // RFC 7231, 6.6.4\n\tStatusGatewayTimeout                = 504 // RFC 7231, 6.6.5\n\tStatusHTTPVersionNotSupported       = 505 // RFC 7231, 6.6.6\n\tStatusVariantAlsoNegotiates         = 506 // RFC 2295, 8.1\n\tStatusInsufficientStorage           = 507 // RFC 4918, 11.5\n\tStatusLoopDetected                  = 508 // RFC 5842, 7.2\n\tStatusNotExtended                   = 510 // RFC 2774, 7\n\tStatusNetworkAuthenticationRequired = 511 // RFC 6585, 6\n)\n\nvar (\n\tstatusLines atomic.Value\n\n\tstatusMessages = map[int]string{\n\t\tStatusContinue:           \"Continue\",\n\t\tStatusSwitchingProtocols: \"Switching Protocols\",\n\t\tStatusProcessing:         \"Processing\",\n\n\t\tStatusOK:                   \"OK\",\n\t\tStatusCreated:              \"Created\",\n\t\tStatusAccepted:             \"Accepted\",\n\t\tStatusNonAuthoritativeInfo: \"Non-Authoritative Information\",\n\t\tStatusNoContent:            \"No Content\",\n\t\tStatusResetContent:         \"Reset Content\",\n\t\tStatusPartialContent:       \"Partial Content\",\n\t\tStatusMultiStatus:          \"Multi-Status\",\n\t\tStatusAlreadyReported:      \"Already Reported\",\n\t\tStatusIMUsed:               \"IM Used\",\n\n\t\tStatusMultipleChoices:   \"Multiple Choices\",\n\t\tStatusMovedPermanently:  \"Moved Permanently\",\n\t\tStatusFound:             \"Found\",\n\t\tStatusSeeOther:          \"See Other\",\n\t\tStatusNotModified:       \"Not Modified\",\n\t\tStatusUseProxy:          \"Use Proxy\",\n\t\tStatusTemporaryRedirect: \"Temporary Redirect\",\n\t\tStatusPermanentRedirect: \"Permanent Redirect\",\n\n\t\tStatusBadRequest:                   \"Bad Request\",\n\t\tStatusUnauthorized:                 \"Unauthorized\",\n\t\tStatusPaymentRequired:              \"Payment Required\",\n\t\tStatusForbidden:                    \"Forbidden\",\n\t\tStatusNotFound:                     \"Not Found\",\n\t\tStatusMethodNotAllowed:             \"Method Not Allowed\",\n\t\tStatusNotAcceptable:                \"Not Acceptable\",\n\t\tStatusProxyAuthRequired:            \"Proxy Authentication Required\",\n\t\tStatusRequestTimeout:               \"Request Timeout\",\n\t\tStatusConflict:                     \"Conflict\",\n\t\tStatusGone:                         \"Gone\",\n\t\tStatusLengthRequired:               \"Length Required\",\n\t\tStatusPreconditionFailed:           \"Precondition Failed\",\n\t\tStatusRequestEntityTooLarge:        \"Request Entity Too Large\",\n\t\tStatusRequestURITooLong:            \"Request URI Too Long\",\n\t\tStatusUnsupportedMediaType:         \"Unsupported Media Type\",\n\t\tStatusRequestedRangeNotSatisfiable: \"Requested Range Not Satisfiable\",\n\t\tStatusExpectationFailed:            \"Expectation Failed\",\n\t\tStatusTeapot:                       \"I'm a teapot\",\n\t\tStatusUnprocessableEntity:          \"Unprocessable Entity\",\n\t\tStatusLocked:                       \"Locked\",\n\t\tStatusFailedDependency:             \"Failed Dependency\",\n\t\tStatusUpgradeRequired:              \"Upgrade Required\",\n\t\tStatusPreconditionRequired:         \"Precondition Required\",\n\t\tStatusTooManyRequests:              \"Too Many Requests\",\n\t\tStatusRequestHeaderFieldsTooLarge:  \"Request Header Fields Too Large\",\n\t\tStatusUnavailableForLegalReasons:   \"Unavailable For Legal Reasons\",\n\n\t\tStatusInternalServerError:           \"Internal Server Error\",\n\t\tStatusNotImplemented:                \"Not Implemented\",\n\t\tStatusBadGateway:                    \"Bad Gateway\",\n\t\tStatusServiceUnavailable:            \"Service Unavailable\",\n\t\tStatusGatewayTimeout:                \"Gateway Timeout\",\n\t\tStatusHTTPVersionNotSupported:       \"HTTP Version Not Supported\",\n\t\tStatusVariantAlsoNegotiates:         \"Variant Also Negotiates\",\n\t\tStatusInsufficientStorage:           \"Insufficient Storage\",\n\t\tStatusLoopDetected:                  \"Loop Detected\",\n\t\tStatusNotExtended:                   \"Not Extended\",\n\t\tStatusNetworkAuthenticationRequired: \"Network Authentication Required\",\n\t}\n)\n\n// StatusMessage returns HTTP status message for the given status code.\nfunc StatusMessage(statusCode int) string {\n\tif statusCode < statusMessageMin || statusCode > statusMessageMax {\n\t\treturn \"Unknown Status Code\"\n\t}\n\n\ts := statusMessages[statusCode]\n\tif s == \"\" {\n\t\ts = \"Unknown Status Code\"\n\t}\n\treturn s\n}\n\nfunc init() {\n\tstatusLines.Store(make(map[int][]byte))\n}\n\nfunc StatusLine(statusCode int) []byte {\n\tm := statusLines.Load().(map[int][]byte)\n\th := m[statusCode]\n\tif h != nil {\n\t\treturn h\n\t}\n\n\tstatusText := StatusMessage(statusCode)\n\n\th = []byte(fmt.Sprintf(\"HTTP/1.1 %d %s\\r\\n\", statusCode, statusText))\n\tnewM := make(map[int][]byte, len(m)+1)\n\tfor k, v := range m {\n\t\tnewM[k] = v\n\t}\n\tnewM[statusCode] = h\n\tstatusLines.Store(newM)\n\treturn h\n}\n"
  },
  {
    "path": "pkg/protocol/cookie.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/internal/nocopy\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n)\n\nconst (\n\t// CookieSameSiteDisabled removes the SameSite flag\n\tCookieSameSiteDisabled CookieSameSite = iota\n\t// CookieSameSiteDefaultMode sets the SameSite flag\n\tCookieSameSiteDefaultMode\n\t// CookieSameSiteLaxMode sets the SameSite flag with the \"Lax\" parameter\n\tCookieSameSiteLaxMode\n\t// CookieSameSiteStrictMode sets the SameSite flag with the \"Strict\" parameter\n\tCookieSameSiteStrictMode\n\t// CookieSameSiteNoneMode sets the SameSite flag with the \"None\" parameter\n\t// see https://tools.ietf.org/html/draft-west-cookie-incrementalism-00\n\t// third-party cookies are phasing out, use Partitioned cookies instead\n\t// see https://developers.google.com/privacy-sandbox/3pcd\n\tCookieSameSiteNoneMode\n)\n\nvar zeroTime time.Time\n\nvar (\n\terrNoCookies = errors.NewPublic(\"no cookies found\")\n\n\t// CookieExpireDelete may be set on Cookie.Expire for expiring the given cookie.\n\tCookieExpireDelete = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)\n\n\t// CookieExpireUnlimited indicates that the cookie doesn't expire.\n\tCookieExpireUnlimited = zeroTime\n)\n\n// CookieSameSite is an enum for the mode in which the SameSite flag should be set for the given cookie.\n// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.\ntype CookieSameSite int\n\n// Cookie represents HTTP response cookie.\n//\n// Do not copy Cookie objects. Create new object and use CopyTo instead.\n//\n// Cookie instance MUST NOT be used from concurrently running goroutines.\ntype Cookie struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\tkey    []byte\n\tvalue  []byte\n\texpire time.Time\n\tmaxAge int\n\tdomain []byte\n\tpath   []byte\n\n\thttpOnly bool\n\tsecure   bool\n\t// A partitioned third-party cookie is tied to the top-level site\n\t// where it's initially set and cannot be accessed from elsewhere.\n\tpartitioned bool\n\tsameSite    CookieSameSite\n\n\tbufKV argsKV\n\tbuf   []byte\n}\n\nvar cookiePool = &sync.Pool{\n\tNew: func() interface{} {\n\t\treturn &Cookie{}\n\t},\n}\n\n// AcquireCookie returns an empty Cookie object from the pool.\n//\n// The returned object may be returned back to the pool with ReleaseCookie.\n// This allows reducing GC load.\nfunc AcquireCookie() *Cookie {\n\treturn cookiePool.Get().(*Cookie)\n}\n\n// ReleaseCookie returns the Cookie object acquired with AcquireCookie back\n// to the pool.\n//\n// Do not access released Cookie object, otherwise data races may occur.\nfunc ReleaseCookie(c *Cookie) {\n\tc.Reset()\n\tcookiePool.Put(c)\n}\n\n// SetDomain sets cookie domain.\nfunc (c *Cookie) SetDomain(domain string) {\n\tc.domain = append(c.domain[:0], domain...)\n}\n\n// SetPath sets cookie path.\nfunc (c *Cookie) SetPath(path string) {\n\tc.buf = append(c.buf[:0], path...)\n\tc.path = normalizePath(c.path, c.buf)\n}\n\n// SetPathBytes sets cookie path.\nfunc (c *Cookie) SetPathBytes(path []byte) {\n\tc.buf = append(c.buf[:0], path...)\n\tc.path = normalizePath(c.path, c.buf)\n}\n\n// SetExpire sets cookie expiration time.\n//\n// Set expiration time to CookieExpireDelete for expiring (deleting)\n// the cookie on the client.\n//\n// By default cookie lifetime is limited by browser session.\nfunc (c *Cookie) SetExpire(expire time.Time) {\n\tc.expire = expire\n}\n\n// SetKey sets cookie name.\nfunc (c *Cookie) SetKey(key string) {\n\tc.key = append(c.key[:0], key...)\n}\n\n// SetKeyBytes sets cookie name.\nfunc (c *Cookie) SetKeyBytes(key []byte) {\n\tc.key = append(c.key[:0], key...)\n}\n\n// SetValue sets cookie value.\nfunc (c *Cookie) SetValue(value string) {\n\twarnIfInvalid(bytesconv.S2b(value))\n\tc.value = append(c.value[:0], value...)\n}\n\n// SetValueBytes sets cookie value.\nfunc (c *Cookie) SetValueBytes(value []byte) {\n\twarnIfInvalid(value)\n\tc.value = append(c.value[:0], value...)\n}\n\n// AppendBytes appends cookie representation to dst and returns\n// the extended dst.\nfunc (c *Cookie) AppendBytes(dst []byte) []byte {\n\tif len(c.key) > 0 {\n\t\tdst = append(dst, c.key...)\n\t\tdst = append(dst, '=')\n\t}\n\tdst = append(dst, c.value...)\n\n\tif c.maxAge != 0 {\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, bytestr.StrCookieMaxAge...)\n\t\tdst = append(dst, '=')\n\t\tif c.maxAge < 0 {\n\t\t\tdst = append(dst, '0')\n\t\t} else {\n\t\t\tdst = bytesconv.AppendUint(dst, c.maxAge)\n\t\t}\n\t}\n\tif !c.expire.IsZero() {\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, bytestr.StrCookieExpires...)\n\t\tdst = append(dst, '=')\n\t\tdst = bytesconv.AppendHTTPDate(dst, c.expire)\n\t}\n\n\tif len(c.domain) > 0 {\n\t\tdst = appendCookiePart(dst, bytestr.StrCookieDomain, c.domain)\n\t}\n\tif len(c.path) > 0 {\n\t\tdst = appendCookiePart(dst, bytestr.StrCookiePath, c.path)\n\t}\n\tif c.httpOnly {\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, bytestr.StrCookieHTTPOnly...)\n\t}\n\tif c.secure {\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, bytestr.StrCookieSecure...)\n\t}\n\tswitch c.sameSite {\n\tcase CookieSameSiteDefaultMode:\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, bytestr.StrCookieSameSite...)\n\tcase CookieSameSiteLaxMode:\n\t\tdst = appendCookiePart(dst, bytestr.StrCookieSameSite, bytestr.StrCookieSameSiteLax)\n\tcase CookieSameSiteStrictMode:\n\t\tdst = appendCookiePart(dst, bytestr.StrCookieSameSite, bytestr.StrCookieSameSiteStrict)\n\tcase CookieSameSiteNoneMode:\n\t\tdst = appendCookiePart(dst, bytestr.StrCookieSameSite, bytestr.StrCookieSameSiteNone)\n\t}\n\n\tif c.partitioned {\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, bytestr.StrCookiePartitioned...)\n\t}\n\n\treturn dst\n}\n\nfunc appendCookiePart(dst, key, value []byte) []byte {\n\tdst = append(dst, ';', ' ')\n\tdst = append(dst, key...)\n\tdst = append(dst, '=')\n\treturn append(dst, value...)\n}\n\nfunc appendRequestCookieBytes(dst []byte, cookies []argsKV) []byte {\n\tfor i, n := 0, len(cookies); i < n; i++ {\n\t\tkv := &cookies[i]\n\t\tif len(kv.key) > 0 {\n\t\t\tdst = append(dst, kv.key...)\n\t\t\tdst = append(dst, '=')\n\t\t}\n\t\tdst = append(dst, kv.value...)\n\t\tif i+1 < n {\n\t\t\tdst = append(dst, ';', ' ')\n\t\t}\n\t}\n\treturn dst\n}\n\n// For Response we can not use the above function as response cookies\n// already contain the key= in the value.\nfunc appendResponseCookieBytes(dst []byte, cookies []argsKV) []byte {\n\tfor i, n := 0, len(cookies); i < n; i++ {\n\t\tkv := &cookies[i]\n\t\tdst = append(dst, kv.value...)\n\t\tif i+1 < n {\n\t\t\tdst = append(dst, ';', ' ')\n\t\t}\n\t}\n\treturn dst\n}\n\ntype cookieScanner struct {\n\tb []byte\n}\n\nfunc parseRequestCookies(cookies []argsKV, src []byte) []argsKV {\n\tvar s cookieScanner\n\ts.b = src\n\tvar kv *argsKV\n\tcookies, kv = allocArg(cookies)\n\tfor s.next(kv) {\n\t\tif len(kv.key) > 0 || len(kv.value) > 0 {\n\t\t\tcookies, kv = allocArg(cookies)\n\t\t}\n\t}\n\treturn releaseArg(cookies)\n}\n\nfunc (s *cookieScanner) next(kv *argsKV) bool {\n\tb := s.b\n\tif len(b) == 0 {\n\t\treturn false\n\t}\n\n\tisKey := true\n\tk := 0\n\tfor i, c := range b {\n\t\tswitch c {\n\t\tcase '=':\n\t\t\tif isKey {\n\t\t\t\tisKey = false\n\t\t\t\tkv.key = decodeCookieArg(kv.key, b[:i], false)\n\t\t\t\tk = i + 1\n\t\t\t}\n\t\tcase ';':\n\t\t\tif isKey {\n\t\t\t\tkv.key = kv.key[:0]\n\t\t\t}\n\t\t\tkv.value = decodeCookieArg(kv.value, b[k:i], true)\n\t\t\ts.b = b[i+1:]\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif isKey {\n\t\tkv.key = kv.key[:0]\n\t}\n\tkv.value = decodeCookieArg(kv.value, b[k:], true)\n\ts.b = b[len(b):]\n\treturn true\n}\n\n// Key returns cookie name.\n//\n// The returned value is valid until the next Cookie modification method call.\nfunc (c *Cookie) Key() []byte {\n\treturn c.key\n}\n\n// Cookie returns cookie representation.\n//\n// The returned value is valid until the next call to Cookie methods.\nfunc (c *Cookie) Cookie() []byte {\n\tc.buf = c.AppendBytes(c.buf[:0])\n\treturn c.buf\n}\n\n// Reset clears the cookie.\nfunc (c *Cookie) Reset() {\n\tc.key = c.key[:0]\n\tc.value = c.value[:0]\n\tc.expire = zeroTime\n\tc.maxAge = 0\n\tc.domain = c.domain[:0]\n\tc.path = c.path[:0]\n\tc.httpOnly = false\n\tc.secure = false\n\tc.sameSite = CookieSameSiteDisabled\n\tc.partitioned = false\n}\n\n// Value returns cookie value.\n//\n// The returned value is valid until the next Cookie modification method call.\nfunc (c *Cookie) Value() []byte {\n\treturn c.value\n}\n\n// Parse parses Set-Cookie header.\nfunc (c *Cookie) Parse(src string) error {\n\tc.buf = append(c.buf[:0], src...)\n\treturn c.ParseBytes(c.buf)\n}\n\n// ParseBytes parses Set-Cookie header.\nfunc (c *Cookie) ParseBytes(src []byte) error {\n\tc.Reset()\n\n\tvar s cookieScanner\n\ts.b = src\n\n\tkv := &c.bufKV\n\tif !s.next(kv) {\n\t\treturn errNoCookies\n\t}\n\n\tc.key = append(c.key[:0], kv.key...)\n\tc.value = append(c.value[:0], kv.value...)\n\n\tfor s.next(kv) {\n\t\tif len(kv.key) != 0 {\n\t\t\t// Case-insensitive switch on first char\n\t\t\tswitch kv.key[0] | 0x20 {\n\t\t\tcase 'm':\n\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookieMaxAge, kv.key) {\n\t\t\t\t\tmaxAge, err := bytesconv.ParseUint(kv.value)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tc.maxAge = maxAge\n\t\t\t\t}\n\n\t\t\tcase 'e': // \"expires\"\n\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookieExpires, kv.key) {\n\t\t\t\t\tv := bytesconv.B2s(kv.value)\n\t\t\t\t\t// Try the same two formats as net/http\n\t\t\t\t\t// See: https://github.com/golang/go/blob/00379be17e63a5b75b3237819392d2dc3b313a27/src/net/http/cookie.go#L133-L135\n\t\t\t\t\texptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\texptime, err = time.Parse(\"Mon, 02-Jan-2006 15:04:05 MST\", v)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tc.expire = exptime\n\t\t\t\t}\n\n\t\t\tcase 'd': // \"domain\"\n\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookieDomain, kv.key) {\n\t\t\t\t\tc.domain = append(c.domain[:0], kv.value...)\n\t\t\t\t}\n\n\t\t\tcase 'p': // \"path\"\n\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookiePath, kv.key) {\n\t\t\t\t\tc.path = append(c.path[:0], kv.value...)\n\t\t\t\t}\n\n\t\t\tcase 's': // \"samesite\"\n\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookieSameSite, kv.key) {\n\t\t\t\t\t// Case-insensitive switch on first char\n\t\t\t\t\tswitch kv.value[0] | 0x20 {\n\t\t\t\t\tcase 'l': // \"lax\"\n\t\t\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookieSameSiteLax, kv.value) {\n\t\t\t\t\t\t\tc.sameSite = CookieSameSiteLaxMode\n\t\t\t\t\t\t}\n\t\t\t\t\tcase 's': // \"strict\"\n\t\t\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookieSameSiteStrict, kv.value) {\n\t\t\t\t\t\t\tc.sameSite = CookieSameSiteStrictMode\n\t\t\t\t\t\t}\n\t\t\t\t\tcase 'n': // \"none\"\n\t\t\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookieSameSiteNone, kv.value) {\n\t\t\t\t\t\t\tc.sameSite = CookieSameSiteNoneMode\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if len(kv.value) != 0 {\n\t\t\t// Case-insensitive switch on first char\n\t\t\tswitch kv.value[0] | 0x20 {\n\t\t\tcase 'h': // \"httponly\"\n\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookieHTTPOnly, kv.value) {\n\t\t\t\t\tc.httpOnly = true\n\t\t\t\t}\n\n\t\t\tcase 's': // \"secure\"\n\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookieSecure, kv.value) {\n\t\t\t\t\tc.secure = true\n\t\t\t\t} else if utils.CaseInsensitiveCompare(bytestr.StrCookieSameSite, kv.value) {\n\t\t\t\t\tc.sameSite = CookieSameSiteDefaultMode\n\t\t\t\t}\n\t\t\tcase 'p': // \"partitioned\"\n\t\t\t\tif utils.CaseInsensitiveCompare(bytestr.StrCookiePartitioned, kv.value) {\n\t\t\t\t\tc.partitioned = true\n\t\t\t\t}\n\t\t\t}\n\t\t} // else empty or no match\n\t}\n\n\treturn nil\n}\n\n// MaxAge returns the seconds until the cookie is meant to expire or 0\n// if no max age.\nfunc (c *Cookie) MaxAge() int {\n\treturn c.maxAge\n}\n\n// SetMaxAge sets cookie expiration time based on seconds.\n//\n// Values:\n//\n//\t> 0: Set max-age to the specified number of seconds\n//\t= 0: Unset the max-age attribute (no max-age appears in the cookie)\n//\t< 0: Set max-age=0 to instruct the browser to immediately delete the cookie\nfunc (c *Cookie) SetMaxAge(seconds int) {\n\tc.maxAge = seconds\n}\n\n// Expire returns cookie expiration time.\n//\n// CookieExpireUnlimited is returned if cookie doesn't expire\nfunc (c *Cookie) Expire() time.Time {\n\texpire := c.expire\n\tif expire.IsZero() {\n\t\texpire = CookieExpireUnlimited\n\t}\n\treturn expire\n}\n\n// Domain returns cookie domain.\n//\n// The returned domain is valid until the next Cookie modification method call.\nfunc (c *Cookie) Domain() []byte {\n\treturn c.domain\n}\n\n// Path returns cookie path.\nfunc (c *Cookie) Path() []byte {\n\treturn c.path\n}\n\n// Secure returns true if the cookie is secure.\nfunc (c *Cookie) Secure() bool {\n\treturn c.secure\n}\n\n// SetSecure sets cookie's secure flag to the given value.\nfunc (c *Cookie) SetSecure(secure bool) {\n\tc.secure = secure\n}\n\n// SameSite returns the SameSite mode.\nfunc (c *Cookie) SameSite() CookieSameSite {\n\treturn c.sameSite\n}\n\n// Partitioned returns if cookie is partitioned.\nfunc (c *Cookie) Partitioned() bool {\n\treturn c.partitioned\n}\n\n// SetSameSite sets the cookie's SameSite flag to the given value.\n// set value CookieSameSiteNoneMode will set Secure to true also to avoid browser rejection\nfunc (c *Cookie) SetSameSite(mode CookieSameSite) {\n\tc.sameSite = mode\n\tif mode == CookieSameSiteNoneMode {\n\t\tc.SetSecure(true)\n\t}\n}\n\n// HTTPOnly returns true if the cookie is http only.\nfunc (c *Cookie) HTTPOnly() bool {\n\treturn c.httpOnly\n}\n\n// SetHTTPOnly sets cookie's httpOnly flag to the given value.\nfunc (c *Cookie) SetHTTPOnly(httpOnly bool) {\n\tc.httpOnly = httpOnly\n}\n\n// SetPartitioned sets cookie as partitioned. Setting Partitioned to true will also set Secure.\nfunc (c *Cookie) SetPartitioned(partitioned bool) {\n\tc.partitioned = partitioned\n\tif partitioned {\n\t\tc.SetSecure(true)\n\t}\n}\n\n// String returns cookie representation.\nfunc (c *Cookie) String() string {\n\treturn string(c.Cookie())\n}\n\nfunc decodeCookieArg(dst, src []byte, skipQuotes bool) []byte {\n\tfor len(src) > 0 && src[0] == ' ' {\n\t\tsrc = src[1:]\n\t}\n\tfor len(src) > 0 && src[len(src)-1] == ' ' {\n\t\tsrc = src[:len(src)-1]\n\t}\n\tif skipQuotes {\n\t\tif len(src) > 1 && src[0] == '\"' && src[len(src)-1] == '\"' {\n\t\t\tsrc = src[1 : len(src)-1]\n\t\t}\n\t}\n\treturn append(dst[:0], src...)\n}\n\nfunc getCookieKey(dst, src []byte) []byte {\n\tn := bytes.IndexByte(src, '=')\n\tif n >= 0 {\n\t\tsrc = src[:n]\n\t}\n\treturn decodeCookieArg(dst, src, false)\n}\n\nfunc warnIfInvalid(value []byte) bool {\n\tfor i := range value {\n\t\tif bytesconv.ValidCookieValueTable[value[i]] == 0 {\n\t\t\thlog.SystemLogger().Warnf(\"Invalid byte %q in Cookie.Value, \"+\n\t\t\t\t\"it may cause compatibility problems with user agents\", value[i])\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/protocol/cookie_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestCookieAppendBytes(t *testing.T) {\n\tt.Parallel()\n\n\tc := &Cookie{}\n\n\ttestCookieAppendBytes(t, c, \"\", \"bar\", \"bar\")\n\ttestCookieAppendBytes(t, c, \"foo\", \"\", \"foo=\")\n\ttestCookieAppendBytes(t, c, \"ффф\", \"12 лодлы\", \"ффф=12 лодлы\")\n\n\tc.SetDomain(\"foobar.com\")\n\ttestCookieAppendBytes(t, c, \"a\", \"b\", \"a=b; domain=foobar.com\")\n\n\tc.SetPath(\"/a/b\")\n\ttestCookieAppendBytes(t, c, \"aa\", \"bb\", \"aa=bb; domain=foobar.com; path=/a/b\")\n\n\tc.SetExpire(CookieExpireDelete)\n\ttestCookieAppendBytes(t, c, \"xxx\", \"yyy\", \"xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b\")\n\n\tc.SetPartitioned(true)\n\ttestCookieAppendBytes(t, c, \"xxx\", \"yyy\", \"xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b; secure; Partitioned\")\n}\n\nfunc testCookieAppendBytes(t *testing.T, c *Cookie, key, value, expectedS string) {\n\tc.SetKey(key)\n\tc.SetValue(value)\n\tresult := string(c.AppendBytes(nil))\n\tif result != expectedS {\n\t\tt.Fatalf(\"Unexpected cookie %q. Expecting %q\", result, expectedS)\n\t}\n}\n\nfunc TestParseRequestCookies(t *testing.T) {\n\tt.Parallel()\n\n\ttestParseRequestCookies(t, \"\", \"\")\n\ttestParseRequestCookies(t, \"=\", \"\")\n\ttestParseRequestCookies(t, \"foo\", \"foo\")\n\ttestParseRequestCookies(t, \"=foo\", \"foo\")\n\ttestParseRequestCookies(t, \"bar=\", \"bar=\")\n\ttestParseRequestCookies(t, \"xxx=aa;bb=c; =d; ;;e=g\", \"xxx=aa; bb=c; d; e=g\")\n\ttestParseRequestCookies(t, \"a;b;c; d=1;d=2\", \"a; b; c; d=1; d=2\")\n\ttestParseRequestCookies(t, \"   %D0%B8%D0%B2%D0%B5%D1%82=a%20b%3Bc   ;s%20s=aaa  \", \"%D0%B8%D0%B2%D0%B5%D1%82=a%20b%3Bc; s%20s=aaa\")\n}\n\nfunc testParseRequestCookies(t *testing.T, s, expectedS string) {\n\tcookies := parseRequestCookies(nil, []byte(s))\n\tss := string(appendRequestCookieBytes(nil, cookies))\n\tif ss != expectedS {\n\t\tt.Fatalf(\"Unexpected cookies after parsing: %q. Expecting %q. String to parse %q\", ss, expectedS, s)\n\t}\n}\n\nfunc TestAppendRequestCookieBytes(t *testing.T) {\n\tt.Parallel()\n\n\ttestAppendRequestCookieBytes(t, \"=\", \"\")\n\ttestAppendRequestCookieBytes(t, \"foo=\", \"foo=\")\n\ttestAppendRequestCookieBytes(t, \"=bar\", \"bar\")\n\ttestAppendRequestCookieBytes(t, \"привет=a bc&s s=aaa\", \"привет=a bc; s s=aaa\")\n}\n\nfunc testAppendRequestCookieBytes(t *testing.T, s, expectedS string) {\n\tkvs := strings.Split(s, \"&\")\n\tcookies := make([]argsKV, 0, len(kvs))\n\tfor _, ss := range kvs {\n\t\ttmp := strings.SplitN(ss, \"=\", 2)\n\t\tif len(tmp) != 2 {\n\t\t\tt.Fatalf(\"Cannot find '=' in %q, part of %q\", ss, s)\n\t\t}\n\t\tcookies = append(cookies, argsKV{\n\t\t\tkey:   []byte(tmp[0]),\n\t\t\tvalue: []byte(tmp[1]),\n\t\t})\n\t}\n\n\tprefix := \"foobar\"\n\tresult := string(appendRequestCookieBytes([]byte(prefix), cookies))\n\tif result[:len(prefix)] != prefix {\n\t\tt.Fatalf(\"unexpected prefix %q. Expecting %q for cookie %q\", result[:len(prefix)], prefix, s)\n\t}\n\tresult = result[len(prefix):]\n\tif result != expectedS {\n\t\tt.Fatalf(\"Unexpected result %q. Expecting %q for cookie %q\", result, expectedS, s)\n\t}\n}\n\nfunc TestCookieSecureHTTPOnly(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tif err := c.Parse(\"foo=bar; HttpOnly; secure\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif !c.Secure() {\n\t\tt.Fatalf(\"secure must be set\")\n\t}\n\tif !c.HTTPOnly() {\n\t\tt.Fatalf(\"HttpOnly must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; secure\") {\n\t\tt.Fatalf(\"missing secure flag in cookie %q\", s)\n\t}\n\tif !strings.Contains(s, \"; HttpOnly\") {\n\t\tt.Fatalf(\"missing HttpOnly flag in cookie %q\", s)\n\t}\n}\n\nfunc TestCookieSecure(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tif err := c.Parse(\"foo=bar; secure\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif !c.Secure() {\n\t\tt.Fatalf(\"secure must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; secure\") {\n\t\tt.Fatalf(\"missing secure flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif c.Secure() {\n\t\tt.Fatalf(\"Unexpected secure flag set\")\n\t}\n\ts = c.String()\n\tif strings.Contains(s, \"secure\") {\n\t\tt.Fatalf(\"unexpected secure flag in cookie %q\", s)\n\t}\n}\n\nfunc TestCookieSameSite(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tif err := c.Parse(\"foo=bar; samesite\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif c.SameSite() != CookieSameSiteDefaultMode {\n\t\tt.Fatalf(\"SameSite must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; SameSite\") {\n\t\tt.Fatalf(\"missing SameSite flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar; samesite=lax\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif c.SameSite() != CookieSameSiteLaxMode {\n\t\tt.Fatalf(\"SameSite Lax Mode must be set\")\n\t}\n\ts = c.String()\n\tif !strings.Contains(s, \"; SameSite=Lax\") {\n\t\tt.Fatalf(\"missing SameSite flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar; samesite=strict\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif c.SameSite() != CookieSameSiteStrictMode {\n\t\tt.Fatalf(\"SameSite Strict Mode must be set\")\n\t}\n\ts = c.String()\n\tif !strings.Contains(s, \"; SameSite=Strict\") {\n\t\tt.Fatalf(\"missing SameSite flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar; samesite=none\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif c.SameSite() != CookieSameSiteNoneMode {\n\t\tt.Fatalf(\"SameSite None Mode must be set\")\n\t}\n\ts = c.String()\n\tif !strings.Contains(s, \"; SameSite=None\") {\n\t\tt.Fatalf(\"missing SameSite flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tc.SetSameSite(CookieSameSiteNoneMode)\n\ts = c.String()\n\tif !strings.Contains(s, \"; SameSite=None\") {\n\t\tt.Fatalf(\"missing SameSite flag in cookie %q\", s)\n\t}\n\tif !strings.Contains(s, \"; secure\") {\n\t\tt.Fatalf(\"missing Secure flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif c.SameSite() != CookieSameSiteDisabled {\n\t\tt.Fatalf(\"Unexpected SameSite flag set\")\n\t}\n\ts = c.String()\n\tif strings.Contains(s, \"SameSite\") {\n\t\tt.Fatalf(\"unexpected SameSite flag in cookie %q\", s)\n\t}\n}\n\nfunc TestCookiePartitioned(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tif err := c.Parse(\"__Host-name=value; Secure; Path=/; SameSite=None; Partitioned;\"); err != nil {\n\t\tt.Fatalf(\"unexpected error for valid paritionedd cookie: %s\", err)\n\t}\n\tif !c.Partitioned() {\n\t\tt.Fatalf(\"partitioned must be set\")\n\t}\n\n\tif err := c.Parse(\"foo=bar\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tc.SetPartitioned(true)\n\ts := c.String()\n\tif !strings.Contains(s, \"; Partitioned\") {\n\t\tt.Fatalf(\"missing Partitioned flag in cookie %q\", s)\n\t}\n\tif !strings.Contains(s, \"; secure\") {\n\t\tt.Fatalf(\"missing Secure flag in cookie %q\", s)\n\t}\n}\n\nfunc TestCookieMaxAgeExpires(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tmaxAge := 100\n\tif err := c.Parse(\"foo=bar; max-age=100\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif maxAge != c.MaxAge() {\n\t\tt.Fatalf(\"max-age must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; max-age=100\") {\n\t\tt.Fatalf(\"missing max-age flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar; expires=Tue, 10 Nov 2009 23:00:00 GMT; max-age=100;\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif maxAge != c.MaxAge() {\n\t\tt.Fatalf(\"max-age ignored\")\n\t}\n\texpectedExpires := time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC)\n\tif !c.Expire().Equal(expectedExpires) {\n\t\tt.Fatalf(\"expires not parsed correctly. Got %v, expected %v\", c.Expire(), expectedExpires)\n\t}\n\n\tc.SetExpire(time.Time{})\n\ts = c.String()\n\tif s != \"foo=bar; max-age=100\" {\n\t\tt.Fatalf(\"missing max-age in cookie %q\", s)\n\t}\n\n\tc.SetMaxAge(-1)\n\ts = c.String()\n\tif s != \"foo=bar; max-age=0\" {\n\t\tt.Fatalf(\"negative max-age should output 0: %q\", s)\n\t}\n\n\texpires := time.Unix(100, 0)\n\tc.SetExpire(expires)\n\ts = c.String()\n\tif s != \"foo=bar; max-age=0; expires=Thu, 01 Jan 1970 00:01:40 GMT\" {\n\t\tt.Fatalf(\"expires should be included along with negative max-age (output as 0): %q\", s)\n\t}\n\n\tc.SetMaxAge(0)\n\ts = c.String()\n\tif s != \"foo=bar; expires=Thu, 01 Jan 1970 00:01:40 GMT\" {\n\t\tt.Fatalf(\"missing expires %q\", s)\n\t}\n}\n\nfunc TestCookieHttpOnly(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tif err := c.Parse(\"foo=bar; HttpOnly\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif !c.HTTPOnly() {\n\t\tt.Fatalf(\"HTTPOnly must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; HttpOnly\") {\n\t\tt.Fatalf(\"missing HttpOnly flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif c.HTTPOnly() {\n\t\tt.Fatalf(\"Unexpected HTTPOnly flag set\")\n\t}\n\ts = c.String()\n\tif strings.Contains(s, \"HttpOnly\") {\n\t\tt.Fatalf(\"unexpected HttpOnly flag in cookie %q\", s)\n\t}\n}\n\nfunc TestCookieParse(t *testing.T) {\n\tt.Parallel()\n\n\ttestCookieParse(t, \"foo\", \"foo\")\n\ttestCookieParse(t, \"foo=bar\", \"foo=bar\")\n\ttestCookieParse(t, \"foo=\", \"foo=\")\n\ttestCookieParse(t, `foo=\"bar\"`, \"foo=bar\")\n\ttestCookieParse(t, `\"foo\"=bar`, `\"foo\"=bar`)\n\ttestCookieParse(t, \"foo=bar; Domain=aaa.com; PATH=/foo/bar\", \"foo=bar; domain=aaa.com; path=/foo/bar\")\n\ttestCookieParse(t, \"foo=bar; max-age= 101 ; expires= Tue, 10 Nov 2009 23:00:00 GMT\", \"foo=bar; max-age=101; expires=Tue, 10 Nov 2009 23:00:00 GMT\")\n\ttestCookieParse(t, \" xxx = yyy  ; path=/a/b;;;domain=foobar.com ; expires= Tue, 10 Nov 2009 23:00:00 GMT ; ;;\",\n\t\t\"xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b\")\n}\n\nfunc Test_decodeCookieArg(t *testing.T) {\n\tsrc := []byte(\"          \\\"aaaaabbbbb\\\"         \")\n\tdst := make([]byte, 0)\n\tdst = decodeCookieArg(dst, src, true)\n\tassert.DeepEqual(t, []byte(\"aaaaabbbbb\"), dst)\n}\n\nfunc testCookieParse(t *testing.T, s, expectedS string) {\n\tvar c Cookie\n\tif err := c.Parse(s); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tresult := string(c.Cookie())\n\tif result != expectedS {\n\t\tt.Fatalf(\"unexpected cookies %q. Expecting %q. Original %q\", result, expectedS, s)\n\t}\n}\n\nfunc Test_WarnIfInvalid(t *testing.T) {\n\tassert.False(t, warnIfInvalid([]byte(\";\")))\n\tassert.False(t, warnIfInvalid([]byte(\"\\\\\")))\n\tassert.False(t, warnIfInvalid([]byte(\"\\\"\")))\n\tassert.True(t, warnIfInvalid([]byte(\"\")))\n\tfor i := 0; i < 5; i++ {\n\t\tvalidCookie := getValidCookie()\n\t\tassert.True(t, warnIfInvalid(validCookie))\n\t}\n}\n\nfunc getValidCookie() []byte {\n\tvar validCookie []byte\n\tfor i := 0; i < 100; i++ {\n\t\tr := rand.Intn(0x78-0x20) + 0x20\n\t\tif r == ';' || r == '\\\\' || r == '\"' {\n\t\t\tcontinue\n\t\t}\n\t\tvalidCookie = append(validCookie, byte(r))\n\t}\n\treturn validCookie\n}\n"
  },
  {
    "path": "pkg/protocol/doc.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The files in bytebufferpool package are forked from fasthttp[github.com/valyala/fasthttp],\n// and we keep the original Copyright[Copyright 2015 fasthttp authors] and License of fasthttp for those files.\n// We also need to modify as we need, the modifications are Copyright of 2022 CloudWeGo Authors.\n// Thanks for fasthttp authors! Below is the source code information:\n// \t\tRepo: github.com/valyala/fasthttp\n//\t\tForked Version: v1.36.0\n\npackage protocol\n"
  },
  {
    "path": "pkg/protocol/header.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/internal/nocopy\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nvar (\n\tServerDate     atomic.Value\n\tServerDateOnce sync.Once // serverDateOnce.Do(updateServerDate)\n)\n\ntype RequestHeader struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\tdisableNormalizing   bool\n\tconnectionClose      bool\n\tnoDefaultContentType bool\n\n\t// These two fields have been moved close to other bool fields\n\t// for reducing RequestHeader object size.\n\tcookiesCollected bool\n\n\tcontentLength      int\n\tcontentLengthBytes []byte\n\n\tmethod      []byte\n\trequestURI  []byte\n\thost        []byte\n\tcontentType []byte\n\n\tuserAgent []byte\n\tmulHeader [][]byte\n\tprotocol  string\n\n\th       []argsKV\n\tbufKV   argsKV\n\ttrailer *Trailer\n\n\tcookies []argsKV\n\n\t// stores an immutable copy of headers as they were received from the\n\t// wire.\n\trawHeaders []byte\n}\n\nfunc (h *RequestHeader) SetRawHeaders(r []byte) {\n\th.rawHeaders = r\n}\n\n// ResponseHeader represents HTTP response header.\n//\n// It is forbidden copying ResponseHeader instances.\n// Create new instances instead and use CopyTo.\n//\n// ResponseHeader instance MUST NOT be used from concurrently running\n// goroutines.\ntype ResponseHeader struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\tdisableNormalizing   bool\n\tconnectionClose      bool\n\tnoDefaultContentType bool\n\tnoDefaultDate        bool\n\n\tstatusCode         int\n\tcontentLength      int\n\tcontentLengthBytes []byte\n\tcontentEncoding    []byte\n\n\tcontentType []byte\n\tserver      []byte\n\tmulHeader   [][]byte\n\tprotocol    string\n\n\th       []argsKV\n\tbufKV   argsKV\n\ttrailer *Trailer\n\n\tcookies []argsKV\n\n\theaderLength int\n}\n\n// SetHeaderLength sets the size of header for tracer.\nfunc (h *ResponseHeader) SetHeaderLength(length int) {\n\th.headerLength = length\n}\n\n// GetHeaderLength gets the size of header for tracer.\nfunc (h *ResponseHeader) GetHeaderLength() int {\n\treturn h.headerLength\n}\n\n// SetContentRange sets 'Content-Range: bytes startPos-endPos/contentLength'\n// header.\nfunc (h *ResponseHeader) SetContentRange(startPos, endPos, contentLength int) {\n\tb := h.bufKV.value[:0]\n\tb = append(b, bytestr.StrBytes...)\n\tb = append(b, ' ')\n\tb = bytesconv.AppendUint(b, startPos)\n\tb = append(b, '-')\n\tb = bytesconv.AppendUint(b, endPos)\n\tb = append(b, '/')\n\tb = bytesconv.AppendUint(b, contentLength)\n\th.bufKV.value = b\n\n\th.SetCanonical(bytestr.StrContentRange, h.bufKV.value)\n}\n\nfunc (h *ResponseHeader) NoDefaultContentType() bool {\n\treturn h.noDefaultContentType\n}\n\n// SetConnectionClose sets 'Connection: close' header.\nfunc (h *ResponseHeader) SetConnectionClose(close bool) {\n\th.connectionClose = close\n}\n\nfunc (h *ResponseHeader) PeekArgBytes(key []byte) []byte {\n\treturn peekArgBytes(h.h, key)\n}\n\n// Deprecated: Use ResponseHeader.SetProtocol(consts.HTTP11) instead\n//\n//\tNow SetNoHTTP11(true) equal to SetProtocol(consts.HTTP10)\n//\t\tSetNoHTTP11(false) equal to SetProtocol(consts.HTTP11)\nfunc (h *ResponseHeader) SetNoHTTP11(b bool) {\n\tif b {\n\t\th.protocol = consts.HTTP10\n\t\treturn\n\t}\n\n\th.protocol = consts.HTTP11\n}\n\n// Cookie fills cookie for the given cookie.Key.\n//\n// Returns false if cookie with the given cookie.Key is missing.\nfunc (h *ResponseHeader) Cookie(cookie *Cookie) bool {\n\tv := peekArgBytes(h.cookies, cookie.Key())\n\tif v == nil {\n\t\treturn false\n\t}\n\tcookie.ParseBytes(v) //nolint:errcheck\n\treturn true\n}\n\n// FullCookie returns complete cookie bytes\nfunc (h *ResponseHeader) FullCookie() []byte {\n\treturn h.Peek(consts.HeaderSetCookie)\n}\n\n// IsHTTP11 returns true if the response is HTTP/1.1.\nfunc (h *ResponseHeader) IsHTTP11() bool {\n\treturn h.protocol == consts.HTTP11\n}\n\n// SetContentType sets Content-Type header value.\nfunc (h *ResponseHeader) SetContentType(contentType string) {\n\th.contentType = append(h.contentType[:0], contentType...)\n}\n\nfunc (h *ResponseHeader) GetHeaders() []argsKV {\n\treturn h.h\n}\n\n// Reset clears response header.\nfunc (h *ResponseHeader) Reset() {\n\th.disableNormalizing = false\n\th.Trailer().disableNormalizing = false\n\th.noDefaultContentType = false\n\th.noDefaultDate = false\n\th.ResetSkipNormalize()\n}\n\n// CopyTo copies all the headers to dst.\nfunc (h *ResponseHeader) CopyTo(dst *ResponseHeader) {\n\tdst.Reset()\n\n\tdst.disableNormalizing = h.disableNormalizing\n\tdst.connectionClose = h.connectionClose\n\tdst.noDefaultContentType = h.noDefaultContentType\n\tdst.noDefaultDate = h.noDefaultDate\n\n\tdst.statusCode = h.statusCode\n\tdst.contentLength = h.contentLength\n\tdst.contentLengthBytes = append(dst.contentLengthBytes[:0], h.contentLengthBytes...)\n\tdst.contentEncoding = append(dst.contentEncoding[:0], h.contentEncoding...)\n\tdst.contentType = append(dst.contentType[:0], h.contentType...)\n\tdst.server = append(dst.server[:0], h.server...)\n\tdst.h = copyArgs(dst.h, h.h)\n\tdst.cookies = copyArgs(dst.cookies, h.cookies)\n\tdst.protocol = h.protocol\n\tdst.headerLength = h.headerLength\n\th.Trailer().CopyTo(dst.Trailer())\n}\n\n// Multiple headers with the same key may be added with this function.\n// Use Set for setting a single header for the given key.\n//\n// the Content-Type, Content-Length, Connection, Cookie,\n// Transfer-Encoding, Host and User-Agent headers can only be set once\n// and will overwrite the previous value.\nfunc (h *RequestHeader) Add(key, value string) {\n\tif h.setSpecialHeader(bytesconv.S2b(key), bytesconv.S2b(value)) {\n\t\treturn\n\t}\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\th.h = appendArg(h.h, bytesconv.B2s(k), value, ArgsHasValue)\n}\n\n// VisitAll calls f for each header.\n//\n// f must not retain references to key and/or value after returning.\n// Copy key and/or value contents before returning if you need retaining them.\nfunc (h *ResponseHeader) VisitAll(f func(key, value []byte)) {\n\tif len(h.contentLengthBytes) > 0 {\n\t\tf(bytestr.StrContentLength, h.contentLengthBytes)\n\t}\n\tcontentType := h.ContentType()\n\tif len(contentType) > 0 {\n\t\tf(bytestr.StrContentType, contentType)\n\t}\n\tcontentEncoding := h.ContentEncoding()\n\tif len(contentEncoding) > 0 {\n\t\tf(bytestr.StrContentEncoding, contentEncoding)\n\t}\n\tserver := h.Server()\n\tif len(server) > 0 {\n\t\tf(bytestr.StrServer, server)\n\t}\n\tif len(h.cookies) > 0 {\n\t\tvisitArgs(h.cookies, func(k, v []byte) {\n\t\t\tf(bytestr.StrSetCookie, v)\n\t\t})\n\t}\n\tif !h.Trailer().Empty() {\n\t\tf(bytestr.StrTrailer, h.Trailer().GetBytes())\n\t}\n\tvisitArgs(h.h, f)\n\tif h.ConnectionClose() {\n\t\tf(bytestr.StrConnection, bytestr.StrClose)\n\t}\n}\n\n// IsHTTP11 returns true if the request is HTTP/1.1.\nfunc (h *RequestHeader) IsHTTP11() bool {\n\treturn h.protocol == consts.HTTP11\n}\n\nfunc (h *RequestHeader) SetProtocol(p string) {\n\th.protocol = p\n}\n\nfunc (h *RequestHeader) GetProtocol() string {\n\treturn h.protocol\n}\n\n// Deprecated: Use RequestHeader.SetProtocol(consts.HTTP11) instead\n//\n//\tNow SetNoHTTP11(true) equal to SetProtocol(consts.HTTP10)\n//\t\tSetNoHTTP11(false) equal to SetProtocol(consts.HTTP11)\nfunc (h *RequestHeader) SetNoHTTP11(b bool) {\n\tif b {\n\t\th.protocol = consts.HTTP10\n\t\treturn\n\t}\n\n\th.protocol = consts.HTTP11\n}\n\nfunc (h *RequestHeader) InitBufValue(size int) {\n\tif size > cap(h.bufKV.value) {\n\t\th.bufKV.value = make([]byte, 0, size)\n\t}\n}\n\nfunc (h *RequestHeader) GetBufValue() []byte {\n\treturn h.bufKV.value\n}\n\n// HasAcceptEncodingBytes returns true if the header contains\n// the given Accept-Encoding value.\nfunc (h *RequestHeader) HasAcceptEncodingBytes(acceptEncoding []byte) bool {\n\tae := h.peek(consts.HeaderAcceptEncoding)\n\tn := bytes.Index(ae, acceptEncoding)\n\tif n < 0 {\n\t\treturn false\n\t}\n\tb := ae[n+len(acceptEncoding):]\n\tif len(b) > 0 && b[0] != ',' {\n\t\treturn false\n\t}\n\tif n == 0 {\n\t\treturn true\n\t}\n\treturn ae[n-1] == ' '\n}\n\nfunc (h *RequestHeader) PeekIfModifiedSinceBytes() []byte {\n\treturn h.peek(consts.HeaderIfModifiedSince)\n}\n\n// RequestURI returns RequestURI from the first HTTP request line.\nfunc (h *RequestHeader) RequestURI() []byte {\n\trequestURI := h.requestURI\n\tif len(requestURI) == 0 {\n\t\trequestURI = bytestr.StrSlash\n\t}\n\treturn requestURI\n}\n\nfunc (h *RequestHeader) PeekArgBytes(key []byte) []byte {\n\treturn peekArgBytes(h.h, key)\n}\n\n// RawHeaders returns raw header key/value bytes.\n//\n// Depending on server configuration, header keys may be normalized to\n// capital-case in place.\n//\n// This copy is set aside during parsing, so empty slice is returned for all\n// cases where parsing did not happen. Similarly, request line is not stored\n// during parsing and can not be returned.\n//\n// The slice is not safe to use after the handler returns.\nfunc (h *RequestHeader) RawHeaders() []byte {\n\treturn h.rawHeaders\n}\n\n// AppendBytes appends request header representation to dst and returns\n// the extended dst.\nfunc (h *RequestHeader) AppendBytes(dst []byte) []byte {\n\tdst = append(dst, h.Method()...)\n\tdst = append(dst, ' ')\n\tdst = append(dst, h.RequestURI()...)\n\tdst = append(dst, ' ')\n\tdst = append(dst, bytestr.StrHTTP11...)\n\tdst = append(dst, bytestr.StrCRLF...)\n\n\tuserAgent := h.UserAgent()\n\tif len(userAgent) > 0 {\n\t\tdst = appendHeaderLine(dst, bytestr.StrUserAgent, userAgent)\n\t}\n\n\thost := h.Host()\n\tif len(host) > 0 {\n\t\tdst = appendHeaderLine(dst, bytestr.StrHost, host)\n\t}\n\n\tcontentType := h.ContentType()\n\tif len(contentType) == 0 && !h.IgnoreBody() && !h.noDefaultContentType {\n\t\tcontentType = bytestr.MIMEPostForm\n\t}\n\tif len(contentType) > 0 {\n\t\tdst = appendHeaderLine(dst, bytestr.StrContentType, contentType)\n\t}\n\tif len(h.contentLengthBytes) > 0 {\n\t\tdst = appendHeaderLine(dst, bytestr.StrContentLength, h.contentLengthBytes)\n\t}\n\n\tfor i, n := 0, len(h.h); i < n; i++ {\n\t\tkv := &h.h[i]\n\t\tdst = appendHeaderLine(dst, kv.key, kv.value)\n\t}\n\n\tif !h.Trailer().Empty() {\n\t\tdst = appendHeaderLine(dst, bytestr.StrTrailer, h.Trailer().GetBytes())\n\t}\n\n\t// there is no need in h.collectCookies() here, since if cookies aren't collected yet,\n\t// they all are located in h.h.\n\tn := len(h.cookies)\n\tif n > 0 {\n\t\tdst = append(dst, bytestr.StrCookie...)\n\t\tdst = append(dst, bytestr.StrColonSpace...)\n\t\tdst = appendRequestCookieBytes(dst, h.cookies)\n\t\tdst = append(dst, bytestr.StrCRLF...)\n\t}\n\n\tif h.ConnectionClose() {\n\t\tdst = appendHeaderLine(dst, bytestr.StrConnection, bytestr.StrClose)\n\t}\n\n\treturn append(dst, bytestr.StrCRLF...)\n}\n\n// Header returns request header representation.\n//\n// The returned representation is valid until the next call to RequestHeader methods.\nfunc (h *RequestHeader) Header() []byte {\n\th.bufKV.value = h.AppendBytes(h.bufKV.value[:0])\n\treturn h.bufKV.value\n}\n\n// IsPut returns true if request method is PUT.\nfunc (h *RequestHeader) IsPut() bool {\n\treturn bytes.Equal(h.Method(), bytestr.StrPut)\n}\n\n// IsHead returns true if request method is HEAD.\nfunc (h *RequestHeader) IsHead() bool {\n\treturn bytes.Equal(h.Method(), bytestr.StrHead)\n}\n\n// IsPost returns true if request method is POST.\nfunc (h *RequestHeader) IsPost() bool {\n\treturn bytes.Equal(h.Method(), bytestr.StrPost)\n}\n\n// IsDelete returns true if request method is DELETE.\nfunc (h *RequestHeader) IsDelete() bool {\n\treturn bytes.Equal(h.Method(), bytestr.StrDelete)\n}\n\n// IsConnect returns true if request method is CONNECT.\nfunc (h *RequestHeader) IsConnect() bool {\n\treturn bytes.Equal(h.Method(), bytestr.StrConnect)\n}\n\nfunc (h *RequestHeader) IgnoreBody() bool {\n\treturn h.IsGet() || h.IsHead()\n}\n\n// ContentLength returns Content-Length header value.\n//\n// It may be negative:\n// -1 means Transfer-Encoding: chunked.\nfunc (h *RequestHeader) ContentLength() int {\n\treturn h.contentLength\n}\n\n// SetHost sets Host header value.\nfunc (h *RequestHeader) SetHost(host string) {\n\th.host = append(h.host[:0], host...)\n}\n\n// SetStatusCode sets response status code.\nfunc (h *ResponseHeader) SetStatusCode(statusCode int) {\n\tcheckWriteHeaderCode(statusCode)\n\th.statusCode = statusCode\n}\n\nfunc checkWriteHeaderCode(code int) {\n\t// For now, we only emit a warning for bad codes.\n\t// In the future we might block things over 599 or under 100\n\tif code < 100 || code > 599 {\n\t\thlog.SystemLogger().Warnf(\"Invalid StatusCode code %v, status code should not be under 100 or over 599.\\n\"+\n\t\t\t\"For more info: https://www.rfc-editor.org/rfc/rfc9110.html#name-status-codes\", code)\n\t}\n}\n\nfunc (h *ResponseHeader) ResetSkipNormalize() {\n\th.protocol = \"\"\n\th.connectionClose = false\n\n\th.statusCode = 0\n\th.contentLength = 0\n\th.contentLengthBytes = h.contentLengthBytes[:0]\n\th.contentEncoding = h.contentEncoding[:0]\n\n\th.contentType = h.contentType[:0]\n\th.server = h.server[:0]\n\n\th.h = h.h[:0]\n\th.cookies = h.cookies[:0]\n\th.Trailer().ResetSkipNormalize()\n\th.mulHeader = h.mulHeader[:0]\n}\n\n// ContentLength returns Content-Length header value.\n//\n// It may be negative:\n// -1 means Transfer-Encoding: chunked.\n// -2 means Transfer-Encoding: identity.\nfunc (h *ResponseHeader) ContentLength() int {\n\treturn h.contentLength\n}\n\n// Set sets the given 'key: value' header.\n//\n// Use Add for setting multiple header values under the same key.\nfunc (h *ResponseHeader) Set(key, value string) {\n\tinitHeaderKV(&h.bufKV, key, value, h.disableNormalizing)\n\th.SetCanonical(h.bufKV.key, h.bufKV.value)\n}\n\n// Add adds the given 'key: value' header.\n//\n// Multiple headers with the same key may be added with this function.\n// Use Set for setting a single header for the given key.\n//\n// the Content-Type, Content-Length, Connection, Server, Set-Cookie,\n// Transfer-Encoding and Date headers can only be set once and will\n// overwrite the previous value.\nfunc (h *ResponseHeader) Add(key, value string) {\n\tif h.setSpecialHeader(bytesconv.S2b(key), bytesconv.S2b(value)) {\n\t\treturn\n\t}\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\th.h = appendArg(h.h, bytesconv.B2s(k), value, ArgsHasValue)\n}\n\n// SetContentLength sets Content-Length header value.\n//\n// Content-Length may be negative:\n// -1 means Transfer-Encoding: chunked.\n// -2 means Transfer-Encoding: identity.\nfunc (h *ResponseHeader) SetContentLength(contentLength int) {\n\tif h.MustSkipContentLength() {\n\t\treturn\n\t}\n\th.contentLength = contentLength\n\tif contentLength >= 0 {\n\t\th.contentLengthBytes = bytesconv.AppendUint(h.contentLengthBytes[:0], contentLength)\n\t\th.h = delAllArgsBytes(h.h, bytestr.StrTransferEncoding)\n\t} else {\n\t\th.contentLengthBytes = h.contentLengthBytes[:0]\n\t\tvalue := bytestr.StrChunked\n\t\tif contentLength == -2 {\n\t\t\th.SetConnectionClose(true)\n\t\t\tvalue = bytestr.StrIdentity\n\t\t}\n\t\th.h = setArgBytes(h.h, bytestr.StrTransferEncoding, value, ArgsHasValue)\n\t}\n}\n\nfunc (h *ResponseHeader) ContentLengthBytes() []byte {\n\treturn h.contentLengthBytes\n}\n\nfunc (h *ResponseHeader) InitContentLengthWithValue(contentLength int) {\n\th.contentLength = contentLength\n}\n\n// VisitAllCookie calls f for each response cookie.\n//\n// Cookie name is passed in key and the whole Set-Cookie header value\n// is passed in value on each f invocation. Value may be parsed\n// with Cookie.ParseBytes().\n//\n// f must not retain references to key and/or value after returning.\nfunc (h *ResponseHeader) VisitAllCookie(f func(key, value []byte)) {\n\tvisitArgs(h.cookies, f)\n}\n\n// DelAllCookies removes all the cookies from response headers.\nfunc (h *ResponseHeader) DelAllCookies() {\n\th.cookies = h.cookies[:0]\n}\n\n// DelCookie removes cookie under the given key from response header.\n//\n// Note that DelCookie doesn't remove the cookie from the client.\n// Use DelClientCookie instead.\nfunc (h *ResponseHeader) DelCookie(key string) {\n\th.cookies = delAllArgs(h.cookies, key)\n}\n\n// DelCookieBytes removes cookie under the given key from response header.\n//\n// Note that DelCookieBytes doesn't remove the cookie from the client.\n// Use DelClientCookieBytes instead.\nfunc (h *ResponseHeader) DelCookieBytes(key []byte) {\n\th.DelCookie(bytesconv.B2s(key))\n}\n\n// DelBytes deletes header with the given key.\nfunc (h *ResponseHeader) DelBytes(key []byte) {\n\tk := []byte(string(key))\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\th.del(k)\n}\n\n// Header returns response header representation.\n//\n// The returned value is valid until the next call to ResponseHeader methods.\nfunc (h *ResponseHeader) Header() []byte {\n\th.bufKV.value = h.AppendBytes(h.bufKV.value[:0])\n\treturn h.bufKV.value\n}\n\nfunc (h *ResponseHeader) PeekLocation() []byte {\n\treturn h.peek(consts.HeaderLocation)\n}\n\n// DelClientCookie instructs the client to remove the given cookie.\n// This doesn't work for a cookie with specific domain or path,\n// you should delete it manually like:\n//\n//\tc := AcquireCookie()\n//\tc.SetKey(key)\n//\tc.SetDomain(\"example.com\")\n//\tc.SetPath(\"/path\")\n//\tc.SetExpire(CookieExpireDelete)\n//\th.SetCookie(c)\n//\tReleaseCookie(c)\n//\n// Use DelCookie if you want just removing the cookie from response header.\nfunc (h *ResponseHeader) DelClientCookie(key string) {\n\th.DelCookie(key)\n\n\tc := AcquireCookie()\n\tc.SetKey(key)\n\tc.SetExpire(CookieExpireDelete)\n\th.SetCookie(c)\n\tReleaseCookie(c)\n}\n\n// DelClientCookieBytes instructs the client to remove the given cookie.\n// This doesn't work for a cookie with specific domain or path,\n// you should delete it manually like:\n//\n//\tc := AcquireCookie()\n//\tc.SetKey(key)\n//\tc.SetDomain(\"example.com\")\n//\tc.SetPath(\"/path\")\n//\tc.SetExpire(CookieExpireDelete)\n//\th.SetCookie(c)\n//\tReleaseCookie(c)\n//\n// Use DelCookieBytes if you want just removing the cookie from response header.\nfunc (h *ResponseHeader) DelClientCookieBytes(key []byte) {\n\th.DelClientCookie(bytesconv.B2s(key))\n}\n\n// Peek returns header value for the given key.\n//\n// Returned value is valid until the next call to ResponseHeader.\n// Do not store references to returned value. Make copies instead.\nfunc (h *ResponseHeader) Peek(key string) []byte {\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\treturn h.peek(bytesconv.B2s(k))\n}\n\nfunc (h *ResponseHeader) IsDisableNormalizing() bool {\n\treturn h.disableNormalizing\n}\n\nfunc (h *ResponseHeader) ParseSetCookie(value []byte) {\n\tvar kv *argsKV\n\th.cookies, kv = allocArg(h.cookies)\n\tkv.key = getCookieKey(kv.key, value)\n\tkv.value = append(kv.value[:0], value...)\n}\n\nfunc (h *ResponseHeader) peek(key string) []byte {\n\tswitch key {\n\tcase consts.HeaderContentType:\n\t\treturn h.ContentType()\n\tcase consts.HeaderContentEncoding:\n\t\treturn h.ContentEncoding()\n\tcase consts.HeaderServer:\n\t\treturn h.Server()\n\tcase consts.HeaderConnection:\n\t\tif h.ConnectionClose() {\n\t\t\treturn bytestr.StrClose\n\t\t}\n\t\treturn peekArgStr(h.h, key)\n\tcase consts.HeaderContentLength:\n\t\treturn h.contentLengthBytes\n\tcase consts.HeaderSetCookie:\n\t\treturn appendResponseCookieBytes(nil, h.cookies)\n\tcase consts.HeaderTrailer:\n\t\treturn h.Trailer().GetBytes()\n\tdefault:\n\t\treturn peekArgStr(h.h, key)\n\t}\n}\n\n// PeekAll returns all header value for the given key.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseResponse or your request handler returning.\n// Any future calls to the Peek* will modify the returned value.\n// Do not store references to returned value. Use ResponseHeader.GetAll(key) instead.\nfunc (h *ResponseHeader) PeekAll(key string) [][]byte {\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\treturn h.peekAll(k)\n}\n\nfunc (h *ResponseHeader) peekAll(key []byte) [][]byte {\n\th.mulHeader = h.mulHeader[:0]\n\tswitch string(key) {\n\tcase consts.HeaderContentType:\n\t\tif contentType := h.ContentType(); len(contentType) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, contentType)\n\t\t}\n\tcase consts.HeaderContentEncoding:\n\t\tif contentEncoding := h.ContentEncoding(); len(contentEncoding) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, contentEncoding)\n\t\t}\n\tcase consts.HeaderServer:\n\t\tif server := h.Server(); len(server) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, server)\n\t\t}\n\tcase consts.HeaderConnection:\n\t\tif h.ConnectionClose() {\n\t\t\th.mulHeader = append(h.mulHeader, bytestr.StrClose)\n\t\t} else {\n\t\t\th.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key)\n\t\t}\n\tcase consts.HeaderContentLength:\n\t\th.mulHeader = append(h.mulHeader, h.contentLengthBytes)\n\tcase consts.HeaderSetCookie:\n\t\th.mulHeader = append(h.mulHeader, appendResponseCookieBytes(nil, h.cookies))\n\tdefault:\n\t\th.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key)\n\t}\n\treturn h.mulHeader\n}\n\n// PeekAll returns all header value for the given key.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Any future calls to the Peek* will modify the returned value.\n// Do not store references to returned value. Use RequestHeader.GetAll(key) instead.\nfunc (h *RequestHeader) PeekAll(key string) [][]byte {\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\treturn h.peekAll(k)\n}\n\nfunc (h *RequestHeader) peekAll(key []byte) [][]byte {\n\th.mulHeader = h.mulHeader[:0]\n\tswitch string(key) {\n\tcase consts.HeaderHost:\n\t\tif host := h.Host(); len(host) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, host)\n\t\t}\n\tcase consts.HeaderContentType:\n\t\tif contentType := h.ContentType(); len(contentType) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, contentType)\n\t\t}\n\tcase consts.HeaderUserAgent:\n\t\tif ua := h.UserAgent(); len(ua) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, ua)\n\t\t}\n\tcase consts.HeaderConnection:\n\t\tif h.ConnectionClose() {\n\t\t\th.mulHeader = append(h.mulHeader, bytestr.StrClose)\n\t\t} else {\n\t\t\th.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key)\n\t\t}\n\tcase consts.HeaderContentLength:\n\t\th.mulHeader = append(h.mulHeader, h.contentLengthBytes)\n\tcase consts.HeaderCookie:\n\t\tif h.cookiesCollected {\n\t\t\th.mulHeader = append(h.mulHeader, appendRequestCookieBytes(nil, h.cookies))\n\t\t} else {\n\t\t\th.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key)\n\t\t}\n\tdefault:\n\t\th.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key)\n\t}\n\treturn h.mulHeader\n}\n\n// SetContentTypeBytes sets Content-Type header value.\nfunc (h *ResponseHeader) SetContentTypeBytes(contentType []byte) {\n\th.contentType = append(h.contentType[:0], contentType...)\n}\n\n// ContentEncoding returns Content-Encoding header value.\nfunc (h *ResponseHeader) ContentEncoding() []byte {\n\treturn h.contentEncoding\n}\n\n// SetContentEncoding sets Content-Encoding header value.\nfunc (h *ResponseHeader) SetContentEncoding(contentEncoding string) {\n\th.contentEncoding = append(h.contentEncoding[:0], contentEncoding...)\n}\n\n// SetContentEncodingBytes sets Content-Encoding header value.\nfunc (h *ResponseHeader) SetContentEncodingBytes(contentEncoding []byte) {\n\th.contentEncoding = append(h.contentEncoding[:0], contentEncoding...)\n}\n\nfunc (h *ResponseHeader) SetContentLengthBytes(contentLength []byte) {\n\th.contentLengthBytes = append(h.contentLengthBytes[:0], contentLength...)\n}\n\n// SetCanonical sets the given 'key: value' header assuming that\n// key is in canonical form.\nfunc (h *ResponseHeader) SetCanonical(key, value []byte) {\n\tif h.setSpecialHeader(key, value) {\n\t\treturn\n\t}\n\n\th.h = setArgBytes(h.h, key, value, ArgsHasValue)\n}\n\n// ResetConnectionClose clears 'Connection: close' header if it exists.\nfunc (h *ResponseHeader) ResetConnectionClose() {\n\tif h.connectionClose {\n\t\th.connectionClose = false\n\t\th.h = delAllArgsBytes(h.h, bytestr.StrConnection)\n\t}\n}\n\n// Server returns Server header value.\nfunc (h *ResponseHeader) Server() []byte {\n\treturn h.server\n}\n\nfunc (h *ResponseHeader) AddArgBytes(key, value []byte, noValue bool) {\n\th.h = appendArgBytes(h.h, key, value, noValue)\n}\n\nfunc (h *ResponseHeader) SetArgBytes(key, value []byte, noValue bool) {\n\th.h = setArgBytes(h.h, key, value, noValue)\n}\n\n// AppendBytes appends response header representation to dst and returns\n// the extended dst.\nfunc (h *ResponseHeader) AppendBytes(dst []byte) []byte {\n\tstatusCode := h.StatusCode()\n\tif statusCode < 0 {\n\t\tstatusCode = consts.StatusOK\n\t}\n\tdst = append(dst, consts.StatusLine(statusCode)...)\n\n\tserver := h.Server()\n\tif len(server) != 0 {\n\t\tdst = appendHeaderLine(dst, bytestr.StrServer, server)\n\t}\n\n\tif !h.noDefaultDate {\n\t\tServerDateOnce.Do(UpdateServerDate)\n\t\tdst = appendHeaderLine(dst, bytestr.StrDate, ServerDate.Load().([]byte))\n\t}\n\n\t// Append Content-Type only for non-zero responses\n\t// or if it is explicitly set.\n\tif h.ContentLength() != 0 || len(h.contentType) > 0 {\n\t\tcontentType := h.ContentType()\n\t\tif len(contentType) > 0 {\n\t\t\tdst = appendHeaderLine(dst, bytestr.StrContentType, contentType)\n\t\t}\n\t}\n\tcontentEncoding := h.ContentEncoding()\n\tif len(contentEncoding) > 0 {\n\t\tdst = appendHeaderLine(dst, bytestr.StrContentEncoding, contentEncoding)\n\t}\n\tif len(h.contentLengthBytes) > 0 {\n\t\tdst = appendHeaderLine(dst, bytestr.StrContentLength, h.contentLengthBytes)\n\t}\n\n\tfor i, n := 0, len(h.h); i < n; i++ {\n\t\tkv := &h.h[i]\n\t\tif h.noDefaultDate || !bytes.Equal(kv.key, bytestr.StrDate) {\n\t\t\tdst = appendHeaderLine(dst, kv.key, kv.value)\n\t\t}\n\t}\n\n\tif !h.Trailer().Empty() {\n\t\tdst = appendHeaderLine(dst, bytestr.StrTrailer, h.Trailer().GetBytes())\n\t}\n\n\tn := len(h.cookies)\n\tif n > 0 {\n\t\tfor i := 0; i < n; i++ {\n\t\t\tkv := &h.cookies[i]\n\t\t\tdst = appendHeaderLine(dst, bytestr.StrSetCookie, kv.value)\n\t\t}\n\t}\n\n\tif h.ConnectionClose() {\n\t\tdst = appendHeaderLine(dst, bytestr.StrConnection, bytestr.StrClose)\n\t}\n\n\treturn append(dst, bytestr.StrCRLF...)\n}\n\n// ConnectionClose returns true if 'Connection: close' header is set.\nfunc (h *ResponseHeader) ConnectionClose() bool {\n\treturn h.connectionClose\n}\n\nfunc (h *ResponseHeader) GetCookies() []argsKV {\n\treturn h.cookies\n}\n\n// ContentType returns Content-Type header value.\nfunc (h *ResponseHeader) ContentType() []byte {\n\tcontentType := h.contentType\n\tif !h.noDefaultContentType && len(h.contentType) == 0 {\n\t\tcontentType = bytestr.DefaultContentType\n\t}\n\treturn contentType\n}\n\n// SetNoDefaultContentType set noDefaultContentType value of ResponseHeader.\nfunc (h *ResponseHeader) SetNoDefaultContentType(b bool) {\n\th.noDefaultContentType = b\n}\n\n// SetNoDefaultDate set noDefaultDate value of ResponseHeader.\nfunc (h *ResponseHeader) SetNoDefaultDate(b bool) {\n\th.noDefaultDate = b\n}\n\n// SetServerBytes sets Server header value.\nfunc (h *ResponseHeader) SetServerBytes(server []byte) {\n\th.server = append(h.server[:0], server...)\n}\n\nfunc (h *ResponseHeader) MustSkipContentLength() bool {\n\t// From http/1.1 specs:\n\t// All 1xx (informational), 204 (no content), and 304 (not modified) responses MUST NOT include a message-body\n\tstatusCode := h.StatusCode()\n\n\t// Fast path.\n\tif statusCode < 100 || statusCode == consts.StatusOK {\n\t\treturn false\n\t}\n\n\t// Slow path.\n\treturn statusCode == consts.StatusNotModified || statusCode == consts.StatusNoContent || statusCode < 200\n}\n\n// StatusCode returns response status code.\nfunc (h *ResponseHeader) StatusCode() int {\n\tif h.statusCode == 0 {\n\t\treturn consts.StatusOK\n\t}\n\treturn h.statusCode\n}\n\n// Del deletes header with the given key.\nfunc (h *ResponseHeader) Del(key string) {\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\th.del(k)\n}\n\nfunc (h *ResponseHeader) del(key []byte) {\n\tswitch string(key) {\n\tcase consts.HeaderContentType:\n\t\th.contentType = h.contentType[:0]\n\tcase consts.HeaderContentEncoding:\n\t\th.contentEncoding = h.contentEncoding[:0]\n\tcase consts.HeaderServer:\n\t\th.server = h.server[:0]\n\tcase consts.HeaderSetCookie:\n\t\th.cookies = h.cookies[:0]\n\tcase consts.HeaderContentLength:\n\t\th.contentLength = 0\n\t\th.contentLengthBytes = h.contentLengthBytes[:0]\n\tcase consts.HeaderConnection:\n\t\th.connectionClose = false\n\tcase consts.HeaderTrailer:\n\t\th.Trailer().ResetSkipNormalize()\n\t}\n\th.h = delAllArgsBytes(h.h, key)\n}\n\n// SetBytesV sets the given 'key: value' header.\n//\n// Use AddBytesV for setting multiple header values under the same key.\nfunc (h *ResponseHeader) SetBytesV(key string, value []byte) {\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\th.SetCanonical(k, value)\n}\n\n// Len returns the number of headers set,\n// i.e. the number of times f is called in VisitAll.\nfunc (h *ResponseHeader) Len() int {\n\tn := 0\n\th.VisitAll(func(k, v []byte) { n++ })\n\treturn n\n}\n\n// Len returns the number of headers set,\n// i.e. the number of times f is called in VisitAll.\nfunc (h *RequestHeader) Len() int {\n\tn := 0\n\th.VisitAll(func(k, v []byte) { n++ })\n\treturn n\n}\n\n// Reset clears request header.\nfunc (h *RequestHeader) Reset() {\n\th.disableNormalizing = false\n\th.Trailer().disableNormalizing = false\n\th.ResetSkipNormalize()\n}\n\n// SetByteRange sets 'Range: bytes=startPos-endPos' header.\n//\n//   - If startPos is negative, then 'bytes=-startPos' value is set.\n//   - If endPos is negative, then 'bytes=startPos-' value is set.\nfunc (h *RequestHeader) SetByteRange(startPos, endPos int) {\n\tb := h.bufKV.value[:0]\n\tb = append(b, bytestr.StrBytes...)\n\tb = append(b, '=')\n\tif startPos >= 0 {\n\t\tb = bytesconv.AppendUint(b, startPos)\n\t} else {\n\t\tendPos = -startPos\n\t}\n\tb = append(b, '-')\n\tif endPos >= 0 {\n\t\tb = bytesconv.AppendUint(b, endPos)\n\t}\n\th.bufKV.value = b\n\n\th.SetCanonical(bytestr.StrRange, h.bufKV.value)\n}\n\n// DelBytes deletes header with the given key.\nfunc (h *RequestHeader) DelBytes(key []byte) {\n\tk := []byte(string(key))\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\th.del(k)\n}\n\n// Del deletes header with the given key.\nfunc (h *RequestHeader) Del(key string) {\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\th.del(k)\n}\n\nfunc (h *RequestHeader) SetArgBytes(key, value []byte, noValue bool) {\n\th.h = setArgBytes(h.h, key, value, noValue)\n}\n\nfunc (h *RequestHeader) del(key []byte) {\n\tswitch string(key) {\n\tcase consts.HeaderHost:\n\t\th.host = h.host[:0]\n\tcase consts.HeaderContentType:\n\t\th.contentType = h.contentType[:0]\n\tcase consts.HeaderUserAgent:\n\t\th.userAgent = h.userAgent[:0]\n\tcase consts.HeaderCookie:\n\t\th.cookies = h.cookies[:0]\n\tcase consts.HeaderContentLength:\n\t\th.contentLength = 0\n\t\th.contentLengthBytes = h.contentLengthBytes[:0]\n\tcase consts.HeaderConnection:\n\t\th.connectionClose = false\n\tcase consts.HeaderTrailer:\n\t\th.Trailer().ResetSkipNormalize()\n\t}\n\th.h = delAllArgsBytes(h.h, key)\n}\n\n// CopyTo copies all the headers to dst.\nfunc (h *RequestHeader) CopyTo(dst *RequestHeader) {\n\tdst.Reset()\n\n\tdst.disableNormalizing = h.disableNormalizing\n\tdst.connectionClose = h.connectionClose\n\tdst.noDefaultContentType = h.noDefaultContentType\n\n\tdst.contentLength = h.contentLength\n\tdst.contentLengthBytes = append(dst.contentLengthBytes[:0], h.contentLengthBytes...)\n\tdst.method = append(dst.method[:0], h.method...)\n\tdst.requestURI = append(dst.requestURI[:0], h.requestURI...)\n\tdst.host = append(dst.host[:0], h.host...)\n\tdst.contentType = append(dst.contentType[:0], h.contentType...)\n\tdst.userAgent = append(dst.userAgent[:0], h.userAgent...)\n\th.Trailer().CopyTo(dst.Trailer())\n\tdst.h = copyArgs(dst.h, h.h)\n\tdst.cookies = copyArgs(dst.cookies, h.cookies)\n\tdst.cookiesCollected = h.cookiesCollected\n\tdst.rawHeaders = append(dst.rawHeaders[:0], h.rawHeaders...)\n\tdst.protocol = h.protocol\n}\n\n// Peek returns header value for the given key.\n//\n// Returned value is valid until the next call to RequestHeader.\n// Do not store references to returned value. Make copies instead.\nfunc (h *RequestHeader) Peek(key string) []byte {\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\treturn h.peek(bytesconv.B2s(k))\n}\n\n// SetMultipartFormBoundary sets the following Content-Type:\n// 'multipart/form-data; boundary=...'\n// where ... is substituted by the given boundary.\nfunc (h *RequestHeader) SetMultipartFormBoundary(boundary string) {\n\tb := h.bufKV.value[:0]\n\tb = append(b, bytestr.MIMEFormData...)\n\tb = append(b, ';', ' ')\n\tb = append(b, bytestr.StrBoundary...)\n\tb = append(b, '=')\n\tb = append(b, boundary...)\n\th.bufKV.value = b\n\n\th.SetContentTypeBytes(h.bufKV.value)\n}\n\nfunc (h *RequestHeader) ContentLengthBytes() []byte {\n\treturn h.contentLengthBytes\n}\n\nfunc (h *RequestHeader) SetContentLengthBytes(contentLength []byte) {\n\th.contentLengthBytes = append(h.contentLengthBytes[:0], contentLength...)\n}\n\n// SetContentTypeBytes sets Content-Type header value.\nfunc (h *RequestHeader) SetContentTypeBytes(contentType []byte) {\n\th.contentType = append(h.contentType[:0], contentType...)\n}\n\n// ContentType returns Content-Type header value.\nfunc (h *RequestHeader) ContentType() []byte {\n\treturn h.contentType\n}\n\n// SetNoDefaultContentType controls the default Content-Type header behaviour.\n//\n// When set to false, the Content-Type header is sent with a default value if no Content-Type value is specified.\n// When set to true, no Content-Type header is sent if no Content-Type value is specified.\nfunc (h *RequestHeader) SetNoDefaultContentType(b bool) {\n\th.noDefaultContentType = b\n}\n\n// SetContentLength sets Content-Length header value.\n//\n// Negative content-length sets 'Transfer-Encoding: chunked' header.\nfunc (h *RequestHeader) SetContentLength(contentLength int) {\n\th.contentLength = contentLength\n\tif contentLength >= 0 {\n\t\th.contentLengthBytes = bytesconv.AppendUint(h.contentLengthBytes[:0], contentLength)\n\t\th.h = delAllArgsBytes(h.h, bytestr.StrTransferEncoding)\n\t} else {\n\t\th.contentLengthBytes = h.contentLengthBytes[:0]\n\t\th.h = setArgBytes(h.h, bytestr.StrTransferEncoding, bytestr.StrChunked, ArgsHasValue)\n\t}\n}\n\nfunc (h *RequestHeader) InitContentLengthWithValue(contentLength int) {\n\th.contentLength = contentLength\n}\n\n// MultipartFormBoundary returns boundary part\n// from 'multipart/form-data; boundary=...' Content-Type.\nfunc (h *RequestHeader) MultipartFormBoundary() []byte {\n\tb := h.ContentType()\n\tif !bytes.HasPrefix(b, bytestr.MIMEFormData) {\n\t\treturn nil\n\t}\n\tb = b[len(bytestr.MIMEFormData):]\n\tif len(b) == 0 || b[0] != ';' {\n\t\treturn nil\n\t}\n\n\tvar n int\n\tfor len(b) > 0 {\n\t\tn++\n\t\tfor len(b) > n && b[n] == ' ' {\n\t\t\tn++\n\t\t}\n\t\tb = b[n:]\n\t\tif !bytes.HasPrefix(b, bytestr.StrBoundary) {\n\t\t\tif n = bytes.IndexByte(b, ';'); n < 0 {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tb = b[len(bytestr.StrBoundary):]\n\t\tif len(b) == 0 || b[0] != '=' {\n\t\t\treturn nil\n\t\t}\n\t\tb = b[1:]\n\t\tif n = bytes.IndexByte(b, ';'); n >= 0 {\n\t\t\tb = b[:n]\n\t\t}\n\t\tif len(b) > 1 && b[0] == '\"' && b[len(b)-1] == '\"' {\n\t\t\tb = b[1 : len(b)-1]\n\t\t}\n\t\treturn b\n\t}\n\treturn nil\n}\n\n// ConnectionClose returns true if 'Connection: close' header is set.\nfunc (h *RequestHeader) ConnectionClose() bool {\n\treturn h.connectionClose\n}\n\n// Method returns HTTP request method.\nfunc (h *RequestHeader) Method() []byte {\n\tif len(h.method) == 0 {\n\t\treturn bytestr.StrGet\n\t}\n\treturn h.method\n}\n\n// IsGet returns true if request method is GET.\nfunc (h *RequestHeader) IsGet() bool {\n\treturn bytes.Equal(h.Method(), bytestr.StrGet)\n}\n\n// IsOptions returns true if request method is Options.\nfunc (h *RequestHeader) IsOptions() bool {\n\treturn bytes.Equal(h.Method(), bytestr.StrOptions)\n}\n\n// IsTrace returns true if request method is Trace.\nfunc (h *RequestHeader) IsTrace() bool {\n\treturn bytes.Equal(h.Method(), bytestr.StrTrace)\n}\n\n// SetHostBytes sets Host header value.\nfunc (h *RequestHeader) SetHostBytes(host []byte) {\n\th.host = append(h.host[:0], host...)\n}\n\n// SetRequestURIBytes sets RequestURI for the first HTTP request line.\n// RequestURI must be properly encoded.\n// Use URI.RequestURI for constructing proper RequestURI if unsure.\nfunc (h *RequestHeader) SetRequestURIBytes(requestURI []byte) {\n\th.requestURI = append(h.requestURI[:0], requestURI...)\n}\n\n// SetBytesKV sets the given 'key: value' header.\n//\n// Use AddBytesKV for setting multiple header values under the same key.\nfunc (h *RequestHeader) SetBytesKV(key, value []byte) {\n\tk := []byte(string(key))\n\tutils.NormalizeHeaderKey(k, h.disableNormalizing)\n\th.SetCanonical(k, value)\n}\n\nfunc (h *RequestHeader) AddArgBytes(key, value []byte, noValue bool) {\n\th.h = appendArgBytes(h.h, key, value, noValue)\n}\n\n// SetUserAgentBytes sets User-Agent header value.\nfunc (h *RequestHeader) SetUserAgentBytes(userAgent []byte) {\n\th.userAgent = append(h.userAgent[:0], userAgent...)\n}\n\n// SetCookie sets 'key: value' cookies.\nfunc (h *RequestHeader) SetCookie(key, value string) {\n\th.collectCookies()\n\th.cookies = setArg(h.cookies, key, value, ArgsHasValue)\n}\n\n// SetCookie sets the given response cookie.\n// It is save re-using the cookie after the function returns.\nfunc (h *ResponseHeader) SetCookie(cookie *Cookie) {\n\th.cookies = setArgBytes(h.cookies, cookie.Key(), cookie.Cookie(), ArgsHasValue)\n}\n\n// Cookie returns cookie for the given key.\nfunc (h *RequestHeader) Cookie(key string) []byte {\n\th.collectCookies()\n\treturn peekArgStr(h.cookies, key)\n}\n\n// Cookies returns all the request cookies.\n//\n// It's a good idea to call protocol.ReleaseCookie to reduce GC load after the cookie used.\nfunc (h *RequestHeader) Cookies() []*Cookie {\n\tvar cookies []*Cookie\n\th.VisitAllCookie(func(key, value []byte) {\n\t\tcookie := AcquireCookie()\n\t\tcookie.SetKeyBytes(key)\n\t\tcookie.SetValueBytes(value)\n\t\tcookies = append(cookies, cookie)\n\t})\n\treturn cookies\n}\n\nfunc (h *RequestHeader) PeekRange() []byte {\n\treturn h.peek(consts.HeaderRange)\n}\n\nfunc (h *RequestHeader) PeekContentEncoding() []byte {\n\treturn h.peek(consts.HeaderContentEncoding)\n}\n\n// FullCookie returns complete cookie bytes\nfunc (h *RequestHeader) FullCookie() []byte {\n\treturn h.Peek(consts.HeaderCookie)\n}\n\n// DelCookie removes cookie under the given key.\nfunc (h *RequestHeader) DelCookie(key string) {\n\th.collectCookies()\n\th.cookies = delAllArgs(h.cookies, key)\n}\n\n// DelAllCookies removes all the cookies from request headers.\nfunc (h *RequestHeader) DelAllCookies() {\n\th.collectCookies()\n\th.cookies = h.cookies[:0]\n}\n\n// VisitAllCookie calls f for each request cookie.\n//\n// f must not retain references to key and/or value after returning.\nfunc (h *RequestHeader) VisitAllCookie(f func(key, value []byte)) {\n\th.collectCookies()\n\tvisitArgs(h.cookies, f)\n}\n\nfunc (h *RequestHeader) collectCookies() {\n\tif h.cookiesCollected {\n\t\treturn\n\t}\n\n\tfor i, n := 0, len(h.h); i < n; i++ {\n\t\tkv := &h.h[i]\n\t\tif bytes.Equal(kv.key, bytestr.StrCookie) {\n\t\t\th.cookies = parseRequestCookies(h.cookies, kv.value)\n\t\t\ttmp := *kv\n\t\t\tcopy(h.h[i:], h.h[i+1:])\n\t\t\tn--\n\t\t\ti--\n\t\t\th.h[n] = tmp\n\t\t\th.h = h.h[:n]\n\t\t}\n\t}\n\th.cookiesCollected = true\n}\n\nfunc (h *RequestHeader) SetConnectionClose(close bool) {\n\th.connectionClose = close\n}\n\n// ResetConnectionClose clears 'Connection: close' header if it exists.\nfunc (h *RequestHeader) ResetConnectionClose() {\n\tif h.connectionClose {\n\t\th.connectionClose = false\n\t\th.h = delAllArgsBytes(h.h, bytestr.StrConnection)\n\t}\n}\n\n// SetMethod sets HTTP request method.\nfunc (h *RequestHeader) SetMethod(method string) {\n\th.method = append(h.method[:0], method...)\n}\n\n// SetRequestURI sets RequestURI for the first HTTP request line.\n// RequestURI must be properly encoded.\n// Use URI.RequestURI for constructing proper RequestURI if unsure.\nfunc (h *RequestHeader) SetRequestURI(requestURI string) {\n\th.requestURI = append(h.requestURI[:0], requestURI...)\n}\n\n// Set sets the given 'key: value' header.\n//\n// Use Add for setting multiple header values under the same key.\nfunc (h *RequestHeader) Set(key, value string) {\n\tinitHeaderKV(&h.bufKV, key, value, h.disableNormalizing)\n\th.SetCanonical(h.bufKV.key, h.bufKV.value)\n}\n\nfunc initHeaderKV(kv *argsKV, key, value string, disableNormalizing bool) {\n\tkv.key = append(kv.key[:0], key...)\n\tutils.NormalizeHeaderKey(kv.key, disableNormalizing)\n\tkv.value = append(kv.value[:0], value...)\n}\n\n// SetCanonical sets the given 'key: value' header assuming that\n// key is in canonical form.\nfunc (h *RequestHeader) SetCanonical(key, value []byte) {\n\tif h.setSpecialHeader(key, value) {\n\t\treturn\n\t}\n\n\th.h = setArgBytes(h.h, key, value, ArgsHasValue)\n}\n\nfunc (h *RequestHeader) ResetSkipNormalize() {\n\th.connectionClose = false\n\th.protocol = \"\"\n\th.noDefaultContentType = false\n\n\th.contentLength = 0\n\th.contentLengthBytes = h.contentLengthBytes[:0]\n\n\th.method = h.method[:0]\n\th.requestURI = h.requestURI[:0]\n\th.host = h.host[:0]\n\th.contentType = h.contentType[:0]\n\th.userAgent = h.userAgent[:0]\n\n\th.h = h.h[:0]\n\th.cookies = h.cookies[:0]\n\th.cookiesCollected = false\n\n\th.rawHeaders = h.rawHeaders[:0]\n\th.mulHeader = h.mulHeader[:0]\n\th.Trailer().ResetSkipNormalize()\n}\n\nfunc peekRawHeader(buf, key []byte) []byte {\n\tn := bytes.Index(buf, key)\n\tif n < 0 {\n\t\treturn nil\n\t}\n\tif n > 0 && buf[n-1] != '\\n' {\n\t\treturn nil\n\t}\n\tn += len(key)\n\tif n >= len(buf) {\n\t\treturn nil\n\t}\n\tif buf[n] != ':' {\n\t\treturn nil\n\t}\n\tn++\n\tif buf[n] != ' ' {\n\t\treturn nil\n\t}\n\tn++\n\tbuf = buf[n:]\n\tn = bytes.IndexByte(buf, '\\n')\n\tif n < 0 {\n\t\treturn nil\n\t}\n\tif n > 0 && buf[n-1] == '\\r' {\n\t\tn--\n\t}\n\treturn buf[:n]\n}\n\n// Host returns Host header value.\nfunc (h *RequestHeader) Host() []byte {\n\treturn h.host\n}\n\n// UserAgent returns User-Agent header value.\nfunc (h *RequestHeader) UserAgent() []byte {\n\treturn h.userAgent\n}\n\n// DisableNormalizing disables header names' normalization.\n//\n// By default all the header names are normalized by uppercasing\n// the first letter and all the first letters following dashes,\n// while lowercasing all the other letters.\n// Examples:\n//\n//   - CONNECTION -> Connection\n//   - conteNT-tYPE -> Content-Type\n//   - foo-bar-baz -> Foo-Bar-Baz\n//\n// Disable header names' normalization only if you know what are you doing.\nfunc (h *RequestHeader) DisableNormalizing() {\n\th.disableNormalizing = true\n\th.Trailer().DisableNormalizing()\n}\n\nfunc (h *RequestHeader) IsDisableNormalizing() bool {\n\treturn h.disableNormalizing\n}\n\n// String returns request header representation.\nfunc (h *RequestHeader) String() string {\n\treturn string(h.Header())\n}\n\n// VisitAll calls f for each header.\n//\n// f must not retain references to key and/or value after returning.\n// Copy key and/or value contents before returning if you need retaining them.\n//\n// To get the headers in order they were received use VisitAllInOrder.\nfunc (h *RequestHeader) VisitAll(f func(key, value []byte)) {\n\thost := h.Host()\n\tif len(host) > 0 {\n\t\tf(bytestr.StrHost, host)\n\t}\n\tif len(h.contentLengthBytes) > 0 {\n\t\tf(bytestr.StrContentLength, h.contentLengthBytes)\n\t}\n\tcontentType := h.ContentType()\n\tif len(contentType) > 0 {\n\t\tf(bytestr.StrContentType, contentType)\n\t}\n\tuserAgent := h.UserAgent()\n\tif len(userAgent) > 0 {\n\t\tf(bytestr.StrUserAgent, userAgent)\n\t}\n\tif !h.Trailer().Empty() {\n\t\tf(bytestr.StrTrailer, h.Trailer().GetBytes())\n\t}\n\n\th.collectCookies()\n\tif len(h.cookies) > 0 {\n\t\th.bufKV.value = appendRequestCookieBytes(h.bufKV.value[:0], h.cookies)\n\t\tf(bytestr.StrCookie, h.bufKV.value)\n\t}\n\tvisitArgs(h.h, f)\n\tif h.ConnectionClose() {\n\t\tf(bytestr.StrConnection, bytestr.StrClose)\n\t}\n}\n\n// VisitAllCustomHeader calls f for each header in header.h which contains all headers\n// except cookie, host, content-length, content-type, user-agent and connection.\n//\n// f must not retain references to key and/or value after returning.\n// Copy key and/or value contents before returning if you need retaining them.\n//\n// To get the headers in order they were received use VisitAllInOrder.\nfunc (h *RequestHeader) VisitAllCustomHeader(f func(key, value []byte)) {\n\tvisitArgs(h.h, f)\n}\n\nfunc ParseContentLength(b []byte) (int, error) {\n\tv, n, err := bytesconv.ParseUintBuf(b)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\tif n != len(b) {\n\t\treturn -1, errs.NewPublic(\"non-numeric chars at the end of Content-Length\")\n\t}\n\treturn v, nil\n}\n\nfunc appendArgBytes(args []argsKV, key, value []byte, noValue bool) []argsKV {\n\tvar kv *argsKV\n\targs, kv = allocArg(args)\n\tkv.key = append(kv.key[:0], key...)\n\tif noValue {\n\t\tkv.value = kv.value[:0]\n\t} else {\n\t\tkv.value = append(kv.value[:0], value...)\n\t}\n\tkv.noValue = noValue\n\treturn args\n}\n\nfunc appendArg(args []argsKV, key, value string, noValue bool) []argsKV {\n\tvar kv *argsKV\n\targs, kv = allocArg(args)\n\tkv.key = append(kv.key[:0], key...)\n\tif noValue {\n\t\tkv.value = kv.value[:0]\n\t} else {\n\t\tkv.value = append(kv.value[:0], value...)\n\t}\n\tkv.noValue = noValue\n\treturn args\n}\n\nfunc (h *RequestHeader) peek(key string) []byte {\n\tswitch key {\n\tcase consts.HeaderHost:\n\t\treturn h.Host()\n\tcase consts.HeaderContentType:\n\t\treturn h.ContentType()\n\tcase consts.HeaderUserAgent:\n\t\treturn h.UserAgent()\n\tcase consts.HeaderConnection:\n\t\tif h.ConnectionClose() {\n\t\t\treturn bytestr.StrClose\n\t\t}\n\t\treturn peekArgStr(h.h, key)\n\tcase consts.HeaderContentLength:\n\t\treturn h.contentLengthBytes\n\tcase consts.HeaderCookie:\n\t\tif h.cookiesCollected {\n\t\t\treturn appendRequestCookieBytes(nil, h.cookies)\n\t\t}\n\t\treturn peekArgStr(h.h, key)\n\tcase consts.HeaderTrailer:\n\t\treturn h.Trailer().GetBytes()\n\tdefault:\n\t\treturn peekArgStr(h.h, key)\n\t}\n}\n\nfunc (h *RequestHeader) Get(key string) string {\n\treturn string(h.Peek(key))\n}\n\nfunc (h *ResponseHeader) Get(key string) string {\n\treturn string(h.Peek(key))\n}\n\n// GetAll returns all header value for the given key\n// it is concurrent safety and long lifetime.\nfunc (h *RequestHeader) GetAll(key string) []string {\n\theaders := h.PeekAll(key)\n\tres := make([]string, 0, len(headers))\n\tfor _, header := range headers {\n\t\tres = append(res, string(header))\n\t}\n\treturn res\n}\n\n// GetAll returns all header value for the given key and is concurrent safety.\n// it is concurrent safety and long lifetime.\nfunc (h *ResponseHeader) GetAll(key string) []string {\n\theaders := h.PeekAll(key)\n\tres := make([]string, 0, len(headers))\n\tfor _, header := range headers {\n\t\tres = append(res, string(header))\n\t}\n\treturn res\n}\n\nfunc appendHeaderLine(dst, key, value []byte) []byte {\n\tfor _, k := range key {\n\t\t// if header field contains invalid key, just skip it.\n\t\tif bytesconv.ValidHeaderFieldNameTable[k] == 0 {\n\t\t\treturn dst\n\t\t}\n\t}\n\tdst = append(dst, key...)\n\tdst = append(dst, bytestr.StrColonSpace...)\n\tdst = appendHeaderValue(dst, value)\n\treturn append(dst, bytestr.StrCRLF...)\n}\n\nfunc appendHeaderValue(dst, v []byte) []byte {\n\tret := append(dst, v...)\n\tv = ret[len(dst):]\n\tfor i, c := range v { // '\\r' or '\\n' -> ' '\n\t\tif c == '\\r' || c == '\\n' {\n\t\t\tv[i] = ' '\n\t\t}\n\t}\n\treturn ret\n}\n\nfunc UpdateServerDate() {\n\trefreshServerDate()\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Second)\n\t\t\trefreshServerDate()\n\t\t}\n\t}()\n}\n\nfunc refreshServerDate() {\n\tb := bytesconv.AppendHTTPDate(make([]byte, 0, len(http.TimeFormat)), time.Now())\n\tServerDate.Store(b)\n}\n\n// SetMethodBytes sets HTTP request method.\nfunc (h *RequestHeader) SetMethodBytes(method []byte) {\n\th.method = append(h.method[:0], method...)\n}\n\n// DisableNormalizing disables header names' normalization.\n//\n// By default all the header names are normalized by uppercasing\n// the first letter and all the first letters following dashes,\n// while lowercasing all the other letters.\n// Examples:\n//\n//   - CONNECTION -> Connection\n//   - conteNT-tYPE -> Content-Type\n//   - foo-bar-baz -> Foo-Bar-Baz\n//\n// Disable header names' normalization only if you know what are you doing.\nfunc (h *ResponseHeader) DisableNormalizing() {\n\th.disableNormalizing = true\n\th.Trailer().DisableNormalizing()\n}\n\n// setSpecialHeader handles special headers and return true when a header is processed.\nfunc (h *ResponseHeader) setSpecialHeader(key, value []byte) bool {\n\tif len(key) == 0 {\n\t\treturn false\n\t}\n\n\tswitch key[0] | 0x20 {\n\tcase 'c':\n\t\tif utils.CaseInsensitiveCompare(bytestr.StrContentType, key) {\n\t\t\th.SetContentTypeBytes(value)\n\t\t\treturn true\n\t\t} else if utils.CaseInsensitiveCompare(bytestr.StrContentLength, key) {\n\t\t\tif contentLength, err := ParseContentLength(value); err == nil {\n\t\t\t\th.contentLength = contentLength\n\t\t\t\th.contentLengthBytes = append(h.contentLengthBytes[:0], value...)\n\t\t\t}\n\t\t\treturn true\n\t\t} else if utils.CaseInsensitiveCompare(bytestr.StrContentEncoding, key) {\n\t\t\th.SetContentEncodingBytes(value)\n\t\t\treturn true\n\t\t} else if utils.CaseInsensitiveCompare(bytestr.StrConnection, key) {\n\t\t\tif bytes.Equal(bytestr.StrClose, value) {\n\t\t\t\th.SetConnectionClose(true)\n\t\t\t} else {\n\t\t\t\th.ResetConnectionClose()\n\t\t\t\th.h = setArgBytes(h.h, key, value, ArgsHasValue)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\tcase 's':\n\t\tif utils.CaseInsensitiveCompare(bytestr.StrServer, key) {\n\t\t\th.SetServerBytes(value)\n\t\t\treturn true\n\t\t} else if utils.CaseInsensitiveCompare(bytestr.StrSetCookie, key) {\n\t\t\tvar kv *argsKV\n\t\t\th.cookies, kv = allocArg(h.cookies)\n\t\t\tkv.key = getCookieKey(kv.key, value)\n\t\t\tkv.value = append(kv.value[:0], value...)\n\t\t\treturn true\n\t\t}\n\tcase 't':\n\t\tif utils.CaseInsensitiveCompare(bytestr.StrTransferEncoding, key) {\n\t\t\t// Transfer-Encoding is managed automatically.\n\t\t\treturn true\n\t\t} else if utils.CaseInsensitiveCompare(bytestr.StrTrailer, key) {\n\t\t\t// copy value to avoid panic\n\t\t\tvalue = append(h.bufKV.value[:0], value...)\n\t\t\th.Trailer().SetTrailers(value)\n\t\t\treturn true\n\t\t}\n\tcase 'd':\n\t\tif utils.CaseInsensitiveCompare(bytestr.StrDate, key) {\n\t\t\t// Date is managed automatically.\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// setSpecialHeader handles special headers and return true when a header is processed.\nfunc (h *RequestHeader) setSpecialHeader(key, value []byte) bool {\n\tif len(key) == 0 {\n\t\treturn false\n\t}\n\n\tswitch key[0] | 0x20 {\n\tcase 'c':\n\t\tif utils.CaseInsensitiveCompare(bytestr.StrContentType, key) {\n\t\t\th.SetContentTypeBytes(value)\n\t\t\treturn true\n\t\t} else if utils.CaseInsensitiveCompare(bytestr.StrContentLength, key) {\n\t\t\tif contentLength, err := ParseContentLength(value); err == nil {\n\t\t\t\th.contentLength = contentLength\n\t\t\t\th.contentLengthBytes = append(h.contentLengthBytes[:0], value...)\n\t\t\t}\n\t\t\treturn true\n\t\t} else if utils.CaseInsensitiveCompare(bytestr.StrConnection, key) {\n\t\t\tif bytes.Equal(bytestr.StrClose, value) {\n\t\t\t\th.SetConnectionClose(true)\n\t\t\t} else {\n\t\t\t\th.ResetConnectionClose()\n\t\t\t\th.h = setArgBytes(h.h, key, value, ArgsHasValue)\n\t\t\t}\n\t\t\treturn true\n\t\t} else if utils.CaseInsensitiveCompare(bytestr.StrCookie, key) {\n\t\t\th.collectCookies()\n\t\t\th.cookies = parseRequestCookies(h.cookies, value)\n\t\t\treturn true\n\t\t}\n\tcase 't':\n\t\tif utils.CaseInsensitiveCompare(bytestr.StrTransferEncoding, key) {\n\t\t\t// Transfer-Encoding is managed automatically.\n\t\t\treturn true\n\t\t} else if utils.CaseInsensitiveCompare(bytestr.StrTrailer, key) {\n\t\t\t// copy value to avoid panic\n\t\t\tvalue = append(h.bufKV.value[:0], value...)\n\t\t\th.Trailer().SetTrailers(value)\n\t\t\treturn true\n\t\t}\n\tcase 'h':\n\t\tif utils.CaseInsensitiveCompare(bytestr.StrHost, key) {\n\t\t\th.SetHostBytes(value)\n\t\t\treturn true\n\t\t}\n\tcase 'u':\n\t\tif utils.CaseInsensitiveCompare(bytestr.StrUserAgent, key) {\n\t\t\th.SetUserAgentBytes(value)\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// Trailer returns the Trailer of HTTP Header.\nfunc (h *ResponseHeader) Trailer() *Trailer {\n\tif h.trailer == nil {\n\t\th.trailer = new(Trailer)\n\t}\n\treturn h.trailer\n}\n\n// Trailer returns the Trailer of HTTP Header.\nfunc (h *RequestHeader) Trailer() *Trailer {\n\tif h.trailer == nil {\n\t\th.trailer = new(Trailer)\n\t}\n\treturn h.trailer\n}\n\nfunc (h *ResponseHeader) SetProtocol(p string) {\n\th.protocol = p\n}\n\nfunc (h *ResponseHeader) GetProtocol() string {\n\treturn h.protocol\n}\n"
  },
  {
    "path": "pkg/protocol/header_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc TestRequestHeaderSetRawHeaders(t *testing.T) {\n\th := RequestHeader{}\n\th.SetRawHeaders([]byte(\"foo\"))\n\tassert.DeepEqual(t, h.rawHeaders, []byte(\"foo\"))\n}\n\nfunc TestResponseHeaderSetHeaderLength(t *testing.T) {\n\th := ResponseHeader{}\n\th.SetHeaderLength(15)\n\tassert.DeepEqual(t, h.headerLength, 15)\n\tassert.DeepEqual(t, h.GetHeaderLength(), 15)\n}\n\nfunc TestSetNoHTTP11(t *testing.T) {\n\trh := ResponseHeader{}\n\trh.SetProtocol(consts.HTTP10)\n\tassert.DeepEqual(t, consts.HTTP10, rh.protocol)\n\n\trh.SetProtocol(consts.HTTP11)\n\tassert.DeepEqual(t, consts.HTTP11, rh.protocol)\n\tassert.True(t, rh.IsHTTP11())\n\n\th := RequestHeader{}\n\th.SetProtocol(consts.HTTP10)\n\tassert.DeepEqual(t, consts.HTTP10, h.protocol)\n\n\th.SetProtocol(consts.HTTP11)\n\tassert.DeepEqual(t, consts.HTTP11, h.protocol)\n\tassert.True(t, h.IsHTTP11())\n}\n\nfunc TestResponseHeaderSetContentType(t *testing.T) {\n\th := ResponseHeader{}\n\th.SetContentType(\"foo\")\n\tassert.DeepEqual(t, h.contentType, []byte(\"foo\"))\n}\n\nfunc TestSetContentLengthBytes(t *testing.T) {\n\th := RequestHeader{}\n\th.SetContentLengthBytes([]byte(\"foo\"))\n\tassert.DeepEqual(t, h.contentLengthBytes, []byte(\"foo\"))\n\n\trh := ResponseHeader{}\n\trh.SetContentLengthBytes([]byte(\"foo\"))\n\tassert.DeepEqual(t, rh.contentLengthBytes, []byte(\"foo\"))\n}\n\nfunc TestInitContentLengthWithValue(t *testing.T) {\n\tinitLength := 100\n\th := RequestHeader{}\n\th.InitContentLengthWithValue(initLength)\n\tassert.DeepEqual(t, h.contentLength, initLength)\n\n\trh := ResponseHeader{}\n\trh.InitContentLengthWithValue(initLength)\n\tassert.DeepEqual(t, rh.contentLength, initLength)\n}\n\nfunc TestSetContentEncoding(t *testing.T) {\n\trh := ResponseHeader{}\n\trh.SetContentEncoding(\"gzip\")\n\tassert.DeepEqual(t, rh.contentEncoding, []byte(\"gzip\"))\n}\n\nfunc Test_peekRawHeader(t *testing.T) {\n\ts := \"Expect: 100-continue\\r\\nUser-Agent: foo\\r\\nHost: 127.0.0.1\\r\\nConnection: Keep-Alive\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tassert.DeepEqual(t, []byte(\"127.0.0.1\"), peekRawHeader([]byte(s), []byte(\"Host\")))\n}\n\nfunc TestResponseHeader_SetContentLength(t *testing.T) {\n\trh := new(ResponseHeader)\n\trh.SetContentLength(-1)\n\tassert.True(t, strings.Contains(string(rh.Header()), \"Transfer-Encoding: chunked\"))\n\trh.SetContentLength(-2)\n\tassert.True(t, strings.Contains(string(rh.Header()), \"Transfer-Encoding: identity\"))\n}\n\nfunc TestResponseHeader_SetContentRange(t *testing.T) {\n\trh := new(ResponseHeader)\n\trh.SetContentRange(1, 5, 10)\n\tassert.DeepEqual(t, rh.bufKV.value, []byte(\"bytes 1-5/10\"))\n}\n\nfunc TestSetCanonical(t *testing.T) {\n\th := ResponseHeader{}\n\th.SetCanonical([]byte(consts.HeaderContentType), []byte(\"foo\"))\n\th.SetCanonical([]byte(consts.HeaderServer), []byte(\"foo1\"))\n\th.SetCanonical([]byte(consts.HeaderSetCookie), []byte(\"foo2\"))\n\th.SetCanonical([]byte(consts.HeaderContentLength), []byte(\"3\"))\n\th.SetCanonical([]byte(consts.HeaderConnection), []byte(\"foo4\"))\n\th.SetCanonical([]byte(consts.HeaderTransferEncoding), []byte(\"foo5\"))\n\th.SetCanonical([]byte(consts.HeaderTrailer), []byte(\"foo7\"))\n\th.SetCanonical([]byte(\"bar\"), []byte(\"foo6\"))\n\n\tassert.DeepEqual(t, []byte(\"foo\"), h.ContentType())\n\tassert.DeepEqual(t, []byte(\"foo1\"), h.Server())\n\tassert.DeepEqual(t, true, strings.Contains(string(h.Header()), \"foo2\"))\n\tassert.DeepEqual(t, 3, h.ContentLength())\n\tassert.DeepEqual(t, false, h.ConnectionClose())\n\tassert.DeepEqual(t, false, strings.Contains(string(h.ContentType()), \"foo5\"))\n\tassert.DeepEqual(t, true, strings.Contains(string(h.Header()), \"Trailer: Foo7\"))\n\tassert.DeepEqual(t, true, strings.Contains(string(h.Header()), \"bar: foo6\"))\n}\n\nfunc TestHasAcceptEncodingBytes(t *testing.T) {\n\th := RequestHeader{}\n\th.Set(consts.HeaderAcceptEncoding, \"gzip\")\n\tassert.True(t, h.HasAcceptEncodingBytes([]byte(\"gzip\")))\n}\n\nfunc TestRequestHeaderGet(t *testing.T) {\n\th := RequestHeader{}\n\trightVal := \"yyy\"\n\th.Set(\"xxx\", rightVal)\n\tval := h.Get(\"xxx\")\n\tif val != rightVal {\n\t\tt.Fatalf(\"Unexpected %v. Expected %v\", val, rightVal)\n\t}\n}\n\nfunc TestResponseHeaderGet(t *testing.T) {\n\th := ResponseHeader{}\n\trightVal := \"yyy\"\n\th.Set(\"xxx\", rightVal)\n\tval := h.Get(\"xxx\")\n\tassert.DeepEqual(t, val, rightVal)\n}\n\nfunc TestRequestHeaderGetAll(t *testing.T) {\n\th := RequestHeader{}\n\th.Set(\"Foo-Bar\", \"foo\")\n\th.Add(\"Foo-Bar\", \"bar\")\n\th.Add(\"Foo-Bar\", \"foo-bar\")\n\tvalues := h.GetAll(\"Foo-Bar\")\n\tassert.DeepEqual(t, values, []string{\"foo\", \"bar\", \"foo-bar\"})\n}\n\nfunc TestResponseHeaderGetAll(t *testing.T) {\n\th := ResponseHeader{}\n\th.Set(\"Foo-Bar\", \"foo\")\n\th.Add(\"Foo-Bar\", \"bar\")\n\th.Add(\"Foo-Bar\", \"foo-bar\")\n\tvalues := h.GetAll(\"Foo-Bar\")\n\tassert.DeepEqual(t, values, []string{\"foo\", \"bar\", \"foo-bar\"})\n}\n\nfunc TestRequestHeaderVisitAll(t *testing.T) {\n\th := RequestHeader{}\n\th.Set(\"xxx\", \"yyy\")\n\th.Set(\"xxx2\", \"yyy2\")\n\th.SetHost(\"host\")\n\th.SetContentLengthBytes([]byte(\"content-length\"))\n\th.Set(consts.HeaderContentType, \"content-type\")\n\th.Set(consts.HeaderUserAgent, \"user-agent\")\n\terr := h.Trailer().SetTrailers([]byte(\"foo, bar\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Set trailer err %v\", err)\n\t}\n\th.SetCookie(\"foo\", \"bar\")\n\th.Set(consts.HeaderConnection, \"close\")\n\th.VisitAll(func(k, v []byte) {\n\t\tkey := string(k)\n\t\tvalue := string(v)\n\t\tswitch key {\n\t\tcase consts.HeaderHost:\n\t\t\tassert.DeepEqual(t, value, \"host\")\n\t\tcase consts.HeaderContentLength:\n\t\t\tassert.DeepEqual(t, value, \"content-length\")\n\t\tcase consts.HeaderContentType:\n\t\t\tassert.DeepEqual(t, value, \"content-type\")\n\t\tcase consts.HeaderUserAgent:\n\t\t\tassert.DeepEqual(t, value, \"user-agent\")\n\t\tcase consts.HeaderTrailer:\n\t\t\tassert.DeepEqual(t, value, \"Foo, Bar\")\n\t\tcase consts.HeaderCookie:\n\t\t\tassert.DeepEqual(t, value, \"foo=bar\")\n\t\tcase consts.HeaderConnection:\n\t\t\tassert.DeepEqual(t, value, \"close\")\n\t\tcase \"Xxx\":\n\t\t\tassert.DeepEqual(t, value, \"yyy\")\n\t\tcase \"Xxx2\":\n\t\t\tassert.DeepEqual(t, value, \"yyy2\")\n\t\tdefault:\n\t\t\tt.Fatalf(\"Unexpected key %v\", key)\n\t\t}\n\t})\n}\n\nfunc TestRequestHeaderCookie(t *testing.T) {\n\tvar h RequestHeader\n\th.SetCookie(\"foo\", \"bar\")\n\tcookie := h.Cookie(\"foo\")\n\tassert.DeepEqual(t, []byte(\"bar\"), cookie)\n}\n\nfunc TestRequestHeaderCookies(t *testing.T) {\n\tvar h RequestHeader\n\th.SetCookie(\"foo\", \"bar\")\n\th.SetCookie(\"привет\", \"мир\")\n\tcookies := h.Cookies()\n\tassert.DeepEqual(t, 2, len(cookies))\n\tassert.DeepEqual(t, []byte(\"foo\"), cookies[0].Key())\n\tassert.DeepEqual(t, []byte(\"bar\"), cookies[0].Value())\n\tassert.DeepEqual(t, []byte(\"привет\"), cookies[1].Key())\n\tassert.DeepEqual(t, []byte(\"мир\"), cookies[1].Value())\n}\n\nfunc TestRequestHeaderDel(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\th.Set(\"Foo-Bar\", \"baz\")\n\th.Set(\"aaa\", \"bbb\")\n\th.Set(\"ccc\", \"ddd\")\n\th.Set(consts.HeaderConnection, \"keep-alive\")\n\th.Set(consts.HeaderContentType, \"aaa\")\n\th.Set(consts.HeaderServer, \"aaabbb\")\n\th.Set(consts.HeaderContentLength, \"1123\")\n\th.Set(consts.HeaderTrailer, \"foo, bar\")\n\th.Set(consts.HeaderUserAgent, \"foo-bar\")\n\th.SetHost(\"foobar\")\n\th.SetCookie(\"foo\", \"bar\")\n\n\th.del([]byte(\"Foo-Bar\"))\n\th.del([]byte(\"Connection\"))\n\th.DelBytes([]byte(\"Content-Type\"))\n\th.del([]byte(consts.HeaderServer))\n\th.del([]byte(\"Content-Length\"))\n\th.del([]byte(\"Set-Cookie\"))\n\th.del([]byte(\"Host\"))\n\th.del([]byte(consts.HeaderTrailer))\n\th.del([]byte(consts.HeaderUserAgent))\n\th.DelCookie(\"foo\")\n\th.Del(\"ccc\")\n\n\thv := h.Peek(\"aaa\")\n\tif string(hv) != \"bbb\" {\n\t\tt.Fatalf(\"unexpected header value: %q. Expecting %q\", hv, \"bbb\")\n\t}\n\thv = h.Peek(\"ccc\")\n\tif string(hv) != \"\" {\n\t\tt.Fatalf(\"unexpected header value: %q. Expecting %q\", hv, \"\")\n\t}\n\thv = h.Peek(\"Foo-Bar\")\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero header value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderConnection)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderContentType)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderServer)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderContentLength)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.FullCookie()\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderCookie)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderTrailer)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderUserAgent)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\tif h.ContentLength() != 0 {\n\t\tt.Fatalf(\"unexpected content-length: %d. Expecting 0\", h.ContentLength())\n\t}\n}\n\nfunc TestResponseHeaderDel(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\th.Set(\"Foo-Bar\", \"baz\")\n\th.Set(\"aaa\", \"bbb\")\n\th.Set(consts.HeaderConnection, \"keep-alive\")\n\th.Set(consts.HeaderContentType, \"aaa\")\n\th.Set(consts.HeaderContentEncoding, \"gzip\")\n\th.Set(consts.HeaderServer, \"aaabbb\")\n\th.Set(consts.HeaderContentLength, \"1123\")\n\th.Set(consts.HeaderTrailer, \"foo, bar\")\n\n\tvar c Cookie\n\tc.SetKey(\"foo\")\n\tc.SetValue(\"bar\")\n\th.SetCookie(&c)\n\n\th.Del(\"foo-bar\")\n\th.Del(\"connection\")\n\th.DelBytes([]byte(\"content-type\"))\n\th.Del(consts.HeaderServer)\n\th.Del(\"content-length\")\n\th.Del(\"set-cookie\")\n\th.Del(\"content-encoding\")\n\th.Del(consts.HeaderTrailer)\n\n\thv := h.Peek(\"aaa\")\n\tif string(hv) != \"bbb\" {\n\t\tt.Fatalf(\"unexpected header value: %q. Expecting %q\", hv, \"bbb\")\n\t}\n\thv = h.Peek(\"Foo-Bar\")\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero header value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderConnection)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderContentType)\n\tif string(hv) != string(bytestr.DefaultContentType) {\n\t\tt.Fatalf(\"unexpected content-type: %q. Expecting %q\", hv, bytestr.DefaultContentType)\n\t}\n\thv = h.Peek(consts.HeaderContentEncoding)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderServer)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(consts.HeaderContentLength)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\n\thv = h.Peek(consts.HeaderTrailer)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\n\tif h.Cookie(&c) {\n\t\tt.Fatalf(\"unexpected cookie obtained: %v\", &c)\n\t}\n\n\tif h.ContentLength() != 0 {\n\t\tt.Fatalf(\"unexpected content-length: %d. Expecting 0\", h.ContentLength())\n\t}\n}\n\nfunc TestResponseHeaderDelClientCookie(t *testing.T) {\n\tt.Parallel()\n\n\tcookieName := \"foobar\"\n\n\tvar h ResponseHeader\n\tc := AcquireCookie()\n\tc.SetKey(cookieName)\n\tc.SetValue(\"aasdfsdaf\")\n\th.SetCookie(c)\n\n\th.DelClientCookieBytes([]byte(cookieName))\n\tif !h.Cookie(c) {\n\t\tt.Fatalf(\"expecting cookie %q\", c.Key())\n\t}\n\tif !c.Expire().Equal(CookieExpireDelete) {\n\t\tt.Fatalf(\"unexpected cookie expiration time: %s. Expecting %s\", c.Expire(), CookieExpireDelete)\n\t}\n\tif len(c.Value()) > 0 {\n\t\tt.Fatalf(\"unexpected cookie value: %q. Expecting empty value\", c.Value())\n\t}\n\tReleaseCookie(c)\n}\n\nfunc TestResponseHeaderResetConnectionClose(t *testing.T) {\n\th := ResponseHeader{}\n\th.Set(consts.HeaderConnection, \"close\")\n\thv := h.Peek(consts.HeaderConnection)\n\tassert.DeepEqual(t, hv, []byte(\"close\"))\n\th.SetConnectionClose(true)\n\th.ResetConnectionClose()\n\tassert.False(t, h.connectionClose)\n\thv = h.Peek(consts.HeaderConnection)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"ResetConnectionClose do not work,Connection: %q\", hv)\n\t}\n}\n\nfunc TestRequestHeaderResetConnectionClose(t *testing.T) {\n\th := RequestHeader{}\n\th.Set(consts.HeaderConnection, \"close\")\n\thv := h.Peek(consts.HeaderConnection)\n\tassert.DeepEqual(t, hv, []byte(\"close\"))\n\th.connectionClose = true\n\th.ResetConnectionClose()\n\tassert.False(t, h.connectionClose)\n\thv = h.Peek(consts.HeaderConnection)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"ResetConnectionClose do not work,Connection: %q\", hv)\n\t}\n}\n\nfunc TestCheckWriteHeaderCode(t *testing.T) {\n\tbuffer := bytes.NewBuffer(make([]byte, 0, 1024))\n\thlog.SetOutput(buffer)\n\tcheckWriteHeaderCode(99)\n\tassert.True(t, strings.Contains(buffer.String(), \"[Warn] HERTZ: Invalid StatusCode code\"))\n\tbuffer.Reset()\n\tcheckWriteHeaderCode(600)\n\tassert.True(t, strings.Contains(buffer.String(), \"[Warn] HERTZ: Invalid StatusCode code\"))\n\tbuffer.Reset()\n\tcheckWriteHeaderCode(100)\n\tassert.False(t, strings.Contains(buffer.String(), \"[Warn] HERTZ: Invalid StatusCode code\"))\n\tbuffer.Reset()\n\tcheckWriteHeaderCode(599)\n\tassert.False(t, strings.Contains(buffer.String(), \"[Warn] HERTZ: Invalid StatusCode code\"))\n}\n\nfunc TestResponseHeaderAdd(t *testing.T) {\n\tt.Parallel()\n\n\tm := make(map[string]struct{})\n\tvar h ResponseHeader\n\th.Add(\"aaa\", \"bbb\")\n\th.Add(\"content-type\", \"xxx\")\n\th.SetContentEncoding(\"gzip\")\n\tm[\"bbb\"] = struct{}{}\n\tm[\"xxx\"] = struct{}{}\n\tm[\"gzip\"] = struct{}{}\n\tfor i := 0; i < 10; i++ {\n\t\tv := fmt.Sprintf(\"%d\", i)\n\t\th.Add(\"Foo-Bar\", v)\n\t\tm[v] = struct{}{}\n\t}\n\tif h.Len() != 13 {\n\t\tt.Fatalf(\"unexpected header len %d. Expecting 13\", h.Len())\n\t}\n\n\th.VisitAll(func(k, v []byte) {\n\t\tswitch string(k) {\n\t\tcase \"Aaa\", \"Foo-Bar\", \"Content-Type\", \"Content-Encoding\":\n\t\t\tif _, ok := m[string(v)]; !ok {\n\t\t\t\tt.Fatalf(\"unexpected value found %q. key %q\", v, k)\n\t\t\t}\n\t\t\tdelete(m, string(v))\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected key found: %q\", k)\n\t\t}\n\t})\n\tif len(m) > 0 {\n\t\tt.Fatalf(\"%d headers are missed\", len(m))\n\t}\n}\n\nfunc TestRequestHeaderAdd(t *testing.T) {\n\tt.Parallel()\n\n\tm := make(map[string]struct{})\n\tvar h RequestHeader\n\th.Add(\"aaa\", \"bbb\")\n\th.Add(\"user-agent\", \"xxx\")\n\tm[\"bbb\"] = struct{}{}\n\tm[\"xxx\"] = struct{}{}\n\tfor i := 0; i < 10; i++ {\n\t\tv := fmt.Sprintf(\"%d\", i)\n\t\th.Add(\"Foo-Bar\", v)\n\t\tm[v] = struct{}{}\n\t}\n\tif h.Len() != 12 {\n\t\tt.Fatalf(\"unexpected header len %d. Expecting 12\", h.Len())\n\t}\n\n\th.VisitAll(func(k, v []byte) {\n\t\tswitch string(k) {\n\t\tcase \"Aaa\", \"Foo-Bar\", \"User-Agent\":\n\t\t\tif _, ok := m[string(v)]; !ok {\n\t\t\t\tt.Fatalf(\"unexpected value found %q. key %q\", v, k)\n\t\t\t}\n\t\t\tdelete(m, string(v))\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected key found: %q\", k)\n\t\t}\n\t})\n\tif len(m) > 0 {\n\t\tt.Fatalf(\"%d headers are missed\", len(m))\n\t}\n}\n\nfunc TestResponseHeaderAddContentType(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\th.Add(\"Content-Type\", \"test\")\n\n\tgot := string(h.Peek(\"Content-Type\"))\n\texpected := \"test\"\n\tif got != expected {\n\t\tt.Errorf(\"expected %q got %q\", expected, got)\n\t}\n\n\tif n := strings.Count(string(h.Header()), \"Content-Type: \"); n != 1 {\n\t\tt.Errorf(\"Content-Type occurred %d times\", n)\n\t}\n}\n\nfunc TestResponseHeaderAddContentEncoding(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\th.Add(\"Content-Encoding\", \"test\")\n\n\tgot := string(h.ContentEncoding())\n\texpected := \"test\"\n\tif got != expected {\n\t\tt.Errorf(\"expected %q got %q\", expected, got)\n\t}\n\n\tif n := strings.Count(string(h.Header()), \"Content-Encoding: \"); n != 1 {\n\t\tt.Errorf(\"Content-Encoding occurred %d times\", n)\n\t}\n}\n\nfunc TestRequestHeaderAddContentType(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\th.Add(\"Content-Type\", \"test\")\n\n\tgot := string(h.Peek(\"Content-Type\"))\n\texpected := \"test\"\n\tif got != expected {\n\t\tt.Errorf(\"expected %q got %q\", expected, got)\n\t}\n\n\tif n := strings.Count(h.String(), \"Content-Type: \"); n != 1 {\n\t\tt.Errorf(\"Content-Type occurred %d times\", n)\n\t}\n}\n\nfunc TestSetMultipartFormBoundary(t *testing.T) {\n\th := RequestHeader{}\n\th.SetMultipartFormBoundary(\"foo\")\n\tassert.DeepEqual(t, h.contentType, []byte(\"multipart/form-data; boundary=foo\"))\n}\n\nfunc TestRequestHeaderSetByteRange(t *testing.T) {\n\tvar h RequestHeader\n\th.SetByteRange(1, 5)\n\thv := h.Peek(consts.HeaderRange)\n\tassert.DeepEqual(t, hv, []byte(\"bytes=1-5\"))\n}\n\nfunc TestRequestHeaderSetMethodBytes(t *testing.T) {\n\tvar h RequestHeader\n\th.SetMethodBytes([]byte(\"foo\"))\n\tassert.DeepEqual(t, h.Method(), []byte(\"foo\"))\n}\n\nfunc TestRequestHeaderSetBytesKV(t *testing.T) {\n\tvar h RequestHeader\n\th.SetBytesKV([]byte(\"foo\"), []byte(\"foo1\"))\n\thv := h.Peek(\"foo\")\n\tassert.DeepEqual(t, hv, []byte(\"foo1\"))\n}\n\nfunc TestResponseHeaderSetBytesV(t *testing.T) {\n\tvar h ResponseHeader\n\th.SetBytesV(\"foo\", []byte(\"foo1\"))\n\thv := h.Peek(\"foo\")\n\tassert.DeepEqual(t, hv, []byte(\"foo1\"))\n}\n\nfunc TestRequestHeaderInitBufValue(t *testing.T) {\n\tvar h RequestHeader\n\tslice := make([]byte, 0, 10)\n\th.InitBufValue(10)\n\tassert.DeepEqual(t, cap(h.bufKV.value), cap(slice))\n\tassert.DeepEqual(t, h.GetBufValue(), slice)\n}\n\nfunc TestRequestHeaderDelAllCookies(t *testing.T) {\n\tvar h RequestHeader\n\th.SetCanonical([]byte(consts.HeaderSetCookie), []byte(\"foo2\"))\n\th.DelAllCookies()\n\thv := h.FullCookie()\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n}\n\nfunc TestResponseHeaderDelAllCookies(t *testing.T) {\n\tvar h ResponseHeader\n\th.SetCanonical([]byte(consts.HeaderSetCookie), []byte(\"foo\"))\n\th.DelAllCookies()\n\thv := h.FullCookie()\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n}\n\nfunc TestRequestHeaderSetNoDefaultContentType(t *testing.T) {\n\tvar h RequestHeader\n\th.SetMethod(http.MethodPost)\n\tb := h.AppendBytes(nil)\n\tassert.DeepEqual(t, b, []byte(\"POST / HTTP/1.1\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\n\\r\\n\"))\n\th.SetNoDefaultContentType(true)\n\tb = h.AppendBytes(nil)\n\tassert.DeepEqual(t, b, []byte(\"POST / HTTP/1.1\\r\\n\\r\\n\"))\n}\n\nfunc TestRequestHeader_PeekAll(t *testing.T) {\n\tt.Parallel()\n\th := &RequestHeader{}\n\th.Add(consts.HeaderConnection, \"keep-alive\")\n\th.Add(\"Content-Type\", \"aaa\")\n\th.Add(consts.HeaderHost, \"aaabbb\")\n\th.Add(\"User-Agent\", \"asdfas\")\n\th.Add(\"Content-Length\", \"1123\")\n\th.Add(\"Cookie\", \"foobar=baz\")\n\th.Add(\"aaa\", \"aaa\")\n\th.Add(\"aaa\", \"bbb\")\n\n\texpectRequestHeaderAll(t, h, consts.HeaderConnection, [][]byte{[]byte(\"keep-alive\")})\n\texpectRequestHeaderAll(t, h, \"Content-Type\", [][]byte{[]byte(\"aaa\")})\n\texpectRequestHeaderAll(t, h, consts.HeaderHost, [][]byte{[]byte(\"aaabbb\")})\n\texpectRequestHeaderAll(t, h, \"User-Agent\", [][]byte{[]byte(\"asdfas\")})\n\texpectRequestHeaderAll(t, h, \"Content-Length\", [][]byte{[]byte(\"1123\")})\n\texpectRequestHeaderAll(t, h, \"Cookie\", [][]byte{[]byte(\"foobar=baz\")})\n\texpectRequestHeaderAll(t, h, \"aaa\", [][]byte{[]byte(\"aaa\"), []byte(\"bbb\")})\n\n\th.DelBytes([]byte(\"Content-Type\"))\n\th.DelBytes([]byte((consts.HeaderHost)))\n\th.DelBytes([]byte(\"aaa\"))\n\texpectRequestHeaderAll(t, h, \"Content-Type\", [][]byte{})\n\texpectRequestHeaderAll(t, h, consts.HeaderHost, [][]byte{})\n\texpectRequestHeaderAll(t, h, \"aaa\", [][]byte{})\n}\n\nfunc expectRequestHeaderAll(t *testing.T, h *RequestHeader, key string, expectedValue [][]byte) {\n\tif len(h.PeekAll(key)) != len(expectedValue) {\n\t\tt.Fatalf(\"Unexpected size for key %q: %d. Expected %d\", key, len(h.PeekAll(key)), len(expectedValue))\n\t}\n\tassert.DeepEqual(t, h.PeekAll(key), expectedValue)\n}\n\nfunc TestResponseHeader_PeekAll(t *testing.T) {\n\tt.Parallel()\n\n\th := &ResponseHeader{}\n\th.Add(consts.HeaderContentType, \"aaa/bbb\")\n\th.Add(consts.HeaderContentEncoding, \"gzip\")\n\th.Add(consts.HeaderConnection, \"close\")\n\th.Add(consts.HeaderContentLength, \"1234\")\n\th.Add(consts.HeaderServer, \"aaaa\")\n\th.Add(consts.HeaderSetCookie, \"cccc\")\n\th.Add(\"aaa\", \"aaa\")\n\th.Add(\"aaa\", \"bbb\")\n\n\texpectResponseHeaderAll(t, h, consts.HeaderContentType, [][]byte{[]byte(\"aaa/bbb\")})\n\texpectResponseHeaderAll(t, h, consts.HeaderContentEncoding, [][]byte{[]byte(\"gzip\")})\n\texpectResponseHeaderAll(t, h, consts.HeaderConnection, [][]byte{[]byte(\"close\")})\n\texpectResponseHeaderAll(t, h, consts.HeaderContentLength, [][]byte{[]byte(\"1234\")})\n\texpectResponseHeaderAll(t, h, consts.HeaderServer, [][]byte{[]byte(\"aaaa\")})\n\texpectResponseHeaderAll(t, h, consts.HeaderSetCookie, [][]byte{[]byte(\"cccc\")})\n\texpectResponseHeaderAll(t, h, \"aaa\", [][]byte{[]byte(\"aaa\"), []byte(\"bbb\")})\n\n\th.Del(consts.HeaderContentType)\n\th.Del(consts.HeaderContentEncoding)\n\texpectResponseHeaderAll(t, h, consts.HeaderContentType, [][]byte{bytestr.DefaultContentType})\n\texpectResponseHeaderAll(t, h, consts.HeaderContentEncoding, [][]byte{})\n}\n\nfunc expectResponseHeaderAll(t *testing.T, h *ResponseHeader, key string, expectedValue [][]byte) {\n\tif len(h.PeekAll(key)) != len(expectedValue) {\n\t\tt.Fatalf(\"Unexpected size for key %q: %d. Expected %d\", key, len(h.PeekAll(key)), len(expectedValue))\n\t}\n\tassert.DeepEqual(t, h.PeekAll(key), expectedValue)\n}\n\nfunc TestRequestHeaderCopyTo(t *testing.T) {\n\tt.Parallel()\n\n\th, hCopy := &RequestHeader{}, &RequestHeader{}\n\th.SetProtocol(consts.HTTP10)\n\th.SetMethod(consts.MethodPatch)\n\th.SetNoDefaultContentType(true)\n\th.Add(consts.HeaderConnection, \"keep-alive\")\n\th.Add(\"Content-Type\", \"aaa\")\n\th.Add(consts.HeaderHost, \"aaabbb\")\n\th.Add(\"User-Agent\", \"asdfas\")\n\th.Add(\"Content-Length\", \"1123\")\n\th.Add(\"Cookie\", \"foobar=baz\")\n\th.Add(\"aaa\", \"aaa\")\n\th.Add(\"aaa\", \"bbb\")\n\n\th.CopyTo(hCopy)\n\texpectRequestHeaderAll(t, hCopy, consts.HeaderConnection, [][]byte{[]byte(\"keep-alive\")})\n\texpectRequestHeaderAll(t, hCopy, \"Content-Type\", [][]byte{[]byte(\"aaa\")})\n\texpectRequestHeaderAll(t, hCopy, consts.HeaderHost, [][]byte{[]byte(\"aaabbb\")})\n\texpectRequestHeaderAll(t, hCopy, \"User-Agent\", [][]byte{[]byte(\"asdfas\")})\n\texpectRequestHeaderAll(t, hCopy, \"Content-Length\", [][]byte{[]byte(\"1123\")})\n\texpectRequestHeaderAll(t, hCopy, \"Cookie\", [][]byte{[]byte(\"foobar=baz\")})\n\texpectRequestHeaderAll(t, hCopy, \"aaa\", [][]byte{[]byte(\"aaa\"), []byte(\"bbb\")})\n\tassert.DeepEqual(t, hCopy.GetProtocol(), consts.HTTP10)\n\tassert.DeepEqual(t, hCopy.noDefaultContentType, true)\n\tassert.DeepEqual(t, string(hCopy.Method()), consts.MethodPatch)\n}\n\nfunc TestResponseHeaderCopyTo(t *testing.T) {\n\tt.Parallel()\n\n\th, hCopy := &ResponseHeader{}, &ResponseHeader{}\n\th.SetProtocol(consts.HTTP10)\n\th.SetHeaderLength(100)\n\th.SetNoDefaultContentType(true)\n\th.Add(consts.HeaderContentType, \"aaa/bbb\")\n\th.Add(consts.HeaderContentEncoding, \"gzip\")\n\th.Add(consts.HeaderConnection, \"close\")\n\th.Add(consts.HeaderContentLength, \"1234\")\n\th.Add(consts.HeaderServer, \"aaaa\")\n\th.Add(consts.HeaderSetCookie, \"cccc\")\n\th.Add(\"aaa\", \"aaa\")\n\th.Add(\"aaa\", \"bbb\")\n\n\th.CopyTo(hCopy)\n\texpectResponseHeaderAll(t, hCopy, consts.HeaderContentType, [][]byte{[]byte(\"aaa/bbb\")})\n\texpectResponseHeaderAll(t, hCopy, consts.HeaderContentEncoding, [][]byte{[]byte(\"gzip\")})\n\texpectResponseHeaderAll(t, hCopy, consts.HeaderConnection, [][]byte{[]byte(\"close\")})\n\texpectResponseHeaderAll(t, hCopy, consts.HeaderContentLength, [][]byte{[]byte(\"1234\")})\n\texpectResponseHeaderAll(t, hCopy, consts.HeaderServer, [][]byte{[]byte(\"aaaa\")})\n\texpectResponseHeaderAll(t, hCopy, consts.HeaderSetCookie, [][]byte{[]byte(\"cccc\")})\n\texpectResponseHeaderAll(t, hCopy, \"aaa\", [][]byte{[]byte(\"aaa\"), []byte(\"bbb\")})\n\tassert.DeepEqual(t, hCopy.GetProtocol(), consts.HTTP10)\n\tassert.DeepEqual(t, hCopy.noDefaultContentType, true)\n\tassert.DeepEqual(t, hCopy.GetHeaderLength(), 100)\n}\n\nfunc TestResponseHeaderDateEmpty(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\th.noDefaultDate = true\n\theaders := string(h.Header())\n\n\tif strings.Contains(headers, \"\\r\\nDate: \") {\n\t\tt.Fatalf(\"ResponseDateNoDefaultNotEmpty fail, response: \\n%+v\\noutcome: \\n%q\\n\", h, headers) //nolint:govet\n\t}\n}\n\nfunc TestSetTrailerWithROString(t *testing.T) {\n\th := &RequestHeader{}\n\th.Add(consts.HeaderTrailer, \"foo,bar,hertz\")\n\tassert.DeepEqual(t, \"Foo, Bar, Hertz\", h.Get(consts.HeaderTrailer))\n\n\th1 := &ResponseHeader{}\n\th1.Add(consts.HeaderTrailer, \"foo,bar,hertz\")\n\tassert.DeepEqual(t, \"Foo, Bar, Hertz\", h1.Get(consts.HeaderTrailer))\n}\n\nfunc Benchmark_RequestHeader_Peek(b *testing.B) {\n\th := &RequestHeader{}\n\th.Add(\"hello\", \"world\")\n\tif s := string(h.Peek(\"hello\")); s != \"world\" {\n\t\tb.Fatal(s)\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\th.Peek(\"hello\")\n\t}\n}\n\nfunc TestAppendHeaderLine(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tdst      []byte\n\t\tkey      []byte\n\t\tvalue    []byte\n\t\texpected []byte\n\t}{\n\t\t{\n\t\t\tname:     \"basic header\",\n\t\t\tdst:      []byte{},\n\t\t\tkey:      []byte(\"Content-Type\"),\n\t\t\tvalue:    []byte(\"application/json\"),\n\t\t\texpected: []byte(\"Content-Type: application/json\\r\\n\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"value with newlines\",\n\t\t\tdst:      []byte{},\n\t\t\tkey:      []byte(\"X-Custom\"),\n\t\t\tvalue:    []byte(\"value\\nwith\\rnewlines\"),\n\t\t\texpected: []byte(\"X-Custom: value with newlines\\r\\n\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"invalid key\",\n\t\t\tdst:      []byte(\"initial\"),\n\t\t\tkey:      []byte(\"Invalid\\x00Key\"),\n\t\t\tvalue:    []byte(\"value\"),\n\t\t\texpected: []byte(\"initial\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := appendHeaderLine(tt.dst, tt.key, tt.value)\n\t\t\tif !bytes.Equal(result, tt.expected) {\n\t\t\t\tt.Errorf(\"appendHeaderLine() = %q, want %q\", result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/header_timing_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"net/http\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc BenchmarkHTTPHeaderGet(b *testing.B) {\n\thh := make(http.Header)\n\thh.Set(\"X-tt-logid\", \"abc123456789\")\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\thh.Get(\"X-tt-logid\")\n\t}\n}\n\nfunc BenchmarkHertzHeaderGet(b *testing.B) {\n\tzh := new(ResponseHeader)\n\tzh.Set(\"X-tt-logid\", \"abc123456789\")\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tzh.Get(\"X-tt-logid\")\n\t}\n}\n\nfunc BenchmarkHTTPHeaderSet(b *testing.B) {\n\thh := make(http.Header)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\thh.Set(\"X-tt-logid\", \"abc123456789\")\n\t}\n}\n\nfunc BenchmarkHertzHeaderSet(b *testing.B) {\n\tzh := new(ResponseHeader)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tzh.Set(\"X-tt-logid\", \"abc123456789\")\n\t}\n}\n\nfunc BenchmarkHTTPHeaderAdd(b *testing.B) {\n\thh := make(http.Header)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\thh.Add(\"X-tt-\"+strconv.Itoa(i), \"abc123456789\")\n\t}\n}\n\nfunc BenchmarkHertzHeaderAdd(b *testing.B) {\n\tzh := new(ResponseHeader)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tzh.Add(\"X-tt-\"+strconv.Itoa(i), \"abc123456789\")\n\t}\n}\n\nfunc BenchmarkRefreshServerDate(b *testing.B) {\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\trefreshServerDate()\n\t}\n}\n\nfunc BenchmarkHeaderAppendBytes(b *testing.B) {\n\th := new(ResponseHeader)\n\th.Set(\"X-tt-logid\", \"abc123456789\")\n\th.SetServerBytes([]byte(\"hertz\"))\n\n\tbuf := make([]byte, 0, 1024)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = h.AppendBytes(buf)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/http1/client.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage http1\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/internal/nocopy\"\n\t\"github.com/cloudwego/hertz/pkg/app/client/retry\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/timer\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/dialer\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/client\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/proxy\"\n\treqI \"github.com/cloudwego/hertz/pkg/protocol/http1/req\"\n\trespI \"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n)\n\nvar (\n\terrConnectionClosed = errs.NewPublic(\"the server closed connection before returning the first response byte. \" +\n\t\t\"Make sure the server returns 'Connection: close' response header before closing the connection\")\n\n\terrTimeout = errs.New(errs.ErrTimeout, errs.ErrorTypePublic, \"host client\")\n)\n\n// HostClient balances http requests among hosts listed in Addr.\n//\n// HostClient may be used for balancing load among multiple upstream hosts.\n// While multiple addresses passed to HostClient.Addr may be used for balancing\n// load among them, it would be better using LBClient instead, since HostClient\n// may unevenly balance load among upstream hosts.\n//\n// It is forbidden copying HostClient instances. Create new instances instead.\n//\n// It is safe calling HostClient methods from concurrently running goroutines.\ntype HostClient struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\t*ClientOptions\n\n\t// Comma-separated list of upstream HTTP server host addresses,\n\t// which are passed to Dialer in a round-robin manner.\n\t//\n\t// Each address may contain port if default dialer is used.\n\t// For example,\n\t//\n\t//    - foobar.com:80\n\t//    - foobar.com:443\n\t//    - foobar.com:8080\n\tAddr     string\n\tIsTLS    bool\n\tProxyURI *protocol.URI\n\n\tclientName  atomic.Value\n\tlastUseTime uint32\n\n\tconnsLock  sync.Mutex\n\tconnsCount int\n\tconns      []*clientConn\n\tconnsWait  *wantConnQueue\n\n\taddrsLock sync.Mutex\n\taddrs     []string\n\taddrIdx   uint32\n\n\ttlsConfigMap     map[string]*tls.Config\n\ttlsConfigMapLock sync.Mutex\n\n\tpendingRequests int32\n\n\tconnsCleanerRun bool\n\n\tclosed chan struct{}\n}\n\nfunc (c *HostClient) SetDynamicConfig(dc *client.DynamicConfig) {\n\tc.Addr = dc.Addr\n\tc.ProxyURI = dc.ProxyURI\n\tc.IsTLS = dc.IsTLS\n\n\t// start observation after setting addr to avoid race\n\tif c.StateObserve != nil {\n\t\tgo func() {\n\t\t\tt := time.NewTicker(c.ObservationInterval)\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-c.closed:\n\t\t\t\t\treturn\n\t\t\t\tcase <-t.C:\n\t\t\t\t\tc.StateObserve(c)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n}\n\ntype clientConn struct {\n\tc network.Conn\n\n\tcreatedTime time.Time\n\tlastUseTime time.Time\n}\n\nvar startTimeUnix = time.Now().Unix()\n\n// LastUseTime returns time the client was last used\nfunc (c *HostClient) LastUseTime() time.Time {\n\tn := atomic.LoadUint32(&c.lastUseTime)\n\treturn time.Unix(startTimeUnix+int64(n), 0)\n}\n\n// Get returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\nfunc (c *HostClient) Get(ctx context.Context, dst []byte, url string) (statusCode int, body []byte, err error) {\n\treturn client.GetURL(ctx, dst, url, c)\n}\n\nfunc (c *HostClient) ConnectionCount() (count int) {\n\tc.connsLock.Lock()\n\tcount = len(c.conns)\n\tc.connsLock.Unlock()\n\treturn\n}\n\nfunc (c *HostClient) WantConnectionCount() (count int) {\n\treturn c.connsWait.len()\n}\n\nfunc (c *HostClient) ConnPoolState() config.ConnPoolState {\n\tc.connsLock.Lock()\n\tdefer c.connsLock.Unlock()\n\tcps := config.ConnPoolState{\n\t\tPoolConnNum:  len(c.conns),\n\t\tTotalConnNum: c.connsCount,\n\t\tAddr:         c.Addr,\n\t\tMaxConns:     c.MaxConns,\n\t}\n\n\tif c.connsWait != nil {\n\t\tcps.WaitConnNum = c.connsWait.len()\n\t}\n\treturn cps\n}\n\n// GetTimeout returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// errTimeout error is returned if url contents couldn't be fetched\n// during the given timeout.\nfunc (c *HostClient) GetTimeout(ctx context.Context, dst []byte, url string, timeout time.Duration) (statusCode int, body []byte, err error) {\n\treturn client.GetURLTimeout(ctx, dst, url, timeout, c)\n}\n\n// GetDeadline returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// errTimeout error is returned if url contents couldn't be fetched\n// until the given deadline.\nfunc (c *HostClient) GetDeadline(ctx context.Context, dst []byte, url string, deadline time.Time) (statusCode int, body []byte, err error) {\n\treturn client.GetURLDeadline(ctx, dst, url, deadline, c)\n}\n\n// Post sends POST request to the given url with the given POST arguments.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// Empty POST body is sent if postArgs is nil.\nfunc (c *HostClient) Post(ctx context.Context, dst []byte, url string, postArgs *protocol.Args) (statusCode int, body []byte, err error) {\n\treturn client.PostURL(ctx, dst, url, postArgs, c)\n}\n\n// A wantConnQueue is a queue of wantConns.\n//\n// inspired by net/http/transport.go\ntype wantConnQueue struct {\n\t// This is a queue, not a deque.\n\t// It is split into two stages - head[headPos:] and tail.\n\t// popFront is trivial (headPos++) on the first stage, and\n\t// pushBack is trivial (append) on the second stage.\n\t// If the first stage is empty, popFront can swap the\n\t// first and second stages to remedy the situation.\n\t//\n\t// This two-stage split is analogous to the use of two lists\n\t// in Okasaki's purely functional queue but without the\n\t// overhead of reversing the list when swapping stages.\n\thead    []*wantConn\n\theadPos int\n\ttail    []*wantConn\n}\n\n// A wantConn records state about a wanted connection\n// (that is, an active call to getConn).\n// The conn may be gotten by dialing or by finding an idle connection,\n// or a cancellation may make the conn no longer wanted.\n// These three options are racing against each other and use\n// wantConn to coordinate and agree about the winning outcome.\n//\n// inspired by net/http/transport.go\ntype wantConn struct {\n\tready chan struct{}\n\tmu    sync.Mutex // protects conn, err, close(ready)\n\tconn  *clientConn\n\terr   error\n}\n\n// DoTimeout performs the given request and waits for response during\n// the given timeout duration.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// errTimeout is returned if the response wasn't returned during\n// the given timeout.\n//\n// If MaxConns is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\n//\n// Warning: DoTimeout does not terminate the request itself. The request will\n// continue in the background and the response will be discarded.\n// If requests take too long and the connection pool gets filled up please\n// try setting a ReadTimeout.\nfunc (c *HostClient) DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration) error {\n\treturn client.DoTimeout(ctx, req, resp, timeout, c)\n}\n\n// DoDeadline performs the given request and waits for response until\n// the given deadline.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// errTimeout is returned if the response wasn't returned until\n// the given deadline.\n//\n// If MaxConns is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *HostClient) DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time) error {\n\treturn client.DoDeadline(ctx, req, resp, deadline, c)\n}\n\n// DoRedirects performs the given http request and fills the given http response,\n// following up to maxRedirectsCount redirects. When the redirect count exceeds\n// maxRedirectsCount, ErrTooManyRedirects is returned.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// Response is ignored if resp is nil.\n//\n// If MaxConns is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *HostClient) DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, maxRedirectsCount int) error {\n\t_, _, err := client.DoRequestFollowRedirects(ctx, req, resp, req.URI().String(), maxRedirectsCount, c)\n\treturn err\n}\n\n// Do performs the given http request and sets the corresponding response.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// If MaxConns is configured (> 0), ErrNoFreeConns is returned\n// when all connections to the host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *HostClient) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error {\n\tif ctx == nil {\n\t\tpanic(\"ctx is nil\")\n\t}\n\n\tvar (\n\t\terr                error\n\t\tcanIdempotentRetry bool\n\t\tisDefaultRetryFunc                    = true\n\t\tattempts           uint               = 0\n\t\tconnAttempts       uint               = 0\n\t\tmaxAttempts        uint               = 1\n\t\tisRequestRetryable client.RetryIfFunc = client.DefaultRetryIf\n\t)\n\tretryCfg := c.ClientOptions.RetryConfig\n\tif retryCfg != nil {\n\t\tmaxAttempts = retryCfg.MaxAttemptTimes\n\t}\n\n\tif c.ClientOptions.RetryIfFunc != nil {\n\t\tisRequestRetryable = c.ClientOptions.RetryIfFunc\n\t\t// if the user has provided a custom retry function, the canIdempotentRetry has no meaning anymore.\n\t\t// User will have full control over the retry logic through the custom retry function.\n\t\tisDefaultRetryFunc = false\n\t}\n\n\tatomic.AddInt32(&c.pendingRequests, 1)\n\treq.Options().StartRequest()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treq.CloseBodyStream() //nolint:errcheck\n\t\t\treturn ctx.Err()\n\t\tdefault:\n\t\t}\n\n\t\tcanIdempotentRetry, err = c.do(req, resp)\n\t\t// If there is no custom retry and err is equal to nil, the loop simply exits.\n\t\tif err == nil && isDefaultRetryFunc {\n\t\t\tif connAttempts != 0 {\n\t\t\t\thlog.SystemLogger().Warnf(\"Client connection attempt times: %d, url: %s. \"+\n\t\t\t\t\t\"This is mainly because the connection in pool is closed by peer in advance. \"+\n\t\t\t\t\t\"If this number is too high which indicates that long-connection are basically unavailable, \"+\n\t\t\t\t\t\"try to change the request to short-connection.\\n\", connAttempts, req.URI().FullURI())\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\t// This connection is closed by the peer when it is in the connection pool.\n\t\t//\n\t\t// This case is possible if the server closes the idle\n\t\t// keep-alive connection on timeout.\n\t\t//\n\t\t// Apache and nginx usually do this.\n\t\tif canIdempotentRetry && client.DefaultRetryIf(req, resp, err) && errors.Is(err, errs.ErrBadPoolConn) {\n\t\t\tconnAttempts++\n\t\t\tcontinue\n\t\t}\n\n\t\tif isDefaultRetryFunc {\n\t\t\tbreak\n\t\t}\n\n\t\tattempts++\n\t\tif attempts >= maxAttempts {\n\t\t\tbreak\n\t\t}\n\n\t\t// Check whether this request should be retried\n\t\tif !isRequestRetryable(req, resp, err) {\n\t\t\tbreak\n\t\t}\n\n\t\twait := retry.Delay(attempts, err, retryCfg)\n\t\t// Retry after wait time\n\t\ttime.Sleep(wait)\n\t}\n\tatomic.AddInt32(&c.pendingRequests, -1)\n\n\tif err == io.EOF {\n\t\terr = errConnectionClosed\n\t}\n\treturn err\n}\n\n// PendingRequests returns the current number of requests the client\n// is executing.\n//\n// This function may be used for balancing load among multiple HostClient\n// instances.\nfunc (c *HostClient) PendingRequests() int {\n\treturn int(atomic.LoadInt32(&c.pendingRequests))\n}\n\nfunc (c *HostClient) do(req *protocol.Request, resp *protocol.Response) (bool, error) {\n\tnilResp := false\n\tif resp == nil {\n\t\tnilResp = true\n\t\tresp = protocol.AcquireResponse()\n\t}\n\n\tcanIdempotentRetry, err := c.doNonNilReqResp(req, resp)\n\n\tif nilResp {\n\t\tprotocol.ReleaseResponse(resp)\n\t}\n\n\treturn canIdempotentRetry, err\n}\n\nfunc timeUntil(deadline time.Time) time.Duration {\n\ttimeout := time.Until(deadline)\n\tif timeout <= 0 {\n\t\treturn -1\n\t}\n\treturn timeout\n}\n\n// calcTimeout checks deadline and returns timeout for conn.SetXXXTimeout\n//\n// returns 0 which means no timeout\n// returns -1 if deadline exceeded\nfunc calcTimeout(deadline time.Time, timeout time.Duration) time.Duration {\n\tif timeout <= 0 {\n\t\tif deadline.IsZero() {\n\t\t\treturn 0\n\t\t}\n\t\treturn timeUntil(deadline)\n\t}\n\tif deadline.IsZero() {\n\t\treturn timeout // must > 0\n\t}\n\tif d := timeUntil(deadline); d < timeout {\n\t\treturn d\n\t}\n\treturn timeout\n}\n\nfunc (c *HostClient) getTimeouts(o *config.RequestOptions) (dtimeout, rtimeout, wtimeout time.Duration) {\n\tdtimeout = c.DialTimeout\n\tif v := o.DialTimeout(); v > 0 {\n\t\tdtimeout = v\n\t}\n\trtimeout = c.ReadTimeout\n\tif v := o.ReadTimeout(); v > 0 {\n\t\trtimeout = v\n\t}\n\twtimeout = c.WriteTimeout\n\tif v := o.WriteTimeout(); v > 0 {\n\t\twtimeout = v\n\t}\n\treturn\n}\n\nfunc (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Response) (bool, error) {\n\tif req == nil {\n\t\tpanic(\"BUG: req cannot be nil\")\n\t}\n\tif resp == nil {\n\t\tpanic(\"BUG: resp cannot be nil\")\n\t}\n\n\tatomic.StoreUint32(&c.lastUseTime, uint32(time.Now().Unix()-startTimeUnix))\n\n\t// Free up resources occupied by response before sending the request,\n\t// so the GC may reclaim these resources (e.g. response body).\n\t// backing up SkipBody in case it was set explicitly\n\tcustomSkipBody := resp.SkipBody\n\tresp.Reset()\n\tresp.SkipBody = customSkipBody\n\n\tif c.DisablePathNormalizing {\n\t\treq.URI().DisablePathNormalizing = true\n\t}\n\n\to := req.Options()\n\tdeadline := time.Time{}\n\tif v := o.RequestTimeout(); v > 0 {\n\t\tdeadline = o.StartTime().Add(v)\n\t}\n\n\tdtimeout, rtimeout, wtimeout := c.getTimeouts(o)\n\n\t// dial starts\n\n\ttimeout := calcTimeout(deadline, dtimeout)\n\tif timeout < 0 {\n\t\treturn false, errTimeout\n\t}\n\tcc, inPool, err := c.acquireConn(timeout)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tconn := cc.c\n\tresp.ParseNetAddr(conn)\n\n\tif c.IsTLS && timeout > 0 { // force handshake using dial timeout\n\t\t// NOTE: Handshake() here is optional as Write would tirigger handshake\n\t\t// but for tls handshake, it writes and reads, and we need to set deadline for that.\n\t\ttlsconn, ok := conn.(network.ConnTLSer)\n\t\tif ok {\n\t\t\t// currently netpoll doesn't support conn.SetDeadline nor tls, but crypto/tls.Conn does.\n\t\t\t// in case netpoll supports tls in the future, may need to change this to\n\t\t\t// call both conn.SetReadTimeout, and conn.SetWriteTimeout\n\t\t\terr := conn.SetDeadline(time.Now().Add(timeout))\n\t\t\tif err == nil {\n\t\t\t\terr = tlsconn.Handshake()\n\t\t\t\t// NOTE: no need conn.SetDeadline(time.Time{})?\n\t\t\t\t// we always reset before Write and Read\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tc.closeConn(cc)\n\t\t\t\treturn true, err\n\t\t\t}\n\t\t}\n\t}\n\n\tusingProxy := false\n\tif c.ProxyURI != nil && bytes.Equal(req.Scheme(), bytestr.StrHTTP) {\n\t\tusingProxy = true\n\t\tproxy.SetProxyAuthHeader(&req.Header, c.ProxyURI)\n\t}\n\n\t// write starts\n\n\ttimeout = calcTimeout(deadline, wtimeout)\n\tif timeout < 0 {\n\t\tc.closeConn(cc)\n\t\treturn false, errTimeout\n\t}\n\tif err = conn.SetWriteTimeout(timeout); err != nil {\n\t\tc.closeConn(cc)\n\t\treturn true, err\n\t}\n\n\tresetConnection := false\n\tif c.MaxConnDuration > 0 && time.Since(cc.createdTime) > c.MaxConnDuration && !req.ConnectionClose() {\n\t\treq.SetConnectionClose()\n\t\tresetConnection = true\n\t}\n\n\tuserAgentOld := req.Header.UserAgent()\n\tif len(userAgentOld) == 0 {\n\t\treq.Header.SetUserAgentBytes(c.getClientName())\n\t}\n\tzw := c.acquireWriter(conn)\n\n\tif !usingProxy {\n\t\terr = reqI.Write(req, zw)\n\t} else {\n\t\terr = reqI.ProxyWrite(req, zw)\n\t}\n\tif resetConnection {\n\t\treq.Header.ResetConnectionClose()\n\t}\n\tif err == nil {\n\t\terr = zw.Flush()\n\t}\n\tif err != nil {\n\t\tdefer c.closeConn(cc)\n\t\terrNorm, ok := conn.(network.ErrorNormalization)\n\t\tif ok {\n\t\t\terr = errNorm.ToHertzError(err)\n\t\t}\n\t\tif !errors.Is(err, errs.ErrConnectionClosed) {\n\t\t\treturn true, err\n\t\t}\n\n\t\t// introduced by https://github.com/cloudwego/hertz/pull/412\n\t\t// only for reading 4xx err\n\n\t\t// short period of time (50ms) is enough for this case\n\t\t// NOTE: can't use deadline since it likely already exceeded deadline when write\n\t\ttimeout = 50 * time.Millisecond\n\t\tif rtimeout > 0 && timeout > rtimeout {\n\t\t\ttimeout = rtimeout\n\t\t}\n\t\tif conn.SetReadTimeout(timeout) != nil {\n\t\t\treturn true, err\n\t\t}\n\t\tzr := c.acquireReader(conn)\n\t\tdefer zr.Release()\n\t\tif respI.ReadHeaderAndLimitBody(resp, zr, c.MaxResponseBodySize) == nil {\n\t\t\tif code := resp.StatusCode(); code >= 400 && code < 600 {\n\t\t\t\t// strictly for 4xx only, but 5xx is also acceptable.\n\t\t\t\t// both can be considered better response rather than write err\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\n\t\tif inPool {\n\t\t\terr = errs.ErrBadPoolConn\n\t\t}\n\t\treturn true, err\n\t}\n\n\t// read starts\n\n\ttimeout = calcTimeout(deadline, rtimeout)\n\tif timeout < 0 {\n\t\tc.closeConn(cc)\n\t\treturn false, errTimeout\n\t}\n\tif err = conn.SetReadTimeout(timeout); err != nil {\n\t\tc.closeConn(cc)\n\t\treturn true, err\n\t}\n\n\tif customSkipBody || req.Header.IsHead() || req.Header.IsConnect() {\n\t\tresp.SkipBody = true\n\t}\n\tif c.DisableHeaderNamesNormalizing {\n\t\tresp.Header.DisableNormalizing()\n\t}\n\tzr := c.acquireReader(conn)\n\n\t// errs.ErrBadPoolConn error are returned when the\n\t// 1 byte peek read fails, and we're actually anticipating a response.\n\t// Usually this is just due to the inherent keep-alive shut down race,\n\t// where the server closed the connection at the same time the client\n\t// wrote. The underlying err field is usually io.EOF or some\n\t// ECONNRESET sort of thing which varies by platform.\n\t_, err = zr.Peek(1)\n\tif err != nil {\n\t\tzr.Release() //nolint:errcheck\n\t\tc.closeConn(cc)\n\t\tif inPool && (err == io.EOF || err == syscall.ECONNRESET) {\n\t\t\treturn true, errs.ErrBadPoolConn\n\t\t}\n\t\t// if this is not a pooled connection,\n\t\t// we should not retry to avoid getting stuck in an endless retry loop.\n\t\terrNorm, ok := conn.(network.ErrorNormalization)\n\t\tif ok {\n\t\t\terr = errNorm.ToHertzError(err)\n\t\t}\n\t\treturn false, err\n\t}\n\n\t// init here for passing in ReadBodyStream's closure\n\t// and this value will be assigned after reading Response's Header\n\t//\n\t// This is to solve the circular dependency problem of Response and BodyStream\n\tshouldCloseConn := false\n\n\tif err = respI.ReadHeaders(resp, zr); err != nil {\n\t\t_ = zr.Release()\n\t\tc.closeConn(cc)\n\t\treturn true, err\n\t}\n\n\tstream := c.ResponseBodyStream\n\n\t// if it's server-sent event response,\n\t// we should set stream=true or it may block till timeout\n\tif !stream && resp.Header.ContentLength() < 0 &&\n\t\tbytes.HasPrefix(resp.Header.ContentType(), bytestr.MIMETextEventStream) {\n\t\tstream = true\n\t}\n\tif !stream {\n\t\terr = respI.ReadRespBody(resp, zr, c.MaxResponseBodySize)\n\t} else {\n\t\terr = respI.ReadRespBodyStream(resp, zr, c.MaxResponseBodySize, func(shouldClose bool) error {\n\t\t\tif shouldCloseConn || shouldClose {\n\t\t\t\tc.closeConn(cc)\n\t\t\t} else {\n\t\t\t\tc.releaseConn(cc)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\tzr.Release() //nolint:errcheck\n\n\tif err != nil {\n\t\tc.closeConn(cc)\n\t\t// Don't retry in case of ErrBodyTooLarge since we will just get the same again.\n\t\tretry := !errors.Is(err, errs.ErrBodyTooLarge)\n\t\treturn retry, err\n\t}\n\tshouldCloseConn = resetConnection || req.ConnectionClose() || resp.ConnectionClose()\n\n\tif resp.Header.StatusCode() == consts.StatusSwitchingProtocols &&\n\t\tbytes.EqualFold(resp.Header.Peek(consts.HeaderConnection), bytestr.StrUpgrade) {\n\t\t// can not reuse connection in this case, it's no longer http1 protocol.\n\t\t// set BodyStream for (*Response).Hijack\n\t\tresp.SetBodyStream(newUpgradeConn(c, cc), -1)\n\t\treturn false, nil\n\t}\n\n\t// In stream mode, we still can close/release the connection immediately if there is no content on the wire.\n\tif stream && resp.BodyStream() != protocol.NoResponseBody {\n\t\treturn false, nil\n\t}\n\n\tif shouldCloseConn {\n\t\tc.closeConn(cc)\n\t} else {\n\t\tc.releaseConn(cc)\n\t}\n\treturn false, nil\n}\n\nvar poolUpgradeConn = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn &upgradeConn{}\n\t},\n}\n\ntype upgradeConn struct {\n\tc  *HostClient\n\tcc *clientConn\n}\n\nfunc newUpgradeConn(c *HostClient, cc *clientConn) *upgradeConn {\n\tp := poolUpgradeConn.Get().(*upgradeConn)\n\tp.c = c\n\tp.cc = cc\n\truntime.SetFinalizer(p, (*upgradeConn).gc)\n\treturn p\n}\n\n// Read implements io.Reader\nfunc (p *upgradeConn) Read(b []byte) (int, error) { return p.cc.c.Read(b) }\n\n// Hijack returns underlying network.Conn. This method is called by (*Response).Hijack\nfunc (p *upgradeConn) Hijack() (network.Conn, error) { return p.cc.c, nil }\n\n// gc closes conn and reuse upgradeConn.\n//\n// It MUST be called only by go runtime to avoid concurenccy issue.\n// For the 1st GC, it closes conn, and put upgradeConn back to pool\n// For the 2nd GC, it will be recycled if it's still in pool\nfunc (p *upgradeConn) gc() error {\n\tif p.c != nil {\n\t\truntime.SetFinalizer(p, nil)\n\t\tp.c.closeConn(p.cc)\n\t\tp.c = nil\n\t\tp.cc = nil\n\t\tpoolUpgradeConn.Put(p)\n\t}\n\treturn nil\n}\n\nfunc (c *HostClient) Close() error {\n\tclose(c.closed)\n\treturn nil\n}\n\n// SetMaxConns sets up the maximum number of connections which may be established to all hosts listed in Addr.\nfunc (c *HostClient) SetMaxConns(newMaxConns int) {\n\tc.connsLock.Lock()\n\tc.MaxConns = newMaxConns\n\tc.connsLock.Unlock()\n}\n\nfunc (c *HostClient) acquireConn(dialTimeout time.Duration) (cc *clientConn, inPool bool, err error) {\n\tcreateConn := false\n\tstartCleaner := false\n\n\tvar n int\n\tc.connsLock.Lock()\n\tn = len(c.conns)\n\tif n == 0 {\n\t\tif c.MaxConns <= 0 || c.connsCount < c.MaxConns {\n\t\t\tc.connsCount++\n\t\t\tcreateConn = true\n\t\t\tif !c.connsCleanerRun {\n\t\t\t\tstartCleaner = true\n\t\t\t\tc.connsCleanerRun = true\n\t\t\t}\n\t\t}\n\t} else {\n\t\tn--\n\t\tcc = c.conns[n]\n\t\tc.conns[n] = nil\n\t\tc.conns = c.conns[:n]\n\t}\n\tc.connsLock.Unlock()\n\n\tif cc != nil {\n\t\treturn cc, true, nil\n\t}\n\tif !createConn {\n\t\tif c.MaxConnWaitTimeout <= 0 {\n\t\t\treturn nil, true, errs.ErrNoFreeConns\n\t\t}\n\n\t\ttimeout := c.MaxConnWaitTimeout\n\n\t\t// wait for a free connection\n\t\ttc := timer.AcquireTimer(timeout)\n\t\tdefer timer.ReleaseTimer(tc)\n\n\t\tw := &wantConn{\n\t\t\tready: make(chan struct{}, 1),\n\t\t}\n\t\tdefer func() {\n\t\t\tif err != nil {\n\t\t\t\tw.cancel(c, err)\n\t\t\t}\n\t\t}()\n\n\t\t// Note: In the case of setting MaxConnWaitTimeout, if the number\n\t\t// of connections in the connection pool exceeds the maximum\n\t\t// number of connections and needs to establish a connection while\n\t\t// waiting, the dialtimeout on the hostclient is used instead of\n\t\t// the dialtimeout in request options.\n\t\tc.queueForIdle(w)\n\n\t\tselect {\n\t\tcase <-w.ready:\n\t\t\treturn w.conn, true, w.err\n\t\tcase <-tc.C:\n\t\t\treturn nil, true, errs.ErrNoFreeConns\n\t\t}\n\t}\n\n\tif startCleaner {\n\t\tgo c.connsCleaner()\n\t}\n\n\tconn, err := c.dialHostHard(dialTimeout)\n\tif err != nil {\n\t\tc.decConnsCount()\n\t\treturn nil, false, err\n\t}\n\tcc = acquireClientConn(conn)\n\n\treturn cc, false, nil\n}\n\nfunc (c *HostClient) queueForIdle(w *wantConn) {\n\tc.connsLock.Lock()\n\tdefer c.connsLock.Unlock()\n\tif c.connsWait == nil {\n\t\tc.connsWait = &wantConnQueue{}\n\t}\n\tc.connsWait.clearFront()\n\tc.connsWait.pushBack(w)\n}\n\nfunc (c *HostClient) dialConnFor(w *wantConn) {\n\tconn, err := c.dialHostHard(c.DialTimeout)\n\tif err != nil {\n\t\tw.tryDeliver(nil, err)\n\t\tc.decConnsCount()\n\t\treturn\n\t}\n\n\tcc := acquireClientConn(conn)\n\tdelivered := w.tryDeliver(cc, nil)\n\tif !delivered {\n\t\t// not delivered, return idle connection\n\t\tc.releaseConn(cc)\n\t}\n}\n\n// CloseIdleConnections closes any connections which were previously\n// connected from previous requests but are now sitting idle in a\n// \"keep-alive\" state. It does not interrupt any connections currently\n// in use.\nfunc (c *HostClient) CloseIdleConnections() {\n\tc.connsLock.Lock()\n\tscratch := append([]*clientConn{}, c.conns...)\n\tfor i := range c.conns {\n\t\tc.conns[i] = nil\n\t}\n\tc.conns = c.conns[:0]\n\tc.connsLock.Unlock()\n\n\tfor _, cc := range scratch {\n\t\tc.closeConn(cc)\n\t}\n}\n\nfunc (c *HostClient) ShouldRemove() bool {\n\tc.connsLock.Lock()\n\tdefer c.connsLock.Unlock()\n\treturn c.connsCount == 0\n}\n\nfunc (c *HostClient) connsCleaner() {\n\tvar (\n\t\tscratch             []*clientConn\n\t\tmaxIdleConnDuration = c.MaxIdleConnDuration\n\t)\n\tif maxIdleConnDuration <= 0 {\n\t\tmaxIdleConnDuration = consts.DefaultMaxIdleConnDuration\n\t}\n\tfor {\n\t\tcurrentTime := time.Now()\n\n\t\t// Determine idle connections to be closed.\n\t\tc.connsLock.Lock()\n\t\tconns := c.conns\n\t\tn := len(conns)\n\t\ti := 0\n\n\t\tfor i < n && currentTime.Sub(conns[i].lastUseTime) > maxIdleConnDuration {\n\t\t\ti++\n\t\t}\n\t\tsleepFor := maxIdleConnDuration\n\t\tif i < n {\n\t\t\t// + 1 so we actually sleep past the expiration time and not up to it.\n\t\t\t// Otherwise the > check above would still fail.\n\t\t\tsleepFor = maxIdleConnDuration - currentTime.Sub(conns[i].lastUseTime) + 1\n\t\t}\n\t\tscratch = append(scratch[:0], conns[:i]...)\n\t\tif i > 0 {\n\t\t\tm := copy(conns, conns[i:])\n\t\t\tfor i = m; i < n; i++ {\n\t\t\t\tconns[i] = nil\n\t\t\t}\n\t\t\tc.conns = conns[:m]\n\t\t}\n\t\tc.connsLock.Unlock()\n\n\t\t// Close idle connections.\n\t\tfor i, cc := range scratch {\n\t\t\tc.closeConn(cc)\n\t\t\tscratch[i] = nil\n\t\t}\n\n\t\t// Determine whether to stop the connsCleaner.\n\t\tc.connsLock.Lock()\n\t\tmustStop := c.connsCount == 0\n\t\tif mustStop {\n\t\t\tc.connsCleanerRun = false\n\t\t}\n\t\tc.connsLock.Unlock()\n\t\tif mustStop {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(sleepFor)\n\t}\n}\n\nfunc (c *HostClient) closeConn(cc *clientConn) {\n\tc.decConnsCount()\n\tcc.c.Close()\n\treleaseClientConn(cc)\n}\n\nfunc (c *HostClient) decConnsCount() {\n\tif c.MaxConnWaitTimeout <= 0 {\n\t\tc.connsLock.Lock()\n\t\tc.connsCount--\n\t\tc.connsLock.Unlock()\n\t\treturn\n\t}\n\n\tc.connsLock.Lock()\n\tdefer c.connsLock.Unlock()\n\tdialed := false\n\tif q := c.connsWait; q != nil && q.len() > 0 {\n\t\tfor q.len() > 0 {\n\t\t\tw := q.popFront()\n\t\t\tif w.waiting() {\n\t\t\t\tgo c.dialConnFor(w)\n\t\t\t\tdialed = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif !dialed {\n\t\tc.connsCount--\n\t}\n}\n\nfunc acquireClientConn(conn network.Conn) *clientConn {\n\tv := clientConnPool.Get()\n\tif v == nil {\n\t\tv = &clientConn{}\n\t}\n\tcc := v.(*clientConn)\n\tcc.c = conn\n\tcc.createdTime = time.Now()\n\treturn cc\n}\n\nfunc releaseClientConn(cc *clientConn) {\n\t// Reset all fields.\n\t*cc = clientConn{}\n\tclientConnPool.Put(cc)\n}\n\nvar clientConnPool sync.Pool\n\nfunc (c *HostClient) releaseConn(cc *clientConn) {\n\tif cc.c.Len() > 0 {\n\t\t// unexpected buffered data due to malformed response\n\t\tc.closeConn(cc)\n\t\treturn\n\t}\n\tcc.lastUseTime = time.Now()\n\tif c.MaxConnWaitTimeout <= 0 {\n\t\tc.connsLock.Lock()\n\t\tc.conns = append(c.conns, cc)\n\t\tc.connsLock.Unlock()\n\t\treturn\n\t}\n\n\t// try to deliver an idle connection to a *wantConn\n\tc.connsLock.Lock()\n\tdefer c.connsLock.Unlock()\n\tdelivered := false\n\tif q := c.connsWait; q != nil && q.len() > 0 {\n\t\tfor q.len() > 0 {\n\t\t\tw := q.popFront()\n\t\t\tif w.waiting() {\n\t\t\t\tdelivered = w.tryDeliver(cc, nil)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif !delivered {\n\t\tc.conns = append(c.conns, cc)\n\t}\n}\n\nfunc (c *HostClient) acquireWriter(conn network.Conn) network.Writer {\n\treturn conn\n}\n\nfunc (c *HostClient) acquireReader(conn network.Conn) network.Reader {\n\treturn conn\n}\n\nfunc newClientTLSConfig(c *tls.Config, addr string) *tls.Config {\n\tif c == nil {\n\t\tc = &tls.Config{}\n\t} else {\n\t\tc = c.Clone()\n\t}\n\n\tif c.ClientSessionCache == nil {\n\t\tc.ClientSessionCache = tls.NewLRUClientSessionCache(0)\n\t}\n\n\tif len(c.ServerName) == 0 {\n\t\tserverName := tlsServerName(addr)\n\t\tif serverName == \"*\" {\n\t\t\tc.InsecureSkipVerify = true\n\t\t} else {\n\t\t\tc.ServerName = serverName\n\t\t}\n\t}\n\treturn c\n}\n\nfunc tlsServerName(addr string) string {\n\tif !strings.Contains(addr, \":\") {\n\t\treturn addr\n\t}\n\thost, _, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn \"*\"\n\t}\n\treturn host\n}\n\nfunc (c *HostClient) nextAddr() string {\n\tc.addrsLock.Lock()\n\tif c.addrs == nil {\n\t\tc.addrs = strings.Split(c.Addr, \",\")\n\t}\n\taddr := c.addrs[0]\n\tif len(c.addrs) > 1 {\n\t\taddr = c.addrs[c.addrIdx%uint32(len(c.addrs))]\n\t\tc.addrIdx++\n\t}\n\tc.addrsLock.Unlock()\n\treturn addr\n}\n\nfunc (c *HostClient) dialHostHard(dialTimeout time.Duration) (conn network.Conn, err error) {\n\t// attempt to dial all the available hosts before giving up.\n\n\tc.addrsLock.Lock()\n\tn := len(c.addrs)\n\tc.addrsLock.Unlock()\n\n\tif n == 0 {\n\t\t// It looks like c.addrs isn't initialized yet.\n\t\tn = 1\n\t}\n\n\tdeadline := time.Now().Add(dialTimeout)\n\tfor n > 0 {\n\t\taddr := c.nextAddr()\n\t\ttlsConfig := c.cachedTLSConfig(addr)\n\t\tconn, err = dialAddr(addr, c.Dialer, c.DialDualStack, tlsConfig, dialTimeout, c.ProxyURI, c.IsTLS)\n\t\tif err == nil {\n\t\t\treturn conn, nil\n\t\t}\n\t\tif time.Since(deadline) >= 0 {\n\t\t\tbreak\n\t\t}\n\t\tn--\n\t}\n\treturn nil, err\n}\n\nfunc (c *HostClient) cachedTLSConfig(addr string) *tls.Config {\n\tvar cfgAddr string\n\tif c.ProxyURI != nil && bytes.Equal(c.ProxyURI.Scheme(), bytestr.StrHTTPS) {\n\t\tcfgAddr = bytesconv.B2s(c.ProxyURI.Host())\n\t}\n\n\tif c.IsTLS && cfgAddr == \"\" {\n\t\tcfgAddr = addr\n\t}\n\n\tif cfgAddr == \"\" {\n\t\treturn nil\n\t}\n\n\tc.tlsConfigMapLock.Lock()\n\tif c.tlsConfigMap == nil {\n\t\tc.tlsConfigMap = make(map[string]*tls.Config)\n\t}\n\tcfg := c.tlsConfigMap[cfgAddr]\n\tif cfg == nil {\n\t\tcfg = newClientTLSConfig(c.TLSConfig, cfgAddr)\n\t\tc.tlsConfigMap[cfgAddr] = cfg\n\t}\n\tc.tlsConfigMapLock.Unlock()\n\n\treturn cfg\n}\n\nfunc dialAddr(addr string, dial network.Dialer, dialDualStack bool, tlsConfig *tls.Config, timeout time.Duration, proxyURI *protocol.URI, isTLS bool) (network.Conn, error) {\n\tvar conn network.Conn\n\tvar err error\n\tif dial == nil {\n\t\thlog.SystemLogger().Warn(\"HostClient: no dialer specified, trying to use default dialer\")\n\t\tdial = dialer.DefaultDialer()\n\t}\n\tdialFunc := dial.DialConnection\n\n\t// addr has already been added port, no need to do it here\n\tif proxyURI != nil {\n\t\t// use tcp connection first, proxy will AddTLS to it\n\t\tconn, err = dialFunc(\"tcp\", string(proxyURI.Host()), timeout, nil)\n\t} else {\n\t\tconn, err = dialFunc(\"tcp\", addr, timeout, tlsConfig)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif conn == nil {\n\t\tpanic(\"BUG: dial.DialConnection returned (nil, nil)\")\n\t}\n\n\tif proxyURI != nil {\n\t\tconn, err = proxy.SetupProxy(conn, addr, proxyURI, tlsConfig, isTLS, dial)\n\t}\n\n\t// conn must be nil when got error, so doesn't need to close it\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn conn, nil\n}\n\nfunc (c *HostClient) getClientName() []byte {\n\tv := c.clientName.Load()\n\tvar clientName []byte\n\tif v == nil {\n\t\tclientName = []byte(c.Name)\n\t\tif len(clientName) == 0 && !c.NoDefaultUserAgentHeader {\n\t\t\tclientName = bytestr.DefaultUserAgent\n\t\t}\n\t\tc.clientName.Store(clientName)\n\t} else {\n\t\tclientName = v.([]byte)\n\t}\n\treturn clientName\n}\n\n// waiting reports whether w is still waiting for an answer (connection or error).\nfunc (w *wantConn) waiting() bool {\n\tselect {\n\tcase <-w.ready:\n\t\treturn false\n\tdefault:\n\t\treturn true\n\t}\n}\n\n// tryDeliver attempts to deliver conn, err to w and reports whether it succeeded.\nfunc (w *wantConn) tryDeliver(conn *clientConn, err error) bool {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\n\tif w.conn != nil || w.err != nil {\n\t\treturn false\n\t}\n\tw.conn = conn\n\tw.err = err\n\tif w.conn == nil && w.err == nil {\n\t\tpanic(\"hertz: internal error: misuse of tryDeliver\")\n\t}\n\tclose(w.ready)\n\treturn true\n}\n\n// cancel marks w as no longer wanting a result (for example, due to cancellation).\n// If a connection has been delivered already, cancel returns it with c.releaseConn.\nfunc (w *wantConn) cancel(c *HostClient, err error) {\n\tw.mu.Lock()\n\tif w.conn == nil && w.err == nil {\n\t\tclose(w.ready) // catch misbehavior in future delivery\n\t}\n\n\tconn := w.conn\n\tw.conn = nil\n\tw.err = err\n\tw.mu.Unlock()\n\n\tif conn != nil {\n\t\tc.releaseConn(conn)\n\t}\n}\n\n// len returns the number of items in the queue.\nfunc (q *wantConnQueue) len() int {\n\treturn len(q.head) - q.headPos + len(q.tail)\n}\n\n// pushBack adds w to the back of the queue.\nfunc (q *wantConnQueue) pushBack(w *wantConn) {\n\tq.tail = append(q.tail, w)\n}\n\n// popFront removes and returns the wantConn at the front of the queue.\nfunc (q *wantConnQueue) popFront() *wantConn {\n\tif q.headPos >= len(q.head) {\n\t\tif len(q.tail) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\t// Pick up tail as new head, clear tail.\n\t\tq.head, q.headPos, q.tail = q.tail, 0, q.head[:0]\n\t}\n\n\tw := q.head[q.headPos]\n\tq.head[q.headPos] = nil\n\tq.headPos++\n\treturn w\n}\n\n// peekFront returns the wantConn at the front of the queue without removing it.\nfunc (q *wantConnQueue) peekFront() *wantConn {\n\tif q.headPos < len(q.head) {\n\t\treturn q.head[q.headPos]\n\t}\n\tif len(q.tail) > 0 {\n\t\treturn q.tail[0]\n\t}\n\treturn nil\n}\n\n// cleanFront pops any wantConns that are no longer waiting from the head of the\n// queue, reporting whether any were popped.\nfunc (q *wantConnQueue) clearFront() (cleaned bool) {\n\tfor {\n\t\tw := q.peekFront()\n\t\tif w == nil || w.waiting() {\n\t\t\treturn cleaned\n\t\t}\n\t\tq.popFront()\n\t\tcleaned = true\n\t}\n}\n\nfunc NewHostClient(c *ClientOptions) client.HostClient {\n\thc := &HostClient{\n\t\tClientOptions: c,\n\t\tclosed:        make(chan struct{}),\n\t}\n\n\treturn hc\n}\n\ntype ClientOptions struct {\n\t// Client name. Used in User-Agent request header.\n\tName string\n\n\t// NoDefaultUserAgentHeader when set to true, causes the default\n\t// User-Agent header to be excluded from the Request.\n\tNoDefaultUserAgentHeader bool\n\n\t// Callback for establishing new connection to the host.\n\t//\n\t// Default Dialer is used if not set.\n\tDialer network.Dialer\n\n\t// Timeout for establishing new connections to hosts.\n\t//\n\t// Default DialTimeout is used if not set.\n\tDialTimeout time.Duration\n\n\t// Attempt to connect to both ipv4 and ipv6 host addresses\n\t// if set to true.\n\t//\n\t// This option is used only if default TCP dialer is used,\n\t// i.e. if Dialer is blank.\n\t//\n\t// By default client connects only to ipv4 addresses,\n\t// since unfortunately ipv6 remains broken in many networks worldwide :)\n\tDialDualStack bool\n\n\t// Whether to use TLS (aka SSL or HTTPS) for host connections.\n\t// Optional TLS config.\n\tTLSConfig *tls.Config\n\n\t// Maximum number of connections which may be established to all hosts\n\t// listed in Addr.\n\t//\n\t// You can change this value while the HostClient is being used\n\t// using HostClient.SetMaxConns(value)\n\t//\n\t// no limit if <= 0.\n\tMaxConns int\n\n\t// Keep-alive connections are closed after this duration.\n\t//\n\t// By default connection duration is unlimited.\n\tMaxConnDuration time.Duration\n\n\t// Idle keep-alive connections are closed after this duration.\n\t//\n\t// By default idle connections are closed\n\t// after DefaultMaxIdleConnDuration.\n\tMaxIdleConnDuration time.Duration\n\n\t// Maximum duration for full response reading (including body).\n\t//\n\t// By default response read timeout is unlimited.\n\tReadTimeout time.Duration\n\n\t// Maximum duration for full request writing (including body).\n\t//\n\t// By default request write timeout is unlimited.\n\tWriteTimeout time.Duration\n\n\t// Maximum response body size.\n\t//\n\t// The client returns errBodyTooLarge if this limit is greater than 0\n\t// and response body is greater than the limit.\n\t//\n\t// By default response body size is unlimited.\n\tMaxResponseBodySize int\n\n\t// Header names are passed as-is without normalization\n\t// if this option is set.\n\t//\n\t// Disabled header names' normalization may be useful only for proxying\n\t// responses to other clients expecting case-sensitive header names.\n\t//\n\t// By default request and response header names are normalized, i.e.\n\t// The first letter and the first letters following dashes\n\t// are uppercased, while all the other letters are lowercased.\n\t// Examples:\n\t//\n\t//     * HOST -> Host\n\t//     * content-type -> Content-Type\n\t//     * cONTENT-lenGTH -> Content-Length\n\tDisableHeaderNamesNormalizing bool\n\n\t// Path values are sent as-is without normalization\n\t//\n\t// Disabled path normalization may be useful for proxying incoming requests\n\t// to servers that are expecting paths to be forwarded as-is.\n\t//\n\t// By default path values are normalized, i.e.\n\t// extra slashes are removed, special characters are encoded.\n\tDisablePathNormalizing bool\n\n\t// Maximum duration for waiting for a free connection.\n\t//\n\t// By default will not wait, return ErrNoFreeConns immediately\n\tMaxConnWaitTimeout time.Duration\n\n\t// ResponseBodyStream enables response body streaming\n\tResponseBodyStream bool\n\n\t// All configurations related to retry\n\tRetryConfig *retry.Config\n\n\tRetryIfFunc client.RetryIfFunc\n\n\t// Observe hostclient state\n\tStateObserve config.HostClientStateFunc\n\n\t// StateObserve execution interval\n\tObservationInterval time.Duration\n}\n"
  },
  {
    "path": "pkg/protocol/http1/client_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage http1\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/netpoll\"\n\n\t\"github.com/cloudwego/hertz/pkg/app/client/retry\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/dialer\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/client\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n)\n\nvar errDialTimeout = errs.New(errs.ErrTimeout, errs.ErrorTypePublic, \"dial timeout\")\n\nfunc TestHostClientMaxConnWaitTimeoutWithEarlierDeadline(t *testing.T) {\n\tvar (\n\t\temptyBodyCount uint8\n\t\twg             sync.WaitGroup\n\t\t// make deadline reach earlier than conns wait timeout\n\t\ttimeout = 10 * time.Millisecond\n\t)\n\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn mock.SlowReadDialer(addr)\n\t\t\t}),\n\t\t\tMaxConns:           1,\n\t\t\tMaxConnWaitTimeout: 50 * time.Millisecond,\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\tvar errTimeoutCount uint32\n\tfor i := 0; i < 5; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\treq := protocol.AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\treq.Header.SetMethod(consts.MethodPost)\n\t\t\treq.SetBodyString(\"bar\")\n\t\t\tresp := protocol.AcquireResponse()\n\n\t\t\tif err := c.DoDeadline(context.Background(), req, resp, time.Now().Add(timeout)); err != nil {\n\t\t\t\tif !errors.Is(err, errs.ErrTimeout) {\n\t\t\t\t\tt.Errorf(\"unexpected error: %s. Expecting %s\", err, errs.ErrTimeout)\n\t\t\t\t}\n\t\t\t\tatomic.AddUint32(&errTimeoutCount, 1)\n\t\t\t} else {\n\t\t\t\tif resp.StatusCode() != consts.StatusOK {\n\t\t\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), consts.StatusOK)\n\t\t\t\t}\n\n\t\t\t\tbody := resp.Body()\n\t\t\t\tif string(body) != \"foo\" {\n\t\t\t\t\tt.Errorf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tc.connsLock.Lock()\n\tfor {\n\t\tw := c.connsWait.popFront()\n\t\tif w == nil {\n\t\t\tbreak\n\t\t}\n\t\tw.mu.Lock()\n\t\tif w.err != nil && !errors.Is(w.err, errs.ErrNoFreeConns) {\n\t\t\tt.Errorf(\"unexpected error: %s. Expecting %s\", w.err, errs.ErrNoFreeConns)\n\t\t}\n\t\tw.mu.Unlock()\n\t}\n\tc.connsLock.Unlock()\n\tif errTimeoutCount == 0 {\n\t\tt.Errorf(\"unexpected errTimeoutCount: %d. Expecting > 0\", errTimeoutCount)\n\t}\n\n\tif emptyBodyCount > 0 {\n\t\tt.Fatalf(\"at least one request body was empty\")\n\t}\n}\n\nfunc TestReadHeaderErr(t *testing.T) {\n\tln, _ := net.Listen(\"tcp\", \"localhost:0\")\n\tdefer ln.Close()\n\tsvr := http.Server{}\n\tsvr.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\thj := w.(http.Hijacker)\n\t\tconn, rw, err := hj.Hijack()\n\t\tassert.Nil(t, err)\n\t\tdefer conn.Close()\n\t\trw.Write([]byte(\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Ty\"))\n\t\trw.Flush()\n\t})\n\tgo svr.Serve(ln)\n\n\treq := protocol.AcquireRequest()\n\tdefer protocol.ReleaseRequest(req)\n\treq.SetRequestURI(\"http://\" + ln.Addr().String())\n\n\tresp := protocol.AcquireResponse()\n\tdefer protocol.ReleaseResponse(resp)\n\tc := &HostClient{\n\t\tAddr: ln.Addr().String(),\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialer.DefaultDialer(),\n\t\t},\n\t}\n\terr := c.Do(context.Background(), req, resp)\n\tassert.NotNil(t, err)\n}\n\nfunc TestResponseReadBodyStream(t *testing.T) {\n\t// small body\n\tgenBody := \"abcdef4343\"\n\ts := \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 5\\r\\n\\r\\n\"\n\ttestContinueReadResponseBodyStream(t, s, genBody, 10, 5, 0, 5)\n\ttestContinueReadResponseBodyStream(t, s, genBody, 1, 5, 0, 0)\n\n\t// big body (> 8193)\n\ts1 := \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 9216\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\"\n\tgenBody = strings.Repeat(\"1\", 9*1024)\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 10*1024, 5*1024, 4*1024, 0)\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 10*1024, 1*1024, 8*1024, 0)\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 10*1024, 9*1024, 0*1024, 0)\n\n\t// normal stream\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 1*1024, 5*1024, 4*1024, 0)\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 1*1024, 1*1024, 8*1024, 0)\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 1*1024, 9*1024, 0*1024, 0)\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 5, 5*1024, 4*1024, 0)\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 5, 1*1024, 8*1024, 0)\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 5, 9*1024, 0, 0)\n\n\t// critical point\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 8*1024+1, 5*1024, 4*1024, 0)\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 8*1024+1, 1*1024, 8*1024, 0)\n\ttestContinueReadResponseBodyStream(t, s1, genBody, 8*1024+1, 9*1024, 0*1024, 0)\n\n\t// chunked body\n\ts2 := \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n3\\r\\nabc\\r\\n5\\r\\n12345\\r\\n0\\r\\n\\r\\ntrail\"\n\ttestContinueReadResponseBodyStream(t, s2, \"\", 10*1024, 3, 5, 5)\n\ts3 := \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n3\\r\\nabc\\r\\n5\\r\\n12345\\r\\n0\\r\\n\\r\\n\"\n\ttestContinueReadResponseBodyStream(t, s3, \"\", 10*1024, 3, 5, 0)\n}\n\nfunc testContinueReadResponseBodyStream(t *testing.T, header, body string, maxBodySize, firstRead, leftBytes, bytesLeftInReader int) {\n\tmr := netpoll.NewReader(bytes.NewBufferString(header + body))\n\tvar r protocol.Response\n\tif err := resp.ReadHeaderBodyStream(&r, mr, maxBodySize, nil); err != nil {\n\t\tt.Fatalf(\"error when reading request body stream: %s\", err)\n\t}\n\tfRead := firstRead\n\tstreamRead := make([]byte, fRead)\n\tsR, _ := r.BodyStream().Read(streamRead)\n\n\tif sR != firstRead {\n\t\tt.Fatalf(\"should read %d from stream body, but got %d\", firstRead, sR)\n\t}\n\n\tleftB, _ := ioutil.ReadAll(r.BodyStream())\n\tif len(leftB) != leftBytes {\n\t\tt.Fatalf(\"should left %d bytes from stream body, but left %d\", leftBytes, len(leftB))\n\t}\n\tif r.Header.ContentLength() > 0 {\n\t\tgotBody := append(streamRead, leftB...)\n\t\tif !bytes.Equal([]byte(body[:r.Header.ContentLength()]), gotBody) {\n\t\t\tt.Fatalf(\"body read from stream is not equal to the origin. Got: %s\", gotBody)\n\t\t}\n\t}\n\n\tleft, _ := mr.Next(mr.Len())\n\n\tif len(left) != bytesLeftInReader {\n\t\tfmt.Printf(\"##########header:%s,body:%s,%d:max,first:%d,left:%d,leftin:%d\\n\", header, body, maxBodySize, firstRead, leftBytes, bytesLeftInReader)\n\t\tfmt.Printf(\"##########left: %s\\n\", left)\n\t\tt.Fatalf(\"should left %d bytes in original reader. got %q\", bytesLeftInReader, len(left))\n\t}\n}\n\ntype dialerFunc func(network, addr string, timeout time.Duration) (network.Conn, error)\n\nfunc (f dialerFunc) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {\n\treturn f(network, address, timeout)\n}\n\nfunc (_ dialerFunc) DialTimeout(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn net.Conn, err error) {\n\treturn nil, nil\n}\n\nfunc (_ dialerFunc) AddTLS(conn network.Conn, tlsConfig *tls.Config) (network.Conn, error) {\n\treturn nil, nil\n}\n\ntype slowDialer struct {\n\tnetwork.Dialer\n}\n\nfunc (s *slowDialer) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {\n\ttime.Sleep(timeout)\n\treturn nil, errDialTimeout\n}\n\nfunc TestTimeoutPriority(t *testing.T) {\n\trtimeoutDialer := dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\treturn mock.SlowReadDialer(addr)\n\t})\n\twtimeoutDialer := dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\treturn mock.SlowWriteDialer(addr)\n\t})\n\n\tnoopRequestOpt := config.RequestOption{F: func(o *config.RequestOptions) {}}\n\n\ttests := []struct {\n\t\tname        string\n\t\tdialer      network.Dialer\n\t\tclientOpts  *ClientOptions\n\t\treqOpt      config.RequestOption\n\t\texpectDelay time.Duration\n\t\texpectedErr error\n\t}{\n\t\t// ReadTimeout cases\n\t\t{\n\t\t\t\"ReadTimeout_cli_60ms_req_100ms\",\n\t\t\trtimeoutDialer,\n\t\t\t&ClientOptions{ReadTimeout: 60 * time.Millisecond},\n\t\t\tconfig.WithReadTimeout(100 * time.Millisecond),\n\t\t\t100 * time.Millisecond,\n\t\t\tmock.ErrReadTimeout,\n\t\t},\n\t\t{\n\t\t\t\"ReadTimeout_cli_100ms_req_60ms\",\n\t\t\trtimeoutDialer,\n\t\t\t&ClientOptions{ReadTimeout: 100 * time.Millisecond},\n\t\t\tconfig.WithReadTimeout(60 * time.Millisecond),\n\t\t\t60 * time.Millisecond,\n\t\t\tmock.ErrReadTimeout,\n\t\t},\n\t\t{\n\t\t\t\"ReadTimeout_cli_unset_req_60ms\",\n\t\t\trtimeoutDialer,\n\t\t\t&ClientOptions{},\n\t\t\tconfig.WithReadTimeout(60 * time.Millisecond),\n\t\t\t60 * time.Millisecond,\n\t\t\tmock.ErrReadTimeout,\n\t\t},\n\t\t{\n\t\t\t\"ReadTimeout_cli_60ms_req_unset\",\n\t\t\trtimeoutDialer,\n\t\t\t&ClientOptions{ReadTimeout: 60 * time.Millisecond},\n\t\t\tnoopRequestOpt,\n\t\t\t60 * time.Millisecond,\n\t\t\tmock.ErrReadTimeout,\n\t\t},\n\t\t// WriteTimeout cases\n\t\t{\n\t\t\t\"WriteTimeout_cli_100ms_req_150ms\",\n\t\t\twtimeoutDialer,\n\t\t\t&ClientOptions{WriteTimeout: 100 * time.Millisecond},\n\t\t\tconfig.WithWriteTimeout(150 * time.Millisecond),\n\t\t\t150 * time.Millisecond,\n\t\t\tmock.ErrWriteTimeout,\n\t\t},\n\t\t{\n\t\t\t\"WriteTimeout_cli_150ms_req_100ms\",\n\t\t\twtimeoutDialer,\n\t\t\t&ClientOptions{WriteTimeout: 150 * time.Millisecond},\n\t\t\tconfig.WithWriteTimeout(100 * time.Millisecond),\n\t\t\t100 * time.Millisecond,\n\t\t\tmock.ErrWriteTimeout,\n\t\t},\n\t\t{\n\t\t\t\"WriteTimeout_cli_unset_req_120ms\",\n\t\t\twtimeoutDialer,\n\t\t\t&ClientOptions{},\n\t\t\tconfig.WithWriteTimeout(120 * time.Millisecond),\n\t\t\t120 * time.Millisecond,\n\t\t\tmock.ErrWriteTimeout,\n\t\t},\n\t\t{\n\t\t\t\"WriteTimeout_cli_120ms_req_unset\",\n\t\t\twtimeoutDialer,\n\t\t\t&ClientOptions{WriteTimeout: 120 * time.Millisecond},\n\t\t\tnoopRequestOpt,\n\t\t\t120 * time.Millisecond,\n\t\t\tmock.ErrWriteTimeout,\n\t\t},\n\t\t// DialTimeout cases\n\t\t{\n\t\t\t\"DialTimeout_cli_60ms_req_100ms\",\n\t\t\t&slowDialer{},\n\t\t\t&ClientOptions{DialTimeout: 60 * time.Millisecond},\n\t\t\tconfig.WithDialTimeout(100 * time.Millisecond),\n\t\t\t100 * time.Millisecond,\n\t\t\terrDialTimeout,\n\t\t},\n\t\t{\n\t\t\t\"DialTimeout_cli_100ms_req_60ms\",\n\t\t\t&slowDialer{},\n\t\t\t&ClientOptions{DialTimeout: 100 * time.Millisecond},\n\t\t\tconfig.WithDialTimeout(60 * time.Millisecond),\n\t\t\t60 * time.Millisecond,\n\t\t\terrDialTimeout,\n\t\t},\n\t\t{\n\t\t\t\"DialTimeout_cli_unset_req_60ms\",\n\t\t\t&slowDialer{},\n\t\t\t&ClientOptions{},\n\t\t\tconfig.WithDialTimeout(60 * time.Millisecond),\n\t\t\t60 * time.Millisecond,\n\t\t\terrDialTimeout,\n\t\t},\n\t\t{\n\t\t\t\"DialTimeout_cli_60ms_req_unset\",\n\t\t\t&slowDialer{},\n\t\t\t&ClientOptions{DialTimeout: 60 * time.Millisecond},\n\t\t\tnoopRequestOpt,\n\t\t\t60 * time.Millisecond,\n\t\t\terrDialTimeout,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.clientOpts.Dialer = tt.dialer\n\t\t\tc := &HostClient{ClientOptions: tt.clientOpts, Addr: \"foobar\"}\n\n\t\t\treq := protocol.AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\treq.SetOptions(tt.reqOpt)\n\n\t\t\tstart := time.Now()\n\t\t\terr := c.Do(context.Background(), req, protocol.AcquireResponse())\n\t\t\tduration := time.Since(start)\n\n\t\t\tassert.DeepEqual(t, tt.expectedErr, err)\n\n\t\t\t// Check if duration is within expected delay ±30ms\n\t\t\ttolerance := 30 * time.Millisecond\n\t\t\tif !(duration >= tt.expectDelay-tolerance && duration <= tt.expectDelay+tolerance) {\n\t\t\t\tt.Errorf(\"Duration %v not within expected %v ±%v\", duration, tt.expectDelay, tolerance)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDoNonNilReqResp(t *testing.T) {\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn mock.NewConn(\"HTTP/1.1 400 OK\\nContent-Length: 6\\n\\n123456\"), nil\n\t\t\t}),\n\t\t},\n\t}\n\treq := protocol.AcquireRequest()\n\tresp := protocol.AcquireResponse()\n\treq.SetHost(\"foobar\")\n\tretry, err := c.doNonNilReqResp(req, resp)\n\tassert.False(t, retry)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, resp.StatusCode(), 400)\n\tassert.DeepEqual(t, resp.Body(), []byte(\"123456\"))\n}\n\nfunc TestDoNonNilReqResp_WriteErr(t *testing.T) {\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{},\n\t}\n\treq := protocol.AcquireRequest()\n\tresp := protocol.AcquireResponse()\n\treq.SetHost(\"foobar\")\n\treq.SetConnectionClose() // won't reuse the conn\n\n\t// 200 with write err, will return write err\n\tc.ClientOptions.Dialer = dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\treturn &writeErrConn{mock.NewConn(\"HTTP/1.1 200 OK\\nContent-Length: 6\\n\\n123456\")}, nil\n\t})\n\tretry, err := c.doNonNilReqResp(req, resp)\n\tassert.True(t, retry)\n\tassert.NotNil(t, err)\n\n\tc = &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn &writeErrConn{mock.NewConn(\"HTTP/1.1 400 OK\\nContent-Length: 6\\n\\n123456\")}, nil\n\t\t\t}),\n\t\t},\n\t}\n\n\t// 400 with write err, will NOT return write err\n\tc.ClientOptions.Dialer = dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\treturn &writeErrConn{mock.NewConn(\"HTTP/1.1 400 OK\\nContent-Length: 6\\n\\n123456\")}, nil\n\t})\n\tretry, err = c.doNonNilReqResp(req, resp)\n\tassert.False(t, retry)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, resp.StatusCode(), 400)\n\tassert.DeepEqual(t, resp.Body(), []byte(\"123456\"))\n}\n\nfunc TestDoNonNilReqResp_TLS(t *testing.T) {\n\tconst (\n\t\tdialTimeout = 123 * time.Millisecond\n\t\tdev         = 10 * time.Millisecond\n\t)\n\tconn := mock.NewConn(\"HTTP/1.1 200 OK\\nContent-Length: 5\\n\\n54321\")\n\ttlsconn := mock.NewTLSConn(conn)\n\tc := &HostClient{\n\t\tIsTLS: true,\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialTimeout: dialTimeout,\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn tlsconn, nil\n\t\t\t}),\n\t\t},\n\t}\n\treq := protocol.AcquireRequest()\n\tresp := protocol.AcquireResponse()\n\treq.SetHost(\"foobar\")\n\n\t// HandshakeErr != nil\n\ttlsconn.HandshakeErr = errors.New(\"testerr\")\n\tretry, err := c.doNonNilReqResp(req, resp)\n\tassert.True(t, retry)\n\tassert.True(t, err == tlsconn.HandshakeErr)\n\tif diff := conn.GetReadTimeout() - dialTimeout; diff < -dev || diff > dev {\n\t\tt.Fatal(\"unexpected timeout. got\", conn.GetReadTimeout(), \"expect\", dialTimeout)\n\t}\n\tassert.True(t, conn.GetReadTimeout() == conn.GetWriteTimeout())\n\n\t// HandshakeErr == nil\n\ttlsconn.HandshakeErr = nil\n\tretry, err = c.doNonNilReqResp(req, resp)\n\tassert.False(t, retry)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, resp.StatusCode(), 200)\n\tassert.DeepEqual(t, resp.Body(), []byte(\"54321\"))\n}\n\nfunc TestDoNonNilReqResp_Err(t *testing.T) {\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn peekErrConn{writeErrConn{mock.NewConn(\"\")}}, nil\n\t\t\t}),\n\t\t},\n\t}\n\treq := protocol.AcquireRequest()\n\tresp := protocol.AcquireResponse()\n\treq.SetHost(\"foobar\")\n\tretry, err := c.doNonNilReqResp(req, resp)\n\tassert.True(t, retry)\n\tassert.NotNil(t, err)\n\tassert.Assert(t, err == errs.ErrConnectionClosed, err) // returned by writeErrConn\n}\n\nfunc doGET(t *testing.T, addr, path string) *protocol.Response {\n\treq := protocol.AcquireRequest()\n\tdefer protocol.ReleaseRequest(req)\n\treq.SetRequestURI(\"http://\" + addr + path)\n\n\tresp := protocol.AcquireResponse()\n\tc := &HostClient{\n\t\tAddr: addr,\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialer.DefaultDialer(),\n\t\t},\n\t}\n\terr := c.Do(context.Background(), req, resp)\n\tassert.Nil(t, err)\n\treturn resp\n}\n\nfunc TestStreamResponse_EventStream(t *testing.T) {\n\tln, _ := net.Listen(\"tcp\", \"localhost:0\")\n\tdefer ln.Close()\n\tsvr := http.Server{}\n\tsvr.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\t\tf := w.(http.Flusher)\n\t\tfor i := 0; i < 5; i++ {\n\t\t\t_, err := w.Write([]byte(fmt.Sprintf(\"data:%d\", i)))\n\t\t\tassert.Nil(t, err)\n\t\t\tf.Flush() // Transfer-Encoding chunked\n\t\t\ttime.Sleep(20 * time.Millisecond)\n\t\t}\n\t})\n\tgo svr.Serve(ln)\n\n\tresp := doGET(t, ln.Addr().String(), \"/\")\n\tdefer protocol.ReleaseResponse(resp)\n\tassert.Assert(t, resp.IsBodyStream())\n\tr := resp.BodyStream()\n\tb := make([]byte, 10)\n\tfor i := 0; i < 5; i++ {\n\t\tn, err := r.Read(b)\n\t\tassert.Nil(t, err)\n\t\tassert.Assert(t, string(b[:n]) == fmt.Sprintf(\"data:%d\", i))\n\t}\n}\n\nfunc TestStreamResponse_ConnUpgrade(t *testing.T) {\n\tln, _ := net.Listen(\"tcp\", \"localhost:0\")\n\tdefer ln.Close()\n\tsvr := http.Server{}\n\tsvr.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\thj, ok := w.(http.Hijacker)\n\t\tif !ok {\n\t\t\thttp.Error(w, \"webserver doesn't support hijacking\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\tconn, rw, err := hj.Hijack()\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\tdefer conn.Close()\n\t\t_, err = rw.WriteString(\"HTTP/1.1 101 Switching Protocols\\nConnection: Upgrade\\n\\n\")\n\t\tassert.Nil(t, err)\n\t\tassert.Nil(t, rw.Flush())\n\t\tb := make([]byte, 100)\n\t\tfor { // echo with \"echo:\" prefix\n\t\t\tn, err := rw.Read(b)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_, err = rw.Write([]byte(\"echo:\" + string(b[:n])))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_ = rw.Flush()\n\t\t}\n\t})\n\tgo svr.Serve(ln)\n\n\tresp := doGET(t, ln.Addr().String(), \"/\")\n\tdefer protocol.ReleaseResponse(resp)\n\tassert.DeepEqual(t, resp.StatusCode(), 101)\n\n\ts := resp.BodyStream()\n\tassert.NotNil(t, s)\n\tconn, err := resp.Hijack()\n\tassert.Nil(t, err)\n\n\tb := make([]byte, 100)\n\t_, _ = conn.Write(append(b[:0], \"hello\"...))\n\tn, err := s.Read(b) // same as conn.Read\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, string(b[:n]), \"echo:hello\")\n}\n\nfunc TestStateObserve(t *testing.T) {\n\tsyncState := struct {\n\t\tmu    sync.Mutex\n\t\tstate config.ConnPoolState\n\t}{}\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn mock.SlowReadDialer(addr)\n\t\t\t}),\n\t\t\tStateObserve: func(hcs config.HostClientState) {\n\t\t\t\tsyncState.mu.Lock()\n\t\t\t\tdefer syncState.mu.Unlock()\n\t\t\t\tsyncState.state = hcs.ConnPoolState()\n\t\t\t},\n\t\t\tObservationInterval: 50 * time.Millisecond,\n\t\t},\n\t\tAddr:   \"foobar\",\n\t\tclosed: make(chan struct{}),\n\t}\n\n\tc.SetDynamicConfig(&client.DynamicConfig{\n\t\tAddr: utils.AddMissingPort(c.Addr, true),\n\t})\n\n\ttime.Sleep(500 * time.Millisecond)\n\tassert.Nil(t, c.Close())\n\tsyncState.mu.Lock()\n\tassert.DeepEqual(t, \"foobar:443\", syncState.state.Addr)\n\tsyncState.mu.Unlock()\n}\n\nfunc TestCachedTLSConfig(t *testing.T) {\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn mock.SlowReadDialer(addr)\n\t\t\t}),\n\t\t\tTLSConfig: &tls.Config{\n\t\t\t\tInsecureSkipVerify: true,\n\t\t\t},\n\t\t},\n\t\tAddr:  \"foobar\",\n\t\tIsTLS: true,\n\t}\n\n\tcfg1 := c.cachedTLSConfig(\"foobar\")\n\tcfg2 := c.cachedTLSConfig(\"baz\")\n\tassert.NotEqual(t, cfg1, cfg2)\n\tcfg3 := c.cachedTLSConfig(\"foobar\")\n\tassert.DeepEqual(t, cfg1, cfg3)\n}\n\nfunc TestRetry(t *testing.T) {\n\tvar times int32\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\ttimes++\n\t\t\t\tif times < 3 {\n\t\t\t\t\treturn &retryConn{\n\t\t\t\t\t\tConn: mock.NewConn(\"\"),\n\t\t\t\t\t}, nil\n\t\t\t\t}\n\t\t\t\treturn mock.NewConn(\"HTTP/1.1 200 OK\\r\\nContent-Length: 10\\r\\nContent-Type: foo/bar\\r\\n\\r\\n0123456789\"), nil\n\t\t\t}),\n\t\t\tRetryConfig: &retry.Config{\n\t\t\t\tMaxAttemptTimes: 5,\n\t\t\t\tDelay:           time.Millisecond * 10,\n\t\t\t},\n\t\t\tRetryIfFunc: func(req *protocol.Request, resp *protocol.Response, err error) bool {\n\t\t\t\treturn resp.Header.ContentLength() != 10\n\t\t\t},\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\treq := protocol.AcquireRequest()\n\treq.SetRequestURI(\"http://foobar/baz\")\n\treq.SetOptions(config.WithWriteTimeout(time.Millisecond * 100))\n\tresp := protocol.AcquireResponse()\n\n\tch := make(chan error, 1)\n\tgo func() {\n\t\tch <- c.Do(context.Background(), req, resp)\n\t}()\n\tselect {\n\tcase <-time.After(time.Second * 2):\n\t\tt.Fatalf(\"should use writeTimeout in request options\")\n\tcase err := <-ch:\n\t\tassert.Nil(t, err)\n\t\tassert.True(t, times == 3)\n\t\tassert.DeepEqual(t, resp.StatusCode(), 200)\n\t\tassert.DeepEqual(t, resp.Body(), []byte(\"0123456789\"))\n\t}\n}\n\n// mockConn for getting error when write binary data.\ntype writeErrConn struct {\n\tnetwork.Conn\n}\n\nfunc (w writeErrConn) WriteBinary(b []byte) (n int, err error) {\n\treturn 0, errs.ErrConnectionClosed\n}\n\ntype peekErrConn struct {\n\tnetwork.Conn\n}\n\nfunc (c peekErrConn) Peek(n int) ([]byte, error) {\n\treturn nil, errors.New(\"peek err\")\n}\n\ntype retryConn struct {\n\tnetwork.Conn\n}\n\nfunc (w retryConn) SetWriteTimeout(t time.Duration) error {\n\treturn errors.New(\"should retry\")\n}\n\nfunc TestConnInPoolRetry(t *testing.T) {\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn mock.NewOneTimeConn(\"HTTP/1.1 200 OK\\r\\nContent-Length: 10\\r\\nContent-Type: foo/bar\\r\\n\\r\\n0123456789\"), nil\n\t\t\t}),\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\treq := protocol.AcquireRequest()\n\treq.SetRequestURI(\"http://foobar/baz\")\n\treq.SetOptions(config.WithWriteTimeout(time.Millisecond * 100))\n\tresp := protocol.AcquireResponse()\n\n\tlogbuf := &bytes.Buffer{}\n\thlog.SetOutput(logbuf)\n\n\terr := c.Do(context.Background(), req, resp)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, resp.StatusCode(), 200)\n\tassert.DeepEqual(t, string(resp.Body()), \"0123456789\")\n\tassert.True(t, logbuf.String() == \"\")\n\tprotocol.ReleaseResponse(resp)\n\tresp = protocol.AcquireResponse()\n\terr = c.Do(context.Background(), req, resp)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, resp.StatusCode(), 200)\n\tassert.DeepEqual(t, string(resp.Body()), \"0123456789\")\n\tassert.True(t, strings.Contains(logbuf.String(), \"Client connection attempt times: 1\"))\n}\n\nfunc TestConnNotRetry(t *testing.T) {\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn mock.NewBrokenConn(\"\"), nil\n\t\t\t}),\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\treq := protocol.AcquireRequest()\n\treq.SetRequestURI(\"http://foobar/baz\")\n\treq.SetOptions(config.WithWriteTimeout(time.Millisecond * 100))\n\tresp := protocol.AcquireResponse()\n\tlogbuf := &bytes.Buffer{}\n\thlog.SetOutput(logbuf)\n\terr := c.Do(context.Background(), req, resp)\n\tassert.DeepEqual(t, errs.ErrConnectionClosed, err)\n\tassert.True(t, logbuf.String() == \"\")\n\tprotocol.ReleaseResponse(resp)\n}\n\ntype countCloseConn struct {\n\tnetwork.Conn\n\tisClose bool\n}\n\nfunc (c *countCloseConn) Close() error {\n\tc.isClose = true\n\treturn nil\n}\n\nfunc newCountCloseConn(s string) *countCloseConn {\n\treturn &countCloseConn{\n\t\tConn: mock.NewConn(s),\n\t}\n}\n\nfunc TestStreamNoContent(t *testing.T) {\n\tconn := newCountCloseConn(\"HTTP/1.1 204 Foo Bar\\r\\nContent-Type: aab\\r\\nTrailer: Foo\\r\\nContent-Encoding: deflate\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n0\\r\\nFoo: bar\\r\\n\\r\\nHTTP/1.2\")\n\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn conn, nil\n\t\t\t}),\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\tc.ResponseBodyStream = true\n\n\treq := protocol.AcquireRequest()\n\treq.SetRequestURI(\"http://foobar/baz\")\n\treq.Header.SetConnectionClose(true)\n\tresp := protocol.AcquireResponse()\n\n\tc.Do(context.Background(), req, resp)\n\n\tassert.True(t, conn.isClose)\n}\n\nfunc TestDialTimeout(t *testing.T) {\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialTimeout: time.Second * 10,\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\tassert.DeepEqual(t, time.Second*10, timeout)\n\t\t\t\treturn nil, errors.New(\"test error\")\n\t\t\t}),\n\t\t},\n\t\tAddr: \"foobar\",\n\t}\n\n\treq := protocol.AcquireRequest()\n\treq.SetRequestURI(\"http://foobar/baz\")\n\tresp := protocol.AcquireResponse()\n\n\tc.Do(context.Background(), req, resp)\n}\n\nfunc TestContextNil(t *testing.T) {\n\tdefer func() {\n\t\tv := recover()\n\t\tassert.NotNil(t, v)\n\t\tassert.True(t, fmt.Sprint(v) == \"ctx is nil\")\n\t}()\n\tc := &HostClient{}\n\tc.Do(nil, nil, nil) //nolint:staticcheck // SA1012: do not pass a nil Context\n}\n\nfunc TestCalcimeout(t *testing.T) {\n\tnow := time.Now()\n\n\ttests := []struct {\n\t\tname     string\n\t\tdeadline time.Time\n\t\ttimeout  time.Duration\n\t\texpected time.Duration\n\t}{\n\t\t{\"zero deadline, positive timeout\", time.Time{}, 5 * time.Second, 5 * time.Second},\n\t\t{\"zero deadline, zero timeout\", time.Time{}, 0, 0},\n\t\t{\"zero deadline, negative timeout\", time.Time{}, -1 * time.Second, 0},\n\t\t{\"future deadline, zero timeout\", now.Add(10 * time.Second), 0, 10 * time.Second},\n\t\t{\"future deadline, positive timeout (deadline < timeout)\", now.Add(3 * time.Second), 5 * time.Second, 3 * time.Second},\n\t\t{\"future deadline, positive timeout (deadline > timeout)\", now.Add(8 * time.Second), 5 * time.Second, 5 * time.Second},\n\t\t{\"past deadline, zero timeout\", now.Add(-5 * time.Second), 0, -1},\n\t\t{\"past deadline, positive timeout\", now.Add(-5 * time.Second), time.Second, -1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := calcTimeout(tt.deadline, tt.timeout)\n\t\t\tdiff := result - tt.expected\n\t\t\tif diff < -50*time.Millisecond || diff > 50*time.Millisecond {\n\t\t\t\tt.Errorf(\"calcTimeout(%v, %v) = %v, expected %v\",\n\t\t\t\t\ttt.deadline, tt.timeout, result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype mockConnClosed struct {\n\tclosed bool\n\tnetwork.Conn\n}\n\nfunc (m *mockConnClosed) Close() error {\n\tm.closed = true\n\treturn m.Conn.Close()\n}\n\n// mock CRLF attacking\nfunc TestDoNonNilReqResp_releaseConn(t *testing.T) {\n\trespStr := \"HTTP/1.1 400 OK\\nContent-Length: 6\\n\\n123456\"\n\tconn := &mockConnClosed{Conn: mock.NewConn(respStr + respStr)}\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {\n\t\t\t\treturn conn, nil\n\t\t\t}),\n\t\t},\n\t}\n\treq := protocol.AcquireRequest()\n\tresp := protocol.AcquireResponse()\n\treq.SetHost(\"foobar\")\n\tretry, err := c.doNonNilReqResp(req, resp)\n\tassert.False(t, retry)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, resp.StatusCode(), 400)\n\tassert.DeepEqual(t, resp.Body(), []byte(\"123456\"))\n\tassert.True(t, conn.closed)\n}\n"
  },
  {
    "path": "pkg/protocol/http1/client_unix_test.go",
    "content": "// Copyright 2023 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris\n\npackage http1\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/testutils\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/network/netpoll\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc TestGcBodyStream(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tsrv := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\tfor range [1024]int{} {\n\t\t\tw.Write([]byte(\"hello world\\n\"))\n\t\t}\n\t})}\n\tgo srv.Serve(ln)\n\n\taddr := ln.Addr().String()\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer:             netpoll.NewDialer(),\n\t\t\tResponseBodyStream: true,\n\t\t},\n\t\tAddr: addr,\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\treq, resp := protocol.AcquireRequest(), protocol.AcquireResponse()\n\t\treq.SetRequestURI(\"http://\" + addr)\n\t\treq.SetMethod(consts.MethodPost)\n\t\terr := c.Do(context.Background(), req, resp)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"client Do error=%v\", err.Error())\n\t\t}\n\t}\n\n\truntime.GC()\n\t// wait for gc\n\ttime.Sleep(100 * time.Millisecond)\n\tc.CloseIdleConnections()\n\tassert.DeepEqual(t, 0, c.ConnPoolState().TotalConnNum)\n}\n\nfunc TestMaxConn(t *testing.T) {\n\tln := testutils.NewTestListener(t)\n\tdefer ln.Close()\n\tsrv := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\tw.Write([]byte(\"hello world\\n\"))\n\t})}\n\tgo srv.Serve(ln)\n\n\taddr := ln.Addr().String()\n\tc := &HostClient{\n\t\tClientOptions: &ClientOptions{\n\t\t\tDialer:             netpoll.NewDialer(),\n\t\t\tResponseBodyStream: true,\n\t\t\tMaxConnWaitTimeout: time.Millisecond * 100,\n\t\t\tMaxConns:           5,\n\t\t},\n\t\tAddr: addr,\n\t}\n\n\tvar successCount int32\n\tvar noFreeCount int32\n\twg := sync.WaitGroup{}\n\tfor i := 0; i < 10; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\treq, resp := protocol.AcquireRequest(), protocol.AcquireResponse()\n\t\t\treq.SetRequestURI(\"http://\" + addr)\n\t\t\treq.SetMethod(consts.MethodPost)\n\t\t\terr := c.Do(context.Background(), req, resp)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, errs.ErrNoFreeConns) {\n\t\t\t\t\tatomic.AddInt32(&noFreeCount, 1)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"client Do error=%v\", err.Error())\n\t\t\t}\n\t\t\tatomic.AddInt32(&successCount, 1)\n\t\t}()\n\t}\n\twg.Wait()\n\n\tassert.True(t, atomic.LoadInt32(&successCount) == 5)\n\tassert.True(t, atomic.LoadInt32(&noFreeCount) == 5)\n\tassert.DeepEqual(t, 0, c.ConnectionCount())\n\tassert.DeepEqual(t, 5, c.WantConnectionCount())\n\n\truntime.GC()\n\t// wait for gc\n\ttime.Sleep(100 * time.Millisecond)\n\tc.CloseIdleConnections()\n\tassert.DeepEqual(t, 0, c.WantConnectionCount())\n}\n"
  },
  {
    "path": "pkg/protocol/http1/ext/common.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage ext\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nconst maxContentLengthInStream = 8 * 1024\n\nvar errBrokenChunk = errs.NewPublic(\"cannot find crlf at the end of chunk\").SetMeta(\"when read body chunk\")\n\nfunc MustPeekBuffered(r network.Reader) []byte {\n\tl := r.Len()\n\tbuf, err := r.Peek(l)\n\tif len(buf) == 0 || err != nil {\n\t\tpanic(fmt.Sprintf(\"bufio.Reader.Peek() returned unexpected data (%q, %v)\", buf, err))\n\t}\n\n\treturn buf\n}\n\nfunc MustDiscard(r network.Reader, n int) {\n\tif err := r.Skip(n); err != nil {\n\t\tpanic(fmt.Sprintf(\"bufio.Reader.Discard(%d) failed: %s\", n, err))\n\t}\n}\n\nfunc ReadRawHeaders(dst, buf []byte) ([]byte, int, error) {\n\tn := bytes.IndexByte(buf, '\\n')\n\tif n < 0 {\n\t\treturn dst[:0], 0, errNeedMore\n\t}\n\tif (n == 1 && buf[0] == '\\r') || n == 0 {\n\t\t// empty headers\n\t\treturn dst, n + 1, nil\n\t}\n\n\tn++\n\tb := buf\n\tm := n\n\tfor {\n\t\tb = b[m:]\n\t\tm = bytes.IndexByte(b, '\\n')\n\t\tif m < 0 {\n\t\t\treturn dst, 0, errNeedMore\n\t\t}\n\t\tm++\n\t\tn += m\n\t\tif (m == 2 && b[0] == '\\r') || m == 1 {\n\t\t\tdst = append(dst, buf[:n]...)\n\t\t\treturn dst, n, nil\n\t\t}\n\t}\n}\n\nfunc WriteBodyChunked(w network.Writer, r io.Reader) error {\n\tvbuf := utils.CopyBufPool.Get()\n\tbuf := vbuf.([]byte)\n\n\tvar err error\n\tvar n int\n\tfor {\n\t\tn, err = r.Read(buf)\n\t\tif n == 0 {\n\t\t\tif err == nil {\n\t\t\t\tpanic(\"BUG: io.Reader returned 0, nil\")\n\t\t\t}\n\n\t\t\tif !errors.Is(err, io.EOF) {\n\t\t\t\thlog.SystemLogger().Warnf(\"writing chunked response body encountered an error from the reader, \"+\n\t\t\t\t\t\"this may cause the short of the content in response body, error: %s\", err.Error())\n\t\t\t}\n\n\t\t\tif err = WriteChunk(w, buf[:0], true); err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t\tif err = WriteChunk(w, buf[:n], true); err != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tutils.CopyBufPool.Put(vbuf)\n\treturn err\n}\n\nfunc WriteBodyFixedSize(w network.Writer, r io.Reader, size int64) error {\n\tif size == 0 {\n\t\treturn nil\n\t}\n\tif size > consts.MaxSmallFileSize {\n\t\tif err := w.Flush(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif size > 0 {\n\t\tr = io.LimitReader(r, size)\n\t}\n\n\tn, err := utils.CopyZeroAlloc(w, r)\n\n\tif n != size && err == nil {\n\t\terr = fmt.Errorf(\"copied %d bytes from body stream instead of %d bytes\", n, size)\n\t}\n\treturn err\n}\n\nfunc appendBodyFixedSize(r network.Reader, dst []byte, n int) ([]byte, error) {\n\tif n == 0 {\n\t\treturn dst, nil\n\t}\n\n\toffset := len(dst)\n\tdstLen := offset + n\n\tif cap(dst) < dstLen {\n\t\tb := make([]byte, round2(dstLen))\n\t\tcopy(b, dst)\n\t\tdst = b\n\t}\n\tdst = dst[:dstLen]\n\n\t// Prefer io.Reader over Peek to avoid holding a ref to the underlying buffer.\n\tif rd, ok := r.(io.Reader); ok {\n\t\trn, err := io.ReadFull(rd, dst[offset:])\n\t\treturn dst[:offset+rn], err\n\t}\n\n\t// Peek can get all data, otherwise it will through error\n\tbuf, err := r.Peek(n)\n\tif err != nil {\n\t\tif err == io.EOF {\n\t\t\terr = io.ErrUnexpectedEOF\n\t\t}\n\t\treturn dst[:offset], err\n\t}\n\tcopy(dst[offset:], buf)\n\tr.Skip(len(buf)) // nolint: errcheck\n\treturn dst, nil\n}\n\nfunc readBodyIdentity(r network.Reader, maxBodySize int, dst []byte) ([]byte, error) {\n\tdst = dst[:cap(dst)]\n\tif len(dst) == 0 {\n\t\tdst = make([]byte, 1024)\n\t}\n\toffset := 0\n\tfor {\n\t\tnn := r.Len()\n\n\t\tif nn == 0 {\n\t\t\t_, err := r.Peek(1)\n\t\t\tif err != nil {\n\t\t\t\treturn dst[:offset], nil\n\t\t\t}\n\t\t\tnn = r.Len()\n\t\t}\n\t\tif nn >= (len(dst) - offset) {\n\t\t\tnn = len(dst) - offset\n\t\t}\n\n\t\tbuf, err := r.Peek(nn)\n\t\tif err != nil {\n\t\t\treturn dst[:offset], err\n\t\t}\n\t\tcopy(dst[offset:], buf)\n\t\tr.Skip(nn) // nolint: errcheck\n\n\t\toffset += nn\n\t\tif maxBodySize > 0 && offset > maxBodySize {\n\t\t\treturn dst[:offset], errBodyTooLarge\n\t\t}\n\t\tif len(dst) == offset {\n\t\t\tn := round2(2 * offset)\n\t\t\tif maxBodySize > 0 && n > maxBodySize {\n\t\t\t\tn = maxBodySize + 1\n\t\t\t}\n\t\t\tb := make([]byte, n)\n\t\t\tcopy(b, dst)\n\t\t\tdst = b\n\t\t}\n\t}\n}\n\nfunc ReadBody(r network.Reader, contentLength, maxBodySize int, dst []byte) ([]byte, error) {\n\tdst = dst[:0]\n\tif contentLength >= 0 {\n\t\tif maxBodySize > 0 && contentLength > maxBodySize {\n\t\t\treturn dst, errBodyTooLarge\n\t\t}\n\t\treturn appendBodyFixedSize(r, dst, contentLength)\n\t}\n\tif contentLength == -1 {\n\t\treturn readBodyChunked(r, maxBodySize, dst)\n\t}\n\treturn readBodyIdentity(r, maxBodySize, dst)\n}\n\nfunc LimitedReaderSize(r io.Reader) int64 {\n\tlr, ok := r.(*io.LimitedReader)\n\tif !ok {\n\t\treturn -1\n\t}\n\treturn lr.N\n}\n\nfunc readBodyChunked(r network.Reader, maxBodySize int, dst []byte) ([]byte, error) {\n\tif len(dst) > 0 {\n\t\tpanic(\"BUG: expected zero-length buffer\")\n\t}\n\n\tstrCRLFLen := len(bytestr.StrCRLF)\n\tfor {\n\t\tchunkSize, err := utils.ParseChunkSize(r)\n\t\tif err != nil {\n\t\t\treturn dst, err\n\t\t}\n\t\t// If it is the end of chunk, Read CRLF after reading trailer\n\t\tif chunkSize == 0 {\n\t\t\treturn dst, nil\n\t\t}\n\t\tif maxBodySize > 0 && len(dst)+chunkSize > maxBodySize {\n\t\t\treturn dst, errBodyTooLarge\n\t\t}\n\t\tdst, err = appendBodyFixedSize(r, dst, chunkSize+strCRLFLen)\n\t\tif err != nil {\n\t\t\treturn dst, err\n\t\t}\n\t\tif !bytes.Equal(dst[len(dst)-strCRLFLen:], bytestr.StrCRLF) {\n\t\t\treturn dst, errBrokenChunk\n\t\t}\n\t\tdst = dst[:len(dst)-strCRLFLen]\n\t}\n}\n\nfunc round2(n int) int {\n\tif n <= 0 {\n\t\treturn 0\n\t}\n\tn--\n\tx := uint(0)\n\tfor n > 0 {\n\t\tn >>= 1\n\t\tx++\n\t}\n\treturn 1 << x\n}\n\nfunc WriteChunk(w network.Writer, b []byte, withFlush bool) error {\n\tn := len(b)\n\n\tsz := bytesconv.EncodedIntHexLen(uint64(n)) + 2 // len + CRLF\n\tif n > 0 {\n\t\tsz += n + 2 // data + CRLF\n\t}\n\twb, err := w.Malloc(sz)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\twb = bytesconv.AppendIntHex(wb[:0], uint64(n))\n\twb = append(wb, '\\r', '\\n')\n\tif n > 0 {\n\t\twb = append(wb, b...)\n\t\twb = append(wb, '\\r', '\\n')\n\t}\n\tif len(wb) != sz {\n\t\tpanic(\"[BUG] len mismatch\")\n\t}\n\tif !withFlush {\n\t\treturn nil\n\t}\n\treturn w.Flush()\n}\n\nfunc isOnlyCRLF(b []byte) bool {\n\tfor _, ch := range b {\n\t\tif ch != '\\r' && ch != '\\n' {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc BufferSnippet(b []byte) string {\n\tn := len(b)\n\tstart := 20\n\tend := n - start\n\tif start >= end {\n\t\tstart = n\n\t\tend = n\n\t}\n\tbStart, bEnd := b[:start], b[end:]\n\tif len(bEnd) == 0 {\n\t\treturn fmt.Sprintf(\"%q\", b)\n\t}\n\treturn fmt.Sprintf(\"%q...%q\", bStart, bEnd)\n}\n\nfunc normalizeHeaderValue(ov, ob []byte, headerLength int) (nv, nb []byte, nhl int) {\n\tnv = ov\n\tlength := len(ov)\n\tif length <= 0 {\n\t\treturn\n\t}\n\twrite := 0\n\tshrunk := 0\n\tlineStart := false\n\tfor read := 0; read < length; read++ {\n\t\tc := ov[read]\n\t\tif c == '\\r' || c == '\\n' {\n\t\t\tshrunk++\n\t\t\tif c == '\\n' {\n\t\t\t\tlineStart = true\n\t\t\t}\n\t\t\tcontinue\n\t\t} else if lineStart && c == '\\t' {\n\t\t\tc = ' '\n\t\t} else {\n\t\t\tlineStart = false\n\t\t}\n\t\tnv[write] = c\n\t\twrite++\n\t}\n\n\tnv = nv[:write]\n\tcopy(ob[write:], ob[write+shrunk:])\n\n\t// Check if we need to skip \\r\\n or just \\n\n\tskip := 0\n\tif ob[write] == '\\r' {\n\t\tif ob[write+1] == '\\n' {\n\t\t\tskip += 2\n\t\t} else {\n\t\t\tskip++\n\t\t}\n\t} else if ob[write] == '\\n' {\n\t\tskip++\n\t}\n\n\tnb = ob[write+skip : len(ob)-shrunk]\n\tnhl = headerLength - shrunk\n\treturn\n}\n\nfunc stripSpace(b []byte) []byte {\n\tfor len(b) > 0 && b[0] == ' ' {\n\t\tb = b[1:]\n\t}\n\tfor len(b) > 0 && b[len(b)-1] == ' ' {\n\t\tb = b[:len(b)-1]\n\t}\n\treturn b\n}\n\nfunc SkipTrailer(r network.Reader) error {\n\tn := 1\n\tfor {\n\t\terr := trySkipTrailer(r, n)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t\tif !errors.Is(err, errs.ErrNeedMore) {\n\t\t\treturn err\n\t\t}\n\t\t// No more data available on the wire, try block peek(by netpoll)\n\t\tif n == r.Len() {\n\t\t\tn++\n\n\t\t\tcontinue\n\t\t}\n\t\tn = r.Len()\n\t}\n}\n\nfunc trySkipTrailer(r network.Reader, n int) error {\n\tb, err := r.Peek(n)\n\tif len(b) == 0 {\n\t\t// Return ErrTimeout on any timeout.\n\t\tif err != nil && strings.Contains(err.Error(), \"timeout\") {\n\t\t\treturn errs.New(errs.ErrTimeout, errs.ErrorTypePublic, \"read response header\")\n\t\t}\n\n\t\tif n == 1 || err == io.EOF {\n\t\t\treturn io.EOF\n\t\t}\n\n\t\treturn errs.NewPublicf(\"error when reading request trailer: %w\", err)\n\t}\n\tb = MustPeekBuffered(r)\n\theadersLen, errParse := skipTrailer(b)\n\tif errParse != nil {\n\t\tif err == io.EOF {\n\t\t\treturn err\n\t\t}\n\t\treturn HeaderError(\"response\", err, errParse, b)\n\t}\n\tMustDiscard(r, headersLen)\n\treturn nil\n}\n\nfunc skipTrailer(buf []byte) (int, error) {\n\tskip := 0\n\tstrCRLFLen := len(bytestr.StrCRLF)\n\tfor {\n\t\tindex := bytes.Index(buf, bytestr.StrCRLF)\n\t\tif index == -1 {\n\t\t\treturn 0, errs.ErrNeedMore\n\t\t}\n\n\t\tbuf = buf[index+strCRLFLen:]\n\t\tskip += index + strCRLFLen\n\n\t\tif index == 0 {\n\t\t\treturn skip, nil\n\t\t}\n\t}\n}\n\nfunc ReadTrailer(t *protocol.Trailer, r network.Reader) error {\n\tn := 1\n\tfor {\n\t\terr := tryReadTrailer(t, r, n)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t\tif !errors.Is(err, errs.ErrNeedMore) {\n\t\t\tt.ResetSkipNormalize()\n\t\t\treturn err\n\t\t}\n\t\t// No more data available on the wire, try block peek(by netpoll)\n\t\tif n == r.Len() {\n\t\t\tn++\n\n\t\t\tcontinue\n\t\t}\n\t\tn = r.Len()\n\t}\n}\n\nfunc tryReadTrailer(t *protocol.Trailer, r network.Reader, n int) error {\n\tb, err := r.Peek(n)\n\tif len(b) == 0 {\n\t\t// Return ErrTimeout on any timeout.\n\t\tif err != nil && strings.Contains(err.Error(), \"timeout\") {\n\t\t\treturn errs.New(errs.ErrTimeout, errs.ErrorTypePublic, \"read response header\")\n\t\t}\n\n\t\tif n == 1 || err == io.EOF {\n\t\t\treturn io.EOF\n\t\t}\n\n\t\treturn errs.NewPublicf(\"error when reading request trailer: %w\", err)\n\t}\n\tb = MustPeekBuffered(r)\n\theadersLen, errParse := parseTrailer(t, b)\n\tif errParse != nil {\n\t\tif err == io.EOF {\n\t\t\treturn err\n\t\t}\n\t\treturn HeaderError(\"response\", err, errParse, b)\n\t}\n\tMustDiscard(r, headersLen)\n\treturn nil\n}\n\nfunc parseTrailer(t *protocol.Trailer, buf []byte) (int, error) {\n\t// Skip any 0 length chunk.\n\tif buf[0] == '0' {\n\t\tskip := len(bytestr.StrCRLF) + 1\n\t\tif len(buf) < skip {\n\t\t\treturn 0, io.EOF\n\t\t}\n\t\tbuf = buf[skip:]\n\t}\n\n\tvar s HeaderScanner\n\ts.B = buf\n\ts.DisableNormalizing = t.IsDisableNormalizing()\n\tvar err error\n\tfor s.Next() {\n\t\tif len(s.Key) > 0 {\n\t\t\tif bytes.IndexByte(s.Key, ' ') != -1 || bytes.IndexByte(s.Key, '\\t') != -1 {\n\t\t\t\terr = fmt.Errorf(\"invalid trailer key %q\", s.Key)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terr = t.UpdateArgBytes(s.Key, s.Value)\n\t\t}\n\t}\n\tif s.Err != nil {\n\t\treturn 0, s.Err\n\t}\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn s.HLen, nil\n}\n\n// writeTrailer writes response trailer to w\nfunc WriteTrailer(t *protocol.Trailer, w network.Writer) error {\n\t_, err := w.WriteBinary(t.Header())\n\treturn err\n}\n"
  },
  {
    "path": "pkg/protocol/http1/ext/common_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage ext\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\nfunc Test_stripSpace(t *testing.T) {\n\ta := stripSpace([]byte(\"     a\"))\n\tb := stripSpace([]byte(\"b       \"))\n\tc := stripSpace([]byte(\"    c     \"))\n\tassert.DeepEqual(t, []byte(\"a\"), a)\n\tassert.DeepEqual(t, []byte(\"b\"), b)\n\tassert.DeepEqual(t, []byte(\"c\"), c)\n}\n\nfunc Test_bufferSnippet(t *testing.T) {\n\ta := make([]byte, 39)\n\tb := make([]byte, 41)\n\tassert.False(t, strings.Contains(BufferSnippet(a), \"\\\"...\\\"\"))\n\tassert.True(t, strings.Contains(BufferSnippet(b), \"\\\"...\\\"\"))\n}\n\nfunc Test_isOnlyCRLF(t *testing.T) {\n\tassert.True(t, isOnlyCRLF([]byte(\"\\r\\n\")))\n\tassert.True(t, isOnlyCRLF([]byte(\"\\n\")))\n}\n\nfunc TestReadTrailer(t *testing.T) {\n\texceptedTrailers := map[string]string{\"Hertz\": \"test\"}\n\tzr := mock.NewZeroCopyReader(\"0\\r\\nHertz: test\\r\\n\\r\\n\")\n\ttrailer := protocol.Trailer{}\n\tkeys := make([]string, 0, len(exceptedTrailers))\n\tfor k := range exceptedTrailers {\n\t\tkeys = append(keys, k)\n\t}\n\ttrailer.SetTrailers([]byte(strings.Join(keys, \", \")))\n\terr := ReadTrailer(&trailer, zr)\n\tif err != nil {\n\t\tt.Fatalf(\"Cannot read trailer: %v\", err)\n\t}\n\n\tfor k, v := range exceptedTrailers {\n\t\tgot := trailer.Peek(k)\n\t\tif !bytes.Equal(got, []byte(v)) {\n\t\t\tt.Fatalf(\"Unexpected trailer %q. Expected %q. Got %q\", k, v, got)\n\t\t}\n\t}\n}\n\nfunc TestReadTrailerError(t *testing.T) {\n\t// with bad trailer\n\tzr := mock.NewZeroCopyReader(\"0\\r\\nHertz: test\\r\\nContent-Type: aaa\\r\\n\\r\\n\")\n\ttrailer := protocol.Trailer{}\n\terr := ReadTrailer(&trailer, zr)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error.\")\n\t}\n\n\t// eof\n\ter := mock.EOFReader{}\n\ttrailer = protocol.Trailer{}\n\terr = ReadTrailer(&trailer, &er)\n\tassert.DeepEqual(t, io.EOF, err)\n}\n\nfunc TestReadTrailer1(t *testing.T) {\n\texceptedTrailers := map[string]string{}\n\tzr := mock.NewZeroCopyReader(\"0\\r\\n\\r\\n\")\n\ttrailer := protocol.Trailer{}\n\terr := ReadTrailer(&trailer, zr)\n\tif err != nil {\n\t\tt.Fatalf(\"Cannot read trailer: %v\", err)\n\t}\n\n\tfor k, v := range exceptedTrailers {\n\t\tgot := trailer.Peek(k)\n\t\tif !bytes.Equal(got, []byte(v)) {\n\t\t\tt.Fatalf(\"Unexpected trailer %q. Expected %q. Got %q\", k, v, got)\n\t\t}\n\t}\n}\n\nfunc TestReadRawHeaders(t *testing.T) {\n\ts := \"HTTP/1.1 200 OK\\r\\n\" +\n\t\t\"EmptyValue1:\\r\\n\" +\n\t\t\"Content-Type: foo/bar;\\r\\n\\tnewline;\\r\\n another/newline\\r\\n\" +\n\t\t\"Foo: Bar\\r\\n\" +\n\t\t\"Multi-Line: one;\\r\\n two\\r\\n\" +\n\t\t\"Values: v1;\\r\\n v2; v3;\\r\\n v4;\\tv5\\r\\n\" +\n\t\t\"Content-Length: 5\\r\\n\\r\\n\" +\n\t\t\"HELLOaaa\"\n\n\tvar dst []byte\n\trawHeaders, index, err := ReadRawHeaders(dst, []byte(s))\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, s[:index], string(rawHeaders))\n}\n\nfunc TestBodyChunked(t *testing.T) {\n\tvar log bytes.Buffer\n\thlog.SetOutput(&log)\n\n\tbody := \"foobar baz aaa bbb ccc\"\n\tchunk := \"16\\r\\nfoobar baz aaa bbb ccc\\r\\n0\\r\\n\"\n\tb := bytes.NewBufferString(body)\n\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\tWriteBodyChunked(zw, b)\n\n\tassert.DeepEqual(t, chunk, w.String())\n\n\tzr := mock.NewZeroCopyReader(chunk)\n\trb, err := ReadBody(zr, -1, 0, nil)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, body, string(rb))\n\n\tassert.DeepEqual(t, 0, log.Len())\n}\n\nfunc TestBrokenBodyChunked(t *testing.T) {\n\tbrokenReader := mock.NewBrokenConn(\"\")\n\tvar log bytes.Buffer\n\thlog.SetOutput(&log)\n\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\terr := WriteBodyChunked(zw, brokenReader)\n\tassert.Nil(t, err)\n\n\tassert.DeepEqual(t, []byte(\"0\\r\\n\"), w.Bytes())\n\tassert.True(t, bytes.Contains(log.Bytes(), []byte(\"writing chunked response body encountered an error from the reader\")))\n}\n\nfunc TestBodyFixedSize(t *testing.T) {\n\tbody := mock.CreateFixedBody(10)\n\tb := bytes.NewBuffer(body)\n\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\tWriteBodyFixedSize(zw, b, int64(len(body)))\n\n\tassert.DeepEqual(t, body, w.Bytes())\n\n\tzr := mock.NewZeroCopyReader(string(body))\n\trb, err := ReadBody(zr, len(body), 0, nil)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, body, rb)\n}\n\nfunc TestBodyFixedSizeQuickPath(t *testing.T) {\n\tconn := mock.NewBrokenConn(\"\")\n\terr := WriteBodyFixedSize(conn.Writer(), conn, 0)\n\tassert.Nil(t, err)\n}\n\nfunc TestBodyIdentity(t *testing.T) {\n\tbody := mock.CreateFixedBody(1024)\n\tzr := mock.NewZeroCopyReader(string(body))\n\trb, err := ReadBody(zr, -2, 0, nil)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, string(body), string(rb))\n}\n\nfunc TestBodySkipTrailer(t *testing.T) {\n\tt.Run(\"TestBodySkipTrailer\", func(t *testing.T) {\n\t\tbody := mock.CreateFixedBody(10)\n\t\ttrailer := map[string]string{\"Foo\": \"chunked shit\"}\n\t\tchunkedBody := mock.CreateChunkedBody(body, trailer, true)\n\t\tr := mock.NewSlowReadConn(string(chunkedBody))\n\t\terr := SkipTrailer(r)\n\t\tassert.Nil(t, err)\n\t\t_, err = r.ReadByte()\n\t\tassert.NotNil(t, err)\n\t\tassert.True(t, errors.Is(err, netpoll.ErrEOF))\n\t})\n\n\tt.Run(\"TestBodySkipTrailerError\", func(t *testing.T) {\n\t\t//  timeout error\n\t\tsr := mock.NewSlowReadConn(\"\")\n\t\terr := SkipTrailer(sr)\n\t\tassert.NotNil(t, err)\n\t\tassert.True(t, errors.Is(err, errs.ErrTimeout))\n\t\t//  EOF error\n\t\ter := &mock.EOFReader{}\n\t\terr = SkipTrailer(er)\n\t\tassert.NotNil(t, err)\n\t\tassert.True(t, errors.Is(err, io.EOF))\n\t})\n}\n"
  },
  {
    "path": "pkg/protocol/http1/ext/error.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage ext\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n)\n\nvar (\n\terrNeedMore     = errs.New(errs.ErrNeedMore, errs.ErrorTypePublic, \"cannot find trailing lf\")\n\terrBodyTooLarge = errs.New(errs.ErrBodyTooLarge, errs.ErrorTypePublic, \"ext\")\n)\n\nfunc HeaderError(typ string, err, errParse error, b []byte) error {\n\tif !errors.Is(errParse, errs.ErrNeedMore) {\n\t\treturn headerErrorMsg(typ, errParse, b)\n\t}\n\tif err == nil {\n\t\treturn errNeedMore\n\t}\n\n\t// Buggy servers may leave trailing CRLFs after http body.\n\t// Treat this case as EOF.\n\tif isOnlyCRLF(b) {\n\t\treturn io.EOF\n\t}\n\n\treturn headerErrorMsg(typ, err, b)\n}\n\nfunc headerErrorMsg(typ string, err error, b []byte) error {\n\treturn errs.NewPublic(fmt.Sprintf(\"error when reading %s headers: %s. Buffer size=%d, contents: %s\", typ, err, len(b), BufferSnippet(b)))\n}\n"
  },
  {
    "path": "pkg/protocol/http1/ext/headerscanner.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage ext\n\nimport (\n\t\"bytes\"\n\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n)\n\nvar errInvalidName = errs.NewPublic(\"invalid header name\")\n\ntype HeaderScanner struct {\n\tB     []byte\n\tKey   []byte\n\tValue []byte\n\tErr   error\n\n\t// HLen stores header subslice len\n\tHLen int\n\n\tDisableNormalizing bool\n\n\t// by checking whether the Next line contains a colon or not to tell\n\t// it's a header entry or a multi line value of current header entry.\n\t// the side effect of this operation is that we know the index of the\n\t// Next colon and new line, so this can be used during Next iteration,\n\t// instead of find them again.\n\tnextColon   int\n\tnextNewLine int\n\n\tinitialized bool\n}\n\ntype HeaderValueScanner struct {\n\tB     []byte\n\tValue []byte\n}\n\nfunc (s *HeaderScanner) Next() bool {\n\tif !s.initialized {\n\t\ts.nextColon = -1\n\t\ts.nextNewLine = -1\n\t\ts.initialized = true\n\t}\n\tbLen := len(s.B)\n\tif bLen >= 2 && s.B[0] == '\\r' && s.B[1] == '\\n' {\n\t\ts.B = s.B[2:]\n\t\ts.HLen += 2\n\t\treturn false\n\t}\n\tif bLen >= 1 && s.B[0] == '\\n' {\n\t\ts.B = s.B[1:]\n\t\ts.HLen++\n\t\treturn false\n\t}\n\tvar n int\n\tif s.nextColon >= 0 {\n\t\tn = s.nextColon\n\t\ts.nextColon = -1\n\t} else {\n\t\tn = bytes.IndexByte(s.B, ':')\n\n\t\t// There can't be a \\n inside the header name, check for this.\n\t\tx := bytes.IndexByte(s.B, '\\n')\n\t\tif x < 0 {\n\t\t\t// A header name should always at some point be followed by a \\n\n\t\t\t// even if it's the one that terminates the header block.\n\t\t\ts.Err = errNeedMore\n\t\t\treturn false\n\t\t}\n\t\tif x < n {\n\t\t\t// There was a \\n before the :\n\t\t\ts.Err = errInvalidName\n\t\t\treturn false\n\t\t}\n\t}\n\tif n < 0 {\n\t\ts.Err = errNeedMore\n\t\treturn false\n\t}\n\ts.Key = s.B[:n]\n\tutils.NormalizeHeaderKey(s.Key, s.DisableNormalizing)\n\tn++\n\tfor len(s.B) > n && s.B[n] == ' ' {\n\t\tn++\n\t\t// the newline index is a relative index, and lines below trimmed `s.b` by `n`,\n\t\t// so the relative newline index also shifted forward. it's safe to decrease\n\t\t// to a minus value, it means it's invalid, and will find the newline again.\n\t\ts.nextNewLine--\n\t}\n\ts.HLen += n\n\ts.B = s.B[n:]\n\tif s.nextNewLine >= 0 {\n\t\tn = s.nextNewLine\n\t\ts.nextNewLine = -1\n\t} else {\n\t\tn = bytes.IndexByte(s.B, '\\n')\n\t}\n\tif n < 0 {\n\t\ts.Err = errNeedMore\n\t\treturn false\n\t}\n\tisMultiLineValue := false\n\tfor {\n\t\tif n+1 >= len(s.B) {\n\t\t\tbreak\n\t\t}\n\t\tif s.B[n+1] != ' ' && s.B[n+1] != '\\t' {\n\t\t\tbreak\n\t\t}\n\t\td := bytes.IndexByte(s.B[n+1:], '\\n')\n\t\tif d <= 0 {\n\t\t\tbreak\n\t\t} else if d == 1 && s.B[n+1] == '\\r' {\n\t\t\tbreak\n\t\t}\n\t\te := n + d + 1\n\t\tif c := bytes.IndexByte(s.B[n+1:e], ':'); c >= 0 {\n\t\t\ts.nextColon = c\n\t\t\ts.nextNewLine = d - c - 1\n\t\t\tbreak\n\t\t}\n\t\tisMultiLineValue = true\n\t\tn = e\n\t}\n\tif n >= len(s.B) {\n\t\ts.Err = errNeedMore\n\t\treturn false\n\t}\n\toldB := s.B\n\ts.Value = s.B[:n]\n\ts.HLen += n + 1\n\ts.B = s.B[n+1:]\n\n\tif n > 0 && s.Value[n-1] == '\\r' {\n\t\tn--\n\t}\n\tfor n > 0 && s.Value[n-1] == ' ' {\n\t\tn--\n\t}\n\ts.Value = s.Value[:n]\n\tif isMultiLineValue {\n\t\ts.Value, s.B, s.HLen = normalizeHeaderValue(s.Value, oldB, s.HLen)\n\t}\n\treturn true\n}\n\nfunc (s *HeaderValueScanner) next() bool {\n\tb := s.B\n\tif len(b) == 0 {\n\t\treturn false\n\t}\n\tn := bytes.IndexByte(b, ',')\n\tif n < 0 {\n\t\ts.Value = stripSpace(b)\n\t\ts.B = b[len(b):]\n\t\treturn true\n\t}\n\ts.Value = stripSpace(b[:n])\n\ts.B = b[n+1:]\n\treturn true\n}\n\nfunc HasHeaderValue(s, value []byte) bool {\n\tvar vs HeaderValueScanner\n\tvs.B = s\n\tfor vs.next() {\n\t\tif utils.CaseInsensitiveCompare(vs.Value, value) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/protocol/http1/ext/headerscanner_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage ext\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestHasHeaderValue(t *testing.T) {\n\ts := []byte(\"Expect: 100-continue, User-Agent: foo, Host: 127.0.0.1, Connection: Keep-Alive, Content-Length: 5\")\n\tassert.True(t, HasHeaderValue(s, []byte(\"Connection: Keep-Alive\")))\n\tassert.False(t, HasHeaderValue(s, []byte(\"Connection: Keep-Alive1\")))\n}\n\nfunc TestResponseHeaderMultiLineValue(t *testing.T) {\n\tfirstLine := \"HTTP/1.1 200 OK\\r\\n\"\n\trawHeaders := \"EmptyValue1:\\r\\n\" +\n\t\t\"Content-Type: foo/bar;\\r\\n\\tnewline;\\r\\n another/newline\\r\\n\" +\n\t\t\"Foo: Bar\\r\\n\" +\n\t\t\"Multi-Line: one;\\r\\n two\\r\\n\" +\n\t\t\"Values: v1;\\r\\n v2; v3;\\r\\n v4;\\tv5\\r\\n\" +\n\t\t\"\\r\\n\"\n\n\t// compared with http response\n\tresponse, err := http.ReadResponse(bufio.NewReader(strings.NewReader(firstLine+rawHeaders)), nil)\n\tassert.Nil(t, err)\n\tdefer func() { response.Body.Close() }()\n\n\ths := &HeaderScanner{}\n\ths.B = []byte(rawHeaders)\n\ths.DisableNormalizing = false\n\thmap := make(map[string]string, len(response.Header))\n\tfor hs.Next() {\n\t\tif len(hs.Key) > 0 {\n\t\t\thmap[string(hs.Key)] = string(hs.Value)\n\t\t}\n\t}\n\n\tfor name, vals := range response.Header {\n\t\tgot := hmap[name]\n\t\twant := vals[0]\n\t\tassert.DeepEqual(t, want, got)\n\t}\n}\n\nfunc TestHeaderScannerError(t *testing.T) {\n\tt.Run(\"TestHeaderScannerErrorInvalidName\", func(t *testing.T) {\n\t\trawHeaders := \"Host: go.dev\\r\\nGopher-New-\\r\\n Line: This is a header on multiple lines\\r\\n\\r\\n\"\n\t\ttestTestHeaderScannerError(t, rawHeaders, errInvalidName)\n\t})\n\tt.Run(\"TestHeaderScannerErrorNeedMore\", func(t *testing.T) {\n\t\trawHeaders := \"This is a header on multiple lines\"\n\t\ttestTestHeaderScannerError(t, rawHeaders, errs.ErrNeedMore)\n\n\t\trawHeaders = \"Gopher-New-\\r\\n Line\"\n\t\ttestTestHeaderScannerError(t, rawHeaders, errs.ErrNeedMore)\n\t})\n}\n\nfunc testTestHeaderScannerError(t *testing.T, rawHeaders string, expectError error) {\n\ths := &HeaderScanner{}\n\ths.B = []byte(rawHeaders)\n\ths.DisableNormalizing = false\n\tfor hs.Next() {\n\t}\n\tassert.NotNil(t, hs.Err)\n\tassert.True(t, errors.Is(hs.Err, expectError))\n}\n"
  },
  {
    "path": "pkg/protocol/http1/ext/stream.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage ext\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\nvar (\n\terrChunkedStream = errs.New(errs.ErrChunkedStream, errs.ErrorTypePublic, nil)\n\n\tbodyStreamPool = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\treturn &bodyStream{}\n\t\t},\n\t}\n)\n\n// Deprecated: Use github.com/cloudwego/hertz/pkg/protocol.NoBody instead.\nvar NoBody = protocol.NoBody\n\ntype bodyStream struct {\n\tprefetchedBytes *bytes.Reader\n\treader          network.Reader\n\ttrailer         *protocol.Trailer\n\toffset          int\n\tcontentLength   int\n\tchunkLeft       int\n\t// whether the chunk has reached the EOF\n\tchunkEOF bool\n}\n\nfunc ReadBodyWithStreaming(zr network.Reader, contentLength, maxBodySize int, dst []byte) (b []byte, err error) {\n\tif contentLength == -1 {\n\t\t// handled in requestStream.Read()\n\t\treturn b, errChunkedStream\n\t}\n\tdst = dst[:0]\n\n\tif maxBodySize <= 0 {\n\t\tmaxBodySize = maxContentLengthInStream\n\t}\n\treadN := maxBodySize\n\tif readN > contentLength {\n\t\treadN = contentLength\n\t}\n\tif readN > maxContentLengthInStream {\n\t\treadN = maxContentLengthInStream\n\t}\n\n\tif contentLength >= 0 && maxBodySize >= contentLength {\n\t\tb, err = appendBodyFixedSize(zr, dst, readN)\n\t} else {\n\t\tb, err = readBodyIdentity(zr, readN, dst)\n\t}\n\n\tif err != nil {\n\t\treturn b, err\n\t}\n\tif contentLength > maxBodySize {\n\t\treturn b, errBodyTooLarge\n\t}\n\treturn b, nil\n}\n\nfunc AcquireBodyStream(b *bytebufferpool.ByteBuffer, r network.Reader, t *protocol.Trailer, contentLength int) io.Reader {\n\trs := bodyStreamPool.Get().(*bodyStream)\n\trs.prefetchedBytes = bytes.NewReader(b.B)\n\trs.reader = r\n\trs.contentLength = contentLength\n\trs.trailer = t\n\trs.chunkEOF = false\n\n\treturn rs\n}\n\nfunc (rs *bodyStream) Read(p []byte) (int, error) {\n\tdefer func() {\n\t\tif rs.reader != nil {\n\t\t\trs.reader.Release() //nolint:errcheck\n\t\t}\n\t}()\n\tif rs.contentLength == -1 {\n\t\tif rs.chunkEOF {\n\t\t\treturn 0, io.EOF\n\t\t}\n\n\t\tif rs.chunkLeft == 0 {\n\t\t\tchunkSize, err := utils.ParseChunkSize(rs.reader)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tif chunkSize == 0 {\n\t\t\t\terr = ReadTrailer(rs.trailer, rs.reader)\n\t\t\t\tif err == nil {\n\t\t\t\t\trs.chunkEOF = true\n\t\t\t\t\terr = io.EOF\n\t\t\t\t}\n\t\t\t\treturn 0, err\n\t\t\t}\n\n\t\t\trs.chunkLeft = chunkSize\n\t\t}\n\t\tbytesToRead := len(p)\n\n\t\tif bytesToRead > rs.chunkLeft {\n\t\t\tbytesToRead = rs.chunkLeft\n\t\t}\n\n\t\tsrc, err := rs.reader.Peek(bytesToRead)\n\t\tcopied := copy(p, src)\n\t\trs.reader.Skip(copied) // nolint: errcheck\n\t\trs.chunkLeft -= copied\n\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\terr = io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\treturn copied, err\n\t\t}\n\n\t\tif rs.chunkLeft == 0 {\n\t\t\terr = utils.SkipCRLF(rs.reader)\n\t\t\tif err == io.EOF {\n\t\t\t\terr = io.ErrUnexpectedEOF\n\t\t\t}\n\t\t}\n\n\t\treturn copied, err\n\t}\n\tif rs.offset == rs.contentLength {\n\t\treturn 0, io.EOF\n\t}\n\tvar n int\n\tvar err error\n\t// read from the pre-read buffer\n\tif int(rs.prefetchedBytes.Size()) > rs.offset {\n\t\tn, err = rs.prefetchedBytes.Read(p)\n\t\trs.offset += n\n\t\tif rs.offset == rs.contentLength {\n\t\t\treturn n, io.EOF\n\t\t}\n\t\tif err != nil || len(p) == n {\n\t\t\treturn n, err\n\t\t}\n\t}\n\n\t// read from the wire\n\tm := len(p) - n\n\tremain := rs.contentLength - rs.offset\n\n\tif m > remain {\n\t\tm = remain\n\t}\n\n\tif conn, ok := rs.reader.(io.Reader); ok {\n\t\tm, err = conn.Read(p[n:])\n\t} else {\n\t\tvar tmp []byte\n\t\ttmp, err = rs.reader.Peek(m)\n\t\tm = copy(p[n:], tmp)\n\t\trs.reader.Skip(m) // nolint: errcheck\n\t}\n\trs.offset += m\n\tn += m\n\n\tif err != nil {\n\t\t// the data on stream may be incomplete\n\t\tif err == io.EOF {\n\t\t\tif rs.offset != rs.contentLength && rs.contentLength != -2 {\n\t\t\t\terr = io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\t// ensure that skipRest works fine\n\t\t\trs.offset = rs.contentLength\n\t\t}\n\t\treturn n, err\n\t}\n\tif rs.offset == rs.contentLength {\n\t\terr = io.EOF\n\t}\n\treturn n, err\n}\n\nfunc (rs *bodyStream) skipRest() error {\n\t// The body length doesn't exceed the maxContentLengthInStream or\n\t// the bodyStream has been skip rest\n\tif rs.prefetchedBytes == nil {\n\t\treturn nil\n\t}\n\n\t// the request is chunked encoding\n\tif rs.contentLength == -1 {\n\t\tif rs.chunkEOF {\n\t\t\treturn nil\n\t\t}\n\n\t\tstrCRLFLen := len(bytestr.StrCRLF)\n\t\tfor {\n\t\t\tchunkSize, err := utils.ParseChunkSize(rs.reader)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif chunkSize == 0 {\n\t\t\t\trs.chunkEOF = true\n\t\t\t\treturn SkipTrailer(rs.reader)\n\t\t\t}\n\n\t\t\terr = rs.reader.Skip(chunkSize)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tcrlf, err := rs.reader.Peek(strCRLFLen)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif !bytes.Equal(crlf, bytestr.StrCRLF) {\n\t\t\t\treturn errBrokenChunk\n\t\t\t}\n\n\t\t\terr = rs.reader.Skip(strCRLFLen)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// After Skip, the buffer needs to be released to prevent OOM if there are too much data on conn.\n\t\t\terr = rs.reader.Release()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t}\n\t}\n\t// max value of pSize is 8193, it's safe.\n\tpSize := int(rs.prefetchedBytes.Size())\n\tif rs.contentLength <= pSize || rs.offset == rs.contentLength {\n\t\treturn nil\n\t}\n\n\tneedSkipLen := 0\n\tif rs.offset > pSize {\n\t\tneedSkipLen = rs.contentLength - rs.offset\n\t} else {\n\t\tneedSkipLen = rs.contentLength - pSize\n\t}\n\n\t// must skip size\n\tfor {\n\t\tskip := rs.reader.Len()\n\t\tif skip == 0 {\n\t\t\t_, err := rs.reader.Peek(1)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tskip = rs.reader.Len()\n\t\t}\n\t\tif skip > needSkipLen {\n\t\t\tskip = needSkipLen\n\t\t}\n\t\terr := rs.reader.Skip(skip)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// After Skip, the buffer needs to be released to prevent OOM if there are too much data on conn.\n\t\terr = rs.reader.Release()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tneedSkipLen -= skip\n\t\tif needSkipLen == 0 {\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\n// ReleaseBodyStream releases the body stream.\n// Error of skipRest may be returned if there is one.\n//\n// NOTE: Be careful to use this method unless you know what it's for.\nfunc ReleaseBodyStream(requestReader io.Reader) (err error) {\n\tif rs, ok := requestReader.(*bodyStream); ok {\n\t\terr = rs.skipRest()\n\t\trs.reset()\n\t\tbodyStreamPool.Put(rs)\n\t}\n\treturn\n}\n\nfunc (rs *bodyStream) reset() {\n\trs.prefetchedBytes = nil\n\trs.offset = 0\n\trs.reader = nil\n\trs.trailer = nil\n\trs.chunkEOF = false\n\trs.chunkLeft = 0\n\trs.contentLength = 0\n}\n"
  },
  {
    "path": "pkg/protocol/http1/ext/stream_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage ext\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\nfunc createChunkedBody(body, rest []byte, trailer map[string]string, hasTrailer bool) []byte {\n\tvar b []byte\n\tchunkSize := 1\n\tfor len(body) > 0 {\n\t\tif chunkSize > len(body) {\n\t\t\tchunkSize = len(body)\n\t\t}\n\t\tb = append(b, []byte(fmt.Sprintf(\"%x\\r\\n\", chunkSize))...)\n\t\tb = append(b, body[:chunkSize]...)\n\t\tb = append(b, []byte(\"\\r\\n\")...)\n\t\tbody = body[chunkSize:]\n\t\tchunkSize++\n\t}\n\tif hasTrailer {\n\t\tb = append(b, \"0\\r\\n\"...)\n\t\tfor k, v := range trailer {\n\t\t\tb = append(b, k...)\n\t\t\tb = append(b, \": \"...)\n\t\t\tb = append(b, v...)\n\t\t\tb = append(b, \"\\r\\n\"...)\n\t\t}\n\t\tb = append(b, \"\\r\\n\"...)\n\t}\n\treturn append(b, rest...)\n}\n\nfunc testChunkedSkipRest(t *testing.T, data, rest string) {\n\tvar pool bytebufferpool.Pool\n\treader := mock.NewZeroCopyReader(data)\n\n\tbs := AcquireBodyStream(pool.Get(), reader, &protocol.Trailer{}, -1)\n\terr := bs.(*bodyStream).skipRest()\n\tassert.Nil(t, err)\n\n\trest_data, err := io.ReadAll(reader)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, rest, string(rest_data))\n}\n\nfunc testChunkedSkipRestWithBodySize(t *testing.T, bodySize int) {\n\tbody := mock.CreateFixedBody(bodySize)\n\trest := mock.CreateFixedBody(bodySize)\n\tdata := createChunkedBody(body, rest, map[string]string{\"foo\": \"bar\"}, true)\n\n\ttestChunkedSkipRest(t, string(data), string(rest))\n}\n\nfunc TestChunkedSkipRest(t *testing.T) {\n\tt.Parallel()\n\n\ttestChunkedSkipRest(t, \"0\\r\\n\\r\\n\", \"\")\n\ttestChunkedSkipRest(t, \"0\\r\\n\\r\\nHTTP/1.1 / POST\", \"HTTP/1.1 / POST\")\n\ttestChunkedSkipRest(t, \"0\\r\\nHertz: test\\r\\nfoo: bar\\r\\n\\r\\nHTTP/1.1 / POST\", \"HTTP/1.1 / POST\")\n\n\ttestChunkedSkipRestWithBodySize(t, 5)\n\n\t// medium-size body\n\ttestChunkedSkipRestWithBodySize(t, 43488)\n\n\t// big body\n\ttestChunkedSkipRestWithBodySize(t, 3*1024*1024)\n}\n\nfunc TestBodyStream_Reset(t *testing.T) {\n\tt.Parallel()\n\tbs := bodyStream{\n\t\tprefetchedBytes: bytes.NewReader([]byte(\"aaa\")),\n\t\treader:          mock.NewZeroCopyReader(\"bbb\"),\n\t\ttrailer:         &protocol.Trailer{},\n\t\toffset:          10,\n\t\tcontentLength:   20,\n\t\tchunkLeft:       50,\n\t\tchunkEOF:        true,\n\t}\n\n\tbs.reset()\n\n\tassert.Nil(t, bs.prefetchedBytes)\n\tassert.Nil(t, bs.reader)\n\tassert.Nil(t, bs.trailer)\n\tassert.DeepEqual(t, 0, bs.offset)\n\tassert.DeepEqual(t, 0, bs.contentLength)\n\tassert.DeepEqual(t, 0, bs.chunkLeft)\n\tassert.False(t, bs.chunkEOF)\n}\n\nfunc TestReadBodyWithStreaming(t *testing.T) {\n\tt.Run(\"TestBodyFixedSize\", func(t *testing.T) {\n\t\tbodySize := 1024\n\t\tbody := mock.CreateFixedBody(bodySize)\n\t\treader := mock.NewZeroCopyReader(string(body))\n\t\tdst, err := ReadBodyWithStreaming(reader, bodySize, -1, nil)\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, body, dst)\n\t})\n\n\tt.Run(\"TestBodyFixedSizeMaxContentLength\", func(t *testing.T) {\n\t\tbodySize := 8 * 1024 * 2\n\t\tbody := mock.CreateFixedBody(bodySize)\n\t\treader := mock.NewZeroCopyReader(string(body))\n\t\tdst, err := ReadBodyWithStreaming(reader, bodySize, 8*1024*10, nil)\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, body[:maxContentLengthInStream], dst)\n\t})\n\n\tt.Run(\"TestBodyIdentity\", func(t *testing.T) {\n\t\tbodySize := 1024\n\t\tbody := mock.CreateFixedBody(bodySize)\n\t\treader := mock.NewZeroCopyReader(string(body))\n\t\tdst, err := ReadBodyWithStreaming(reader, -2, 512, nil)\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, body, dst)\n\t})\n\n\tt.Run(\"TestErrBodyTooLarge\", func(t *testing.T) {\n\t\tbodySize := 2048\n\t\tbody := mock.CreateFixedBody(bodySize)\n\t\treader := mock.NewZeroCopyReader(string(body))\n\t\tdst, err := ReadBodyWithStreaming(reader, bodySize, 1024, nil)\n\t\tassert.True(t, errors.Is(err, errBodyTooLarge))\n\t\tassert.DeepEqual(t, body[:len(dst)], dst)\n\t})\n\n\tt.Run(\"TestErrChunkedStream\", func(t *testing.T) {\n\t\tbodySize := 1024\n\t\tbody := mock.CreateFixedBody(bodySize)\n\t\treader := mock.NewZeroCopyReader(string(body))\n\t\tdst, err := ReadBodyWithStreaming(reader, -1, bodySize, nil)\n\t\tassert.True(t, errors.Is(err, errChunkedStream))\n\t\tassert.Nil(t, dst)\n\t})\n}\n\nfunc TestBodyStream(t *testing.T) {\n\tt.Run(\"TestBodyStreamPrereadBuffer\", func(t *testing.T) {\n\t\tbodySize := 1024\n\t\tbody := mock.CreateFixedBody(bodySize)\n\t\tbyteBuffer := &bytebufferpool.ByteBuffer{}\n\t\tbyteBuffer.Set(body)\n\n\t\tbs := AcquireBodyStream(byteBuffer, mock.NewSlowReadConn(\"\"), nil, len(body))\n\t\tdefer func() {\n\t\t\tReleaseBodyStream(bs)\n\t\t}()\n\n\t\tb := make([]byte, bodySize)\n\t\terr := bodyStreamRead(bs, b)\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, len(body), len(b))\n\t\tassert.DeepEqual(t, string(body), string(b))\n\t})\n\n\tt.Run(\"TestBodyStreamRelease\", func(t *testing.T) {\n\t\tbodySize := 1024\n\t\tbody := mock.CreateFixedBody(bodySize)\n\t\tbyteBuffer := &bytebufferpool.ByteBuffer{}\n\t\tbyteBuffer.Set(body)\n\t\tbs := AcquireBodyStream(byteBuffer, mock.NewSlowReadConn(string(body)), nil, bodySize*2)\n\t\terr := ReleaseBodyStream(bs)\n\t\tassert.Nil(t, err)\n\t})\n\n\tt.Run(\"TestBodyStreamChunked\", func(t *testing.T) {\n\t\tbodySize := 5\n\t\tbody := mock.CreateFixedBody(bodySize)\n\t\texpectedTrailer := map[string]string{\"Foo\": \"chunked shit\"}\n\t\tchunkedBody := mock.CreateChunkedBody(body, expectedTrailer, true)\n\n\t\tbyteBuffer := &bytebufferpool.ByteBuffer{}\n\t\tbyteBuffer.Set(chunkedBody)\n\n\t\tbs := AcquireBodyStream(byteBuffer, mock.NewSlowReadConn(string(chunkedBody)), &protocol.Trailer{}, -1)\n\t\tdefer func() {\n\t\t\tReleaseBodyStream(bs)\n\t\t}()\n\n\t\tb := make([]byte, bodySize)\n\t\terr := bodyStreamRead(bs, b)\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, len(body), len(b))\n\t\tassert.DeepEqual(t, string(body), string(b))\n\t})\n\n\tt.Run(\"TestBodyStreamReadFromWire\", func(t *testing.T) {\n\t\tbodySize := 1024\n\t\tbody := mock.CreateFixedBody(bodySize)\n\t\tbyteBuffer := &bytebufferpool.ByteBuffer{}\n\t\tbyteBuffer.Set(body)\n\n\t\trcBodySize := 128\n\t\trcBody := mock.CreateFixedBody(rcBodySize)\n\t\tbs := AcquireBodyStream(byteBuffer, mock.NewSlowReadConn(string(rcBody)), nil, -2)\n\t\tdefer func() {\n\t\t\tReleaseBodyStream(bs)\n\t\t}()\n\n\t\tb := make([]byte, bodySize)\n\t\terr := bodyStreamRead(bs, b)\n\t\tassert.Nil(t, err)\n\t\tassert.DeepEqual(t, len(body), len(b))\n\t\tassert.DeepEqual(t, string(body), string(b))\n\t})\n}\n\nfunc bodyStreamRead(bs io.Reader, b []byte) (err error) {\n\tnb := 0\n\tfor {\n\t\tp := make([]byte, 64)\n\t\tn, rErr := bs.Read(p)\n\t\tif n > 0 {\n\t\t\tcopy(b[nb:], p[:])\n\t\t\tnb = nb + n\n\t\t}\n\n\t\tif rErr != nil {\n\t\t\tif rErr != io.EOF {\n\t\t\t\terr = rErr\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/protocol/http1/factory/client.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage factory\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/protocol/client\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/suite\"\n)\n\nvar _ suite.ClientFactory = (*clientFactory)(nil)\n\ntype clientFactory struct {\n\toption *http1.ClientOptions\n}\n\nfunc (s *clientFactory) NewHostClient() (client client.HostClient, err error) {\n\treturn http1.NewHostClient(s.option), nil\n}\n\nfunc NewClientFactory(option *http1.ClientOptions) suite.ClientFactory {\n\treturn &clientFactory{\n\t\toption: option,\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/http1/factory/server.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage factory\n\nimport (\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/suite\"\n)\n\nvar _ suite.ServerFactory = (*serverFactory)(nil)\n\ntype serverFactory struct {\n\toption *http1.Option\n}\n\n// New is called by Hertz during engine.Run()\nfunc (s *serverFactory) New(core suite.Core) (server protocol.Server, err error) {\n\tserv := http1.NewServer()\n\tserv.Option = *s.option\n\tserv.Core = core\n\treturn serv, nil\n}\n\nfunc NewServerFactory(option *http1.Option) suite.ServerFactory {\n\treturn &serverFactory{\n\t\toption: option,\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/http1/proxy/proxy.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Copyright 2016 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\treqI \"github.com/cloudwego/hertz/pkg/protocol/http1/req\"\n\trespI \"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n)\n\nfunc SetupProxy(conn network.Conn, addr string, proxyURI *protocol.URI, tlsConfig *tls.Config, isTLS bool, dialer network.Dialer) (network.Conn, error) {\n\tvar err error\n\tif bytes.Equal(proxyURI.Scheme(), bytestr.StrHTTPS) {\n\t\tconn, err = dialer.AddTLS(conn, tlsConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tswitch {\n\tcase proxyURI == nil:\n\t\t// Do nothing. Not using a proxy.\n\tcase isTLS: // target addr is https\n\t\tconnectReq, connectResp := protocol.AcquireRequest(), protocol.AcquireResponse()\n\t\tdefer func() {\n\t\t\tprotocol.ReleaseRequest(connectReq)\n\t\t\tprotocol.ReleaseResponse(connectResp)\n\t\t}()\n\n\t\tSetProxyAuthHeader(&connectReq.Header, proxyURI)\n\t\tconnectReq.SetMethod(consts.MethodConnect)\n\t\tconnectReq.SetHost(addr)\n\n\t\t// Skip response body when send CONNECT request.\n\t\tconnectResp.SkipBody = true\n\n\t\t// If there's no done channel (no deadline or cancellation\n\t\t// from the caller possible), at least set some (long)\n\t\t// timeout here. This will make sure we don't block forever\n\t\t// and leak a goroutine if the connection stops replying\n\t\t// after the TCP connect.\n\t\tconnectCtx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)\n\t\tdefer cancel()\n\n\t\tdidReadResponse := make(chan struct{}) // closed after CONNECT write+read is done or fails\n\n\t\t// Write the CONNECT request & read the response.\n\t\tgo func() {\n\t\t\tdefer close(didReadResponse)\n\n\t\t\terr = reqI.Write(connectReq, conn)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = conn.Flush()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = respI.Read(connectResp, conn)\n\t\t}()\n\t\tselect {\n\t\tcase <-connectCtx.Done():\n\t\t\tconn.Close()\n\t\t\t<-didReadResponse\n\n\t\t\treturn nil, connectCtx.Err()\n\t\tcase <-didReadResponse:\n\t\t}\n\n\t\tif err != nil {\n\t\t\tconn.Close()\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif connectResp.StatusCode() != consts.StatusOK {\n\t\t\tconn.Close()\n\n\t\t\treturn nil, errors.NewPublic(consts.StatusMessage(connectResp.StatusCode()))\n\t\t}\n\t}\n\n\tif proxyURI != nil && isTLS {\n\t\tconn, err = dialer.AddTLS(conn, tlsConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn conn, nil\n}\n\nfunc SetProxyAuthHeader(h *protocol.RequestHeader, proxyURI *protocol.URI) {\n\tif username := proxyURI.Username(); username != nil {\n\t\tpassword := proxyURI.Password()\n\t\tauth := base64.StdEncoding.EncodeToString(bytesconv.S2b(bytesconv.B2s(username) + \":\" + bytesconv.B2s(password)))\n\t\th.Set(\"Proxy-Authorization\", \"Basic \"+auth)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/http1/req/header.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage req\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/ext\"\n)\n\nvar errEOFReadHeader = errs.NewPublic(\"error when reading request headers: EOF\")\n\n// Write writes request header to w.\nfunc WriteHeader(h *protocol.RequestHeader, w network.Writer) error {\n\theader := h.Header()\n\t_, err := w.WriteBinary(header)\n\treturn err\n}\n\nfunc ReadHeader(h *protocol.RequestHeader, r network.Reader) error {\n\treturn ReadHeaderWithLimit(h, r, 0)\n}\n\nfunc ReadHeaderWithLimit(h *protocol.RequestHeader, r network.Reader, maxHeaderBytes int) error {\n\tn := 1\n\tfor {\n\t\terr := tryReadWithLimit(h, r, n, maxHeaderBytes)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t\tif !errors.Is(err, errs.ErrNeedMore) {\n\t\t\th.ResetSkipNormalize()\n\t\t\treturn err\n\t\t}\n\n\t\t// No more data available on the wire, try block peek\n\t\tif n == r.Len() {\n\t\t\tn++\n\t\t\tcontinue\n\t\t}\n\t\tn = r.Len()\n\t}\n}\n\nfunc tryReadWithLimit(h *protocol.RequestHeader, r network.Reader, n, maxHeaderBytes int) error {\n\th.ResetSkipNormalize()\n\tb, err := r.Peek(n)\n\tif len(b) == 0 {\n\t\tif err != io.EOF {\n\t\t\treturn err\n\t\t}\n\n\t\t// n == 1 on the first read for the request.\n\t\tif n == 1 {\n\t\t\t// We didn't read a single byte.\n\t\t\treturn errs.New(errs.ErrNothingRead, errs.ErrorTypePrivate, err)\n\t\t}\n\n\t\treturn errEOFReadHeader\n\t}\n\tb = ext.MustPeekBuffered(r)\n\tif maxHeaderBytes > 0 && len(b) > maxHeaderBytes {\n\t\tb = b[:maxHeaderBytes]\n\t}\n\theadersLen, errParse := parse(h, b)\n\tif errParse != nil {\n\t\tif maxHeaderBytes > 0 && len(b) >= maxHeaderBytes && errors.Is(errParse, errs.ErrNeedMore) {\n\t\t\treturn errHeaderTooLarge\n\t\t}\n\t\treturn ext.HeaderError(\"request\", err, errParse, b)\n\t}\n\text.MustDiscard(r, headersLen)\n\treturn nil\n}\n\nfunc parse(h *protocol.RequestHeader, buf []byte) (int, error) {\n\tm, err := parseFirstLine(h, buf)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\trawHeaders, _, err := ext.ReadRawHeaders(h.RawHeaders()[:0], buf[m:])\n\th.SetRawHeaders(rawHeaders)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tn, err := parseHeaders(h, buf[m:])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn m + n, nil\n}\n\nconst (\n\tmaxCheckMethodLen = 10\n\n\t// reuse ValidHeaderFieldNameTable for Method, both are `token`\n\t// see:\n\t//\thttps://www.rfc-editor.org/rfc/rfc9110.html#name-methods\n\t//\thttps://www.rfc-editor.org/rfc/rfc9110.html#name-field-names\n\tvalidMethodCharTable = bytesconv.ValidHeaderFieldNameTable\n)\n\nvar errMalformedHTTPRequest = errors.New(\"malformed HTTP request\")\n\n// request-line = method SP request-target SP HTTP-version CRLF\nfunc parseFirstLine(h *protocol.RequestHeader, buf []byte) (int, error) {\n\tb, leftb, err := utils.NextLine(buf)\n\tif err != nil {\n\t\t// errs.ErrNeedMore?\n\t\t// check malformed HTTP request before reading more data\n\t\t// NOTE:\n\t\t//  only check method bytes if errs.ErrNeedMore for closing malformed connections.\n\t\t//  for performance concern, it won't be checked in the hot path.\n\t\tfor i, c := range buf {\n\t\t\tif c == ' ' || i > maxCheckMethodLen {\n\t\t\t\tbreak // skip if SP or reach maxCheckMethodLen\n\t\t\t}\n\t\t\tif validMethodCharTable[c] == 0 {\n\t\t\t\treturn 0, errMalformedHTTPRequest\n\t\t\t}\n\t\t}\n\t\treturn 0, err\n\t}\n\n\t// parse method\n\tn := bytes.IndexByte(b, ' ')\n\tif n <= 0 {\n\t\treturn 0, errMalformedHTTPRequest\n\t}\n\th.SetMethodBytes(b[:n])\n\tb = b[n+1:]\n\n\t// parse request-target (uri)\n\tn = bytes.IndexByte(b, ' ')\n\tif n <= 0 {\n\t\treturn 0, errMalformedHTTPRequest\n\t}\n\th.SetRequestURIBytes(b[:n])\n\tb = b[n+1:]\n\n\t// parse http protocol\n\tswitch string(b) {\n\tcase consts.HTTP11: // likely HTTP/1.1\n\t\th.SetProtocol(consts.HTTP11)\n\tcase consts.HTTP10:\n\t\th.SetProtocol(consts.HTTP10)\n\tdefault:\n\t\tif len(b) < 5 || string(b[:5]) != \"HTTP/\" {\n\t\t\treturn 0, errMalformedHTTPRequest\n\t\t}\n\t\t// XXX: all other cases are considered to be HTTP/1.0 for safe\n\t\th.SetProtocol(consts.HTTP10)\n\t}\n\treturn len(buf) - len(leftb), nil\n}\n\n// validHeaderFieldValue is equal to httpguts.ValidHeaderFieldValue（shares the same context）\nfunc validHeaderFieldValue(val []byte) bool {\n\tfor _, v := range val {\n\t\tif bytesconv.ValidHeaderFieldValueTable[v] == 0 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc parseHeaders(h *protocol.RequestHeader, buf []byte) (int, error) {\n\th.InitContentLengthWithValue(-2)\n\n\tvar s ext.HeaderScanner\n\ts.B = buf\n\ts.DisableNormalizing = h.IsDisableNormalizing()\n\tvar err error\n\tfor s.Next() {\n\t\tif len(s.Key) > 0 {\n\t\t\t// Spaces between the header key and colon are not allowed.\n\t\t\t// See RFC 7230, Section 3.2.4.\n\t\t\tif bytes.IndexByte(s.Key, ' ') != -1 || bytes.IndexByte(s.Key, '\\t') != -1 {\n\t\t\t\terr = fmt.Errorf(\"invalid header key %q\", s.Key)\n\t\t\t\treturn 0, err\n\t\t\t}\n\n\t\t\t// Check the invalid chars in header value\n\t\t\tif !validHeaderFieldValue(s.Value) {\n\t\t\t\terr = fmt.Errorf(\"invalid header value %q\", s.Value)\n\t\t\t\treturn 0, err\n\t\t\t}\n\n\t\t\tswitch s.Key[0] | 0x20 {\n\t\t\tcase 'h':\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrHost) {\n\t\t\t\t\th.SetHostBytes(s.Value)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\tcase 'u':\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrUserAgent) {\n\t\t\t\t\th.SetUserAgentBytes(s.Value)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\tcase 'c':\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrContentType) {\n\t\t\t\t\th.SetContentTypeBytes(s.Value)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrContentLength) {\n\t\t\t\t\tif h.ContentLength() != -1 {\n\t\t\t\t\t\tvar nerr error\n\t\t\t\t\t\tvar contentLength int\n\t\t\t\t\t\tif contentLength, nerr = protocol.ParseContentLength(s.Value); nerr != nil {\n\t\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\t\terr = nerr\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\th.InitContentLengthWithValue(-2)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\th.InitContentLengthWithValue(contentLength)\n\t\t\t\t\t\t\th.SetContentLengthBytes(s.Value)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrConnection) {\n\t\t\t\t\tif bytes.Equal(s.Value, bytestr.StrClose) {\n\t\t\t\t\t\th.SetConnectionClose(true)\n\t\t\t\t\t} else {\n\t\t\t\t\t\th.SetConnectionClose(false)\n\t\t\t\t\t\th.AddArgBytes(s.Key, s.Value, protocol.ArgsHasValue)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\tcase 't':\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrTransferEncoding) {\n\t\t\t\t\tif !bytes.Equal(s.Value, bytestr.StrIdentity) {\n\t\t\t\t\t\th.InitContentLengthWithValue(-1)\n\t\t\t\t\t\th.SetArgBytes(bytestr.StrTransferEncoding, bytestr.StrChunked, protocol.ArgsHasValue)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrTrailer) {\n\t\t\t\t\tif nerr := h.Trailer().SetTrailers(s.Value); nerr != nil {\n\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\terr = nerr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\th.AddArgBytes(s.Key, s.Value, protocol.ArgsHasValue)\n\t}\n\n\tif s.Err != nil && err == nil {\n\t\terr = s.Err\n\t}\n\tif err != nil {\n\t\th.SetConnectionClose(true)\n\t\treturn 0, err\n\t}\n\n\tif h.ContentLength() < 0 {\n\t\th.SetContentLengthBytes(h.ContentLengthBytes()[:0])\n\t}\n\tif !h.IsHTTP11() && !h.ConnectionClose() {\n\t\t// close connection for non-http/1.1 request unless 'Connection: keep-alive' is set.\n\t\tv := h.PeekArgBytes(bytestr.StrConnection)\n\t\th.SetConnectionClose(!ext.HasHeaderValue(v, bytestr.StrKeepAlive))\n\t}\n\treturn s.HLen, nil\n}\n"
  },
  {
    "path": "pkg/protocol/http1/req/header_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage req\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\nfunc TestRequestHeader_Read(t *testing.T) {\n\ts := \"PUT /foo/bar HTTP/1.1\\r\\nExpect: 100-continue\\r\\nUser-Agent: foo\\r\\nHost: 127.0.0.1\\r\\nConnection: Keep-Alive\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tzr := mock.NewZeroCopyReader(s)\n\trh := protocol.RequestHeader{}\n\tReadHeader(&rh, zr)\n\n\t// firstline\n\tassert.DeepEqual(t, []byte(consts.MethodPut), rh.Method())\n\tassert.DeepEqual(t, []byte(\"/foo/bar\"), rh.RequestURI())\n\tassert.True(t, rh.IsHTTP11())\n\n\t// headers\n\tassert.DeepEqual(t, 5, rh.ContentLength())\n\tassert.DeepEqual(t, []byte(\"foo/bar\"), rh.ContentType())\n\tcount := 0\n\trh.VisitAll(func(key, value []byte) {\n\t\tcount += 1\n\t})\n\tassert.DeepEqual(t, 6, count)\n\tassert.DeepEqual(t, []byte(\"foo\"), rh.UserAgent())\n\tassert.DeepEqual(t, []byte(\"127.0.0.1\"), rh.Host())\n\tassert.DeepEqual(t, []byte(\"100-continue\"), rh.Peek(\"Expect\"))\n}\n\nfunc TestRequestHeader_Peek(t *testing.T) {\n\ts := \"PUT /foo/bar HTTP/1.1\\r\\nExpect: 100-continue\\r\\nUser-Agent: foo\\r\\nHost: 127.0.0.1\\r\\nConnection: Keep-Alive\\r\\nContent-Length: 5\\r\\nTransfer-Encoding: foo\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tzr := mock.NewZeroCopyReader(s)\n\trh := protocol.RequestHeader{}\n\tReadHeader(&rh, zr)\n\tassert.DeepEqual(t, []byte(\"100-continue\"), rh.Peek(\"Expect\"))\n\tassert.DeepEqual(t, []byte(\"127.0.0.1\"), rh.Peek(\"Host\"))\n\tassert.DeepEqual(t, []byte(\"foo\"), rh.Peek(\"User-Agent\"))\n\tassert.DeepEqual(t, []byte(\"Keep-Alive\"), rh.Peek(\"Connection\"))\n\tassert.DeepEqual(t, []byte(\"\"), rh.Peek(\"Content-Length\"))\n\tassert.DeepEqual(t, []byte(\"foo/bar\"), rh.Peek(\"Content-Type\"))\n}\n\nfunc TestRequestHeaderSetGet(t *testing.T) {\n\tt.Parallel()\n\n\th := &protocol.RequestHeader{}\n\th.SetRequestURI(\"/aa/bbb\")\n\th.SetMethod(consts.MethodPost)\n\th.Set(\"foo\", \"bar\")\n\th.Set(\"host\", \"12345\")\n\th.Set(\"content-type\", \"aaa/bbb\")\n\th.Set(\"content-length\", \"1234\")\n\th.Set(\"user-agent\", \"aaabbb\")\n\th.Set(\"referer\", \"axcv\")\n\th.Set(\"baz\", \"xxxxx\")\n\th.Set(\"transfer-encoding\", \"chunked\")\n\th.Set(\"connection\", \"close\")\n\n\texpectRequestHeaderGet(t, h, \"Foo\", \"bar\")\n\texpectRequestHeaderGet(t, h, consts.HeaderHost, \"12345\")\n\texpectRequestHeaderGet(t, h, consts.HeaderContentType, \"aaa/bbb\")\n\texpectRequestHeaderGet(t, h, consts.HeaderContentLength, \"1234\")\n\texpectRequestHeaderGet(t, h, \"USER-AGent\", \"aaabbb\")\n\texpectRequestHeaderGet(t, h, consts.HeaderReferer, \"axcv\")\n\texpectRequestHeaderGet(t, h, \"baz\", \"xxxxx\")\n\texpectRequestHeaderGet(t, h, consts.HeaderTransferEncoding, \"\")\n\texpectRequestHeaderGet(t, h, \"connecTION\", \"close\")\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"unset connection: close\")\n\t}\n\n\tif h.ContentLength() != 1234 {\n\t\tt.Fatalf(\"Unexpected content-length %d. Expected %d\", h.ContentLength(), 1234)\n\t}\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tzw := netpoll.NewWriter(bw)\n\terr := WriteHeader(h, zw)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when writing request header: %s\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error when flushing request header: %s\", err)\n\t}\n\tzw.Flush()\n\tbw.Flush()\n\n\tvar h1 protocol.RequestHeader\n\tbr := bufio.NewReader(w)\n\tzr := mock.ZeroCopyReader{Reader: br}\n\tif err = ReadHeader(&h1, zr); err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading request header: %s\", err)\n\t}\n\n\tif h1.ContentLength() != h.ContentLength() {\n\t\tt.Fatalf(\"Unexpected Content-Length %d. Expected %d\", h1.ContentLength(), h.ContentLength())\n\t}\n\n\texpectRequestHeaderGet(t, &h1, \"Foo\", \"bar\")\n\texpectRequestHeaderGet(t, &h1, \"HOST\", \"12345\")\n\texpectRequestHeaderGet(t, &h1, consts.HeaderContentType, \"aaa/bbb\")\n\texpectRequestHeaderGet(t, &h1, consts.HeaderContentLength, \"1234\")\n\texpectRequestHeaderGet(t, &h1, \"USER-AGent\", \"aaabbb\")\n\texpectRequestHeaderGet(t, &h1, consts.HeaderReferer, \"axcv\")\n\texpectRequestHeaderGet(t, &h1, \"baz\", \"xxxxx\")\n\texpectRequestHeaderGet(t, &h1, consts.HeaderTransferEncoding, \"\")\n\texpectRequestHeaderGet(t, &h1, consts.HeaderConnection, \"close\")\n\tif !h1.ConnectionClose() {\n\t\tt.Fatalf(\"unset connection: close\")\n\t}\n}\n\nfunc TestRequestHeaderCookie(t *testing.T) {\n\tt.Parallel()\n\n\tvar h protocol.RequestHeader\n\th.SetRequestURI(\"/foobar\")\n\th.Set(consts.HeaderHost, \"foobar.com\")\n\n\th.SetCookie(\"foo\", \"bar\")\n\th.SetCookie(\"привет\", \"мир\")\n\n\tif string(h.Cookie(\"foo\")) != \"bar\" {\n\t\tt.Fatalf(\"Unexpected cookie value %q. Expected %q\", h.Cookie(\"foo\"), \"bar\")\n\t}\n\tif string(h.Cookie(\"привет\")) != \"мир\" {\n\t\tt.Fatalf(\"Unexpected cookie value %q. Expected %q\", h.Cookie(\"привет\"), \"мир\")\n\t}\n\n\tw := &bytes.Buffer{}\n\tzw := netpoll.NewWriter(w)\n\tif err := WriteHeader(&h, zw); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tif err := zw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\n\tvar h1 protocol.RequestHeader\n\tbr := bufio.NewReader(w)\n\tzr := mock.ZeroCopyReader{Reader: br}\n\tif err := ReadHeader(&h1, zr); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\n\tif !bytes.Equal(h1.Cookie(\"foo\"), h.Cookie(\"foo\")) {\n\t\tt.Fatalf(\"Unexpected cookie value %q. Expected %q\", h1.Cookie(\"foo\"), h.Cookie(\"foo\"))\n\t}\n\th1.DelCookie(\"foo\")\n\tif len(h1.Cookie(\"foo\")) > 0 {\n\t\tt.Fatalf(\"Unexpected cookie found: %q\", h1.Cookie(\"foo\"))\n\t}\n\tif !bytes.Equal(h1.Cookie(\"привет\"), h.Cookie(\"привет\")) {\n\t\tt.Fatalf(\"Unexpected cookie value %q. Expected %q\", h1.Cookie(\"привет\"), h.Cookie(\"привет\"))\n\t}\n\th1.DelCookie(\"привет\")\n\tif len(h1.Cookie(\"привет\")) > 0 {\n\t\tt.Fatalf(\"Unexpected cookie found: %q\", h1.Cookie(\"привет\"))\n\t}\n\n\th.DelAllCookies()\n\tif len(h.Cookie(\"foo\")) > 0 {\n\t\tt.Fatalf(\"Unexpected cookie found: %q\", h.Cookie(\"foo\"))\n\t}\n\tif len(h.Cookie(\"привет\")) > 0 {\n\t\tt.Fatalf(\"Unexpected cookie found: %q\", h.Cookie(\"привет\"))\n\t}\n}\n\nfunc TestRequestRawHeaders(t *testing.T) {\n\tt.Parallel()\n\n\tkvs := \"hOsT: foobar\\r\\n\" +\n\t\t\"value:  b\\r\\n\" +\n\t\t\"\\r\\n\"\n\tt.Run(\"normalized\", func(t *testing.T) {\n\t\ts := \"GET / HTTP/1.1\\r\\n\" + kvs\n\t\texp := kvs\n\t\tvar h protocol.RequestHeader\n\t\tzr := mock.NewZeroCopyReader(s)\n\t\tif err := ReadHeader(&h, zr); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif string(h.Host()) != \"foobar\" {\n\t\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), \"foobar\")\n\t\t}\n\t\tv2 := h.Peek(\"Value\")\n\t\tif !bytes.Equal(v2, []byte{'b'}) {\n\t\t\tt.Fatalf(\"expecting non empty value. Got %q\", v2)\n\t\t}\n\t\tif raw := h.RawHeaders(); string(raw) != exp {\n\t\t\tt.Fatalf(\"expected header %q, got %q\", exp, raw)\n\t\t}\n\t})\n\tfor _, n := range []int{0, 1, 4, 8} {\n\t\tt.Run(fmt.Sprintf(\"post-%dk\", n), func(t *testing.T) {\n\t\t\tl := 1024 * n\n\t\t\tbody := make([]byte, l)\n\t\t\tfor i := range body {\n\t\t\t\tbody[i] = 'a'\n\t\t\t}\n\t\t\tcl := fmt.Sprintf(\"Content-Length: %d\\r\\n\", l)\n\t\t\ts := \"POST / HTTP/1.1\\r\\n\" + cl + kvs + string(body)\n\t\t\texp := cl + kvs\n\t\t\tvar h protocol.RequestHeader\n\t\t\tzr := mock.NewZeroCopyReader(s)\n\t\t\tif err := ReadHeader(&h, zr); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif string(h.Host()) != \"foobar\" {\n\t\t\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), \"foobar\")\n\t\t\t}\n\t\t\tv2 := h.Peek(\"Value\")\n\t\t\tif !bytes.Equal(v2, []byte{'b'}) {\n\t\t\t\tt.Fatalf(\"expecting non empty value. Got %q\", v2)\n\t\t\t}\n\t\t\tif raw := h.RawHeaders(); string(raw) != exp {\n\t\t\t\tt.Fatalf(\"expected header %q, got %q\", exp, raw)\n\t\t\t}\n\t\t})\n\t}\n\tt.Run(\"http10\", func(t *testing.T) {\n\t\ts := \"GET / HTTP/1.0\\r\\n\" + kvs\n\t\texp := kvs\n\t\tvar h protocol.RequestHeader\n\t\tzr := mock.NewZeroCopyReader(s)\n\t\tif err := ReadHeader(&h, zr); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif string(h.Host()) != \"foobar\" {\n\t\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), \"foobar\")\n\t\t}\n\t\tv2 := h.Peek(\"Value\")\n\t\tif !bytes.Equal(v2, []byte{'b'}) {\n\t\t\tt.Fatalf(\"expecting non empty value. Got %q\", v2)\n\t\t}\n\t\tif raw := h.RawHeaders(); string(raw) != exp {\n\t\t\tt.Fatalf(\"expected header %q, got %q\", exp, raw)\n\t\t}\n\t})\n\tt.Run(\"no-kvs\", func(t *testing.T) {\n\t\ts := \"GET / HTTP/1.1\\r\\n\\r\\n\"\n\t\texp := \"\"\n\t\tvar h protocol.RequestHeader\n\t\th.DisableNormalizing()\n\t\tzr := mock.NewZeroCopyReader(s)\n\t\tif err := ReadHeader(&h, zr); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t\tif string(h.Host()) != \"\" {\n\t\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), \"\")\n\t\t}\n\t\tv1 := h.Peek(\"NoKey\")\n\t\tif len(v1) > 0 {\n\t\t\tt.Fatalf(\"expecting empty value. Got %q\", v1)\n\t\t}\n\t\tif raw := h.RawHeaders(); string(raw) != exp {\n\t\t\tt.Fatalf(\"expected header %q, got %q\", exp, raw)\n\t\t}\n\t})\n}\n\nfunc TestRequestHeaderEmptyValueFromHeader(t *testing.T) {\n\tt.Parallel()\n\n\tvar h1 protocol.RequestHeader\n\th1.SetRequestURI(\"/foo/bar\")\n\th1.SetHost(\"foobar\")\n\th1.Set(\"EmptyValue1\", \"\")\n\th1.Set(\"EmptyValue2\", \" \")\n\ts := h1.String()\n\n\tvar h protocol.RequestHeader\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := ReadHeader(&h, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(h.Host()) != string(h1.Host()) {\n\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), h1.Host())\n\t}\n\tv1 := h.Peek(\"EmptyValue1\")\n\tif len(v1) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v1)\n\t}\n\tv2 := h.Peek(\"EmptyValue2\")\n\tif len(v2) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v2)\n\t}\n}\n\nfunc TestRequestHeaderEmptyValueFromString(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"GET / HTTP/1.1\\r\\n\" +\n\t\t\"EmptyValue1:\\r\\n\" +\n\t\t\"Host: foobar\\r\\n\" +\n\t\t\"EmptyValue2: \\r\\n\" +\n\t\t\"\\r\\n\"\n\tvar h protocol.RequestHeader\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := ReadHeader(&h, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(h.Host()) != \"foobar\" {\n\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), \"foobar\")\n\t}\n\tv1 := h.Peek(\"EmptyValue1\")\n\tif len(v1) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v1)\n\t}\n\tv2 := h.Peek(\"EmptyValue2\")\n\tif len(v2) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v2)\n\t}\n}\n\nfunc expectRequestHeaderGet(t *testing.T, h *protocol.RequestHeader, key, expectedValue string) {\n\tif string(h.Peek(key)) != expectedValue {\n\t\tt.Fatalf(\"Unexpected value for key %q: %q. Expected %q\", key, h.Peek(key), expectedValue)\n\t}\n}\n\nfunc TestRequestHeader_PeekIfExists(t *testing.T) {\n\ts := \"PUT /foo/bar HTTP/1.1\\r\\nExpect: 100-continue\\r\\nexists: \\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\trh := protocol.RequestHeader{}\n\terr := ReadHeader(&rh, mock.NewZeroCopyReader(s))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.DeepEqual(t, []byte{}, rh.Peek(\"exists\"))\n\tassert.DeepEqual(t, []byte(nil), rh.Peek(\"non-exists\"))\n}\n\nfunc TestRequestHeaderError(t *testing.T) {\n\ter := mock.EOFReader{}\n\trh := protocol.RequestHeader{}\n\terr := ReadHeader(&rh, &er)\n\tassert.True(t, errors.Is(err, errs.ErrNothingRead))\n}\n\nfunc TestReadHeader(t *testing.T) {\n\ts := \"P\"\n\tzr := mock.NewZeroCopyReader(s)\n\trh := protocol.RequestHeader{}\n\terr := ReadHeader(&rh, zr)\n\tassert.NotNil(t, err)\n}\n\nfunc TestParseHeaders(t *testing.T) {\n\trh := protocol.RequestHeader{}\n\t_, err := parseHeaders(&rh, []byte{' '})\n\tassert.NotNil(t, err)\n}\n\nfunc TestTryRead(t *testing.T) {\n\trh := protocol.RequestHeader{}\n\ts := \"P\"\n\tzr := mock.NewZeroCopyReader(s)\n\terr := tryReadWithLimit(&rh, zr, 0, 0)\n\tassert.Nil(t, err)\n}\n\nfunc TestReadHeaderWithLimit(t *testing.T) {\n\tvalidRequest := \"GET /path HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\"\n\tzr := mock.NewZeroCopyReader(validRequest)\n\th := &protocol.RequestHeader{}\n\n\terr := ReadHeaderWithLimit(h, zr, 0)\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, string(h.Method()), \"GET\")\n}\n\nfunc TestReadHeaderWithLimitExceeded(t *testing.T) {\n\tlargeRequest := \"GET /path HTTP/1.1\\r\\nHost: example.com\\r\\nLarge-Header: \" +\n\t\tstrings.Repeat(\"x\", 100) + \"\\r\\n\\r\\n\"\n\tzr := mock.NewZeroCopyReader(largeRequest)\n\th := &protocol.RequestHeader{}\n\n\terr := ReadHeaderWithLimit(h, zr, 50)\n\tassert.NotNil(t, err)\n}\n\nfunc TestParseFirstLine(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    []byte\n\t\tmethod   string\n\t\turi      string\n\t\tprotocol string\n\t\terr      error\n\t}{\n\t\t{\n\t\t\tname:     \"case: normal\",\n\t\t\tinput:    []byte(\"GET /path/to/resource HTTP/1.0\\r\\n\"),\n\t\t\tmethod:   \"GET\",\n\t\t\turi:      \"/path/to/resource\",\n\t\t\tprotocol: \"HTTP/1.0\",\n\t\t},\n\t\t{\n\t\t\tname:  \"case: empty uri\",\n\t\t\tinput: []byte(\"GET  HTTP/1.1\\r\\n\"),\n\t\t\terr:   errMalformedHTTPRequest,\n\t\t},\n\t\t{\n\t\t\tname:     \"case: unknown protocol should use HTTP/1.0\",\n\t\t\tinput:    []byte(\"POST /path/to/resource HTTP/1.2\\r\\n\"),\n\t\t\tmethod:   \"POST\",\n\t\t\turi:      \"/path/to/resource\",\n\t\t\tprotocol: \"HTTP/1.0\",\n\t\t},\n\t\t{\n\t\t\tname:  \"case: invalid protocol\",\n\t\t\tinput: []byte(\"POST /path/to/resource XTTP/1.1\\r\\n\"),\n\t\t\terr:   errMalformedHTTPRequest,\n\t\t},\n\t\t{\n\t\t\tname:  \"case: input too large\",\n\t\t\tinput: make([]byte, 9<<10),\n\t\t\terr:   errMalformedHTTPRequest,\n\t\t},\n\t\t{\n\t\t\tname:  \"case: method invalid\",\n\t\t\tinput: []byte(\"< / HTTP/1.\"),\n\t\t\terr:   errMalformedHTTPRequest,\n\t\t},\n\t\t{\n\t\t\tname:  \"case: need more err\",\n\t\t\tinput: []byte(\"GET / HTTP/1.\"),\n\t\t\terr:   errs.ErrNeedMore,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\th := &protocol.RequestHeader{}\n\t\t_, err := parseFirstLine(h, tc.input)\n\t\tif tc.err != nil {\n\t\t\tassert.Assert(t, errors.Is(err, tc.err), tc.name, err)\n\t\t\tcontinue\n\t\t}\n\t\tassert.Assert(t, err == nil, tc.name, err)\n\t\tif string(h.Method()) != tc.method ||\n\t\t\tstring(h.RequestURI()) != tc.uri ||\n\t\t\th.GetProtocol() != tc.protocol {\n\t\t\tt.Fatal(tc.name, \"got\", h.String())\n\t\t}\n\n\t}\n}\n\nfunc TestParse(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    []byte\n\t\texpected int\n\t\twantErr  bool\n\t}{\n\t\t// normal test\n\t\t{\n\t\t\tname:     \"normal\",\n\t\t\tinput:    []byte(\"GET /path/to/resource HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\"),\n\t\t\texpected: len([]byte(\"GET /path/to/resource HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\")),\n\t\t\twantErr:  false,\n\t\t},\n\t\t// parseFirstLine error\n\t\t{\n\t\t\tname:     \"parseFirstLine error\",\n\t\t\tinput:    []byte(\"INVALID_LINE\\r\\nHost: example.com\\r\\n\\r\\n\"),\n\t\t\texpected: 0,\n\t\t\twantErr:  true,\n\t\t},\n\t\t// ext.ReadRawHeaders error\n\t\t{\n\t\t\tname:     \"ext.ReadRawHeaders error\",\n\t\t\tinput:    []byte(\"GET /path/to/resource HTTP/1.1\\r\\nINVALID_HEADER\\r\\n\\r\\n\"),\n\t\t\texpected: 0,\n\t\t\twantErr:  true,\n\t\t},\n\t\t// parseHeaders error\n\t\t{\n\t\t\tname:     \"parseHeaders error\",\n\t\t\tinput:    []byte(\"GET /path/to/resource HTTP/1.1\\r\\nHost: example.com\\r\\nINVALID_HEADER\\r\\n\"),\n\t\t\texpected: 0,\n\t\t\twantErr:  true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\theader := &protocol.RequestHeader{}\n\t\t\tbytesRead, err := parse(header, tc.input)\n\t\t\tif (err != nil) != tc.wantErr {\n\t\t\t\tt.Errorf(\"Expected error: %v, but got: %v\", tc.wantErr, err)\n\t\t\t}\n\t\t\tif bytesRead != tc.expected {\n\t\t\t\tt.Errorf(\"Expected bytes read: %d, but got: %d\", tc.expected, bytesRead)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/http1/req/request.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage req\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/ext\"\n)\n\nvar (\n\terrRequestHostRequired = errs.NewPublic(\"missing required Host header in request\")\n\terrGetOnly             = errs.NewPublic(\"non-GET request received\")\n\terrBodyTooLarge        = errs.New(errs.ErrBodyTooLarge, errs.ErrorTypePublic, \"http1/req\")\n\terrHeaderTooLarge      = errs.New(errs.ErrHeaderTooLarge, errs.ErrorTypePublic, \"http1/req\")\n)\n\ntype h1Request struct {\n\t*protocol.Request\n}\n\n// String returns request representation.\n//\n// Returns error message instead of request representation on error.\n//\n// Use Write instead of String for performance-critical code.\nfunc (h1Req *h1Request) String() string {\n\tw := bytebufferpool.Get()\n\tzw := network.NewWriter(w)\n\tif err := Write(h1Req.Request, zw); err != nil {\n\t\treturn err.Error()\n\t}\n\tif err := zw.Flush(); err != nil {\n\t\treturn err.Error()\n\t}\n\ts := string(w.B)\n\tbytebufferpool.Put(w)\n\treturn s\n}\n\nfunc GetHTTP1Request(req *protocol.Request) fmt.Stringer {\n\treturn &h1Request{req}\n}\n\n// ReadHeaderAndLimitBody reads request from the given r, limiting the body size.\n//\n// If maxBodySize > 0 and the body size exceeds maxBodySize,\n// then errBodyTooLarge is returned.\n//\n// RemoveMultipartFormFiles or Reset must be called after\n// reading multipart/form-data request in order to delete temporarily\n// uploaded files.\n//\n// If MayContinue returns true, the caller must:\n//\n//   - Either send StatusExpectationFailed response if request headers don't\n//     satisfy the caller.\n//   - Or send StatusContinue response before reading request body\n//     with ContinueReadBody.\n//   - Or close the connection.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc ReadHeaderAndLimitBody(req *protocol.Request, r network.Reader, maxBodySize int, preParse ...bool) error {\n\tvar parse bool\n\tif len(preParse) == 0 {\n\t\tparse = true\n\t} else {\n\t\tparse = preParse[0]\n\t}\n\treq.ResetSkipHeader()\n\n\tif err := ReadHeader(&req.Header, r); err != nil {\n\t\treturn err\n\t}\n\n\treturn ReadLimitBody(req, r, maxBodySize, false, parse)\n}\n\n// Read reads request (including body) from the given r.\n//\n// RemoveMultipartFormFiles or Reset must be called after\n// reading multipart/form-data request in order to delete temporarily\n// uploaded files.\n//\n// If MayContinue returns true, the caller must:\n//\n//   - Either send StatusExpectationFailed response if request headers don't\n//     satisfy the caller.\n//   - Or send StatusContinue response before reading request body\n//     with ContinueReadBody.\n//   - Or close the connection.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc Read(req *protocol.Request, r network.Reader, preParse ...bool) error {\n\treturn ReadHeaderAndLimitBody(req, r, 0, preParse...)\n}\n\n// Write writes request to w.\n//\n// Write doesn't flush request to w for performance reasons.\n//\n// See also WriteTo.\nfunc Write(req *protocol.Request, w network.Writer) error {\n\treturn write(req, w, false)\n}\n\n// ProxyWrite is like Write but writes the request in the form\n// expected by an HTTP proxy. In particular, ProxyWrite writes the\n// initial Request-URI line of the request with an absolute URI, per\n// section 5.3 of RFC 7230, including the scheme and host.\nfunc ProxyWrite(req *protocol.Request, w network.Writer) error {\n\treturn write(req, w, true)\n}\n\n// write writes request to w.\n// It supports proxy situation.\nfunc write(req *protocol.Request, w network.Writer, usingProxy bool) error {\n\tif len(req.Header.Host()) == 0 || req.IsURIParsed() {\n\t\turi := req.URI()\n\t\thost := uri.Host()\n\t\tif len(host) == 0 {\n\t\t\treturn errRequestHostRequired\n\t\t}\n\n\t\tif len(req.Header.Host()) == 0 {\n\t\t\treq.Header.SetHostBytes(host)\n\t\t}\n\n\t\truri := uri.RequestURI()\n\t\tif bytes.Equal(req.Method(), bytestr.StrConnect) {\n\t\t\truri = uri.Host()\n\t\t} else if usingProxy {\n\t\t\truri = uri.FullURI()\n\t\t}\n\n\t\treq.Header.SetRequestURIBytes(ruri)\n\n\t\tif len(uri.Username()) > 0 {\n\t\t\t// RequestHeader.SetBytesKV only uses RequestHeader.bufKV.key\n\t\t\t// So we are free to use RequestHeader.bufKV.value as a scratch pad for\n\t\t\t// the base64 encoding.\n\t\t\tnl := len(uri.Username()) + len(uri.Password()) + 1\n\t\t\tnb := nl + len(bytestr.StrBasicSpace)\n\t\t\ttl := nb + base64.StdEncoding.EncodedLen(nl)\n\n\t\t\treq.Header.InitBufValue(tl)\n\t\t\tbuf := req.Header.GetBufValue()[:0]\n\t\t\tbuf = append(buf, uri.Username()...)\n\t\t\tbuf = append(buf, bytestr.StrColon...)\n\t\t\tbuf = append(buf, uri.Password()...)\n\t\t\tbuf = append(buf, bytestr.StrBasicSpace...)\n\t\t\tbase64.StdEncoding.Encode(buf[nb:tl], buf[:nl])\n\t\t\treq.Header.SetBytesKV(bytestr.StrAuthorization, buf[nl:tl])\n\t\t}\n\t}\n\n\tif req.IsBodyStream() {\n\t\treturn writeBodyStream(req, w)\n\t}\n\n\tbody := req.BodyBytes()\n\terr := handleMultipart(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error when handle multipart: %s\", err)\n\t}\n\tif req.OnlyMultipartForm() {\n\t\tm, _ := req.MultipartForm() // req.multipartForm != nil\n\t\tbody, err = protocol.MarshalMultipartForm(m, req.MultipartFormBoundary())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error when marshaling multipart form: %s\", err)\n\t\t}\n\t\treq.Header.SetMultipartFormBoundary(req.MultipartFormBoundary())\n\t}\n\n\thasBody := false\n\tif len(body) == 0 {\n\t\tbody = req.PostArgString()\n\t}\n\tif len(body) != 0 || !req.Header.IgnoreBody() {\n\t\thasBody = true\n\t\treq.Header.SetContentLength(len(body))\n\t}\n\n\theader := req.Header.Header()\n\tif _, err := w.WriteBinary(header); err != nil {\n\t\treturn err\n\t}\n\n\t// Write body\n\tif hasBody {\n\t\tw.WriteBinary(body) //nolint:errcheck\n\t} else if len(body) > 0 {\n\t\treturn fmt.Errorf(\"non-zero body for non-POST request. body=%q\", body)\n\t}\n\treturn nil\n}\n\n// ContinueReadBodyStream reads request body in stream if request header contains\n// 'Expect: 100-continue'.\n//\n// The caller must send StatusContinue response before calling this method.\n//\n// If maxBodySize > 0 and the body size exceeds maxBodySize,\n// then errBodyTooLarge is returned.\nfunc ContinueReadBodyStream(req *protocol.Request, zr network.Reader, maxBodySize int, preParseMultipartForm ...bool) error {\n\tvar err error\n\tcontentLength := req.Header.ContentLength()\n\tif contentLength > 0 {\n\t\tif len(preParseMultipartForm) == 0 || preParseMultipartForm[0] {\n\t\t\t// Pre-read multipart form data of known length.\n\t\t\t// This way we limit memory usage for large file uploads, since their contents\n\t\t\t// is streamed into temporary files if file size exceeds defaultMaxInMemoryFileSize.\n\t\t\treq.SetMultipartFormBoundary(string(req.Header.MultipartFormBoundary()))\n\t\t\tif len(req.MultipartFormBoundary()) > 0 && len(req.Header.PeekContentEncoding()) == 0 {\n\t\t\t\terr := protocol.ParseMultipartForm(zr.(io.Reader), req, contentLength, consts.DefaultMaxInMemoryFileSize)\n\t\t\t\tif err != nil {\n\t\t\t\t\treq.Reset()\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif contentLength == -2 {\n\t\t// identity body has no sense for http requests, since\n\t\t// the end of body is determined by connection close.\n\t\t// So just ignore request body for requests without\n\t\t// 'Content-Length' and 'Transfer-Encoding' headers.\n\n\t\t// refer to https://tools.ietf.org/html/rfc7230#section-3.3.2\n\t\tif !req.Header.IgnoreBody() {\n\t\t\treq.Header.SetContentLength(0)\n\t\t}\n\t\treturn nil\n\t}\n\n\tbodyBuf := req.BodyBuffer()\n\tbodyBuf.Reset()\n\tbodyBuf.B, err = ext.ReadBodyWithStreaming(zr, contentLength, maxBodySize, bodyBuf.B)\n\tif err != nil {\n\t\tif errors.Is(err, errs.ErrBodyTooLarge) {\n\t\t\treq.Header.SetContentLength(contentLength)\n\t\t\treq.ConstructBodyStream(bodyBuf, ext.AcquireBodyStream(bodyBuf, zr, req.Header.Trailer(), contentLength))\n\n\t\t\treturn nil\n\t\t}\n\t\tif errors.Is(err, errs.ErrChunkedStream) {\n\t\t\treq.ConstructBodyStream(bodyBuf, ext.AcquireBodyStream(bodyBuf, zr, req.Header.Trailer(), contentLength))\n\t\t\treturn nil\n\t\t}\n\t\treq.Reset()\n\t\treturn err\n\t}\n\n\treq.ConstructBodyStream(bodyBuf, ext.AcquireBodyStream(bodyBuf, zr, req.Header.Trailer(), contentLength))\n\treturn nil\n}\n\nfunc ContinueReadBody(req *protocol.Request, r network.Reader, maxBodySize int, preParseMultipartForm ...bool) error {\n\tvar err error\n\tcontentLength := req.Header.ContentLength()\n\tif contentLength > 0 {\n\t\tif maxBodySize > 0 && contentLength > maxBodySize {\n\t\t\treturn errBodyTooLarge\n\t\t}\n\n\t\tif len(preParseMultipartForm) == 0 || preParseMultipartForm[0] {\n\t\t\t// Pre-read multipart form data of known length.\n\t\t\t// This way we limit memory usage for large file uploads, since their contents\n\t\t\t// is streamed into temporary files if file size exceeds defaultMaxInMemoryFileSize.\n\t\t\treq.SetMultipartFormBoundary(string(req.Header.MultipartFormBoundary()))\n\t\t\tif len(req.MultipartFormBoundary()) > 0 && len(req.Header.PeekContentEncoding()) == 0 {\n\t\t\t\terr := protocol.ParseMultipartForm(r.(io.Reader), req, contentLength, consts.DefaultMaxInMemoryFileSize)\n\t\t\t\tif err != nil {\n\t\t\t\t\treq.Reset()\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// This optimization is just suitable for ping-pong case and the ext.ReadBody is\n\t\t// a common function, so we just handle this situation before ext.ReadBody\n\t\tbuf, err := r.Peek(contentLength)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Skip(contentLength) // nolint: errcheck\n\t\treq.SetBodyRaw(buf)\n\t\treturn nil\n\t}\n\n\tif contentLength == -2 {\n\t\t// identity body has no sense for http requests, since\n\t\t// the end of body is determined by connection close.\n\t\t// So just ignore request body for requests without\n\t\t// 'Content-Length' and 'Transfer-Encoding' headers.\n\n\t\t// refer to https://tools.ietf.org/html/rfc7230#section-3.3.2\n\t\tif !req.Header.IgnoreBody() {\n\t\t\treq.Header.SetContentLength(0)\n\t\t}\n\t\treturn nil\n\t}\n\n\tbodyBuf := req.BodyBuffer()\n\tbodyBuf.Reset()\n\tbodyBuf.B, err = ext.ReadBody(r, contentLength, maxBodySize, bodyBuf.B)\n\tif err != nil {\n\t\treq.Reset()\n\t\treturn err\n\t}\n\n\tif req.Header.ContentLength() == -1 {\n\t\terr = ext.ReadTrailer(req.Header.Trailer(), r)\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treq.Header.SetContentLength(len(bodyBuf.B))\n\treturn nil\n}\n\nfunc ReadBodyStream(req *protocol.Request, zr network.Reader, maxBodySize int, getOnly, preParseMultipartForm bool) error {\n\tif getOnly && !req.Header.IsGet() {\n\t\treturn errGetOnly\n\t}\n\n\tif req.MayContinue() {\n\t\t// 'Expect: 100-continue' header found. Let the caller deciding\n\t\t// whether to read request body or\n\t\t// to return StatusExpectationFailed.\n\t\treturn nil\n\t}\n\n\treturn ContinueReadBodyStream(req, zr, maxBodySize, preParseMultipartForm)\n}\n\nfunc ReadLimitBody(req *protocol.Request, r network.Reader, maxBodySize int, getOnly, preParseMultipartForm bool) error {\n\t// Do not reset the request here - the caller must reset it before\n\t// calling this method.\n\tif getOnly && !req.Header.IsGet() {\n\t\treturn errGetOnly\n\t}\n\n\tif req.MayContinue() {\n\t\t// 'Expect: 100-continue' header found. Let the caller deciding\n\t\t// whether to read request body or\n\t\t// to return StatusExpectationFailed.\n\t\treturn nil\n\t}\n\n\treturn ContinueReadBody(req, r, maxBodySize, preParseMultipartForm)\n}\n\nfunc writeBodyStream(req *protocol.Request, w network.Writer) error {\n\tvar err error\n\n\tcontentLength := req.Header.ContentLength()\n\tif contentLength < 0 {\n\t\tlrSize := ext.LimitedReaderSize(req.BodyStream())\n\t\tif lrSize >= 0 {\n\t\t\tcontentLength = int(lrSize)\n\t\t\tif int64(contentLength) != lrSize {\n\t\t\t\tcontentLength = -1\n\t\t\t}\n\t\t\tif contentLength >= 0 {\n\t\t\t\treq.Header.SetContentLength(contentLength)\n\t\t\t}\n\t\t}\n\t}\n\tif contentLength >= 0 {\n\t\tif err = WriteHeader(&req.Header, w); err == nil {\n\t\t\terr = ext.WriteBodyFixedSize(w, req.BodyStream(), int64(contentLength))\n\t\t}\n\t} else {\n\t\treq.Header.SetContentLength(-1)\n\t\terr = WriteHeader(&req.Header, w)\n\t\tif err == nil {\n\t\t\terr = ext.WriteBodyChunked(w, req.BodyStream())\n\t\t}\n\t\tif err == nil {\n\t\t\terr = ext.WriteTrailer(req.Header.Trailer(), w)\n\t\t}\n\t}\n\terr1 := req.CloseBodyStream()\n\tif err == nil {\n\t\terr = err1\n\t}\n\treturn err\n}\n\nfunc handleMultipart(req *protocol.Request) error {\n\tif len(req.MultipartFiles()) == 0 && len(req.MultipartFields()) == 0 {\n\t\treturn nil\n\t}\n\tvar err error\n\tbodyBuffer := &bytes.Buffer{}\n\tw := multipart.NewWriter(bodyBuffer)\n\tif len(req.MultipartFiles()) > 0 {\n\t\tfor _, f := range req.MultipartFiles() {\n\t\t\tif f.Reader != nil {\n\t\t\t\terr = protocol.WriteMultipartFormFile(w, f.ParamName, f.Name, f.Reader)\n\t\t\t} else {\n\t\t\t\terr = protocol.AddFile(w, f.ParamName, f.Name)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(req.MultipartFields()) > 0 {\n\t\tfor _, mf := range req.MultipartFields() {\n\t\t\tif err = protocol.AddMultipartFormField(w, mf); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treq.Header.Set(consts.HeaderContentType, w.FormDataContentType())\n\tif err = w.Close(); err != nil {\n\t\treturn err\n\t}\n\n\tr := multipart.NewReader(bodyBuffer, w.Boundary())\n\tf, err := r.ReadForm(int64(bodyBuffer.Len()))\n\tif err != nil {\n\t\treturn err\n\t}\n\tprotocol.SetMultipartFormWithBoundary(req, f, w.Boundary())\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/protocol/http1/req/request_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage req\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"mime/multipart\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/common/compress\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/ext\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\nfunc TestRequestContinueReadBody(t *testing.T) {\n\tt.Parallel()\n\ts := \"PUT /foo/bar HTTP/1.1\\r\\nExpect: 100-continue\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tzr := mock.NewZeroCopyReader(s)\n\n\tvar r protocol.Request\n\tif err := Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tif err := ContinueReadBody(&r, zr, 0, true); err != nil {\n\t\tt.Fatalf(\"error when reading request body: %s\", err)\n\t}\n\tbody := r.Body()\n\tif string(body) != \"abcde\" {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, \"abcde\")\n\t}\n\n\ttail, err := zr.Peek(zr.Len())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(tail) != \"f4343\" {\n\t\tt.Fatalf(\"unexpected tail %q. Expecting %q\", tail, \"f4343\")\n\t}\n}\n\nfunc TestRequestReadNoBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Request\n\n\ts := \"GET / HTTP/1.1\\r\\n\\r\\n\"\n\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tr.SetHost(\"foobar\")\n\theaderStr := r.Header.String()\n\tif strings.Contains(headerStr, \"Content-Length: \") {\n\t\tt.Fatalf(\"unexpected Content-Length\")\n\t}\n}\n\nfunc TestRequestRead(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Request\n\n\ts := \"POST / HTTP/1.1\\r\\n\\r\\n\"\n\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tr.SetHost(\"foobar\")\n\theaderStr := r.Header.String()\n\tif !strings.Contains(headerStr, \"Content-Length: \") {\n\t\tt.Fatalf(\"should contain Content-Length\")\n\t}\n\tcLen := r.Header.Peek(consts.HeaderContentLength)\n\tif string(cLen) != \"0\" {\n\t\tt.Fatalf(\"unexpected Content-Length: %s, Expecting 0\", string(cLen))\n\t}\n}\n\nfunc TestRequestReadNoBodyStreaming(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Request\n\tr.Header.SetContentLength(-2)\n\tr.Header.SetMethod(\"GET\")\n\n\ts := \"\"\n\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := ContinueReadBodyStream(&r, zr, 2048, true); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tr.SetHost(\"foobar\")\n\theaderStr := r.Header.String()\n\tif strings.Contains(headerStr, \"Content-Length: \") {\n\t\tt.Fatalf(\"unexpected Content-Length\")\n\t}\n}\n\nfunc TestRequestReadStreaming(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Request\n\tr.Header.SetContentLength(-2)\n\tr.Header.SetMethod(\"POST\")\n\n\ts := \"\"\n\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := ContinueReadBodyStream(&r, zr, 2048, true); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tr.SetHost(\"foobar\")\n\theaderStr := r.Header.String()\n\tif !strings.Contains(headerStr, \"Content-Length: \") {\n\t\tt.Fatalf(\"should contain Content-Length\")\n\t}\n\tcLen := r.Header.Peek(consts.HeaderContentLength)\n\tif string(cLen) != \"0\" {\n\t\tt.Fatalf(\"unexpected Content-Length: %s, Expecting 0\", string(cLen))\n\t}\n}\n\nfunc TestMethodAndPathAndQueryString(t *testing.T) {\n\ts := \"PUT /foo/bar?query=1 HTTP/1.1\\r\\nExpect: 100-continue\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tzr := mock.NewZeroCopyReader(s)\n\n\tvar r protocol.Request\n\tif err := Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(r.RequestURI()) != \"/foo/bar?query=1\" {\n\t\tt.Fatalf(\"unexpected request uri %s. Expecting %s\", r.RequestURI(), \"/foo/bar?query=1\")\n\t}\n\tif string(r.Method()) != \"PUT\" {\n\t\tt.Fatalf(\"unexpected method %s. Expecting %s\", r.Header.Method(), \"PUT\")\n\t}\n\n\tif string(r.Path()) != \"/foo/bar\" {\n\t\tt.Fatalf(\"unexpected uri path %s. Expecting %s\", r.URI().Path(), \"/foo/bar\")\n\t}\n\tif string(r.QueryString()) != \"query=1\" {\n\t\tt.Fatalf(\"unexpected query string %s. Expecting %s\", r.URI().QueryString(), \"query=1\")\n\t}\n}\n\nfunc TestRequestSuccess(t *testing.T) {\n\tt.Parallel()\n\n\t// empty method, user-agent and body\n\ttestRequestSuccess(t, \"\", \"/foo/bar\", \"google.com\", \"\", \"\", consts.MethodGet)\n\n\t// non-empty user-agent\n\ttestRequestSuccess(t, consts.MethodGet, \"/foo/bar\", \"google.com\", \"MSIE\", \"\", consts.MethodGet)\n\n\t// non-empty method\n\ttestRequestSuccess(t, consts.MethodHead, \"/aaa\", \"fobar\", \"\", \"\", consts.MethodHead)\n\n\t// POST method with body\n\ttestRequestSuccess(t, consts.MethodPost, \"/bbb\", \"aaa.com\", \"Chrome aaa\", \"post body\", consts.MethodPost)\n\n\t// PUT method with body\n\ttestRequestSuccess(t, consts.MethodPut, \"/aa/bb\", \"a.com\", \"aaa\", \"put body\", consts.MethodPut)\n\n\t// only host is set\n\ttestRequestSuccess(t, \"\", \"\", \"gooble.com\", \"\", \"\", consts.MethodGet)\n\n\t// get with body\n\ttestRequestSuccess(t, consts.MethodGet, \"/foo/bar\", \"aaa.com\", \"\", \"foobar\", consts.MethodGet)\n}\n\nfunc TestRequestMultipartFormBoundary(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: multipart/form-data; boundary=foobar\\r\\n\\r\\n\", \"foobar\")\n\n\t// incorrect content-type\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\", \"\")\n\n\t// empty boundary\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: multipart/form-data; boundary=\\r\\n\\r\\n\", \"\")\n\n\t// missing boundary\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: multipart/form-data\\r\\n\\r\\n\", \"\")\n\n\t// boundary after other content-type params\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: multipart/form-data;   foo=bar;   boundary=--aaabb  \\r\\n\\r\\n\", \"--aaabb\")\n\n\t// quoted boundary\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: multipart/form-data; boundary=\\\"foobar\\\"\\r\\n\\r\\n\", \"foobar\")\n\n\tvar h protocol.RequestHeader\n\th.SetMultipartFormBoundary(\"foobarbaz\")\n\tb := h.MultipartFormBoundary()\n\tif string(b) != \"foobarbaz\" {\n\t\tt.Fatalf(\"unexpected boundary %q. Expecting %q\", b, \"foobarbaz\")\n\t}\n}\n\nfunc testRequestSuccess(t *testing.T, method, requestURI, host, userAgent, body, expectedMethod string) {\n\tvar req protocol.Request\n\n\treq.Header.SetMethod(method)\n\treq.Header.SetRequestURI(requestURI)\n\treq.Header.Set(consts.HeaderHost, host)\n\treq.Header.Set(consts.HeaderUserAgent, userAgent)\n\treq.SetBody([]byte(body))\n\n\tcontentType := \"foobar\"\n\tif method == consts.MethodPost {\n\t\treq.Header.Set(consts.HeaderContentType, contentType)\n\t}\n\n\tw := &bytes.Buffer{}\n\tzw := netpoll.NewWriter(w)\n\terr := Write(&req, zw)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when calling Write(): %s\", err)\n\t}\n\n\tif err = zw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error when flushing bufio.Writer: %s\", err)\n\t}\n\n\tvar req1 protocol.Request\n\tbr := bufio.NewReader(w)\n\tzr := netpoll.NewReader(br)\n\tif err = Read(&req1, zr); err != nil {\n\t\tt.Fatalf(\"Unexpected error when calling Read(): %s\", err)\n\t}\n\tif string(req1.Header.Method()) != expectedMethod {\n\t\tt.Fatalf(\"Unexpected method: %q. Expected %q\", req1.Header.Method(), expectedMethod)\n\t}\n\tif len(requestURI) == 0 {\n\t\trequestURI = \"/\"\n\t}\n\tif string(req1.Header.RequestURI()) != requestURI {\n\t\tt.Fatalf(\"Unexpected RequestURI: %q. Expected %q\", req1.Header.RequestURI(), requestURI)\n\t}\n\tif string(req1.Header.Peek(consts.HeaderHost)) != host {\n\t\tt.Fatalf(\"Unexpected host: %q. Expected %q\", req1.Header.Peek(consts.HeaderHost), host)\n\t}\n\tif string(req1.Header.Peek(consts.HeaderUserAgent)) != userAgent {\n\t\tt.Fatalf(\"Unexpected user-agent: %q. Expected %q\", req1.Header.Peek(consts.HeaderUserAgent), userAgent)\n\t}\n\tif !bytes.Equal(req1.Body(), []byte(body)) {\n\t\tt.Fatalf(\"Unexpected body: %q. Expected %q\", req1.Body(), body)\n\t}\n\n\tif method == consts.MethodPost && string(req1.Header.Peek(consts.HeaderContentType)) != contentType {\n\t\tt.Fatalf(\"Unexpected content-type: %q. Expected %q\", req1.Header.Peek(consts.HeaderContentType), contentType)\n\t}\n}\n\nfunc TestRequestWriteError(t *testing.T) {\n\tt.Parallel()\n\n\t// no host\n\ttestRequestWriteError(t, \"\", \"/foo/bar\", \"\", \"\", \"\")\n}\n\nfunc TestRequestPostArgsSuccess(t *testing.T) {\n\tt.Parallel()\n\n\tvar req protocol.Request\n\n\ttestRequestPostArgsSuccess(t, &req, \"POST / HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nContent-Length: 0\\r\\n\\r\\n\", 0, \"foo=\", \"=\")\n\n\ttestRequestPostArgsSuccess(t, &req, \"POST / HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nContent-Length: 18\\r\\n\\r\\nfoo&b%20r=b+z=&qwe\", 3, \"foo=\", \"b r=b z=\", \"qwe=\")\n}\n\nfunc testRequestPostArgsSuccess(t *testing.T, req *protocol.Request, s string, expectedArgsLen int, expectedArgs ...string) {\n\tr := bytes.NewBufferString(s)\n\tzr := netpoll.NewReader(r)\n\terr := Read(req, zr)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading %q: %s\", s, err)\n\t}\n\n\targs := req.PostArgs()\n\tif args.Len() != expectedArgsLen {\n\t\tt.Fatalf(\"Unexpected args len %d. Expected %d for %q\", args.Len(), expectedArgsLen, s)\n\t}\n\tfor _, x := range expectedArgs {\n\t\ttmp := strings.SplitN(x, \"=\", 2)\n\t\tk := tmp[0]\n\t\tv := tmp[1]\n\t\tvv := string(args.Peek(k))\n\t\tif vv != v {\n\t\t\tt.Fatalf(\"Unexpected value for key %q: %q. Expected %q for %q\", k, vv, v, s)\n\t\t}\n\t}\n}\n\nfunc TestRequestPostArgsBodyStream(t *testing.T) {\n\tvar req protocol.Request\n\ts := \"POST / HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nContent-Length: 8196\\r\\n\\r\\n\"\n\tcontentB := make([]byte, 8192)\n\tfor i := 0; i < len(contentB); i++ {\n\t\tcontentB[i] = 'a'\n\t}\n\tcontent := string(contentB)\n\trequestString := s + url.Values{\"key\": []string{content}}.Encode()\n\n\tr := bytes.NewBufferString(requestString)\n\tzr := netpoll.NewReader(r)\n\tif err := ReadHeader(&req.Header, zr); err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading header %q: %s\", s, err)\n\t}\n\n\terr := ReadBodyStream(&req, zr, 1024*4, false, false)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading bodystream %q: %s\", s, err)\n\t}\n\tif string(req.PostArgs().Peek(\"key\")) != content {\n\t\tassert.DeepEqual(t, content, string(req.PostArgs().Peek(\"key\")))\n\t}\n}\n\nfunc testRequestWriteError(t *testing.T, method, requestURI, host, userAgent, body string) {\n\tvar req protocol.Request\n\n\treq.Header.SetMethod(method)\n\treq.Header.SetRequestURI(requestURI)\n\treq.Header.Set(consts.HeaderHost, host)\n\treq.Header.Set(consts.HeaderUserAgent, userAgent)\n\treq.SetBody([]byte(body))\n\n\tw := &bytebufferpool.ByteBuffer{}\n\tzw := netpoll.NewWriter(w)\n\terr := Write(&req, zw)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when writing request=%#v\", &req)\n\t}\n}\n\nfunc TestChunkedUnexpectedEOF(t *testing.T) {\n\treader := &mock.EOFReader{}\n\n\t_, err := ext.ReadBody(reader, -1, 0, nil)\n\tif err != io.ErrUnexpectedEOF {\n\t\tassert.DeepEqual(t, io.ErrUnexpectedEOF, err)\n\t}\n\n\tvar pool bytebufferpool.Pool\n\tvar req1 protocol.Request\n\tbs := ext.AcquireBodyStream(pool.Get(), reader, req1.Header.Trailer(), -1)\n\tbyteSlice := make([]byte, 4096)\n\t_, err = bs.Read(byteSlice)\n\tif err != io.ErrUnexpectedEOF {\n\t\tassert.DeepEqual(t, io.ErrUnexpectedEOF, err)\n\t}\n}\n\nfunc TestReadBodyChunked(t *testing.T) {\n\tt.Parallel()\n\n\t// zero-size body\n\ttestReadBodyChunked(t, 0)\n\n\t// small-size body\n\ttestReadBodyChunked(t, 5)\n\n\t// medium-size body\n\ttestReadBodyChunked(t, 43488)\n\n\t// big body\n\ttestReadBodyChunked(t, 3*1024*1024)\n\n\t// smaller body after big one\n\ttestReadBodyChunked(t, 12343)\n}\n\nfunc TestReadBodyFixedSize(t *testing.T) {\n\tt.Parallel()\n\n\t// zero-size body\n\ttestReadBodyFixedSize(t, 0)\n\n\t// small-size body\n\ttestReadBodyFixedSize(t, 3)\n\n\t// medium-size body\n\ttestReadBodyFixedSize(t, 1024)\n\n\t// large-size body\n\ttestReadBodyFixedSize(t, 1024*1024)\n\n\t// smaller body after big one\n\ttestReadBodyFixedSize(t, 34345)\n}\n\nfunc TestRequestWriteRequestURINoHost(t *testing.T) {\n\tt.Parallel()\n\n\tvar req protocol.Request\n\treq.Header.SetRequestURI(\"http://user:pass@google.com/foo/bar?baz=aaa\")\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\tif err := Write(&req, zw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tif err := zw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tvar req1 protocol.Request\n\tbr := bufio.NewReader(&w)\n\tzr := netpoll.NewReader(br)\n\tif err := Read(&req1, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(req1.Header.Host()) != \"google.com\" {\n\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", req1.Header.Host(), \"google.com\")\n\t}\n\tif string(req.Header.RequestURI()) != \"/foo/bar?baz=aaa\" {\n\t\tt.Fatalf(\"unexpected requestURI: %q. Expecting %q\", req.Header.RequestURI(), \"/foo/bar?baz=aaa\")\n\t}\n\t// authorization\n\tauthorization := req.Header.Get(string(bytestr.StrAuthorization))\n\tauthor, err := base64.StdEncoding.DecodeString(authorization[len(bytestr.StrBasicSpace):])\n\tif err != nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\n\tif string(author) != \"user:pass\" {\n\t\tt.Fatalf(\"unexpected Authorization: %q. Expecting %q\", authorization, \"user:pass\")\n\t}\n\n\t// verify that Write returns error on non-absolute RequestURI\n\treq.Reset()\n\treq.Header.SetRequestURI(\"/foo/bar\")\n\tw.Reset()\n\tif err := Write(&req, zw); err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n}\n\nfunc TestRequestWriteMultipartFile(t *testing.T) {\n\tt.Parallel()\n\n\tvar req protocol.Request\n\treq.Header.SetHost(\"foobar.com\")\n\treq.Header.SetMethod(consts.MethodPost)\n\treq.SetFileReader(\"filea\", \"filea.txt\", bytes.NewReader([]byte(\"This is filea.\")))\n\treq.SetMultipartField(\"fileb\", \"fileb.txt\", \"text/plain\", bytes.NewReader([]byte(\"This is fileb.\")))\n\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\tif err := Write(&req, zw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif err := zw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tvar req1 protocol.Request\n\tzr := mock.NewZeroCopyReader(w.String())\n\tif err := Read(&req1, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tfilea, err := req1.FormFile(\"filea\")\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, \"filea.txt\", filea.Filename)\n\tfileb, err := req1.FormFile(\"fileb\")\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, \"fileb.txt\", fileb.Filename)\n}\n\nfunc TestSetRequestBodyStreamChunked(t *testing.T) {\n\tt.Parallel()\n\n\ttestSetRequestBodyStreamChunked(t, \"\", map[string]string{\"Foo\": \"bar\"})\n\n\tbody := \"foobar baz aaa bbb ccc\"\n\ttestSetRequestBodyStreamChunked(t, body, nil)\n\n\tbody = string(mock.CreateFixedBody(10001))\n\ttestSetRequestBodyStreamChunked(t, body, map[string]string{\"Foo\": \"test\", \"Bar\": \"test\"})\n}\n\nfunc TestSetRequestBodyStreamFixedSize(t *testing.T) {\n\tt.Parallel()\n\n\ttestSetRequestBodyStream(t, \"a\")\n\ttestSetRequestBodyStream(t, string(mock.CreateFixedBody(4097)))\n\ttestSetRequestBodyStream(t, string(mock.CreateFixedBody(100500)))\n}\n\nfunc TestRequestHostFromRequestURI(t *testing.T) {\n\tt.Parallel()\n\n\thExpected := \"foobar.com\"\n\tvar req protocol.Request\n\treq.SetRequestURI(\"http://proxy-host:123/foobar?baz\")\n\treq.SetHost(hExpected)\n\th := bytesconv.B2s(req.Host())\n\tif h != hExpected {\n\t\tt.Fatalf(\"unexpected host set: %q. Expecting %q\", h, hExpected)\n\t}\n}\n\nfunc TestRequestHostFromHeader(t *testing.T) {\n\tt.Parallel()\n\n\thExpected := \"foobar.com\"\n\tvar req protocol.Request\n\treq.Header.SetHost(hExpected)\n\th := bytesconv.B2s(req.Host())\n\tif h != hExpected {\n\t\tt.Fatalf(\"unexpected host set: %q. Expecting %q\", h, hExpected)\n\t}\n}\n\nfunc TestRequestContentTypeWithCharset(t *testing.T) {\n\tt.Parallel()\n\n\texpectedContentType := consts.MIMEApplicationHTMLFormUTF8\n\texpectedBody := \"0123=56789\"\n\ts := fmt.Sprintf(\"POST / HTTP/1.1\\r\\nContent-Type: %s\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\texpectedContentType, len(expectedBody), expectedBody)\n\n\tzr := mock.NewZeroCopyReader(s)\n\tvar r protocol.Request\n\tif err := Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tbody := r.Body()\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\tct := r.Header.ContentType()\n\tif string(ct) != expectedContentType {\n\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", ct, expectedContentType)\n\t}\n\targs := r.PostArgs()\n\tif args.Len() != 1 {\n\t\tt.Fatalf(\"unexpected number of POST args: %d. Expecting 1\", args.Len())\n\t}\n\tav := args.Peek(\"0123\")\n\tif string(av) != \"56789\" {\n\t\tt.Fatalf(\"unexpected POST arg value: %q. Expecting %q\", av, \"56789\")\n\t}\n}\n\nfunc TestRequestBodyStreamMultipleBodyCalls(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Request\n\n\ts := \"foobar baz abc\"\n\tif r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tr.SetBodyStream(bytes.NewBufferString(s), len(s))\n\tif !r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\tbody := r.Body()\n\t\tif string(body) != s {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q. iteration %d\", body, s, i)\n\t\t}\n\t}\n}\n\nfunc TestRequestNoContentLength(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Request\n\n\tr.Header.SetMethod(consts.MethodHead)\n\tr.Header.SetHost(\"foobar\")\n\n\ts := GetHTTP1Request(&r).String()\n\tif strings.Contains(s, \"Content-Length: \") {\n\t\tt.Fatalf(\"unexpected content-length in HEAD request %q\", s)\n\t}\n\n\tr.Header.SetMethod(consts.MethodPost)\n\tfmt.Fprintf(r.BodyWriter(), \"foobar body\")\n\ts = GetHTTP1Request(&r).String()\n\tif !strings.Contains(s, \"Content-Length: \") {\n\t\tt.Fatalf(\"missing content-length header in non-GET request %q\", s)\n\t}\n}\n\nfunc TestRequestReadGzippedBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Request\n\n\tbodyOriginal := \"foo bar baz compress me better!\"\n\tbody := compress.AppendGzipBytes(nil, []byte(bodyOriginal))\n\ts := fmt.Sprintf(\"POST /foobar HTTP/1.1\\r\\nContent-Type: foo/bar\\r\\nContent-Encoding: gzip\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\tlen(body), body)\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tif string(r.Header.Peek(consts.HeaderContentEncoding)) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected content-encoding: %q. Expecting %q\", r.Header.Peek(consts.HeaderContentEncoding), \"gzip\")\n\t}\n\tif r.Header.ContentLength() != len(body) {\n\t\tt.Fatalf(\"unexpected content-length: %d. Expecting %d\", r.Header.ContentLength(), len(body))\n\t}\n\tif string(r.Body()) != string(body) {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", r.Body(), body)\n\t}\n\n\tbodyGunzipped, err := compress.AppendGunzipBytes(nil, r.Body())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when uncompressing data: %s\", err)\n\t}\n\tif string(bodyGunzipped) != bodyOriginal {\n\t\tt.Fatalf(\"unexpected uncompressed body %q. Expecting %q\", bodyGunzipped, bodyOriginal)\n\t}\n}\n\nfunc TestRequestReadPostNoBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Request\n\n\ts := \"POST /foo/bar HTTP/1.1\\r\\nContent-Type: aaa/bbb\\r\\n\\r\\naaaa\"\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tif string(r.Header.RequestURI()) != \"/foo/bar\" {\n\t\tt.Fatalf(\"unexpected request uri %q. Expecting %q\", r.Header.RequestURI(), \"/foo/bar\")\n\t}\n\tif string(r.Header.ContentType()) != \"aaa/bbb\" {\n\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", r.Header.ContentType(), \"aaa/bbb\")\n\t}\n\tif len(r.Body()) != 0 {\n\t\tt.Fatalf(\"unexpected body found %q. Expecting empty body\", r.Body())\n\t}\n\tif r.Header.ContentLength() != 0 {\n\t\tt.Fatalf(\"unexpected content-length: %d. Expecting 0\", r.Header.ContentLength())\n\t}\n\n\ttail, err := ioutil.ReadAll(zr)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(tail) != \"aaaa\" {\n\t\tt.Fatalf(\"unexpected tail %q. Expecting %q\", tail, \"aaaa\")\n\t}\n}\n\nfunc TestRequestContinueReadBodyDisablePrereadMultipartForm(t *testing.T) {\n\tt.Parallel()\n\n\tvar w bytes.Buffer\n\tmw := multipart.NewWriter(&w)\n\tfor i := 0; i < 10; i++ {\n\t\tk := fmt.Sprintf(\"key_%d\", i)\n\t\tv := fmt.Sprintf(\"value_%d\", i)\n\t\tif err := mw.WriteField(k, v); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t}\n\tboundary := mw.Boundary()\n\tif err := mw.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tformData := w.Bytes()\n\n\ts := fmt.Sprintf(\"POST / HTTP/1.1\\r\\nHost: aaa\\r\\nContent-Type: multipart/form-data; boundary=%s\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\tboundary, len(formData), formData)\n\tzr := mock.NewZeroCopyReader(s)\n\n\tvar r protocol.Request\n\n\tif err := ReadHeader(&r.Header, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error reading headers: %s\", err)\n\t}\n\n\tif err := ReadLimitBody(&r, zr, 10000, false, false); err != nil {\n\t\tt.Fatalf(\"unexpected error reading body: %s\", err)\n\t}\n\n\tif r.HasMultipartForm() {\n\t\tt.Fatalf(\"The multipartForm of the Request must be nil\")\n\t}\n\n\tif string(formData) != string(r.Body()) {\n\t\tt.Fatalf(\"The body given must equal the body in the Request\")\n\t}\n}\n\nfunc TestRequestReadLimitBody(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestReadLimitBodyReadOnly(t, \"POST /foo HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 9\\r\\nContent-Type: aaa\\r\\n\\r\\n123456789\")\n\t// request with content-length\n\ttestRequestReadLimitBodySuccess(t, \"POST /foo HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 9\\r\\nContent-Type: aaa\\r\\n\\r\\n123456789\", 9)\n\ttestRequestReadLimitBodySuccess(t, \"POST /foo HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 9\\r\\nContent-Type: aaa\\r\\n\\r\\n123456789\", 92)\n\ttestRequestReadLimitBodyError(t, \"POST /foo HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 9\\r\\nContent-Type: aaa\\r\\n\\r\\n123456789\", 5)\n\n\t// chunked request\n\ttestRequestReadLimitBodySuccess(t, \"POST /a HTTP/1.1\\r\\nHost: a.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 9)\n\ttestRequestReadLimitBodySuccess(t, \"POST /a HTTP/1.1\\r\\nHost: a.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 999)\n\ttestRequestReadLimitBodyError(t, \"POST /a HTTP/1.1\\r\\nHost: a.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 8)\n}\n\nfunc testRequestReadLimitBodyReadOnly(t *testing.T, s string) {\n\tvar req protocol.Request\n\tzr := mock.NewZeroCopyReader(s)\n\n\tReadHeader(&req.Header, zr)\n\tif err := ReadLimitBody(&req, zr, 10, true, false); err == nil {\n\t\tt.Fatalf(\"expected error: %s\", errGetOnly.Error())\n\t}\n}\n\nfunc TestRequestString(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Request\n\tr.SetRequestURI(\"http://foobar.com/aaa\")\n\ts := GetHTTP1Request(&r).String()\n\texpectedS := \"GET /aaa HTTP/1.1\\r\\nHost: foobar.com\\r\\n\\r\\n\"\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected request: %q. Expecting %q\", s, expectedS)\n\t}\n}\n\nfunc TestRequestReadChunked(t *testing.T) {\n\tt.Parallel()\n\n\tvar req protocol.Request\n\n\ts := \"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n3\\r\\nabc\\r\\n5\\r\\n12345\\r\\n0\\r\\n\\r\\nTrail: test\\r\\n\\r\\n\"\n\tzr := netpoll.NewReader(bytes.NewBufferString(s))\n\terr := Read(&req, zr)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading chunked request: %s\", err)\n\t}\n\texpectedBody := \"abc12345\"\n\tif string(req.Body()) != expectedBody {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", req.Body(), expectedBody)\n\t}\n\tverifyRequestHeader(t, &req.Header, 8, \"/foo\", \"google.com\", \"\", \"aa/bb\")\n\tverifyTrailer(t, zr, map[string]string{\"Trail\": \"test\"})\n}\n\nfunc verifyTrailer(t *testing.T, r network.Reader, exceptedTrailers map[string]string) {\n\ttrailer := protocol.Trailer{}\n\tkeys := make([]string, 0, len(exceptedTrailers))\n\tfor k := range exceptedTrailers {\n\t\tkeys = append(keys, k)\n\t}\n\ttrailer.SetTrailers([]byte(strings.Join(keys, \", \")))\n\terr := ext.ReadTrailer(&trailer, r)\n\tif err == io.EOF && exceptedTrailers == nil {\n\t\treturn\n\t}\n\tif err != nil {\n\t\tt.Fatalf(\"Cannot read trailer: %v\", err)\n\t}\n\n\tfor k, v := range exceptedTrailers {\n\t\tgot := trailer.Peek(k)\n\t\tif !bytes.Equal(got, []byte(v)) {\n\t\t\tt.Fatalf(\"Unexpected trailer %q. Expected %q. Got %q\", k, v, got)\n\t\t}\n\t}\n}\n\nfunc TestRequestChunkedWhitespace(t *testing.T) {\n\tt.Parallel()\n\n\tvar req protocol.Request\n\n\ts := \"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n3  \\r\\nabc\\r\\n0\\r\\n\\r\\n\"\n\tzr := mock.NewZeroCopyReader(s)\n\n\terr := Read(&req, zr)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading chunked request: %s\", err)\n\t}\n\texpectedBody := \"abc\"\n\tif string(req.Body()) != expectedBody {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", req.Body(), expectedBody)\n\t}\n}\n\nfunc testRequestReadLimitBodyError(t *testing.T, s string, maxBodySize int) {\n\tvar req protocol.Request\n\tzr := mock.NewZeroCopyReader(s)\n\n\terr := ReadHeaderAndLimitBody(&req, zr, maxBodySize)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error. s=%q, maxBodySize=%d\", s, maxBodySize)\n\t}\n\tif !errors.Is(err, errs.ErrBodyTooLarge) {\n\t\tt.Fatalf(\"unexpected error: %s. Expecting %s. s=%q, maxBodySize=%d\", err, errBodyTooLarge, s, maxBodySize)\n\t}\n}\n\nfunc testRequestReadLimitBodySuccess(t *testing.T, s string, maxBodySize int) {\n\tvar req protocol.Request\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := ReadHeaderAndLimitBody(&req, zr, maxBodySize); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s. s=%q, maxBodySize=%d\", err, s, maxBodySize)\n\t}\n}\n\nfunc testSetRequestBodyStream(t *testing.T, body string) {\n\tvar req protocol.Request\n\treq.Header.SetHost(\"foobar.com\")\n\treq.Header.SetMethod(consts.MethodPost)\n\n\tbodySize := len(body)\n\tif req.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\treq.SetBodyStream(bytes.NewBufferString(body), bodySize)\n\tif !req.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\tif err := Write(&req, zw); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing request: %s. body=%q\", err, body)\n\t}\n\n\tif err := zw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error when flushing request: %s. body=%q\", err, body)\n\t}\n\n\tvar req1 protocol.Request\n\tbr := bufio.NewReader(&w)\n\tzr := netpoll.NewReader(br)\n\tif err := Read(&req1, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error when reading request: %s. body=%q\", err, body)\n\t}\n\tif string(req1.Body()) != body {\n\t\tfmt.Println(string(req1.Body()))\n\t\tfmt.Println(body)\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", req1.Body(), body)\n\t}\n}\n\nfunc testSetRequestBodyStreamChunked(t *testing.T, body string, trailer map[string]string) {\n\tvar req protocol.Request\n\treq.Header.SetHost(\"foobar.com\")\n\treq.Header.SetMethod(consts.MethodPost)\n\n\tif req.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\treq.SetBodyStream(bytes.NewBufferString(body), -1)\n\tif !req.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\tfor k, v := range trailer {\n\t\terr := req.Header.Trailer().Add(k, v)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t}\n\tif err := Write(&req, zw); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing request: %v, body=%q\", err, body)\n\t}\n\tif err := zw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error when flushing request: %v, body=%q\", err, body)\n\t}\n\n\tvar req1 protocol.Request\n\tbr := bufio.NewReader(&w)\n\tzr := netpoll.NewReader(br)\n\tif err := Read(&req1, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error when reading request: %v. body=%q\", err, body)\n\t}\n\tif string(req1.Body()) != body {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", req1.Body(), body)\n\t}\n\tfor k, v := range trailer {\n\t\tr := req.Header.Trailer().Peek(k)\n\t\tif string(r) != v {\n\t\t\tt.Fatalf(\"unexpected trailer %q. Expecting %q. Got %q\", k, v, r)\n\t\t}\n\t}\n}\n\nfunc TestRequestMultipartForm(t *testing.T) {\n\tt.Parallel()\n\n\tvar w bytes.Buffer\n\tmw := multipart.NewWriter(&w)\n\tfor i := 0; i < 10; i++ {\n\t\tk := fmt.Sprintf(\"key_%d\", i)\n\t\tv := fmt.Sprintf(\"value_%d\", i)\n\t\tif err := mw.WriteField(k, v); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t}\n\tboundary := mw.Boundary()\n\tif err := mw.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tformData := w.Bytes()\n\tfor i := 0; i < 5; i++ {\n\t\tformData = testRequestMultipartForm(t, boundary, formData, 10)\n\t\ttestRequestMultipartFormNotPreParse(t, boundary, formData, 10)\n\t}\n\n\t// verify request unmarshalling / marshalling\n\ts := \"POST / HTTP/1.1\\r\\nHost: aaa\\r\\nContent-Type: multipart/form-data; boundary=foobar\\r\\nContent-Length: 213\\r\\n\\r\\n--foobar\\r\\nContent-Disposition: form-data; name=\\\"key_0\\\"\\r\\n\\r\\nvalue_0\\r\\n--foobar\\r\\nContent-Disposition: form-data; name=\\\"key_1\\\"\\r\\n\\r\\nvalue_1\\r\\n--foobar\\r\\nContent-Disposition: form-data; name=\\\"key_2\\\"\\r\\n\\r\\nvalue_2\\r\\n--foobar--\\r\\n\"\n\tvar r protocol.Request\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\ts = GetHTTP1Request(&r).String()\n\tzr = mock.NewZeroCopyReader(s)\n\tif err := Read(&r, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\ttestRequestMultipartForm(t, \"foobar\", r.Body(), 3)\n}\n\nfunc testRequestMultipartForm(t *testing.T, boundary string, formData []byte, partsCount int) []byte {\n\ts := fmt.Sprintf(\"POST / HTTP/1.1\\r\\nHost: aaa\\r\\nContent-Type: multipart/form-data; boundary=%s\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\tboundary, len(formData), formData)\n\tvar req protocol.Request\n\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := Read(&req, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tf, err := req.MultipartForm()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tdefer req.RemoveMultipartFormFiles()\n\n\tif len(f.File) > 0 {\n\t\tt.Fatalf(\"unexpected files found in the multipart form: %d\", len(f.File))\n\t}\n\n\tif len(f.Value) != partsCount {\n\t\tt.Fatalf(\"unexpected number of values found: %d. Expecting %d\", len(f.Value), partsCount)\n\t}\n\n\tfor k, vv := range f.Value {\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of values found for key=%q: %d. Expecting 1\", k, len(vv))\n\t\t}\n\t\tif !strings.HasPrefix(k, \"key_\") {\n\t\t\tt.Fatalf(\"unexpected key prefix=%q. Expecting %q\", k, \"key_\")\n\t\t}\n\t\tv := vv[0]\n\t\tif !strings.HasPrefix(v, \"value_\") {\n\t\t\tt.Fatalf(\"unexpected value prefix=%q. expecting %q\", v, \"value_\")\n\t\t}\n\t\tif k[len(\"key_\"):] != v[len(\"value_\"):] {\n\t\t\tt.Fatalf(\"key and value suffixes don't match: %q vs %q\", k, v)\n\t\t}\n\t}\n\n\treturn req.Body()\n}\n\nfunc testRequestMultipartFormNotPreParse(t *testing.T, boundary string, formData []byte, partsCount int) []byte {\n\ts := fmt.Sprintf(\"POST / HTTP/1.1\\r\\nHost: aaa\\r\\nContent-Type: multipart/form-data; boundary=%s\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\tboundary, len(formData), formData)\n\tvar req protocol.Request\n\n\tzr := mock.NewZeroCopyReader(s)\n\tif err := Read(&req, zr, false); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tf, err := req.MultipartForm()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tdefer req.RemoveMultipartFormFiles()\n\n\tif len(f.File) > 0 {\n\t\tt.Fatalf(\"unexpected files found in the multipart form: %d\", len(f.File))\n\t}\n\n\tif len(f.Value) != partsCount {\n\t\tt.Fatalf(\"unexpected number of values found: %d. Expecting %d\", len(f.Value), partsCount)\n\t}\n\n\tfor k, vv := range f.Value {\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of values found for key=%q: %d. Expecting 1\", k, len(vv))\n\t\t}\n\t\tif !strings.HasPrefix(k, \"key_\") {\n\t\t\tt.Fatalf(\"unexpected key prefix=%q. Expecting %q\", k, \"key_\")\n\t\t}\n\t\tv := vv[0]\n\t\tif !strings.HasPrefix(v, \"value_\") {\n\t\t\tt.Fatalf(\"unexpected value prefix=%q. expecting %q\", v, \"value_\")\n\t\t}\n\t\tif k[len(\"key_\"):] != v[len(\"value_\"):] {\n\t\t\tt.Fatalf(\"key and value suffixes don't match: %q vs %q\", k, v)\n\t\t}\n\t}\n\n\treturn req.Body()\n}\n\nfunc testReadBodyChunked(t *testing.T, bodySize int) {\n\tbody := mock.CreateFixedBody(bodySize)\n\texpectedTrailer := map[string]string{\"Foo\": \"chunked shit\"}\n\tchunkedBody := mock.CreateChunkedBody(body, expectedTrailer, true)\n\n\tzr := mock.NewZeroCopyReader(string(chunkedBody))\n\n\t// p,_ := mr.Next(3687)\n\tb, err := ext.ReadBody(zr, -1, 0, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error for bodySize=%d: %s. body=%q, chunkedBody=%q\", bodySize, err, body, chunkedBody)\n\t}\n\tif !bytes.Equal(b, body) {\n\t\tt.Fatalf(\"Unexpected response read for bodySize=%d: %q. Expected %q. chunkedBody=%q\", bodySize, b, body, chunkedBody)\n\t}\n\tverifyTrailer(t, zr, expectedTrailer)\n}\n\nfunc testReadBodyFixedSize(t *testing.T, bodySize int) {\n\tbody := mock.CreateFixedBody(bodySize)\n\n\tzr := mock.NewZeroCopyReader(string(body))\n\tb, err := ext.ReadBody(zr, bodySize, 0, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error in ReadResponseBody(%d): %s\", bodySize, err)\n\t}\n\tif !bytes.Equal(b, body) {\n\t\tt.Fatalf(\"Unexpected response read for bodySize=%d: %q. Expected %q\", bodySize, b, body)\n\t}\n\tverifyTrailer(t, zr, nil)\n}\n\nfunc TestRequestFormFile(t *testing.T) {\n\tt.Parallel()\n\n\ts := `POST /upload HTTP/1.1\nHost: localhost:10000\nContent-Length: 521\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f1\"\n\nvalue1\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"fileaaa\"; filename=\"TODO\"\nContent-Type: application/octet-stream\n\n- SessionClient with referer and cookies support.\n- Client with requests' pipelining support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\ntailfoobar`\n\n\tmr := mock.NewZeroCopyReader(s)\n\n\tvar r protocol.Request\n\tif err := Read(&r, mr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\ttail, err := ioutil.ReadAll(mr)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(tail) != \"tailfoobar\" {\n\t\tt.Fatalf(\"unexpected tail %q. Expecting %q\", tail, \"tailfoobar\")\n\t}\n\tfh, err := r.FormFile(\"fileaaa\")\n\tif err != nil {\n\t\tt.Fatalf(\"TestRequestFormFile error: %#v\", err.Error())\n\t}\n\tif fh == nil {\n\t\tt.Fatalf(\"fh unexpected nil\")\n\t}\n}\n\nfunc TestRequest_ContinueReadBodyStream(t *testing.T) {\n\t// small body\n\tgenBody := \"abcdef4343\"\n\ts := \"PUT /foo/bar HTTP/1.1\\r\\nExpect: 100-continue\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\"\n\ttestContinueReadBodyStream(t, s, genBody, 10, 5, 0, 5)\n\ttestContinueReadBodyStream(t, s, genBody, 1, 5, 0, 0)\n\n\t// big body (> 8193)\n\ts1 := \"PUT /foo/bar HTTP/1.1\\r\\nExpect: 100-continue\\r\\nContent-Length: 9216\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\"\n\tgenBody = strings.Repeat(\"1\", 9*1024)\n\ttestContinueReadBodyStream(t, s1, genBody, 10*1024, 5*1024, 4*1024, 0)\n\ttestContinueReadBodyStream(t, s1, genBody, 10*1024, 1*1024, 8*1024, 0)\n\ttestContinueReadBodyStream(t, s1, genBody, 10*1024, 9*1024, 0*1024, 0)\n\n\t// normal stream\n\ttestContinueReadBodyStream(t, s1, genBody, 1*1024, 5*1024, 4*1024, 0)\n\ttestContinueReadBodyStream(t, s1, genBody, 1*1024, 1*1024, 8*1024, 0)\n\ttestContinueReadBodyStream(t, s1, genBody, 1*1024, 9*1024, 0*1024, 0)\n\ttestContinueReadBodyStream(t, s1, genBody, 5, 5*1024, 4*1024, 0)\n\ttestContinueReadBodyStream(t, s1, genBody, 5, 1*1024, 8*1024, 0)\n\ttestContinueReadBodyStream(t, s1, genBody, 5, 9*1024, 0, 0)\n\n\t// critical point\n\ttestContinueReadBodyStream(t, s1, genBody, 8*1024+1, 5*1024, 4*1024, 0)\n\ttestContinueReadBodyStream(t, s1, genBody, 8*1024+1, 1*1024, 8*1024, 0)\n\ttestContinueReadBodyStream(t, s1, genBody, 8*1024+1, 9*1024, 0*1024, 0)\n\n\t// chunked body\n\ts2 := \"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n3\\r\\nabc\\r\\n5\\r\\n12345\\r\\n0\\r\\n\\r\\ntrail\"\n\ttestContinueReadBodyStream(t, s2, \"\", 10*1024, 3, 5, 5)\n\ts3 := \"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n3\\r\\nabc\\r\\n5\\r\\n12345\\r\\n0\\r\\n\\r\\n\"\n\ttestContinueReadBodyStream(t, s3, \"\", 10*1024, 3, 5, 0)\n}\n\nfunc TestRequest_Chunked(t *testing.T) {\n\tt.Parallel()\n\ts4 := \"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n5\\r\\n12345\\r\\n0\\r\\n\\r\\n\"\n\ttestReadChunked(t, s4, \"\", 3, 2)\n\n\ts5 := \"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n5\\r\\n12345\\r\\n3\\r\\n1230\\r\\n\\r\\n\"\n\ttestReadChunked(t, s5, \"\", 3, 5)\n}\n\nfunc TestRequest_ReadIncompleteStream(t *testing.T) {\n\tt.Parallel()\n\t// small body\n\tgenBody := \"abcdef4343\"\n\ts := \"PUT /foo/bar HTTP/1.1\\r\\nExpect: 100-continue\\r\\nContent-Length: 100\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\"\n\ttestReadIncompleteStream(t, s, genBody)\n\n\t// big body (> 8193)\n\ts1 := \"PUT /foo/bar HTTP/1.1\\r\\nExpect: 100-continue\\r\\nContent-Length: 10000\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\"\n\tgenBody = strings.Repeat(\"1\", 9*1024)\n\ttestReadIncompleteStream(t, s1, genBody)\n}\n\nfunc testReadIncompleteStream(t *testing.T, header, body string) {\n\tmr := mock.NewZeroCopyReader(header + body)\n\tvar r protocol.Request\n\tif err := ReadHeader(&r.Header, mr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif err := ContinueReadBodyStream(&r, mr, 1, true); err != nil {\n\t\tt.Fatalf(\"error when reading request body stream: %s\", err)\n\t}\n\treadBody, err := ioutil.ReadAll(r.BodyStream())\n\tif !bytes.Equal(readBody, []byte(body)) || len(readBody) != len(body) {\n\t\tt.Fatalf(\"readBody is not equal to the rawBody: %b(len: %d)\", readBody, len(readBody))\n\t}\n\tif err != io.ErrUnexpectedEOF {\n\t\tt.Fatalf(\"error should be io.ErrUnexpectedEOF, but got: %s\", err)\n\t}\n}\n\nfunc testReadChunked(t *testing.T, header, body string, firstRead, leftBytes int) {\n\tmr := mock.NewZeroCopyReader(header + body)\n\n\tvar r protocol.Request\n\tif err := ReadHeader(&r.Header, mr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif err := ContinueReadBodyStream(&r, mr, 2048, true); err != nil {\n\t\tt.Fatalf(\"error when reading request body stream: %s\", err)\n\t}\n\tif r.Header.ContentLength() >= 0 {\n\t\tt.Fatalf(\"expect a chunked body\")\n\t}\n\tstreamRead := make([]byte, firstRead)\n\tfr, err := r.BodyStream().Read(streamRead)\n\tif err != nil {\n\t\tt.Fatalf(\"read stream error=%v\", err)\n\t}\n\tif fr != firstRead {\n\t\tt.Fatalf(\"should read %d from stream body, but got %d\", streamRead, fr)\n\t}\n\tleftB, _ := ioutil.ReadAll(r.BodyStream())\n\tif len(leftB) != leftBytes {\n\t\tt.Fatalf(\"should left %d bytes from stream body, but left %d\", leftBytes, len(leftB))\n\t}\n}\n\nfunc testContinueReadBodyStream(t *testing.T, header, body string, maxBodySize, firstRead, leftBytes, bytesLeftInReader int) {\n\tmr := mock.NewZeroCopyReader(header + body)\n\tvar r protocol.Request\n\tif err := ReadHeader(&r.Header, mr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif err := ContinueReadBodyStream(&r, mr, maxBodySize, true); err != nil {\n\t\tt.Fatalf(\"error when reading request body stream: %s\", err)\n\t}\n\tfRead := firstRead\n\tstreamRead := make([]byte, fRead)\n\tsR, _ := r.BodyStream().Read(streamRead)\n\n\tif sR != firstRead {\n\t\tt.Fatalf(\"should read %d from stream body, but got %d\", firstRead, sR)\n\t}\n\n\tleftB, _ := ioutil.ReadAll(r.BodyStream())\n\tif len(leftB) != leftBytes {\n\t\tt.Fatalf(\"should left %d bytes from stream body, but left %d\", leftBytes, len(leftB))\n\t}\n\tif r.Header.ContentLength() > 0 {\n\t\tgotBody := append(streamRead, leftB...)\n\t\tif !bytes.Equal([]byte(body[:r.Header.ContentLength()]), gotBody) {\n\t\t\tt.Fatalf(\"body read from stream is not equal to the origin. Got: %s\", gotBody)\n\t\t}\n\t}\n\n\tleft, _ := mr.Peek(mr.Len())\n\n\tif len(left) != bytesLeftInReader {\n\t\tfmt.Printf(\"##########header:%s,body:%s,%d:max,first:%d,left:%d,leftin:%d\\n\", header, body, maxBodySize, firstRead, leftBytes, bytesLeftInReader)\n\t\tfmt.Printf(\"##########left: %s\\n\", left)\n\t\tt.Fatalf(\"should left %d bytes in original reader. got %q\", bytesLeftInReader, len(left))\n\t}\n}\n\nfunc verifyRequestHeader(t *testing.T, h *protocol.RequestHeader, expectedContentLength int,\n\texpectedRequestURI, expectedHost, expectedReferer, expectedContentType string,\n) {\n\tif h.ContentLength() != expectedContentLength {\n\t\tt.Fatalf(\"Unexpected Content-Length %d. Expected %d\", h.ContentLength(), expectedContentLength)\n\t}\n\tif string(h.RequestURI()) != expectedRequestURI {\n\t\tt.Fatalf(\"Unexpected RequestURI %q. Expected %q\", h.RequestURI(), expectedRequestURI)\n\t}\n\tif string(h.Peek(consts.HeaderHost)) != expectedHost {\n\t\tt.Fatalf(\"Unexpected host %q. Expected %q\", h.Peek(consts.HeaderHost), expectedHost)\n\t}\n\tif string(h.Peek(consts.HeaderReferer)) != expectedReferer {\n\t\tt.Fatalf(\"Unexpected referer %q. Expected %q\", h.Peek(consts.HeaderReferer), expectedReferer)\n\t}\n\tif string(h.Peek(consts.HeaderContentType)) != expectedContentType {\n\t\tt.Fatalf(\"Unexpected content-type %q. Expected %q\", h.Peek(consts.HeaderContentType), expectedContentType)\n\t}\n}\n\nfunc TestRequestReadMultipartFormWithFile(t *testing.T) {\n\tt.Parallel()\n\n\ts := `POST /upload HTTP/1.1\nHost: localhost:10000\nContent-Length: 521\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f1\"\n\nvalue1\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"fileaaa\"; filename=\"TODO\"\nContent-Type: application/octet-stream\n\n- SessionClient with referer and cookies support.\n- Client with requests' pipelining support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\ntailfoobar`\n\n\tmr := mock.NewZeroCopyReader(s)\n\n\tvar r protocol.Request\n\tif err := Read(&r, mr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\ttail, err := ioutil.ReadAll(mr)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(tail) != \"tailfoobar\" {\n\t\tt.Fatalf(\"unexpected tail %q. Expecting %q\", tail, \"tailfoobar\")\n\t}\n\n\tf, err := r.MultipartForm()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tdefer r.RemoveMultipartFormFiles()\n\n\t// verify values\n\tif len(f.Value) != 1 {\n\t\tt.Fatalf(\"unexpected number of values in multipart form: %d. Expecting 1\", len(f.Value))\n\t}\n\tfor k, vv := range f.Value {\n\t\tif k != \"f1\" {\n\t\t\tt.Fatalf(\"unexpected value name %q. Expecting %q\", k, \"f1\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v != \"value1\" {\n\t\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"value1\")\n\t\t}\n\t}\n\n\t// verify files\n\tif len(f.File) != 1 {\n\t\tt.Fatalf(\"unexpected number of file values in multipart form: %d. Expecting 1\", len(f.File))\n\t}\n\tfor k, vv := range f.File {\n\t\tif k != \"fileaaa\" {\n\t\t\tt.Fatalf(\"unexpected file value name %q. Expecting %q\", k, \"fileaaa\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of file values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v.Filename != \"TODO\" {\n\t\t\tt.Fatalf(\"unexpected filename %q. Expecting %q\", v.Filename, \"TODO\")\n\t\t}\n\t\tct := v.Header.Get(\"Content-Type\")\n\t\tif ct != \"application/octet-stream\" {\n\t\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", ct, \"application/octet-stream\")\n\t\t}\n\t}\n}\n\nfunc testRequestMultipartFormBoundary(t *testing.T, s, boundary string) {\n\tvar h protocol.RequestHeader\n\tzr := mock.NewZeroCopyReader(s)\n\n\tif err := ReadHeader(&h, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s. s=%q, boundary=%q\", err, s, boundary)\n\t}\n\n\tb := h.MultipartFormBoundary()\n\tif string(b) != boundary {\n\t\tt.Fatalf(\"unexpected boundary %q. Expecting %q. s=%q\", b, boundary, s)\n\t}\n}\n\nfunc TestStreamNotEnoughData(t *testing.T) {\n\treq := protocol.AcquireRequest()\n\treq.Header.SetContentLength(1 << 16)\n\tconn := mock.NewStreamConn()\n\tconst maxBodySize = 4 * 1024 * 1024\n\terr := ContinueReadBodyStream(req, conn, maxBodySize)\n\tassert.Nil(t, err)\n\terr = ext.ReleaseBodyStream(req.BodyStream())\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, 0, len(conn.Data))\n\tassert.DeepEqual(t, true, conn.HasReleased)\n}\n\nfunc TestRequestBodyStreamWithTrailer(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestBodyStreamWithTrailer(t, []byte(\"test\"), false)\n\ttestRequestBodyStreamWithTrailer(t, mock.CreateFixedBody(4097), false)\n\ttestRequestBodyStreamWithTrailer(t, mock.CreateFixedBody(105000), false)\n}\n\nfunc testRequestBodyStreamWithTrailer(t *testing.T, body []byte, disableNormalizing bool) {\n\texpectedTrailer := map[string]string{\n\t\t\"foo\": \"testfoo\",\n\t\t\"bar\": \"testbar\",\n\t}\n\n\tvar req1 protocol.Request\n\tif disableNormalizing {\n\t\treq1.Header.DisableNormalizing()\n\t}\n\treq1.SetHost(\"google.com\")\n\treq1.SetBodyStream(bytes.NewBuffer(body), -1)\n\tfor k, v := range expectedTrailer {\n\t\terr := req1.Header.Trailer().Set(k, v)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t}\n\n\tw := &bytes.Buffer{}\n\tzw := netpoll.NewWriter(w)\n\tif err := Write(&req1, zw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif err := zw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tvar req2 protocol.Request\n\tif disableNormalizing {\n\t\treq2.Header.DisableNormalizing()\n\t}\n\n\tbr := netpoll.NewReader(w)\n\tif err := Read(&req2, br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\treqBody := req2.Body()\n\tif !bytes.Equal(reqBody, body) {\n\t\tt.Fatalf(\"unexpected body: %q. Excepting %q\", reqBody, body)\n\t}\n\n\tfor k, v := range expectedTrailer {\n\t\tkBytes := []byte(k)\n\t\tutils.NormalizeHeaderKey(kBytes, disableNormalizing)\n\t\tr := req2.Header.Trailer().Peek(k)\n\t\tif string(r) != v {\n\t\t\tt.Fatalf(\"unexpected trailer header %q: %q. Expecting %s\", kBytes, r, v)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/http1/resp/header.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage resp\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/ext\"\n)\n\nvar errTimeout = errs.New(errs.ErrTimeout, errs.ErrorTypePublic, \"read response header\")\n\n// Read reads response header from r.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc ReadHeader(h *protocol.ResponseHeader, r network.Reader) error {\n\tn := 1\n\tfor {\n\t\terr := tryRead(h, r, n)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t\tif !errors.Is(err, errs.ErrNeedMore) {\n\t\t\th.ResetSkipNormalize()\n\t\t\treturn err\n\t\t}\n\n\t\t// No more data available on the wire, try block peek(by netpoll)\n\t\tif n == r.Len() {\n\t\t\tn++\n\n\t\t\tcontinue\n\t\t}\n\t\tn = r.Len()\n\t}\n}\n\n// WriteHeader writes response header to w.\nfunc WriteHeader(h *protocol.ResponseHeader, w network.Writer) error {\n\t// Data may become invalid after the next call of ResponseHeader.\n\t// copy before WriteHeader returns\n\theader := h.Header()\n\tb, err := w.Malloc(len(header))\n\tif err != nil {\n\t\treturn err\n\t}\n\th.SetHeaderLength(copy(b, header))\n\treturn nil\n}\n\n// ConnectionUpgrade returns true if 'Connection: Upgrade' header is set.\nfunc ConnectionUpgrade(h *protocol.ResponseHeader) bool {\n\treturn ext.HasHeaderValue(h.Peek(consts.HeaderConnection), bytestr.StrKeepAlive)\n}\n\nfunc tryRead(h *protocol.ResponseHeader, r network.Reader, n int) error {\n\th.ResetSkipNormalize()\n\tb, err := r.Peek(n)\n\tif len(b) == 0 {\n\t\t// Return ErrTimeout on any timeout.\n\t\tif err != nil && strings.Contains(err.Error(), \"timeout\") {\n\t\t\treturn errTimeout\n\t\t}\n\t\t// treat all other errors on the first byte read as EOF\n\t\tif n == 1 || err == io.EOF {\n\t\t\treturn io.EOF\n\t\t}\n\n\t\treturn fmt.Errorf(\"error when reading response headers: %s\", err)\n\t}\n\tb = ext.MustPeekBuffered(r)\n\theadersLen, errParse := parse(h, b)\n\tif errParse != nil {\n\t\treturn ext.HeaderError(\"response\", err, errParse, b)\n\t}\n\text.MustDiscard(r, headersLen)\n\treturn nil\n}\n\nfunc parseHeaders(h *protocol.ResponseHeader, buf []byte) (int, error) {\n\t// 'identity' content-length by default\n\th.InitContentLengthWithValue(-2)\n\n\tvar s ext.HeaderScanner\n\ts.B = buf\n\ts.DisableNormalizing = h.IsDisableNormalizing()\n\tvar err error\n\tfor s.Next() {\n\t\tif len(s.Key) > 0 {\n\t\t\tswitch s.Key[0] | 0x20 {\n\t\t\tcase 'c':\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrContentType) {\n\t\t\t\t\th.SetContentTypeBytes(s.Value)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrContentEncoding) {\n\t\t\t\t\th.SetContentEncodingBytes(s.Value)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrContentLength) {\n\t\t\t\t\tvar contentLength int\n\t\t\t\t\tif h.ContentLength() != -1 {\n\t\t\t\t\t\tif contentLength, err = protocol.ParseContentLength(s.Value); err != nil {\n\t\t\t\t\t\t\th.InitContentLengthWithValue(-2)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\th.InitContentLengthWithValue(contentLength)\n\t\t\t\t\t\t\th.SetContentLengthBytes(s.Value)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrConnection) {\n\t\t\t\t\tif bytes.Equal(s.Value, bytestr.StrClose) {\n\t\t\t\t\t\th.SetConnectionClose(true)\n\t\t\t\t\t} else {\n\t\t\t\t\t\th.SetConnectionClose(false)\n\t\t\t\t\t\th.AddArgBytes(s.Key, s.Value, protocol.ArgsHasValue)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\tcase 's':\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrServer) {\n\t\t\t\t\th.SetServerBytes(s.Value)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrSetCookie) {\n\t\t\t\t\th.ParseSetCookie(s.Value)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\tcase 't':\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrTransferEncoding) {\n\t\t\t\t\tif !bytes.Equal(s.Value, bytestr.StrIdentity) {\n\t\t\t\t\t\th.InitContentLengthWithValue(-1)\n\t\t\t\t\t\th.SetArgBytes(bytestr.StrTransferEncoding, bytestr.StrChunked, protocol.ArgsHasValue)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif utils.CaseInsensitiveCompare(s.Key, bytestr.StrTrailer) {\n\t\t\t\t\terr = h.Trailer().SetTrailers(s.Value)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\th.AddArgBytes(s.Key, s.Value, protocol.ArgsHasValue)\n\t\t}\n\t}\n\tif s.Err != nil {\n\t\th.SetConnectionClose(true)\n\t\treturn 0, s.Err\n\t}\n\n\tif h.ContentLength() < 0 {\n\t\th.SetContentLengthBytes(h.ContentLengthBytes()[:0])\n\t}\n\tif h.ContentLength() == -2 && !ConnectionUpgrade(h) && !h.MustSkipContentLength() {\n\t\th.SetArgBytes(bytestr.StrTransferEncoding, bytestr.StrIdentity, protocol.ArgsHasValue)\n\t\th.SetConnectionClose(true)\n\t}\n\tif !h.IsHTTP11() && !h.ConnectionClose() {\n\t\t// close connection for non-http/1.1 response unless 'Connection: keep-alive' is set.\n\t\tv := h.PeekArgBytes(bytestr.StrConnection)\n\t\th.SetConnectionClose(!ext.HasHeaderValue(v, bytestr.StrKeepAlive))\n\t}\n\n\treturn len(buf) - len(s.B), err\n}\n\nfunc parse(h *protocol.ResponseHeader, buf []byte) (int, error) {\n\tm, err := parseFirstLine(h, buf)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tn, err := parseHeaders(h, buf[m:])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn m + n, nil\n}\n\nfunc parseFirstLine(h *protocol.ResponseHeader, buf []byte) (int, error) {\n\tbNext := buf\n\tvar b []byte\n\tvar err error\n\tfor len(b) == 0 {\n\t\tif b, bNext, err = utils.NextLine(bNext); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\t// parse protocol\n\tn := bytes.IndexByte(b, ' ')\n\tif n < 0 {\n\t\treturn 0, fmt.Errorf(\"cannot find whitespace in the first line of response %q\", buf)\n\t}\n\n\tisHTTP11 := bytes.Equal(b[:n], bytestr.StrHTTP11)\n\tif !isHTTP11 {\n\t\th.SetProtocol(consts.HTTP10)\n\t} else {\n\t\th.SetProtocol(consts.HTTP11)\n\t}\n\n\tb = b[n+1:]\n\n\t// parse status code\n\tvar statusCode int\n\tstatusCode, n, err = bytesconv.ParseUintBuf(b)\n\th.SetStatusCode(statusCode)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"cannot parse response status code: %s. Response %q\", err, buf)\n\t}\n\tif len(b) > n && b[n] != ' ' {\n\t\treturn 0, fmt.Errorf(\"unexpected char at the end of status code. Response %q\", buf)\n\t}\n\n\treturn len(buf) - len(bNext), nil\n}\n"
  },
  {
    "path": "pkg/protocol/http1/resp/header_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage resp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\nfunc TestResponseHeaderCookie(t *testing.T) {\n\tt.Parallel()\n\n\tvar h protocol.ResponseHeader\n\tvar c protocol.Cookie\n\n\tc.SetKey(\"foobar\")\n\tc.SetValue(\"aaa\")\n\th.SetCookie(&c)\n\n\tc.SetKey(\"йцук\")\n\tc.SetDomain(\"foobar.com\")\n\th.SetCookie(&c)\n\n\tc.Reset()\n\tc.SetKey(\"foobar\")\n\tif !h.Cookie(&c) {\n\t\tt.Fatalf(\"Cannot find cookie %q\", c.Key())\n\t}\n\n\tvar expectedC1 protocol.Cookie\n\texpectedC1.SetKey(\"foobar\")\n\texpectedC1.SetValue(\"aaa\")\n\tif !equalCookie(&expectedC1, &c) {\n\t\tt.Fatalf(\"unexpected cookie\\n%#v\\nExpected\\n%#v\\n\", &c, &expectedC1)\n\t}\n\n\tc.SetKey(\"йцук\")\n\tif !h.Cookie(&c) {\n\t\tt.Fatalf(\"cannot find cookie %q\", c.Key())\n\t}\n\n\tvar expectedC2 protocol.Cookie\n\texpectedC2.SetKey(\"йцук\")\n\texpectedC2.SetValue(\"aaa\")\n\texpectedC2.SetDomain(\"foobar.com\")\n\tif !equalCookie(&expectedC2, &c) {\n\t\tt.Fatalf(\"unexpected cookie\\n%v\\nExpected\\n%v\\n\", &c, &expectedC2)\n\t}\n\n\th.VisitAllCookie(func(key, value []byte) {\n\t\tvar cc protocol.Cookie\n\t\tif err := cc.ParseBytes(value); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !bytes.Equal(key, cc.Key()) {\n\t\t\tt.Fatalf(\"Unexpected cookie key %q. Expected %q\", key, cc.Key())\n\t\t}\n\t\tswitch {\n\t\tcase bytes.Equal(key, []byte(\"foobar\")):\n\t\t\tif !equalCookie(&expectedC1, &cc) {\n\t\t\t\tt.Fatalf(\"unexpected cookie\\n%v\\nExpected\\n%v\\n\", &cc, &expectedC1)\n\t\t\t}\n\t\tcase bytes.Equal(key, []byte(\"йцук\")):\n\t\t\tif !equalCookie(&expectedC2, &cc) {\n\t\t\t\tt.Fatalf(\"unexpected cookie\\n%v\\nExpected\\n%v\\n\", &cc, &expectedC2)\n\t\t\t}\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected cookie key %q\", key)\n\t\t}\n\t})\n\n\tw := &bytes.Buffer{}\n\tzw := netpoll.NewWriter(w)\n\tif err := WriteHeader(&h, zw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif err := zw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\th.DelAllCookies()\n\n\tvar h1 protocol.ResponseHeader\n\tzr := netpoll.NewReader(w)\n\tif err := ReadHeader(&h1, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tc.SetKey(\"foobar\")\n\tif !h1.Cookie(&c) {\n\t\tt.Fatalf(\"Cannot find cookie %q\", c.Key())\n\t}\n\tif !equalCookie(&expectedC1, &c) {\n\t\tt.Fatalf(\"unexpected cookie\\n%v\\nExpected\\n%v\\n\", &c, &expectedC1)\n\t}\n\n\th1.DelCookie(\"foobar\")\n\tif h.Cookie(&c) {\n\t\tt.Fatalf(\"Unexpected cookie found: %v\", &c)\n\t}\n\tif h1.Cookie(&c) {\n\t\tt.Fatalf(\"Unexpected cookie found: %v\", &c)\n\t}\n\n\tc.SetKey(\"йцук\")\n\tif !h1.Cookie(&c) {\n\t\tt.Fatalf(\"cannot find cookie %q\", c.Key())\n\t}\n\tif !equalCookie(&expectedC2, &c) {\n\t\tt.Fatalf(\"unexpected cookie\\n%v\\nExpected\\n%v\\n\", &c, &expectedC2)\n\t}\n\n\th1.DelCookie(\"йцук\")\n\tif h.Cookie(&c) {\n\t\tt.Fatalf(\"Unexpected cookie found: %v\", &c)\n\t}\n\tif h1.Cookie(&c) {\n\t\tt.Fatalf(\"Unexpected cookie found: %v\", &c)\n\t}\n}\n\nfunc equalCookie(c1, c2 *protocol.Cookie) bool {\n\tif !bytes.Equal(c1.Key(), c2.Key()) {\n\t\treturn false\n\t}\n\tif !bytes.Equal(c1.Value(), c2.Value()) {\n\t\treturn false\n\t}\n\tif !c1.Expire().Equal(c2.Expire()) {\n\t\treturn false\n\t}\n\tif !bytes.Equal(c1.Domain(), c2.Domain()) {\n\t\treturn false\n\t}\n\tif !bytes.Equal(c1.Path(), c2.Path()) {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc TestResponseHeaderMultiLineValue(t *testing.T) {\n\ts := \"HTTP/1.1 200 OK\\r\\n\" +\n\t\t\"EmptyValue1:\\r\\n\" +\n\t\t\"Content-Type: foo/bar;\\r\\n\\tnewline;\\r\\n another/newline\\r\\n\" +\n\t\t\"Foo: Bar\\r\\n\" +\n\t\t\"Multi-Line: one;\\r\\n two\\r\\n\" +\n\t\t\"Values: v1;\\r\\n v2; v3;\\r\\n v4;\\tv5\\r\\n\" +\n\t\t\"\\r\\n\"\n\n\theader := new(protocol.ResponseHeader)\n\tif _, err := parse(header, []byte(s)); err != nil {\n\t\tt.Fatalf(\"parse headers with multi-line values failed, %s\", err)\n\t}\n\tresponse, err := http.ReadResponse(bufio.NewReader(strings.NewReader(s)), nil)\n\tif err != nil {\n\t\tt.Fatalf(\"parse response using net/http failed, %s\", err)\n\t}\n\n\tfor name, vals := range response.Header {\n\t\tgot := string(header.Peek(name))\n\t\twant := vals[0]\n\n\t\tif got != want {\n\t\t\tt.Errorf(\"unexpected %s got: %q want: %q\", name, got, want)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/http1/resp/response.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage resp\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"runtime\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/ext\"\n)\n\n// ErrBodyStreamWritePanic is returned when panic happens during writing body stream.\ntype ErrBodyStreamWritePanic struct {\n\terror\n}\n\ntype h1Response struct {\n\t*protocol.Response\n}\n\n// String returns request representation.\n//\n// Returns error message instead of request representation on error.\n//\n// Use Write instead of String for performance-critical code.\nfunc (h1Resp *h1Response) String() string {\n\tw := bytebufferpool.Get()\n\tzw := network.NewWriter(w)\n\tif err := Write(h1Resp.Response, zw); err != nil {\n\t\treturn err.Error()\n\t}\n\tif err := zw.Flush(); err != nil {\n\t\treturn err.Error()\n\t}\n\ts := string(w.B)\n\tbytebufferpool.Put(w)\n\treturn s\n}\n\nfunc GetHTTP1Response(resp *protocol.Response) fmt.Stringer {\n\treturn &h1Response{resp}\n}\n\n// ReadHeaders reads http header into *protocol.Response\nfunc ReadHeaders(resp *protocol.Response, r network.Reader) error {\n\tresp.ResetBody()\n\terr := ReadHeader(&resp.Header, r)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif resp.Header.StatusCode() == consts.StatusContinue {\n\t\t// Read the next response according to http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html .\n\t\tif err = ReadHeader(&resp.Header, r); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// ReadHeaderAndLimitBody ...\nfunc ReadHeaderAndLimitBody(resp *protocol.Response, r network.Reader, maxBodySize int) error {\n\tif err := ReadHeaders(resp, r); err != nil {\n\t\treturn err\n\t}\n\treturn ReadRespBody(resp, r, maxBodySize)\n}\n\n// ReadRespBody reads response body from the given r, limiting the body size.\n//\n// If maxBodySize > 0 and the body size exceeds maxBodySize,\n// then ErrBodyTooLarge is returned.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc ReadRespBody(resp *protocol.Response, r network.Reader, maxBodySize int) (err error) {\n\tif resp.MustSkipBody() {\n\t\treturn nil\n\t}\n\tbodyBuf := resp.BodyBuffer()\n\tbodyBuf.Reset()\n\tbodyBuf.B, err = ext.ReadBody(r, resp.Header.ContentLength(), maxBodySize, bodyBuf.B)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif resp.Header.ContentLength() == -1 {\n\t\terr = ext.ReadTrailer(resp.Header.Trailer(), r)\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn err\n\t\t}\n\t}\n\tresp.Header.SetContentLength(len(bodyBuf.B))\n\treturn nil\n}\n\ntype clientRespStream struct {\n\tmu sync.Mutex\n\n\tr             io.Reader\n\tcloseCallback func(shouldClose bool) error\n}\n\n// ForceClose closes underlying conn. It enables `Read` call to return instead of blocking.\n//\n// This method is ONLY used by hertz internally.\n// Normally, users call `Close` when the body is no longer used.\nfunc (c *clientRespStream) ForceClose() (err error) {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tif c.closeCallback != nil {\n\t\terr = c.closeCallback(true)\n\t\tc.closeCallback = nil\n\t}\n\t// NOTE: DO NOT put back to pool here,\n\t// user may still use clientRespStream and call Close() like `defer body.Close()`\n\treturn\n}\n\n// Close closes response stream gracefully.\n//\n// NOTE:\n// * Since Close() will put it back to pool, MUST ensure it only be called when no longer use.\n// * MUST NOT call Close() and `Read()` concurrently to avoid race issue\nfunc (c *clientRespStream) Close() (err error) {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\truntime.SetFinalizer(c, nil)\n\t// If error happened in ReleaseBodyStream, the connection may be in abnormal state.\n\t// Close it in the callback in order to avoid other unexpected problems.\n\terr = ext.ReleaseBodyStream(c.r)\n\tif c.closeCallback != nil {\n\t\tif err != nil {\n\t\t\thlog.SystemLogger().Warnf(\"error occurred during the stream body close: %s\", err)\n\t\t}\n\t\terr = c.closeCallback(err != nil)\n\t}\n\tc.r = nil\n\tc.closeCallback = nil\n\tclientRespStreamPool.Put(c)\n\treturn\n}\n\nfunc (c *clientRespStream) Read(p []byte) (n int, err error) {\n\treturn c.r.Read(p)\n}\n\nvar clientRespStreamPool = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn &clientRespStream{}\n\t},\n}\n\nfunc convertClientRespStream(bs io.Reader, fn func(shouldClose bool) error) *clientRespStream {\n\tclientStream := clientRespStreamPool.Get().(*clientRespStream)\n\tclientStream.r = bs\n\tclientStream.closeCallback = fn\n\truntime.SetFinalizer(clientStream, (*clientRespStream).Close)\n\treturn clientStream\n}\n\n// ReadHeaderBodyStream ...\nfunc ReadHeaderBodyStream(resp *protocol.Response, r network.Reader,\n\tmaxBodySize int, closeCallBack func(shouldClose bool) error) error {\n\tif err := ReadHeaders(resp, r); err != nil {\n\t\treturn err\n\t}\n\treturn ReadRespBodyStream(resp, r, maxBodySize, closeCallBack)\n}\n\n// Deprecated: use ReadHeaderBodyStream\nvar ReadBodyStream = ReadHeaderBodyStream\n\n// ReadRespBodyStream reads response body in stream\nfunc ReadRespBodyStream(resp *protocol.Response, r network.Reader,\n\tmaxBodySize int, closeCallBack func(shouldClose bool) error) (err error) {\n\tif resp.MustSkipBody() {\n\t\treturn nil\n\t}\n\tbodyBuf := resp.BodyBuffer()\n\tbodyBuf.Reset()\n\tbodyBuf.B, err = ext.ReadBodyWithStreaming(r, resp.Header.ContentLength(), maxBodySize, bodyBuf.B)\n\tif err != nil {\n\t\tif errors.Is(err, errs.ErrBodyTooLarge) {\n\t\t\tbodyStream := ext.AcquireBodyStream(bodyBuf, r, resp.Header.Trailer(), resp.Header.ContentLength())\n\t\t\tresp.ConstructBodyStream(bodyBuf, convertClientRespStream(bodyStream, closeCallBack))\n\t\t\treturn nil\n\t\t}\n\n\t\tif errors.Is(err, errs.ErrChunkedStream) {\n\t\t\tbodyStream := ext.AcquireBodyStream(bodyBuf, r, resp.Header.Trailer(), -1)\n\t\t\tresp.ConstructBodyStream(bodyBuf, convertClientRespStream(bodyStream, closeCallBack))\n\t\t\treturn nil\n\t\t}\n\n\t\tresp.Reset()\n\t\treturn err\n\t}\n\tbodyStream := ext.AcquireBodyStream(bodyBuf, r, resp.Header.Trailer(), resp.Header.ContentLength())\n\tresp.ConstructBodyStream(bodyBuf, convertClientRespStream(bodyStream, closeCallBack))\n\treturn nil\n}\n\n// Read reads response (including body) from the given r.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc Read(resp *protocol.Response, r network.Reader) error {\n\treturn ReadHeaderAndLimitBody(resp, r, 0)\n}\n\n// Write writes response to w.\n//\n// Write doesn't flush response to w for performance reasons.\n//\n// See also WriteTo.\nfunc Write(resp *protocol.Response, w network.Writer) error {\n\tsendBody := !resp.MustSkipBody()\n\n\tif resp.IsBodyStream() {\n\t\treturn writeBodyStream(resp, w, sendBody)\n\t}\n\n\tbody := resp.BodyBytes()\n\tbodyLen := len(body)\n\tif sendBody || bodyLen > 0 {\n\t\tresp.Header.SetContentLength(bodyLen)\n\t}\n\n\theader := resp.Header.Header()\n\t_, err := w.WriteBinary(header)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp.Header.SetHeaderLength(len(header))\n\t// Write body\n\tif sendBody && bodyLen > 0 {\n\t\t_, err = w.WriteBinary(body)\n\t}\n\treturn err\n}\n\nfunc writeBodyStream(resp *protocol.Response, w network.Writer, sendBody bool) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = &ErrBodyStreamWritePanic{\n\t\t\t\terror: fmt.Errorf(\"panic while writing body stream: %+v\", r),\n\t\t\t}\n\t\t}\n\t}()\n\n\tcontentLength := resp.Header.ContentLength()\n\tif contentLength < 0 {\n\t\tlrSize := ext.LimitedReaderSize(resp.BodyStream())\n\t\tif lrSize >= 0 {\n\t\t\tcontentLength = int(lrSize)\n\t\t\tif int64(contentLength) != lrSize {\n\t\t\t\tcontentLength = -1\n\t\t\t}\n\t\t\tif contentLength >= 0 {\n\t\t\t\tresp.Header.SetContentLength(contentLength)\n\t\t\t}\n\t\t}\n\t}\n\tif contentLength >= 0 {\n\t\tif err = WriteHeader(&resp.Header, w); err == nil && sendBody {\n\t\t\tif resp.ImmediateHeaderFlush {\n\t\t\t\terr = w.Flush()\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\terr = ext.WriteBodyFixedSize(w, resp.BodyStream(), int64(contentLength))\n\t\t\t}\n\t\t}\n\t} else {\n\t\tresp.Header.SetContentLength(-1)\n\t\tif err = WriteHeader(&resp.Header, w); err == nil && sendBody {\n\t\t\tif resp.ImmediateHeaderFlush {\n\t\t\t\terr = w.Flush()\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\terr = ext.WriteBodyChunked(w, resp.BodyStream())\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\terr = ext.WriteTrailer(resp.Header.Trailer(), w)\n\t\t\t}\n\t\t}\n\t}\n\terr1 := resp.CloseBodyStream()\n\tif err == nil {\n\t\terr = err1\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/protocol/http1/resp/response_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage resp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/ext\"\n\t\"github.com/cloudwego/netpoll\"\n)\n\nvar errBodyTooLarge = errs.New(errs.ErrBodyTooLarge, errs.ErrorTypePublic, \"test\")\n\ntype ErroneousBodyStream struct {\n\terrOnRead  bool\n\terrOnClose bool\n}\n\ntype testReader struct {\n\tread chan (int)\n\tcb   chan (struct{})\n}\n\nfunc (r *testReader) Read(b []byte) (int, error) {\n\tread := <-r.read\n\n\tif read == -1 {\n\t\treturn 0, io.EOF\n\t}\n\n\tr.cb <- struct{}{}\n\n\ttotal := len(b)\n\tif total > read {\n\t\ttotal = read\n\t}\n\n\tfor i := 0; i < total; i++ {\n\t\tb[i] = 'x'\n\t}\n\n\treturn total, nil\n}\n\nfunc (ebs *ErroneousBodyStream) Read(p []byte) (n int, err error) {\n\tif ebs.errOnRead {\n\t\tpanic(\"reading erroneous body stream\")\n\t}\n\treturn 0, io.EOF\n}\n\nfunc (ebs *ErroneousBodyStream) Close() error {\n\tif ebs.errOnClose {\n\t\tpanic(\"closing erroneous body stream\")\n\t}\n\treturn nil\n}\n\nfunc TestResponseBodyStreamErrorOnPanicDuringClose(t *testing.T) {\n\tt.Parallel()\n\tvar resp protocol.Response\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\n\tebs := &ErroneousBodyStream{errOnRead: false, errOnClose: true}\n\tresp.SetBodyStream(ebs, 42)\n\terr := Write(&resp, zw)\n\tif err == nil {\n\t\tt.Fatalf(\"expected error when writing response.\")\n\t}\n\te, ok := err.(*ErrBodyStreamWritePanic)\n\tif !ok {\n\t\tt.Fatalf(\"expected error struct to be *ErrBodyStreamWritePanic, got: %+v.\", e)\n\t}\n\tif e.Error() != \"panic while writing body stream: closing erroneous body stream\" {\n\t\tt.Fatalf(\"unexpected error value, got: %+v.\", e.Error())\n\t}\n}\n\nfunc TestResponseBodyStreamErrorOnPanicDuringRead(t *testing.T) {\n\tt.Parallel()\n\tvar resp protocol.Response\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\n\tebs := &ErroneousBodyStream{errOnRead: true, errOnClose: false}\n\tresp.SetBodyStream(ebs, 42)\n\terr := Write(&resp, zw)\n\tif err == nil {\n\t\tt.Fatalf(\"expected error when writing response.\")\n\t}\n\te, ok := err.(*ErrBodyStreamWritePanic)\n\tif !ok {\n\t\tt.Fatalf(\"expected error struct to be *ErrBodyStreamWritePanic, got: %+v.\", e)\n\t}\n\tif e.Error() != \"panic while writing body stream: reading erroneous body stream\" {\n\t\tt.Fatalf(\"unexpected error value, got: %+v.\", e.Error())\n\t}\n}\n\nfunc testResponseReadError(t *testing.T, resp *protocol.Response, response string) {\n\tzr := mock.NewZeroCopyReader(response)\n\terr := Read(resp, zr)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error for response=%q\", response)\n\t}\n\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 303 Redisred sedfs sdf\\r\\nContent-Type: aaa\\r\\nContent-Length: 5\\r\\n\\r\\nHELLOaaa\",\n\t\tconsts.StatusSeeOther, 5, \"aaa\", \"HELLO\", nil, consts.HTTP11)\n}\n\nfunc testResponseReadSuccess(t *testing.T, resp *protocol.Response, response string, expectedStatusCode, expectedContentLength int,\n\texpectedContentType, expectedBody string, expectedTrailer map[string]string, expectedProtocol string,\n) {\n\tzr := mock.NewZeroCopyReader(response)\n\terr := Read(resp, zr)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\n\tverifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, \"\", expectedProtocol)\n\tif !bytes.Equal(resp.Body(), []byte(expectedBody)) {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", resp.Body(), []byte(expectedBody))\n\t}\n\tverifyResponseTrailer(t, &resp.Header, expectedTrailer)\n}\n\nfunc TestResponseReadSuccess(t *testing.T) {\n\tt.Parallel()\n\n\tresp := &protocol.Response{}\n\n\t// usual response\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Length: 10\\r\\nContent-Type: foo/bar\\r\\n\\r\\n0123456789\",\n\t\tconsts.StatusOK, 10, \"foo/bar\", \"0123456789\", nil, consts.HTTP11)\n\n\t// zero response\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 500 OK\\r\\nContent-Length: 0\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\",\n\t\tconsts.StatusInternalServerError, 0, \"foo/bar\", \"\", nil, consts.HTTP11)\n\n\t// response with trailer\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 300 OK\\r\\nTransfer-Encoding: chunked\\r\\nTrailer: foo\\r\\nContent-Type: bar\\r\\n\\r\\n5\\r\\n56789\\r\\n0\\r\\nfoo: bar\\r\\n\\r\\n\",\n\t\tconsts.StatusMultipleChoices, 5, \"bar\", \"56789\", map[string]string{\"Foo\": \"bar\"}, consts.HTTP11)\n\n\t// response with trailer disableNormalizing\n\tresp.Header.DisableNormalizing()\n\tresp.Header.Trailer().DisableNormalizing()\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 300 OK\\r\\nTransfer-Encoding: chunked\\r\\nTrailer: foo\\r\\nContent-Type: bar\\r\\n\\r\\n5\\r\\n56789\\r\\n0\\r\\nfoo: bar\\r\\n\\r\\n\",\n\t\tconsts.StatusMultipleChoices, 5, \"bar\", \"56789\", map[string]string{\"foo\": \"bar\"}, consts.HTTP11)\n\n\t// no content-length ('identity' transfer-encoding)\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: foobar\\r\\n\\r\\nzxxxx\",\n\t\tconsts.StatusOK, 5, \"foobar\", \"zxxxx\", nil, consts.HTTP11)\n\n\t// explicitly stated 'Transfer-Encoding: identity'\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 234 ss\\r\\nContent-Type: xxx\\r\\n\\r\\nxag\",\n\t\t234, 3, \"xxx\", \"xag\", nil, consts.HTTP11)\n\n\t// big 'identity' response\n\tbody := string(mock.CreateFixedBody(100500))\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\n\\r\\n\"+body,\n\t\tconsts.StatusOK, 100500, \"aa\", body, nil, consts.HTTP11)\n\n\t// chunked response\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\nTrailer: Foo2\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n4\\r\\nqwer\\r\\n2\\r\\nty\\r\\n0\\r\\nFoo2: bar2\\r\\n\\r\\n\",\n\t\t200, 6, \"text/html\", \"qwerty\", map[string]string{\"Foo2\": \"bar2\"}, consts.HTTP11)\n\n\t// chunked response with non-chunked Transfer-Encoding.\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 230 OK\\r\\nContent-Type: text\\r\\nTrailer: Foo3\\r\\nTransfer-Encoding: aaabbb\\r\\n\\r\\n2\\r\\ner\\r\\n2\\r\\nty\\r\\n0\\r\\nFoo3: bar3\\r\\n\\r\\n\",\n\t\t230, 4, \"text\", \"erty\", map[string]string{\"Foo3\": \"bar3\"}, consts.HTTP11)\n\n\t// chunked response with empty body\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\nTrailer: Foo5\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n0\\r\\nFoo5: bar5\\r\\n\\r\\n\",\n\t\tconsts.StatusOK, 0, \"text/html\", \"\", map[string]string{\"Foo5\": \"bar5\"}, consts.HTTP11)\n}\n\nfunc TestResponseReadError(t *testing.T) {\n\tt.Parallel()\n\n\tresp := &protocol.Response{}\n\n\t// empty response\n\ttestResponseReadError(t, resp, \"\")\n\n\t// invalid header\n\ttestResponseReadError(t, resp, \"foobar\")\n\n\t// empty body\n\ttestResponseReadError(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: aaa\\r\\nContent-Length: 1234\\r\\n\\r\\n\")\n\n\t// short body\n\ttestResponseReadError(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: aaa\\r\\nContent-Length: 1234\\r\\n\\r\\nshort\")\n}\n\nfunc TestResponseImmediateHeaderFlushChunked(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Response\n\n\tr.ImmediateHeaderFlush = true\n\n\tch := make(chan int)\n\tcb := make(chan struct{})\n\n\tbuf := &testReader{read: ch, cb: cb}\n\n\tr.SetBodyStream(buf, -1)\n\n\tw := bytes.NewBuffer([]byte{})\n\tzw := netpoll.NewWriter(w)\n\n\twaitForIt := make(chan struct{})\n\n\tgo func() {\n\t\tif err := Write(&r, zw); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t}\n\n\t\twaitForIt <- struct{}{}\n\t}()\n\n\tch <- 3\n\n\tif !strings.Contains(w.String(), \"Transfer-Encoding: chunked\") {\n\t\tt.Fatalf(\"Expected headers to be flushed\")\n\t}\n\n\tif strings.Contains(w.String(), \"xxx\") {\n\t\tt.Fatalf(\"Did not expect body to be written yet\")\n\t}\n\n\t<-cb\n\tch <- -1\n\n\t<-waitForIt\n}\n\nfunc TestResponseImmediateHeaderFlushFixedLength(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Response\n\n\tr.ImmediateHeaderFlush = true\n\n\tch := make(chan int)\n\tcb := make(chan struct{})\n\n\tbuf := &testReader{read: ch, cb: cb}\n\n\tr.SetBodyStream(buf, 3)\n\n\tw := bytes.NewBuffer([]byte{})\n\tzw := netpoll.NewWriter(w)\n\n\twaitForIt := make(chan struct{})\n\n\tgo func() {\n\t\tif err := Write(&r, zw); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t}\n\t\twaitForIt <- struct{}{}\n\t}()\n\n\t// reader have more data than bodySize, but only the bodySize length of data will be send.\n\tch <- 10\n\n\tif !strings.Contains(w.String(), \"Content-Length: 3\") {\n\t\tt.Fatalf(\"Expected headers to be flushed\")\n\t}\n\n\tif strings.Contains(w.String(), \"xxx\") {\n\t\tt.Fatalf(\"Did not expect body to be written yet\")\n\t}\n\n\t<-cb\n\t// ch <- -1\n\n\t<-waitForIt\n}\n\nfunc TestResponseImmediateHeaderFlushFixedLengthWithFewerData(t *testing.T) {\n\tt.Parallel()\n\n\tvar r protocol.Response\n\n\tr.ImmediateHeaderFlush = true\n\n\tch := make(chan int)\n\tcb := make(chan struct{})\n\n\tbuf := &testReader{read: ch, cb: cb}\n\n\tr.SetBodyStream(buf, 3)\n\n\tw := bytes.NewBuffer([]byte{})\n\tzw := netpoll.NewWriter(w)\n\n\twaitForIt := make(chan struct{})\n\n\tgo func() {\n\t\tif err := Write(&r, zw); err != nil {\n\t\t\tassert.NotNil(t, err)\n\t\t}\n\t\twaitForIt <- struct{}{}\n\t}()\n\n\t// reader have less data than bodySize, server should raise a error in this case\n\tch <- 2\n\n\t<-cb\n\tch <- -1\n\n\t<-waitForIt\n}\n\nfunc TestResponseSuccess(t *testing.T) {\n\tt.Parallel()\n\n\t// 200 response\n\ttestResponseSuccess(t, consts.StatusOK, \"test/plain\", \"server\", \"foobar\",\n\t\tconsts.StatusOK, \"test/plain\", \"server\")\n\n\t// response with missing statusCode\n\ttestResponseSuccess(t, 0, \"text/plain\", \"server\", \"foobar\",\n\t\tconsts.StatusOK, \"text/plain\", \"server\")\n\n\t// response with missing server\n\ttestResponseSuccess(t, consts.StatusInternalServerError, \"aaa\", \"\", \"aaadfsd\",\n\t\tconsts.StatusInternalServerError, \"aaa\", \"\")\n\n\t// empty body\n\ttestResponseSuccess(t, consts.StatusOK, \"bbb\", \"qwer\", \"\",\n\t\tconsts.StatusOK, \"bbb\", \"qwer\")\n\n\t// missing content-type\n\ttestResponseSuccess(t, consts.StatusOK, \"\", \"asdfsd\", \"asdf\",\n\t\tconsts.StatusOK, string(bytestr.DefaultContentType), \"asdfsd\")\n}\n\nfunc TestResponseReadLimitBody(t *testing.T) {\n\tt.Parallel()\n\n\t// response with content-length\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 10\\r\\n\\r\\n9876543210\", 10)\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 10\\r\\n\\r\\n9876543210\", 100)\n\ttestResponseReadLimitBodyError(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 10\\r\\n\\r\\n9876543210\", 9)\n\t// response with content-encoding\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Encoding: gzip\\r\\n\\r\\n9876543210\", 10)\n\t// chunked response\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 9)\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\nFoo: bar\\r\\n\\r\\n\", 9)\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 100)\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\nfoobar\\r\\n\\r\\n\", 100)\n\ttestResponseReadLimitBodyError(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 2)\n\n\t// identity response\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 400 OK\\r\\nContent-Type: aa\\r\\n\\r\\n123456\", 6)\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 400 OK\\r\\nContent-Type: aa\\r\\n\\r\\n123456\", 106)\n\ttestResponseReadLimitBodyError(t, \"HTTP/1.1 400 OK\\r\\nContent-Type: aa\\r\\n\\r\\n123456\", 5)\n}\n\nfunc TestResponseReadWithoutBody(t *testing.T) {\n\tvar resp protocol.Response\n\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP/1.1 304 Not Modified\\r\\nContent-Type: aa\\r\\nContent-Encoding: gzip\\r\\nContent-Length: 1235\\r\\n\\r\\n\", false,\n\t\tconsts.StatusNotModified, 1235, \"aa\", nil, \"gzip\", consts.HTTP11)\n\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP/1.1 200 Foo Bar\\r\\nContent-Type: aab\\r\\nTrailer: Foo\\r\\nContent-Encoding: deflate\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n0\\r\\nFoo: bar\\r\\n\\r\\nHTTP/1.2\", false,\n\t\tconsts.StatusOK, 0, \"aab\", map[string]string{\"Foo\": \"bar\"}, \"deflate\", consts.HTTP11)\n\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP/1.1 204 Foo Bar\\r\\nContent-Type: aab\\r\\nTrailer: Foo\\r\\nContent-Encoding: deflate\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n0\\r\\nFoo: bar\\r\\n\\r\\nHTTP/1.2\", true,\n\t\tconsts.StatusNoContent, -1, \"aab\", nil, \"deflate\", consts.HTTP11)\n\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP/1.1 123 AAA\\r\\nContent-Type: xxx\\r\\nContent-Encoding: gzip\\r\\nContent-Length: 3434\\r\\n\\r\\n\", false,\n\t\t123, 3434, \"xxx\", nil, \"gzip\", consts.HTTP11)\n\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP 200 OK\\r\\nContent-Type: text/xml\\r\\nContent-Encoding: deflate\\r\\nContent-Length: 123\\r\\n\\r\\nfoobar\\r\\n\", true,\n\t\tconsts.StatusOK, 123, \"text/xml\", nil, \"deflate\", consts.HTTP10)\n\n\t// '100 Continue' must be skipped.\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP/1.1 100 Continue\\r\\nFoo-bar: baz\\r\\n\\r\\nHTTP/1.1 329 aaa\\r\\nContent-Type: qwe\\r\\nContent-Encoding: gzip\\r\\nContent-Length: 894\\r\\n\\r\\n\", true,\n\t\t329, 894, \"qwe\", nil, \"gzip\", consts.HTTP11)\n}\n\nfunc verifyResponseHeader(t *testing.T, h *protocol.ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType, expectedContentEncoding, expectedProtocol string) {\n\tif h.StatusCode() != expectedStatusCode {\n\t\tt.Fatalf(\"Unexpected status code %d. Expected %d\", h.StatusCode(), expectedStatusCode)\n\t}\n\tif h.ContentLength() != expectedContentLength {\n\t\tt.Fatalf(\"Unexpected content length %d. Expected %d\", h.ContentLength(), expectedContentLength)\n\t}\n\tif string(h.ContentType()) != expectedContentType {\n\t\tt.Fatalf(\"Unexpected content type %q. Expected %q\", h.ContentType(), expectedContentType)\n\t}\n\tif string(h.ContentEncoding()) != expectedContentEncoding {\n\t\tt.Fatalf(\"Unexpected content encoding %q. Expected %q\", h.ContentEncoding(), expectedContentEncoding)\n\t}\n\n\tif h.GetProtocol() != expectedProtocol {\n\t\tt.Fatalf(\"Unexpected protocol %q. Expected %q\", h.GetProtocol(), expectedProtocol)\n\t}\n}\n\nfunc testResponseSuccess(t *testing.T, statusCode int, contentType, serverName, body string,\n\texpectedStatusCode int, expectedContentType, expectedServerName string,\n) {\n\tvar resp protocol.Response\n\tresp.SetStatusCode(statusCode)\n\tresp.Header.Set(\"Content-Type\", contentType)\n\tresp.Header.Set(\"Server\", serverName)\n\tresp.SetBody([]byte(body))\n\n\tw := &bytes.Buffer{}\n\t// bw := bufio.NewWriter(w)\n\tzw := netpoll.NewWriter(w)\n\terr := Write(&resp, zw)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when calling Response.Write(): %s\", err)\n\t}\n\n\tif err = zw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error when flushing bufio.Writer: %s\", err)\n\t}\n\n\tvar resp1 protocol.Response\n\tbr := bufio.NewReader(w)\n\tzr := netpoll.NewReader(br)\n\tif err = Read(&resp1, zr); err != nil {\n\t\tt.Fatalf(\"Unexpected error when calling Response.Read(): %s\", err)\n\t}\n\tif resp1.StatusCode() != expectedStatusCode {\n\t\tt.Fatalf(\"Unexpected status code: %d. Expected %d\", resp1.StatusCode(), expectedStatusCode)\n\t}\n\tif resp1.Header.ContentLength() != len(body) {\n\t\tt.Fatalf(\"Unexpected content-length: %d. Expected %d\", resp1.Header.ContentLength(), len(body))\n\t}\n\tif string(resp1.Header.Peek(consts.HeaderContentType)) != expectedContentType {\n\t\tt.Fatalf(\"Unexpected content-type: %q. Expected %q\", resp1.Header.Peek(consts.HeaderContentType), expectedContentType)\n\t}\n\tif string(resp1.Header.Peek(consts.HeaderServer)) != expectedServerName {\n\t\tt.Fatalf(\"Unexpected server: %q. Expected %q\", resp1.Header.Peek(consts.HeaderServer), expectedServerName)\n\t}\n\tif !bytes.Equal(resp1.Body(), []byte(body)) {\n\t\tt.Fatalf(\"Unexpected body: %q. Expected %q\", resp1.Body(), body)\n\t}\n}\n\nfunc testResponseReadWithoutBody(t *testing.T, resp *protocol.Response, s string, skipBody bool,\n\texpectedStatusCode, expectedContentLength int, expectedContentType string, expectedTrailer map[string]string, expectedContentEncoding, expectedProtocol string,\n) {\n\tzr := mock.NewZeroCopyReader(s)\n\tresp.SkipBody = skipBody\n\terr := Read(resp, zr)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading response without body: %s. response=%q\", err, s)\n\t}\n\tif len(resp.Body()) != 0 {\n\t\tt.Fatalf(\"Unexpected response body %q. Expected %q. response=%q\", resp.Body(), \"\", s)\n\t}\n\n\tverifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, expectedContentEncoding, expectedProtocol)\n\tverifyResponseTrailer(t, &resp.Header, expectedTrailer)\n\n\t// verify that ordinal response is read after null-body response\n\tresp.SkipBody = false\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 300 OK\\r\\nContent-Length: 5\\r\\nContent-Type: bar\\r\\n\\r\\n56789\",\n\t\tconsts.StatusMultipleChoices, 5, \"bar\", \"56789\", nil, consts.HTTP11)\n}\n\nfunc verifyResponseTrailer(t *testing.T, h *protocol.ResponseHeader, expectedTrailers map[string]string) {\n\tfor k, v := range expectedTrailers {\n\t\tgot := h.Trailer().Peek(k)\n\t\tif !bytes.Equal(got, []byte(v)) {\n\t\t\tt.Fatalf(\"Unexpected trailer %q. Expected %q. Got %q\", k, v, got)\n\t\t}\n\t}\n\n\th.Trailer().VisitAll(func(key, value []byte) {\n\t\tif v := expectedTrailers[string(key)]; string(value) != v {\n\t\t\tt.Fatalf(\"Unexpected trailer %q. Expected %q. Got %q\", string(key), v, string(value))\n\t\t}\n\t})\n}\n\nfunc testResponseReadLimitBodyError(t *testing.T, s string, maxBodySize int) {\n\tvar resp protocol.Response\n\tzr := netpoll.NewReader(bytes.NewBufferString(s))\n\terr := ReadHeaderAndLimitBody(&resp, zr, maxBodySize)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error. s=%q, maxBodySize=%d\", s, maxBodySize)\n\t}\n\tif !errors.Is(err, errs.ErrBodyTooLarge) {\n\t\tt.Fatalf(\"unexpected error: %s. Expecting %s. s=%q, maxBodySize=%d\", err, errBodyTooLarge, s, maxBodySize)\n\t}\n}\n\nfunc testResponseReadLimitBodySuccess(t *testing.T, s string, maxBodySize int) {\n\tvar resp protocol.Response\n\tmr := mock.NewZeroCopyReader(s)\n\tif err := ReadHeaderAndLimitBody(&resp, mr, maxBodySize); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s. s=%q, maxBodySize=%d\", err, s, maxBodySize)\n\t}\n}\n\nfunc TestResponseBodyStreamWithTrailer(t *testing.T) {\n\tt.Parallel()\n\n\ttestResponseBodyStreamWithTrailer(t, nil, false)\n\n\tbody := mock.CreateFixedBody(1e5)\n\ttestResponseBodyStreamWithTrailer(t, body, false)\n\ttestResponseBodyStreamWithTrailer(t, body, true)\n}\n\nfunc testResponseBodyStreamWithTrailer(t *testing.T, body []byte, disableNormalizing bool) {\n\texpectedTrailer := map[string]string{\n\t\t\"foo\": \"testfoo\",\n\t\t\"bar\": \"testbar\",\n\t}\n\tvar resp1 protocol.Response\n\tif disableNormalizing {\n\t\tresp1.Header.DisableNormalizing()\n\t}\n\tresp1.SetBodyStream(bytes.NewReader(body), -1)\n\tfor k, v := range expectedTrailer {\n\t\terr := resp1.Header.Trailer().Add(k, v)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t}\n\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\tif err := Write(&resp1, zw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif err := zw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tvar resp2 protocol.Response\n\tif disableNormalizing {\n\t\tresp2.Header.DisableNormalizing()\n\t}\n\tbr := bufio.NewReader(&w)\n\tzr := netpoll.NewReader(br)\n\tif err := Read(&resp2, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\trespBody := resp2.Body()\n\tif !bytes.Equal(respBody, body) {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", respBody, body)\n\t}\n\n\tfor k, v := range expectedTrailer {\n\t\tkBytes := []byte(k)\n\t\tutils.NormalizeHeaderKey(kBytes, disableNormalizing)\n\t\tr := resp2.Header.Trailer().Peek(k)\n\t\tif string(r) != v {\n\t\t\tt.Fatalf(\"unexpected trailer header %q: %q. Expecting %s\", kBytes, r, v)\n\t\t}\n\t}\n}\n\nfunc TestResponseReadBodyStreamBadReader(t *testing.T) {\n\tt.Parallel()\n\n\tresp := protocol.AcquireResponse()\n\n\terrReader := mock.NewErrorReadConn(errors.New(\"test error\"))\n\n\tbodyBuf := resp.BodyBuffer()\n\tbodyBuf.Reset()\n\n\tbodyStream := ext.AcquireBodyStream(bodyBuf, errReader, resp.Header.Trailer(), 100)\n\tresp.ConstructBodyStream(bodyBuf, convertClientRespStream(bodyStream, func(shouldClose bool) error {\n\t\tassert.True(t, shouldClose)\n\t\treturn nil\n\t}))\n\n\tstBody := resp.BodyStream()\n\tcloser, _ := stBody.(io.Closer)\n\tcloser.Close()\n}\n\nfunc TestSetResponseBodyStreamFixedSize(t *testing.T) {\n\tt.Parallel()\n\n\ttestSetResponseBodyStream(t, \"a\")\n\ttestSetResponseBodyStream(t, string(mock.CreateFixedBody(4097)))\n\ttestSetResponseBodyStream(t, string(mock.CreateFixedBody(100500)))\n}\n\nfunc TestSetResponseBodyStreamChunked(t *testing.T) {\n\tt.Parallel()\n\n\ttestSetResponseBodyStreamChunked(t, \"\", map[string]string{\"Foo\": \"bar\"})\n\n\tbody := \"foobar baz aaa bbb ccc\"\n\ttestSetResponseBodyStreamChunked(t, body, nil)\n\n\tbody = string(mock.CreateFixedBody(10001))\n\ttestSetResponseBodyStreamChunked(t, body, map[string]string{\"Foo\": \"test\", \"Bar\": \"test\"})\n}\n\nfunc testSetResponseBodyStream(t *testing.T, body string) {\n\tvar resp protocol.Response\n\tbodySize := len(body)\n\tif resp.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tresp.SetBodyStream(bytes.NewBufferString(body), bodySize)\n\tif !resp.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\tif err := Write(&resp, zw); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing response: %s. body=%q\", err, body)\n\t}\n\tif err := zw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error when flushing response: %s. body=%q\", err, body)\n\t}\n\n\tvar resp1 protocol.Response\n\tbr := bufio.NewReader(&w)\n\tzr := netpoll.NewReader(br)\n\tif err := Read(&resp1, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error when reading response: %s. body=%q\", err, body)\n\t}\n\tif string(resp1.Body()) != body {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", resp1.Body(), body)\n\t}\n}\n\nfunc testSetResponseBodyStreamChunked(t *testing.T, body string, trailer map[string]string) {\n\tvar resp protocol.Response\n\tif resp.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tresp.SetBodyStream(bytes.NewBufferString(body), -1)\n\tif !resp.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\n\tvar w bytes.Buffer\n\tzw := netpoll.NewWriter(&w)\n\tfor k, v := range trailer {\n\t\terr := resp.Header.Trailer().Add(k, v)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t}\n\t}\n\tif err := Write(&resp, zw); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing response: %s. body=%q\", err, body)\n\t}\n\tif err := zw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error when flushing response: %s. body=%q\", err, body)\n\t}\n\tvar resp1 protocol.Response\n\tbr := bufio.NewReader(&w)\n\tzr := netpoll.NewReader(br)\n\tif err := Read(&resp1, zr); err != nil {\n\t\tt.Fatalf(\"unexpected error when reading response: %s. body=%q\", err, body)\n\t}\n\tif string(resp1.Body()) != body {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", resp1.Body(), body)\n\t}\n\tfor k, v := range trailer {\n\t\tr := resp.Header.Trailer().Peek(k)\n\t\tif string(r) != v {\n\t\t\tt.Fatalf(\"unexpected trailer %s. Expecting %s. Got %q\", k, v, r)\n\t\t}\n\t}\n}\n\nfunc testResponseReadBodyStreamSuccess(t *testing.T, resp *protocol.Response, response string, expectedStatusCode, expectedContentLength int,\n\texpectedContentType, expectedBody string, expectedTrailer map[string]string, expectedProtocol string,\n) {\n\tzr := mock.NewZeroCopyReader(response)\n\terr := ReadHeaderBodyStream(resp, zr, 0, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tassert.True(t, resp.IsBodyStream())\n\n\tbody, err := ioutil.ReadAll(resp.BodyStream())\n\tif err != nil && err != io.EOF {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tverifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, \"\", expectedProtocol)\n\tif !bytes.Equal(body, []byte(expectedBody)) {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", resp.Body(), []byte(expectedBody))\n\t}\n\tverifyResponseTrailer(t, &resp.Header, expectedTrailer)\n}\n\nfunc testResponseReadBodyStreamBadTrailer(t *testing.T, resp *protocol.Response, response string) {\n\tzr := mock.NewZeroCopyReader(response)\n\terr := ReadHeaderBodyStream(resp, zr, 0, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tassert.True(t, resp.IsBodyStream())\n\n\t_, err = ioutil.ReadAll(resp.BodyStream())\n\tif err == nil || err == io.EOF {\n\t\tt.Fatalf(\"expected error when reading response.\")\n\t}\n}\n\nfunc TestResponseReadBodyStream(t *testing.T) {\n\tt.Parallel()\n\n\tresp := &protocol.Response{}\n\n\t// usual response\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Length: 10\\r\\nContent-Type: foo/bar\\r\\n\\r\\n0123456789\",\n\t\tconsts.StatusOK, 10, \"foo/bar\", \"0123456789\", nil, consts.HTTP11)\n\n\t// zero response\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 500 OK\\r\\nContent-Length: 0\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\",\n\t\tconsts.StatusInternalServerError, 0, \"foo/bar\", \"\", nil, consts.HTTP11)\n\n\t// response with trailer\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 300 OK\\r\\nTransfer-Encoding: chunked\\r\\nTrailer: Foo\\r\\nContent-Type: bar\\r\\n\\r\\n5\\r\\n56789\\r\\n0\\r\\nfoo: bar\\r\\n\\r\\n\",\n\t\tconsts.StatusMultipleChoices, -1, \"bar\", \"56789\", map[string]string{\"Foo\": \"bar\"}, consts.HTTP11)\n\n\tbodyWithLongLength := strings.Repeat(\"1\", 8*1024+1)\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Length: 8193\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\"+bodyWithLongLength,\n\t\tconsts.StatusOK, 8193, \"foo/bar\", bodyWithLongLength, nil, consts.HTTP11)\n\n\t// response with trailer disableNormalizing\n\tresp.Header.DisableNormalizing()\n\tresp.Header.Trailer().DisableNormalizing()\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 300 OK\\r\\nTransfer-Encoding: chunked\\r\\nTrailer: foo\\r\\nContent-Type: bar\\r\\n\\r\\n5\\r\\n56789\\r\\n0\\r\\nfoo: bar\\r\\n\\r\\n\",\n\t\tconsts.StatusMultipleChoices, -1, \"bar\", \"56789\", map[string]string{\"foo\": \"bar\"}, consts.HTTP11)\n\n\t// no content-length ('identity' transfer-encoding)\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: foobar\\r\\n\\r\\nzxxxx\",\n\t\tconsts.StatusOK, -2, \"foobar\", \"zxxxx\", nil, consts.HTTP11)\n\n\t// explicitly stated 'Transfer-Encoding: identity'\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 234 ss\\r\\nContent-Type: xxx\\r\\n\\r\\nxag\",\n\t\t234, -2, \"xxx\", \"xag\", nil, consts.HTTP11)\n\n\t// big 'identity' response\n\tbody := string(mock.CreateFixedBody(100500))\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\n\\r\\n\"+body,\n\t\tconsts.StatusOK, -2, \"aa\", body, nil, consts.HTTP11)\n\n\t// chunked response\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\nTrailer: Foo2\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n4\\r\\nqwer\\r\\n2\\r\\nty\\r\\n0\\r\\nFoo2: bar2\\r\\n\\r\\n\",\n\t\t200, -1, \"text/html\", \"qwerty\", map[string]string{\"Foo2\": \"bar2\"}, consts.HTTP11)\n\n\t// chunked response with non-chunked Transfer-Encoding.\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 230 OK\\r\\nContent-Type: text\\r\\nTrailer: Foo3\\r\\nTransfer-Encoding: aaabbb\\r\\n\\r\\n2\\r\\ner\\r\\n2\\r\\nty\\r\\n0\\r\\nFoo3: bar3\\r\\n\\r\\n\",\n\t\t230, -1, \"text\", \"erty\", map[string]string{\"Foo3\": \"bar3\"}, consts.HTTP11)\n\n\t// chunked response with empty body\n\ttestResponseReadBodyStreamSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\nTrailer: Foo5\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n0\\r\\nFoo5: bar5\\r\\n\\r\\n\",\n\t\tconsts.StatusOK, -1, \"text/html\", \"\", map[string]string{\"Foo5\": \"bar5\"}, consts.HTTP11)\n}\n\nfunc TestResponseReadBodyStreamBadTrailer(t *testing.T) {\n\tt.Parallel()\n\n\tresp := &protocol.Response{}\n\n\ttestResponseReadBodyStreamBadTrailer(t, resp, \"HTTP/1.1 300 OK\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: bar\\r\\n\\r\\n5\\r\\n56789\\r\\n0\\r\\ncontent-type: bar\\r\\n\\r\\n\")\n\ttestResponseReadBodyStreamBadTrailer(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n4\\r\\nqwer\\r\\n2\\r\\nty\\r\\n0\\r\\nproxy-connection: bar2\\r\\n\\r\\n\")\n}\n"
  },
  {
    "path": "pkg/protocol/http1/resp/writer.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage resp\n\nimport (\n\t\"errors\"\n\t\"runtime\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/ext\"\n)\n\nvar chunkWriterPool sync.Pool\n\nfunc init() {\n\tchunkWriterPool = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\treturn &chunkedBodyWriter{}\n\t\t},\n\t}\n}\n\ntype chunkedBodyWriter struct {\n\tr *protocol.Response\n\tw network.Writer\n\n\terr         error\n\tfinalized   bool\n\twroteHeader bool\n}\n\nvar errChunkedFinished = errors.New(\"chunked response is finished; no more data will be written.\")\n\n// Write implements network.ExtWriter.Write / io.Writer.Write\nfunc (c *chunkedBodyWriter) Write(p []byte) (n int, err error) {\n\tif c.finalized {\n\t\treturn 0, errChunkedFinished\n\t}\n\tif c.err != nil {\n\t\treturn 0, c.err\n\t}\n\tif err := c.WriteHeader(); err != nil {\n\t\treturn 0, err\n\t}\n\tif len(p) == 0 {\n\t\t// prevent from sending zero-len chunk which indicates stream ends.\n\t\t// callers may write with zero-len buf unintentionally.\n\t\t// use Finalize() instead.\n\t\treturn 0, nil\n\t}\n\tif err := c.writeChunk(p); err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(p), nil\n}\n\n// WriteHeader writes the response header for chunked encoding\nfunc (c *chunkedBodyWriter) WriteHeader() error {\n\tif c.wroteHeader {\n\t\treturn c.err\n\t}\n\tc.wroteHeader = true\n\tc.r.Header.SetContentLength(-1)\n\tif c.err = WriteHeader(&c.r.Header, c.w); c.err != nil {\n\t\treturn c.err\n\t}\n\treturn nil\n}\n\nfunc (c *chunkedBodyWriter) writeChunk(b []byte) error {\n\tif c.err = ext.WriteChunk(c.w, b, false); c.err != nil {\n\t\treturn c.err\n\t}\n\treturn nil\n}\n\nfunc (c *chunkedBodyWriter) Flush() error {\n\treturn c.w.Flush()\n}\n\n// Finalize will write the ending chunk as well as trailer and flush the writer.\n// Warning: do not call this method by yourself, unless you know what you are doing.\nfunc (c *chunkedBodyWriter) Finalize() error {\n\tif c.finalized || c.err != nil {\n\t\treturn c.err\n\t}\n\tc.finalized = true\n\tif err := c.WriteHeader(); err != nil {\n\t\treturn err\n\t}\n\t// zero-len chunk\n\tif err := c.writeChunk(nil); err != nil {\n\t\treturn err\n\t}\n\t// trailer which ends with \\r\\n\n\t_, c.err = c.w.WriteBinary(c.r.Header.Trailer().Header())\n\tif c.err == nil {\n\t\tc.err = c.Flush()\n\t}\n\treturn c.err\n}\n\nfunc (c *chunkedBodyWriter) release() {\n\tc.r = nil\n\tc.w = nil\n\tc.err = nil\n\tc.finalized = false\n\tc.wroteHeader = false\n\tchunkWriterPool.Put(c)\n}\n\n// NewChunkedBodyWriter creates a new chunked body writer.\nfunc NewChunkedBodyWriter(r *protocol.Response, w network.Writer) network.ExtWriter {\n\textWriter := chunkWriterPool.Get().(*chunkedBodyWriter)\n\textWriter.r = r\n\textWriter.w = w\n\truntime.SetFinalizer(extWriter, (*chunkedBodyWriter).release)\n\treturn extWriter\n}\n"
  },
  {
    "path": "pkg/protocol/http1/resp/writer_test.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage resp\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\nfunc TestNewChunkedBodyWriter(t *testing.T) {\n\tresponse := protocol.AcquireResponse()\n\tdefer protocol.ReleaseResponse(response)\n\n\tmockConn := mock.NewConn(\"\")\n\tw := NewChunkedBodyWriter(response, mockConn)\n\t_, _ = w.Write([]byte(\"hello\"))\n\t_, _ = w.Write(nil) // noop\n\tassert.Nil(t, w.Flush())\n\tout, _ := mockConn.WriterRecorder().Peek(mockConn.WriterRecorder().WroteLen())\n\tresp := string(out)\n\tassert.True(t, strings.Contains(resp, \"Transfer-Encoding: chunked\"))\n\tassert.True(t, strings.HasSuffix(resp, \"5\\r\\nhello\\r\\n\"))\n\n\t// Finalize adds 0\\r\\n\\r\\n\n\tassert.Nil(t, w.Finalize())\n\tassert.Nil(t, w.Finalize()) // noop\n\tout, _ = mockConn.WriterRecorder().Peek(mockConn.WriterRecorder().WroteLen())\n\tresp = string(out)\n\tassert.True(t, strings.HasSuffix(resp, \"5\\r\\nhello\\r\\n0\\r\\n\\r\\n\"))\n\n\t_, err := w.Write([]byte(\"world\"))\n\tassert.True(t, err == errChunkedFinished)\n}\n\nfunc TestNewChunkedBodyWriter_Err(t *testing.T) {\n\tresponse := protocol.AcquireResponse()\n\tdefer protocol.ReleaseResponse(response)\n\n\tmw := mock.NewMockWriter(nil)\n\tw := NewChunkedBodyWriter(response, mw)\n\n\texpectErr := errors.New(\"mock malloc err\")\n\n\tmw.MockMalloc = func(n int) ([]byte, error) {\n\t\treturn nil, expectErr\n\t}\n\t_, err := w.Write([]byte(\"hello\"))\n\tassert.True(t, err == expectErr)\n\n\tmw.MockMalloc = nil\n\t_, err = w.Write([]byte(\"world\"))\n\tassert.True(t, err == expectErr) // next call will return last err\n\n\tw = NewChunkedBodyWriter(response, mw)\n\n\tmw.MockMalloc = func(n int) ([]byte, error) {\n\t\treturn nil, expectErr\n\t}\n\terr = w.Finalize()\n\tassert.True(t, err == expectErr)\n}\n"
  },
  {
    "path": "pkg/protocol/http1/server.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage http1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\tinternalStats \"github.com/cloudwego/hertz/internal/stats\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/render\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/stats\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/ext\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/req\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/suite\"\n)\n\nfunc init() {\n\tif b, err := utils.GetBoolFromEnv(\"HERTZ_DISABLE_REQUEST_CONTEXT_POOL\"); err == nil {\n\t\tdisabaleRequestContextPool = b\n\t}\n}\n\n// NextProtoTLS is the NPN/ALPN protocol negotiated during\n// HTTP/1.1's TLS setup.\n// Also used for server addressing\nconst NextProtoTLS = suite.HTTP1\n\nvar (\n\terrHijacked        = errs.New(errs.ErrHijacked, errs.ErrorTypePublic, nil)\n\terrIdleTimeout     = errs.New(errs.ErrIdleTimeout, errs.ErrorTypePrivate, nil)\n\terrShortConnection = errs.New(errs.ErrShortConnection, errs.ErrorTypePublic, \"server is going to close the connection\")\n\terrUnexpectedEOF   = errs.NewPublic(io.ErrUnexpectedEOF.Error() + \" when reading request\")\n\n\tdisabaleRequestContextPool = false\n)\n\ntype Option struct {\n\tStreamRequestBody             bool\n\tGetOnly                       bool\n\tNoDefaultDate                 bool\n\tNoDefaultContentType          bool\n\tDisablePreParseMultipartForm  bool\n\tDisableKeepalive              bool\n\tNoDefaultServerHeader         bool\n\tDisableHeaderNamesNormalizing bool\n\tMaxRequestBodySize            int\n\tMaxHeaderBytes                int\n\tIdleTimeout                   time.Duration\n\tReadTimeout                   time.Duration\n\tServerName                    []byte\n\tTLS                           *tls.Config\n\tHTMLRender                    render.HTMLRender\n\tEnableTrace                   bool\n\tContinueHandler               func(header *protocol.RequestHeader) bool\n\tHijackConnHandle              func(c network.Conn, h app.HijackHandler)\n}\n\ntype Server struct {\n\tOption\n\tCore suite.Core\n\n\teventStackPool *sync.Pool\n}\n\nfunc (s Server) getRequestContext() *app.RequestContext {\n\tif disabaleRequestContextPool {\n\t\treturn &app.RequestContext{}\n\t}\n\treturn s.Core.GetCtxPool().Get().(*app.RequestContext)\n}\n\nfunc (s Server) putRequestContext(ctx *app.RequestContext) {\n\tif disabaleRequestContextPool {\n\t\treturn\n\t}\n\tctx.Reset()\n\ts.Core.GetCtxPool().Put(ctx)\n}\n\nfunc (s Server) Serve(c context.Context, conn network.Conn) (err error) {\n\tvar (\n\t\tzr network.Reader\n\t\tzw network.Writer\n\n\t\tserverName      []byte\n\t\tisHTTP11        bool\n\t\tconnectionClose bool\n\n\t\tcontinueReadingRequest = true\n\n\t\thijackHandler app.HijackHandler\n\n\t\t// HTTP1 path\n\t\t// 1. Get a request context\n\t\t// 2. Prepare it\n\t\t// 3. Process it\n\t\t// 4. Reset and recycle(in pooled mode)\n\t\tctx = s.getRequestContext()\n\n\t\ttraceCtl        = s.Core.GetTracer()\n\t\teventsToTrigger *eventStack\n\n\t\t// Use a new variable to hold the standard context to avoid modify the initial\n\t\t// context.\n\t\tcc = c\n\t)\n\n\tif s.EnableTrace {\n\t\teventsToTrigger = s.eventStackPool.Get().(*eventStack)\n\t}\n\n\tdefer func() {\n\t\tif s.EnableTrace {\n\t\t\t// in case of error, we need to trigger all events\n\t\t\tif eventsToTrigger != nil {\n\t\t\t\tfor last := eventsToTrigger.pop(); last != nil; last = eventsToTrigger.pop() {\n\t\t\t\t\tlast(ctx.GetTraceInfo(), err)\n\t\t\t\t}\n\t\t\t\ts.eventStackPool.Put(eventsToTrigger)\n\t\t\t}\n\t\t\tif shouldRecordInTraceError(err) {\n\t\t\t\ttraceCtl.DoFinish(cc, ctx, err)\n\t\t\t} else {\n\t\t\t\ttraceCtl.DoFinish(cc, ctx, nil)\n\t\t\t}\n\t\t}\n\n\t\t// Hijack may release and close the connection already\n\t\tif zr != nil && !errors.Is(err, errs.ErrHijacked) {\n\t\t\tzr.Release() //nolint:errcheck\n\t\t\tzr = nil\n\t\t}\n\n\t\tif ctx.IsExiled() {\n\t\t\treturn\n\t\t}\n\n\t\ts.putRequestContext(ctx)\n\t}()\n\n\tctx.HTMLRender = s.HTMLRender\n\tctx.SetConn(conn)\n\n\tctx.Request.SetIsTLS(s.TLS != nil)\n\tctx.SetEnableTrace(s.EnableTrace)\n\n\tif !s.NoDefaultServerHeader {\n\t\tserverName = s.ServerName\n\t}\n\n\tconnRequestNum := uint64(0)\n\n\tfor {\n\t\tconnRequestNum++\n\n\t\tif zr == nil {\n\t\t\tzr = ctx.GetReader()\n\t\t}\n\n\t\t// If this is a keep-alive connection we want to try and read the first bytes\n\t\t// within the idle time.\n\t\tif connRequestNum > 1 {\n\t\t\tctx.GetConn().SetReadTimeout(s.IdleTimeout) //nolint:errcheck\n\n\t\t\t_, err = zr.Peek(4)\n\t\t\t// This is not the first request, and we haven't read a single byte\n\t\t\t// of a new request yet. This means it's just a keep-alive connection\n\t\t\t// closing down either because the remote closed it or because\n\t\t\t// or a read timeout on our side. Either way just close the connection\n\t\t\t// and don't return any error response.\n\t\t\tif err != nil {\n\t\t\t\terr = errIdleTimeout\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Reset the real read timeout for the coming request\n\t\t\tctx.GetConn().SetReadTimeout(s.ReadTimeout) //nolint:errcheck\n\t\t}\n\n\t\tif s.EnableTrace {\n\t\t\tcc = traceCtl.DoStart(c, ctx)\n\t\t\tinternalStats.Record(ctx.GetTraceInfo(), stats.ReadHeaderStart, err)\n\t\t\teventsToTrigger.push(func(ti traceinfo.TraceInfo, err error) {\n\t\t\t\tinternalStats.Record(ti, stats.ReadHeaderFinish, err)\n\t\t\t})\n\t\t}\n\n\t\tctx.Response.Header.SetNoDefaultDate(s.NoDefaultDate)\n\t\tctx.Response.Header.SetNoDefaultContentType(s.NoDefaultContentType)\n\n\t\tif s.DisableHeaderNamesNormalizing {\n\t\t\tctx.Request.Header.DisableNormalizing()\n\t\t\tctx.Response.Header.DisableNormalizing()\n\t\t}\n\n\t\t// Read Headers\n\t\tif err = req.ReadHeaderWithLimit(&ctx.Request.Header, zr, s.MaxHeaderBytes); err == nil {\n\t\t\tif s.EnableTrace {\n\t\t\t\t// read header finished\n\t\t\t\tif last := eventsToTrigger.pop(); last != nil {\n\t\t\t\t\tlast(ctx.GetTraceInfo(), err)\n\t\t\t\t}\n\t\t\t\tinternalStats.Record(ctx.GetTraceInfo(), stats.ReadBodyStart, err)\n\t\t\t\teventsToTrigger.push(func(ti traceinfo.TraceInfo, err error) {\n\t\t\t\t\tinternalStats.Record(ti, stats.ReadBodyFinish, err)\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Read body\n\t\t\tif s.StreamRequestBody {\n\t\t\t\terr = req.ReadBodyStream(&ctx.Request, zr, s.MaxRequestBodySize, s.GetOnly, !s.DisablePreParseMultipartForm)\n\t\t\t} else {\n\t\t\t\terr = req.ReadLimitBody(&ctx.Request, zr, s.MaxRequestBodySize, s.GetOnly, !s.DisablePreParseMultipartForm)\n\t\t\t}\n\t\t}\n\n\t\tif s.EnableTrace {\n\t\t\tif ctx.Request.Header.ContentLength() >= 0 {\n\t\t\t\tctx.GetTraceInfo().Stats().SetRecvSize(len(ctx.Request.Header.RawHeaders()) + ctx.Request.Header.ContentLength())\n\t\t\t} else {\n\t\t\t\tctx.GetTraceInfo().Stats().SetRecvSize(0)\n\t\t\t}\n\t\t\t// read body finished\n\t\t\tif last := eventsToTrigger.pop(); last != nil {\n\t\t\t\tlast(ctx.GetTraceInfo(), err)\n\t\t\t}\n\t\t}\n\n\t\tif err != nil {\n\t\t\tif errors.Is(err, errs.ErrNothingRead) {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif err == io.EOF {\n\t\t\t\treturn errUnexpectedEOF\n\t\t\t}\n\t\t\twriteErrorResponse(zw, ctx, serverName, err)\n\t\t\treturn\n\t\t}\n\n\t\t// 'Expect: 100-continue' request handling.\n\t\t// See https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3 for details.\n\t\tif ctx.Request.MayContinue() {\n\t\t\t// Allow the ability to deny reading the incoming request body\n\t\t\tif s.ContinueHandler != nil {\n\t\t\t\tif continueReadingRequest = s.ContinueHandler(&ctx.Request.Header); !continueReadingRequest {\n\t\t\t\t\tctx.SetStatusCode(consts.StatusExpectationFailed)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif continueReadingRequest {\n\t\t\t\tzw = ctx.GetWriter()\n\t\t\t\t// Send 'HTTP/1.1 100 Continue' response.\n\t\t\t\t_, err = zw.WriteBinary(bytestr.StrResponseContinue)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\terr = zw.Flush()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Read body.\n\t\t\t\tif zr == nil {\n\t\t\t\t\tzr = ctx.GetReader()\n\t\t\t\t}\n\t\t\t\tif s.StreamRequestBody {\n\t\t\t\t\terr = req.ContinueReadBodyStream(&ctx.Request, zr, s.MaxRequestBodySize, !s.DisablePreParseMultipartForm)\n\t\t\t\t} else {\n\t\t\t\t\terr = req.ContinueReadBody(&ctx.Request, zr, s.MaxRequestBodySize, !s.DisablePreParseMultipartForm)\n\t\t\t\t}\n\n\t\t\t\tif err != nil {\n\t\t\t\t\twriteErrorResponse(zw, ctx, serverName, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconnectionClose = s.DisableKeepalive || ctx.Request.Header.ConnectionClose()\n\t\tisHTTP11 = ctx.Request.Header.IsHTTP11()\n\n\t\tif serverName != nil {\n\t\t\tctx.Response.Header.SetServerBytes(serverName)\n\t\t}\n\t\tif s.EnableTrace {\n\t\t\tinternalStats.Record(ctx.GetTraceInfo(), stats.ServerHandleStart, err)\n\t\t\teventsToTrigger.push(func(ti traceinfo.TraceInfo, err error) {\n\t\t\t\tinternalStats.Record(ti, stats.ServerHandleFinish, err)\n\t\t\t})\n\t\t}\n\n\t\tif ctx.Request.IsURIParsed() {\n\t\t\t// ctx.Request.URI() must not be called before ServeHTTP\n\t\t\t// The only case is concurrency issue when parsing a new request,\n\t\t\t// and user is reading the old request in background.\n\t\t\thlog.SystemLogger().Warnf(\"%s\\n%s\\n%s\\n%s\",\n\t\t\t\t\"Race detected.\",\n\t\t\t\t\"Please be aware that the protocol.Request passed to handler is only valid before the handler returns.\",\n\t\t\t\t\"DO NOT attempt to keep and access protocol.Request after the handler returns.\",\n\t\t\t\t\"Try build with -race to check the race issue.\")\n\t\t\treturn errors.New(\"race detected\")\n\t\t}\n\n\t\t// Handle the request\n\t\t//\n\t\t// NOTE: All middlewares and business handler will be executed in this. And at this point, the request has been parsed\n\t\t// and the route has been matched.\n\t\ts.Core.ServeHTTP(cc, ctx)\n\t\tif s.EnableTrace {\n\t\t\t// application layer handle finished\n\t\t\tif last := eventsToTrigger.pop(); last != nil {\n\t\t\t\tlast(ctx.GetTraceInfo(), err)\n\t\t\t}\n\t\t}\n\n\t\t// exit check\n\t\tif !s.Core.IsRunning() {\n\t\t\tconnectionClose = true\n\t\t}\n\n\t\tif !ctx.IsGet() && ctx.IsHead() {\n\t\t\tctx.Response.SkipBody = true\n\t\t}\n\n\t\thijackHandler = ctx.GetHijackHandler()\n\t\tctx.SetHijackHandler(nil)\n\n\t\tconnectionClose = connectionClose || ctx.Response.ConnectionClose()\n\t\tif connectionClose {\n\t\t\tctx.Response.Header.SetCanonical(bytestr.StrConnection, bytestr.StrClose)\n\t\t} else if !isHTTP11 {\n\t\t\tctx.Response.Header.SetCanonical(bytestr.StrConnection, bytestr.StrKeepAlive)\n\t\t}\n\n\t\tif zw == nil {\n\t\t\tzw = ctx.GetWriter()\n\t\t}\n\t\tif s.EnableTrace {\n\t\t\tinternalStats.Record(ctx.GetTraceInfo(), stats.WriteStart, err)\n\t\t\teventsToTrigger.push(func(ti traceinfo.TraceInfo, err error) {\n\t\t\t\tinternalStats.Record(ti, stats.WriteFinish, err)\n\t\t\t})\n\t\t}\n\t\tif err = writeResponse(ctx, zw); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif s.EnableTrace {\n\t\t\tif ctx.Response.Header.ContentLength() > 0 {\n\t\t\t\tctx.GetTraceInfo().Stats().SetSendSize(ctx.Response.Header.GetHeaderLength() + ctx.Response.Header.ContentLength())\n\t\t\t} else {\n\t\t\t\tctx.GetTraceInfo().Stats().SetSendSize(0)\n\t\t\t}\n\t\t}\n\n\t\t// Release the zeroCopyReader before flush to prevent data race\n\t\tif zr != nil {\n\t\t\tzr.Release() //nolint:errcheck\n\t\t\tzr = nil\n\t\t}\n\t\t// Flush the response.\n\t\tif err = zw.Flush(); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif s.EnableTrace {\n\t\t\t// write finished\n\t\t\tif last := eventsToTrigger.pop(); last != nil {\n\t\t\t\tlast(ctx.GetTraceInfo(), err)\n\t\t\t}\n\t\t}\n\n\t\t// Release request body stream\n\t\tif ctx.Request.IsBodyStream() {\n\t\t\terr = ext.ReleaseBodyStream(ctx.RequestBodyStream())\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif hijackHandler != nil {\n\t\t\t// Hijacked conn process the timeout by itself\n\t\t\terr = ctx.GetConn().SetReadTimeout(0)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Hijack and block the connection until the hijackHandler return\n\t\t\ts.HijackConnHandle(ctx.GetConn(), hijackHandler)\n\t\t\terr = errHijacked\n\t\t\treturn\n\t\t}\n\n\t\tif connectionClose {\n\t\t\treturn errShortConnection\n\t\t}\n\t\t// Back to network layer to trigger.\n\t\t// For now, only netpoll network mode has this feature.\n\t\t// FIXME: check\n\t\tif s.IdleTimeout == 0 {\n\t\t\treturn\n\t\t}\n\t\t// general case\n\t\tif s.EnableTrace {\n\t\t\tif shouldRecordInTraceError(err) {\n\t\t\t\ttraceCtl.DoFinish(cc, ctx, err)\n\t\t\t} else {\n\t\t\t\ttraceCtl.DoFinish(cc, ctx, nil)\n\t\t\t}\n\t\t}\n\n\t\tctx.ResetWithoutConn()\n\t}\n}\n\nfunc NewServer() *Server {\n\treturn &Server{\n\t\teventStackPool: &sync.Pool{\n\t\t\tNew: func() interface{} {\n\t\t\t\treturn &eventStack{}\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc writeErrorResponse(zw network.Writer, ctx *app.RequestContext, serverName []byte, err error) network.Writer {\n\terrorHandler := defaultErrorHandler\n\n\terrorHandler(ctx, err)\n\n\tif serverName != nil {\n\t\tctx.Response.Header.SetServerBytes(serverName)\n\t}\n\tctx.SetConnectionClose()\n\tif zw == nil {\n\t\tzw = ctx.GetWriter()\n\t}\n\twriteResponse(ctx, zw) //nolint:errcheck\n\tzw.Flush()             //nolint:errcheck\n\treturn zw\n}\n\nfunc writeResponse(ctx *app.RequestContext, w network.Writer) error {\n\t// Skip default response writing logic if it has been hijacked\n\tif ctx.Response.GetHijackWriter() != nil {\n\t\treturn ctx.Response.GetHijackWriter().Finalize()\n\t}\n\n\terr := resp.Write(&ctx.Response, w)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn err\n}\n\nfunc defaultErrorHandler(ctx *app.RequestContext, err error) {\n\tif netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {\n\t\tctx.AbortWithMsg(\"Request timeout\", consts.StatusRequestTimeout)\n\t} else if errors.Is(err, errs.ErrBodyTooLarge) {\n\t\tctx.AbortWithMsg(\"Request Entity Too Large\", consts.StatusRequestEntityTooLarge)\n\t} else if errors.Is(err, errs.ErrHeaderTooLarge) {\n\t\tctx.AbortWithMsg(\"Request Header Fields Too Large\", consts.StatusRequestHeaderFieldsTooLarge)\n\t} else {\n\t\tctx.AbortWithMsg(\"Error when parsing request\", consts.StatusBadRequest)\n\t}\n}\n\ntype eventStack []func(ti traceinfo.TraceInfo, err error)\n\nfunc (e *eventStack) isEmpty() bool {\n\treturn len(*e) == 0\n}\n\nfunc (e *eventStack) push(f func(ti traceinfo.TraceInfo, err error)) {\n\t*e = append(*e, f)\n}\n\nfunc (e *eventStack) pop() func(ti traceinfo.TraceInfo, err error) {\n\tif e.isEmpty() {\n\t\treturn nil\n\t}\n\tlast := (*e)[len(*e)-1]\n\t*e = (*e)[:len(*e)-1]\n\treturn last\n}\n\nfunc shouldRecordInTraceError(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\tif errors.Is(err, errs.ErrIdleTimeout) {\n\t\treturn false\n\t}\n\n\tif errors.Is(err, errs.ErrHijacked) {\n\t\treturn false\n\t}\n\n\tif errors.Is(err, errs.ErrShortConnection) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "pkg/protocol/http1/server_test.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage http1\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\tinStats \"github.com/cloudwego/hertz/internal/stats\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/stats\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/req\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n)\n\nvar pool = &sync.Pool{New: func() interface{} {\n\treturn &eventStack{}\n}}\n\nfunc TestTraceEventCompleted(t *testing.T) {\n\tserver := &Server{}\n\tserver.eventStackPool = pool\n\tserver.EnableTrace = true\n\treqCtx := &app.RequestContext{}\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\tti := traceinfo.NewTraceInfo()\n\t\t\tti.Stats().SetLevel(2)\n\t\t\treqCtx.SetTraceInfo(&mockTraceInfo{ti})\n\t\t\treturn reqCtx\n\t\t}},\n\t\tcontroller: &inStats.Controller{},\n\t}\n\terr := server.Serve(context.TODO(), mock.NewConn(\"GET /aaa HTTP/1.1\\nHost: foobar.com\\n\\n\"))\n\tassert.True(t, errors.Is(err, errs.ErrShortConnection))\n\ttraceInfo := reqCtx.GetTraceInfo()\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.HTTPStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadHeaderStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadHeaderFinish).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadBodyStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadBodyFinish).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ServerHandleStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ServerHandleFinish).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.WriteStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.WriteFinish).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.HTTPFinish).IsNil())\n\tassert.Nil(t, traceInfo.Stats().Error())\n}\n\nfunc TestTraceEventReadHeaderError(t *testing.T) {\n\tserver := &Server{}\n\tserver.eventStackPool = pool\n\tserver.EnableTrace = true\n\treqCtx := &app.RequestContext{}\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\tti := traceinfo.NewTraceInfo()\n\t\t\tti.Stats().SetLevel(2)\n\t\t\treqCtx.SetTraceInfo(&mockTraceInfo{ti})\n\t\t\treturn reqCtx\n\t\t}},\n\t\tcontroller: &inStats.Controller{},\n\t}\n\terr := server.Serve(context.TODO(), mock.NewConn(\"ErrorFirstLine\\r\\n\\r\\n\"))\n\tassert.NotNil(t, err)\n\ttraceInfo := reqCtx.GetTraceInfo()\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.HTTPStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadHeaderStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadHeaderFinish).IsNil())\n\tassert.Nil(t, traceInfo.Stats().GetEvent(stats.ReadBodyStart))\n\tassert.Nil(t, traceInfo.Stats().GetEvent(stats.ReadBodyFinish))\n\tassert.Nil(t, traceInfo.Stats().GetEvent(stats.ServerHandleStart))\n\tassert.Nil(t, traceInfo.Stats().GetEvent(stats.ServerHandleFinish))\n\tassert.Nil(t, traceInfo.Stats().GetEvent(stats.WriteStart))\n\tassert.Nil(t, traceInfo.Stats().GetEvent(stats.WriteFinish))\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.HTTPFinish).IsNil())\n}\n\nfunc TestTraceEventReadBodyError(t *testing.T) {\n\tserver := &Server{}\n\tserver.eventStackPool = pool\n\tserver.EnableTrace = true\n\tserver.GetOnly = true\n\treqCtx := &app.RequestContext{}\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\tti := traceinfo.NewTraceInfo()\n\t\t\tti.Stats().SetLevel(2)\n\t\t\treqCtx.SetTraceInfo(&mockTraceInfo{ti})\n\t\t\treturn reqCtx\n\t\t}},\n\t\tcontroller: &inStats.Controller{},\n\t}\n\terr := server.Serve(context.TODO(), mock.NewConn(\"POST /aaa HTTP/1.1\\nHost: foobar.com\\nContent-Length: 5\\nContent-Type: foo/bar\\n\\n12346\\n\\n\"))\n\tassert.NotNil(t, err)\n\n\ttraceInfo := reqCtx.GetTraceInfo()\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.HTTPStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadHeaderStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadHeaderFinish).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadBodyStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadBodyFinish).IsNil())\n\tassert.Nil(t, traceInfo.Stats().GetEvent(stats.ServerHandleStart))\n\tassert.Nil(t, traceInfo.Stats().GetEvent(stats.ServerHandleFinish))\n\tassert.Nil(t, traceInfo.Stats().GetEvent(stats.WriteStart))\n\tassert.Nil(t, traceInfo.Stats().GetEvent(stats.WriteFinish))\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.HTTPFinish).IsNil())\n}\n\nfunc TestTraceEventWriteError(t *testing.T) {\n\tserver := &Server{}\n\tserver.eventStackPool = pool\n\tserver.EnableTrace = true\n\treqCtx := &app.RequestContext{}\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\tti := traceinfo.NewTraceInfo()\n\t\t\tti.Stats().SetLevel(2)\n\t\t\treqCtx.SetTraceInfo(&mockTraceInfo{ti})\n\t\t\treturn reqCtx\n\t\t}},\n\t\tcontroller: &inStats.Controller{},\n\t}\n\terr := server.Serve(\n\t\tcontext.TODO(),\n\t\t&mockErrorWriter{\n\t\t\tmock.NewConn(\"POST /aaa HTTP/1.1\\nHost: foobar.com\\nContent-Length: 5\\nContent-Type: foo/bar\\n\\n12346\\n\\n\"),\n\t\t},\n\t)\n\tassert.NotNil(t, err)\n\ttraceInfo := reqCtx.GetTraceInfo()\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.HTTPStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadHeaderStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadHeaderFinish).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadBodyStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ReadBodyFinish).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ServerHandleStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.ServerHandleFinish).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.WriteStart).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.WriteFinish).IsNil())\n\tassert.False(t, traceInfo.Stats().GetEvent(stats.HTTPFinish).IsNil())\n}\n\nfunc TestEventStack(t *testing.T) {\n\t// Create a stack.\n\ts := &eventStack{}\n\tassert.True(t, s.isEmpty())\n\n\tcount := 0\n\n\t// Push 10 events.\n\tfor i := 0; i < 10; i++ {\n\t\ts.push(func(ti traceinfo.TraceInfo, err error) {\n\t\t\tcount += 1\n\t\t})\n\t}\n\n\tassert.False(t, s.isEmpty())\n\t// Pop 10 events and process them.\n\tfor last := s.pop(); last != nil; last = s.pop() {\n\t\tlast(nil, nil)\n\t}\n\n\tassert.DeepEqual(t, 10, count)\n\n\t// Pop an empty stack.\n\te := s.pop()\n\tif e != nil {\n\t\tt.Fatalf(\"should be nil\")\n\t}\n}\n\nfunc TestDefaultWriter(t *testing.T) {\n\tserver := &Server{}\n\treqCtx := &app.RequestContext{}\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\treturn reqCtx\n\t\t}},\n\t\tmockHandler: func(c context.Context, ctx *app.RequestContext) {\n\t\t\tctx.Write([]byte(\"hello, hertz\"))\n\t\t\tctx.Flush()\n\t\t},\n\t}\n\tdefaultConn := mock.NewConn(\"GET / HTTP/1.1\\nHost: foobar.com\\n\\n\")\n\terr := server.Serve(context.TODO(), defaultConn)\n\tassert.True(t, errors.Is(err, errs.ErrShortConnection))\n\tdefaultResponseResult := defaultConn.WriterRecorder()\n\tassert.DeepEqual(t, 0, defaultResponseResult.Len()) // all data is flushed so the buffer length is 0\n\tresponse := protocol.AcquireResponse()\n\tresp.Read(response, defaultResponseResult)\n\tassert.DeepEqual(t, \"hello, hertz\", string(response.Body()))\n}\n\nfunc TestServerDisableReqCtxPool(t *testing.T) {\n\tserver := &Server{}\n\treqCtx := &app.RequestContext{}\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\treqCtx.Set(\"POOL_KEY\", \"in pool\")\n\t\t\treturn reqCtx\n\t\t}},\n\t\tmockHandler: func(c context.Context, ctx *app.RequestContext) {\n\t\t\tif ctx.GetString(\"POOL_KEY\") != \"in pool\" {\n\t\t\t\tt.Fatal(\"reqCtx is not in pool\")\n\t\t\t}\n\t\t},\n\t\tisRunning: true,\n\t}\n\tdefaultConn := mock.NewConn(\"GET / HTTP/1.1\\nHost: foobar.com\\n\\n\")\n\terr := server.Serve(context.TODO(), defaultConn)\n\tassert.Nil(t, err)\n\tdisabaleRequestContextPool = true\n\tdefer func() {\n\t\t// reset global variable\n\t\tdisabaleRequestContextPool = false\n\t}()\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\treqCtx.Set(\"POOL_KEY\", \"in pool\")\n\t\t\treturn reqCtx\n\t\t}},\n\t\tmockHandler: func(c context.Context, ctx *app.RequestContext) {\n\t\t\tif len(ctx.GetString(\"POOL_KEY\")) != 0 {\n\t\t\t\tt.Fatal(\"must not get pool key\")\n\t\t\t}\n\t\t},\n\t\tisRunning: true,\n\t}\n\tdefaultConn = mock.NewConn(\"GET / HTTP/1.1\\nHost: foobar.com\\n\\n\")\n\terr = server.Serve(context.TODO(), defaultConn)\n\tassert.Nil(t, err)\n}\n\nfunc TestServer_RaceDetect(t *testing.T) {\n\tc := &app.RequestContext{}\n\t_ = c.Request.URI() // parsedURI = true\n\tm := &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\treturn c\n\t\t}},\n\t\tmockHandler: func(c context.Context, ctx *app.RequestContext) {\n\t\t\tpanic(\"must not be called\")\n\t\t},\n\t}\n\ts := &Server{}\n\ts.Core = m\n\tconn := mock.NewConn(\"GET / HTTP/1.1\\nHost: foobar.com\\n\\n\")\n\terr := s.Serve(context.Background(), conn)\n\tassert.NotNil(t, err)\n\tassert.Assert(t, err.Error() == \"race detected\")\n}\n\nfunc TestHijackResponseWriter(t *testing.T) {\n\tserver := &Server{}\n\treqCtx := &app.RequestContext{}\n\tbuf := new(bytes.Buffer)\n\tisFinal := false\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\treturn reqCtx\n\t\t}},\n\t\tmockHandler: func(c context.Context, ctx *app.RequestContext) {\n\t\t\t// response before write will be dropped\n\t\t\tctx.Write([]byte(\"invalid data\"))\n\n\t\t\tctx.Response.HijackWriter(&mock.ExtWriter{\n\t\t\t\tBuf:     buf,\n\t\t\t\tIsFinal: &isFinal,\n\t\t\t})\n\n\t\t\tctx.Write([]byte(\"hello, hertz\"))\n\t\t\tctx.Flush()\n\t\t},\n\t}\n\tdefaultConn := mock.NewConn(\"GET / HTTP/1.1\\nHost: foobar.com\\n\\n\")\n\terr := server.Serve(context.TODO(), defaultConn)\n\tassert.True(t, errors.Is(err, errs.ErrShortConnection))\n\tdefaultResponseResult := defaultConn.WriterRecorder()\n\tresponse := protocol.AcquireResponse()\n\tresp.Read(response, defaultResponseResult)\n\tassert.DeepEqual(t, 0, len(response.Body()))\n\tassert.DeepEqual(t, \"hello, hertz\", buf.String())\n\tassert.True(t, isFinal)\n}\n\nfunc TestHijackHandler(t *testing.T) {\n\tserver := NewServer()\n\treqCtx := &app.RequestContext{}\n\toriginReadTimeout := time.Second\n\thijackReadTimeout := 200 * time.Millisecond\n\treqCtx.SetHijackHandler(func(c network.Conn) {\n\t\tc.SetReadTimeout(hijackReadTimeout) // hijack read timeout\n\t})\n\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\treturn reqCtx\n\t\t}},\n\t}\n\n\tserver.HijackConnHandle = func(c network.Conn, h app.HijackHandler) {\n\t\th(c)\n\t}\n\n\tdefaultConn := mock.NewConn(\"GET / HTTP/1.1\\nHost: foobar.com\\n\\n\")\n\tdefaultConn.SetReadTimeout(originReadTimeout)\n\tassert.DeepEqual(t, originReadTimeout, defaultConn.GetReadTimeout())\n\terr := server.Serve(context.TODO(), defaultConn)\n\tassert.True(t, errors.Is(err, errs.ErrHijacked))\n\tassert.DeepEqual(t, hijackReadTimeout, defaultConn.GetReadTimeout())\n}\n\nfunc TestKeepAlive(t *testing.T) {\n\tserver := NewServer()\n\treqCtx := &app.RequestContext{}\n\ttimes := 0\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\treturn reqCtx\n\t\t}},\n\t\tisRunning: true,\n\t\tmockHandler: func(c context.Context, ctx *app.RequestContext) {\n\t\t\ttimes++\n\t\t\tif string(ctx.Path()) == \"/close\" {\n\t\t\t\tctx.SetConnectionClose()\n\t\t\t}\n\t\t},\n\t}\n\tserver.IdleTimeout = time.Second\n\n\tvar s strings.Builder\n\ts.WriteString(\"GET / HTTP/1.1\\r\\nHost: aaa\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n\ts.WriteString(\"GET /close HTTP/1.0\\r\\nHost: aaa\\r\\nConnection: keep-alive\\r\\n\\r\\n\") // set connection close\n\n\tdefaultConn := mock.NewConn(s.String())\n\terr := server.Serve(context.TODO(), defaultConn)\n\tassert.True(t, errors.Is(err, errs.ErrShortConnection))\n\tassert.DeepEqual(t, times, 2)\n}\n\nfunc TestExpect100Continue(t *testing.T) {\n\tserver := &Server{}\n\treqCtx := &app.RequestContext{}\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\treturn reqCtx\n\t\t}},\n\t\tmockHandler: func(c context.Context, ctx *app.RequestContext) {\n\t\t\tdata, err := ctx.Body()\n\t\t\tif err == nil {\n\t\t\t\tctx.Write(data)\n\t\t\t}\n\t\t},\n\t}\n\n\tdefaultConn := mock.NewConn(\"POST /foo HTTP/1.1\\r\\nHost: gle.com\\r\\nExpect: 100-continue\\r\\nContent-Length: 5\\r\\nContent-Type: a/b\\r\\n\\r\\n12345\")\n\terr := server.Serve(context.TODO(), defaultConn)\n\tassert.True(t, errors.Is(err, errs.ErrShortConnection))\n\tdefaultResponseResult := defaultConn.WriterRecorder()\n\tassert.DeepEqual(t, 0, defaultResponseResult.Len())\n\tresponse := protocol.AcquireResponse()\n\tresp.Read(response, defaultResponseResult)\n\tassert.DeepEqual(t, \"12345\", string(response.Body()))\n}\n\nfunc TestExpect100ContinueHandler(t *testing.T) {\n\tserver := &Server{}\n\treqCtx := &app.RequestContext{}\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\treturn reqCtx\n\t\t}},\n\t\tmockHandler: func(c context.Context, ctx *app.RequestContext) {\n\t\t\tdata, err := ctx.Body()\n\t\t\tif err == nil {\n\t\t\t\tctx.Write(data)\n\t\t\t}\n\t\t},\n\t}\n\tserver.ContinueHandler = func(header *protocol.RequestHeader) bool {\n\t\treturn false\n\t}\n\n\tdefaultConn := mock.NewConn(\"POST /foo HTTP/1.1\\r\\nHost: gle.com\\r\\nExpect: 100-continue\\r\\nContent-Length: 5\\r\\nContent-Type: a/b\\r\\n\\r\\n12345\")\n\terr := server.Serve(context.TODO(), defaultConn)\n\tassert.True(t, errors.Is(err, errs.ErrShortConnection))\n\tdefaultResponseResult := defaultConn.WriterRecorder()\n\tassert.DeepEqual(t, 0, defaultResponseResult.Len())\n\tresponse := protocol.AcquireResponse()\n\tresp.Read(response, defaultResponseResult)\n\tassert.DeepEqual(t, consts.StatusExpectationFailed, response.StatusCode())\n\tassert.DeepEqual(t, \"\", string(response.Body()))\n}\n\ntype mockController struct {\n\tFinishTimes int\n}\n\nfunc (m *mockController) Append(col tracer.Tracer) {}\n\nfunc (m *mockController) DoStart(ctx context.Context, c *app.RequestContext) context.Context {\n\treturn ctx\n}\n\nfunc (m *mockController) DoFinish(ctx context.Context, c *app.RequestContext, err error) {\n\tm.FinishTimes++\n}\n\nfunc (m *mockController) HasTracer() bool { return true }\n\nfunc (m *mockController) reset() { m.FinishTimes = 0 }\n\nfunc TestTraceDoFinishTimes(t *testing.T) {\n\tserver := &Server{}\n\tserver.eventStackPool = pool\n\tserver.EnableTrace = true\n\treqCtx := &app.RequestContext{}\n\tcontroller := &mockController{}\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\tti := traceinfo.NewTraceInfo()\n\t\t\tti.Stats().SetLevel(2)\n\t\t\treqCtx.SetTraceInfo(&mockTraceInfo{ti})\n\t\t\treturn reqCtx\n\t\t}},\n\t\tcontroller: controller,\n\t}\n\t// for disableKeepAlive case\n\tserver.DisableKeepalive = true\n\terr := server.Serve(context.TODO(), mock.NewConn(\"GET /aaa HTTP/1.1\\nHost: foobar.com\\n\\n\"))\n\tassert.True(t, errors.Is(err, errs.ErrShortConnection))\n\tassert.DeepEqual(t, 1, controller.FinishTimes)\n\t// for IdleTimeout==0 case\n\tserver.IdleTimeout = 0\n\tcontroller.reset()\n\terr = server.Serve(context.TODO(), mock.NewConn(\"GET /aaa HTTP/1.1\\nHost: foobar.com\\n\\n\"))\n\tassert.True(t, errors.Is(err, errs.ErrShortConnection))\n\tassert.DeepEqual(t, 1, controller.FinishTimes)\n}\n\ntype mockCore struct {\n\tctxPool     *sync.Pool\n\tcontroller  tracer.Controller\n\tmockHandler func(c context.Context, ctx *app.RequestContext)\n\tisRunning   bool\n}\n\nfunc (m *mockCore) IsRunning() bool {\n\treturn m.isRunning\n}\n\nfunc (m *mockCore) GetCtxPool() *sync.Pool {\n\treturn m.ctxPool\n}\n\nfunc (m *mockCore) ServeHTTP(c context.Context, ctx *app.RequestContext) {\n\tif m.mockHandler != nil {\n\t\tm.mockHandler(c, ctx)\n\t}\n}\n\nfunc (m *mockCore) GetTracer() tracer.Controller {\n\treturn m.controller\n}\n\ntype mockTraceInfo struct {\n\ttraceinfo.TraceInfo\n}\n\nfunc (m *mockTraceInfo) Reset() {}\n\ntype mockErrorWriter struct {\n\tnetwork.Conn\n}\n\nfunc (errorWriter *mockErrorWriter) Flush() error {\n\treturn errors.New(\"error\")\n}\n\nfunc TestShouldRecordInTraceError(t *testing.T) {\n\tassert.False(t, shouldRecordInTraceError(nil))\n\tassert.False(t, shouldRecordInTraceError(errHijacked))\n\tassert.False(t, shouldRecordInTraceError(errIdleTimeout))\n\tassert.False(t, shouldRecordInTraceError(errShortConnection))\n\n\tassert.True(t, shouldRecordInTraceError(errTimeout))\n\tassert.True(t, shouldRecordInTraceError(errors.New(\"foo error\")))\n}\n\nfunc TestServerMaxHeaderBytes(t *testing.T) {\n\ts := &Server{Option: Option{MaxHeaderBytes: 50}}\n\n\tlargeHeaderReq := \"GET / HTTP/1.1\\r\\nHost: example.com\\r\\nVery-Long-Header-Name: \" +\n\t\tstrings.Repeat(\"x\", 200) + \"\\r\\n\\r\\n\"\n\n\treader := mock.NewZeroCopyReader(largeHeaderReq)\n\th := &protocol.RequestHeader{}\n\n\terr := req.ReadHeaderWithLimit(h, reader, s.MaxHeaderBytes)\n\tassert.NotNil(t, err)\n}\n\nfunc TestDefaultErrorHandlerHeaderTooLarge(t *testing.T) {\n\tctx := app.NewContext(0)\n\tdefaultErrorHandler(ctx, errs.ErrHeaderTooLarge)\n\tassert.DeepEqual(t, ctx.Response.StatusCode(), consts.StatusRequestHeaderFieldsTooLarge)\n}\n"
  },
  {
    "path": "pkg/protocol/http1/server_timing_test.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage http1\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n\n\tinStats \"github.com/cloudwego/hertz/internal/stats\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo\"\n)\n\nfunc BenchmarkServer_Serve(b *testing.B) {\n\tserver := &Server{}\n\tserver.eventStackPool = &sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\treturn &eventStack{}\n\t\t},\n\t}\n\tserver.EnableTrace = true\n\treqCtx := &app.RequestContext{}\n\tserver.Core = &mockCore{\n\t\tctxPool: &sync.Pool{New: func() interface{} {\n\t\t\tti := traceinfo.NewTraceInfo()\n\t\t\tti.Stats().SetLevel(2)\n\t\t\treqCtx.SetTraceInfo(&mockTraceInfo{ti})\n\t\t\treturn reqCtx\n\t\t}},\n\t\tcontroller: &inStats.Controller{},\n\t}\n\terr := server.Serve(context.TODO(), mock.NewConn(\"GET /aaa HTTP/1.1\\nHost: foobar.com\\n\\n\"))\n\tif err != nil {\n\t\tfmt.Println(err.Error())\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\tserver.Serve(context.TODO(), mock.NewConn(\"GET /aaa HTTP/1.1\\nHost: foobar.com\\n\\n\"))\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/multipart.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/textproto\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc ReadMultipartForm(r io.Reader, boundary string, size, maxInMemoryFileSize int) (*multipart.Form, error) {\n\t// Do not care about memory allocations here, since they are tiny\n\t// compared to multipart data (aka multi-MB files) usually sent\n\t// in multipart/form-data requests.\n\n\tif size <= 0 {\n\t\treturn nil, fmt.Errorf(\"form size must be greater than 0. Given %d\", size)\n\t}\n\tlr := io.LimitReader(r, int64(size))\n\tmr := multipart.NewReader(lr, boundary)\n\tf, err := mr.ReadForm(int64(maxInMemoryFileSize))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot read multipart/form-data body: %s\", err)\n\t}\n\treturn f, nil\n}\n\n// WriteMultipartForm writes the given multipart form f with the given\n// boundary to w.\nfunc WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error {\n\t// Do not care about memory allocations here, since multipart\n\t// form processing is slow.\n\tif len(boundary) == 0 {\n\t\tpanic(\"BUG: form boundary cannot be empty\")\n\t}\n\n\tmw := multipart.NewWriter(w)\n\tif err := mw.SetBoundary(boundary); err != nil {\n\t\treturn fmt.Errorf(\"cannot use form boundary %q: %s\", boundary, err)\n\t}\n\n\t// marshal values\n\tfor k, vv := range f.Value {\n\t\tfor _, v := range vv {\n\t\t\tif err := mw.WriteField(k, v); err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot write form field %q value %q: %s\", k, v, err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// marshal files\n\tfor k, fvv := range f.File {\n\t\tfor _, fv := range fvv {\n\t\t\tvw, err := mw.CreatePart(fv.Header)\n\t\t\tzw := network.NewWriter(vw)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot create form file %q (%q): %s\", k, fv.Filename, err)\n\t\t\t}\n\t\t\tfh, err := fv.Open()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot open form file %q (%q): %s\", k, fv.Filename, err)\n\t\t\t}\n\t\t\tif _, err = utils.CopyZeroAlloc(zw, fh); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error when copying form file %q (%q): %s\", k, fv.Filename, err)\n\t\t\t}\n\t\t\tif err = fh.Close(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot close form file %q (%q): %s\", k, fv.Filename, err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := mw.Close(); err != nil {\n\t\treturn fmt.Errorf(\"error when closing multipart form writer: %s\", err)\n\t}\n\n\treturn nil\n}\n\nfunc MarshalMultipartForm(f *multipart.Form, boundary string) ([]byte, error) {\n\tvar buf bytebufferpool.ByteBuffer\n\tif err := WriteMultipartForm(&buf, f, boundary); err != nil {\n\t\treturn nil, err\n\t}\n\treturn buf.B, nil\n}\n\nfunc WriteMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error {\n\t// Auto detect actual multipart content type\n\tcbuf := make([]byte, 512)\n\tsize, err := r.Read(cbuf)\n\tif err != nil && err != io.EOF {\n\t\treturn err\n\t}\n\n\tpartWriter, err := w.CreatePart(CreateMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf[:size])))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif _, err = partWriter.Write(cbuf[:size]); err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.Copy(partWriter, r)\n\treturn err\n}\n\nfunc CreateMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {\n\thdr := make(textproto.MIMEHeader)\n\n\tvar contentDispositionValue string\n\tif len(strings.TrimSpace(fileName)) == 0 {\n\t\tcontentDispositionValue = fmt.Sprintf(`form-data; name=\"%s\"`, param)\n\t} else {\n\t\tcontentDispositionValue = fmt.Sprintf(`form-data; name=\"%s\"; filename=\"%s\"`,\n\t\t\tparam, fileName)\n\t}\n\thdr.Set(\"Content-Disposition\", contentDispositionValue)\n\n\tif len(contentType) > 0 {\n\t\thdr.Set(consts.HeaderContentType, contentType)\n\t}\n\treturn hdr\n}\n\nfunc AddFile(w *multipart.Writer, fieldName, path string) error {\n\tfile, err := os.Open(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\treturn WriteMultipartFormFile(w, fieldName, filepath.Base(path), file)\n}\n\nfunc ParseMultipartForm(r io.Reader, request *Request, size, maxInMemoryFileSize int) error {\n\tm, err := ReadMultipartForm(r, request.multipartFormBoundary, size, maxInMemoryFileSize)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trequest.multipartForm = m\n\treturn nil\n}\n\nfunc SetMultipartFormWithBoundary(req *Request, m *multipart.Form, boundary string) {\n\treq.multipartForm = m\n\treq.multipartFormBoundary = boundary\n}\n"
  },
  {
    "path": "pkg/protocol/multipart_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\t\"mime/multipart\"\n\t\"net/textproto\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestWriteMultipartForm(t *testing.T) {\n\tt.Parallel()\n\tvar w bytes.Buffer\n\ts := strings.Replace(`--foo\nContent-Disposition: form-data; name=\"key\"\n\nvalue\n--foo\nContent-Disposition: form-data; name=\"file\"; filename=\"test.json\"\nContent-Type: application/json\n\n{\"foo\": \"bar\"}\n--foo--\n`, \"\\n\", \"\\r\\n\", -1)\n\tmr := multipart.NewReader(strings.NewReader(s), \"foo\")\n\tform, err := mr.ReadForm(1024)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\t// The length of boundary is in the range of [1,70], which can be verified for strings outside this range.\n\terr = WriteMultipartForm(&w, form, s)\n\tassert.NotNil(t, err)\n\n\t// set Boundary as empty\n\tassert.Panic(t, func() {\n\t\terr = WriteMultipartForm(&w, form, \"\")\n\t})\n\n\t// call WriteField as twice\n\tvar body bytes.Buffer\n\tmw := multipart.NewWriter(&body)\n\tif err = mw.WriteField(\"field1\", \"value1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = WriteMultipartForm(&w, form, s)\n\tassert.NotNil(t, err)\n\n\t// normal test\n\terr = WriteMultipartForm(&w, form, \"foo\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\tif w.String() != s {\n\t\tt.Fatalf(\"unexpected output %q\", w.Bytes())\n\t}\n}\n\nfunc TestParseMultipartForm(t *testing.T) {\n\tt.Parallel()\n\ts := strings.Replace(`--foo\nContent-Disposition: form-data; name=\"key\"\n\nvalue\n--foo--\n`, \"\\n\", \"\\r\\n\", -1)\n\treq1 := Request{}\n\treq1.SetMultipartFormBoundary(\"foo\")\n\t// test size 0\n\tassert.NotNil(t, ParseMultipartForm(strings.NewReader(s), &req1, 0, 0))\n\n\terr := ParseMultipartForm(strings.NewReader(s), &req1, 1024, 1024)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error %s\", err)\n\t}\n\n\treq2 := Request{}\n\tmr := multipart.NewReader(strings.NewReader(s), \"foo\")\n\tform, err := mr.ReadForm(1024)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tSetMultipartFormWithBoundary(&req2, form, \"foo\")\n\tassert.DeepEqual(t, &req1, &req2)\n\n\t// set Boundary as \" \"\n\treq1.SetMultipartFormBoundary(\" \")\n\terr = ParseMultipartForm(strings.NewReader(s), &req1, 1024, 1024)\n\tassert.NotNil(t, err)\n\n\t// set size 0\n\terr = ParseMultipartForm(strings.NewReader(s), &req1, 0, 0)\n\tassert.NotNil(t, err)\n}\n\nfunc TestWriteMultipartFormFile(t *testing.T) {\n\tt.Parallel()\n\tbodyBuffer := &bytes.Buffer{}\n\tw := multipart.NewWriter(bodyBuffer)\n\n\t// read multipart.go to buf1\n\tf1, err := os.Open(\"./multipart.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"open file %s error: %s\", f1.Name(), err)\n\t}\n\tdefer f1.Close()\n\n\tmultipartFile := File{\n\t\tName:      f1.Name(),\n\t\tParamName: \"multipartCode\",\n\t\tReader:    f1,\n\t}\n\n\terr = WriteMultipartFormFile(w, multipartFile.ParamName, f1.Name(), multipartFile.Reader)\n\tif err != nil {\n\t\tt.Fatalf(\"write multipart error: %s\", err)\n\t}\n\n\tfileInfo1, err := f1.Stat()\n\tif err != nil {\n\t\tt.Fatalf(\"get file state error: %s\", err)\n\t}\n\n\tbuf1 := make([]byte, fileInfo1.Size())\n\t_, err = f1.ReadAt(buf1, 0)\n\tif err != nil {\n\t\tt.Fatalf(\"read file to bytes error: %s\", err)\n\t}\n\tassert.True(t, strings.Contains(bodyBuffer.String(), string(buf1)))\n\n\t// test file not found\n\tassert.Nil(t, WriteMultipartFormFile(w, multipartFile.ParamName, \"test.go\", multipartFile.Reader))\n\n\t// Test Add File Function\n\terr = AddFile(w, \"responseCode\", \"./response.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"add file error: %s\", err)\n\t}\n\n\t// read response.go to buf2\n\tf2, err := os.Open(\"./response.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"open file %s error: %s\", f2.Name(), err)\n\t}\n\tdefer f2.Close()\n\n\tfileInfo2, err := f2.Stat()\n\tif err != nil {\n\t\tt.Fatalf(\"get file state error: %s\", err)\n\t}\n\tbuf2 := make([]byte, fileInfo2.Size())\n\t_, err = f2.ReadAt(buf2, 0)\n\tif err != nil {\n\t\tt.Fatalf(\"read file to bytes error: %s\", err)\n\t}\n\tassert.True(t, strings.Contains(bodyBuffer.String(), string(buf2)))\n\n\t// test file not found\n\terr = AddFile(w, \"responseCode\", \"./test.go\")\n\tassert.NotNil(t, err)\n\n\t// test WriteMultipartFormFile without file name\n\tbodyBuffer = &bytes.Buffer{}\n\tw = multipart.NewWriter(bodyBuffer)\n\t// read multipart.go to buf1\n\tf3, err := os.Open(\"./multipart.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"open file %s error: %s\", f3.Name(), err)\n\t}\n\tdefer f3.Close()\n\terr = WriteMultipartFormFile(w, \"multipart\", \" \", f3)\n\tif err != nil {\n\t\tt.Fatalf(\"write multipart error: %s\", err)\n\t}\n\tassert.False(t, strings.Contains(bodyBuffer.String(), f3.Name()))\n\n\t// test empty file\n\tassert.Nil(t, WriteMultipartFormFile(w, \"empty_test\", \"test.data\", bytes.NewBuffer(nil)))\n}\n\nfunc TestMarshalMultipartForm(t *testing.T) {\n\ts := strings.Replace(`--foo\nContent-Disposition: form-data; name=\"key\"\n\nvalue\n--foo\nContent-Disposition: form-data; name=\"file\"; filename=\"test.json\"\nContent-Type: application/json\n\n{\"foo\": \"bar\"}\n--foo--\n`, \"\\n\", \"\\r\\n\", -1)\n\tmr := multipart.NewReader(strings.NewReader(s), \"foo\")\n\tform, err := mr.ReadForm(1024)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tbufs, err := MarshalMultipartForm(form, \"foo\")\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, s, string(bufs))\n\n\t// set boundary invalid\n\t_, err = MarshalMultipartForm(form, \" \")\n\tassert.NotNil(t, err)\n}\n\nfunc TestAddFile(t *testing.T) {\n\tt.Parallel()\n\tbodyBuffer := &bytes.Buffer{}\n\tw := multipart.NewWriter(bodyBuffer)\n\t// add null file\n\terr := AddFile(w, \"test\", \"/test\")\n\tassert.NotNil(t, err)\n}\n\nfunc TestCreateMultipartHeader(t *testing.T) {\n\tt.Parallel()\n\n\t// filename == Null\n\thdr1 := make(textproto.MIMEHeader)\n\thdr1.Set(\"Content-Disposition\", `form-data; name=\"test\"`)\n\thdr1.Set(\"Content-Type\", \"application/json\")\n\tassert.DeepEqual(t, hdr1, CreateMultipartHeader(\"test\", \"\", \"application/json\"))\n\n\t// normal test\n\thdr2 := make(textproto.MIMEHeader)\n\thdr2.Set(\"Content-Disposition\", `form-data; name=\"test\"; filename=\"/test.go\"`)\n\thdr2.Set(\"Content-Type\", \"application/json\")\n\tassert.DeepEqual(t, hdr2, CreateMultipartHeader(\"test\", \"/test.go\", \"application/json\"))\n}\n"
  },
  {
    "path": "pkg/protocol/request.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/internal/nocopy\"\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/common/compress\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nvar (\n\tErrMissingFile = errors.NewPublic(\"http: no such file\")\n\n\tresponseBodyPool bytebufferpool.Pool\n\trequestBodyPool  bytebufferpool.Pool\n\n\trequestPool sync.Pool\n)\n\n// NoBody is an io.ReadCloser with no bytes. Read always returns EOF\n// and Close always returns nil. It can be used in an outgoing client\n// request to explicitly signal that a request has zero bytes.\nvar NoBody = noBody{}\n\ntype noBody struct{}\n\nfunc (noBody) Read([]byte) (int, error) { return 0, io.EOF }\nfunc (noBody) Close() error             { return nil }\n\ntype Request struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\tHeader RequestHeader\n\n\turi      URI\n\tpostArgs Args\n\n\tbodyStream      io.Reader\n\tw               requestBodyWriter\n\tbody            *bytebufferpool.ByteBuffer\n\tbodyRaw         []byte\n\tmaxKeepBodySize int\n\n\tmultipartForm         *multipart.Form\n\tmultipartFormBoundary string\n\n\t// Group bool members in order to reduce Request object size.\n\tparsedURI      bool\n\tparsedPostArgs bool\n\n\tisTLS bool\n\n\tmultipartFiles  []*File\n\tmultipartFields []*MultipartField\n\n\t// Request level options, service discovery options etc.\n\toptions *config.RequestOptions\n}\n\ntype requestBodyWriter struct {\n\tr *Request\n}\n\n// File struct represent file information for multipart request\ntype File struct {\n\tName      string\n\tParamName string\n\tio.Reader\n}\n\n// MultipartField struct represent custom data part for multipart request\ntype MultipartField struct {\n\tParam       string\n\tFileName    string\n\tContentType string\n\tio.Reader\n}\n\nfunc (w *requestBodyWriter) Write(p []byte) (int, error) {\n\tw.r.AppendBody(p)\n\treturn len(p), nil\n}\n\nfunc (req *Request) Options() *config.RequestOptions {\n\tif req.options == nil {\n\t\treq.options = config.NewRequestOptions(nil)\n\t}\n\treturn req.options\n}\n\n// AppendBody appends p to request body.\n//\n// It is safe re-using p after the function returns.\nfunc (req *Request) AppendBody(p []byte) {\n\treq.RemoveMultipartFormFiles()\n\treq.CloseBodyStream()     //nolint:errcheck\n\treq.BodyBuffer().Write(p) //nolint:errcheck\n}\n\nfunc (req *Request) BodyBuffer() *bytebufferpool.ByteBuffer {\n\tif req.body == nil {\n\t\treq.body = requestBodyPool.Get()\n\t}\n\treq.bodyRaw = nil\n\treturn req.body\n}\n\n// MayContinue returns true if the request contains\n// 'Expect: 100-continue' header.\n//\n// The caller must do one of the following actions if MayContinue returns true:\n//\n//   - Either send StatusExpectationFailed response if request headers don't\n//     satisfy the caller.\n//   - Or send StatusContinue response before reading request body\n//     with ContinueReadBody.\n//   - Or close the connection.\nfunc (req *Request) MayContinue() bool {\n\treturn bytes.Equal(req.Header.peek(consts.HeaderExpect), bytestr.Str100Continue)\n}\n\n// Scheme returns the scheme of the request.\n// uri will be parsed in ServeHTTP(before user's process), so that there is no need for uri nil-check.\nfunc (req *Request) Scheme() []byte {\n\treturn req.uri.Scheme()\n}\n\n// For keepalive connection reuse.\n// It is roughly the same as ResetSkipHeader, except that the connection related fields are removed:\n// - req.isTLS\nfunc (req *Request) resetSkipHeaderAndConn() {\n\treq.ResetBody()\n\treq.uri.Reset()\n\treq.parsedURI = false\n\treq.parsedPostArgs = false\n\treq.postArgs.Reset()\n}\n\nfunc (req *Request) ResetSkipHeader() {\n\treq.resetSkipHeaderAndConn()\n\treq.isTLS = false\n}\n\nfunc SwapRequestBody(a, b *Request) {\n\ta.body, b.body = b.body, a.body\n\ta.bodyRaw, b.bodyRaw = b.bodyRaw, a.bodyRaw\n\ta.bodyStream, b.bodyStream = b.bodyStream, a.bodyStream\n\ta.multipartFields, b.multipartFields = b.multipartFields, a.multipartFields\n\ta.multipartFiles, b.multipartFiles = b.multipartFiles, a.multipartFiles\n}\n\n// Reset clears request contents.\nfunc (req *Request) Reset() {\n\treq.Header.Reset()\n\treq.ResetSkipHeader()\n\treq.CloseBodyStream()\n\n\treq.options = nil\n}\n\nfunc (req *Request) IsURIParsed() bool {\n\treturn req.parsedURI\n}\n\nfunc (req *Request) PostArgString() []byte {\n\treturn req.postArgs.QueryString()\n}\n\n// MultipartForm returns request's multipart form.\n//\n// Returns errors.ErrNoMultipartForm if request's Content-Type\n// isn't 'multipart/form-data'.\n//\n// RemoveMultipartFormFiles must be called after returned multipart form\n// is processed.\nfunc (req *Request) MultipartForm() (*multipart.Form, error) {\n\tif req.multipartForm != nil {\n\t\treturn req.multipartForm, nil\n\t}\n\treq.multipartFormBoundary = string(req.Header.MultipartFormBoundary())\n\tif len(req.multipartFormBoundary) == 0 {\n\t\treturn nil, errors.ErrNoMultipartForm\n\t}\n\n\tce := req.Header.peek(consts.HeaderContentEncoding)\n\tvar err error\n\tvar f *multipart.Form\n\n\tif !req.IsBodyStream() {\n\t\tbody := req.BodyBytes()\n\t\tif bytes.Equal(ce, bytestr.StrGzip) {\n\t\t\t// Do not care about memory usage here.\n\t\t\tvar err error\n\t\t\tif body, err = compress.AppendGunzipBytes(nil, body); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"cannot gunzip request body: %s\", err)\n\t\t\t}\n\t\t} else if len(ce) > 0 {\n\t\t\treturn nil, fmt.Errorf(\"unsupported Content-Encoding: %q\", ce)\n\t\t}\n\t\tf, err = ReadMultipartForm(bytes.NewReader(body), req.multipartFormBoundary, len(body), len(body))\n\t} else {\n\t\tbodyStream := req.bodyStream\n\t\tif req.Header.contentLength > 0 {\n\t\t\tbodyStream = io.LimitReader(bodyStream, int64(req.Header.contentLength))\n\t\t}\n\t\tif bytes.Equal(ce, bytestr.StrGzip) {\n\t\t\t// Do not care about memory usage here.\n\t\t\tif bodyStream, err = gzip.NewReader(bodyStream); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"cannot gunzip request body: %w\", err)\n\t\t\t}\n\t\t} else if len(ce) > 0 {\n\t\t\treturn nil, fmt.Errorf(\"unsupported Content-Encoding: %q\", ce)\n\t\t}\n\n\t\tmr := multipart.NewReader(bodyStream, req.multipartFormBoundary)\n\n\t\tf, err = mr.ReadForm(8 * 1024)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.multipartForm = f\n\treturn f, nil\n}\n\n// AppendBodyString appends s to request body.\nfunc (req *Request) AppendBodyString(s string) {\n\treq.RemoveMultipartFormFiles()\n\treq.CloseBodyStream()           //nolint:errcheck\n\treq.BodyBuffer().WriteString(s) //nolint:errcheck\n}\n\n// SetRequestURI sets RequestURI.\nfunc (req *Request) SetRequestURI(requestURI string) {\n\treq.Header.SetRequestURI(requestURI)\n\treq.parsedURI = false\n}\n\nfunc (req *Request) SetMaxKeepBodySize(n int) {\n\treq.maxKeepBodySize = n\n}\n\n// RequestURI returns the RequestURI for the given request.\nfunc (req *Request) RequestURI() []byte {\n\treturn req.Header.RequestURI()\n}\n\n// FormFile returns the first file for the provided form key.\nfunc (req *Request) FormFile(name string) (*multipart.FileHeader, error) {\n\tmf, err := req.MultipartForm()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif mf.File == nil {\n\t\treturn nil, err\n\t}\n\tfhh := mf.File[name]\n\tif fhh == nil {\n\t\treturn nil, ErrMissingFile\n\t}\n\treturn fhh[0], nil\n}\n\n// SetHost sets host for the request.\nfunc (req *Request) SetHost(host string) {\n\treq.URI().SetHost(host)\n}\n\n// Host returns the host for the given request.\nfunc (req *Request) Host() []byte {\n\treturn req.URI().Host()\n}\n\n// SetIsTLS is used by TLS server to mark whether the request is a TLS request.\n// Client shouldn't use this method but should depend on the uri.scheme instead.\nfunc (req *Request) SetIsTLS(isTLS bool) {\n\treq.isTLS = isTLS\n}\n\n// SwapBody swaps request body with the given body and returns\n// the previous request body.\n//\n// It is forbidden to use the body passed to SwapBody after\n// the function returns.\nfunc (req *Request) SwapBody(body []byte) []byte {\n\tbb := req.BodyBuffer()\n\tzw := network.NewWriter(bb)\n\n\tif req.IsBodyStream() {\n\t\tbb.Reset()\n\t\t_, err := utils.CopyZeroAlloc(zw, req.bodyStream)\n\t\treq.CloseBodyStream() //nolint:errcheck\n\t\tif err != nil {\n\t\t\tbb.Reset()\n\t\t\tbb.SetString(err.Error())\n\t\t}\n\t}\n\n\treq.bodyRaw = nil\n\n\toldBody := bb.B\n\tbb.B = body\n\treturn oldBody\n}\n\n// CopyTo copies req contents to dst except of body stream.\nfunc (req *Request) CopyTo(dst *Request) {\n\treq.CopyToSkipBody(dst)\n\tif req.bodyRaw != nil {\n\t\tdst.bodyRaw = append(dst.bodyRaw[:0], req.bodyRaw...)\n\t\tif dst.body != nil {\n\t\t\tdst.body.Reset()\n\t\t}\n\t} else if req.body != nil {\n\t\tdst.BodyBuffer().Set(req.body.B)\n\t} else if dst.body != nil {\n\t\tdst.body.Reset()\n\t}\n}\n\nfunc (req *Request) CopyToSkipBody(dst *Request) {\n\tdst.Reset()\n\treq.Header.CopyTo(&dst.Header)\n\n\treq.uri.CopyTo(&dst.uri)\n\tdst.parsedURI = req.parsedURI\n\n\treq.postArgs.CopyTo(&dst.postArgs)\n\tdst.parsedPostArgs = req.parsedPostArgs\n\tdst.isTLS = req.isTLS\n\n\tif req.options != nil {\n\t\tdst.options = &config.RequestOptions{}\n\t\treq.options.CopyTo(dst.options)\n\t}\n\n\t// do not copy multipartForm - it will be automatically\n\t// re-created on the first call to MultipartForm.\n}\n\nfunc (req *Request) BodyBytes() []byte {\n\tif req.bodyRaw != nil {\n\t\treturn req.bodyRaw\n\t}\n\tif req.body == nil {\n\t\treturn nil\n\t}\n\treturn req.body.B\n}\n\n// ResetBody resets request body.\nfunc (req *Request) ResetBody() {\n\treq.bodyRaw = nil\n\treq.RemoveMultipartFormFiles()\n\treq.CloseBodyStream() //nolint:errcheck\n\tif req.body != nil {\n\t\tif req.body.Cap() <= req.maxKeepBodySize {\n\t\t\treq.body.Reset()\n\t\t\treturn\n\t\t}\n\t\trequestBodyPool.Put(req.body)\n\t\treq.body = nil\n\t}\n}\n\n// SetBodyRaw sets request body, but without copying it.\n//\n// From this point onward the body argument must not be changed.\nfunc (req *Request) SetBodyRaw(body []byte) {\n\treq.ResetBody()\n\treq.bodyRaw = body\n}\n\n// SetMultipartFormBoundary will set the multipart form boundary for the request.\nfunc (req *Request) SetMultipartFormBoundary(b string) {\n\treq.multipartFormBoundary = b\n}\n\nfunc (req *Request) MultipartFormBoundary() string {\n\treturn req.multipartFormBoundary\n}\n\n// SetBody sets request body.\n//\n// It is safe re-using body argument after the function returns.\nfunc (req *Request) SetBody(body []byte) {\n\treq.RemoveMultipartFormFiles()\n\treq.CloseBodyStream() //nolint:errcheck\n\treq.BodyBuffer().Set(body)\n}\n\n// SetBodyString sets request body.\nfunc (req *Request) SetBodyString(body string) {\n\treq.RemoveMultipartFormFiles()\n\treq.CloseBodyStream() //nolint:errcheck\n\treq.BodyBuffer().SetString(body)\n}\n\n// SetQueryString sets query string.\nfunc (req *Request) SetQueryString(queryString string) {\n\treq.URI().SetQueryString(queryString)\n}\n\n// SetFormData sets x-www-form-urlencoded params\nfunc (req *Request) SetFormData(data map[string]string) {\n\tfor k, v := range data {\n\t\treq.postArgs.Add(k, v)\n\t}\n\treq.parsedPostArgs = true\n\treq.Header.SetContentTypeBytes(bytestr.MIMEPostForm)\n}\n\n// SetFormDataFromValues sets x-www-form-urlencoded params from url values.\nfunc (req *Request) SetFormDataFromValues(data url.Values) {\n\tfor k, v := range data {\n\t\tfor _, kv := range v {\n\t\t\treq.postArgs.Add(k, kv)\n\t\t}\n\t}\n\treq.parsedPostArgs = true\n\treq.Header.SetContentTypeBytes(bytestr.MIMEPostForm)\n}\n\n// SetFile sets single file field name and its path for multipart upload.\nfunc (req *Request) SetFile(param, filePath string) {\n\treq.multipartFiles = append(req.multipartFiles, &File{\n\t\tName:      filePath,\n\t\tParamName: param,\n\t})\n}\n\n// SetFiles sets multiple file field name and its path for multipart upload.\nfunc (req *Request) SetFiles(files map[string]string) {\n\tfor f, fp := range files {\n\t\treq.multipartFiles = append(req.multipartFiles, &File{\n\t\t\tName:      fp,\n\t\t\tParamName: f,\n\t\t})\n\t}\n}\n\n// SetFileReader sets single file using io.Reader for multipart upload.\nfunc (req *Request) SetFileReader(param, fileName string, reader io.Reader) {\n\treq.multipartFiles = append(req.multipartFiles, &File{\n\t\tName:      fileName,\n\t\tParamName: param,\n\t\tReader:    reader,\n\t})\n}\n\n// SetMultipartFormData method allows simple form data to be attached to the request as `multipart:form-data`\nfunc (req *Request) SetMultipartFormData(data map[string]string) {\n\tfor k, v := range data {\n\t\treq.SetMultipartField(k, \"\", \"\", strings.NewReader(v))\n\t}\n}\n\nfunc (req *Request) MultipartFiles() []*File {\n\treturn req.multipartFiles\n}\n\n// SetMultipartField sets custom data using io.Reader for multipart upload.\nfunc (req *Request) SetMultipartField(param, fileName, contentType string, reader io.Reader) {\n\treq.multipartFields = append(req.multipartFields, &MultipartField{\n\t\tParam:       param,\n\t\tFileName:    fileName,\n\t\tContentType: contentType,\n\t\tReader:      reader,\n\t})\n}\n\n// SetMultipartFields sets multiple data fields using io.Reader for multipart upload.\nfunc (req *Request) SetMultipartFields(fields ...*MultipartField) {\n\treq.multipartFields = append(req.multipartFields, fields...)\n}\n\nfunc (req *Request) MultipartFields() []*MultipartField {\n\treturn req.multipartFields\n}\n\n// SetBasicAuth sets the basic authentication header in the current HTTP request.\nfunc (req *Request) SetBasicAuth(username, password string) {\n\tencodeStr := base64.StdEncoding.EncodeToString([]byte(username + \":\" + password))\n\treq.SetHeader(consts.HeaderAuthorization, \"Basic \"+encodeStr)\n}\n\n// BasicAuth can return the username and password in the request's Authorization\n// header, if the request uses the HTTP Basic Authorization.\nfunc (req *Request) BasicAuth() (username, password string, ok bool) {\n\t// Using Peek to reduce the cost for type transfer.\n\tauth := req.Header.Peek(consts.HeaderAuthorization)\n\tif auth == nil {\n\t\treturn\n\t}\n\n\treturn parseBasicAuth(auth)\n}\n\nvar prefix = []byte{'B', 'a', 's', 'i', 'c', ' '}\n\n// parseBasicAuth can parse an HTTP Basic Authorization string encrypted by base64.\n// Example: \"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\" returns (\"Aladdin\", \"open sesame\", true).\nfunc parseBasicAuth(auth []byte) (username, password string, ok bool) {\n\tif len(auth) < len(prefix) || !bytes.EqualFold(auth[:len(prefix)], prefix) {\n\t\treturn\n\t}\n\n\tdecodeLen := base64.StdEncoding.DecodedLen(len(auth[len(prefix):]))\n\t// base64.StdEncoding.Decode(dst,rsc []byte) will return less than DecodedLen(len(src)))\n\tdecodeData := make([]byte, decodeLen)\n\tnum, err := base64.StdEncoding.Decode(decodeData, auth[len(prefix):])\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcs := bytesconv.B2s(decodeData[:num])\n\ts := strings.IndexByte(cs, ':')\n\n\tif s < 0 {\n\t\treturn\n\t}\n\n\treturn cs[:s], cs[s+1:], true\n}\n\n// SetAuthToken sets the auth token header(Default Scheme: Bearer) in the current HTTP request. Header example:\n//\n//\tAuthorization: Bearer <auth-token-value-comes-here>\nfunc (req *Request) SetAuthToken(token string) {\n\treq.SetHeader(consts.HeaderAuthorization, \"Bearer \"+token)\n}\n\n// SetAuthSchemeToken sets the auth token scheme type in the HTTP request. For Example:\n//\n//\tAuthorization: <auth-scheme-value-set-here> <auth-token-value>\nfunc (req *Request) SetAuthSchemeToken(scheme, token string) {\n\treq.SetHeader(consts.HeaderAuthorization, scheme+\" \"+token)\n}\n\n// SetHeader sets a single header field and its value in the current request.\nfunc (req *Request) SetHeader(header, value string) {\n\treq.Header.Set(header, value)\n}\n\n// SetHeaders sets multiple header field and its value in the current request.\nfunc (req *Request) SetHeaders(headers map[string]string) {\n\tfor h, v := range headers {\n\t\treq.Header.Set(h, v)\n\t}\n}\n\n// SetCookie appends a single cookie in the current request instance.\nfunc (req *Request) SetCookie(key, value string) {\n\treq.Header.SetCookie(key, value)\n}\n\n// SetCookies sets an array of cookies in the current request instance.\nfunc (req *Request) SetCookies(hc map[string]string) {\n\tfor k, v := range hc {\n\t\treq.Header.SetCookie(k, v)\n\t}\n}\n\n// SetMethod sets http method for this request.\nfunc (req *Request) SetMethod(method string) {\n\treq.Header.SetMethod(method)\n}\n\nfunc (req *Request) OnlyMultipartForm() bool {\n\treturn req.multipartForm != nil && (req.body == nil || len(req.body.B) == 0)\n}\n\nfunc (req *Request) HasMultipartForm() bool {\n\treturn req.multipartForm != nil\n}\n\n// IsBodyStream returns true if body is set via SetBodyStream*\nfunc (req *Request) IsBodyStream() bool {\n\treturn req.bodyStream != nil && req.bodyStream != NoBody\n}\n\nfunc (req *Request) BodyStream() io.Reader {\n\tif req.bodyStream == nil {\n\t\treturn NoBody\n\t}\n\treturn req.bodyStream\n}\n\n// SetBodyStream sets request body stream and, optionally body size.\n//\n// If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes\n// before returning io.EOF.\n//\n// If bodySize < 0, then bodyStream is read until io.EOF.\n//\n// bodyStream.Close() is called after finishing reading all body data\n// if it implements io.Closer.\n//\n// Note that GET and HEAD requests cannot have body.\n//\n// See also SetBodyStreamWriter.\nfunc (req *Request) SetBodyStream(bodyStream io.Reader, bodySize int) {\n\treq.ResetBody()\n\treq.bodyStream = bodyStream\n\treq.Header.SetContentLength(bodySize)\n}\n\nfunc (req *Request) ConstructBodyStream(body *bytebufferpool.ByteBuffer, bodyStream io.Reader) {\n\treq.body = body\n\treq.bodyStream = bodyStream\n}\n\n// BodyWriter returns writer for populating request body.\nfunc (req *Request) BodyWriter() io.Writer {\n\treq.w.r = req\n\treturn &req.w\n}\n\n// PostArgs returns POST arguments.\nfunc (req *Request) PostArgs() *Args {\n\treq.parsePostArgs()\n\treturn &req.postArgs\n}\n\nfunc (req *Request) parsePostArgs() {\n\tif req.parsedPostArgs {\n\t\treturn\n\t}\n\treq.parsedPostArgs = true\n\tif !bytes.HasPrefix(req.Header.ContentType(), bytestr.MIMEPostForm) {\n\t\treturn\n\t}\n\treq.postArgs.ParseBytes(req.Body())\n}\n\n// BodyE returns request body.\nfunc (req *Request) BodyE() ([]byte, error) {\n\tif req.bodyRaw != nil {\n\t\treturn req.bodyRaw, nil\n\t}\n\tif req.IsBodyStream() {\n\t\tbodyBuf := req.BodyBuffer()\n\t\tbodyBuf.Reset()\n\t\tzw := network.NewWriter(bodyBuf)\n\t\t_, err := utils.CopyZeroAlloc(zw, req.bodyStream)\n\t\treq.CloseBodyStream() //nolint:errcheck\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn req.BodyBytes(), nil\n\t}\n\tif req.OnlyMultipartForm() {\n\t\tbody, err := MarshalMultipartForm(req.multipartForm, req.multipartFormBoundary)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn body, nil\n\t}\n\treturn req.BodyBytes(), nil\n}\n\n// Body returns request body.\n// if get body failed, returns nil.\nfunc (req *Request) Body() []byte {\n\tbody, _ := req.BodyE()\n\treturn body\n}\n\n// BodyWriteTo writes request body to w.\nfunc (req *Request) BodyWriteTo(w io.Writer) error {\n\tif req.IsBodyStream() {\n\t\tzw := network.NewWriter(w)\n\t\t_, err := utils.CopyZeroAlloc(zw, req.bodyStream)\n\t\treq.CloseBodyStream() //nolint:errcheck\n\t\treturn err\n\t}\n\tif req.OnlyMultipartForm() {\n\t\treturn WriteMultipartForm(w, req.multipartForm, req.multipartFormBoundary)\n\t}\n\t_, err := w.Write(req.BodyBytes())\n\treturn err\n}\n\nfunc (req *Request) CloseBodyStream() error {\n\tif req.bodyStream == nil {\n\t\treturn nil\n\t}\n\n\tvar err error\n\tif bsc, ok := req.bodyStream.(io.Closer); ok {\n\t\terr = bsc.Close()\n\t}\n\treq.bodyStream = nil\n\treturn err\n}\n\n// URI returns request URI\nfunc (req *Request) URI() *URI {\n\treq.ParseURI()\n\treturn &req.uri\n}\n\nfunc (req *Request) ParseURI() {\n\tif req.parsedURI {\n\t\treturn\n\t}\n\treq.parsedURI = true\n\n\treq.uri.parse(req.Header.Host(), req.Header.RequestURI(), req.isTLS)\n}\n\n// RemoveMultipartFormFiles removes multipart/form-data temporary files\n// associated with the request.\nfunc (req *Request) RemoveMultipartFormFiles() {\n\tif req.multipartForm != nil {\n\t\t// Do not check for error, since these files may be deleted or moved\n\t\t// to new places by user code.\n\t\treq.multipartForm.RemoveAll() //nolint:errcheck\n\t\treq.multipartForm = nil\n\t}\n\treq.multipartFormBoundary = \"\"\n\treq.multipartFiles = nil\n\treq.multipartFields = nil\n}\n\nfunc AddMultipartFormField(w *multipart.Writer, mf *MultipartField) error {\n\tpartWriter, err := w.CreatePart(CreateMultipartHeader(mf.Param, mf.FileName, mf.ContentType))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.Copy(partWriter, mf.Reader)\n\treturn err\n}\n\n// Method returns request method\nfunc (req *Request) Method() []byte {\n\treturn req.Header.Method()\n}\n\n// Path returns request path\nfunc (req *Request) Path() []byte {\n\treturn req.URI().Path()\n}\n\n// QueryString returns request query\nfunc (req *Request) QueryString() []byte {\n\treturn req.URI().QueryString()\n}\n\n// SetOptions is used to set request options.\n// These options can be used to do something in middlewares such as service discovery.\nfunc (req *Request) SetOptions(opts ...config.RequestOption) {\n\treq.Options().Apply(opts)\n}\n\n// ConnectionClose returns true if 'Connection: close' header is set.\nfunc (req *Request) ConnectionClose() bool {\n\treturn req.Header.ConnectionClose()\n}\n\n// SetConnectionClose sets 'Connection: close' header.\nfunc (req *Request) SetConnectionClose() {\n\treq.Header.SetConnectionClose(true)\n}\n\nfunc (req *Request) ResetWithoutConn() {\n\treq.Header.Reset()\n\treq.resetSkipHeaderAndConn()\n\treq.CloseBodyStream()\n\n\treq.options = nil\n}\n\n// AcquireRequest returns an empty Request instance from request pool.\n//\n// The returned Request instance may be passed to ReleaseRequest when it is\n// no longer needed. This allows Request recycling, reduces GC pressure\n// and usually improves performance.\nfunc AcquireRequest() *Request {\n\tv := requestPool.Get()\n\tif v == nil {\n\t\treturn &Request{}\n\t}\n\treturn v.(*Request)\n}\n\n// ReleaseRequest returns req acquired via AcquireRequest to request pool.\n//\n// It is forbidden accessing req and/or its members after returning\n// it to request pool.\nfunc ReleaseRequest(req *Request) {\n\treq.Reset()\n\trequestPool.Put(req)\n}\n\n// NewRequest makes a new Request given a method, URL, and\n// optional body.\n//\n// # Method's default value is GET\n//\n// Url must contain fully qualified uri, i.e. with scheme and host,\n// and http is assumed if scheme is omitted.\n//\n// Protocol version is always HTTP/1.1\n//\n// NewRequest just uses for unit-testing. Use AcquireRequest() in other cases.\nfunc NewRequest(method, url string, body io.Reader) *Request {\n\tif method == \"\" {\n\t\tmethod = consts.MethodGet\n\t}\n\n\treq := new(Request)\n\treq.SetRequestURI(url)\n\treq.SetIsTLS(bytes.HasPrefix(bytesconv.S2b(url), bytestr.StrHTTPS))\n\treq.ParseURI()\n\treq.SetMethod(method)\n\treq.Header.SetHost(string(req.URI().Host()))\n\treq.Header.SetRequestURIBytes(req.URI().RequestURI())\n\n\tif !req.Header.IgnoreBody() {\n\t\treq.SetBodyStream(body, -1)\n\t\tswitch v := req.BodyStream().(type) {\n\t\tcase *bytes.Buffer:\n\t\t\treq.Header.SetContentLength(v.Len())\n\t\tcase *bytes.Reader:\n\t\t\treq.Header.SetContentLength(v.Len())\n\t\tcase *strings.Reader:\n\t\t\treq.Header.SetContentLength(v.Len())\n\t\tdefault:\n\t\t}\n\t}\n\n\treturn req\n}\n"
  },
  {
    "path": "pkg/protocol/request_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"mime/multipart\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/common/compress\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\ntype errorReader struct{}\n\nfunc (er errorReader) Read(p []byte) (int, error) {\n\treturn 0, fmt.Errorf(\"dummy!\")\n}\n\nfunc TestMultiForm(t *testing.T) {\n\tvar r Request\n\t// r.Header.Set()\n\t_, err := r.MultipartForm()\n\tfmt.Println(err)\n}\n\nfunc TestRequestBodyWriterWrite(t *testing.T) {\n\tw := requestBodyWriter{&Request{}}\n\tw.Write([]byte(\"test\"))\n\tassert.DeepEqual(t, \"test\", string(w.r.body.B))\n}\n\nfunc TestRequestScheme(t *testing.T) {\n\treq := NewRequest(\"\", \"ptth://127.0.0.1:8080\", nil)\n\tassert.DeepEqual(t, \"ptth\", string(req.Scheme()))\n\treq = NewRequest(\"\", \"127.0.0.1:8080\", nil)\n\tassert.DeepEqual(t, \"http\", string(req.Scheme()))\n\tassert.DeepEqual(t, true, req.IsURIParsed())\n}\n\nfunc TestRequestHost(t *testing.T) {\n\treq := &Request{}\n\treq.SetHost(\"127.0.0.1:8080\")\n\tassert.DeepEqual(t, \"127.0.0.1:8080\", string(req.Host()))\n}\n\nfunc TestRequestSwapBody(t *testing.T) {\n\treqA := &Request{}\n\treqA.SetBodyRaw([]byte(\"testA\"))\n\treqB := &Request{}\n\treqB.SetBodyRaw([]byte(\"testB\"))\n\tSwapRequestBody(reqA, reqB)\n\tassert.DeepEqual(t, \"testA\", string(reqB.bodyRaw))\n\tassert.DeepEqual(t, \"testB\", string(reqA.bodyRaw))\n\treqA.SetBody([]byte(\"testA\"))\n\treqB.SetBody([]byte(\"testB\"))\n\tSwapRequestBody(reqA, reqB)\n\tassert.DeepEqual(t, \"testA\", string(reqB.body.B))\n\tassert.DeepEqual(t, \"\", string(reqB.bodyRaw))\n\tassert.DeepEqual(t, \"testB\", string(reqA.body.B))\n\tassert.DeepEqual(t, \"\", string(reqA.bodyRaw))\n\treqA.SetBodyStream(strings.NewReader(\"testA\"), len(\"testA\"))\n\treqB.SetBodyStream(strings.NewReader(\"testB\"), len(\"testB\"))\n\tSwapRequestBody(reqA, reqB)\n\tbody := make([]byte, 5)\n\treqB.bodyStream.Read(body)\n\tassert.DeepEqual(t, \"testA\", string(body))\n\treqA.bodyStream.Read(body)\n\tassert.DeepEqual(t, \"testB\", string(body))\n}\n\nfunc TestRequestKnownSizeStreamMultipartFormWithFile(t *testing.T) {\n\tt.Parallel()\n\n\ts := `------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f1\"\n\nvalue1\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"fileaaa\"; filename=\"TODO\"\nContent-Type: application/octet-stream\n\n- SessionClient with referer and cookies support.\n- Client with requests' pipelining support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\ntailfoobar`\n\tmr := strings.NewReader(s)\n\tr := NewRequest(\"POST\", \"/upload\", mr)\n\tr.Header.SetContentLength(521)\n\tr.Header.SetContentTypeBytes([]byte(\"multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\"))\n\tassert.DeepEqual(t, false, r.HasMultipartForm())\n\tf, err := r.MultipartForm()\n\tassert.DeepEqual(t, true, r.HasMultipartForm())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tdefer r.RemoveMultipartFormFiles()\n\n\t// verify tail\n\ttail, err := ioutil.ReadAll(mr)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(tail) != \"tailfoobar\" {\n\t\tt.Fatalf(\"unexpected tail %q. Expecting %q\", tail, \"tailfoobar\")\n\t}\n\n\t// verify values\n\tif len(f.Value) != 1 {\n\t\tt.Fatalf(\"unexpected number of values in multipart form: %d. Expecting 1\", len(f.Value))\n\t}\n\tfor k, vv := range f.Value {\n\t\tif k != \"f1\" {\n\t\t\tt.Fatalf(\"unexpected value name %q. Expecting %q\", k, \"f1\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v != \"value1\" {\n\t\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"value1\")\n\t\t}\n\t}\n\n\t// verify files\n\tif len(f.File) != 1 {\n\t\tt.Fatalf(\"unexpected number of file values in multipart form: %d. Expecting 1\", len(f.File))\n\t}\n\tfor k, vv := range f.File {\n\t\tif k != \"fileaaa\" {\n\t\t\tt.Fatalf(\"unexpected file value name %q. Expecting %q\", k, \"fileaaa\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of file values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v.Filename != \"TODO\" {\n\t\t\tt.Fatalf(\"unexpected filename %q. Expecting %q\", v.Filename, \"TODO\")\n\t\t}\n\t\tct := v.Header.Get(\"Content-Type\")\n\t\tif ct != \"application/octet-stream\" {\n\t\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", ct, \"application/octet-stream\")\n\t\t}\n\t}\n\n\tfirstFile, err := r.FormFile(\"fileaaa\")\n\tassert.DeepEqual(t, \"TODO\", firstFile.Filename)\n\tassert.Nil(t, err)\n}\n\nfunc TestRequestUnknownSizeStreamMultipartFormWithFile(t *testing.T) {\n\tt.Parallel()\n\n\ts := `------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f1\"\n\nvalue1\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"fileaaa\"; filename=\"TODO\"\nContent-Type: application/octet-stream\n\n- SessionClient with referer and cookies support.\n- Client with requests' pipelining support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\ntailfoobar`\n\tmr := strings.NewReader(s)\n\tr := NewRequest(\"POST\", \"/upload\", mr)\n\tr.Header.SetContentLength(-1)\n\tr.Header.SetContentTypeBytes([]byte(\"multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\"))\n\n\tf, err := r.MultipartForm()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tdefer r.RemoveMultipartFormFiles()\n\n\t// all data must be consumed if the content length is unknown\n\ttail, err := ioutil.ReadAll(mr)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tif string(tail) != \"\" {\n\t\tt.Fatalf(\"unexpected tail %q. Expecting empty string\", tail)\n\t}\n\n\t// verify values\n\tif len(f.Value) != 1 {\n\t\tt.Fatalf(\"unexpected number of values in multipart form: %d. Expecting 1\", len(f.Value))\n\t}\n\tfor k, vv := range f.Value {\n\t\tif k != \"f1\" {\n\t\t\tt.Fatalf(\"unexpected value name %q. Expecting %q\", k, \"f1\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v != \"value1\" {\n\t\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"value1\")\n\t\t}\n\t}\n\n\t// verify files\n\tif len(f.File) != 1 {\n\t\tt.Fatalf(\"unexpected number of file values in multipart form: %d. Expecting 1\", len(f.File))\n\t}\n\tfor k, vv := range f.File {\n\t\tif k != \"fileaaa\" {\n\t\t\tt.Fatalf(\"unexpected file value name %q. Expecting %q\", k, \"fileaaa\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of file values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v.Filename != \"TODO\" {\n\t\t\tt.Fatalf(\"unexpected filename %q. Expecting %q\", v.Filename, \"TODO\")\n\t\t}\n\t\tct := v.Header.Get(\"Content-Type\")\n\t\tif ct != \"application/octet-stream\" {\n\t\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", ct, \"application/octet-stream\")\n\t\t}\n\t}\n}\n\nfunc TestRequestStreamMultipartFormWithFileGzip(t *testing.T) {\n\tt.Parallel()\n\n\ts := `------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f1\"\n\nvalue1\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"fileaaa\"; filename=\"TODO\"\nContent-Type: application/octet-stream\n\n- SessionClient with referer and cookies support.\n- Client with requests' pipelining support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\ntailfoobar`\n\n\tns := compress.AppendGzipBytes(nil, []byte(s))\n\n\tmr := bytes.NewBuffer(ns)\n\tr := NewRequest(\"POST\", \"/upload\", mr)\n\tr.Header.Set(\"Content-Encoding\", \"gzip\")\n\tr.Header.SetContentLength(len(s))\n\tr.Header.SetContentTypeBytes([]byte(\"multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\"))\n\n\tf, err := r.MultipartForm()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\tdefer r.RemoveMultipartFormFiles()\n\n\t// verify values\n\tif len(f.Value) != 1 {\n\t\tt.Fatalf(\"unexpected number of values in multipart form: %d. Expecting 1\", len(f.Value))\n\t}\n\tfor k, vv := range f.Value {\n\t\tif k != \"f1\" {\n\t\t\tt.Fatalf(\"unexpected value name %q. Expecting %q\", k, \"f1\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v != \"value1\" {\n\t\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"value1\")\n\t\t}\n\t}\n\n\t// verify files\n\tif len(f.File) != 1 {\n\t\tt.Fatalf(\"unexpected number of file values in multipart form: %d. Expecting 1\", len(f.File))\n\t}\n\tfor k, vv := range f.File {\n\t\tif k != \"fileaaa\" {\n\t\t\tt.Fatalf(\"unexpected file value name %q. Expecting %q\", k, \"fileaaa\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of file values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v.Filename != \"TODO\" {\n\t\t\tt.Fatalf(\"unexpected filename %q. Expecting %q\", v.Filename, \"TODO\")\n\t\t}\n\t\tct := v.Header.Get(\"Content-Type\")\n\t\tif ct != \"application/octet-stream\" {\n\t\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", ct, \"application/octet-stream\")\n\t\t}\n\t}\n}\n\nfunc TestRequestMultipartFormBoundary(t *testing.T) {\n\tr := &Request{}\n\tr.SetMultipartFormBoundary(\"----boundary----\")\n\tassert.DeepEqual(t, \"----boundary----\", r.MultipartFormBoundary())\n}\n\nfunc TestRequestSetQueryString(t *testing.T) {\n\tr := &Request{}\n\tr.SetQueryString(\"test\")\n\tassert.DeepEqual(t, \"test\", string(r.URI().queryString))\n}\n\nfunc TestRequestSetFormData(t *testing.T) {\n\tr := &Request{}\n\tdata := map[string]string{\"username\": \"admin\"}\n\tr.SetFormData(data)\n\tassert.DeepEqual(t, \"username\", string(r.postArgs.args[0].key))\n\tassert.DeepEqual(t, \"admin\", string(r.postArgs.args[0].value))\n\tassert.DeepEqual(t, true, r.parsedPostArgs)\n\tassert.DeepEqual(t, consts.MIMEApplicationHTMLForm, string(r.Header.contentType))\n\n\tr = &Request{}\n\tvalue := map[string][]string{\"item\": {\"apple\", \"peach\"}}\n\tr.SetFormDataFromValues(value)\n\tassert.DeepEqual(t, \"item\", string(r.postArgs.args[0].key))\n\tassert.DeepEqual(t, \"apple\", string(r.postArgs.args[0].value))\n\tassert.DeepEqual(t, \"item\", string(r.postArgs.args[1].key))\n\tassert.DeepEqual(t, \"peach\", string(r.postArgs.args[1].value))\n}\n\nfunc TestRequestSetFile(t *testing.T) {\n\tr := &Request{}\n\tr.SetFile(\"file\", \"/usr/bin/test.txt\")\n\tassert.DeepEqual(t, &File{\"/usr/bin/test.txt\", \"file\", nil}, r.multipartFiles[0])\n\n\tfiles := map[string]string{\"f1\": \"/usr/bin/test1.txt\"}\n\tr.SetFiles(files)\n\tassert.DeepEqual(t, &File{\"/usr/bin/test1.txt\", \"f1\", nil}, r.multipartFiles[1])\n\n\tassert.DeepEqual(t, []*File{{\"/usr/bin/test.txt\", \"file\", nil}, {\"/usr/bin/test1.txt\", \"f1\", nil}}, r.MultipartFiles())\n}\n\nfunc TestRequestSetFileReader(t *testing.T) {\n\tr := &Request{}\n\tr.SetFileReader(\"file\", \"/usr/bin/test.txt\", nil)\n\tassert.DeepEqual(t, &File{\"/usr/bin/test.txt\", \"file\", nil}, r.multipartFiles[0])\n}\n\nfunc TestRequestSetMultipartFormData(t *testing.T) {\n\tr := &Request{}\n\tdata := map[string]string{\"item\": \"apple\"}\n\tr.SetMultipartFormData(data)\n\tassert.DeepEqual(t, &MultipartField{\"item\", \"\", \"\", strings.NewReader(\"apple\")}, r.multipartFields[0])\n\n\tr = &Request{}\n\tfields := []*MultipartField{{\"item2\", \"\", \"\", strings.NewReader(\"apple2\")}, {\"item3\", \"\", \"\", strings.NewReader(\"apple3\")}}\n\tr.SetMultipartFields(fields...)\n\tassert.DeepEqual(t, fields, r.MultipartFields())\n}\n\nfunc TestRequestSetBasicAuth(t *testing.T) {\n\tr := &Request{}\n\tr.SetBasicAuth(\"admin\", \"admin\")\n\tassert.DeepEqual(t, \"Authorization\", string(r.Header.h[0].key))\n\tassert.DeepEqual(t, \"Basic \"+base64.StdEncoding.EncodeToString([]byte(\"admin:admin\")), string(r.Header.h[0].value))\n}\n\nfunc TestRequestSetAuthToken(t *testing.T) {\n\tr := &Request{}\n\tr.SetAuthToken(\"token\")\n\tassert.DeepEqual(t, \"Authorization\", string(r.Header.h[0].key))\n\tassert.DeepEqual(t, \"Bearer token\", string(r.Header.h[0].value))\n\n\tr = &Request{}\n\tr.SetAuthSchemeToken(\"http\", \"token\")\n\tassert.DeepEqual(t, \"Authorization\", string(r.Header.h[0].key))\n\tassert.DeepEqual(t, \"http token\", string(r.Header.h[0].value))\n}\n\nfunc TestRequestSetHeaders(t *testing.T) {\n\tr := &Request{}\n\theaders := map[string]string{\"Key1\": \"value1\"}\n\tr.SetHeaders(headers)\n\tassert.DeepEqual(t, \"Key1\", string(r.Header.h[0].key))\n\tassert.DeepEqual(t, \"value1\", string(r.Header.h[0].value))\n}\n\nfunc TestRequestSetCookie(t *testing.T) {\n\tr := &Request{}\n\tr.SetCookie(\"cookie1\", \"cookie1\")\n\tassert.DeepEqual(t, \"cookie1\", string(r.Header.cookies[0].key))\n\tassert.DeepEqual(t, \"cookie1\", string(r.Header.cookies[0].value))\n\n\tr.SetCookies(map[string]string{\"cookie2\": \"cookie2\"})\n\tassert.DeepEqual(t, \"cookie2\", string(r.Header.cookies[1].key))\n\tassert.DeepEqual(t, \"cookie2\", string(r.Header.cookies[1].value))\n}\n\nfunc TestRequestPath(t *testing.T) {\n\tr := NewRequest(\"POST\", \"/upload?test\", nil)\n\tassert.DeepEqual(t, \"/upload\", string(r.Path()))\n\tassert.DeepEqual(t, \"test\", string(r.QueryString()))\n}\n\nfunc TestRequestConnectionClose(t *testing.T) {\n\tr := NewRequest(\"POST\", \"/upload?test\", nil)\n\tassert.DeepEqual(t, false, r.ConnectionClose())\n\tr.SetConnectionClose()\n\tassert.DeepEqual(t, true, r.ConnectionClose())\n}\n\nfunc TestRequestBodyWriteToPlain(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\texpectedS := \"foobarbaz\"\n\tr.AppendBodyString(expectedS)\n\n\ttestBodyWriteTo(t, &r, expectedS, true)\n}\n\nfunc TestRequestBodyWriteToMultipart(t *testing.T) {\n\tt.Parallel()\n\n\texpectedS := \"--foobar\\r\\nContent-Disposition: form-data; name=\\\"key_0\\\"\\r\\n\\r\\nvalue_0\\r\\n--foobar--\\r\\n\"\n\n\tvar r Request\n\tSetMultipartFormWithBoundary(&r, &multipart.Form{Value: map[string][]string{\"key_0\": {\"value_0\"}}}, \"foobar\")\n\n\ttestBodyWriteTo(t, &r, expectedS, true)\n}\n\nfunc TestNewRequest(t *testing.T) {\n\t// get\n\treq := NewRequest(\"GET\", \"http://www.google.com/hi\", bytes.NewReader([]byte(\"hello\")))\n\tassert.NotNil(t, req)\n\tassert.DeepEqual(t, \"GET /hi HTTP/1.1\\r\\nHost: www.google.com\\r\\n\\r\\n\", string(req.Header.Header()))\n\tassert.Nil(t, req.Body())\n\n\t// post + bytes reader\n\treq = NewRequest(\"POST\", \"http://www.google.com/hi\", bytes.NewReader([]byte(\"hello\")))\n\tassert.NotNil(t, req)\n\tassert.DeepEqual(t, \"POST /hi HTTP/1.1\\r\\nHost: www.google.com\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nContent-Length: 5\\r\\n\\r\\n\", string(req.Header.Header()))\n\tassert.DeepEqual(t, \"hello\", string(req.Body()))\n\n\t// post + string reader\n\treq = NewRequest(\"POST\", \"http://www.google.com/hi\", strings.NewReader(\"hello world\"))\n\tassert.NotNil(t, req)\n\tassert.DeepEqual(t, \"POST /hi HTTP/1.1\\r\\nHost: www.google.com\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nContent-Length: 11\\r\\n\\r\\n\", string(req.Header.Header()))\n\tassert.DeepEqual(t, \"hello world\", string(req.Body()))\n\n\t// post + bytes buffer\n\treq = NewRequest(\"POST\", \"http://www.google.com/hi\", bytes.NewBuffer([]byte(\"hello hertz!\")))\n\tassert.NotNil(t, req)\n\tassert.DeepEqual(t, \"POST /hi HTTP/1.1\\r\\nHost: www.google.com\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nContent-Length: 12\\r\\n\\r\\n\", string(req.Header.Header()))\n\tassert.DeepEqual(t, \"hello hertz!\", string(req.Body()))\n\n\t// empty method\n\treq = NewRequest(\"\", \"/\", bytes.NewBufferString(\"\"))\n\tassert.DeepEqual(t, \"GET\", string(req.Method()))\n\t// unstandard method\n\treq = NewRequest(\"DUMMY\", \"/\", bytes.NewBufferString(\"\"))\n\tassert.DeepEqual(t, \"DUMMY\", string(req.Method()))\n\n\t// empty body\n\treq = NewRequest(\"GET\", \"/\", nil)\n\tassert.NotNil(t, req)\n\t// wrong body\n\treq = NewRequest(\"POST\", \"/\", errorReader{})\n\t_, err := req.BodyE()\n\tassert.DeepEqual(t, err.Error(), \"dummy!\")\n\treq = NewRequest(\"POST\", \"/\", errorReader{})\n\tbody := req.Body()\n\tassert.Nil(t, body)\n\n\t// GET RequestURI\n\treq = NewRequest(\"GET\", \"http://www.google.com/hi?a=1&b=2\", nil)\n\tassert.DeepEqual(t, \"/hi?a=1&b=2\", string(req.RequestURI()))\n\n\t// POST RequestURI\n\treq = NewRequest(\"POST\", \"http://www.google.com/hi?a=1&b=2\", nil)\n\tassert.DeepEqual(t, \"/hi?a=1&b=2\", string(req.RequestURI()))\n\n\t// nil-interface body\n\tassert.Panic(t, func() {\n\t\tfake := func() *errorReader {\n\t\t\treturn nil\n\t\t}\n\t\treq = NewRequest(\"POST\", \"/\", fake())\n\t\treq.Body()\n\t})\n}\n\nfunc TestRequestResetBody(t *testing.T) {\n\treq := Request{}\n\treq.BodyBuffer()\n\tassert.NotNil(t, req.body)\n\treq.maxKeepBodySize = math.MaxUint32\n\treq.ResetBody()\n\tassert.NotNil(t, req.body)\n\treq.maxKeepBodySize = -1\n\treq.ResetBody()\n\tassert.Nil(t, req.body)\n}\n\nfunc TestRequestConstructBodyStream(t *testing.T) {\n\tr := &Request{}\n\tb := []byte(\"test\")\n\tr.ConstructBodyStream(&bytebufferpool.ByteBuffer{B: b}, strings.NewReader(\"test\"))\n\tassert.DeepEqual(t, \"test\", string(r.body.B))\n\tstream := make([]byte, 4)\n\tr.bodyStream.Read(stream)\n\tassert.DeepEqual(t, \"test\", string(stream))\n}\n\nfunc TestRequestPostArgs(t *testing.T) {\n\tt.Parallel()\n\n\ts := `username=admin&password=admin`\n\tmr := strings.NewReader(s)\n\tr := &Request{}\n\tr.SetBodyStream(mr, len(s))\n\tr.Header.contentType = []byte(consts.MIMEApplicationHTMLForm)\n\targ := r.PostArgs()\n\tassert.DeepEqual(t, \"username\", string(arg.args[0].key))\n\tassert.DeepEqual(t, \"admin\", string(arg.args[0].value))\n\tassert.DeepEqual(t, \"password\", string(arg.args[1].key))\n\tassert.DeepEqual(t, \"admin\", string(arg.args[1].value))\n\tassert.DeepEqual(t, \"username=admin&password=admin\", string(r.PostArgString()))\n}\n\nfunc TestRequestMayContinue(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\tif r.MayContinue() {\n\t\tt.Fatalf(\"MayContinue on empty request must return false\")\n\t}\n\n\tr.Header.Set(\"Expect\", \"123sdfds\")\n\tif r.MayContinue() {\n\t\tt.Fatalf(\"MayContinue on invalid Expect header must return false\")\n\t}\n\n\tr.Header.Set(\"Expect\", \"100-continue\")\n\tif !r.MayContinue() {\n\t\tt.Fatalf(\"MayContinue on 'Expect: 100-continue' header must return true\")\n\t}\n}\n\nfunc TestRequestSwapBodySerial(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestSwapBody(t)\n}\n\nfunc testRequestSwapBody(t *testing.T) {\n\tvar b []byte\n\tr := &Request{}\n\tfor i := 0; i < 20; i++ {\n\t\tbOrig := r.Body()\n\t\tb = r.SwapBody(b)\n\t\tif !bytes.Equal(bOrig, b) {\n\t\t\tt.Fatalf(\"unexpected body returned: %q. Expecting %q\", b, bOrig)\n\t\t}\n\t\tr.AppendBodyString(\"foobar\")\n\t}\n\n\ts := \"aaaabbbbcccc\"\n\tb = b[:0]\n\tfor i := 0; i < 10; i++ {\n\t\tr.SetBodyStream(bytes.NewBufferString(s), len(s))\n\t\tb = r.SwapBody(b)\n\t\tif string(b) != s {\n\t\t\tt.Fatalf(\"unexpected body returned: %q. Expecting %q\", b, s)\n\t\t}\n\t\tb = r.SwapBody(b)\n\t\tif len(b) > 0 {\n\t\t\tt.Fatalf(\"unexpected body with non-zero size returned: %q\", b)\n\t\t}\n\t}\n}\n\n// Test case for testing BasicAuth\nvar BasicAuthTests = []struct {\n\theader, username, password string\n\tok                         bool\n}{\n\t{\"Basic \" + base64.StdEncoding.EncodeToString([]byte(\"Aladdin:open sesame\")), \"Aladdin\", \"open sesame\", true},\n\n\t// Case doesn't matter:\n\t{\"BASIC \" + base64.StdEncoding.EncodeToString([]byte(\"Aladdin:open sesame\")), \"Aladdin\", \"open sesame\", true},\n\t{\"basic \" + base64.StdEncoding.EncodeToString([]byte(\"Aladdin:open sesame\")), \"Aladdin\", \"open sesame\", true},\n\n\t{\"Basic \" + base64.StdEncoding.EncodeToString([]byte(\"Aladdin:open:sesame\")), \"Aladdin\", \"open:sesame\", true},\n\t{\"Basic \" + base64.StdEncoding.EncodeToString([]byte(\":\")), \"\", \"\", true},\n\t{\"Basic\" + base64.StdEncoding.EncodeToString([]byte(\"Aladdin:open sesame\")), \"\", \"\", false},\n\t{base64.StdEncoding.EncodeToString([]byte(\"Aladdin:open sesame\")), \"\", \"\", false},\n\t{\"Basic \", \"\", \"\", false},\n\t{\"Basic Aladdin:open sesame\", \"\", \"\", false},\n\t{`Digest username=\"Aladdin\"`, \"\", \"\", false},\n}\n\n// struct for\ntype getBasicAuthTest struct {\n\tusername, password string\n\tok                 bool\n}\n\nfunc TestRequestBasicAuth(t *testing.T) {\n\tfor _, tt := range BasicAuthTests {\n\t\treq := NewRequest(\"GET\", \"http://www.google.com/hi\", bytes.NewReader([]byte(\"hello\")))\n\t\treq.SetHeader(\"Authorization\", tt.header)\n\t\tusername, password, ok := req.BasicAuth()\n\t\tif ok != tt.ok || username != tt.username || password != tt.password {\n\t\t\tt.Fatalf(\"BasicAuth() = %+v, want %+v\", getBasicAuthTest{username, password, ok},\n\t\t\t\tgetBasicAuthTest{tt.username, tt.password, tt.ok})\n\t\t}\n\t}\n}\n\n// Issue: NewRequest should create a Request that doesn't use input parameters as its struct,\n// otherwise it will cause panic when we pass a const string as method to NewRequest and call req.SetMethod()\nfunc TestNewRequestWithConstParam(t *testing.T) {\n\tconst method = \"POST\"\n\tconst uri = \"http://www.google.com/hi\"\n\treq := NewRequest(method, uri, nil)\n\treq.SetMethod(\"POST\")\n\treq.SetRequestURI(\"http://www.google.com/hi\")\n}\n\nfunc TestRequestCopyToWithOptions(t *testing.T) {\n\treq := AcquireRequest()\n\tk1 := \"a\"\n\tv1 := \"A\"\n\tk2 := \"b\"\n\tv2 := \"B\"\n\treq.SetOptions(config.WithTag(k1, v1), config.WithTag(k2, v2), config.WithSD(true))\n\treqCopy := AcquireRequest()\n\treq.CopyTo(reqCopy)\n\tassert.DeepEqual(t, v1, reqCopy.options.Tag(k1))\n\tassert.DeepEqual(t, v2, reqCopy.options.Tag(k2))\n\tassert.DeepEqual(t, true, reqCopy.options.IsSD())\n}\n\nfunc TestRequestSetMaxKeepBodySize(t *testing.T) {\n\tr := &Request{}\n\tr.SetMaxKeepBodySize(1024)\n\tassert.DeepEqual(t, 1024, r.maxKeepBodySize)\n}\n\nfunc TestRequestBodyReuse(t *testing.T) {\n\treq := Request{}\n\treq.maxKeepBodySize = 1024\n\n\tbuf := req.BodyBuffer()\n\t// set a big body\n\tbuf.Write(make([]byte, req.maxKeepBodySize+1))\n\treq.ResetBody()\n\tassert.Nil(t, req.body)\n\t// NOTICE: bytebufferpool may not get a big enough buffer,\n\t// so we just mock a new one here\n\treq.body = &bytebufferpool.ByteBuffer{\n\t\tB: make([]byte, 0, req.maxKeepBodySize+1),\n\t}\n\t// set a small body\n\tbuf = req.BodyBuffer()\n\tbuf.Write(make([]byte, 1))\n\treq.ResetBody()\n\tassert.Nil(t, req.body)\n}\n\nfunc TestRequestGetBodyAfterGetBodyStream(t *testing.T) {\n\treq := AcquireRequest()\n\treq.SetBodyString(\"abc\")\n\treq.BodyStream()\n\tassert.DeepEqual(t, req.Body(), []byte(\"abc\"))\n}\n\nfunc TestRequestSetOptionsNotOverwrite(t *testing.T) {\n\treq := AcquireRequest()\n\treq.SetOptions(config.WithSD(true))\n\treq.SetOptions(config.WithTag(\"a\", \"b\"))\n\treq.SetOptions(config.WithTag(\"c\", \"d\"))\n\tassert.DeepEqual(t, true, req.Options().IsSD())\n\tassert.DeepEqual(t, \"b\", req.Options().Tag(\"a\"))\n\tassert.DeepEqual(t, \"d\", req.Options().Tag(\"c\"))\n\n\treq.SetOptions(config.WithTag(\"a\", \"c\"))\n\tassert.DeepEqual(t, \"c\", req.Options().Tag(\"a\"))\n}\n\ntype bodyWriterTo interface {\n\tBodyWriteTo(writer io.Writer) error\n\tBody() []byte\n}\n\nfunc testBodyWriteTo(t *testing.T, bw bodyWriterTo, expectedS string, isRetainedBody bool) {\n\tvar buf bytebufferpool.ByteBuffer\n\tif err := bw.BodyWriteTo(&buf); err != nil {\n\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t}\n\n\ts := buf.B\n\tif string(s) != expectedS {\n\t\tt.Fatalf(\"unexpected result %q. Expecting %q\", s, expectedS)\n\t}\n\n\tbody := bw.Body()\n\tif isRetainedBody {\n\t\tif string(body) != expectedS {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedS)\n\t\t}\n\t} else {\n\t\tif len(body) > 0 {\n\t\t\tt.Fatalf(\"unexpected non-zero body after BodyWriteTo: %q\", body)\n\t\t}\n\t}\n}\n\nfunc TestReqSafeCopy(t *testing.T) {\n\treq := AcquireRequest()\n\treq.bodyRaw = make([]byte, 1)\n\treqs := make([]*Request, 10)\n\tfor i := 0; i < 10; i++ {\n\t\treq.bodyRaw[0] = byte(i)\n\t\ttmpReq := AcquireRequest()\n\t\treq.CopyTo(tmpReq)\n\t\treqs[i] = tmpReq\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\tassert.DeepEqual(t, []byte{byte(i)}, reqs[i].Body())\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/response.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/nocopy\"\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/common/compress\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\nvar (\n\tresponsePool sync.Pool\n\t// NoResponseBody is an io.ReadCloser with no bytes. Read always returns EOF\n\t// and Close always returns nil. It can be used in an ingoing client\n\t// response to explicitly signal that a response has zero bytes.\n\tNoResponseBody = noBody{}\n)\n\n// Response represents HTTP response.\n//\n// It is forbidden copying Response instances. Create new instances\n// and use CopyTo instead.\n//\n// Response instance MUST NOT be used from concurrently running goroutines.\ntype Response struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\t// Response header\n\t//\n\t// Copying Header by value is forbidden. Use pointer to Header instead.\n\tHeader ResponseHeader\n\n\t// Flush headers as soon as possible without waiting for first body bytes.\n\t// Relevant for bodyStream only.\n\tImmediateHeaderFlush bool\n\n\tbodyStream      io.Reader\n\tw               responseBodyWriter\n\tbody            *bytebufferpool.ByteBuffer\n\tbodyRaw         []byte\n\tmaxKeepBodySize int\n\n\t// Response.Read() skips reading body if set to true.\n\t// Use it for reading HEAD responses.\n\t//\n\t// Response.Write() skips writing body if set to true.\n\t// Use it for writing HEAD responses.\n\tSkipBody bool\n\n\t// Remote TCPAddr from concurrently net.Conn\n\traddr net.Addr\n\t// Local TCPAddr from concurrently net.Conn\n\tladdr net.Addr\n\n\t// If set a hijackWriter, hertz will skip the default header/body writer process.\n\thijackWriter network.ExtWriter\n}\n\nfunc (resp *Response) GetHijackWriter() network.ExtWriter {\n\treturn resp.hijackWriter\n}\n\nfunc (resp *Response) HijackWriter(writer network.ExtWriter) {\n\tresp.hijackWriter = writer\n}\n\ntype responseBodyWriter struct {\n\tr *Response\n}\n\nfunc (w *responseBodyWriter) Write(p []byte) (int, error) {\n\tw.r.AppendBody(p)\n\treturn len(p), nil\n}\n\nfunc (resp *Response) MustSkipBody() bool {\n\treturn resp.SkipBody || resp.Header.MustSkipContentLength()\n}\n\n// BodyGunzip returns un-gzipped body data.\n//\n// This method may be used if the response header contains\n// 'Content-Encoding: gzip' for reading un-gzipped body.\n// Use Body for reading gzipped response body.\nfunc (resp *Response) BodyGunzip() ([]byte, error) {\n\treturn gunzipData(resp.Body())\n}\n\n// SetConnectionClose sets 'Connection: close' header.\nfunc (resp *Response) SetConnectionClose() {\n\tresp.Header.SetConnectionClose(true)\n}\n\n// SetBodyString sets response body.\nfunc (resp *Response) SetBodyString(body string) {\n\tresp.CloseBodyStream()            //nolint:errcheck\n\tresp.BodyBuffer().SetString(body) //nolint:errcheck\n}\n\nfunc (resp *Response) ConstructBodyStream(body *bytebufferpool.ByteBuffer, bodyStream io.Reader) {\n\tresp.body = body\n\tresp.bodyStream = bodyStream\n}\n\n// BodyWriter returns writer for populating response body.\n//\n// If used inside RequestHandler, the returned writer must not be used\n// after returning from RequestHandler. Use RequestContext.Write\n// or SetBodyStreamWriter in this case.\nfunc (resp *Response) BodyWriter() io.Writer {\n\tresp.w.r = resp\n\treturn &resp.w\n}\n\n// SetStatusCode sets response status code.\nfunc (resp *Response) SetStatusCode(statusCode int) {\n\tresp.Header.SetStatusCode(statusCode)\n}\n\nfunc (resp *Response) SetMaxKeepBodySize(n int) {\n\tresp.maxKeepBodySize = n\n}\n\nfunc (resp *Response) BodyBytes() []byte {\n\tif resp.bodyRaw != nil {\n\t\treturn resp.bodyRaw\n\t}\n\tif resp.body == nil {\n\t\treturn nil\n\t}\n\treturn resp.body.B\n}\n\nfunc (resp *Response) HasBodyBytes() bool {\n\treturn len(resp.BodyBytes()) != 0\n}\n\nfunc (resp *Response) CopyToSkipBody(dst *Response) {\n\tdst.Reset()\n\tresp.Header.CopyTo(&dst.Header)\n\tdst.SkipBody = resp.SkipBody\n\tdst.raddr = resp.raddr\n\tdst.laddr = resp.laddr\n}\n\n// IsBodyStream returns true if body is set via SetBodyStream*\nfunc (resp *Response) IsBodyStream() bool {\n\treturn resp.bodyStream != nil\n}\n\n// SetBodyStream sets response body stream and, optionally body size.\n//\n// If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes\n// before returning io.EOF.\n//\n// If bodySize < 0, then bodyStream is read until io.EOF.\n//\n// bodyStream.Close() is called after finishing reading all body data\n// if it implements io.Closer.\n//\n// See also SetBodyStreamWriter.\nfunc (resp *Response) SetBodyStream(bodyStream io.Reader, bodySize int) {\n\tresp.ResetBody()\n\tresp.bodyStream = bodyStream\n\tresp.Header.SetContentLength(bodySize)\n}\n\n// SetBodyStreamNoReset is almost the same as SetBodyStream,\n// but it doesn't reset the bodyStream before.\nfunc (resp *Response) SetBodyStreamNoReset(bodyStream io.Reader, bodySize int) {\n\tresp.bodyStream = bodyStream\n\tresp.Header.SetContentLength(bodySize)\n}\n\n// BodyE returns response body.\nfunc (resp *Response) BodyE() ([]byte, error) {\n\tif resp.bodyStream != nil {\n\t\tbodyBuf := resp.BodyBuffer()\n\t\tbodyBuf.Reset()\n\t\tzw := network.NewWriter(bodyBuf)\n\t\t_, err := utils.CopyZeroAlloc(zw, resp.bodyStream)\n\t\tresp.CloseBodyStream() //nolint:errcheck\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn resp.BodyBytes(), nil\n}\n\n// Body returns response body.\n// if get body failed, returns nil.\nfunc (resp *Response) Body() []byte {\n\tbody, _ := resp.BodyE()\n\treturn body\n}\n\n// BodyWriteTo writes response body to w.\nfunc (resp *Response) BodyWriteTo(w io.Writer) error {\n\tzw := network.NewWriter(w)\n\tif resp.bodyStream != nil {\n\t\t_, err := utils.CopyZeroAlloc(zw, resp.bodyStream)\n\t\tresp.CloseBodyStream() //nolint:errcheck\n\t\treturn err\n\t}\n\n\tbody := resp.BodyBytes()\n\tzw.WriteBinary(body) //nolint:errcheck\n\treturn zw.Flush()\n}\n\n// CopyTo copies resp contents to dst except of body stream.\nfunc (resp *Response) CopyTo(dst *Response) {\n\tresp.CopyToSkipBody(dst)\n\tif resp.bodyRaw != nil {\n\t\tdst.bodyRaw = append(dst.bodyRaw[:0], resp.bodyRaw...)\n\t\tif dst.body != nil {\n\t\t\tdst.body.Reset()\n\t\t}\n\t} else if resp.body != nil {\n\t\tdst.BodyBuffer().Set(resp.body.B)\n\t} else if dst.body != nil {\n\t\tdst.body.Reset()\n\t}\n}\n\nfunc SwapResponseBody(a, b *Response) {\n\ta.body, b.body = b.body, a.body\n\ta.bodyRaw, b.bodyRaw = b.bodyRaw, a.bodyRaw\n\ta.bodyStream, b.bodyStream = b.bodyStream, a.bodyStream\n}\n\n// Reset clears response contents.\nfunc (resp *Response) Reset() {\n\tresp.Header.Reset()\n\tresp.resetSkipHeader()\n\tresp.SkipBody = false\n\tresp.raddr = nil\n\tresp.laddr = nil\n\tresp.ImmediateHeaderFlush = false\n\tresp.hijackWriter = nil\n}\n\nfunc (resp *Response) resetSkipHeader() {\n\tresp.ResetBody()\n}\n\n// ResetBody resets response body.\nfunc (resp *Response) ResetBody() {\n\tresp.bodyRaw = nil\n\tresp.CloseBodyStream() //nolint:errcheck\n\tif resp.body != nil {\n\t\tif resp.body.Cap() <= resp.maxKeepBodySize {\n\t\t\tresp.body.Reset()\n\t\t\treturn\n\t\t}\n\t\tresponseBodyPool.Put(resp.body)\n\t\tresp.body = nil\n\t}\n}\n\n// SetBodyRaw sets response body, but without copying it.\n//\n// From this point onward the body argument must not be changed.\nfunc (resp *Response) SetBodyRaw(body []byte) {\n\tresp.ResetBody()\n\tresp.bodyRaw = body\n}\n\n// StatusCode returns response status code.\nfunc (resp *Response) StatusCode() int {\n\treturn resp.Header.StatusCode()\n}\n\n// SetBody sets response body.\n//\n// It is safe re-using body argument after the function returns.\nfunc (resp *Response) SetBody(body []byte) {\n\tresp.CloseBodyStream() //nolint:errcheck\n\tif resp.GetHijackWriter() == nil {\n\t\tresp.BodyBuffer().Set(body) //nolint:errcheck\n\t\treturn\n\t}\n\n\t// If the hijack writer support .SetBody() api, then use it.\n\tif setter, ok := resp.GetHijackWriter().(interface {\n\t\tSetBody(b []byte)\n\t}); ok {\n\t\tsetter.SetBody(body)\n\t\treturn\n\t}\n\n\t// Otherwise, call .Write() api instead.\n\tresp.GetHijackWriter().Write(body) //nolint:errcheck\n}\n\nfunc (resp *Response) BodyStream() io.Reader {\n\tif resp.bodyStream == nil {\n\t\treturn NoResponseBody\n\t}\n\treturn resp.bodyStream\n}\n\n// Hijack returns the underlying network.Conn if available.\n//\n// It's only available when StatusCode() == 101 and \"Connection: Upgrade\",\n// coz Hertz will NOT reuse connection in this case,\n// then make it optional for users to implement their own protocols.\n//\n// The most common scenario is used with github.com/hertz-contrib/websocket\nfunc (resp *Response) Hijack() (network.Conn, error) {\n\tif resp.bodyStream != nil {\n\t\th, ok := resp.bodyStream.(interface {\n\t\t\tHijack() (network.Conn, error)\n\t\t})\n\t\tif ok {\n\t\t\treturn h.Hijack()\n\t\t}\n\t}\n\treturn nil, errors.New(\"not available\")\n}\n\n// AppendBody appends p to response body.\n//\n// It is safe re-using p after the function returns.\nfunc (resp *Response) AppendBody(p []byte) {\n\tresp.CloseBodyStream() //nolint:errcheck\n\tif resp.hijackWriter != nil {\n\t\tresp.hijackWriter.Write(p) //nolint:errcheck\n\t\treturn\n\t}\n\tresp.BodyBuffer().Write(p) //nolint:errcheck\n}\n\n// AppendBodyString appends s to response body.\nfunc (resp *Response) AppendBodyString(s string) {\n\tresp.CloseBodyStream() //nolint:errcheck\n\tif resp.hijackWriter != nil {\n\t\tresp.hijackWriter.Write(bytesconv.S2b(s)) //nolint:errcheck\n\t\treturn\n\t}\n\tresp.BodyBuffer().WriteString(s) //nolint:errcheck\n}\n\n// ConnectionClose returns true if 'Connection: close' header is set.\nfunc (resp *Response) ConnectionClose() bool {\n\treturn resp.Header.ConnectionClose()\n}\n\n// CloseBodyStream tries call Close() of underlying body stream.\n//\n// NOTE:\n// * MUST NOT call CloseBodyStream() and BodyStream().Read() concurrently to avoid race issue.\nfunc (resp *Response) CloseBodyStream() error {\n\tif resp.bodyStream == nil {\n\t\treturn nil\n\t}\n\tvar err error\n\tif bsc, ok := resp.bodyStream.(io.Closer); ok {\n\t\terr = bsc.Close()\n\t}\n\tresp.bodyStream = nil\n\treturn err\n}\n\nfunc (resp *Response) BodyBuffer() *bytebufferpool.ByteBuffer {\n\tif resp.body == nil {\n\t\tresp.body = responseBodyPool.Get()\n\t}\n\tresp.bodyRaw = nil\n\treturn resp.body\n}\n\nfunc gunzipData(p []byte) ([]byte, error) {\n\tvar bb bytebufferpool.ByteBuffer\n\t_, err := compress.WriteGunzip(&bb, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bb.B, nil\n}\n\n// RemoteAddr returns the remote network address. The Addr returned is shared\n// by all invocations of RemoteAddr, so do not modify it.\nfunc (resp *Response) RemoteAddr() net.Addr {\n\treturn resp.raddr\n}\n\n// LocalAddr returns the local network address. The Addr returned is shared\n// by all invocations of LocalAddr, so do not modify it.\nfunc (resp *Response) LocalAddr() net.Addr {\n\treturn resp.laddr\n}\n\nfunc (resp *Response) ParseNetAddr(conn network.Conn) {\n\tresp.raddr = conn.RemoteAddr()\n\tresp.laddr = conn.LocalAddr()\n}\n\n// AcquireResponse returns an empty Response instance from response pool.\n//\n// The returned Response instance may be passed to ReleaseResponse when it is\n// no longer needed. This allows Response recycling, reduces GC pressure\n// and usually improves performance.\nfunc AcquireResponse() *Response {\n\tv := responsePool.Get()\n\tif v == nil {\n\t\treturn &Response{}\n\t}\n\treturn v.(*Response)\n}\n\n// ReleaseResponse return resp acquired via AcquireResponse to response pool.\n//\n// It is forbidden accessing resp and/or its members after returning\n// it to response pool.\nfunc ReleaseResponse(resp *Response) {\n\tresp.Reset()\n\tresponsePool.Put(resp)\n}\n"
  },
  {
    "path": "pkg/protocol/response_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/common/compress\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc TestResponseCopyTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar resp Response\n\n\t// empty copy\n\ttestResponseCopyTo(t, &resp)\n\n\t// init resp\n\t// resp.laddr = zeroTCPAddr\n\tresp.SkipBody = true\n\tresp.Header.SetStatusCode(consts.StatusOK)\n\tresp.SetBodyString(\"test\")\n\ttestResponseCopyTo(t, &resp)\n}\n\nfunc TestResponseBodyStreamMultipleBodyCalls(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\ts := \"foobar baz abc\"\n\tif r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tr.SetBodyStream(bytes.NewBufferString(s), len(s))\n\tif !r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\tbody := r.Body()\n\t\tif string(body) != s {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q. iteration %d\", body, s, i)\n\t\t}\n\t}\n}\n\nfunc TestResponseBodyWriteToPlain(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\texpectedS := \"foobarbaz\"\n\tr.AppendBodyString(expectedS)\n\n\ttestBodyWriteTo(t, &r, expectedS, true)\n}\n\nfunc TestResponseBodyWriteToStream(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\texpectedS := \"aaabbbccc\"\n\tbuf := bytes.NewBufferString(expectedS)\n\tif r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tr.SetBodyStream(buf, len(expectedS))\n\tif !r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\n\ttestBodyWriteTo(t, &r, expectedS, false)\n}\n\nfunc TestResponseBodyWriter(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\tw := r.BodyWriter()\n\tfor i := 0; i < 10; i++ {\n\t\tfmt.Fprintf(w, \"%d\", i)\n\t}\n\tif string(r.Body()) != \"0123456789\" {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", r.Body(), \"0123456789\")\n\t}\n}\n\nfunc TestResponseRawBodySet(t *testing.T) {\n\tt.Parallel()\n\n\tvar resp Response\n\n\texpectedS := \"test\"\n\tbody := []byte(expectedS)\n\tresp.SetBodyRaw(body)\n\n\ttestBodyWriteTo(t, &resp, expectedS, true)\n}\n\nfunc TestResponseRawBodyReset(t *testing.T) {\n\tt.Parallel()\n\n\tvar resp Response\n\n\tbody := []byte(\"test\")\n\tresp.SetBodyRaw(body)\n\tresp.ResetBody()\n\n\ttestBodyWriteTo(t, &resp, \"\", true)\n}\n\nfunc TestResponseResetBody(t *testing.T) {\n\tresp := Response{}\n\tresp.BodyBuffer()\n\tassert.NotNil(t, resp.body)\n\tresp.maxKeepBodySize = math.MaxUint32\n\tresp.ResetBody()\n\tassert.NotNil(t, resp.body)\n\tresp.maxKeepBodySize = -1\n\tresp.ResetBody()\n\tassert.Nil(t, resp.body)\n}\n\nfunc TestResponseBodyReuse(t *testing.T) {\n\tresp := Response{}\n\tresp.maxKeepBodySize = 1024\n\n\tbuf := resp.BodyBuffer()\n\t// set a big body\n\tbuf.Write(make([]byte, resp.maxKeepBodySize+1))\n\tresp.ResetBody()\n\tassert.Nil(t, resp.body)\n\t// NOTICE: bytebufferpool may not get a big enough buffer,\n\t// so we just mock a new one here\n\tresp.body = &bytebufferpool.ByteBuffer{\n\t\tB: make([]byte, 0, resp.maxKeepBodySize+1),\n\t}\n\t// set a small body\n\tbuf.Write(make([]byte, 1))\n\tresp.ResetBody()\n\tassert.Nil(t, resp.body)\n}\n\nfunc testResponseCopyTo(t *testing.T, src *Response) {\n\tvar dst Response\n\tsrc.CopyTo(&dst)\n\n\tif !reflect.DeepEqual(src, &dst) { //nolint:govet\n\t\tt.Fatalf(\"ResponseCopyTo fail, src: \\n%+v\\ndst: \\n%+v\\n\", src, &dst) //nolint:govet\n\t}\n}\n\nfunc TestResponseMustSkipBody(t *testing.T) {\n\tresp := Response{}\n\tresp.SetStatusCode(consts.StatusOK)\n\tresp.SetBodyString(\"test\")\n\tassert.False(t, resp.MustSkipBody())\n\t// no content 204 means that skip body is necessary\n\tresp.SetStatusCode(consts.StatusNoContent)\n\tresp.ResetBody()\n\tassert.True(t, resp.MustSkipBody())\n}\n\nfunc TestResponseBodyGunzip(t *testing.T) {\n\tt.Parallel()\n\tdst1 := []byte(\"\")\n\tsrc1 := []byte(\"hello\")\n\tres1 := compress.AppendGzipBytes(dst1, src1)\n\tresp := Response{}\n\tresp.SetBody(res1)\n\tzipData, err := resp.BodyGunzip()\n\tassert.Nil(t, err)\n\tassert.DeepEqual(t, zipData, src1)\n}\n\nfunc TestResponseSwapResponseBody(t *testing.T) {\n\tt.Parallel()\n\tresp1 := Response{}\n\tstr1 := \"resp1\"\n\tbyteBuffer1 := &bytebufferpool.ByteBuffer{}\n\tbyteBuffer1.Set([]byte(str1))\n\tresp1.ConstructBodyStream(byteBuffer1, bytes.NewBufferString(str1))\n\tassert.True(t, resp1.HasBodyBytes())\n\tresp2 := Response{}\n\tstr2 := \"resp2\"\n\tbyteBuffer2 := &bytebufferpool.ByteBuffer{}\n\tbyteBuffer2.Set([]byte(str2))\n\tresp2.ConstructBodyStream(byteBuffer2, bytes.NewBufferString(str2))\n\tSwapResponseBody(&resp1, &resp2)\n\tassert.DeepEqual(t, resp1.body.B, []byte(str2))\n\tassert.DeepEqual(t, resp1.BodyStream(), bytes.NewBufferString(str2))\n\tassert.DeepEqual(t, resp2.body.B, []byte(str1))\n\tassert.DeepEqual(t, resp2.BodyStream(), bytes.NewBufferString(str1))\n}\n\nfunc TestResponseAcquireResponse(t *testing.T) {\n\tt.Parallel()\n\tfor i := 0; i < 10; i++ {\n\t\tresp1 := AcquireResponse()\n\t\tassert.NotNil(t, resp1)\n\t\tassert.Nil(t, resp1.body)\n\t\tassert.Assert(t, resp1.BodyStream() == NoResponseBody)\n\t\tassert.Assert(t, resp1.IsBodyStream() == false)\n\n\t\tresp1.SetBody([]byte(\"test\"))\n\t\tresp1.SetStatusCode(consts.StatusOK)\n\t\tReleaseResponse(resp1)\n\t}\n}\n\ntype closeBuffer struct {\n\t*bytes.Buffer\n}\n\nfunc (b *closeBuffer) Close() error {\n\tb.Reset()\n\treturn nil\n}\n\nfunc TestSetBodyStreamNoReset(t *testing.T) {\n\tt.Parallel()\n\tresp := Response{}\n\tbsA := &closeBuffer{bytes.NewBufferString(\"A\")}\n\tbsB := &closeBuffer{bytes.NewBufferString(\"B\")}\n\tbsC := &closeBuffer{bytes.NewBufferString(\"C\")}\n\n\tresp.SetBodyStream(bsA, 1)\n\tresp.SetBodyStreamNoReset(bsB, 1)\n\t// resp.Body() has closed bsB\n\tassert.DeepEqual(t, string(resp.Body()), \"B\")\n\tassert.DeepEqual(t, bsA.String(), \"A\")\n\n\tresp.bodyStream = bsA\n\tresp.SetBodyStream(bsC, 1)\n\tassert.DeepEqual(t, bsA.String(), \"\")\n}\n\nfunc TestRespSafeCopy(t *testing.T) {\n\tresp := AcquireResponse()\n\tdefer ReleaseResponse(resp)\n\n\tresp.bodyRaw = make([]byte, 1)\n\tresps := make([]*Response, 10)\n\tfor i := 0; i < 10; i++ {\n\t\tresp.bodyRaw[0] = byte(i)\n\t\ttmpResq := AcquireResponse()\n\t\tresp.CopyTo(tmpResq)\n\t\tresps[i] = tmpResq\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\tassert.DeepEqual(t, []byte{byte(i)}, resps[i].Body())\n\t}\n}\n\nfunc TestResponse_HijackWriter(t *testing.T) {\n\tresp := AcquireResponse()\n\tdefer ReleaseResponse(resp)\n\n\tbuf := new(bytes.Buffer)\n\tisFinal := false\n\tresp.HijackWriter(&mock.ExtWriter{Buf: buf, IsFinal: &isFinal})\n\tresp.AppendBody([]byte(\"hello\"))\n\tassert.DeepEqual(t, 0, buf.Len())\n\tresp.GetHijackWriter().Flush()\n\tassert.DeepEqual(t, \"hello\", buf.String())\n\tresp.AppendBodyString(\", world\")\n\tassert.DeepEqual(t, \"hello\", buf.String())\n\tresp.GetHijackWriter().Flush()\n\tassert.DeepEqual(t, \"hello, world\", buf.String())\n\tresp.SetBody([]byte(\"hello, hertz\"))\n\tresp.GetHijackWriter().Flush()\n\tassert.DeepEqual(t, \"hello, hertz\", buf.String())\n\tassert.False(t, isFinal)\n\tresp.GetHijackWriter().Finalize()\n\tassert.True(t, isFinal)\n}\n\ntype HijackerFunc func() (network.Conn, error)\n\nfunc (h HijackerFunc) Read(_ []byte) (int, error)    { return 0, errors.New(\"not implemented\") }\nfunc (h HijackerFunc) Hijack() (network.Conn, error) { return h() }\n\nfunc TestResponse_Hijack(t *testing.T) {\n\tresp := AcquireResponse()\n\tdefer ReleaseResponse(resp)\n\n\t_, err := resp.Hijack()\n\tassert.NotNil(t, err)\n\n\tresp.SetBodyStream(HijackerFunc(func() (network.Conn, error) { return nil, nil }), -1)\n\t_, err = resp.Hijack()\n\tassert.Nil(t, err)\n}\n"
  },
  {
    "path": "pkg/protocol/server.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage protocol\n\nimport (\n\t\"context\"\n\n\t\"github.com/cloudwego/hertz/pkg/network\"\n)\n\ntype Server interface {\n\tServe(c context.Context, conn network.Conn) error\n}\n\ntype StreamServer interface {\n\tServe(c context.Context, conn network.StreamConn) error\n}\n"
  },
  {
    "path": "pkg/protocol/sse/event.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n)\n\nconst (\n\tfieldID = 1 << iota\n\tfieldType\n\tfieldData\n\tfieldRetry\n)\n\n// Event represents a Server-Sent Event (SSE).\ntype Event struct {\n\tID   string\n\tType string // aka `event` field, which means event type\n\tData []byte\n\n\t// hertz only supports reading and writing the field,\n\t// and will not take care of retry policy, please implement on your own.\n\tRetry time.Duration\n\n\tbitset uint8\n}\n\nvar poolEvent = sync.Pool{}\n\n// NewEvent creates a new event.\n//\n// Call `Release` when you're done with the event.\nfunc NewEvent() *Event {\n\tif v := poolEvent.Get(); v != nil {\n\t\tret := v.(*Event)\n\t\tret.Reset()\n\t\treturn ret\n\t}\n\treturn &Event{}\n}\n\n// Release releases the event back to the pool.\nfunc (e *Event) Release() {\n\tpoolEvent.Put(e)\n}\n\n// String returns a string representation of the event.\nfunc (e *Event) String() string {\n\treturn fmt.Sprintf(\"Event{ID:%q, Type:%q, Retry:%s, Data:%q}\", e.ID, e.Type, e.Retry, e.Data)\n}\n\n// Reset resets the event fields.\nfunc (e *Event) Reset() {\n\te.ID = \"\"\n\te.Type = \"\"\n\te.Retry = time.Duration(0)\n\te.Data = e.Data[:0]\n\te.bitset = 0\n}\n\n// Clone creates a copy of the event.\n//\n// When it's no longer needed, call `Release` to return it to the pool.\nfunc (e *Event) Clone() *Event {\n\tp := NewEvent()\n\tp.ID = e.ID\n\tp.Type = e.Type\n\tp.Retry = e.Retry\n\tp.Data = append(p.Data[:0], e.Data...)\n\tp.bitset = e.bitset\n\treturn p\n}\n\n// IsSetID returns true if the event ID is set.\n//\n// Please use SetID to set the event ID for differentiating notset or empty\nfunc (e *Event) IsSetID() bool {\n\treturn e.bitset&fieldID != 0 || len(e.ID) > 0\n}\n\n// IsSetType returns true if the event type is set.\n//\n// Please use SetEvent to set the event type for differentiating notset or empty\nfunc (e *Event) IsSetType() bool {\n\treturn e.bitset&fieldType != 0 || len(e.Type) > 0\n}\n\n// IsSetRetry returns true if the retry duration is set.\n//\n// Please use SetRetry to set the event retry duration for differentiating notset or empty\nfunc (e *Event) IsSetRetry() bool {\n\treturn e.bitset&fieldRetry != 0 || e.Retry != 0\n}\n\n// IsSetData returns true if the event data is set.\n//\n// Please use SetData to set or AppendData to append the event data for differentiating notset or empty\nfunc (e *Event) IsSetData() bool {\n\treturn e.bitset&fieldData != 0 || len(e.Data) > 0\n}\n\n// SetID sets the event ID.\nfunc (e *Event) SetID(id string) {\n\te.ID = id\n\te.bitset |= fieldID\n}\n\n// SetEvent sets the event type.\nfunc (e *Event) SetEvent(eventType string) {\n\te.Type = eventType\n\te.bitset |= fieldType\n}\n\n// SetData sets the event data.\nfunc (e *Event) SetData(data []byte) {\n\te.Data = append(e.Data[:0], data...)\n\te.bitset |= fieldData\n}\n\n// SetDataString sets the event data from a string.\nfunc (e *Event) SetDataString(data string) {\n\te.Data = append(e.Data[:0], data...)\n\te.bitset |= fieldData\n}\n\n// AppendData appends data to the event data.\nfunc (e *Event) AppendData(data []byte) {\n\te.Data = append(e.Data, data...)\n\te.bitset |= fieldData\n}\n\n// AppendDataString appends string data to the event data.\nfunc (e *Event) AppendDataString(data string) {\n\te.Data = append(e.Data, data...)\n\te.bitset |= fieldData\n}\n\n// SetRetry sets the retry duration.\nfunc (e *Event) SetRetry(retry time.Duration) {\n\te.Retry = retry\n\te.bitset |= fieldRetry\n}\n"
  },
  {
    "path": "pkg/protocol/sse/event_test.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestEvent_SetAndIsSet(t *testing.T) {\n\te := NewEvent()\n\tdefer e.Release()\n\n\t// Test initial state\n\tassert.Assert(t, !e.IsSetID())\n\tassert.Assert(t, !e.IsSetType())\n\tassert.Assert(t, !e.IsSetRetry())\n\tassert.Assert(t, !e.IsSetData())\n\n\t// Test SetID and IsSetID\n\te.SetID(\"test-id\")\n\tassert.Assert(t, e.IsSetID())\n\tassert.DeepEqual(t, \"test-id\", e.ID)\n\n\t// Test SetEvent and IsSetType\n\te.SetEvent(\"test-event\")\n\tassert.Assert(t, e.IsSetType())\n\tassert.DeepEqual(t, \"test-event\", e.Type)\n\n\t// Test SetRetry and IsSetRetry\n\tr := 3 * time.Second\n\te.SetRetry(r)\n\tassert.Assert(t, e.IsSetRetry())\n\tassert.DeepEqual(t, r, e.Retry)\n\n\t// Test SetData and IsSetData\n\td := []byte(\"test-data\")\n\te.SetData(d)\n\tassert.Assert(t, e.IsSetData())\n\tassert.DeepEqual(t, d, e.Data)\n\te.Reset()\n\tassert.Assert(t, e.IsSetData() == false)\n\te.SetDataString(string(d))\n\tassert.Assert(t, e.IsSetData())\n\tassert.DeepEqual(t, d, e.Data)\n}\n\nfunc TestEvent_AppendData(t *testing.T) {\n\te := NewEvent()\n\tdefer e.Release()\n\n\t// Test AppendData\n\te.AppendData([]byte(\"first\"))\n\tassert.Assert(t, e.IsSetData())\n\tassert.DeepEqual(t, []byte(\"first\"), e.Data)\n\n\t// Append more data\n\te.AppendDataString(\"second\")\n\tassert.DeepEqual(t, []byte(\"firstsecond\"), e.Data)\n}\n\nfunc TestEvent_Reset(t *testing.T) {\n\te := NewEvent()\n\tdefer e.Release()\n\n\t// Set all fields\n\te.SetID(\"test-id\")\n\te.SetEvent(\"test-event\")\n\te.SetRetry(3 * time.Second)\n\te.SetData([]byte(\"test-data\"))\n\n\t// Verify all fields are set\n\tassert.Assert(t, e.IsSetID())\n\tassert.Assert(t, e.IsSetType())\n\tassert.Assert(t, e.IsSetRetry())\n\tassert.Assert(t, e.IsSetData())\n\n\t// Reset and verify all fields are cleared\n\te.Reset()\n\tassert.Assert(t, !e.IsSetID())\n\tassert.Assert(t, !e.IsSetType())\n\tassert.Assert(t, !e.IsSetRetry())\n\tassert.Assert(t, !e.IsSetData())\n\tassert.DeepEqual(t, \"\", e.ID)\n\tassert.DeepEqual(t, \"\", e.Type)\n\tassert.DeepEqual(t, time.Duration(0), e.Retry)\n\tassert.DeepEqual(t, 0, len(e.Data))\n}\n\nfunc TestEvent_Clone(t *testing.T) {\n\te1 := NewEvent()\n\te1.SetID(\"test-id\")\n\te1.SetEvent(\"test-event\")\n\te1.SetRetry(3 * time.Second)\n\te1.SetData([]byte(\"test-data\"))\n\te2 := e1.Clone()\n\tassert.Assert(t, e2.IsSetID())\n\tassert.Assert(t, e2.IsSetType())\n\tassert.Assert(t, e2.IsSetRetry())\n\tassert.Assert(t, e2.IsSetData())\n\tassert.DeepEqual(t, \"test-id\", e2.ID)\n\tassert.DeepEqual(t, \"test-event\", e2.Type)\n\tassert.DeepEqual(t, 3*time.Second, e2.Retry)\n\tassert.DeepEqual(t, []byte(\"test-data\"), e2.Data)\n\te1.Release()\n\te2.Release()\n}\n\nfunc TestEvent_PoolAndRelease(t *testing.T) {\n\te1 := NewEvent()\n\te1.SetID(\"test-id\")\n\te1.Release()\n\n\t// Get another event from the pool, should be the same instance but reset\n\te2 := NewEvent()\n\tassert.Assert(t, !e2.IsSetID())\n\tassert.DeepEqual(t, \"\", e2.ID)\n\te2.Release()\n}\n"
  },
  {
    "path": "pkg/protocol/sse/example_test.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/app/client\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/route\"\n)\n\n// Example demonstrates a simple SSE server and client interaction.\nfunc Example() {\n\t// --- SSE Server ---\n\tln, _ := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tdefer ln.Close()\n\n\topt := config.NewOptions([]config.Option{})\n\topt.Listener = ln\n\tengine := route.NewEngine(opt)\n\tengine.GET(\"/\", func(ctx context.Context, c *app.RequestContext) {\n\t\tprintln(\"Server Got LastEventID\", GetLastEventID(&c.Request))\n\t\tw := NewWriter(c)\n\t\tfor i := 0; i < 5; i++ {\n\t\t\tw.WriteEvent(fmt.Sprintf(\"id-%d\", i), \"message\", []byte(\"hello\\n\\nworld\"))\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t}\n\t\t// [optional] it writes 0\\r\\n\\r\\n to indicate the end of chunked response\n\t\t// hertz will do it after handler returns\n\t\tw.Close()\n\t})\n\tgo engine.Run()\n\tdefer engine.Close()\n\ttime.Sleep(20 * time.Millisecond) // wait for server to start\n\topt.Addr = ln.Addr().String()\n\n\t// --- SSE Client ---\n\tc, _ := client.NewClient()\n\treq, resp := protocol.AcquireRequest(), protocol.AcquireResponse()\n\treq.SetRequestURI(\"http://\" + opt.Addr + \"/\")\n\treq.SetMethod(\"GET\")\n\treq.SetHeader(LastEventIDHeader, \"id-0\")\n\n\t// adds `text/event-stream` to http `Accept` header\n\t// may required for some Model Context Protocol(MCP) servers\n\tAddAcceptMIME(req)\n\n\tif err := c.Do(context.Background(), req, resp); err != nil {\n\t\tpanic(err)\n\t}\n\tr, err := NewReader(resp)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer r.Close()\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo func() {\n\t\ttime.Sleep(200 * time.Millisecond)\n\t\t// cancel can be used to force ForEach returns by closing the remote connection\n\t\t_ = cancel\n\t}()\n\terr = r.ForEach(ctx, func(e *Event) error {\n\t\tprintln(\"Event:\", e.String())\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tprintln(\"Client LastEventID\", r.LastEventID())\n\t// Output:\n\t//\n}\n"
  },
  {
    "path": "pkg/protocol/sse/reader.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\n// errNotSSEContentType is returned when the response's content type is not text/event-stream.\nvar errNotSSEContentType = errors.New(\"Content-Type returned by server is NOT text/event-stream\")\n\n// Reader represents a reader for Server-Sent Events (SSE).\n//\n// It is used to parse the response body and extract individual events.\ntype Reader struct {\n\tresp   *protocol.Response\n\tr      io.Reader\n\ts      *bufio.Scanner\n\tevents int32\n\n\tlastEventID string\n}\n\n// NewReader creates a new SSE reader from the given response.\n//\n// It returns an error if the response's content type is not text/event-stream.\nfunc NewReader(resp *protocol.Response) (*Reader, error) {\n\tif !bytes.HasPrefix(resp.Header.ContentType(), bytestr.MIMETextEventStream) {\n\t\treturn nil, errNotSSEContentType\n\t}\n\tr := &Reader{resp: resp}\n\tif resp.IsBodyStream() {\n\t\tr.r = resp.BodyStream()\n\t} else {\n\t\tr.r = bytes.NewReader(resp.Body())\n\t}\n\tr.s = bufio.NewScanner(r.r)\n\tr.s.Split(scanEOL)\n\treturn r, nil\n}\n\n// SetMaxBufferSize sets the maximum buffer size for the scanner.\n//\n// The scanner will allocate its own buffer as needed, up to max bytes.\n// The default max size without calling this method is bufio.MaxScanTokenSize (64KB).\n//\n// It panics if it is called after reading event has started.\nfunc (r *Reader) SetMaxBufferSize(max int) {\n\t// NOTE: Consider using bytebufferpool if GC becomes an issue.\n\t// Currently using nil to let scanner manage its own buffer internally.\n\tr.s.Buffer(nil, max)\n}\n\ntype forceCloseIf interface {\n\tForceClose() error // implemented by *clientRespStream\n}\n\n// ForEach iterates over all SSE events in the response body,\n// invoking the provided handler function for each event.\n//\n// The handler MUST NOT keep the Event reference after returning.\n// Use (*Event).Clone to create a copy if needed.\n//\n// Iteration stops when:\n//   - The handler returns an error\n//   - Reading fails (e.g., bufio.ErrTooLong for events exceeding buffer size)\n//   - Context is cancelled (if ctx.Done() != nil)\n//   - All events are processed (returns nil)\nfunc (r *Reader) ForEach(ctx context.Context, f func(e *Event) error) error {\n\tif ctx.Done() != nil {\n\t\tch := make(chan struct{})\n\t\tdefer close(ch)\n\t\tgo func() {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\t// force close the underlying connection to release resource\n\t\t\t\t// or r.Read may block until remote server ends\n\t\t\t\tif s, ok := r.r.(forceCloseIf); ok {\n\t\t\t\t\ts.ForceClose()\n\t\t\t\t}\n\t\t\tcase <-ch:\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\t}\n\te := NewEvent()\n\tdefer e.Release()\n\tfor {\n\t\tif err := ctx.Err(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := r.Read(e); err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif er := ctx.Err(); er != nil {\n\t\t\t\terr = er\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tif err := f(e); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n}\n\n// LastEventID returns the last event ID read by the reader.\nfunc (r *Reader) LastEventID() string {\n\treturn r.lastEventID\n}\n\nfunc (r *Reader) onEventRead(e *Event) {\n\tr.events++\n\tif e.IsSetID() {\n\t\tr.lastEventID = e.ID\n\t}\n}\n\n// Read reads a single SSE event from the response body.\n//\n// It populates the provided Event struct with the parsed data.\n// Returns nil on success, io.EOF when no more events, or an error\n// (e.g., bufio.ErrTooLong if an event line exceeds the buffer size).\n// Use SetMaxBufferSize to handle larger events.\nfunc (r *Reader) Read(e *Event) error {\n\te.Reset()\n\tfor i := 0; r.s.Scan(); i++ {\n\t\tline := r.s.Bytes()\n\n\t\t// Trim UTF8 BOM\n\t\tif i == 0 && r.events == 0 && bytes.HasPrefix(line, []byte{0xEF, 0xBB, 0xBF}) {\n\t\t\tline = line[3:]\n\t\t}\n\n\t\tif len(line) == 0 {\n\t\t\t// Empty line marks the end of an event\n\t\t\tif e.bitset != 0 {\n\t\t\t\tr.onEventRead(e)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tcontinue // Skip empty lines at the beginning\n\t\t}\n\n\t\tif line[0] == ':' {\n\t\t\t// Comment which starts with colon\n\t\t\tcontinue\n\t\t}\n\n\t\t// Parse field\n\t\tvar f, v []byte\n\t\ti := bytes.IndexByte(line, ':')\n\t\tif i < 0 {\n\t\t\t// No colon, the entire line is the field name with an empty value\n\t\t\tf = line\n\t\t} else {\n\t\t\tf = line[:i]\n\t\t\t// If the colon is followed by a space, remove it\n\t\t\tif i+1 < len(line) && line[i+1] == ' ' {\n\t\t\t\tv = line[i+2:]\n\t\t\t} else {\n\t\t\t\tv = line[i+1:]\n\t\t\t}\n\t\t}\n\n\t\t// Process the field\n\t\tswitch string(f) {\n\t\tcase \"event\":\n\t\t\te.SetEvent(sseEventType(v))\n\t\tcase \"data\":\n\t\t\tif len(e.Data) > 0 {\n\t\t\t\t// If we already have data, append a newline before the new data\n\t\t\t\te.Data = append(e.Data, '\\n')\n\t\t\t}\n\t\t\te.AppendData(v)\n\t\tcase \"id\":\n\t\t\tid := string(v)\n\t\t\t// Ignore if it contains Null\n\t\t\tif !strings.Contains(id, \"\\u0000\") {\n\t\t\t\te.SetID(id)\n\t\t\t}\n\t\tcase \"retry\":\n\t\t\tif retry, err := strconv.ParseInt(string(v), 10, 64); err == nil {\n\t\t\t\te.SetRetry(time.Duration(retry) * time.Millisecond)\n\t\t\t}\n\t\tdefault:\n\t\t\t// As per spec, ignore if it's not defined.\n\t\t}\n\t}\n\t// Check if scanner encountered an error\n\tif err := r.s.Err(); err != nil {\n\t\treturn err\n\t}\n\tif e.bitset == 0 {\n\t\treturn io.EOF\n\t}\n\tr.onEventRead(e)\n\treturn nil\n}\n\n// Close closes the underlying response body.\n//\n// NOTE:\n// * MUST NOT call Close() and Read() / ForEach() concurrently to avoid race issue.\nfunc (r *Reader) Close() error {\n\treturn r.resp.CloseBodyStream()\n}\n"
  },
  {
    "path": "pkg/protocol/sse/reader_test.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\ntype mockBodyStream struct {\n\treader io.Reader\n\tclosed bool\n}\n\nfunc (m *mockBodyStream) Read(p []byte) (n int, err error) {\n\treturn m.reader.Read(p)\n}\n\nfunc (m *mockBodyStream) Close() error {\n\tm.closed = true\n\treturn nil\n}\n\nfunc TestNewReader(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tcontentType []byte\n\t\tbody        []byte\n\t\twantErr     bool\n\t}{\n\t\t{\n\t\t\tname:        \"Valid content type\",\n\t\t\tcontentType: bytestr.MIMETextEventStream,\n\t\t\tbody:        []byte(\"event: message\\ndata: test\\n\\n\"),\n\t\t\twantErr:     false,\n\t\t},\n\t\t{\n\t\t\tname:        \"Invalid content type\",\n\t\t\tcontentType: []byte(\"text/plain\"),\n\t\t\tbody:        []byte(\"event: message\\ndata: test\\n\\n\"),\n\t\t\twantErr:     true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresp := &protocol.Response{}\n\t\t\tresp.Header.SetContentType(string(tt.contentType))\n\t\t\tresp.SetBody(tt.body)\n\n\t\t\tr, err := NewReader(resp)\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Assert(t, err != nil)\n\t\t\t\tassert.Assert(t, r == nil)\n\t\t\t} else {\n\t\t\t\tassert.Assert(t, err == nil)\n\t\t\t\tassert.Assert(t, r != nil)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestReader_ReadEvent(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected *Event\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:  \"Basic event\",\n\t\t\tinput: \"id: 123\\nevent: update\\ndata: test data\\n\\n\",\n\t\t\texpected: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetID(\"123\")\n\t\t\t\te.SetEvent(\"update\")\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"Event with retry\",\n\t\t\tinput: \"id: 123\\nevent: update\\nretry: 3000\\ndata: test data\\n\\n\",\n\t\t\texpected: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetID(\"123\")\n\t\t\t\te.SetEvent(\"update\")\n\t\t\t\te.SetRetry(3000 * time.Millisecond)\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"Event with multiline data\",\n\t\t\tinput: \"id: 123\\revent: update\\r\\ndata: line1\\rdata: line2\\r\\ndata: line3\\n\\n\",\n\t\t\texpected: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetID(\"123\")\n\t\t\t\te.SetEvent(\"update\")\n\t\t\t\te.SetData([]byte(\"line1\\nline2\\nline3\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"Event with BOM\",\n\t\t\tinput: \"\\xEF\\xBB\\xBFid: 123\\nevent: update\\ndata: test data\\n\\n\",\n\t\t\texpected: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetID(\"123\")\n\t\t\t\te.SetEvent(\"update\")\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"Event with comments\",\n\t\t\tinput: \": this is a comment\\nid: 123\\n: another comment\\nevent: update\\ndata: test data\\n\\n\",\n\t\t\texpected: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetID(\"123\")\n\t\t\t\te.SetEvent(\"update\")\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"Event with no colon in field\",\n\t\t\tinput: \"id\\nevent: update\\ndata: test data\\n\\n\",\n\t\t\texpected: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetID(\"\")\n\t\t\t\te.SetEvent(\"update\")\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"Event with no space after colon\",\n\t\t\tinput: \"id:123\\nevent:update\\ndata:test data\\n\\n\",\n\t\t\texpected: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetID(\"123\")\n\t\t\t\te.SetEvent(\"update\")\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"Event with ID containing null character (should be ignored)\",\n\t\t\tinput: \"id: test\\u0000id\\nevent: update\\ndata: test data\\n\\n\",\n\t\t\texpected: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetEvent(\"update\")\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"Event with invalid retry value\",\n\t\t\tinput: \"id: 123\\nevent: update\\nretry: invalid\\ndata: test data\\n\\n\",\n\t\t\texpected: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetID(\"123\")\n\t\t\t\te.SetEvent(\"update\")\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"Empty event\",\n\t\t\tinput: \"\\n\\n\",\n\t\t\texpected: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\t// Empty event doesn't set any fields, so bitset remains 0\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresp := &protocol.Response{}\n\t\t\tresp.Header.SetContentType(string(bytestr.MIMETextEventStream))\n\t\t\tresp.SetBody([]byte(tt.input))\n\n\t\t\tr, err := NewReader(resp)\n\t\t\tassert.Assert(t, err == nil)\n\n\t\t\te := NewEvent()\n\t\t\terr = r.Read(e)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Assert(t, err != nil)\n\t\t\t} else {\n\t\t\t\tassert.Assert(t, err == nil)\n\t\t\t\tassert.DeepEqual(t, tt.expected.ID, e.ID)\n\t\t\t\tassert.DeepEqual(t, tt.expected.Type, e.Type)\n\t\t\t\tassert.DeepEqual(t, tt.expected.Retry, e.Retry)\n\t\t\t\tassert.DeepEqual(t, tt.expected.Data, e.Data)\n\n\t\t\t\t// LastEventID check\n\t\t\t\tif e.ID != \"\" {\n\t\t\t\t\tassert.DeepEqual(t, r.LastEventID(), e.ID)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\te.Release()\n\t\t})\n\t}\n}\n\nfunc TestReader_ReadEvent_WithBodyStream(t *testing.T) {\n\tinput := \"id: 123\\nevent: update\\ndata: test data\\n\\n\"\n\n\tresp := &protocol.Response{}\n\tresp.Header.SetContentType(string(bytestr.MIMETextEventStream))\n\n\t// Create a mock body stream\n\tms := &mockBodyStream{\n\t\treader: strings.NewReader(input),\n\t}\n\tresp.SetBodyStream(ms, -1)\n\n\tr, err := NewReader(resp)\n\tassert.Assert(t, err == nil)\n\n\te := NewEvent()\n\terr = r.Read(e)\n\tassert.Assert(t, err == nil)\n\n\t// Verify event data\n\tassert.DeepEqual(t, \"123\", e.ID)\n\tassert.DeepEqual(t, \"update\", e.Type)\n\tassert.DeepEqual(t, []byte(\"test data\"), e.Data)\n\n\t// LastEventID check\n\tif e.ID != \"\" {\n\t\tassert.DeepEqual(t, r.LastEventID(), e.ID)\n\t}\n\n\t// Test Close\n\terr = r.Close()\n\tassert.Assert(t, err == nil)\n\tassert.Assert(t, ms.closed)\n\n\te.Release()\n}\n\ntype mockReadForceClose struct {\n\treadFunc  func(b []byte) (int, error)\n\tcloseFunc func() error\n}\n\nfunc (m *mockReadForceClose) Read(b []byte) (int, error) {\n\treturn m.readFunc(b)\n}\n\nfunc (m *mockReadForceClose) ForceClose() error {\n\treturn m.closeFunc()\n}\n\nfunc TestReader_ReadEvent_Error(t *testing.T) {\n\t// Create a reader that will return an error\n\terrReader := &bytes.Reader{}\n\n\tresp := &protocol.Response{}\n\tresp.Header.SetContentType(string(bytestr.MIMETextEventStream))\n\tresp.SetBodyStream(errReader, -1)\n\n\tr, err := NewReader(resp)\n\tassert.Assert(t, err == nil)\n\n\te := NewEvent()\n\terr = r.Read(e)\n\n\t// The error from bytes.Reader will be io.EOF\n\tassert.Assert(t, err == io.EOF)\n\n\te.Release()\n}\n\nfunc TestReader_ForEach(t *testing.T) {\n\t// mock Read & ForceClose\n\tmr := &mockReadForceClose{}\n\tch := make(chan error, 1)\n\tdefer close(ch)\n\tmr.readFunc = func(b []byte) (int, error) {\n\t\treturn 0, <-ch\n\t}\n\tmr.closeFunc = func() error {\n\t\tch <- errors.New(\"closed\")\n\t\treturn nil\n\t}\n\n\t// create protocol.Response\n\tresp := &protocol.Response{}\n\tresp.Header.SetContentType(string(bytestr.MIMETextEventStream))\n\tresp.SetBodyStream(mr, -1)\n\tr, err := NewReader(resp)\n\tassert.Assert(t, err == nil)\n\n\t// test ForEach with context\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo func() { // cancel after 50ms\n\t\ttime.Sleep(50 * time.Millisecond)\n\t\tcancel()\n\t}()\n\terr = r.ForEach(ctx, func(e *Event) error {\n\t\tpanic(\"must not called\")\n\t})\n\tassert.Assert(t, err == ctx.Err())\n}\n\nfunc TestReader_SetMaxBufferSize(t *testing.T) {\n\t// Test that default buffer size fails for events > 64KB\n\tt.Run(\"default buffer size fails for large events\", func(t *testing.T) {\n\t\t// Create a response with a large event (65KB) - just over default 64KB\n\t\tlargeData := strings.Repeat(\"x\", 65*1024)\n\t\tinput := \"event: large\\ndata: \" + largeData + \"\\n\\n\"\n\n\t\tresp := &protocol.Response{}\n\t\tresp.Header.SetContentType(string(bytestr.MIMETextEventStream))\n\t\tresp.SetBody([]byte(input))\n\n\t\tr, err := NewReader(resp)\n\t\tassert.Assert(t, err == nil)\n\n\t\t// Don't call SetMaxBufferSize, use default (64KB)\n\t\t// Reading should fail because the line is too long\n\t\te := NewEvent()\n\t\terr = r.Read(e)\n\t\tassert.Assert(t, errors.Is(err, bufio.ErrTooLong))\n\t\te.Release()\n\t})\n\n\t// Test with custom buffer size for large events\n\tt.Run(\"custom buffer size\", func(t *testing.T) {\n\t\t// Create a response with a large event (65KB) - just over default 64KB\n\t\tlargeData := strings.Repeat(\"x\", 65*1024)\n\t\tinput := \"event: large\\ndata: \" + largeData + \"\\n\\n\"\n\n\t\tresp := &protocol.Response{}\n\t\tresp.Header.SetContentType(string(bytestr.MIMETextEventStream))\n\t\tresp.SetBody([]byte(input))\n\n\t\tr, err := NewReader(resp)\n\t\tassert.Assert(t, err == nil)\n\n\t\t// Set max buffer size to 70KB to handle the large event\n\t\tr.SetMaxBufferSize(70 * 1024)\n\n\t\t// Should be able to read the large event\n\t\te := NewEvent()\n\t\terr = r.Read(e)\n\t\tassert.Assert(t, err == nil)\n\t\tassert.DeepEqual(t, \"large\", e.Type)\n\t\tassert.DeepEqual(t, largeData, string(e.Data))\n\t\te.Release()\n\n\t\t// Test panic when SetMaxBufferSize is called after reading\n\t\tdefer func() {\n\t\t\tif r := recover(); r == nil {\n\t\t\t\tt.Error(\"SetMaxBufferSize should panic after reading has started\")\n\t\t\t}\n\t\t}()\n\t\tr.SetMaxBufferSize(80 * 1024)\n\t})\n}\n"
  },
  {
    "path": "pkg/protocol/sse/request.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n"
  },
  {
    "path": "pkg/protocol/sse/request_test.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n"
  },
  {
    "path": "pkg/protocol/sse/utils.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\nconst LastEventIDHeader = \"Last-Event-ID\"\n\n// GetLastEventID returns the value of the Last-Event-ID header.\nfunc GetLastEventID(req *protocol.Request) string {\n\treturn string(req.Header.Peek(LastEventIDHeader))\n}\n\n// SetLastEventID sets the Last-Event-ID header.\nfunc SetLastEventID(req *protocol.Request, id string) {\n\treq.Header.Set(LastEventIDHeader, id)\n}\n\n// AddAcceptMIME adds `text/event-stream` to http `Accept` header.\n//\n// This is NOT required as per spec:\n// * User agents MAY set (`Accept`, `text/event-stream`) in request's header list.\nfunc AddAcceptMIME(req *protocol.Request) {\n\tv := req.Header.Peek(\"Accept\")\n\tif len(v) > 0 {\n\t\tif bytes.Contains(v, bytestr.MIMETextEventStream) {\n\t\t\treturn\n\t\t}\n\t\t// for better compatibility, only use one Accept header value\n\t\t// append `text/event-stream` to the end of the value\n\t\treq.Header.Set(\"Accept\", string(v)+\", \"+string(bytestr.MIMETextEventStream))\n\t} else {\n\t\treq.Header.Set(\"Accept\", string(bytestr.MIMETextEventStream))\n\t}\n}\n\nfunc sseEventType(v []byte) string {\n\tswitch string(v) {\n\tcase \"message\":\n\t\treturn \"message\"\n\t}\n\treturn string(v)\n}\n\nfunc hasCRLF(s string) bool {\n\tfor i := len(s) - 1; i >= 0; i-- {\n\t\tswitch s[i] {\n\t\tcase '\\r', '\\n':\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream\n// end-of-line   = ( cr lf / cr / lf )\nfunc scanEOL(data []byte, atEOF bool) (advance int, token []byte, err error) {\n\tif atEOF && len(data) == 0 {\n\t\treturn 0, nil, nil\n\t}\n\ti := bytes.IndexByte(data, '\\r')\n\tj := bytes.IndexByte(data, '\\n')\n\tif i >= 0 {\n\t\tif i+1 == j { // \\r\\n\n\t\t\treturn i + 2, data[0:i], nil\n\t\t}\n\t\tif j >= 0 { // choose the nearer \\r or \\n as EOL\n\t\t\tif i < j {\n\t\t\t\treturn i + 1, data[0:i], nil // \\r\n\t\t\t}\n\t\t\treturn j + 1, data[0:j], nil // \\n\n\t\t}\n\t\t// if ends with '\\r', we need to check the next char is NOT '\\n' as per spec\n\t\t// this may cause unexpected blocks on reading more data.\n\t\tif i < len(data)-1 || atEOF {\n\t\t\treturn i + 1, data[0:i], nil\n\t\t}\n\t} else if j >= 0 {\n\t\treturn j + 1, data[0:j], nil\n\t}\n\tif atEOF {\n\t\treturn len(data), data, nil\n\t}\n\treturn 0, nil, nil // more data\n}\n"
  },
  {
    "path": "pkg/protocol/sse/utils_test.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\nfunc TestSetGetLastEventID(t *testing.T) {\n\treq := protocol.AcquireRequest()\n\tdefer protocol.ReleaseRequest(req)\n\n\tSetLastEventID(req, \"123\")\n\tassert.DeepEqual(t, \"123\", GetLastEventID(req))\n}\n\nfunc TestAddAcceptMIME(t *testing.T) {\n\t// Test case 1: Empty Accept header\n\treq := protocol.AcquireRequest()\n\tdefer protocol.ReleaseRequest(req)\n\n\tAddAcceptMIME(req)\n\n\tacceptHeader := req.Header.Peek(\"Accept\")\n\tassert.DeepEqual(t, string(bytestr.MIMETextEventStream), string(acceptHeader))\n\n\t// Test case 2: Existing Accept header without text/event-stream\n\treq.Reset()\n\treq.Header.Set(\"Accept\", \"text/html, application/json\")\n\n\tAddAcceptMIME(req)\n\n\tacceptHeader = req.Header.Peek(\"Accept\")\n\tassert.DeepEqual(t, \"text/html, application/json, text/event-stream\", string(acceptHeader))\n\n\t// Test case 3: Existing Accept header already containing text/event-stream\n\treq.Reset()\n\treq.Header.Set(\"Accept\", \"text/html, text/event-stream, application/json\")\n\n\tAddAcceptMIME(req)\n\n\tacceptHeader = req.Header.Peek(\"Accept\")\n\tassert.DeepEqual(t, \"text/html, text/event-stream, application/json\", string(acceptHeader))\n}\n\nfunc TestHasCRLF(t *testing.T) {\n\tassert.Assert(t, hasCRLF(\"\\nThis is a test string\"))\n\tassert.Assert(t, hasCRLF(\"This is \\na test string\"))\n\tassert.Assert(t, hasCRLF(\"This is a test string\\n\"))\n\tassert.Assert(t, hasCRLF(\"\\rThis is a test string\"))\n\tassert.Assert(t, hasCRLF(\"This is \\rna test string\"))\n\tassert.Assert(t, hasCRLF(\"This is a test string\\r\"))\n\tassert.Assert(t, hasCRLF(\"This is a test string\") == false)\n}\n\nfunc TestSSEEventType(t *testing.T) {\n\tassert.DeepEqual(t, \"message\", sseEventType([]byte(\"message\")))\n\tassert.DeepEqual(t, \"custom\", sseEventType([]byte(\"custom\")))\n}\n\nfunc TestScanEOL(t *testing.T) {\n\ttests := []struct {\n\t\tdata    string\n\t\tatEOF   bool\n\t\tadvance int\n\t\ttoken   string\n\t}{\n\t\t{\"\", true, 0, \"\"},\n\t\t{\"\", false, 0, \"\"},\n\t\t{\"hello\\r\\nworld\", false, 7, \"hello\"},\n\t\t{\"hello\\rworld\", false, 6, \"hello\"},\n\t\t{\"hello\\nworld\", false, 6, \"hello\"},\n\t\t{\"hello world\", false, 0, \"\"},\n\t\t{\"hello world\", true, 11, \"hello world\"},\n\t\t{\"\\r\", false, 0, \"\"},\n\t\t{\"hello\\r\", false, 0, \"\"},\n\t\t{\"hello\\r\", true, 6, \"hello\"},\n\t\t{\"\\n\", false, 1, \"\"},\n\t\t{\"\\r\\nhello\", false, 2, \"\"},\n\t\t{\"\\r\\n\", false, 2, \"\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tadvance, token, _ := scanEOL([]byte(tc.data), tc.atEOF)\n\t\tif advance != tc.advance || string(token) != tc.token {\n\t\t\tt.Fatalf(\"scanLines(data=%q, atEOF=%v) returns (%d, %q) expect (%d, %q)\",\n\t\t\t\ttc.data, tc.atEOF, advance, string(token), tc.advance, tc.token)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/sse/writer.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/bytebufferpool\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/resp\"\n)\n\n// Writer represents a writer for Server-Sent Events (SSE).\n//\n// It is used to write individual events to the response body.\ntype Writer struct {\n\tw network.ExtWriter\n\n\tmu sync.Mutex\n}\n\n// NewWriter creates a new SSE writer.\nfunc NewWriter(c *app.RequestContext) *Writer {\n\t// make sure proxies won't cache the data\n\tc.Response.Header.Set(\"Cache-Control\", \"no-cache\")\n\t// browsers may need charset=utf-8 for logging responses\n\t// even though it's unnecessary as per spec, coz chunks must be in utf8.\n\tc.Response.Header.SetContentType(\"text/event-stream; charset=utf-8\")\n\tw := c.Response.GetHijackWriter()\n\tif w == nil {\n\t\tw = resp.NewChunkedBodyWriter(&c.Response, c.GetWriter())\n\t\tc.Response.HijackWriter(w)\n\t}\n\treturn &Writer{w: w}\n}\n\nvar (\n\terrIDContainsCRLR   = errors.New(`id field contains '\\r' or '\\n'`)\n\terrTypeContainsCRLR = errors.New(`event field contains '\\r' or '\\n'`)\n)\n\n// WriteEvent writes a single SSE event to the response body.\n//\n// If id, eventType, or data are zero-length, they will be ignored.\n// It returns an error if the event contains invalid characters or if the underlying writer fails.\nfunc (w *Writer) WriteEvent(id, eventType string, data []byte) error {\n\treturn w.Write(&Event{\n\t\tID:   id,\n\t\tType: eventType,\n\t\tData: data,\n\t})\n}\n\n// WriteKeepAlive writes a comment line with \"keep-alive\" to the response body.\n//\n// It keeps the underlying connection alive, which is useful when using proxy servers.\nfunc (w *Writer) WriteKeepAlive() error {\n\treturn w.WriteComment(\"keep-alive\")\n}\n\n// WriteComment writes comment lines to the response body.\n//\n// Client-side will ignore lines starting with a U+003A COLON character (:)\n// see: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation\nfunc (w *Writer) WriteComment(s string) error {\n\tp := bytebufferpool.Get()\n\tdefer bytebufferpool.Put(p)\n\n\tbuf := p.B[:0]\n\tfor data := bytesconv.S2b(s); len(data) > 0; {\n\t\ti, b, _ := scanEOL(data, true)\n\t\tbuf = append(buf, ':')\n\t\tbuf = append(buf, b...)\n\t\tbuf = append(buf, '\\n')\n\t\tdata = data[i:]\n\t}\n\tif len(buf) == 0 {\n\t\tbuf = append(buf, ':')\n\t}\n\tp.B = buf\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\tif _, err := w.w.Write(p.B); err != nil {\n\t\treturn err\n\t}\n\treturn w.w.Flush()\n}\n\n// Write writes a single SSE event to the response body.\n//\n// It returns an error if the event contains invalid characters or underlying writer fails.\nfunc (w *Writer) Write(e *Event) error {\n\tp := bytebufferpool.Get()\n\tdefer bytebufferpool.Put(p)\n\n\tbuf := p.B[:0]\n\n\tif e.IsSetID() {\n\t\tif hasCRLF(e.ID) {\n\t\t\treturn errIDContainsCRLR\n\t\t}\n\t\tbuf = append(append(append(buf, \"id: \"...), e.ID...), '\\n')\n\t}\n\n\tif e.IsSetType() {\n\t\tif e.Type == \"message\" {\n\t\t\tbuf = append(buf, \"event: message\\n\"...) // fast path for message\n\t\t} else {\n\t\t\tif hasCRLF(e.Type) {\n\t\t\t\treturn errTypeContainsCRLR\n\t\t\t}\n\t\t\tbuf = append(append(append(buf, \"event: \"...), e.Type...), '\\n')\n\t\t}\n\t}\n\n\tif e.IsSetRetry() {\n\t\tbuf = append(buf, \"retry: \"...)\n\t\tbuf = strconv.AppendInt(buf, e.Retry.Milliseconds(), 10)\n\t\tbuf = append(buf, '\\n')\n\t}\n\n\tif e.IsSetData() {\n\t\tdata := e.Data\n\t\t// replace EOLs with multiple \"data: \" lines\n\t\tfor len(data) > 0 {\n\t\t\ti, b, _ := scanEOL(data, true)\n\t\t\tbuf = append(buf, \"data: \"...)\n\t\t\tbuf = append(buf, b...)\n\t\t\tbuf = append(buf, '\\n')\n\t\t\tdata = data[i:]\n\t\t}\n\t}\n\tp.B = append(buf, '\\n') // end of event\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\tif _, err := w.w.Write(p.B); err != nil {\n\t\treturn err\n\t}\n\treturn w.w.Flush()\n}\n\nfunc (w *Writer) Close() error {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\treturn w.w.Finalize()\n}\n"
  },
  {
    "path": "pkg/protocol/sse/writer_test.go",
    "content": "/*\n * Copyright 2025 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage sse\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\n// mockWriteFlusher implements the writeFlusher interface for testing\ntype mw struct {\n\tbuf         bytes.Buffer\n\tflushCalled bool\n\twriteErr    error\n\tflushErr    error\n\tfinalizeErr error\n}\n\nfunc (m *mw) Write(p []byte) (n int, err error) {\n\tif m.writeErr != nil {\n\t\treturn 0, m.writeErr\n\t}\n\treturn m.buf.Write(p)\n}\n\nfunc (m *mw) Flush() error {\n\tm.flushCalled = true\n\treturn m.flushErr\n}\n\nfunc (m *mw) String() string {\n\treturn m.buf.String()\n}\n\nfunc (m *mw) Finalize() error {\n\treturn m.finalizeErr\n}\n\nfunc TestWriter_WriteEvent(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tid        string\n\t\teventType string\n\t\tdata      []byte\n\t\twantErr   bool\n\t\texpected  string\n\t}{\n\t\t{\n\t\t\tname:      \"Basic event\",\n\t\t\tid:        \"123\",\n\t\t\teventType: \"message\",\n\t\t\tdata:      []byte(\"test data\"),\n\t\t\twantErr:   false,\n\t\t\texpected:  \"id: 123\\nevent: message\\ndata: test data\\n\\n\",\n\t\t},\n\t\t{\n\t\t\tname:      \"Empty fields\",\n\t\t\tid:        \"\",\n\t\t\teventType: \"\",\n\t\t\tdata:      nil,\n\t\t\twantErr:   false,\n\t\t\texpected:  \"\\n\",\n\t\t},\n\t\t{\n\t\t\tname:      \"ID with CRLF\",\n\t\t\tid:        \"test\\nid\",\n\t\t\teventType: \"update\",\n\t\t\tdata:      []byte(\"test data\"),\n\t\t\twantErr:   true,\n\t\t\texpected:  \"\",\n\t\t},\n\t\t{\n\t\t\tname:      \"Event type with CRLF\",\n\t\t\tid:        \"123\",\n\t\t\teventType: \"up\\ndate\",\n\t\t\tdata:      []byte(\"test data\"),\n\t\t\twantErr:   true,\n\t\t\texpected:  \"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tm := &mw{}\n\t\t\tw := &Writer{w: m}\n\n\t\t\terr := w.WriteEvent(tt.id, tt.eventType, tt.data)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Assert(t, err != nil)\n\t\t\t} else {\n\t\t\t\tassert.Assert(t, err == nil)\n\t\t\t\tassert.DeepEqual(t, tt.expected, m.String())\n\t\t\t\tassert.Assert(t, m.flushCalled)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestWriter_Write(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tevent    *Event\n\t\twriteErr error\n\t\tflushErr error\n\t\twantErr  bool\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"Complete event\",\n\t\t\tevent: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetID(\"123\")\n\t\t\t\te.SetEvent(\"update\")\n\t\t\t\te.SetRetry(3 * time.Second)\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr:  false,\n\t\t\texpected: \"id: 123\\nevent: update\\nretry: 3000\\ndata: test data\\n\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"Multiline data\",\n\t\t\tevent: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetData([]byte(\"line1\\rline2\\nline3\\r\\nline4\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twantErr:  false,\n\t\t\texpected: \"data: line1\\ndata: line2\\ndata: line3\\ndata: line4\\n\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"Write error\",\n\t\t\tevent: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\twriteErr: errors.New(\"write error\"),\n\t\t\twantErr:  true,\n\t\t\texpected: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"Flush error\",\n\t\t\tevent: func() *Event {\n\t\t\t\te := NewEvent()\n\t\t\t\te.SetData([]byte(\"test data\"))\n\t\t\t\treturn e\n\t\t\t}(),\n\t\t\tflushErr: errors.New(\"flush error\"),\n\t\t\twantErr:  true,\n\t\t\texpected: \"data: test data\\n\\n\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tm := &mw{\n\t\t\t\twriteErr: tt.writeErr,\n\t\t\t\tflushErr: tt.flushErr,\n\t\t\t}\n\t\t\tw := &Writer{w: m}\n\n\t\t\terr := w.Write(tt.event)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Assert(t, err != nil)\n\t\t\t} else {\n\t\t\t\tassert.Assert(t, err == nil)\n\t\t\t\tassert.DeepEqual(t, tt.expected, m.String())\n\t\t\t\tassert.Assert(t, m.flushCalled)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewWriter(t *testing.T) {\n\tc := app.NewContext(0)\n\tw := NewWriter(c)\n\tassert.Assert(t, w != nil)\n\tassert.DeepEqual(t, \"no-cache\", string(c.Response.Header.Peek(\"Cache-Control\")))\n\tassert.DeepEqual(t, \"text/event-stream; charset=utf-8\", string(c.Response.Header.Peek(\"Content-Type\")))\n}\n\nfunc TestWriter_WriteComment(t *testing.T) {\n\tm := &mw{}\n\tw := &Writer{w: m}\n\n\terr := w.WriteComment(\"test\\ncomment\")\n\n\tassert.Assert(t, err == nil)\n\tassert.DeepEqual(t, \":test\\n:comment\\n\", m.String())\n\tassert.Assert(t, m.flushCalled)\n\n\t// empty string\n\tm = &mw{}\n\tw = &Writer{w: m}\n\terr = w.WriteComment(\"\")\n\tassert.Assert(t, err == nil)\n\tassert.DeepEqual(t, \":\", m.String())\n\n\t// keep-alive\n\tm = &mw{}\n\tw = &Writer{w: m}\n\terr = w.WriteKeepAlive()\n\tassert.Assert(t, err == nil)\n\tassert.DeepEqual(t, \":keep-alive\\n\", m.String())\n}\n\nfunc TestWriter_Close(t *testing.T) {\n\t// Create a mock writeFlusher\n\tm := &mw{}\n\n\t// Create a Writer with the mock\n\tw := &Writer{w: m}\n\n\t// Test Close method\n\terr := w.Close()\n\n\t// Verify no error occurred\n\tassert.Nil(t, err)\n\n\t// Set an error to be returned by Finalize\n\texpectedErr := errors.New(\"finalize error\")\n\tm.finalizeErr = expectedErr\n\n\t// Test Close method with error\n\terr = w.Close()\n\n\t// Verify the error is propagated\n\tassert.DeepEqual(t, expectedErr, err)\n}\n"
  },
  {
    "path": "pkg/protocol/suite/client.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage suite\n\nimport \"github.com/cloudwego/hertz/pkg/protocol/client\"\n\ntype ClientFactory interface {\n\tNewHostClient() (hc client.HostClient, err error)\n}\n"
  },
  {
    "path": "pkg/protocol/suite/server.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage suite\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nconst (\n\t// must be the same with the ALPN nextProto values\n\tHTTP1 = \"http/1.1\"\n\tHTTP2 = \"h2\"\n\t// HTTP3Draft29 is the ALPN protocol negotiated during the TLS handshake, for QUIC draft 29.\n\tHTTP3Draft29 = \"h3-29\"\n\t// HTTP3 is the ALPN protocol negotiated during the TLS handshake, for QUIC v1 and v2.\n\tHTTP3 = \"h3\"\n)\n\n// Core is the core interface that promises to be provided for the protocol layer extensions\ntype Core interface {\n\t// IsRunning Check whether engine is running or not\n\tIsRunning() bool\n\t// A RequestContext pool ready for protocol server impl\n\tGetCtxPool() *sync.Pool\n\t// Business logic entrance\n\t// After pre-read works, protocol server may call this method\n\t// to introduce the middlewares and handlers\n\tServeHTTP(c context.Context, ctx *app.RequestContext)\n\t// GetTracer for tracing requirement\n\tGetTracer() tracer.Controller\n}\n\ntype ServerFactory interface {\n\tNew(core Core) (server protocol.Server, err error)\n}\n\ntype StreamServerFactory interface {\n\tNew(core Core) (server protocol.StreamServer, err error)\n}\n\ntype Config struct {\n\taltServerConfig *altServerConfig\n\tconfigMap       map[string]ServerFactory\n\tstreamConfigMap map[string]StreamServerFactory\n}\n\ntype ServerMap map[string]protocol.Server\n\ntype StreamServerMap map[string]protocol.StreamServer\n\ntype altServerConfig struct {\n\ttargetProtocol   string\n\tsetAltHeaderFunc func(ctx context.Context, reqCtx *app.RequestContext)\n}\n\ntype coreWrapper struct {\n\tCore\n\tbeforeHandler func(c context.Context, ctx *app.RequestContext)\n}\n\nfunc (c *coreWrapper) ServeHTTP(ctx context.Context, reqCtx *app.RequestContext) {\n\tc.beforeHandler(ctx, reqCtx)\n\tc.Core.ServeHTTP(ctx, reqCtx)\n}\n\n// SetAltHeader will set response header \"Alt-Svc\" for the target protocol, altHeader will be the value of the header.\n// Protocols other than the target protocol will carry the altHeader in the request header.\nfunc (c *Config) SetAltHeader(target, altHeader string) {\n\tc.altServerConfig = &altServerConfig{\n\t\ttargetProtocol: target,\n\t\tsetAltHeaderFunc: func(ctx context.Context, reqCtx *app.RequestContext) {\n\t\t\treqCtx.Response.Header.Add(consts.HeaderAltSvc, altHeader)\n\t\t},\n\t}\n}\n\nfunc (c *Config) Add(protocol string, factory interface{}) {\n\tswitch factory := factory.(type) {\n\tcase ServerFactory:\n\t\tif fac := c.configMap[protocol]; fac != nil {\n\t\t\thlog.SystemLogger().Warnf(\"ServerFactory of protocol: %s will be overridden by customized function\", protocol)\n\t\t}\n\t\tc.configMap[protocol] = factory\n\tcase StreamServerFactory:\n\t\tif fac := c.streamConfigMap[protocol]; fac != nil {\n\t\t\thlog.SystemLogger().Warnf(\"StreamServerFactory of protocol: %s will be overridden by customized function\", protocol)\n\t\t}\n\t\tc.streamConfigMap[protocol] = factory\n\tdefault:\n\t\thlog.SystemLogger().Fatalf(\"Unsupported factory type: %T\", factory)\n\t}\n}\n\nfunc (c *Config) Get(name string) ServerFactory {\n\treturn c.configMap[name]\n}\n\nfunc (c *Config) Delete(protocol string) {\n\tdelete(c.configMap, protocol)\n}\n\nfunc (c *Config) Load(core Core, protocol string) (server protocol.Server, err error) {\n\tif c.configMap[protocol] == nil {\n\t\treturn nil, errors.NewPrivate(\"HERTZ: Load server error, not support protocol: \" + protocol)\n\t}\n\tif c.altServerConfig == nil || c.altServerConfig.targetProtocol == protocol {\n\t\treturn c.configMap[protocol].New(core)\n\t}\n\treturn c.configMap[protocol].New(&coreWrapper{Core: core, beforeHandler: c.altServerConfig.setAltHeaderFunc})\n}\n\nfunc (c *Config) LoadAll(core Core) (serverMap ServerMap, streamServerMap StreamServerMap, err error) {\n\tserverMap = make(ServerMap)\n\tvar wrappedCore *coreWrapper\n\tif c.altServerConfig != nil {\n\t\twrappedCore = &coreWrapper{Core: core, beforeHandler: c.altServerConfig.setAltHeaderFunc}\n\t}\n\tvar server protocol.Server\n\tfor proto := range c.configMap {\n\t\tif c.altServerConfig != nil && c.altServerConfig.targetProtocol != proto {\n\t\t\tcore = wrappedCore\n\t\t}\n\t\tif server, err = c.configMap[proto].New(core); err != nil {\n\t\t\treturn nil, nil, err\n\t\t} else {\n\t\t\tserverMap[proto] = server\n\t\t}\n\t}\n\tstreamServerMap = make(StreamServerMap)\n\tvar streamServer protocol.StreamServer\n\tfor proto := range c.streamConfigMap {\n\t\tif c.altServerConfig != nil && c.altServerConfig.targetProtocol != proto {\n\t\t\tcore = wrappedCore\n\t\t}\n\t\tif streamServer, err = c.streamConfigMap[proto].New(core); err != nil {\n\t\t\treturn nil, nil, err\n\t\t} else {\n\t\t\tstreamServerMap[proto] = streamServer\n\t\t}\n\t}\n\treturn serverMap, streamServerMap, nil\n}\n\n// New return an empty Config suite, use .Add() to add protocol impl\nfunc New() *Config {\n\tc := &Config{\n\t\tconfigMap:       make(map[string]ServerFactory),\n\t\tstreamConfigMap: make(map[string]StreamServerFactory),\n\t}\n\n\treturn c\n}\n"
  },
  {
    "path": "pkg/protocol/trailer.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\ntype Trailer struct {\n\th                  []argsKV\n\tbufKV              argsKV\n\tdisableNormalizing bool\n}\n\n// Get returns trailer value for the given key.\nfunc (t *Trailer) Get(key string) string {\n\treturn string(t.Peek(key))\n}\n\n// Peek returns trailer value for the given key.\n//\n// Returned value is valid until the next call to Trailer.\n// Do not store references to returned value. Make copies instead.\nfunc (t *Trailer) Peek(key string) []byte {\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, t.disableNormalizing)\n\treturn peekArgBytes(t.h, k)\n}\n\n// Del deletes trailer with the given key.\nfunc (t *Trailer) Del(key string) {\n\tk := []byte(key)\n\tutils.NormalizeHeaderKey(k, t.disableNormalizing)\n\tt.h = delAllArgsBytes(t.h, k)\n}\n\n// VisitAll calls f for each header.\nfunc (t *Trailer) VisitAll(f func(key, value []byte)) {\n\tvisitArgs(t.h, f)\n}\n\n// Set sets the given 'key: value' trailer.\n//\n// If the key is forbidden by RFC 7230, section 4.1.2, Set will return error\nfunc (t *Trailer) Set(key, value string) error {\n\tinitHeaderKV(&t.bufKV, key, value, t.disableNormalizing)\n\treturn t.setArgBytes(t.bufKV.key, t.bufKV.value, ArgsHasValue)\n}\n\n// Add adds the given 'key: value' trailer.\n//\n// Multiple headers with the same key may be added with this function.\n// Use Set for setting a single header for the given key.\n//\n// If the key is forbidden by RFC 7230, section 4.1.2, Add will return error\nfunc (t *Trailer) Add(key, value string) error {\n\tinitHeaderKV(&t.bufKV, key, value, t.disableNormalizing)\n\treturn t.addArgBytes(t.bufKV.key, t.bufKV.value, ArgsHasValue)\n}\n\nfunc (t *Trailer) addArgBytes(key, value []byte, noValue bool) error {\n\tif IsBadTrailer(key) {\n\t\treturn errs.NewPublicf(\"forbidden trailer key: %q\", key)\n\t}\n\tt.h = appendArgBytes(t.h, key, value, noValue)\n\treturn nil\n}\n\nfunc (t *Trailer) setArgBytes(key, value []byte, noValue bool) error {\n\tif IsBadTrailer(key) {\n\t\treturn errs.NewPublicf(\"forbidden trailer key: %q\", key)\n\t}\n\tt.h = setArgBytes(t.h, key, value, noValue)\n\treturn nil\n}\n\nfunc (t *Trailer) UpdateArgBytes(key, value []byte) error {\n\tif IsBadTrailer(key) {\n\t\treturn errs.NewPublicf(\"forbidden trailer key: %q\", key)\n\t}\n\n\tt.h = updateArgBytes(t.h, key, value)\n\treturn nil\n}\n\nfunc (t *Trailer) GetTrailers() []argsKV {\n\treturn t.h\n}\n\nfunc (t *Trailer) Empty() bool {\n\treturn len(t.h) == 0\n}\n\n// GetBytes return the 'Trailer' Header which is composed by the Trailer key\nfunc (t *Trailer) GetBytes() []byte {\n\tvar dst []byte\n\tfor i, n := 0, len(t.h); i < n; i++ {\n\t\tkv := &t.h[i]\n\t\tdst = append(dst, kv.key...)\n\t\tif i+1 < n {\n\t\t\tdst = append(dst, bytestr.StrCommaSpace...)\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc (t *Trailer) ResetSkipNormalize() {\n\tt.h = t.h[:0]\n}\n\nfunc (t *Trailer) Reset() {\n\tt.disableNormalizing = false\n\tt.ResetSkipNormalize()\n}\n\nfunc (t *Trailer) DisableNormalizing() {\n\tt.disableNormalizing = true\n}\n\nfunc (t *Trailer) IsDisableNormalizing() bool {\n\treturn t.disableNormalizing\n}\n\n// CopyTo copies all the trailer to dst.\nfunc (t *Trailer) CopyTo(dst *Trailer) {\n\tdst.Reset()\n\n\tdst.disableNormalizing = t.disableNormalizing\n\tdst.h = copyArgs(dst.h, t.h)\n}\n\nfunc (t *Trailer) SetTrailers(trailers []byte) (err error) {\n\tt.ResetSkipNormalize()\n\tfor i := -1; i+1 < len(trailers); {\n\t\ttrailers = trailers[i+1:]\n\t\ti = bytes.IndexByte(trailers, ',')\n\t\tif i < 0 {\n\t\t\ti = len(trailers)\n\t\t}\n\t\ttrailerKey := trailers[:i]\n\t\tfor len(trailerKey) > 0 && trailerKey[0] == ' ' {\n\t\t\ttrailerKey = trailerKey[1:]\n\t\t}\n\t\tfor len(trailerKey) > 0 && trailerKey[len(trailerKey)-1] == ' ' {\n\t\t\ttrailerKey = trailerKey[:len(trailerKey)-1]\n\t\t}\n\n\t\tutils.NormalizeHeaderKey(trailerKey, t.disableNormalizing)\n\t\terr = t.addArgBytes(trailerKey, nil, argsNoValue)\n\t}\n\treturn\n}\n\nfunc (t *Trailer) Header() []byte {\n\tt.bufKV.value = t.AppendBytes(t.bufKV.value[:0])\n\treturn t.bufKV.value\n}\n\nfunc (t *Trailer) AppendBytes(dst []byte) []byte {\n\tfor i, n := 0, len(t.h); i < n; i++ {\n\t\tkv := &t.h[i]\n\t\tdst = appendHeaderLine(dst, kv.key, kv.value)\n\t}\n\n\tdst = append(dst, bytestr.StrCRLF...)\n\treturn dst\n}\n\nfunc IsBadTrailer(key []byte) bool {\n\tswitch key[0] | 0x20 {\n\tcase 'a':\n\t\treturn utils.CaseInsensitiveCompare(key, bytestr.StrAuthorization)\n\tcase 'c':\n\t\tif len(key) >= len(consts.HeaderContentType) && utils.CaseInsensitiveCompare(key[:8], bytestr.StrContentType[:8]) {\n\t\t\t// skip compare prefix 'Content-'\n\t\t\treturn utils.CaseInsensitiveCompare(key[8:], bytestr.StrContentEncoding[8:]) ||\n\t\t\t\tutils.CaseInsensitiveCompare(key[8:], bytestr.StrContentLength[8:]) ||\n\t\t\t\tutils.CaseInsensitiveCompare(key[8:], bytestr.StrContentType[8:]) ||\n\t\t\t\tutils.CaseInsensitiveCompare(key[8:], bytestr.StrContentRange[8:])\n\t\t}\n\t\treturn utils.CaseInsensitiveCompare(key, bytestr.StrConnection)\n\tcase 'e':\n\t\treturn utils.CaseInsensitiveCompare(key, bytestr.StrExpect)\n\tcase 'h':\n\t\treturn utils.CaseInsensitiveCompare(key, bytestr.StrHost)\n\tcase 'k':\n\t\treturn utils.CaseInsensitiveCompare(key, bytestr.StrKeepAlive)\n\tcase 'm':\n\t\treturn utils.CaseInsensitiveCompare(key, bytestr.StrMaxForwards)\n\tcase 'p':\n\t\tif len(key) >= len(consts.HeaderProxyConnection) && utils.CaseInsensitiveCompare(key[:6], bytestr.StrProxyConnection[:6]) {\n\t\t\t// skip compare prefix 'Proxy-'\n\t\t\treturn utils.CaseInsensitiveCompare(key[6:], bytestr.StrProxyConnection[6:]) ||\n\t\t\t\tutils.CaseInsensitiveCompare(key[6:], bytestr.StrProxyAuthenticate[6:]) ||\n\t\t\t\tutils.CaseInsensitiveCompare(key[6:], bytestr.StrProxyAuthorization[6:])\n\t\t}\n\tcase 'r':\n\t\treturn utils.CaseInsensitiveCompare(key, bytestr.StrRange)\n\tcase 't':\n\t\treturn utils.CaseInsensitiveCompare(key, bytestr.StrTE) ||\n\t\t\tutils.CaseInsensitiveCompare(key, bytestr.StrTrailer) ||\n\t\t\tutils.CaseInsensitiveCompare(key, bytestr.StrTransferEncoding)\n\tcase 'w':\n\t\treturn utils.CaseInsensitiveCompare(key, bytestr.StrWWWAuthenticate)\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/protocol/trailer_test.go",
    "content": "/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage protocol\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\nfunc TestTrailerAdd(t *testing.T) {\n\tvar tr Trailer\n\tassert.Nil(t, tr.Add(\"foo\", \"value1\"))\n\tassert.Nil(t, tr.Add(\"foo\", \"value2\"))\n\tassert.Nil(t, tr.Add(\"bar\", \"value3\"))\n\tassert.True(t, strings.Contains(string(tr.Header()), \"Foo: value1\"))\n\tassert.True(t, strings.Contains(string(tr.Header()), \"Foo: value2\"))\n\tassert.True(t, strings.Contains(string(tr.Header()), \"Bar: value3\"))\n}\n\nfunc TestHeaderTrailerSet(t *testing.T) {\n\th := &RequestHeader{}\n\n\t// only one trailer\n\th.Set(\"Trailer\", \"Foo\")\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Foo:\"))\n\n\t// multi trailer\n\th.Set(\"Trailer\", \"Foo, bar, HERtz\")\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Foo:\"))\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Bar:\"))\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Hertz:\"))\n\n\t// all lowercase\n\th.Set(\"Trailer\", \"foo,hertz,aaa\")\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Foo:\"))\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Hertz:\"))\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Aaa:\"))\n\n\t// all uppercase\n\th.Set(\"Trailer\", \"FOO,HERTZ,AAA\")\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Foo:\"))\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Hertz:\"))\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Aaa:\"))\n\n\t// with '-'\n\th.Set(\"Trailer\", \"FOO-HERTZ-AAA\")\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Foo-Hertz-Aaa:\"))\n\n\t// more space\n\th.Set(\"Trailer\", \"      foo,      hertz       ,        aaa      \")\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Foo:\"))\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Hertz:\"))\n\tassert.True(t, strings.Contains(string(h.Trailer().Header()), \"Aaa:\"))\n}\n\nfunc TestTrailerAddError(t *testing.T) {\n\tvar tr Trailer\n\tassert.NotNil(t, tr.Add(consts.HeaderContentType, \"\"))\n\tassert.NotNil(t, tr.Set(consts.HeaderProxyConnection, \"\"))\n}\n\nfunc TestTrailerDel(t *testing.T) {\n\tvar tr Trailer\n\tassert.Nil(t, tr.Add(\"foo\", \"value1\"))\n\tassert.Nil(t, tr.Add(\"foo\", \"value2\"))\n\tassert.Nil(t, tr.Add(\"bar\", \"value3\"))\n\ttr.Del(\"foo\")\n\tassert.False(t, strings.Contains(string(tr.Header()), \"Foo: value1\"))\n\tassert.False(t, strings.Contains(string(tr.Header()), \"Foo: value2\"))\n\tassert.True(t, strings.Contains(string(tr.Header()), \"Bar: value3\"))\n}\n\nfunc TestTrailerSet(t *testing.T) {\n\tvar tr Trailer\n\tassert.Nil(t, tr.Set(\"foo\", \"value1\"))\n\tassert.Nil(t, tr.Set(\"foo\", \"value2\"))\n\tassert.Nil(t, tr.Set(\"bar\", \"value3\"))\n\tassert.False(t, strings.Contains(string(tr.Header()), \"Foo: value1\"))\n\tassert.True(t, strings.Contains(string(tr.Header()), \"Foo: value2\"))\n\tassert.True(t, strings.Contains(string(tr.Header()), \"Bar: value3\"))\n}\n\nfunc TestTrailerGet(t *testing.T) {\n\tvar tr Trailer\n\tassert.Nil(t, tr.Add(\"foo\", \"value1\"))\n\tassert.Nil(t, tr.Add(\"bar\", \"value3\"))\n\tassert.DeepEqual(t, tr.Get(\"foo\"), \"value1\")\n\tassert.DeepEqual(t, tr.Get(\"bar\"), \"value3\")\n}\n\nfunc TestTrailerUpdateArgBytes(t *testing.T) {\n\tvar tr Trailer\n\tassert.Nil(t, tr.addArgBytes([]byte(\"Foo\"), []byte(\"value0\"), argsNoValue))\n\tassert.Nil(t, tr.UpdateArgBytes([]byte(\"Foo\"), []byte(\"value1\")))\n\tassert.Nil(t, tr.UpdateArgBytes([]byte(\"Foo\"), []byte(\"value2\")))\n\tassert.Nil(t, tr.UpdateArgBytes([]byte(\"Bar\"), []byte(\"value3\")))\n\tassert.True(t, strings.Contains(string(tr.Header()), \"Foo: value1\"))\n\tassert.False(t, strings.Contains(string(tr.Header()), \"Foo: value2\"))\n\tassert.False(t, strings.Contains(string(tr.Header()), \"Bar: value3\"))\n}\n\nfunc TestTrailerEmpty(t *testing.T) {\n\tvar tr Trailer\n\tassert.DeepEqual(t, tr.Empty(), true)\n\tassert.Nil(t, tr.Set(\"foo\", \"\"))\n\tassert.DeepEqual(t, tr.Empty(), false)\n}\n\nfunc TestTrailerVisitAll(t *testing.T) {\n\tvar tr Trailer\n\tassert.Nil(t, tr.Add(\"foo\", \"value1\"))\n\tassert.Nil(t, tr.Add(\"bar\", \"value2\"))\n\ttr.VisitAll(\n\t\tfunc(k, v []byte) {\n\t\t\tkey := string(k)\n\t\t\tvalue := string(v)\n\t\t\tif (key != \"Foo\" || value != \"value1\") && (key != \"Bar\" || value != \"value2\") {\n\t\t\t\tt.Fatalf(\"Unexpected (%v, %v). Expected %v\", key, value, \"(foo, value1) or (bar, value2)\")\n\t\t\t}\n\t\t})\n}\n\nfunc TestIsBadTrailer(t *testing.T) {\n\tassert.True(t, IsBadTrailer(bytestr.StrAuthorization))\n\tassert.True(t, IsBadTrailer(bytestr.StrContentEncoding))\n\tassert.True(t, IsBadTrailer(bytestr.StrContentLength))\n\tassert.True(t, IsBadTrailer(bytestr.StrContentType))\n\tassert.True(t, IsBadTrailer(bytestr.StrContentRange))\n\tassert.True(t, IsBadTrailer(bytestr.StrConnection))\n\tassert.True(t, IsBadTrailer(bytestr.StrExpect))\n\tassert.True(t, IsBadTrailer(bytestr.StrHost))\n\tassert.True(t, IsBadTrailer(bytestr.StrKeepAlive))\n\tassert.True(t, IsBadTrailer(bytestr.StrMaxForwards))\n\tassert.True(t, IsBadTrailer(bytestr.StrProxyConnection))\n\tassert.True(t, IsBadTrailer(bytestr.StrProxyAuthenticate))\n\tassert.True(t, IsBadTrailer(bytestr.StrProxyAuthorization))\n\tassert.True(t, IsBadTrailer(bytestr.StrRange))\n\tassert.True(t, IsBadTrailer(bytestr.StrTE))\n\tassert.True(t, IsBadTrailer(bytestr.StrTrailer))\n\tassert.True(t, IsBadTrailer(bytestr.StrTransferEncoding))\n\tassert.True(t, IsBadTrailer(bytestr.StrWWWAuthenticate))\n}\n"
  },
  {
    "path": "pkg/protocol/uri.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/internal/nocopy\"\n)\n\n// AcquireURI returns an empty URI instance from the pool.\n//\n// Release the URI with ReleaseURI after the URI is no longer needed.\n// This allows reducing GC load.\nfunc AcquireURI() *URI {\n\treturn uriPool.Get().(*URI)\n}\n\n// ReleaseURI releases the URI acquired via AcquireURI.\n//\n// The released URI mustn't be used after releasing it, otherwise data races\n// may occur.\nfunc ReleaseURI(u *URI) {\n\tu.Reset()\n\turiPool.Put(u)\n}\n\nvar uriPool = &sync.Pool{\n\tNew: func() interface{} {\n\t\treturn &URI{}\n\t},\n}\n\ntype URI struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\tpathOriginal []byte\n\tscheme       []byte\n\tpath         []byte\n\tqueryString  []byte\n\thash         []byte\n\thost         []byte\n\n\tqueryArgs       Args\n\tparsedQueryArgs bool\n\n\tDisablePathNormalizing bool\n\n\tfullURI    []byte\n\trequestURI []byte\n\n\tusername []byte\n\tpassword []byte\n}\n\ntype argsKV struct {\n\tkey     []byte\n\tvalue   []byte\n\tnoValue bool\n}\n\nfunc (kv *argsKV) GetKey() []byte {\n\treturn kv.key\n}\n\nfunc (kv *argsKV) GetValue() []byte {\n\treturn kv.value\n}\n\n// CopyTo copies uri contents to dst.\nfunc (u *URI) CopyTo(dst *URI) {\n\tdst.Reset()\n\tdst.pathOriginal = append(dst.pathOriginal[:0], u.pathOriginal...)\n\tdst.scheme = append(dst.scheme[:0], u.scheme...)\n\tdst.path = append(dst.path[:0], u.path...)\n\tdst.queryString = append(dst.queryString[:0], u.queryString...)\n\tdst.hash = append(dst.hash[:0], u.hash...)\n\tdst.host = append(dst.host[:0], u.host...)\n\tdst.username = append(dst.username[:0], u.username...)\n\tdst.password = append(dst.password[:0], u.password...)\n\n\tu.queryArgs.CopyTo(&dst.queryArgs)\n\tdst.parsedQueryArgs = u.parsedQueryArgs\n\tdst.DisablePathNormalizing = u.DisablePathNormalizing\n\n\t// fullURI and requestURI shouldn't be copied, since they are created\n\t// from scratch on each FullURI() and RequestURI() call.\n}\n\n// QueryArgs returns query args.\nfunc (u *URI) QueryArgs() *Args {\n\tu.parseQueryArgs()\n\treturn &u.queryArgs\n}\n\nfunc (u *URI) parseQueryArgs() {\n\tif u.parsedQueryArgs {\n\t\treturn\n\t}\n\tu.queryArgs.ParseBytes(u.queryString)\n\tu.parsedQueryArgs = true\n}\n\n// Hash returns URI hash, i.e. qwe of http://aaa.com/foo/bar?baz=123#qwe .\n//\n// The returned value is valid until the next URI method call.\nfunc (u *URI) Hash() []byte {\n\treturn u.hash\n}\n\n// SetHash sets URI hash.\nfunc (u *URI) SetHash(hash string) {\n\tu.hash = append(u.hash[:0], hash...)\n}\n\n// SetHashBytes sets URI hash.\nfunc (u *URI) SetHashBytes(hash []byte) {\n\tu.hash = append(u.hash[:0], hash...)\n}\n\n// Username returns URI username\nfunc (u *URI) Username() []byte {\n\treturn u.username\n}\n\n// SetUsername sets URI username.\nfunc (u *URI) SetUsername(username string) {\n\tu.username = append(u.username[:0], username...)\n}\n\n// SetUsernameBytes sets URI username.\nfunc (u *URI) SetUsernameBytes(username []byte) {\n\tu.username = append(u.username[:0], username...)\n}\n\n// Password returns URI password\nfunc (u *URI) Password() []byte {\n\treturn u.password\n}\n\n// SetPassword sets URI password.\nfunc (u *URI) SetPassword(password string) {\n\tu.password = append(u.password[:0], password...)\n}\n\n// SetPasswordBytes sets URI password.\nfunc (u *URI) SetPasswordBytes(password []byte) {\n\tu.password = append(u.password[:0], password...)\n}\n\n// QueryString returns URI query string,\n// i.e. baz=123 of http://aaa.com/foo/bar?baz=123#qwe .\n//\n// The returned value is valid until the next URI method call.\nfunc (u *URI) QueryString() []byte {\n\treturn u.queryString\n}\n\n// SetQueryString sets URI query string.\nfunc (u *URI) SetQueryString(queryString string) {\n\tu.queryString = append(u.queryString[:0], queryString...)\n\tu.parsedQueryArgs = false\n}\n\n// SetQueryStringBytes sets URI query string.\nfunc (u *URI) SetQueryStringBytes(queryString []byte) {\n\tu.queryString = append(u.queryString[:0], queryString...)\n\tu.parsedQueryArgs = false\n}\n\n// Path returns URI path, i.e. /foo/bar of http://aaa.com/foo/bar?baz=123#qwe .\n//\n// The returned path is always urldecoded and normalized,\n// i.e. '//f%20obar/baz/../zzz' becomes '/f obar/zzz'.\n//\n// The returned value is valid until the next URI method call.\nfunc (u *URI) Path() []byte {\n\tpath := u.path\n\tif len(path) == 0 {\n\t\tpath = bytestr.StrSlash\n\t}\n\treturn path\n}\n\n// SetPath sets URI path.\nfunc (u *URI) SetPath(path string) {\n\tu.pathOriginal = append(u.pathOriginal[:0], path...)\n\tu.path = normalizePath(u.path, u.pathOriginal)\n}\n\n// String returns full uri.\nfunc (u *URI) String() string {\n\treturn string(u.FullURI())\n}\n\n// SetPathBytes sets URI path.\nfunc (u *URI) SetPathBytes(path []byte) {\n\tu.pathOriginal = append(u.pathOriginal[:0], path...)\n\tu.path = normalizePath(u.path, u.pathOriginal)\n}\n\n// PathOriginal returns the original path from requestURI passed to URI.Parse().\n//\n// The returned value is valid until the next URI method call.\nfunc (u *URI) PathOriginal() []byte {\n\treturn u.pathOriginal\n}\n\n// Scheme returns URI scheme, i.e. http of http://aaa.com/foo/bar?baz=123#qwe .\n//\n// Returned scheme is always lowercased.\n//\n// The returned value is valid until the next URI method call.\nfunc (u *URI) Scheme() []byte {\n\tscheme := u.scheme\n\tif len(scheme) == 0 {\n\t\tscheme = bytestr.StrHTTP\n\t}\n\treturn scheme\n}\n\n// SetScheme sets URI scheme, i.e. http, https, ftp, etc.\nfunc (u *URI) SetScheme(scheme string) {\n\tu.scheme = append(u.scheme[:0], scheme...)\n\tbytesconv.LowercaseBytes(u.scheme)\n}\n\n// SetSchemeBytes sets URI scheme, i.e. http, https, ftp, etc.\nfunc (u *URI) SetSchemeBytes(scheme []byte) {\n\tu.scheme = append(u.scheme[:0], scheme...)\n\tbytesconv.LowercaseBytes(u.scheme)\n}\n\n// Reset clears uri.\nfunc (u *URI) Reset() {\n\tu.pathOriginal = u.pathOriginal[:0]\n\tu.scheme = u.scheme[:0]\n\tu.path = u.path[:0]\n\tu.queryString = u.queryString[:0]\n\tu.hash = u.hash[:0]\n\tu.username = u.username[:0]\n\tu.password = u.password[:0]\n\n\tu.host = u.host[:0]\n\tu.queryArgs.Reset()\n\tu.parsedQueryArgs = false\n\tu.DisablePathNormalizing = false\n\n\t// There is no need in u.fullURI = u.fullURI[:0], since full uri\n\t// is calculated on each call to FullURI().\n\n\t// There is no need in u.requestURI = u.requestURI[:0], since requestURI\n\t// is calculated on each call to RequestURI().\n}\n\n// Host returns host part, i.e. aaa.com of http://aaa.com/foo/bar?baz=123#qwe .\n//\n// Host is always lowercased.\nfunc (u *URI) Host() []byte {\n\treturn u.host\n}\n\n// SetHost sets host for the uri.\nfunc (u *URI) SetHost(host string) {\n\tu.host = append(u.host[:0], host...)\n\tbytesconv.LowercaseBytes(u.host)\n}\n\n// SetHostBytes sets host for the uri.\nfunc (u *URI) SetHostBytes(host []byte) {\n\tu.host = append(u.host[:0], host...)\n\tbytesconv.LowercaseBytes(u.host)\n}\n\n// LastPathSegment returns the last part of uri path after '/'.\n//\n// Examples:\n//\n//   - For /foo/bar/baz.html path returns baz.html.\n//   - For /foo/bar/ returns empty byte slice.\n//   - For /foobar.js returns foobar.js.\nfunc (u *URI) LastPathSegment() []byte {\n\tpath := u.Path()\n\tn := bytes.LastIndexByte(path, '/')\n\tif n < 0 {\n\t\treturn path\n\t}\n\treturn path[n+1:]\n}\n\n// Update updates uri.\n//\n// The following newURI types are accepted:\n//\n//   - Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original\n//     uri is replaced by newURI.\n//   - Absolute without scheme, i.e. //foobar.com/aaa/bb?cc. In this case\n//     the original scheme is preserved.\n//   - Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part\n//     of the original uri is replaced.\n//   - Relative path, i.e.  xx?yy=abc . In this case the original RequestURI\n//     is updated according to the new relative path.\nfunc (u *URI) Update(newURI string) {\n\tu.UpdateBytes(bytesconv.S2b(newURI))\n}\n\n// UpdateBytes updates uri.\n//\n// The following newURI types are accepted:\n//\n//   - Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original\n//     uri is replaced by newURI.\n//   - Absolute without scheme, i.e. //foobar.com/aaa/bb?cc. In this case\n//     the original scheme is preserved.\n//   - Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part\n//     of the original uri is replaced.\n//   - Relative path, i.e.  xx?yy=abc . In this case the original RequestURI\n//     is updated according to the new relative path.\nfunc (u *URI) UpdateBytes(newURI []byte) {\n\tu.requestURI = u.updateBytes(newURI, u.requestURI)\n}\n\n// Parse initializes URI from the given host and uri.\n//\n// host may be nil. In this case uri must contain fully qualified uri,\n// i.e. with scheme and host. http is assumed if scheme is omitted.\n//\n// uri may contain e.g. RequestURI without scheme and host if host is non-empty.\nfunc (u *URI) Parse(host, uri []byte) {\n\tu.parse(host, uri, false)\n}\n\n// Maybe rawURL is of the form scheme:path.\n// (Scheme must be [a-zA-Z][a-zA-Z0-9+-.]*)\n// If so, return scheme, path; else return nil, rawURL.\nfunc getScheme(rawURL []byte) (scheme, path []byte) {\n\tfor i := 0; i < len(rawURL); i++ {\n\t\tc := rawURL[i]\n\t\tswitch {\n\t\tcase 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':\n\t\t// do nothing\n\t\tcase '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.':\n\t\t\tif i == 0 {\n\t\t\t\treturn nil, rawURL\n\t\t\t}\n\t\tcase c == ':':\n\t\t\treturn checkSchemeWhenCharIsColon(i, rawURL)\n\t\tdefault:\n\t\t\t// we have encountered an invalid character,\n\t\t\t// so there is no valid scheme\n\t\t\treturn nil, rawURL\n\t\t}\n\t}\n\treturn nil, rawURL\n}\n\nfunc (u *URI) parse(host, uri []byte, isTLS bool) {\n\tu.Reset()\n\n\tif stringContainsCTLByte(uri) {\n\t\treturn\n\t}\n\n\tif len(host) == 0 || bytes.Contains(uri, bytestr.StrColonSlashSlash) {\n\t\tscheme, newHost, newURI := splitHostURI(host, uri)\n\t\tu.scheme = append(u.scheme, scheme...)\n\t\tbytesconv.LowercaseBytes(u.scheme)\n\t\thost = newHost\n\t\turi = newURI\n\t}\n\n\tif isTLS {\n\t\tu.scheme = append(u.scheme[:0], bytestr.StrHTTPS...)\n\t}\n\n\tif n := bytes.Index(host, bytestr.StrAt); n >= 0 {\n\t\tauth := host[:n]\n\t\thost = host[n+1:]\n\n\t\tif n := bytes.Index(auth, bytestr.StrColon); n >= 0 {\n\t\t\tu.username = append(u.username[:0], auth[:n]...)\n\t\t\tu.password = append(u.password[:0], auth[n+1:]...)\n\t\t} else {\n\t\t\tu.username = append(u.username[:0], auth...)\n\t\t\tu.password = u.password[:0]\n\t\t}\n\t}\n\n\tu.host = append(u.host, host...)\n\tbytesconv.LowercaseBytes(u.host)\n\n\tb := uri\n\tqueryIndex := bytes.IndexByte(b, '?')\n\tfragmentIndex := bytes.IndexByte(b, '#')\n\t// Ignore query in fragment part\n\tif fragmentIndex >= 0 && queryIndex > fragmentIndex {\n\t\tqueryIndex = -1\n\t}\n\n\tif queryIndex < 0 && fragmentIndex < 0 {\n\t\tu.pathOriginal = append(u.pathOriginal, b...)\n\t\tu.path = normalizePath(u.path, u.pathOriginal)\n\t\treturn\n\t}\n\n\tif queryIndex >= 0 {\n\t\t// Path is everything up to the start of the query\n\t\tu.pathOriginal = append(u.pathOriginal, b[:queryIndex]...)\n\t\tu.path = normalizePath(u.path, u.pathOriginal)\n\n\t\tif fragmentIndex < 0 {\n\t\t\tu.queryString = append(u.queryString, b[queryIndex+1:]...)\n\t\t} else {\n\t\t\tu.queryString = append(u.queryString, b[queryIndex+1:fragmentIndex]...)\n\t\t\tu.hash = append(u.hash, b[fragmentIndex+1:]...)\n\t\t}\n\t\treturn\n\t}\n\n\t// fragmentIndex >= 0 && queryIndex < 0\n\t// Path is up to the start of fragment\n\tu.pathOriginal = append(u.pathOriginal, b[:fragmentIndex]...)\n\tu.path = normalizePath(u.path, u.pathOriginal)\n\tu.hash = append(u.hash, b[fragmentIndex+1:]...)\n}\n\n// stringContainsCTLByte reports whether s contains any ASCII control character.\nfunc stringContainsCTLByte(s []byte) bool {\n\tfor i := 0; i < len(s); i++ {\n\t\tb := s[i]\n\t\tif b < ' ' || b == 0x7f {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc splitHostURI(host, uri []byte) ([]byte, []byte, []byte) {\n\tscheme, path := getScheme(uri)\n\n\tif scheme == nil {\n\t\treturn bytestr.StrHTTP, host, uri\n\t}\n\n\turi = path[len(bytestr.StrSlashSlash):]\n\tn := bytes.IndexByte(uri, '/')\n\tif n < 0 {\n\t\t// A hack for bogus urls like foobar.com?a=b without\n\t\t// slash after host.\n\t\tif n = bytes.IndexByte(uri, '?'); n >= 0 {\n\t\t\treturn scheme, uri[:n], uri[n:]\n\t\t}\n\t\treturn scheme, uri, bytestr.StrSlash\n\t}\n\treturn scheme, uri[:n], uri[n:]\n}\n\nfunc normalizePath(dst, src []byte) []byte {\n\tdst = dst[:0]\n\tdst = addLeadingSlash(dst, src)\n\tdst = decodeArgAppendNoPlus(dst, src)\n\n\t// Windows server need to replace all backslashes with\n\t// forward slashes to avoid path traversal attacks.\n\tif filepath.Separator == '\\\\' {\n\t\tfor {\n\t\t\tn := bytes.IndexByte(dst, '\\\\')\n\t\t\tif n < 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tdst[n] = '/'\n\t\t}\n\t}\n\n\t// remove duplicate slashes\n\tb := dst\n\tbSize := len(b)\n\tfor {\n\t\tn := bytes.Index(b, bytestr.StrSlashSlash)\n\t\tif n < 0 {\n\t\t\tbreak\n\t\t}\n\t\tb = b[n:]\n\t\tcopy(b, b[1:])\n\t\tb = b[:len(b)-1]\n\t\tbSize--\n\t}\n\tdst = dst[:bSize]\n\n\t// remove /./ parts\n\tb = dst\n\tfor {\n\t\tn := bytes.Index(b, bytestr.StrSlashDotSlash)\n\t\tif n < 0 {\n\t\t\tbreak\n\t\t}\n\t\tnn := n + len(bytestr.StrSlashDotSlash) - 1\n\t\tcopy(b[n:], b[nn:])\n\t\tb = b[:len(b)-nn+n]\n\t}\n\n\t// remove /foo/../ parts\n\tfor {\n\t\tn := bytes.Index(b, bytestr.StrSlashDotDotSlash)\n\t\tif n < 0 {\n\t\t\tbreak\n\t\t}\n\t\tnn := bytes.LastIndexByte(b[:n], '/')\n\t\tif nn < 0 {\n\t\t\tnn = 0\n\t\t}\n\t\tn += len(bytestr.StrSlashDotDotSlash) - 1\n\t\tcopy(b[nn:], b[n:])\n\t\tb = b[:len(b)-n+nn]\n\t}\n\n\t// remove trailing /foo/..\n\tn := bytes.LastIndex(b, bytestr.StrSlashDotDot)\n\tif n >= 0 && n+len(bytestr.StrSlashDotDot) == len(b) {\n\t\tnn := bytes.LastIndexByte(b[:n], '/')\n\t\tif nn < 0 {\n\t\t\treturn bytestr.StrSlash\n\t\t}\n\t\tb = b[:nn+1]\n\t}\n\n\treturn b\n}\n\nfunc copyArgs(dst, src []argsKV) []argsKV {\n\tif cap(dst) < len(src) {\n\t\ttmp := make([]argsKV, len(src))\n\t\tcopy(tmp, dst)\n\t\tdst = tmp\n\t}\n\tn := len(src)\n\tdst = dst[:n]\n\tfor i := 0; i < n; i++ {\n\t\tdstKV := &dst[i]\n\t\tsrcKV := &src[i]\n\t\tdstKV.key = append(dstKV.key[:0], srcKV.key...)\n\t\tif srcKV.noValue {\n\t\t\tdstKV.value = dstKV.value[:0]\n\t\t} else {\n\t\t\tdstKV.value = append(dstKV.value[:0], srcKV.value...)\n\t\t}\n\t\tdstKV.noValue = srcKV.noValue\n\t}\n\treturn dst\n}\n\nfunc (u *URI) updateBytes(newURI, buf []byte) []byte {\n\tif len(newURI) == 0 {\n\t\treturn buf\n\t}\n\n\tn := bytes.Index(newURI, bytestr.StrSlashSlash)\n\tif n >= 0 {\n\t\t// absolute uri\n\t\tvar b [32]byte\n\t\tschemeOriginal := b[:0]\n\t\tif len(u.scheme) > 0 {\n\t\t\tschemeOriginal = append([]byte(nil), u.scheme...)\n\t\t}\n\t\tif n == 0 {\n\t\t\tnewURI = bytes.Join([][]byte{u.scheme, bytestr.StrColon, newURI}, nil)\n\t\t}\n\t\tu.Parse(nil, newURI)\n\t\tif len(schemeOriginal) > 0 && len(u.scheme) == 0 {\n\t\t\tu.scheme = append(u.scheme[:0], schemeOriginal...)\n\t\t}\n\t\treturn buf\n\t}\n\n\tif newURI[0] == '/' {\n\t\t// uri without host\n\t\tbuf = u.appendSchemeHost(buf[:0])\n\t\tbuf = append(buf, newURI...)\n\t\tu.Parse(nil, buf)\n\t\treturn buf\n\t}\n\n\t// relative path\n\tswitch newURI[0] {\n\tcase '?':\n\t\t// query string only update\n\t\tu.SetQueryStringBytes(newURI[1:])\n\t\treturn append(buf[:0], u.FullURI()...)\n\tcase '#':\n\t\t// update only hash\n\t\tu.SetHashBytes(newURI[1:])\n\t\treturn append(buf[:0], u.FullURI()...)\n\tdefault:\n\t\t// update the last path part after the slash\n\t\tpath := u.Path()\n\t\tn = bytes.LastIndexByte(path, '/')\n\t\tif n < 0 {\n\t\t\tpanic(\"BUG: path must contain at least one slash\")\n\t\t}\n\t\tbuf = u.appendSchemeHost(buf[:0])\n\t\tbuf = bytesconv.AppendQuotedPath(buf, path[:n+1])\n\t\tbuf = append(buf, newURI...)\n\t\tu.Parse(nil, buf)\n\t\treturn buf\n\t}\n}\n\n// AppendBytes appends full uri to dst and returns the extended dst.\nfunc (u *URI) AppendBytes(dst []byte) []byte {\n\tdst = u.appendSchemeHost(dst)\n\tdst = append(dst, u.RequestURI()...)\n\tif len(u.hash) > 0 {\n\t\tdst = append(dst, '#')\n\t\tdst = append(dst, u.hash...)\n\t}\n\treturn dst\n}\n\n// RequestURI returns RequestURI - i.e. URI without Scheme and Host.\nfunc (u *URI) RequestURI() []byte {\n\tvar dst []byte\n\tif u.DisablePathNormalizing {\n\t\tdst = append(u.requestURI[:0], u.PathOriginal()...)\n\t} else {\n\t\tdst = bytesconv.AppendQuotedPath(u.requestURI[:0], u.Path())\n\t}\n\tif u.queryArgs.Len() > 0 {\n\t\tdst = append(dst, '?')\n\t\tdst = u.queryArgs.AppendBytes(dst)\n\t} else if len(u.queryString) > 0 {\n\t\tdst = append(dst, '?')\n\t\tdst = append(dst, u.queryString...)\n\t}\n\tu.requestURI = dst\n\treturn u.requestURI\n}\n\nfunc (u *URI) appendSchemeHost(dst []byte) []byte {\n\tdst = append(dst, u.Scheme()...)\n\tdst = append(dst, bytestr.StrColonSlashSlash...)\n\treturn append(dst, u.Host()...)\n}\n\n// FullURI returns full uri in the form {Scheme}://{Host}{RequestURI}#{Hash}.\nfunc (u *URI) FullURI() []byte {\n\tu.fullURI = u.AppendBytes(u.fullURI[:0])\n\treturn u.fullURI\n}\n\nfunc ParseURI(uriStr string) *URI {\n\turi := &URI{}\n\turi.Parse(nil, []byte(uriStr))\n\n\treturn uri\n}\n\ntype Proxy func(*Request) (*URI, error)\n\nfunc ProxyURI(fixedURI *URI) Proxy {\n\treturn func(*Request) (*URI, error) {\n\t\treturn fixedURI, nil\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/uri_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"bytes\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestURI_Username(t *testing.T) {\n\tvar req Request\n\treq.SetRequestURI(\"http://user:pass@example.com/foo/bar\")\n\tu := req.URI()\n\tuser1 := string(u.Username())\n\treq.Header.SetRequestURIBytes([]byte(\"/foo/bar\"))\n\tu = req.URI()\n\tuser2 := string(u.Username())\n\tassert.DeepEqual(t, user1, user2)\n\n\texpectUser3 := \"user3\"\n\texpectUser4 := \"user4\"\n\n\tu.SetUsername(expectUser3)\n\tuser3 := string(u.Username())\n\tassert.DeepEqual(t, expectUser3, user3)\n\tu.SetUsername(expectUser4)\n\tuser4 := string(u.Username())\n\tassert.DeepEqual(t, expectUser4, user4)\n\n\tu.SetUsernameBytes([]byte(user3))\n\tassert.DeepEqual(t, expectUser3, user3)\n\tu.SetUsernameBytes([]byte(user4))\n\tassert.DeepEqual(t, expectUser4, user4)\n}\n\nfunc TestURI_Password(t *testing.T) {\n\tu := AcquireURI()\n\tdefer ReleaseURI(u)\n\n\texpectPassword1 := \"password1\"\n\texpectPassword2 := \"password2\"\n\n\tu.SetPassword(expectPassword1)\n\tpassword1 := string(u.Password())\n\tassert.DeepEqual(t, expectPassword1, password1)\n\tu.SetPassword(expectPassword2)\n\tpassword2 := string(u.Password())\n\tassert.DeepEqual(t, expectPassword2, password2)\n\n\tu.SetPasswordBytes([]byte(password1))\n\tassert.DeepEqual(t, expectPassword1, password1)\n\tu.SetPasswordBytes([]byte(password2))\n\tassert.DeepEqual(t, expectPassword2, password2)\n}\n\nfunc TestURI_Hash(t *testing.T) {\n\tu := AcquireURI()\n\tdefer ReleaseURI(u)\n\n\texpectHash1 := \"hash1\"\n\texpectHash2 := \"hash2\"\n\n\tu.SetHash(expectHash1)\n\thash1 := string(u.Hash())\n\tassert.DeepEqual(t, expectHash1, hash1)\n\tu.SetHash(expectHash2)\n\thash2 := string(u.Hash())\n\tassert.DeepEqual(t, expectHash2, hash2)\n}\n\nfunc TestURI_QueryString(t *testing.T) {\n\tu := AcquireURI()\n\tdefer ReleaseURI(u)\n\n\texpectQueryString1 := \"key1=value1&key2=value2\"\n\texpectQueryString2 := \"key3=value3&key4=value4\"\n\n\tu.SetQueryString(expectQueryString1)\n\tqueryString1 := string(u.QueryString())\n\tassert.DeepEqual(t, expectQueryString1, queryString1)\n\tu.SetQueryString(expectQueryString2)\n\tqueryString2 := string(u.QueryString())\n\tassert.DeepEqual(t, expectQueryString2, queryString2)\n}\n\nfunc TestURI_Path(t *testing.T) {\n\tu := AcquireURI()\n\tdefer ReleaseURI(u)\n\n\texpectPath1 := \"/\"\n\texpectPath2 := \"/path1\"\n\texpectPath3 := \"/path3\"\n\n\t// When Path is not set, Path defaults to \"/\"\n\tpath1 := string(u.Path())\n\tassert.DeepEqual(t, expectPath1, path1)\n\n\tu.SetPath(expectPath2)\n\tpath2 := string(u.Path())\n\tassert.DeepEqual(t, expectPath2, path2)\n\tu.SetPath(expectPath3)\n\tpath3 := string(u.Path())\n\tassert.DeepEqual(t, expectPath3, path3)\n\n\tu.SetPathBytes([]byte(path2))\n\tassert.DeepEqual(t, expectPath2, path2)\n\tu.SetPathBytes([]byte(path3))\n\tassert.DeepEqual(t, expectPath3, path3)\n}\n\nfunc TestURI_Scheme(t *testing.T) {\n\tu := AcquireURI()\n\tdefer ReleaseURI(u)\n\n\texpectScheme1 := \"scheme1\"\n\texpectScheme2 := \"scheme2\"\n\n\tu.SetScheme(expectScheme1)\n\tscheme1 := string(u.Scheme())\n\tassert.DeepEqual(t, expectScheme1, scheme1)\n\tu.SetScheme(expectScheme2)\n\tscheme2 := string(u.Scheme())\n\tassert.DeepEqual(t, expectScheme2, scheme2)\n\n\tu.SetSchemeBytes([]byte(scheme1))\n\tassert.DeepEqual(t, expectScheme1, scheme1)\n\tu.SetSchemeBytes([]byte(scheme2))\n\tassert.DeepEqual(t, expectScheme2, scheme2)\n}\n\nfunc TestURI_Host(t *testing.T) {\n\tu := AcquireURI()\n\tdefer ReleaseURI(u)\n\n\texpectHost1 := \"host1\"\n\texpectHost2 := \"host2\"\n\n\tu.SetHost(expectHost1)\n\thost1 := string(u.Host())\n\tassert.DeepEqual(t, expectHost1, host1)\n\tu.SetHost(expectHost2)\n\thost2 := string(u.Host())\n\tassert.DeepEqual(t, expectHost2, host2)\n\n\tu.SetHostBytes([]byte(host1))\n\tassert.DeepEqual(t, expectHost1, host1)\n\tu.SetHostBytes([]byte(host2))\n\tassert.DeepEqual(t, expectHost2, host2)\n}\n\nfunc TestURI_PathOriginal(t *testing.T) {\n\tvar u URI\n\texpectPath := \"/path\"\n\tu.Parse(nil, []byte(expectPath))\n\turi := string(u.PathOriginal())\n\tassert.DeepEqual(t, expectPath, uri)\n}\n\nfunc TestArgsKV_Get(t *testing.T) {\n\tvar argsKV argsKV\n\texpectKey := \"key\"\n\texpectValue := \"value\"\n\targsKV.key = []byte(expectKey)\n\targsKV.value = []byte(expectValue)\n\tkey := string(argsKV.GetKey())\n\tvalue := string(argsKV.GetValue())\n\tassert.DeepEqual(t, expectKey, key)\n\tassert.DeepEqual(t, expectValue, value)\n}\n\nfunc TestURICopyToQueryArgs(t *testing.T) {\n\tt.Parallel()\n\n\tvar u URI\n\ta := u.QueryArgs()\n\ta.Set(\"foo\", \"bar\")\n\n\tvar u1 URI\n\tu.CopyTo(&u1)\n\ta1 := u1.QueryArgs()\n\n\tif string(a1.Peek(\"foo\")) != \"bar\" {\n\t\tt.Fatalf(\"unexpected query args value %q. Expecting %q\", a1.Peek(\"foo\"), \"bar\")\n\t}\n\tassert.DeepEqual(t, \"bar\", string(a1.Peek(\"foo\")))\n}\n\nfunc TestURICopyTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar u URI\n\tvar copyU URI\n\tu.CopyTo(&copyU)\n\tif !reflect.DeepEqual(&u, &copyU) { //nolint:govet\n\t\tt.Fatalf(\"URICopyTo fail, u: \\n%+v\\ncopyu: \\n%+v\\n\", &u, &copyU) //nolint:govet\n\t}\n\n\tu.UpdateBytes([]byte(\"https://google.com/foo?bar=baz&baraz#qqqq\"))\n\tu.CopyTo(&copyU)\n\tif !reflect.DeepEqual(&u, &copyU) { //nolint:govet\n\t\tt.Fatalf(\"URICopyTo fail, u: \\n%+v\\ncopyu: \\n%+v\\n\", &u, &copyU) //nolint:govet\n\t}\n}\n\nfunc TestURILastPathSegment(t *testing.T) {\n\tt.Parallel()\n\n\ttestURILastPathSegment(t, \"\", \"\")\n\ttestURILastPathSegment(t, \"/\", \"\")\n\ttestURILastPathSegment(t, \"/foo/bar/\", \"\")\n\ttestURILastPathSegment(t, \"/foobar.js\", \"foobar.js\")\n\ttestURILastPathSegment(t, \"/foo/bar/baz.html\", \"baz.html\")\n}\n\nfunc testURILastPathSegment(t *testing.T, path, expectedSegment string) {\n\tvar u URI\n\tu.SetPath(path)\n\tsegment := u.LastPathSegment()\n\tassert.DeepEqual(t, expectedSegment, string(segment))\n}\n\nfunc TestURIPathEscape(t *testing.T) {\n\tt.Parallel()\n\n\ttestURIPathEscape(t, \"/foo/bar\", \"/foo/bar\")\n\ttestURIPathEscape(t, \"/f_o-o=b:ar,b.c&q\", \"/f_o-o=b:ar,b.c&q\")\n\ttestURIPathEscape(t, \"/aa?bb.тест~qq\", \"/aa%3Fbb.%D1%82%D0%B5%D1%81%D1%82~qq\")\n}\n\nfunc TestURIUpdate(t *testing.T) {\n\tt.Parallel()\n\n\t// full uri\n\ttestURIUpdate(t, \"http://foo.bar/baz?aaa=22#aaa\", \"https://aaa.com/bb\", \"https://aaa.com/bb\")\n\t// empty uri\n\ttestURIUpdate(t, \"http://aaa.com/aaa.html?234=234#add\", \"\", \"http://aaa.com/aaa.html?234=234#add\")\n\n\t// request uri\n\ttestURIUpdate(t, \"ftp://aaa/xxx/yyy?aaa=bb#aa\", \"/boo/bar?xx\", \"ftp://aaa/boo/bar?xx\")\n\n\t// relative uri\n\ttestURIUpdate(t, \"http://foo.bar/baz/xxx.html?aaa=22#aaa\", \"bb.html?xx=12#pp\", \"http://foo.bar/baz/bb.html?xx=12#pp\")\n\ttestURIUpdate(t, \"http://xx/a/b/c/d\", \"../qwe/p?zx=34\", \"http://xx/a/b/qwe/p?zx=34\")\n\ttestURIUpdate(t, \"https://qqq/aaa.html?foo=bar\", \"?baz=434&aaa#xcv\", \"https://qqq/aaa.html?baz=434&aaa#xcv\")\n\ttestURIUpdate(t, \"http://foo.bar/baz\", \"~a/%20b=c,тест?йцу=ке\", \"http://foo.bar/~a/%20b=c,%D1%82%D0%B5%D1%81%D1%82?йцу=ке\")\n\ttestURIUpdate(t, \"http://foo.bar/baz\", \"/qwe#fragment\", \"http://foo.bar/qwe#fragment\")\n\ttestURIUpdate(t, \"http://foobar/baz/xxx\", \"aaa.html#bb?cc=dd&ee=dfd\", \"http://foobar/baz/aaa.html#bb?cc=dd&ee=dfd\")\n\n\t// hash\n\ttestURIUpdate(t, \"http://foo.bar/baz#aaa\", \"#fragment\", \"http://foo.bar/baz#fragment\")\n\n\t// uri without scheme\n\ttestURIUpdate(t, \"https://foo.bar/baz\", \"//aaa.bbb/cc?dd\", \"https://aaa.bbb/cc?dd\")\n\ttestURIUpdate(t, \"http://foo.bar/baz\", \"//aaa.bbb/cc?dd\", \"http://aaa.bbb/cc?dd\")\n}\n\nfunc testURIUpdate(t *testing.T, base, update, result string) {\n\tvar u URI\n\tu.Parse(nil, []byte(base))\n\tu.Update(update)\n\ts := u.String()\n\tassert.DeepEqual(t, result, s)\n}\n\nfunc testURIPathEscape(t *testing.T, path, expectedRequestURI string) {\n\tvar u URI\n\tu.SetPath(path)\n\trequestURI := u.RequestURI()\n\tassert.DeepEqual(t, expectedRequestURI, string(requestURI))\n}\n\nfunc TestDelArgs(t *testing.T) {\n\tvar args Args\n\targs.Set(\"foo\", \"bar\")\n\tassert.DeepEqual(t, string(args.Peek(\"foo\")), \"bar\")\n\targs.Del(\"foo\")\n\tassert.DeepEqual(t, string(args.Peek(\"foo\")), \"\")\n\n\targs.Set(\"foo2\", \"bar2\")\n\tassert.DeepEqual(t, string(args.Peek(\"foo2\")), \"bar2\")\n\targs.DelBytes([]byte(\"foo2\"))\n\tassert.DeepEqual(t, string(args.Peek(\"foo2\")), \"\")\n}\n\nfunc TestURIFullURI(t *testing.T) {\n\tt.Parallel()\n\n\tvar args Args\n\n\t// empty scheme, path and hash\n\ttestURIFullURI(t, \"\", \"foobar.com\", \"\", \"\", &args, \"http://foobar.com/\")\n\n\t// empty scheme and hash\n\ttestURIFullURI(t, \"\", \"aaa.com\", \"/foo/bar\", \"\", &args, \"http://aaa.com/foo/bar\")\n\t// empty hash\n\ttestURIFullURI(t, \"fTP\", \"XXx.com\", \"/foo\", \"\", &args, \"ftp://xxx.com/foo\")\n\n\t// empty args\n\ttestURIFullURI(t, \"https\", \"xx.com\", \"/\", \"aaa\", &args, \"https://xx.com/#aaa\")\n\n\t// non-empty args and non-ASCII path\n\targs.Set(\"foo\", \"bar\")\n\targs.Set(\"xxx\", \"йух\")\n\ttestURIFullURI(t, \"\", \"xxx.com\", \"/тест123\", \"2er\", &args, \"http://xxx.com/%D1%82%D0%B5%D1%81%D1%82123?foo=bar&xxx=%D0%B9%D1%83%D1%85#2er\")\n\n\t// test with empty args and non-empty query string\n\tvar u URI\n\tu.Parse([]byte(\"google.com\"), []byte(\"/foo?bar=baz&baraz#qqqq\"))\n\turi := u.FullURI()\n\texpectedURI := \"http://google.com/foo?bar=baz&baraz#qqqq\"\n\tassert.DeepEqual(t, expectedURI, string(uri))\n}\n\nfunc testURIFullURI(t *testing.T, scheme, host, path, hash string, args *Args, expectedURI string) {\n\tvar u URI\n\n\tu.SetScheme(scheme)\n\tu.SetHost(host)\n\tu.SetPath(path)\n\tu.SetHash(hash)\n\targs.CopyTo(u.QueryArgs())\n\n\turi := u.FullURI()\n\tassert.DeepEqual(t, expectedURI, string(uri))\n}\n\nfunc TestParsePathWindows(t *testing.T) {\n\tt.Parallel()\n\n\ttestParsePathWindows(t, \"/../../../../../foo\", \"/foo\")\n\ttestParsePathWindows(t, \"/..\\\\..\\\\..\\\\..\\\\..\\\\foo\", \"/foo\")\n\ttestParsePathWindows(t, \"/..%5c..%5cfoo\", \"/foo\")\n}\n\nfunc TestURIPathNormalize(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.SkipNow()\n\t}\n\n\tt.Parallel()\n\n\tvar u URI\n\n\t// double slash\n\ttestURIPathNormalize(t, &u, \"/aa//bb\", \"/aa/bb\")\n\n\t// triple slash\n\ttestURIPathNormalize(t, &u, \"/x///y/\", \"/x/y/\")\n\n\t// multi slashes\n\ttestURIPathNormalize(t, &u, \"/abc//de///fg////\", \"/abc/de/fg/\")\n\n\t// encoded slashes\n\ttestURIPathNormalize(t, &u, \"/xxxx%2fyyy%2f%2F%2F\", \"/xxxx/yyy/\")\n\n\t// dotdot\n\ttestURIPathNormalize(t, &u, \"/aaa/..\", \"/\")\n\n\t// dotdot with trailing slash\n\ttestURIPathNormalize(t, &u, \"/xxx/yyy/../\", \"/xxx/\")\n\n\t// multi dotdots\n\ttestURIPathNormalize(t, &u, \"/aaa/bbb/ccc/../../ddd\", \"/aaa/ddd\")\n\n\t// dotdots separated by other data\n\ttestURIPathNormalize(t, &u, \"/a/b/../c/d/../e/..\", \"/a/c/\")\n\n\t// too many dotdots\n\ttestURIPathNormalize(t, &u, \"/aaa/../../../../xxx\", \"/xxx\")\n\ttestURIPathNormalize(t, &u, \"/../../../../../..\", \"/\")\n\ttestURIPathNormalize(t, &u, \"/../../../../../../\", \"/\")\n\n\t// encoded dotdots\n\ttestURIPathNormalize(t, &u, \"/aaa%2Fbbb%2F%2E.%2Fxxx\", \"/aaa/xxx\")\n\n\t// double slash with dotdots\n\ttestURIPathNormalize(t, &u, \"/aaa////..//b\", \"/b\")\n\n\t// fake dotdot\n\ttestURIPathNormalize(t, &u, \"/aaa/..bbb/ccc/..\", \"/aaa/..bbb/\")\n\n\t// single dot\n\ttestURIPathNormalize(t, &u, \"/a/./b/././c/./d.html\", \"/a/b/c/d.html\")\n\ttestURIPathNormalize(t, &u, \"./foo/\", \"/foo/\")\n\ttestURIPathNormalize(t, &u, \"./../.././../../aaa/bbb/../../../././../\", \"/\")\n\ttestURIPathNormalize(t, &u, \"./a/./.././../b/./foo.html\", \"/b/foo.html\")\n}\n\nfunc testURIPathNormalize(t *testing.T, u *URI, requestURI, expectedPath string) {\n\tu.Parse(nil, []byte(requestURI)) //nolint:errcheck\n\tif string(u.Path()) != expectedPath {\n\t\tt.Fatalf(\"Unexpected path %q. Expected %q. requestURI=%q\", u.Path(), expectedPath, requestURI)\n\t}\n}\n\nfunc testParsePathWindows(t *testing.T, path, expectedPath string) {\n\tvar u URI\n\tu.Parse(nil, []byte(path))\n\tparsedPath := u.Path()\n\tif filepath.Separator == '\\\\' && string(parsedPath) != expectedPath {\n\t\tt.Fatalf(\"Unexpected Path: %q. Expected %q\", parsedPath, expectedPath)\n\t}\n}\n\nfunc TestParseHostWithStr(t *testing.T) {\n\texpectUsername := \"username\"\n\texpectPassword := \"password\"\n\n\ttestParseHostWithStr(t, \"username\", \"\", \"\")\n\ttestParseHostWithStr(t, \"username@\", expectUsername, \"\")\n\ttestParseHostWithStr(t, \"username:password@\", expectUsername, expectPassword)\n\ttestParseHostWithStr(t, \":password@\", \"\", expectPassword)\n\ttestParseHostWithStr(t, \":password\", \"\", \"\")\n}\n\nfunc testParseHostWithStr(t *testing.T, host, expectUsername, expectPassword string) {\n\tvar u URI\n\tu.Parse([]byte(host), nil)\n\tassert.DeepEqual(t, expectUsername, string(u.Username()))\n\tassert.DeepEqual(t, expectPassword, string(u.Password()))\n}\n\nfunc TestParseURI(t *testing.T) {\n\texpectURI := \"http://google.com/foo?bar=baz&baraz#qqqq\"\n\turi := string(ParseURI(expectURI).FullURI())\n\tassert.DeepEqual(t, expectURI, uri)\n}\n\nfunc TestSplitHostURI(t *testing.T) {\n\tcases := []struct {\n\t\thost, uri                      []byte\n\t\twantScheme, wantHost, wantPath []byte\n\t}{\n\t\t{\n\t\t\t[]byte(\"example.com\"), []byte(\"/foobar\"),\n\t\t\t[]byte(\"http\"), []byte(\"example.com\"), []byte(\"/foobar\"),\n\t\t},\n\t\t{\n\t\t\t[]byte(\"example2.com\"), []byte(\"http://example2.com\"),\n\t\t\t[]byte(\"http\"), []byte(\"example2.com\"), []byte(\"/\"),\n\t\t},\n\t\t{\n\t\t\t[]byte(\"example2.com\"), []byte(\"http://example3.com\"),\n\t\t\t[]byte(\"http\"), []byte(\"example3.com\"), []byte(\"/\"),\n\t\t},\n\t\t{\n\t\t\t[]byte(\"example3.com\"), []byte(\"https://foobar.com?a=b\"),\n\t\t\t[]byte(\"https\"), []byte(\"foobar.com\"), []byte(\"?a=b\"),\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tgotScheme, gotHost, gotPath := splitHostURI(c.host, c.uri)\n\t\tif !bytes.Equal(gotScheme, c.wantScheme) || !bytes.Equal(gotHost, c.wantHost) || !bytes.Equal(gotPath, c.wantPath) {\n\t\t\tt.Errorf(\"splitHostURI(%q, %q) == (%q, %q, %q), want (%q, %q, %q)\",\n\t\t\t\tc.host, c.uri, gotScheme, gotHost, gotPath, c.wantScheme, c.wantHost, c.wantPath)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/protocol/uri_timing_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport (\n\t\"testing\"\n)\n\nfunc BenchmarkURIParsePath(b *testing.B) {\n\tbenchmarkURIParse(b, \"google.com\", \"/foo/bar\")\n}\n\nfunc BenchmarkURIParsePathQueryString(b *testing.B) {\n\tbenchmarkURIParse(b, \"google.com\", \"/foo/bar?query=string&other=value\")\n}\n\nfunc BenchmarkURIParsePathQueryStringHash(b *testing.B) {\n\tbenchmarkURIParse(b, \"google.com\", \"/foo/bar?query=string&other=value#hashstring\")\n}\n\nfunc BenchmarkURIParseHostname(b *testing.B) {\n\tbenchmarkURIParse(b, \"google.com\", \"http://foobar.com/foo/bar?query=string&other=value#hashstring\")\n}\n\nfunc BenchmarkURIFullURI(b *testing.B) {\n\thost := []byte(\"foobar.com\")\n\trequestURI := []byte(\"/foobar/baz?aaa=bbb&ccc=ddd\")\n\turiLen := len(host) + len(requestURI) + 7\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar u URI\n\t\tu.Parse(host, requestURI)\n\t\tfor pb.Next() {\n\t\t\turi := u.FullURI()\n\t\t\tif len(uri) != uriLen {\n\t\t\t\tb.Fatalf(\"unexpected uri len %d. Expecting %d\", len(uri), uriLen)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc benchmarkURIParse(b *testing.B, host, uri string) {\n\tstrHost, strURI := []byte(host), []byte(uri)\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar u URI\n\t\tfor pb.Next() {\n\t\t\tu.Parse(strHost, strURI)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/protocol/uri_unix.go",
    "content": "//go:build !windows\n\n/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport \"github.com/cloudwego/hertz/pkg/common/hlog\"\n\nfunc addLeadingSlash(dst, src []byte) []byte {\n\t// add leading slash for unix paths\n\tif len(src) == 0 || src[0] != '/' {\n\t\tdst = append(dst, '/')\n\t}\n\n\treturn dst\n}\n\n// checkSchemeWhenCharIsColon check url begin with :\n// Scenarios that handle protocols like \"http:\"\nfunc checkSchemeWhenCharIsColon(i int, rawURL []byte) (scheme, path []byte) {\n\tif i == 0 {\n\t\thlog.Errorf(\"error happened when try to parse the rawURL(%s): missing protocol scheme\", rawURL)\n\t\treturn\n\t}\n\treturn rawURL[:i], rawURL[i+1:]\n}\n"
  },
  {
    "path": "pkg/protocol/uri_unix_test.go",
    "content": "//go:build !windows\n\n/*\n * Copyright 2023 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage protocol\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestGetScheme(t *testing.T) {\n\tscheme, path := getScheme([]byte(\"https://foo.com\"))\n\tassert.DeepEqual(t, \"https\", string(scheme))\n\tassert.DeepEqual(t, \"//foo.com\", string(path))\n\n\tscheme, path = getScheme([]byte(\":\"))\n\tassert.DeepEqual(t, \"\", string(scheme))\n\tassert.DeepEqual(t, \"\", string(path))\n\n\tscheme, path = getScheme([]byte(\"ws://127.0.0.1\"))\n\tassert.DeepEqual(t, \"ws\", string(scheme))\n\tassert.DeepEqual(t, \"//127.0.0.1\", string(path))\n\n\tscheme, path = getScheme([]byte(\"/hertz/demo\"))\n\tassert.DeepEqual(t, \"\", string(scheme))\n\tassert.DeepEqual(t, \"/hertz/demo\", string(path))\n}\n"
  },
  {
    "path": "pkg/protocol/uri_windows.go",
    "content": "//go:build windows\n\n/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage protocol\n\nimport \"github.com/cloudwego/hertz/pkg/common/hlog\"\n\nfunc addLeadingSlash(dst, src []byte) []byte {\n\t// zero length and \"C:/\" case\n\tisDisk := len(src) > 2 && src[1] == ':'\n\tif len(src) == 0 || (!isDisk && src[0] != '/') {\n\t\tdst = append(dst, '/')\n\t}\n\n\treturn dst\n}\n\n// checkSchemeWhenCharIsColon check url begin with :\n// Scenarios that handle protocols like \"http:\"\n// Add the path to the win file, e.g. \"E:\\gopath\", \"E:\\\".\nfunc checkSchemeWhenCharIsColon(i int, rawURL []byte) (scheme, path []byte) {\n\tif i == 0 {\n\t\thlog.Errorf(\"error happened when trying to parse the rawURL(%s): missing protocol scheme\", rawURL)\n\t\treturn\n\t}\n\n\t// case :\\\n\tif i+1 < len(rawURL) && rawURL[i+1] == '\\\\' {\n\t\treturn nil, rawURL\n\t}\n\n\treturn rawURL[:i], rawURL[i+1:]\n}\n"
  },
  {
    "path": "pkg/protocol/uri_windows_test.go",
    "content": "// Copyright 2023 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage protocol\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestURIPathNormalizeIssue86(t *testing.T) {\n\tt.Parallel()\n\n\tvar u URI\n\n\ttestURIPathNormalize(t, &u, `a`, `/a`)\n\ttestURIPathNormalize(t, &u, \"/../../../../../foo\", \"/foo\")\n\ttestURIPathNormalize(t, &u, \"/..\\\\..\\\\..\\\\..\\\\..\\\\\", \"/\")\n\ttestURIPathNormalize(t, &u, \"/..%5c..%5cfoo\", \"/foo\")\n}\n\nfunc TestGetScheme(t *testing.T) {\n\tscheme, path := getScheme([]byte(\"E:\\\\file.go\"))\n\tassert.DeepEqual(t, \"\", string(scheme))\n\tassert.DeepEqual(t, \"E:\\\\file.go\", string(path))\n\n\tscheme, path = getScheme([]byte(\"E:\\\\\"))\n\tassert.DeepEqual(t, \"\", string(scheme))\n\tassert.DeepEqual(t, \"E:\\\\\", string(path))\n\n\tscheme, path = getScheme([]byte(\"https://foo.com\"))\n\tassert.DeepEqual(t, \"https\", string(scheme))\n\tassert.DeepEqual(t, \"//foo.com\", string(path))\n\n\tscheme, path = getScheme([]byte(\"://\"))\n\tassert.DeepEqual(t, \"\", string(scheme))\n\tassert.DeepEqual(t, \"\", string(path))\n\n\tscheme, path = getScheme([]byte(\"ws://127.0.0.1\"))\n\tassert.DeepEqual(t, \"ws\", string(scheme))\n\tassert.DeepEqual(t, \"//127.0.0.1\", string(path))\n}\n"
  },
  {
    "path": "pkg/route/consts/const.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage consts\n\nimport \"math\"\n\nconst AbortIndex int8 = math.MaxInt8 / 2\n"
  },
  {
    "path": "pkg/route/engine.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage route\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/internal/nocopy\"\n\tinternalStats \"github.com/cloudwego/hertz/internal/stats\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/binding\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/render\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/hlog\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/stats\"\n\t\"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo\"\n\t\"github.com/cloudwego/hertz/pkg/common/utils\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/http1/factory\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/suite\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\nconst unknownTransporterName = \"unknown\"\n\nvar (\n\t// will be netpoll.NewTransporter if available, see: netpoll.go\n\tdefaultTransporter = standard.NewTransporter\n\n\terrInitFailed       = errs.NewPrivate(\"engine has been init already\")\n\terrAlreadyRunning   = errs.NewPrivate(\"engine is already running\")\n\terrStatusNotRunning = errs.NewPrivate(\"engine is not running\")\n\n\tdefault404Body = []byte(\"Not Found\")\n\tdefault405Body = []byte(\"Method Not Allowed\")\n\tdefault400Body = []byte(\"Bad Request\")\n\n\trequiredHostBody = []byte(\"missing required Host header\")\n)\n\ntype hijackConn struct {\n\tnetwork.Conn\n\te *Engine\n}\n\ntype CtxCallback func(ctx context.Context)\n\ntype CtxErrCallback func(ctx context.Context) error\n\n// RouteInfo represents a request route's specification which contains method and path and its handler.\ntype RouteInfo struct {\n\tMethod      string\n\tPath        string\n\tHandler     string\n\tHandlerFunc app.HandlerFunc\n}\n\n// RoutesInfo defines a RouteInfo array.\ntype RoutesInfo []RouteInfo\n\ntype Engine struct {\n\tnoCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used\n\n\t// engine name\n\tName       string\n\tserverName atomic.Value\n\n\t// Options for route and protocol server\n\toptions *config.Options\n\n\t// route\n\tRouterGroup\n\ttrees MethodTrees\n\n\tmaxParams uint16\n\n\tallNoMethod app.HandlersChain\n\tallNoRoute  app.HandlersChain\n\tnoRoute     app.HandlersChain\n\tnoMethod    app.HandlersChain\n\n\t// For render HTML\n\tdelims     render.Delims\n\tfuncMap    template.FuncMap\n\thtmlRender render.HTMLRender\n\n\t// NoHijackConnPool will control whether invite pool to acquire/release the hijackConn or not.\n\t// If it is difficult to guarantee that hijackConn will not be closed repeatedly, set it to true.\n\tNoHijackConnPool bool\n\thijackConnPool   sync.Pool\n\t// KeepHijackedConns is an opt-in disable of connection\n\t// close by hertz after connections' HijackHandler returns.\n\t// This allows to save goroutines, e.g. when hertz used to upgrade\n\t// http connections to WS and connection goes to another handler,\n\t// which will close it when needed.\n\tKeepHijackedConns bool\n\n\t// underlying transport\n\ttransport network.Transporter\n\n\t// trace\n\ttracerCtl   tracer.Controller\n\tenableTrace bool\n\n\t// protocol layer management\n\tprotocolSuite         *suite.Config\n\tprotocolServers       map[string]protocol.Server\n\tprotocolStreamServers map[string]protocol.StreamServer\n\n\t// RequestContext pool\n\tctxPool sync.Pool\n\n\t// Function to handle panics recovered from http handlers.\n\t// It should be used to generate an error page and return the http error code\n\t// 500 (Internal Server Error).\n\t// The handler can be used to keep your server from crashing because of\n\t// unrecovered panics.\n\tPanicHandler app.HandlerFunc\n\n\t// ContinueHandler is called after receiving the Expect 100 Continue Header\n\t//\n\t// https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3\n\t// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.1.1\n\t// Using ContinueHandler a server can make decisioning on whether or not\n\t// to read a potentially large request body based on the headers\n\t//\n\t// The default is to automatically read request bodies of Expect 100 Continue requests\n\t// like they are normal requests\n\tContinueHandler func(header *protocol.RequestHeader) bool\n\n\t// Indicates the engine status (Init/Running/Shutdown/Closed).\n\tstatus uint32\n\n\t// Hook functions get triggered sequentially when engine start\n\tOnRun []CtxErrCallback\n\n\t// Hook functions get triggered simultaneously when engine shutdown\n\tOnShutdown []CtxCallback\n\n\t// Custom Functions\n\tclientIPFunc  app.ClientIP\n\tformValueFunc app.FormValueFunc\n\n\t// Custom Binder\n\tbinder binding.Binder\n}\n\nfunc (engine *Engine) IsTraceEnable() bool {\n\treturn engine.enableTrace\n}\n\nfunc (engine *Engine) GetCtxPool() *sync.Pool {\n\treturn &engine.ctxPool\n}\n\nfunc (engine *Engine) GetOptions() *config.Options {\n\treturn engine.options\n}\n\n// SetTransporter only sets the global default value for the transporter.\n// Use WithTransporter during engine creation to set the transporter for the engine.\nfunc SetTransporter(transporter func(options *config.Options) network.Transporter) {\n\tdefaultTransporter = transporter\n}\n\nfunc (engine *Engine) GetTransporterName() (tName string) {\n\treturn getTransporterName(engine.transport)\n}\n\nfunc getTransporterName(transporter network.Transporter) (tName string) {\n\tdefer func() {\n\t\terr := recover()\n\t\tif err != nil || tName == \"\" {\n\t\t\ttName = unknownTransporterName\n\t\t}\n\t}()\n\tt := reflect.ValueOf(transporter).Type().String()\n\ttName = strings.Split(strings.TrimPrefix(t, \"*\"), \".\")[0]\n\treturn tName\n}\n\n// Deprecated: This only get the global default transporter - may not be the real one used by the engine.\n// Use engine.GetTransporterName for the real transporter used.\nfunc GetTransporterName() (tName string) {\n\tdefer func() {\n\t\terr := recover()\n\t\tif err != nil || tName == \"\" {\n\t\t\ttName = unknownTransporterName\n\t\t}\n\t}()\n\tfName := runtime.FuncForPC(reflect.ValueOf(defaultTransporter).Pointer()).Name()\n\tfSlice := strings.Split(fName, \"/\")\n\tname := fSlice[len(fSlice)-1]\n\tfSlice = strings.Split(name, \".\")\n\ttName = fSlice[0]\n\treturn\n}\n\nfunc (engine *Engine) IsStreamRequestBody() bool {\n\treturn engine.options.StreamRequestBody\n}\n\nfunc (engine *Engine) IsRunning() bool {\n\tif atomic.LoadUint32(&engine.status) != statusRunning {\n\t\treturn false\n\t}\n\t// double check listener\n\ttype ListenerIface interface {\n\t\tListener() net.Listener\n\t}\n\tv, ok := engine.transport.(ListenerIface)\n\tif ok {\n\t\treturn v.Listener() != nil\n\t}\n\treturn true // default behavior if no ListenerIface\n}\n\nfunc (engine *Engine) HijackConnHandle(c network.Conn, h app.HijackHandler) {\n\tengine.hijackConnHandler(c, h)\n}\n\nfunc (engine *Engine) GetTracer() tracer.Controller {\n\treturn engine.tracerCtl\n}\n\nconst (\n\t_ uint32 = iota\n\tstatusInitialized\n\tstatusRunning\n\tstatusShutdown\n\tstatusClosed\n)\n\n// NewContext make a pure RequestContext without any http request/response information\n//\n// Set the Request filed before use it for handlers\nfunc (engine *Engine) NewContext() *app.RequestContext {\n\treturn app.NewContext(engine.maxParams)\n}\n\n// Shutdown starts the server's graceful exit by next steps:\n//\n//  1. Trigger OnShutdown hooks concurrently and wait them until wait timeout or finish\n//  2. Close the net listener, which means new connection won't be accepted\n//  3. Wait all connections get closed:\n//     One connection gets closed after reaching out the shorter time of processing\n//     one request (in hand or next incoming), idleTimeout or ExitWaitTime\n//  4. Exit\nfunc (engine *Engine) Shutdown(ctx context.Context) (err error) {\n\tif atomic.LoadUint32(&engine.status) != statusRunning {\n\t\treturn errStatusNotRunning\n\t}\n\tif !atomic.CompareAndSwapUint32(&engine.status, statusRunning, statusShutdown) {\n\t\treturn\n\t}\n\n\topt := engine.GetOptions()\n\thlog.SystemLogger().Infof(\"Begin graceful shutdown, wait at most %s ...\", opt.ExitWaitTimeout)\n\n\tctx, cancel := context.WithTimeout(ctx, opt.ExitWaitTimeout)\n\tdefer cancel()\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tdefer close(ch)\n\t\tengine.executeOnShutdownHooks(ctx)\n\t}()\n\n\tdefer func() {\n\t\t// ensure that the hook is executed until wait timeout or finish\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\thlog.SystemLogger().Infof(\"Execute OnShutdownHooks timeout: error=%v\", ctx.Err())\n\t\t\treturn\n\t\tcase <-ch:\n\t\t\thlog.SystemLogger().Info(\"Execute OnShutdownHooks finish\")\n\t\t\treturn\n\t\t}\n\t}()\n\n\tif opt.Registry != nil {\n\t\tif err = opt.Registry.Deregister(opt.RegistryInfo); err != nil {\n\t\t\thlog.SystemLogger().Errorf(\"Deregister error=%v\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// call transport shutdown\n\tif err := engine.transport.Shutdown(ctx); err != ctx.Err() {\n\t\treturn err\n\t}\n\n\treturn\n}\n\nfunc (engine *Engine) executeOnShutdownHooks(ctx context.Context) {\n\twg := sync.WaitGroup{}\n\tfor i := range engine.OnShutdown {\n\t\twg.Add(1)\n\t\tgo func(index int) {\n\t\t\tdefer wg.Done()\n\t\t\tengine.OnShutdown[index](ctx)\n\t\t}(i)\n\t}\n\twg.Wait()\n}\n\nfunc (engine *Engine) Run() (err error) {\n\tif err = engine.Init(); err != nil {\n\t\treturn err\n\t}\n\n\t// trigger hooks if any\n\tctx := context.Background()\n\tfor i := range engine.OnRun {\n\t\tif err = engine.OnRun[i](ctx); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err = engine.MarkAsRunning(); err != nil {\n\t\treturn err\n\t}\n\tdefer atomic.StoreUint32(&engine.status, statusClosed)\n\n\treturn engine.listenAndServe()\n}\n\nfunc (engine *Engine) Init() error {\n\t// add built-in http1 server by default\n\tif !engine.HasServer(suite.HTTP1) {\n\t\tengine.AddProtocol(suite.HTTP1, factory.NewServerFactory(newHttp1OptionFromEngine(engine)))\n\t}\n\n\tserverMap, streamServerMap, err := engine.protocolSuite.LoadAll(engine)\n\tif err != nil {\n\t\treturn errs.New(err, errs.ErrorTypePrivate, \"LoadAll protocol suite error\")\n\t}\n\n\tengine.protocolServers = serverMap\n\tengine.protocolStreamServers = streamServerMap\n\n\tif engine.alpnEnable() {\n\t\tengine.options.TLS.NextProtos = append(engine.options.TLS.NextProtos, suite.HTTP1)\n\t}\n\n\tif !atomic.CompareAndSwapUint32(&engine.status, 0, statusInitialized) {\n\t\treturn errInitFailed\n\t}\n\treturn nil\n}\n\nfunc (engine *Engine) alpnEnable() bool {\n\treturn engine.options.TLS != nil && engine.options.ALPN\n}\n\nfunc (engine *Engine) listenAndServe() error {\n\thlog.SystemLogger().Infof(\"Using network library=%s\", engine.GetTransporterName())\n\treturn engine.transport.ListenAndServe(engine.onData)\n}\n\nfunc (c *hijackConn) Close() error {\n\tif !c.e.KeepHijackedConns {\n\t\t// when we do not keep hijacked connections,\n\t\t// it is closed in hijackConnHandler.\n\t\treturn nil\n\t}\n\n\tconn := c.Conn\n\tc.e.releaseHijackConn(c)\n\treturn conn.Close()\n}\n\nfunc (engine *Engine) getNextProto(conn network.Conn) (proto string, err error) {\n\tif tlsConn, ok := conn.(network.ConnTLSer); ok {\n\t\tif engine.options.ReadTimeout > 0 {\n\t\t\tif err := conn.SetReadTimeout(engine.options.ReadTimeout); err != nil {\n\t\t\t\thlog.SystemLogger().Errorf(\"BUG: error in SetReadDeadline=%s: error=%s\", engine.options.ReadTimeout, err)\n\t\t\t}\n\t\t}\n\t\terr = tlsConn.Handshake()\n\t\tif err == nil {\n\t\t\tproto = tlsConn.ConnectionState().NegotiatedProtocol\n\t\t}\n\t}\n\treturn\n}\n\nfunc (engine *Engine) onData(c context.Context, conn interface{}) (err error) {\n\tswitch conn := conn.(type) {\n\tcase network.Conn:\n\t\terr = engine.Serve(c, conn)\n\tcase network.StreamConn:\n\t\terr = engine.ServeStream(c, conn)\n\t}\n\treturn\n}\n\nfunc logError(conn network.Conn, err error) {\n\t// Quiet close the connection\n\tif errors.Is(err, errs.ErrShortConnection) || errors.Is(err, errs.ErrIdleTimeout) {\n\t\treturn\n\t}\n\n\t// Do not process the hijack connection error\n\tif errors.Is(err, errs.ErrHijacked) {\n\t\treturn\n\t}\n\n\t// Get remote address\n\trip := \"\"\n\tif addr := conn.RemoteAddr(); addr != nil {\n\t\trip = addr.String()\n\t}\n\n\t// Handle Specific error\n\tif hsp, ok := conn.(network.HandleSpecificError); ok {\n\t\tif hsp.HandleSpecificError(err, rip) {\n\t\t\treturn\n\t\t}\n\t}\n\t// other errors\n\thlog.SystemLogger().Errorf(hlog.EngineErrorFormat, err.Error(), rip)\n}\n\nfunc (engine *Engine) Close() error {\n\tif engine.htmlRender != nil {\n\t\tengine.htmlRender.Close() //nolint:errcheck\n\t}\n\treturn engine.transport.Close()\n}\n\nfunc (engine *Engine) GetServerName() []byte {\n\tv := engine.serverName.Load()\n\tvar serverName []byte\n\tif v == nil {\n\t\tserverName = []byte(engine.Name)\n\t\tif len(serverName) == 0 {\n\t\t\tserverName = bytestr.DefaultServerName\n\t\t}\n\t\tengine.serverName.Store(serverName)\n\t} else {\n\t\tserverName = v.([]byte)\n\t}\n\treturn serverName\n}\n\nfunc (engine *Engine) Serve(c context.Context, conn network.Conn) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tlogError(conn, err)\n\t\t}\n\t\t// always close conn before Serve returns,\n\t\t// some implementations (e.g., netpoll) may reuse conn if not closed\n\t\t_ = conn.Close()\n\t}()\n\n\t// H2C path\n\tif engine.options.H2C {\n\t\t// protocol sniffer\n\t\tbuf, _ := conn.Peek(len(bytestr.StrClientPreface))\n\t\tif bytes.Equal(buf, bytestr.StrClientPreface) && engine.protocolServers[suite.HTTP2] != nil {\n\t\t\treturn engine.protocolServers[suite.HTTP2].Serve(c, conn)\n\t\t}\n\t\thlog.SystemLogger().Warn(\"HTTP2 server is not loaded, request is going to fallback to HTTP1 server\")\n\t}\n\n\t// ALPN path\n\tif engine.options.ALPN && engine.options.TLS != nil {\n\t\tproto, err1 := engine.getNextProto(conn)\n\t\tif err1 != nil {\n\t\t\t// The client closes the connection when handshake. So just ignore it.\n\t\t\tif err1 == io.EOF {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif re, ok := err1.(tls.RecordHeaderError); ok && re.Conn != nil && utils.TLSRecordHeaderLooksLikeHTTP(re.RecordHeader) {\n\t\t\t\tio.WriteString(re.Conn, \"HTTP/1.0 400 Bad Request\\r\\n\\r\\nClient sent an HTTP request to an HTTPS server.\\n\")\n\t\t\t\tre.Conn.Close()\n\t\t\t\treturn re\n\t\t\t}\n\t\t\treturn err1\n\t\t}\n\t\tif server, ok := engine.protocolServers[proto]; ok {\n\t\t\treturn server.Serve(c, conn)\n\t\t}\n\t}\n\n\t// HTTP1 path\n\terr = engine.protocolServers[suite.HTTP1].Serve(c, conn)\n\n\treturn\n}\n\nfunc (engine *Engine) ServeStream(ctx context.Context, conn network.StreamConn) error {\n\t// ALPN path\n\tif engine.options.ALPN && engine.options.TLS != nil {\n\t\tversion := conn.GetVersion()\n\t\tnextProtocol := versionToALNP(version)\n\t\tif server, ok := engine.protocolStreamServers[nextProtocol]; ok {\n\t\t\treturn server.Serve(ctx, conn)\n\t\t}\n\t}\n\n\t// default path\n\tif server, ok := engine.protocolStreamServers[suite.HTTP3]; ok {\n\t\treturn server.Serve(ctx, conn)\n\t}\n\treturn errs.ErrNotSupportProtocol\n}\n\nfunc (engine *Engine) initBinderAndValidator(opt *config.Options) {\n\tif opt.CustomBinder != nil {\n\t\tengine.initCustomBinder(opt.CustomBinder)\n\t\treturn\n\t}\n\tvf := engine.initValidatorFunc(opt)\n\tif opt.BindConfig == nil {\n\t\tc := binding.NewBindConfig()\n\t\tc.ValidatorFunc = vf\n\t\tengine.binder = binding.NewDefaultBinder(c)\n\t\treturn\n\t}\n\tengine.initDefaultBinder(opt.BindConfig, vf)\n}\n\n// initValidator initializes the validator function and returns whether a custom validator was used\nfunc (engine *Engine) initValidatorFunc(opt *config.Options) binding.ValidatorFunc {\n\tcustomValidator := opt.CustomValidator\n\tif customValidator == nil {\n\t\tconf := opt.ValidateConfig               //nolint:staticcheck // Deprecated\n\t\tvc, ok := conf.(*binding.ValidateConfig) //nolint:staticcheck // Deprecated\n\t\tif !ok && conf != nil {\n\t\t\tpanic(fmt.Errorf(\"ValidateConfig type err: %T\", conf))\n\t\t}\n\t\tif vc == nil {\n\t\t\treturn nil\n\t\t}\n\t\tcustomValidator = binding.NewValidator(vc) //nolint:staticcheck // Deprecated\n\t}\n\tswitch v := customValidator.(type) {\n\tcase binding.ValidatorFunc:\n\t\t// ValidatorFunc (preferred approach)\n\t\treturn v\n\n\tcase func(*protocol.Request, interface{}) error:\n\t\t// Function with correct signature, convert to ValidatorFunc\n\t\treturn binding.ValidatorFunc(v)\n\n\tcase binding.StructValidator: //nolint:staticcheck // Deprecated\n\t\t// StructValidator (backwards compatibility)\n\t\treturn binding.MakeValidatorFunc(v)\n\n\tdefault:\n\t\tpanic(fmt.Errorf(\"customized validator type err: %T\", v))\n\t}\n}\n\n// initCustomBinder handles custom binder initialization\nfunc (engine *Engine) initCustomBinder(customBinder interface{}) {\n\tbinder, ok := customBinder.(binding.Binder)\n\tif !ok {\n\t\tpanic(\"customized binder does not implement binding.Binder\")\n\t}\n\tengine.binder = binder\n}\n\n// initDefaultBinder initializes the default binder with optional custom config\nfunc (engine *Engine) initDefaultBinder(bindConfig interface{}, vf binding.ValidatorFunc) {\n\tbConf, ok := bindConfig.(*binding.BindConfig)\n\tif !ok {\n\t\tpanic(\"opt.BindConfig is not the '*binding.BindConfig' type\")\n\t}\n\t// User customized validator has the highest priority\n\tif vf != nil {\n\t\tbConf.ValidatorFunc = vf\n\t}\n\tengine.binder = binding.NewDefaultBinder(bConf)\n}\n\nfunc NewEngine(opt *config.Options) *Engine {\n\tengine := &Engine{\n\t\ttrees: make(MethodTrees, 0, 9),\n\t\tRouterGroup: RouterGroup{\n\t\t\tHandlers: nil,\n\t\t\tbasePath: opt.BasePath,\n\t\t\troot:     true,\n\t\t},\n\t\ttransport:             defaultTransporter(opt),\n\t\ttracerCtl:             &internalStats.Controller{},\n\t\tprotocolServers:       make(map[string]protocol.Server),\n\t\tprotocolStreamServers: make(map[string]protocol.StreamServer),\n\t\tenableTrace:           true,\n\t\toptions:               opt,\n\t}\n\tengine.initBinderAndValidator(opt)\n\tif opt.TransporterNewer != nil {\n\t\tengine.transport = opt.TransporterNewer(opt)\n\t}\n\tengine.RouterGroup.engine = engine\n\n\ttraceLevel := initTrace(engine)\n\n\t// prepare RequestContext pool\n\tengine.ctxPool.New = func() interface{} {\n\t\tctx := engine.allocateContext()\n\t\tif engine.enableTrace {\n\t\t\tti := traceinfo.NewTraceInfo()\n\t\t\tti.Stats().SetLevel(traceLevel)\n\t\t\tctx.SetTraceInfo(ti)\n\t\t}\n\t\treturn ctx\n\t}\n\n\t// Init protocolSuite\n\tengine.protocolSuite = suite.New()\n\n\treturn engine\n}\n\nfunc initTrace(engine *Engine) stats.Level {\n\tfor _, ti := range engine.options.Tracers {\n\t\tif tracer, ok := ti.(tracer.Tracer); ok {\n\t\t\tengine.tracerCtl.Append(tracer)\n\t\t}\n\t}\n\n\tif !engine.tracerCtl.HasTracer() {\n\t\tengine.enableTrace = false\n\t}\n\n\ttraceLevel := stats.LevelDetailed\n\tif tl, ok := engine.options.TraceLevel.(stats.Level); ok {\n\t\ttraceLevel = tl\n\t}\n\treturn traceLevel\n}\n\nfunc debugPrintRoute(httpMethod, absolutePath string, handlers app.HandlersChain) {\n\tnuHandlers := len(handlers)\n\thandlerName := app.GetHandlerName(handlers.Last())\n\tif handlerName == \"\" {\n\t\thandlerName = utils.NameOfFunction(handlers.Last())\n\t}\n\thlog.SystemLogger().Debugf(\"Method=%-6s absolutePath=%-25s --> handlerName=%s (num=%d handlers)\", httpMethod, absolutePath, handlerName, nuHandlers)\n}\n\nfunc (engine *Engine) addRoute(method, path string, handlers app.HandlersChain) {\n\tif len(path) == 0 {\n\t\tpanic(\"path should not be ''\")\n\t}\n\tutils.Assert(path[0] == '/', \"path must begin with '/'\")\n\tutils.Assert(method != \"\", \"HTTP method can not be empty\")\n\tutils.Assert(len(handlers) > 0, \"there must be at least one handler\")\n\n\tif !engine.options.DisablePrintRoute {\n\t\tdebugPrintRoute(method, path, handlers)\n\t}\n\n\tmethodRouter := engine.trees.get(method)\n\tif methodRouter == nil {\n\t\tmethodRouter = &router{method: method, root: &node{}}\n\t\tengine.trees = append(engine.trees, methodRouter)\n\t}\n\tmethodRouter.addRoute(path, handlers)\n\n\t// Update maxParams\n\tif paramsCount := countParams(path); paramsCount > engine.maxParams {\n\t\tengine.maxParams = paramsCount\n\t}\n}\n\nfunc (engine *Engine) PrintRoute(method string) {\n\troot := engine.trees.get(method)\n\tprintNode(root.root, 0)\n}\n\n// debug use\nfunc printNode(node *node, level int) {\n\tfmt.Println(\"node.prefix: \" + node.prefix)\n\tfmt.Println(\"node.ppath: \" + node.ppath)\n\tfmt.Printf(\"level: %#v\\n\\n\", level)\n\tfor i := 0; i < len(node.children); i++ {\n\t\tprintNode(node.children[i], level+1)\n\t}\n}\n\nfunc (engine *Engine) recv(ctx *app.RequestContext) {\n\tif rcv := recover(); rcv != nil {\n\t\tengine.PanicHandler(context.Background(), ctx)\n\t}\n}\n\n// ServeHTTP makes the router implement the Handler interface.\nfunc (engine *Engine) ServeHTTP(c context.Context, ctx *app.RequestContext) {\n\tctx.SetBinder(engine.binder)\n\tif engine.PanicHandler != nil {\n\t\tdefer engine.recv(ctx)\n\t}\n\n\trPath := string(ctx.Request.URI().Path())\n\n\t// align with https://datatracker.ietf.org/doc/html/rfc2616#section-5.2\n\tif len(ctx.Request.Host()) == 0 && ctx.Request.Header.IsHTTP11() && bytesconv.B2s(ctx.Request.Method()) != consts.MethodConnect {\n\t\tctx.SetHandlers(engine.Handlers)\n\t\tserveError(c, ctx, consts.StatusBadRequest, requiredHostBody)\n\t\treturn\n\t}\n\n\thttpMethod := bytesconv.B2s(ctx.Request.Header.Method())\n\tunescape := false\n\tif engine.options.UseRawPath {\n\t\trPath = string(ctx.Request.URI().PathOriginal())\n\t\tunescape = engine.options.UnescapePathValues\n\t}\n\n\tif engine.options.RemoveExtraSlash {\n\t\trPath = utils.CleanPath(rPath)\n\t}\n\n\t// Follow RFC7230#section-5.3\n\tif rPath == \"\" || rPath[0] != '/' {\n\t\tctx.SetHandlers(engine.Handlers)\n\t\tserveError(c, ctx, consts.StatusBadRequest, default400Body)\n\t\treturn\n\t}\n\n\t// if Params is re-assigned in HandlerFunc and the capacity is not enough we need to realloc\n\tmaxParams := int(engine.maxParams)\n\tif cap(ctx.Params) < maxParams {\n\t\tctx.Params = make(param.Params, 0, maxParams)\n\t}\n\n\t// Find root of the tree for the given HTTP method\n\tt := engine.trees\n\tparamsPointer := &ctx.Params\n\tfor i, tl := 0, len(t); i < tl; i++ {\n\t\tif t[i].method != httpMethod {\n\t\t\tcontinue\n\t\t}\n\t\t// Find route in tree\n\t\tvalue := t[i].find(rPath, paramsPointer, unescape)\n\n\t\tif value.handlers != nil {\n\t\t\tctx.SetHandlers(value.handlers)\n\t\t\tctx.SetFullPath(value.fullPath)\n\t\t\tctx.Next(c)\n\t\t\treturn\n\t\t}\n\t\tif httpMethod != consts.MethodConnect && rPath != \"/\" {\n\t\t\tif value.tsr && engine.options.RedirectTrailingSlash {\n\t\t\t\tredirectTrailingSlash(ctx)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif engine.options.RedirectFixedPath && redirectFixedPath(ctx, t[i].root, engine.options.RedirectFixedPath) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\t}\n\n\tif engine.options.HandleMethodNotAllowed {\n\t\tfor _, tree := range engine.trees {\n\t\t\tif tree.method == httpMethod {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif value := tree.find(rPath, paramsPointer, unescape); value.handlers != nil {\n\t\t\t\tctx.SetHandlers(engine.allNoMethod)\n\t\t\t\tserveError(c, ctx, consts.StatusMethodNotAllowed, default405Body)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\tctx.SetHandlers(engine.allNoRoute)\n\tserveError(c, ctx, consts.StatusNotFound, default404Body)\n}\n\nfunc (engine *Engine) allocateContext() *app.RequestContext {\n\tctx := engine.NewContext()\n\tctx.Request.SetMaxKeepBodySize(engine.options.MaxKeepBodySize)\n\tctx.Response.SetMaxKeepBodySize(engine.options.MaxKeepBodySize)\n\tctx.SetClientIPFunc(engine.clientIPFunc)\n\tctx.SetFormValueFunc(engine.formValueFunc)\n\treturn ctx\n}\n\nfunc serveError(c context.Context, ctx *app.RequestContext, code int, defaultMessage []byte) {\n\tctx.SetStatusCode(code)\n\tctx.Next(c)\n\tif ctx.Response.StatusCode() == code {\n\t\t// if body exists(maybe customized by users), leave it alone.\n\t\tif ctx.Response.HasBodyBytes() || ctx.Response.IsBodyStream() {\n\t\t\treturn\n\t\t}\n\t\tctx.Response.Header.Set(\"Content-Type\", \"text/plain\")\n\t\tctx.Response.SetBody(defaultMessage)\n\t}\n}\n\nfunc trailingSlashURL(ts string) string {\n\ttmpURI := ts + \"/\"\n\tif length := len(ts); length > 1 && ts[length-1] == '/' {\n\t\ttmpURI = ts[:length-1]\n\t}\n\treturn tmpURI\n}\n\nfunc redirectTrailingSlash(c *app.RequestContext) {\n\tp := bytesconv.B2s(c.Request.URI().Path())\n\tif prefix := utils.CleanPath(bytesconv.B2s(c.Request.Header.Peek(\"X-Forwarded-Prefix\"))); prefix != \".\" {\n\t\tp = prefix + \"/\" + p\n\t}\n\n\ttmpURI := trailingSlashURL(p)\n\n\tquery := c.Request.URI().QueryString()\n\n\tif len(query) > 0 {\n\t\ttmpURI = tmpURI + \"?\" + bytesconv.B2s(query)\n\t}\n\n\tc.Request.SetRequestURI(tmpURI)\n\tredirectRequest(c)\n}\n\nfunc redirectRequest(c *app.RequestContext) {\n\tcode := consts.StatusMovedPermanently // Permanent redirect, request with GET method\n\tif bytesconv.B2s(c.Request.Header.Method()) != consts.MethodGet {\n\t\tcode = consts.StatusTemporaryRedirect\n\t}\n\n\tc.Redirect(code, c.Request.URI().RequestURI())\n}\n\nfunc redirectFixedPath(c *app.RequestContext, root *node, trailingSlash bool) bool {\n\trPath := bytesconv.B2s(c.Request.URI().Path())\n\tif fixedPath, ok := root.findCaseInsensitivePath(utils.CleanPath(rPath), trailingSlash); ok {\n\t\tc.Request.SetRequestURI(bytesconv.B2s(fixedPath))\n\t\tredirectRequest(c)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// NoRoute adds handlers for NoRoute. It returns a 404 code by default.\nfunc (engine *Engine) NoRoute(handlers ...app.HandlerFunc) {\n\tengine.noRoute = handlers\n\tengine.rebuild404Handlers()\n}\n\n// NoMethod sets the handlers called when the HTTP method does not match.\nfunc (engine *Engine) NoMethod(handlers ...app.HandlerFunc) {\n\tengine.noMethod = handlers\n\tengine.rebuild405Handlers()\n}\n\nfunc (engine *Engine) rebuild404Handlers() {\n\tengine.allNoRoute = engine.combineHandlers(engine.noRoute)\n}\n\nfunc (engine *Engine) rebuild405Handlers() {\n\tengine.allNoMethod = engine.combineHandlers(engine.noMethod)\n}\n\n// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be\n// included in the handlers chain for every single request. Even 404, 405, static files...\n//\n// For example, this is the right place for a logger or error management middleware.\nfunc (engine *Engine) Use(middleware ...app.HandlerFunc) IRoutes {\n\tengine.RouterGroup.Use(middleware...)\n\tengine.rebuild404Handlers()\n\tengine.rebuild405Handlers()\n\treturn engine\n}\n\n// LoadHTMLGlob loads HTML files identified by glob pattern\n// and associates the result with HTML renderer.\nfunc (engine *Engine) LoadHTMLGlob(pattern string) {\n\ttmpl := template.Must(template.New(\"\").\n\t\tDelims(engine.delims.Left, engine.delims.Right).\n\t\tFuncs(engine.funcMap).\n\t\tParseGlob(pattern))\n\n\tif engine.options.AutoReloadRender {\n\t\tfiles, err := filepath.Glob(pattern)\n\t\tif err != nil {\n\t\t\thlog.SystemLogger().Errorf(\"LoadHTMLGlob: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tengine.SetAutoReloadHTMLTemplate(tmpl, files)\n\t\treturn\n\t}\n\n\tengine.SetHTMLTemplate(tmpl)\n}\n\n// LoadHTMLFiles loads a slice of HTML files\n// and associates the result with HTML renderer.\nfunc (engine *Engine) LoadHTMLFiles(files ...string) {\n\ttmpl := template.Must(template.New(\"\").\n\t\tDelims(engine.delims.Left, engine.delims.Right).\n\t\tFuncs(engine.funcMap).\n\t\tParseFiles(files...))\n\n\tif engine.options.AutoReloadRender {\n\t\tengine.SetAutoReloadHTMLTemplate(tmpl, files)\n\t\treturn\n\t}\n\n\tengine.SetHTMLTemplate(tmpl)\n}\n\n// SetHTMLTemplate associate a template with HTML renderer.\nfunc (engine *Engine) SetHTMLTemplate(tmpl *template.Template) {\n\tengine.htmlRender = render.HTMLProduction{Template: tmpl.Funcs(engine.funcMap)}\n}\n\n// SetAutoReloadHTMLTemplate associate a template with HTML renderer.\nfunc (engine *Engine) SetAutoReloadHTMLTemplate(tmpl *template.Template, files []string) {\n\tengine.htmlRender = &render.HTMLDebug{\n\t\tTemplate:        tmpl,\n\t\tFiles:           files,\n\t\tFuncMap:         engine.funcMap,\n\t\tDelims:          engine.delims,\n\t\tRefreshInterval: engine.options.AutoReloadInterval,\n\t}\n}\n\n// SetFuncMap sets the funcMap used for template.funcMap.\nfunc (engine *Engine) SetFuncMap(funcMap template.FuncMap) {\n\tengine.funcMap = funcMap\n}\n\nfunc (engine *Engine) SetClientIPFunc(f app.ClientIP) {\n\tengine.clientIPFunc = f\n}\n\nfunc (engine *Engine) SetFormValueFunc(f app.FormValueFunc) {\n\tengine.formValueFunc = f\n}\n\n// Delims sets template left and right delims and returns an Engine instance.\nfunc (engine *Engine) Delims(left, right string) *Engine {\n\tengine.delims = render.Delims{Left: left, Right: right}\n\treturn engine\n}\n\nfunc (engine *Engine) acquireHijackConn(c network.Conn) *hijackConn {\n\tif engine.NoHijackConnPool {\n\t\treturn &hijackConn{\n\t\t\tConn: c,\n\t\t\te:    engine,\n\t\t}\n\t}\n\tv := engine.hijackConnPool.Get()\n\tif v == nil {\n\t\treturn &hijackConn{\n\t\t\tConn: c,\n\t\t\te:    engine,\n\t\t}\n\t}\n\thjc := v.(*hijackConn)\n\thjc.Conn = c\n\treturn hjc\n}\n\nfunc (engine *Engine) releaseHijackConn(hjc *hijackConn) {\n\tif engine.NoHijackConnPool {\n\t\treturn\n\t}\n\thjc.Conn = nil\n\tengine.hijackConnPool.Put(hjc)\n}\n\nfunc (engine *Engine) hijackConnHandler(c network.Conn, h app.HijackHandler) {\n\thjc := engine.acquireHijackConn(c)\n\th(hjc)\n\n\tif !engine.KeepHijackedConns {\n\t\tc.Close()\n\t\tengine.releaseHijackConn(hjc)\n\t}\n}\n\n// Routes returns a slice of registered routes, including some useful information, such as:\n// the http method, path and the handler name.\nfunc (engine *Engine) Routes() (routes RoutesInfo) {\n\tfor _, tree := range engine.trees {\n\t\troutes = iterate(tree.method, routes, tree.root)\n\t}\n\n\treturn routes\n}\n\nfunc (engine *Engine) AddProtocol(protocol string, factory interface{}) {\n\tengine.protocolSuite.Add(protocol, factory)\n}\n\n// SetAltHeader sets the value of \"Alt-Svc\" header for protocols other than targetProtocol.\nfunc (engine *Engine) SetAltHeader(targetProtocol, altHeaderValue string) {\n\tengine.protocolSuite.SetAltHeader(targetProtocol, altHeaderValue)\n}\n\nfunc (engine *Engine) HasServer(name string) bool {\n\treturn engine.protocolSuite.Get(name) != nil\n}\n\n// iterate iterates the method tree by depth firstly.\nfunc iterate(method string, routes RoutesInfo, root *node) RoutesInfo {\n\tif len(root.handlers) > 0 {\n\t\thandlerFunc := root.handlers.Last()\n\t\troutes = append(routes, RouteInfo{\n\t\t\tMethod:      method,\n\t\t\tPath:        root.ppath,\n\t\t\tHandler:     utils.NameOfFunction(handlerFunc),\n\t\t\tHandlerFunc: handlerFunc,\n\t\t})\n\t}\n\n\tfor _, child := range root.children {\n\t\troutes = iterate(method, routes, child)\n\t}\n\n\tif root.paramChild != nil {\n\t\troutes = iterate(method, routes, root.paramChild)\n\t}\n\n\tif root.anyChild != nil {\n\t\troutes = iterate(method, routes, root.anyChild)\n\t}\n\treturn routes\n}\n\n// for built-in http1 impl only.\nfunc newHttp1OptionFromEngine(engine *Engine) *http1.Option {\n\topt := &http1.Option{\n\t\tStreamRequestBody:             engine.options.StreamRequestBody,\n\t\tGetOnly:                       engine.options.GetOnly,\n\t\tDisablePreParseMultipartForm:  engine.options.DisablePreParseMultipartForm,\n\t\tDisableKeepalive:              engine.options.DisableKeepalive,\n\t\tNoDefaultServerHeader:         engine.options.NoDefaultServerHeader,\n\t\tMaxRequestBodySize:            engine.options.MaxRequestBodySize,\n\t\tMaxHeaderBytes:                engine.options.MaxHeaderBytes,\n\t\tIdleTimeout:                   engine.options.IdleTimeout,\n\t\tReadTimeout:                   engine.options.ReadTimeout,\n\t\tServerName:                    engine.GetServerName(),\n\t\tContinueHandler:               engine.ContinueHandler,\n\t\tTLS:                           engine.options.TLS,\n\t\tHTMLRender:                    engine.htmlRender,\n\t\tEnableTrace:                   engine.IsTraceEnable(),\n\t\tHijackConnHandle:              engine.HijackConnHandle,\n\t\tDisableHeaderNamesNormalizing: engine.options.DisableHeaderNamesNormalizing,\n\t\tNoDefaultDate:                 engine.options.NoDefaultDate,\n\t\tNoDefaultContentType:          engine.options.NoDefaultContentType,\n\t}\n\t// Idle timeout of standard network must not be zero. Set it to -1 seconds if it is zero.\n\t// Due to the different triggering ways of the network library, see the actual use of this value for the detailed reasons.\n\tif opt.IdleTimeout == 0 && engine.GetTransporterName() == \"standard\" {\n\t\topt.IdleTimeout = -1\n\t}\n\treturn opt\n}\n\nfunc versionToALNP(v uint32) string {\n\tif v == network.Version1 || v == network.Version2 {\n\t\treturn suite.HTTP3\n\t}\n\tif v == network.VersionTLS || v == network.VersionDraft29 {\n\t\treturn suite.HTTP3Draft29\n\t}\n\treturn \"\"\n}\n\n// MarkAsRunning will mark the status of the hertz engine as \"running\".\n// Warning: do not call this method by yourself, unless you know what you are doing.\nfunc (engine *Engine) MarkAsRunning() (err error) {\n\tif !atomic.CompareAndSwapUint32(&engine.status, statusInitialized, statusRunning) {\n\t\treturn errAlreadyRunning\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/route/engine_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage route\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cloudwego/hertz/internal/test/mock/binder\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/binding\"\n\t\"github.com/cloudwego/hertz/pkg/app/server/registry\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\terrs \"github.com/cloudwego/hertz/pkg/common/errors\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/mock\"\n\t\"github.com/cloudwego/hertz/pkg/network\"\n\t\"github.com/cloudwego/hertz/pkg/network/standard\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/suite\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\nfunc TestNew_Engine(t *testing.T) {\n\tdefaultTransporter = standard.NewTransporter\n\topt := config.NewOptions([]config.Option{})\n\trouter := NewEngine(opt)\n\tassert.DeepEqual(t, \"standard\", router.GetTransporterName())\n\tassert.DeepEqual(t, \"/\", router.basePath)\n\tassert.DeepEqual(t, router.engine, router)\n\tassert.DeepEqual(t, 0, len(router.Handlers))\n}\n\nfunc TestNew_Engine_WithTransporter(t *testing.T) {\n\tdefaultTransporter = newMockTransporter\n\topt := config.NewOptions([]config.Option{})\n\trouter := NewEngine(opt)\n\tassert.DeepEqual(t, \"route\", router.GetTransporterName())\n\n\tdefaultTransporter = newMockTransporter\n\topt.TransporterNewer = standard.NewTransporter\n\trouter = NewEngine(opt)\n\tassert.DeepEqual(t, \"standard\", router.GetTransporterName())\n\tassert.DeepEqual(t, \"route\", GetTransporterName())\n}\n\nfunc TestGetTransporterName(t *testing.T) {\n\tname := getTransporterName(&fakeTransporter{})\n\tassert.DeepEqual(t, \"route\", name)\n}\n\nfunc TestEngineUnescape(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\troutes := []string{\n\t\t\"/*all\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/search/:query\",\n\t\t\"/info/:user/project/:project\",\n\t\t\"/info/:user\",\n\t}\n\n\tfor _, r := range routes {\n\t\te.GET(r, func(c context.Context, ctx *app.RequestContext) {\n\t\t\tctx.String(consts.StatusOK, ctx.Param(ctx.Query(\"key\")))\n\t\t})\n\t}\n\n\ttestRoutes := []struct {\n\t\troute string\n\t\tkey   string\n\t\twant  string\n\t}{\n\t\t{\"/\", \"\", \"\"},\n\t\t{\"/cmd/%E4%BD%A0%E5%A5%BD/\", \"tool\", \"你好\"},\n\t\t{\"/src/some/%E4%B8%96%E7%95%8C.png\", \"filepath\", \"some/世界.png\"},\n\t\t{\"/info/%E4%BD%A0%E5%A5%BD/project/%E4%B8%96%E7%95%8C\", \"user\", \"你好\"},\n\t\t{\"/info/%E4%BD%A0%E5%A5%BD/project/%E4%B8%96%E7%95%8C\", \"project\", \"世界\"},\n\t}\n\tfor _, tr := range testRoutes {\n\t\tw := performRequest(e, http.MethodGet, tr.route+\"?key=\"+tr.key)\n\t\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\t\tassert.DeepEqual(t, tr.want, w.Body.String())\n\t}\n}\n\nfunc TestEngineUnescapeRaw(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\te.options.UseRawPath = true\n\n\troutes := []string{\n\t\t\"/*all\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/search/:query\",\n\t\t\"/info/:user/project/:project\",\n\t\t\"/info/:user\",\n\t}\n\n\tfor _, r := range routes {\n\t\te.GET(r, func(c context.Context, ctx *app.RequestContext) {\n\t\t\tctx.String(consts.StatusOK, ctx.Param(ctx.Query(\"key\")))\n\t\t})\n\t}\n\n\ttestRoutes := []struct {\n\t\troute string\n\t\tkey   string\n\t\twant  string\n\t}{\n\t\t{\"/\", \"\", \"\"},\n\t\t{\"/cmd/test/\", \"tool\", \"test\"},\n\t\t{\"/src/some/file.png\", \"filepath\", \"some/file.png\"},\n\t\t{\"/src/some/file+test.png\", \"filepath\", \"some/file test.png\"},\n\t\t{\"/src/some/file++++%%%%test.png\", \"filepath\", \"some/file++++%%%%test.png\"},\n\t\t{\"/src/some/file%2Ftest.png\", \"filepath\", \"some/file/test.png\"},\n\t\t{\"/search/someth!ng+in+ünìcodé\", \"query\", \"someth!ng in ünìcodé\"},\n\t\t{\"/info/gordon/project/go\", \"user\", \"gordon\"},\n\t\t{\"/info/gordon/project/go\", \"project\", \"go\"},\n\t\t{\"/info/slash%2Fgordon\", \"user\", \"slash/gordon\"},\n\t\t{\"/info/slash%2Fgordon/project/Project%20%231\", \"user\", \"slash/gordon\"},\n\t\t{\"/info/slash%2Fgordon/project/Project%20%231\", \"project\", \"Project #1\"},\n\t\t{\"/info/slash%%%%\", \"user\", \"slash%%%%\"},\n\t\t{\"/info/slash%%%%2Fgordon/project/Project%%%%20%231\", \"user\", \"slash%%%%2Fgordon\"},\n\t\t{\"/info/slash%%%%2Fgordon/project/Project%%%%20%231\", \"project\", \"Project%%%%20%231\"},\n\t}\n\tfor _, tr := range testRoutes {\n\t\tw := performRequest(e, http.MethodGet, tr.route+\"?key=\"+tr.key)\n\t\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\t\tassert.DeepEqual(t, tr.want, w.Body.String())\n\t}\n}\n\nfunc TestConnectionClose(t *testing.T) {\n\tengine := NewEngine(config.NewOptions(nil))\n\tatomic.StoreUint32(&engine.status, statusRunning)\n\tengine.Init()\n\tengine.GET(\"/foo\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(consts.StatusOK, \"ok\")\n\t})\n\tconn := mock.NewConn(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\nConnection: close\\r\\n\\r\\n\")\n\terr := engine.Serve(context.Background(), conn)\n\tassert.True(t, errors.Is(err, errs.ErrShortConnection))\n}\n\nfunc TestConnectionClose01(t *testing.T) {\n\tengine := NewEngine(config.NewOptions(nil))\n\tatomic.StoreUint32(&engine.status, statusRunning)\n\tengine.Init()\n\tengine.GET(\"/foo\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.SetConnectionClose()\n\t\tctx.String(consts.StatusOK, \"ok\")\n\t})\n\tconn := mock.NewConn(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\terr := engine.Serve(context.Background(), conn)\n\tassert.True(t, errors.Is(err, errs.ErrShortConnection))\n}\n\nfunc TestIdleTimeout(t *testing.T) {\n\tengine := NewEngine(config.NewOptions(nil))\n\tengine.options.IdleTimeout = 0\n\tatomic.StoreUint32(&engine.status, statusRunning)\n\tengine.Init()\n\tengine.GET(\"/foo\", func(c context.Context, ctx *app.RequestContext) {\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\tctx.String(consts.StatusOK, \"ok\")\n\t})\n\n\tconn := mock.NewConn(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\tch := make(chan error)\n\tstartCh := make(chan error)\n\tgo func() {\n\t\t<-startCh\n\t\tch <- engine.Serve(context.Background(), conn)\n\t}()\n\tclose(startCh)\n\tselect {\n\tcase err := <-ch:\n\t\tif err != nil {\n\t\t\tt.Errorf(\"err happened: %s\", err)\n\t\t}\n\t\treturn\n\tcase <-time.Tick(120 * time.Millisecond):\n\t\tt.Errorf(\"timeout! should have been finished in 120ms...\")\n\t}\n}\n\nfunc TestIdleTimeout01(t *testing.T) {\n\tengine := NewEngine(config.NewOptions(nil))\n\tengine.options.IdleTimeout = 1 * time.Second\n\tatomic.StoreUint32(&engine.status, statusRunning)\n\tengine.Init()\n\tatomic.StoreUint32(&engine.status, statusRunning)\n\tengine.GET(\"/foo\", func(c context.Context, ctx *app.RequestContext) {\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\tctx.String(consts.StatusOK, \"ok\")\n\t})\n\n\tconn := mock.NewConn(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\tch := make(chan error)\n\tstartCh := make(chan error)\n\tgo func() {\n\t\t<-startCh\n\t\tch <- engine.Serve(context.Background(), conn)\n\t}()\n\tclose(startCh)\n\tselect {\n\tcase <-ch:\n\t\tt.Errorf(\"cannot return this early! should wait for at least 1s...\")\n\tcase <-time.Tick(1 * time.Second):\n\t\treturn\n\t}\n}\n\nfunc TestIdleTimeout03(t *testing.T) {\n\tengine := NewEngine(config.NewOptions(nil))\n\tengine.options.IdleTimeout = 0\n\tengine.transport = standard.NewTransporter(engine.options)\n\tatomic.StoreUint32(&engine.status, statusRunning)\n\tengine.Init()\n\tatomic.StoreUint32(&engine.status, statusRunning)\n\tengine.GET(\"/foo\", func(c context.Context, ctx *app.RequestContext) {\n\t\ttime.Sleep(50 * time.Millisecond)\n\t\tctx.String(consts.StatusOK, \"ok\")\n\t})\n\n\tconn := mock.NewConn(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\" +\n\t\t\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\nConnection: close\\r\\n\\r\\n\")\n\n\tch := make(chan error)\n\tstartCh := make(chan error)\n\tgo func() {\n\t\t<-startCh\n\t\tch <- engine.Serve(context.Background(), conn)\n\t}()\n\tclose(startCh)\n\tselect {\n\tcase err := <-ch:\n\t\tif !errors.Is(err, errs.ErrShortConnection) {\n\t\t\tt.Errorf(\"err should be ErrShortConnection, but got %s\", err)\n\t\t}\n\t\treturn\n\tcase <-time.Tick(200 * time.Millisecond):\n\t\tt.Errorf(\"timeout! should have been finished in 200ms...\")\n\t}\n}\n\nfunc TestEngine_Routes(t *testing.T) {\n\tengine := NewEngine(config.NewOptions(nil))\n\tengine.GET(\"/\", handlerTest1)\n\tengine.GET(\"/user\", handlerTest2)\n\tengine.GET(\"/user/:name/*action\", handlerTest1)\n\tengine.GET(\"/anonymous1\", func(c context.Context, ctx *app.RequestContext) {}) // TestEngine_Routes.func1\n\tengine.POST(\"/user\", handlerTest2)\n\tengine.POST(\"/user/:name/*action\", handlerTest2)\n\tengine.POST(\"/anonymous2\", func(c context.Context, ctx *app.RequestContext) {}) // TestEngine_Routes.func2\n\tgroup := engine.Group(\"/v1\")\n\t{\n\t\tgroup.GET(\"/user\", handlerTest1)\n\t\tgroup.POST(\"/login\", handlerTest2)\n\t}\n\tengine.Static(\"/static\", \".\")\n\n\tlist := engine.Routes()\n\n\tassert.DeepEqual(t, 11, len(list))\n\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"GET\",\n\t\tPath:    \"/\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/route.handlerTest1\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"GET\",\n\t\tPath:    \"/user\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/route.handlerTest2\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"GET\",\n\t\tPath:    \"/user/:name/*action\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/route.handlerTest1\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"GET\",\n\t\tPath:    \"/v1/user\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/route.handlerTest1\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"GET\",\n\t\tPath:    \"/static/*filepath\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/app.(*fsHandler).handleRequest-fm\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"GET\",\n\t\tPath:    \"/anonymous1\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/route.TestEngine_Routes.func1\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"POST\",\n\t\tPath:    \"/user\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/route.handlerTest2\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"POST\",\n\t\tPath:    \"/user/:name/*action\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/route.handlerTest2\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"POST\",\n\t\tPath:    \"/anonymous2\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/route.TestEngine_Routes.func2\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"POST\",\n\t\tPath:    \"/v1/login\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/route.handlerTest2\",\n\t})\n\tassertRoutePresent(t, list, RouteInfo{\n\t\tMethod:  \"HEAD\",\n\t\tPath:    \"/static/*filepath\",\n\t\tHandler: \"github.com/cloudwego/hertz/pkg/app.(*fsHandler).handleRequest-fm\",\n\t})\n}\n\nfunc handlerTest1(c context.Context, ctx *app.RequestContext) {}\n\nfunc handlerTest2(c context.Context, ctx *app.RequestContext) {}\n\nfunc assertRoutePresent(t *testing.T, gets RoutesInfo, want RouteInfo) {\n\tfor _, get := range gets {\n\t\tif get.Path == want.Path && get.Method == want.Method && get.Handler == want.Handler {\n\t\t\treturn\n\t\t}\n\t}\n\n\tt.Errorf(\"route not found: %v\", want)\n}\n\nfunc TestGetNextProto(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\tconn := &mockConn{}\n\tproto, err := e.getNextProto(conn)\n\tif proto != \"h2\" {\n\t\tt.Errorf(\"unexpected proto: %#v, expected: %#v\", proto, \"h2\")\n\t}\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err.Error())\n\t}\n}\n\nfunc formatAsDate(t time.Time) string {\n\tyear, month, day := t.Date()\n\treturn fmt.Sprintf(\"%d/%02d/%02d\", year, month, day)\n}\n\nfunc TestRenderHtml(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\te.Delims(\"{[{\", \"}]}\")\n\te.SetFuncMap(template.FuncMap{\n\t\t\"formatAsDate\": formatAsDate,\n\t})\n\te.LoadHTMLGlob(\"../common/testdata/template/htmltemplate.html\")\n\te.GET(\"/templateName\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.HTML(http.StatusOK, \"htmltemplate.html\", map[string]interface{}{\n\t\t\t\"now\": time.Date(2017, 0o7, 0o1, 0, 0, 0, 0, time.UTC),\n\t\t})\n\t})\n\trr := performRequest(e, \"GET\", \"/templateName\")\n\tb, _ := ioutil.ReadAll(rr.Body)\n\tassert.DeepEqual(t, consts.StatusOK, rr.Code)\n\tassert.DeepEqual(t, []byte(\"<h1>Date: 2017/07/01</h1>\"), b)\n\tassert.DeepEqual(t, \"text/html; charset=utf-8\", rr.Header().Get(\"Content-Type\"))\n}\n\nfunc TestTransporterName(t *testing.T) {\n\tSetTransporter(standard.NewTransporter)\n\tassert.DeepEqual(t, \"standard\", GetTransporterName())\n\n\tSetTransporter(newMockTransporter)\n\tassert.DeepEqual(t, \"route\", GetTransporterName())\n}\n\nfunc newMockTransporter(options *config.Options) network.Transporter {\n\treturn &mockTransporter{}\n}\n\ntype mockTransporter struct{}\n\nfunc (m *mockTransporter) ListenAndServe(onData network.OnData) (err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockTransporter) Close() error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockTransporter) Shutdown(ctx context.Context) error {\n\tpanic(\"implement me\")\n}\n\nfunc TestRenderHtmlOfGlobWithAutoRender(t *testing.T) {\n\topt := config.NewOptions([]config.Option{})\n\topt.AutoReloadRender = true\n\te := NewEngine(opt)\n\te.Delims(\"{[{\", \"}]}\")\n\te.SetFuncMap(template.FuncMap{\n\t\t\"formatAsDate\": formatAsDate,\n\t})\n\te.LoadHTMLGlob(\"../common/testdata/template/htmltemplate.html\")\n\te.GET(\"/templateName\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.HTML(http.StatusOK, \"htmltemplate.html\", map[string]interface{}{\n\t\t\t\"now\": time.Date(2017, 0o7, 0o1, 0, 0, 0, 0, time.UTC),\n\t\t})\n\t})\n\trr := performRequest(e, \"GET\", \"/templateName\")\n\tb, _ := ioutil.ReadAll(rr.Body)\n\tassert.DeepEqual(t, consts.StatusOK, rr.Code)\n\tassert.DeepEqual(t, []byte(\"<h1>Date: 2017/07/01</h1>\"), b)\n\tassert.DeepEqual(t, \"text/html; charset=utf-8\", rr.Header().Get(\"Content-Type\"))\n}\n\nfunc TestSetClientIPAndSetFormValue(t *testing.T) {\n\topt := config.NewOptions([]config.Option{})\n\te := NewEngine(opt)\n\te.SetClientIPFunc(func(ctx *app.RequestContext) string {\n\t\treturn \"1.1.1.1\"\n\t})\n\te.SetFormValueFunc(func(requestContext *app.RequestContext, s string) []byte {\n\t\treturn []byte(s)\n\t})\n\te.GET(\"/ping\", func(c context.Context, ctx *app.RequestContext) {\n\t\tassert.DeepEqual(t, ctx.ClientIP(), \"1.1.1.1\")\n\t\tassert.DeepEqual(t, string(ctx.FormValue(\"key\")), \"key\")\n\t})\n\n\t_ = performRequest(e, \"GET\", \"/ping\")\n}\n\nfunc TestRenderHtmlOfFilesWithAutoRender(t *testing.T) {\n\topt := config.NewOptions([]config.Option{})\n\topt.AutoReloadRender = true\n\te := NewEngine(opt)\n\te.Delims(\"{[{\", \"}]}\")\n\te.SetFuncMap(template.FuncMap{\n\t\t\"formatAsDate\": formatAsDate,\n\t})\n\te.LoadHTMLFiles(\"../common/testdata/template/htmltemplate.html\")\n\te.GET(\"/templateName\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.HTML(http.StatusOK, \"htmltemplate.html\", map[string]interface{}{\n\t\t\t\"now\": time.Date(2017, 0o7, 0o1, 0, 0, 0, 0, time.UTC),\n\t\t})\n\t})\n\trr := performRequest(e, \"GET\", \"/templateName\")\n\tb, _ := ioutil.ReadAll(rr.Body)\n\tassert.DeepEqual(t, consts.StatusOK, rr.Code)\n\tassert.DeepEqual(t, []byte(\"<h1>Date: 2017/07/01</h1>\"), b)\n\tassert.DeepEqual(t, \"text/html; charset=utf-8\", rr.Header().Get(\"Content-Type\"))\n}\n\nfunc TestSetEngineRun(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\te.Init()\n\tassert.True(t, !e.IsRunning())\n\te.MarkAsRunning()\n\tassert.True(t, e.IsRunning())\n}\n\ntype mockConn struct{}\n\nfunc (m *mockConn) SetWriteTimeout(t time.Duration) error {\n\t// TODO implement me\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) ReadBinary(n int) (p []byte, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Handshake() error {\n\treturn nil\n}\n\nfunc (m *mockConn) ConnectionState() tls.ConnectionState {\n\treturn tls.ConnectionState{\n\t\tNegotiatedProtocol: \"h2\",\n\t}\n}\n\nfunc (m *mockConn) SetReadTimeout(t time.Duration) error {\n\treturn nil\n}\n\nfunc (m *mockConn) Read(b []byte) (n int, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Write(b []byte) (n int, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Close() error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) LocalAddr() net.Addr {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) RemoteAddr() net.Addr {\n\treturn &net.TCPAddr{\n\t\tIP:   net.ParseIP(\"126.0.0.5\"),\n\t\tPort: 8888,\n\t\tZone: \"\",\n\t}\n}\n\nfunc (m *mockConn) SetDeadline(t time.Time) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) SetReadDeadline(t time.Time) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) SetWriteDeadline(t time.Time) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Release() error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Peek(i int) ([]byte, error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Skip(n int) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) ReadByte() (byte, error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Next(i int) ([]byte, error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Len() int {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Malloc(n int) (buf []byte, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) WriteBinary(b []byte) (n int, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m *mockConn) Flush() error {\n\tpanic(\"implement me\")\n}\n\ntype fakeTransporter struct{}\n\nfunc (f *fakeTransporter) Close() error {\n\t// TODO implement me\n\tpanic(\"implement me\")\n}\n\nfunc (f *fakeTransporter) Shutdown(ctx context.Context) error {\n\t// TODO implement me\n\tpanic(\"implement me\")\n}\n\nfunc (f *fakeTransporter) ListenAndServe(onData network.OnData) error {\n\t// TODO implement me\n\tpanic(\"implement me\")\n}\n\ntype mockNonValidator struct{}\n\nfunc (m *mockNonValidator) ValidateStruct(interface{}) error {\n\treturn fmt.Errorf(\"test mock\")\n}\n\nfunc TestInitBinderAndValidator(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tt.Errorf(\"unexpected panic, %v\", r)\n\t\t}\n\t}()\n\topt := config.NewOptions([]config.Option{})\n\tbindConfig := binding.NewBindConfig()\n\tbindConfig.LooseZeroMode = true\n\topt.BindConfig = bindConfig\n\topt.CustomBinder = binder.NewBinder()\n\tmockValidatorFunc := binding.ValidatorFunc(func(_ *protocol.Request, _ interface{}) error {\n\t\treturn errors.New(\"test mock\")\n\t})\n\topt.CustomValidator = mockValidatorFunc\n\tNewEngine(opt)\n\tvalidateConfig := binding.NewValidateConfig()\n\topt.ValidateConfig = validateConfig\n\topt.CustomValidator = nil\n\tNewEngine(opt)\n}\n\nfunc TestInitValidatorPanic(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"expect a panic, but get nil\")\n\t\t}\n\t}()\n\topt := config.NewOptions([]config.Option{})\n\tbindConfig := binding.NewBindConfig()\n\tbindConfig.LooseZeroMode = true\n\topt.BindConfig = bindConfig\n\topt.CustomValidator = &mockNonValidator{}\n\tNewEngine(opt)\n}\n\nfunc TestBindConfig(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\"`\n\t}\n\topt := config.NewOptions([]config.Option{})\n\tbindConfig := binding.NewBindConfig()\n\tbindConfig.LooseZeroMode = false\n\topt.BindConfig = bindConfig\n\te := NewEngine(opt)\n\te.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expect an error\")\n\t\t}\n\t})\n\tperformRequest(e, \"GET\", \"/bind?a=\")\n\n\tbindConfig = binding.NewBindConfig()\n\tbindConfig.LooseZeroMode = true\n\topt.BindConfig = bindConfig\n\te = NewEngine(opt)\n\te.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error\")\n\t\t}\n\t\tassert.DeepEqual(t, 0, req.A)\n\t})\n\tperformRequest(e, \"GET\", \"/bind?a=\")\n}\n\ntype ValidateError struct {\n\tErrType, FailField, Msg string\n}\n\n// Error implements error interface.\nfunc (e *ValidateError) Error() string {\n\tif e.Msg != \"\" {\n\t\treturn e.ErrType + \": expr_path=\" + e.FailField + \", cause=\" + e.Msg\n\t}\n\treturn e.ErrType + \": expr_path=\" + e.FailField + \", cause=invalid\"\n}\n\nfunc TestValidateConfigSetErrorFactory(t *testing.T) {\n\ttype TestValidate struct {\n\t\tB int `query:\"b\" vd:\"$>100\"`\n\t}\n\topt := config.NewOptions([]config.Option{})\n\tCustomValidateErrFunc := func(failField, msg string) error {\n\t\terr := ValidateError{\n\t\t\tErrType:   \"validateErr\",\n\t\t\tFailField: \"[validateFailField]: \" + failField,\n\t\t\tMsg:       \"[validateErrMsg]: \" + msg,\n\t\t}\n\n\t\treturn &err\n\t}\n\n\tvalidateConfig := binding.NewValidateConfig()\n\tvalidateConfig.SetValidatorErrorFactory(CustomValidateErrFunc)\n\topt.ValidateConfig = validateConfig\n\te := NewEngine(opt)\n\te.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req TestValidate\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expect an error\")\n\t\t}\n\t\tassert.DeepEqual(t, \"validateErr: expr_path=[validateFailField]: B, cause=[validateErrMsg]: \", err.Error())\n\t})\n\tperformRequest(e, \"GET\", \"/bind?b=1\")\n}\n\nfunc TestValidateConfigSetErrorFactoryWithBindConfig(t *testing.T) {\n\ttype TestValidate struct {\n\t\tB int `query:\"b\" vd:\"$>100\"`\n\t}\n\topt := config.NewOptions([]config.Option{})\n\tCustomValidateErrFunc := func(failField, msg string) error {\n\t\terr := ValidateError{\n\t\t\tErrType:   \"validateErr\",\n\t\t\tFailField: \"[validateFailField]: \" + failField,\n\t\t\tMsg:       \"[validateErrMsg]: \" + msg,\n\t\t}\n\n\t\treturn &err\n\t}\n\n\tvalidateConfig := binding.NewValidateConfig()\n\tvalidateConfig.SetValidatorErrorFactory(CustomValidateErrFunc)\n\topt.ValidateConfig = validateConfig\n\topt.BindConfig = binding.NewBindConfig()\n\te := NewEngine(opt)\n\te.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req TestValidate\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expect an error\")\n\t\t}\n\t\tassert.DeepEqual(t, \"validateErr: expr_path=[validateFailField]: B, cause=[validateErrMsg]: \", err.Error())\n\t})\n\tperformRequest(e, \"GET\", \"/bind?b=1\")\n}\n\nfunc TestCustomBinder(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\"`\n\t}\n\topt := config.NewOptions([]config.Option{})\n\topt.CustomBinder = binder.NewBinder()\n\te := NewEngine(opt)\n\te.GET(\"/bind\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error\")\n\t\t}\n\t\tassert.NotEqual(t, 2, req.A)\n\t})\n\tperformRequest(e, \"GET\", \"/bind?a=2\")\n}\n\nfunc TestValidateRegValidateFunc(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\" vd:\"f($)\"`\n\t}\n\topt := config.NewOptions([]config.Option{})\n\tvalidateConfig := &binding.ValidateConfig{}\n\tvalidateConfig.MustRegValidateFunc(\"f\", func(args ...interface{}) error {\n\t\treturn fmt.Errorf(\"test error\")\n\t})\n\te := NewEngine(opt)\n\te.GET(\"/validate\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tassert.NotNil(t, err)\n\t\tassert.DeepEqual(t, \"test error\", err.Error())\n\t})\n\tperformRequest(e, \"GET\", \"/validate?a=2\")\n}\n\nfunc TestCustomValidator(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\" vd:\"d($)\"`\n\t}\n\topt := config.NewOptions([]config.Option{})\n\tvalidateConfig := &binding.ValidateConfig{}\n\tvalidateConfig.MustRegValidateFunc(\"d\", func(args ...interface{}) error {\n\t\treturn fmt.Errorf(\"test error\")\n\t})\n\topt.CustomValidator = binding.ValidatorFunc(func(_ *protocol.Request, _ interface{}) error {\n\t\treturn errors.New(\"test mock\")\n\t})\n\te := NewEngine(opt)\n\te.GET(\"/validate\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tassert.NotNil(t, err)\n\t\tassert.DeepEqual(t, \"test mock\", err.Error())\n\t})\n\tperformRequest(e, \"GET\", \"/validate?a=2\")\n}\n\nfunc TestCustomValidatorFunc(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\" vd:\"$>10\"`\n\t}\n\tvalidatorFunc := func(req *protocol.Request, v any) error {\n\t\treturn fmt.Errorf(\"test validator func\")\n\t}\n\topt := config.NewOptions([]config.Option{})\n\topt.CustomValidator = validatorFunc\n\te := NewEngine(opt)\n\te.GET(\"/validate\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tassert.NotNil(t, err)\n\t\tassert.DeepEqual(t, \"test validator func\", err.Error())\n\t})\n\tperformRequest(e, \"GET\", \"/validate?a=2\")\n}\n\nfunc TestWithCustomValidatorFunc(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\" vd:\"$>10\"`\n\t}\n\tvalidatorFunc := func(req *protocol.Request, v any) error {\n\t\treturn fmt.Errorf(\"test with custom validator func\")\n\t}\n\topt := config.NewOptions([]config.Option{})\n\topt.CustomValidator = validatorFunc\n\te := NewEngine(opt)\n\te.GET(\"/validate\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tassert.NotNil(t, err)\n\t\tassert.DeepEqual(t, \"test with custom validator func\", err.Error())\n\t})\n\tperformRequest(e, \"GET\", \"/validate?a=2\")\n}\n\nfunc TestCustomValidatorInvalidType(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatal(\"expected panic for invalid validator type\")\n\t\t}\n\t}()\n\topt := config.NewOptions([]config.Option{})\n\topt.CustomValidator = \"invalid validator type\"\n\tNewEngine(opt)\n}\n\nfunc TestWithCustomValidatorConversion(t *testing.T) {\n\ttype Req struct {\n\t\tA int `query:\"a\" vd:\"$>10\"`\n\t}\n\t// Create a config using the deprecated WithCustomValidator function\n\topt := config.NewOptions([]config.Option{})\n\n\t// Simulate using the WithCustomValidator function\n\twithValidatorOpt := config.Option{F: func(o *config.Options) {\n\t\to.CustomValidator = binding.ValidatorFunc(func(_ *protocol.Request, _ interface{}) error {\n\t\t\treturn errors.New(\"test mock\")\n\t\t})\n\t}}\n\twithValidatorOpt.F(opt)\n\n\t// Verify it was converted to ValidatorFunc\n\t_, isValidatorFunc := opt.CustomValidator.(binding.ValidatorFunc)\n\tassert.True(t, isValidatorFunc)\n\n\te := NewEngine(opt)\n\te.GET(\"/validate\", func(c context.Context, ctx *app.RequestContext) {\n\t\tvar req Req\n\t\terr := ctx.BindAndValidate(&req)\n\t\tassert.NotNil(t, err)\n\t\tassert.DeepEqual(t, \"test mock\", err.Error())\n\t})\n\tperformRequest(e, \"GET\", \"/validate?a=2\")\n}\n\nvar errTestDeregsitry = fmt.Errorf(\"test deregsitry error\")\n\ntype mockDeregsitryErr struct{}\n\nvar _ registry.Registry = &mockDeregsitryErr{}\n\nfunc (e mockDeregsitryErr) Register(*registry.Info) error {\n\treturn nil\n}\n\nfunc (e mockDeregsitryErr) Deregister(*registry.Info) error {\n\treturn errTestDeregsitry\n}\n\ntype mockStandardTransporter struct {\n\tnetwork.Transporter\n}\n\nfunc (m *mockStandardTransporter) Shutdown(ctx context.Context) error {\n\t// FIXME: standard.Transporter mindlessly blocks on ctx.Done()\n\t// This change help tests run faster\n\tnewctx, cancel := context.WithCancel(ctx)\n\tcancel()\n\treturn m.Transporter.Shutdown(newctx)\n}\n\nfunc newMockStandardTransporter(opt *config.Options) network.Transporter {\n\treturn &mockStandardTransporter{standard.NewTransporter(opt)}\n}\n\nfunc TestEngineShutdown(t *testing.T) {\n\topt := config.NewOptions(nil)\n\topt.Addr = \"127.0.0.1:0\"\n\topt.TransporterNewer = newMockStandardTransporter\n\n\tmockCtxCallback := func(ctx context.Context) {\n\t\t// Shutdown adds `ExitWaitTimeout` to the given context\n\t\tdl, ok := ctx.Deadline()\n\t\tassert.Assert(t, ok)\n\t\tassert.Assert(t,\n\t\t\topt.ExitWaitTimeout-time.Until(dl) < 50*time.Millisecond, // runtime schedule latency\n\t\t\topt.ExitWaitTimeout, time.Until(dl))\n\t}\n\n\tvar wg sync.WaitGroup\n\tvar engine *Engine\n\n\trunEngine := func() {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tengine.Run()\n\t\t}()\n\t\t// wait for engine to start\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\tshutdownEngine := func(ctx context.Context, expectErr error, expectStatus uint32) {\n\t\tt.Helper()\n\t\terr := engine.Shutdown(ctx)\n\t\tif expectErr == nil {\n\t\t\tassert.Nil(t, err)\n\t\t} else {\n\t\t\tassert.DeepEqual(t, expectErr, err)\n\t\t}\n\t\tif expectStatus != 0 {\n\t\t\tif expectStatus == statusShutdown {\n\t\t\t\tassert.DeepEqual(t, expectStatus, atomic.LoadUint32(&engine.status))\n\t\t\t\t// make sure engine.Run() returns\n\t\t\t\t// in case registry fails, it blocks\n\t\t\t\tengine.transport.Shutdown(ctx)\n\t\t\t\texpectStatus = statusClosed\n\t\t\t}\n\t\t\twg.Wait() // wait engine.Run() returns\n\t\t}\n\t\tassert.DeepEqual(t, expectStatus, atomic.LoadUint32(&engine.status))\n\t}\n\n\t// case: serve not running error\n\tengine = NewEngine(opt)\n\tshutdownEngine(context.Background(), errStatusNotRunning, 0)\n\n\t// case: serve successfully running and shutdown\n\tengine = NewEngine(opt)\n\tengine.OnShutdown = []CtxCallback{mockCtxCallback}\n\trunEngine()\n\tshutdownEngine(context.Background(), nil, statusClosed)\n\n\t// case: serve successfully running and shutdown with deregistry error\n\tengine = NewEngine(opt)\n\tengine.OnShutdown = []CtxCallback{mockCtxCallback}\n\tengine.options.Registry = &mockDeregsitryErr{}\n\trunEngine()\n\tshutdownEngine(context.Background(), errTestDeregsitry, statusShutdown)\n\tengine.options.Registry = nil\n\n\t// case: ctx cancelled when Shutdown\n\tengine = NewEngine(opt)\n\n\t// make sure callback is in progress but ctx cancelled\n\tengine.OnShutdown = []CtxCallback{func(ctx context.Context) { time.Sleep(50 * time.Millisecond) }}\n\n\trunEngine()\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\tshutdownEngine(ctx, nil, statusClosed)\n}\n\ntype mockStreamer struct{}\n\ntype mockProtocolServer struct{}\n\nfunc (s *mockStreamer) Serve(c context.Context, conn network.StreamConn) error {\n\treturn nil\n}\n\nfunc (s *mockProtocolServer) Serve(c context.Context, conn network.Conn) error {\n\treturn nil\n}\n\ntype mockStreamConn struct {\n\tnetwork.StreamConn\n\tversion string\n}\n\nvar _ network.StreamConn = &mockStreamConn{}\n\nfunc (m *mockStreamConn) GetVersion() uint32 {\n\treturn network.Version1\n}\n\nfunc TestEngineServeStream(t *testing.T) {\n\tengine := &Engine{\n\t\toptions: &config.Options{\n\t\t\tALPN: true,\n\t\t\tTLS:  &tls.Config{},\n\t\t},\n\t\tprotocolStreamServers: map[string]protocol.StreamServer{\n\t\t\tsuite.HTTP3: &mockStreamer{},\n\t\t},\n\t}\n\n\t// Test ALPN path\n\tconn := &mockStreamConn{version: suite.HTTP3}\n\terr := engine.ServeStream(context.Background(), conn)\n\tassert.Nil(t, err)\n\n\t// Test default path\n\tengine.options.ALPN = false\n\tconn = &mockStreamConn{}\n\terr = engine.ServeStream(context.Background(), conn)\n\tassert.Nil(t, err)\n\n\t// Test unsupported protocol\n\tengine.protocolStreamServers = map[string]protocol.StreamServer{}\n\tconn = &mockStreamConn{}\n\terr = engine.ServeStream(context.Background(), conn)\n\tassert.DeepEqual(t, errs.ErrNotSupportProtocol, err)\n}\n\nfunc TestEngineServe(t *testing.T) {\n\tengine := NewEngine(config.NewOptions(nil))\n\tengine.protocolServers[suite.HTTP1] = &mockProtocolServer{}\n\tengine.protocolServers[suite.HTTP2] = &mockProtocolServer{}\n\n\t// test H2C path\n\tctx := context.Background()\n\tconn := mock.NewConn(\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\")\n\tengine.options.H2C = true\n\terr := engine.Serve(ctx, conn)\n\tassert.Nil(t, err)\n\n\t// test ALPN path\n\tctx = context.Background()\n\tconn = mock.NewConn(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\tengine.options.H2C = false\n\tengine.options.ALPN = true\n\tengine.options.TLS = &tls.Config{}\n\terr = engine.Serve(ctx, conn)\n\tassert.Nil(t, err)\n\n\t// test HTTP1 path\n\tengine.options.ALPN = false\n\terr = engine.Serve(ctx, conn)\n\tassert.Nil(t, err)\n}\n\nfunc TestOndata(t *testing.T) {\n\tctx := context.Background()\n\tengine := NewEngine(config.NewOptions(nil))\n\n\t// test stream conn\n\tstreamConn := &mockStreamConn{version: suite.HTTP3}\n\tengine.protocolStreamServers[suite.HTTP3] = &mockStreamer{}\n\terr := engine.onData(ctx, streamConn)\n\tassert.Nil(t, err)\n\n\t// test conn\n\tconn := mock.NewConn(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\tengine.protocolServers[suite.HTTP1] = &mockProtocolServer{}\n\terr = engine.onData(ctx, conn)\n\tassert.Nil(t, err)\n}\n\nfunc TestAcquireHijackConn(t *testing.T) {\n\tengine := &Engine{\n\t\tNoHijackConnPool: false,\n\t}\n\t// test conn pool\n\tconn := mock.NewConn(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\thijackConn := engine.acquireHijackConn(conn)\n\tassert.NotNil(t, hijackConn)\n\tassert.NotNil(t, hijackConn.Conn)\n\tassert.DeepEqual(t, engine, hijackConn.e)\n\tassert.DeepEqual(t, conn, hijackConn.Conn)\n\n\t// test no conn pool\n\tengine.NoHijackConnPool = true\n\thijackConn = engine.acquireHijackConn(conn)\n\tassert.NotNil(t, hijackConn)\n\tassert.NotNil(t, hijackConn.Conn)\n\tassert.DeepEqual(t, engine, hijackConn.e)\n\tassert.DeepEqual(t, conn, hijackConn.Conn)\n}\n\nfunc TestHandleParamsReassignInHandleFunc(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\troutes := []string{\n\t\t\"/:a/:b/:c\",\n\t}\n\tfor _, r := range routes {\n\t\te.GET(r, func(c context.Context, ctx *app.RequestContext) {\n\t\t\tctx.Params = make([]param.Param, 1)\n\t\t\tctx.String(consts.StatusOK, \"\")\n\t\t})\n\t}\n\ttestRoutes := []string{\n\t\t\"/aaa/bbb/ccc\",\n\t\t\"/asd/alskja/alkdjad\",\n\t\t\"/asd/alskja/alkdjad\",\n\t\t\"/asd/alskja/alkdjad\",\n\t\t\"/asd/alskja/alkdjad\",\n\t\t\"/alksjdlakjd/ooo/askda\",\n\t\t\"/alksjdlakjd/ooo/askda\",\n\t\t\"/alksjdlakjd/ooo/askda\",\n\t}\n\tctx := e.ctxPool.Get().(*app.RequestContext)\n\tfor _, tr := range testRoutes {\n\t\tr := protocol.NewRequest(http.MethodGet, tr, nil)\n\t\tr.CopyTo(&ctx.Request)\n\t\te.ServeHTTP(context.Background(), ctx)\n\t\tctx.ResetWithoutConn()\n\t}\n}\n"
  },
  {
    "path": "pkg/route/netpoll.go",
    "content": "// Copyright 2022 CloudWeGo Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n//go:build (amd64 || arm64) && (linux || darwin)\n\npackage route\n\nimport (\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/cloudwego/hertz/pkg/network/netpoll\"\n)\n\nfunc init() {\n\tif v, _ := strconv.ParseBool(os.Getenv(\"HERTZ_NO_NETPOLL\")); !v {\n\t\tdefaultTransporter = netpoll.NewTransporter\n\t}\n}\n"
  },
  {
    "path": "pkg/route/param/param.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage param\n\n// Param is a single URL parameter, consisting of a key and a value.\ntype Param struct {\n\tKey   string\n\tValue string\n}\n\n// Params is a Param-slice, as returned by the router.\n// The slice is ordered, the first URL parameter is also the first slice value.\n// It is therefore safe to read values by the index.\ntype Params []Param\n\n// Get returns the value of the first Param which key matches the given name.\n// If no matching Param is found, an empty string is returned.\nfunc (ps Params) Get(name string) (string, bool) {\n\tfor _, entry := range ps {\n\t\tif entry.Key == name {\n\t\t\treturn entry.Value, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\n// ByName returns the value of the first Param which key matches the given name.\n// If no matching Param is found, an empty string is returned.\nfunc (ps Params) ByName(name string) (va string) {\n\tva, _ = ps.Get(name)\n\treturn\n}\n"
  },
  {
    "path": "pkg/route/routergroup.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage route\n\nimport (\n\t\"context\"\n\t\"path\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n\trConsts \"github.com/cloudwego/hertz/pkg/route/consts\"\n)\n\n// IRouter defines all router handle interface includes single and group router.\ntype IRouter interface {\n\tIRoutes\n\tGroup(string, ...app.HandlerFunc) *RouterGroup\n}\n\n// IRoutes defines all router handle interface.\ntype IRoutes interface {\n\tUse(...app.HandlerFunc) IRoutes\n\tHandle(string, string, ...app.HandlerFunc) IRoutes\n\tAny(string, ...app.HandlerFunc) IRoutes\n\tGET(string, ...app.HandlerFunc) IRoutes\n\tPOST(string, ...app.HandlerFunc) IRoutes\n\tDELETE(string, ...app.HandlerFunc) IRoutes\n\tPATCH(string, ...app.HandlerFunc) IRoutes\n\tPUT(string, ...app.HandlerFunc) IRoutes\n\tOPTIONS(string, ...app.HandlerFunc) IRoutes\n\tHEAD(string, ...app.HandlerFunc) IRoutes\n\tStaticFile(string, string) IRoutes\n\tStatic(string, string) IRoutes\n\tStaticFS(string, *app.FS) IRoutes\n}\n\n// RouterGroup is used internally to configure router, a RouterGroup is associated with\n// a prefix and an array of handlers (middleware).\ntype RouterGroup struct {\n\tHandlers app.HandlersChain\n\tbasePath string\n\tengine   *Engine\n\troot     bool\n}\n\nvar _ IRouter = (*RouterGroup)(nil)\n\n// Use adds middleware to the group, see example code in GitHub.\nfunc (group *RouterGroup) Use(middleware ...app.HandlerFunc) IRoutes {\n\tgroup.Handlers = append(group.Handlers, middleware...)\n\treturn group.returnObj()\n}\n\n// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.\n// For example, all the routes that use a common middleware for authorization could be grouped.\nfunc (group *RouterGroup) Group(relativePath string, handlers ...app.HandlerFunc) *RouterGroup {\n\treturn &RouterGroup{\n\t\tHandlers: group.combineHandlers(handlers),\n\t\tbasePath: group.calculateAbsolutePath(relativePath),\n\t\tengine:   group.engine,\n\t}\n}\n\n// BasePath returns the base path of router group.\n// For example, if v := router.Group(\"/rest/n/v1/api\"), v.BasePath() is \"/rest/n/v1/api\".\nfunc (group *RouterGroup) BasePath() string {\n\treturn group.basePath\n}\n\nfunc (group *RouterGroup) handle(httpMethod, relativePath string, handlers app.HandlersChain) IRoutes {\n\tabsolutePath := group.calculateAbsolutePath(relativePath)\n\thandlers = group.combineHandlers(handlers)\n\tgroup.engine.addRoute(httpMethod, absolutePath, handlers)\n\treturn group.returnObj()\n}\n\nvar upperLetterReg = regexp.MustCompile(\"^[A-Z]+$\")\n\n// Handle registers a new request handle and middleware with the given path and method.\n// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.\n// See the example code in GitHub.\n//\n// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut\n// functions can be used.\n//\n// This function is intended for bulk loading and to allow the usage of less\n// frequently used, non-standardized or custom methods (e.g. for internal\n// communication with a proxy).\nfunc (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...app.HandlerFunc) IRoutes {\n\tif matches := upperLetterReg.MatchString(httpMethod); !matches {\n\t\tpanic(\"http method \" + httpMethod + \" is not valid\")\n\t}\n\treturn group.handle(httpMethod, relativePath, handlers)\n}\n\n// POST is a shortcut for router.Handle(\"POST\", path, handle).\nfunc (group *RouterGroup) POST(relativePath string, handlers ...app.HandlerFunc) IRoutes {\n\treturn group.handle(consts.MethodPost, relativePath, handlers)\n}\n\n// GET is a shortcut for router.Handle(\"GET\", path, handle).\nfunc (group *RouterGroup) GET(relativePath string, handlers ...app.HandlerFunc) IRoutes {\n\treturn group.handle(consts.MethodGet, relativePath, handlers)\n}\n\n// DELETE is a shortcut for router.Handle(\"DELETE\", path, handle).\nfunc (group *RouterGroup) DELETE(relativePath string, handlers ...app.HandlerFunc) IRoutes {\n\treturn group.handle(consts.MethodDelete, relativePath, handlers)\n}\n\n// PATCH is a shortcut for router.Handle(\"PATCH\", path, handle).\nfunc (group *RouterGroup) PATCH(relativePath string, handlers ...app.HandlerFunc) IRoutes {\n\treturn group.handle(consts.MethodPatch, relativePath, handlers)\n}\n\n// PUT is a shortcut for router.Handle(\"PUT\", path, handle).\nfunc (group *RouterGroup) PUT(relativePath string, handlers ...app.HandlerFunc) IRoutes {\n\treturn group.handle(consts.MethodPut, relativePath, handlers)\n}\n\n// OPTIONS is a shortcut for router.Handle(\"OPTIONS\", path, handle).\nfunc (group *RouterGroup) OPTIONS(relativePath string, handlers ...app.HandlerFunc) IRoutes {\n\treturn group.handle(consts.MethodOptions, relativePath, handlers)\n}\n\n// HEAD is a shortcut for router.Handle(\"HEAD\", path, handle).\nfunc (group *RouterGroup) HEAD(relativePath string, handlers ...app.HandlerFunc) IRoutes {\n\treturn group.handle(consts.MethodHead, relativePath, handlers)\n}\n\n// Any registers a route that matches all the HTTP methods.\n// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.\nfunc (group *RouterGroup) Any(relativePath string, handlers ...app.HandlerFunc) IRoutes {\n\tgroup.handle(consts.MethodGet, relativePath, handlers)\n\tgroup.handle(consts.MethodPost, relativePath, handlers)\n\tgroup.handle(consts.MethodPut, relativePath, handlers)\n\tgroup.handle(consts.MethodPatch, relativePath, handlers)\n\tgroup.handle(consts.MethodHead, relativePath, handlers)\n\tgroup.handle(consts.MethodOptions, relativePath, handlers)\n\tgroup.handle(consts.MethodDelete, relativePath, handlers)\n\tgroup.handle(consts.MethodConnect, relativePath, handlers)\n\tgroup.handle(consts.MethodTrace, relativePath, handlers)\n\treturn group.returnObj()\n}\n\n// StaticFile registers a single route in order to Serve a single file of the local filesystem.\n// router.StaticFile(\"favicon.ico\", \"./resources/favicon.ico\")\nfunc (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {\n\tif strings.Contains(relativePath, \":\") || strings.Contains(relativePath, \"*\") {\n\t\tpanic(\"URL parameters can not be used when serving a static file\")\n\t}\n\thandler := func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.File(filepath)\n\t}\n\tgroup.GET(relativePath, handler)\n\tgroup.HEAD(relativePath, handler)\n\treturn group.returnObj()\n}\n\n// Static serves files from the given file system root.\n// To use the operating system's file system implementation,\n// use :\n//\n//\trouter.Static(\"/static\", \"/var/www\")\nfunc (group *RouterGroup) Static(relativePath, root string) IRoutes {\n\treturn group.StaticFS(relativePath, &app.FS{Root: root})\n}\n\n// StaticFS works just like `Static()` but a custom `FS` can be used instead.\nfunc (group *RouterGroup) StaticFS(relativePath string, fs *app.FS) IRoutes {\n\tif strings.Contains(relativePath, \":\") || strings.Contains(relativePath, \"*\") {\n\t\tpanic(\"URL parameters can not be used when serving a static folder\")\n\t}\n\thandler := fs.NewRequestHandler()\n\turlPattern := path.Join(relativePath, \"/*filepath\")\n\n\t// Register GET and HEAD handlers\n\tgroup.GET(urlPattern, handler)\n\tgroup.HEAD(urlPattern, handler)\n\treturn group.returnObj()\n}\n\nfunc (group *RouterGroup) combineHandlers(handlers app.HandlersChain) app.HandlersChain {\n\tfinalSize := len(group.Handlers) + len(handlers)\n\tif finalSize >= int(rConsts.AbortIndex) {\n\t\tpanic(\"too many handlers\")\n\t}\n\tmergedHandlers := make(app.HandlersChain, finalSize)\n\tcopy(mergedHandlers, group.Handlers)\n\tcopy(mergedHandlers[len(group.Handlers):], handlers)\n\treturn mergedHandlers\n}\n\nfunc (group *RouterGroup) calculateAbsolutePath(relativePath string) string {\n\treturn joinPaths(group.basePath, relativePath)\n}\n\nfunc (group *RouterGroup) returnObj() IRoutes {\n\tif group.root {\n\t\treturn group.engine\n\t}\n\treturn group\n}\n\n// GETEX adds a handlerName param. When handler is decorated or handler is an anonymous function,\n// Hertz cannot get handler name directly. In this case, pass handlerName explicitly.\nfunc (group *RouterGroup) GETEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes {\n\tapp.SetHandlerName(handler, handlerName)\n\treturn group.GET(relativePath, handler)\n}\n\n// POSTEX adds a handlerName param. When handler is decorated or handler is an anonymous function,\n// Hertz cannot get handler name directly. In this case, pass handlerName explicitly.\nfunc (group *RouterGroup) POSTEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes {\n\tapp.SetHandlerName(handler, handlerName)\n\treturn group.POST(relativePath, handler)\n}\n\n// PUTEX adds a handlerName param. When handler is decorated or handler is an anonymous function,\n// Hertz cannot get handler name directly. In this case, pass handlerName explicitly.\nfunc (group *RouterGroup) PUTEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes {\n\tapp.SetHandlerName(handler, handlerName)\n\treturn group.PUT(relativePath, handler)\n}\n\n// DELETEEX adds a handlerName param. When handler is decorated or handler is an anonymous function,\n// Hertz cannot get handler name directly. In this case, pass handlerName explicitly.\nfunc (group *RouterGroup) DELETEEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes {\n\tapp.SetHandlerName(handler, handlerName)\n\treturn group.DELETE(relativePath, handler)\n}\n\n// HEADEX adds a handlerName param. When handler is decorated or handler is an anonymous function,\n// Hertz cannot get handler name directly. In this case, pass handlerName explicitly.\nfunc (group *RouterGroup) HEADEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes {\n\tapp.SetHandlerName(handler, handlerName)\n\treturn group.HEAD(relativePath, handler)\n}\n\n// AnyEX adds a handlerName param. When handler is decorated or handler is an anonymous function,\n// Hertz cannot get handler name directly. In this case, pass handlerName explicitly.\nfunc (group *RouterGroup) AnyEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes {\n\tapp.SetHandlerName(handler, handlerName)\n\treturn group.Any(relativePath, handler)\n}\n\n// HandleEX adds a handlerName param. When handler is decorated or handler is an anonymous function,\n// Hertz cannot get handler name directly. In this case, pass handlerName explicitly.\nfunc (group *RouterGroup) HandleEX(httpMethod, relativePath string, handler app.HandlerFunc, handlerName string) IRoutes {\n\tapp.SetHandlerName(handler, handlerName)\n\treturn group.Handle(httpMethod, relativePath, handler)\n}\n\nfunc joinPaths(absolutePath, relativePath string) string {\n\tif relativePath == \"\" {\n\t\treturn absolutePath\n\t}\n\n\tfinalPath := path.Join(absolutePath, relativePath)\n\tappendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/'\n\tif appendSlash {\n\t\treturn finalPath + \"/\"\n\t}\n\treturn finalPath\n}\n\nfunc lastChar(str string) uint8 {\n\tif str == \"\" {\n\t\tpanic(\"The length of the string can't be 0\")\n\t}\n\treturn str[len(str)-1]\n}\n"
  },
  {
    "path": "pkg/route/routergroup_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage route\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n)\n\nfunc TestRouterGroupBasic(t *testing.T) {\n\tcfg := config.NewOptions(nil)\n\trouter := NewEngine(cfg)\n\tgroup := router.Group(\"/hola\", func(c context.Context, ctx *app.RequestContext) {})\n\tgroup.Use(func(c context.Context, ctx *app.RequestContext) {})\n\n\tassert.DeepEqual(t, len(group.Handlers), 2)\n\tassert.DeepEqual(t, \"/hola\", group.BasePath())\n\tassert.DeepEqual(t, router, group.engine)\n\n\tgroup2 := group.Group(\"manu\")\n\tgroup2.Use(func(c context.Context, ctx *app.RequestContext) {}, func(c context.Context, ctx *app.RequestContext) {})\n\n\tassert.DeepEqual(t, len(group2.Handlers), 4)\n\tassert.DeepEqual(t, \"/hola/manu\", group2.BasePath())\n\tassert.DeepEqual(t, router, group2.engine)\n}\n\nfunc TestRouterGroupBasicHandle(t *testing.T) {\n\tperformRequestInGroup(t, http.MethodGet)\n\tperformRequestInGroup(t, http.MethodPost)\n\tperformRequestInGroup(t, http.MethodPut)\n\tperformRequestInGroup(t, http.MethodPatch)\n\tperformRequestInGroup(t, http.MethodDelete)\n\tperformRequestInGroup(t, http.MethodHead)\n\tperformRequestInGroup(t, http.MethodOptions)\n}\n\nfunc performRequestInGroup(t *testing.T, method string) {\n\trouter := NewEngine(config.NewOptions(nil))\n\tv1 := router.Group(\"v1\", func(c context.Context, ctx *app.RequestContext) {})\n\tassert.DeepEqual(t, \"/v1\", v1.BasePath())\n\n\tlogin := v1.Group(\"/login/\", func(c context.Context, ctx *app.RequestContext) {}, func(c context.Context, ctx *app.RequestContext) {})\n\tassert.DeepEqual(t, \"/v1/login/\", login.BasePath())\n\n\thandler := func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(http.StatusBadRequest, \"the method was %s and index %d\", string(ctx.Request.Header.Method()), ctx.GetIndex())\n\t}\n\n\tswitch method {\n\tcase http.MethodGet:\n\t\tv1.GET(\"/test\", handler)\n\t\tlogin.GET(\"/test\", handler)\n\tcase http.MethodPost:\n\t\tv1.POST(\"/test\", handler)\n\t\tlogin.POST(\"/test\", handler)\n\tcase http.MethodPut:\n\t\tv1.PUT(\"/test\", handler)\n\t\tlogin.PUT(\"/test\", handler)\n\tcase http.MethodPatch:\n\t\tv1.PATCH(\"/test\", handler)\n\t\tlogin.PATCH(\"/test\", handler)\n\tcase http.MethodDelete:\n\t\tv1.DELETE(\"/test\", handler)\n\t\tlogin.DELETE(\"/test\", handler)\n\tcase http.MethodHead:\n\t\tv1.HEAD(\"/test\", handler)\n\t\tlogin.HEAD(\"/test\", handler)\n\tcase http.MethodOptions:\n\t\tv1.OPTIONS(\"/test\", handler)\n\t\tlogin.OPTIONS(\"/test\", handler)\n\tdefault:\n\t\tpanic(\"unknown method\")\n\t}\n\n\tw := performRequest(router, method, \"/v1/login/test\")\n\tassert.DeepEqual(t, http.StatusBadRequest, w.Code)\n\tassert.DeepEqual(t, \"the method was \"+method+\" and index 3\", w.Body.String())\n\n\tw = performRequest(router, method, \"/v1/test\")\n\tassert.DeepEqual(t, http.StatusBadRequest, w.Code)\n\tassert.DeepEqual(t, \"the method was \"+method+\" and index 1\", w.Body.String())\n}\n\nfunc TestRouterGroupStatic(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.Static(\"/\", \".\")\n\tw := performRequest(router, \"GET\", \"/engine.go\")\n\tfd, err := os.Open(\"./engine.go\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tassert.DeepEqual(t, http.StatusOK, w.Code)\n\tdefer fd.Close()\n\tcontent, err := ioutil.ReadAll(fd)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tassert.DeepEqual(t, string(content), w.Body.String())\n}\n\nfunc TestRouterGroupStaticFile(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.StaticFile(\"file\", \"./engine.go\")\n\tw := performRequest(router, \"GET\", \"/file\")\n\tassert.DeepEqual(t, http.StatusOK, w.Code)\n\tfd, err := os.Open(\"./engine.go\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer fd.Close()\n\tcontent, err := ioutil.ReadAll(fd)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tassert.DeepEqual(t, string(content), w.Body.String())\n}\n\nfunc TestRouterGroupInvalidStatic(t *testing.T) {\n\trouter := &RouterGroup{\n\t\tHandlers: nil,\n\t\tbasePath: \"/\",\n\t\troot:     true,\n\t}\n\tassert.Panic(t, func() {\n\t\trouter.Static(\"/path/:param\", \"/\")\n\t})\n\n\tassert.Panic(t, func() {\n\t\trouter.Static(\"/path/*param\", \"/\")\n\t})\n}\n\nfunc TestRouterGroupInvalidStaticFile(t *testing.T) {\n\trouter := &RouterGroup{\n\t\tHandlers: nil,\n\t\tbasePath: \"/\",\n\t\troot:     true,\n\t}\n\tassert.Panic(t, func() {\n\t\trouter.StaticFile(\"/path/:param\", \"favicon.ico\")\n\t})\n\n\tassert.Panic(t, func() {\n\t\trouter.StaticFile(\"/path/*param\", \"favicon.ico\")\n\t})\n}\n\nfunc TestRouterGroupTooManyHandlers(t *testing.T) {\n\tengine := NewEngine(config.NewOptions(nil))\n\thandlers1 := make([]app.HandlerFunc, 40)\n\tengine.Use(handlers1...)\n\n\thandlers2 := make([]app.HandlerFunc, 26)\n\tassert.Panic(t, func() {\n\t\tengine.Use(handlers2...)\n\t})\n\tassert.Panic(t, func() {\n\t\tengine.GET(\"/\", handlers2...)\n\t})\n}\n\nfunc TestRouterGroupBadMethod(t *testing.T) {\n\trouter := &RouterGroup{\n\t\tHandlers: nil,\n\t\tbasePath: \"/\",\n\t\troot:     true,\n\t}\n\tassert.Panic(t, func() {\n\t\trouter.Handle(http.MethodGet, \"/\")\n\t})\n\tassert.Panic(t, func() {\n\t\trouter.Handle(\" GET\", \"/\")\n\t})\n\tassert.Panic(t, func() {\n\t\trouter.Handle(\"GET \", \"/\")\n\t})\n\tassert.Panic(t, func() {\n\t\trouter.Handle(\"\", \"/\")\n\t})\n\tassert.Panic(t, func() {\n\t\trouter.Handle(\"PO ST\", \"/\")\n\t})\n\tassert.Panic(t, func() {\n\t\trouter.Handle(\"1GET\", \"/\")\n\t})\n\tassert.Panic(t, func() {\n\t\trouter.Handle(\"PATCh\", \"/\")\n\t})\n}\n\nfunc TestRouterGroupPipeline(t *testing.T) {\n\topt := config.NewOptions([]config.Option{})\n\trouter := NewEngine(opt)\n\ttestRoutesInterface(t, router)\n\n\tv1 := router.Group(\"/v1\")\n\ttestRoutesInterface(t, v1)\n}\n\nfunc testRoutesInterface(t *testing.T, r IRoutes) {\n\thandler := func(c context.Context, ctx *app.RequestContext) {}\n\tassert.DeepEqual(t, r, r.Use(handler))\n\n\tassert.DeepEqual(t, r, r.Handle(http.MethodGet, \"/handler\", handler))\n\tassert.DeepEqual(t, r, r.Any(\"/any\", handler))\n\tassert.DeepEqual(t, r, r.GET(\"/\", handler))\n\tassert.DeepEqual(t, r, r.POST(\"/\", handler))\n\tassert.DeepEqual(t, r, r.DELETE(\"/\", handler))\n\tassert.DeepEqual(t, r, r.PATCH(\"/\", handler))\n\tassert.DeepEqual(t, r, r.PUT(\"/\", handler))\n\tassert.DeepEqual(t, r, r.OPTIONS(\"/\", handler))\n\tassert.DeepEqual(t, r, r.HEAD(\"/\", handler))\n\n\tassert.DeepEqual(t, r, r.StaticFile(\"/file\", \".\"))\n\tassert.DeepEqual(t, r, r.Static(\"/static\", \".\"))\n\tassert.DeepEqual(t, r, r.StaticFS(\"/static2\", &app.FS{}))\n}\n"
  },
  {
    "path": "pkg/route/routes_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage route\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/common/test/assert\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n\t\"github.com/cloudwego/hertz/pkg/protocol/consts\"\n)\n\ntype header struct {\n\tKey   string\n\tValue string\n}\n\nfunc performRequest(e *Engine, method, path string, headers ...header) *httptest.ResponseRecorder {\n\tctx := e.ctxPool.Get().(*app.RequestContext)\n\tctx.HTMLRender = e.htmlRender\n\n\tr := protocol.NewRequest(method, path, nil)\n\tr.CopyTo(&ctx.Request)\n\tfor _, v := range headers {\n\t\tctx.Request.Header.Add(v.Key, v.Value)\n\t}\n\n\te.ServeHTTP(context.Background(), ctx)\n\n\tw := httptest.NewRecorder()\n\th := w.Header()\n\tctx.Response.Header.VisitAll(func(key, value []byte) {\n\t\th.Add(string(key), string(value))\n\t})\n\tw.WriteHeader(ctx.Response.StatusCode())\n\tif _, err := w.Write(ctx.Response.Body()); err != nil {\n\t\tpanic(err.Error())\n\t}\n\tctx.Reset()\n\te.ctxPool.Put(ctx)\n\n\treturn w\n}\n\nfunc testRouteOK(method string, t *testing.T) {\n\tpassed := false\n\tpassedAny := false\n\tr := NewEngine(config.NewOptions(nil))\n\tr.Any(\"/test2\", func(c context.Context, ctx *app.RequestContext) {\n\t\tpassedAny = true\n\t})\n\tr.Handle(method, \"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t\tpassed = true\n\t})\n\n\tw := performRequest(r, method, \"/test\")\n\tassert.DeepEqual(t, true, passed)\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\n\tperformRequest(r, method, \"/test2\")\n\tassert.DeepEqual(t, true, passedAny)\n}\n\n// TestSingleRouteOK tests that POST route is correctly invoked.\nfunc testRouteNotOK(method string, t *testing.T) {\n\tpassed := false\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.Handle(method, \"/test_2\", func(c context.Context, ctx *app.RequestContext) {\n\t\tpassed = true\n\t})\n\n\tw := performRequest(router, method, \"/test\")\n\n\tassert.DeepEqual(t, false, passed)\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n}\n\n// TestSingleRouteOK tests that POST route is correctly invoked.\nfunc testRouteNotOK2(method string, t *testing.T) {\n\tpassed := false\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.options.HandleMethodNotAllowed = true\n\tvar methodRoute string\n\tif method == consts.MethodPost {\n\t\tmethodRoute = consts.MethodGet\n\t} else {\n\t\tmethodRoute = consts.MethodPost\n\t}\n\trouter.Handle(methodRoute, \"/test\", func(c context.Context, ctx *app.RequestContext) {\n\t\tpassed = true\n\t})\n\n\tw := performRequest(router, method, \"/test\")\n\n\tassert.DeepEqual(t, false, passed)\n\tassert.DeepEqual(t, consts.StatusMethodNotAllowed, w.Code)\n}\n\nfunc testRouteNotOK3(method string, t *testing.T) {\n\tpassed := false\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.Handle(\"GET\", \"/api/v:version/product/local/products/list\", func(c context.Context, ctx *app.RequestContext) {\n\t\tpassed = true\n\t})\n\trouter.Handle(\"GET\", \"/api/v:version/product/product_creation/preload_all_categories\", func(c context.Context, ctx *app.RequestContext) {\n\t\tpassed = true\n\t})\n\n\tw := performRequest(router, method, \"/api/v1/product/products/activate\")\n\n\tassert.DeepEqual(t, false, passed)\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n}\n\nfunc TestRouterMethod(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.PUT(\"/hey2\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(consts.StatusOK, \"sup2\")\n\t})\n\n\trouter.PUT(\"/hey\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(consts.StatusOK, \"called\")\n\t})\n\n\trouter.PUT(\"/hey3\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(consts.StatusOK, \"sup3\")\n\t})\n\n\tw := performRequest(router, consts.MethodPut, \"/hey\")\n\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\tassert.DeepEqual(t, \"called\", w.Body.String())\n}\n\nfunc TestRouterGroupRouteOK(t *testing.T) {\n\ttestRouteOK(consts.MethodGet, t)\n\ttestRouteOK(consts.MethodPost, t)\n\ttestRouteOK(consts.MethodPut, t)\n\ttestRouteOK(consts.MethodPatch, t)\n\ttestRouteOK(consts.MethodHead, t)\n\ttestRouteOK(consts.MethodOptions, t)\n\ttestRouteOK(consts.MethodDelete, t)\n\ttestRouteOK(consts.MethodConnect, t)\n\ttestRouteOK(consts.MethodTrace, t)\n}\n\nfunc TestRouteNotOK1(t *testing.T) {\n\ttestRouteNotOK(consts.MethodGet, t)\n\ttestRouteNotOK(consts.MethodPost, t)\n\ttestRouteNotOK(consts.MethodPut, t)\n\ttestRouteNotOK(consts.MethodPatch, t)\n\ttestRouteNotOK(consts.MethodHead, t)\n\ttestRouteNotOK(consts.MethodOptions, t)\n\ttestRouteNotOK(consts.MethodDelete, t)\n\ttestRouteNotOK(consts.MethodConnect, t)\n\ttestRouteNotOK(consts.MethodTrace, t)\n}\n\nfunc TestRouteNotOK2(t *testing.T) {\n\ttestRouteNotOK2(consts.MethodGet, t)\n\ttestRouteNotOK2(consts.MethodPost, t)\n\ttestRouteNotOK2(consts.MethodPut, t)\n\ttestRouteNotOK2(consts.MethodPatch, t)\n\ttestRouteNotOK2(consts.MethodHead, t)\n\ttestRouteNotOK2(consts.MethodOptions, t)\n\ttestRouteNotOK2(consts.MethodDelete, t)\n\ttestRouteNotOK2(consts.MethodConnect, t)\n\ttestRouteNotOK2(consts.MethodTrace, t)\n}\n\nfunc TestRouteNotOK3(t *testing.T) {\n\ttestRouteNotOK3(consts.MethodGet, t)\n\ttestRouteNotOK3(consts.MethodPost, t)\n\ttestRouteNotOK3(consts.MethodPut, t)\n\ttestRouteNotOK3(consts.MethodPatch, t)\n\ttestRouteNotOK3(consts.MethodHead, t)\n\ttestRouteNotOK3(consts.MethodOptions, t)\n\ttestRouteNotOK3(consts.MethodDelete, t)\n\ttestRouteNotOK3(consts.MethodConnect, t)\n\ttestRouteNotOK3(consts.MethodTrace, t)\n}\n\nfunc TestRouteRedirectTrailingSlash(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.options.RedirectFixedPath = false\n\trouter.options.RedirectTrailingSlash = true\n\trouter.GET(\"/path\", func(c context.Context, ctx *app.RequestContext) {})\n\trouter.GET(\"/path2/\", func(c context.Context, ctx *app.RequestContext) {})\n\trouter.POST(\"/path3\", func(c context.Context, ctx *app.RequestContext) {})\n\trouter.PUT(\"/path4/\", func(c context.Context, ctx *app.RequestContext) {})\n\n\tw := performRequest(router, consts.MethodGet, \"/path/\")\n\tassert.DeepEqual(t, \"/path\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)\n\n\tw = performRequest(router, consts.MethodGet, \"/path2\")\n\tassert.DeepEqual(t, \"/path2/\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)\n\n\tw = performRequest(router, consts.MethodPost, \"/path3/\")\n\tassert.DeepEqual(t, \"/path3\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusTemporaryRedirect, w.Code)\n\n\tw = performRequest(router, consts.MethodPut, \"/path4\")\n\tassert.DeepEqual(t, \"/path4/\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusTemporaryRedirect, w.Code)\n\n\tw = performRequest(router, consts.MethodGet, \"/path\")\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\n\tw = performRequest(router, consts.MethodGet, \"/path2/\")\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\n\tw = performRequest(router, consts.MethodPost, \"/path3\")\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\n\tw = performRequest(router, consts.MethodPut, \"/path4/\")\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\n\tw = performRequest(router, consts.MethodGet, \"/path2\", header{Key: \"X-Forwarded-Prefix\", Value: \"/api\"})\n\tassert.DeepEqual(t, \"/api/path2/\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)\n\n\tw = performRequest(router, consts.MethodGet, \"/path2/\", header{Key: \"X-Forwarded-Prefix\", Value: \"/api/\"})\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\n\trouter.options.RedirectTrailingSlash = false\n\n\tw = performRequest(router, consts.MethodGet, \"/path/\")\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n\tw = performRequest(router, consts.MethodGet, \"/path2\")\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n\tw = performRequest(router, consts.MethodPost, \"/path3/\")\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n\tw = performRequest(router, consts.MethodPut, \"/path4\")\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n}\n\nfunc TestRouteRedirectTrailingSlashWithQuery(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.options.RedirectFixedPath = false\n\trouter.options.RedirectTrailingSlash = true\n\trouter.GET(\"/path\", func(c context.Context, ctx *app.RequestContext) {})\n\trouter.GET(\"/path2/\", func(c context.Context, ctx *app.RequestContext) {})\n\n\tw := performRequest(router, consts.MethodGet, \"/path/?offset=2\")\n\tassert.DeepEqual(t, \"/path?offset=2\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)\n\n\tw = performRequest(router, consts.MethodGet, \"/path2?offset=2\")\n\tassert.DeepEqual(t, \"/path2/?offset=2\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)\n}\n\nfunc TestRouteRedirectFixedPath(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.options.RedirectFixedPath = true\n\trouter.options.RedirectTrailingSlash = false\n\n\trouter.GET(\"/path\", func(c context.Context, ctx *app.RequestContext) {})\n\trouter.GET(\"/Path2\", func(c context.Context, ctx *app.RequestContext) {})\n\trouter.POST(\"/PATH3\", func(c context.Context, ctx *app.RequestContext) {})\n\trouter.POST(\"/Path4/\", func(c context.Context, ctx *app.RequestContext) {})\n\n\tw := performRequest(router, consts.MethodGet, \"/PATH\")\n\tassert.DeepEqual(t, \"/path\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)\n\n\tw = performRequest(router, consts.MethodGet, \"/path2\")\n\tassert.DeepEqual(t, \"/Path2\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusMovedPermanently, w.Code)\n\n\tw = performRequest(router, consts.MethodPost, \"/path3\")\n\tassert.DeepEqual(t, \"/PATH3\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusTemporaryRedirect, w.Code)\n\n\tw = performRequest(router, consts.MethodPost, \"/path4\")\n\tassert.DeepEqual(t, \"/Path4/\", w.Header().Get(\"Location\"))\n\tassert.DeepEqual(t, consts.StatusTemporaryRedirect, w.Code)\n}\n\n// TestContextParamsGet tests that a parameter can be parsed from the URL.\nfunc TestRouteParamsByName(t *testing.T) {\n\tname := \"\"\n\tlastName := \"\"\n\twild := \"\"\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.GET(\"/test/:name/:last_name/*wild\", func(c context.Context, ctx *app.RequestContext) {\n\t\tname = ctx.Params.ByName(\"name\")\n\t\tlastName = ctx.Params.ByName(\"last_name\")\n\t\tvar ok bool\n\t\twild, ok = ctx.Params.Get(\"wild\")\n\n\t\tassert.DeepEqual(t, true, ok)\n\t\tassert.DeepEqual(t, name, ctx.Param(\"name\"))\n\t\tassert.DeepEqual(t, name, ctx.Param(\"name\"))\n\t\tassert.DeepEqual(t, lastName, ctx.Param(\"last_name\"))\n\n\t\tassert.DeepEqual(t, \"\", ctx.Param(\"wtf\"))\n\t\tassert.DeepEqual(t, \"\", ctx.Params.ByName(\"wtf\"))\n\n\t\twtf, ok := ctx.Params.Get(\"wtf\")\n\t\tassert.DeepEqual(t, \"\", wtf)\n\t\tassert.False(t, ok)\n\t})\n\n\tw := performRequest(router, consts.MethodGet, \"/test/john/smith/is/super/great\")\n\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\tassert.DeepEqual(t, \"john\", name)\n\tassert.DeepEqual(t, \"smith\", lastName)\n\tassert.DeepEqual(t, \"is/super/great\", wild)\n}\n\n// TestContextParamsGet tests that a parameter can be parsed from the URL even with extra slashes.\nfunc TestRouteParamsByNameWithExtraSlash(t *testing.T) {\n\tname := \"\"\n\tlastName := \"\"\n\twild := \"\"\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.options.RemoveExtraSlash = true\n\trouter.GET(\"/test/:name/:last_name/*wild\", func(c context.Context, ctx *app.RequestContext) {\n\t\tname = ctx.Params.ByName(\"name\")\n\t\tlastName = ctx.Params.ByName(\"last_name\")\n\t\tvar ok bool\n\t\twild, ok = ctx.Params.Get(\"wild\")\n\n\t\tassert.True(t, ok)\n\t\tassert.DeepEqual(t, name, ctx.Param(\"name\"))\n\t\tassert.DeepEqual(t, name, ctx.Param(\"name\"))\n\t\tassert.DeepEqual(t, lastName, ctx.Param(\"last_name\"))\n\n\t\tassert.DeepEqual(t, \"\", ctx.Param(\"wtf\"))\n\t\tassert.DeepEqual(t, \"\", ctx.Params.ByName(\"wtf\"))\n\n\t\twtf, ok := ctx.Params.Get(\"wtf\")\n\t\tassert.DeepEqual(t, \"\", wtf)\n\t\tassert.False(t, ok)\n\t})\n\n\tw := performRequest(router, consts.MethodGet, \"/test//john//smith//is//super//great\")\n\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\tassert.DeepEqual(t, \"john\", name)\n\tassert.DeepEqual(t, \"smith\", lastName)\n\tassert.DeepEqual(t, \"is/super/great\", wild)\n}\n\n// TestHandleStaticFile - ensure the static file handles properly\nfunc TestRouteStaticFile(t *testing.T) {\n\t// SETUP file\n\ttestRoot, _ := os.Getwd()\n\tf, err := ioutil.TempFile(testRoot, \"\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tdefer os.Remove(f.Name())\n\t_, err = f.WriteString(\"Hertz Web Framework\")\n\tassert.Nil(t, err)\n\tf.Close()\n\n\tdir, filename := filepath.Split(f.Name())\n\n\t// SETUP engine\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.StaticFS(\"/using_static\", &app.FS{Root: dir, AcceptByteRange: true, PathRewrite: app.NewPathSlashesStripper(1)})\n\trouter.StaticFile(\"/result\", f.Name())\n\n\tw := performRequest(router, consts.MethodGet, \"/using_static/\"+filename)\n\tw2 := performRequest(router, consts.MethodGet, \"/result\")\n\n\tassert.DeepEqual(t, w, w2)\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\tassert.DeepEqual(t, \"Hertz Web Framework\", w.Body.String())\n\tassert.DeepEqual(t, \"text/plain; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n\n\tw3 := performRequest(router, consts.MethodHead, \"/using_static/\"+filename)\n\tw4 := performRequest(router, consts.MethodHead, \"/result\")\n\n\tassert.DeepEqual(t, w3, w4)\n\tassert.DeepEqual(t, consts.StatusOK, w3.Code)\n}\n\n// TestHandleStaticDir - ensure the root/sub dir handles properly\nfunc TestRouteStaticListingDir(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.StaticFS(\"/\", &app.FS{Root: \"./\", GenerateIndexPages: true})\n\n\tw := performRequest(router, consts.MethodGet, \"/\")\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\n\tassert.True(t, strings.Contains(w.Body.String(), \"engine.go\"))\n\tassert.DeepEqual(t, \"text/html; charset=utf-8\", w.Header().Get(\"Content-Type\"))\n}\n\n// TestHandleHeadToDir - ensure the root/sub dir handles properly\nfunc TestRouteStaticNoListing(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.Static(\"/\", \"./\")\n\n\tw := performRequest(router, consts.MethodGet, \"/\")\n\n\tassert.DeepEqual(t, http.StatusForbidden, w.Code)\n\tassert.False(t, strings.Contains(w.Body.String(), \"engine.go\"))\n}\n\nfunc TestRouterMiddlewareAndStatic(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\tstatic := router.Group(\"/\", func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Response.Header.Set(\"Last-Modified\", \"Mon, 02 Jan 2006 15:04:05 MST\")\n\t\tctx.Response.Header.Set(\"Last-Modified\", \"Mon, 02 Jan 2006 15:04:05 MST\")\n\t\tctx.Response.Header.Set(\"Expires\", \"Mon, 02 Jan 2006 15:04:05 MST\")\n\t\tctx.Response.Header.Set(\"X-Hertz\", \"Hertz Framework\")\n\t})\n\tstatic.Static(\"/\", \"./\")\n\n\tw := performRequest(router, consts.MethodGet, \"/engine.go\")\n\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\tassert.True(t, strings.Contains(w.Body.String(), \"package route\"))\n\t// when Go version <= 1.16, mime.TypeByExtension will return Content-Type='text/plain; charset=utf-8',\n\t// otherwise it will return Content-Type='text/x-go; charset=utf-8'\n\tassert.NotEqual(t, \"\", w.Header().Get(\"Content-Type\"))\n\tassert.NotEqual(t, w.Header().Get(\"Last-Modified\"), \"Mon, 02 Jan 2006 15:04:05 MST\")\n\tassert.DeepEqual(t, \"Mon, 02 Jan 2006 15:04:05 MST\", w.Header().Get(\"Expires\"))\n\tassert.DeepEqual(t, \"Hertz Framework\", w.Header().Get(\"x-Hertz\"))\n}\n\nfunc TestRouteNotAllowedEnabled(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.options.HandleMethodNotAllowed = true\n\trouter.POST(\"/path\", func(c context.Context, ctx *app.RequestContext) {})\n\tw := performRequest(router, consts.MethodGet, \"/path\")\n\tassert.DeepEqual(t, consts.StatusMethodNotAllowed, w.Code)\n\n\trouter.NoMethod(func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(http.StatusTeapot, \"responseText\")\n\t})\n\tw = performRequest(router, consts.MethodGet, \"/path\")\n\tassert.DeepEqual(t, \"responseText\", w.Body.String())\n\tassert.DeepEqual(t, http.StatusTeapot, w.Code)\n}\n\nfunc TestRouteNotAllowedEnabled2(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.options.HandleMethodNotAllowed = true\n\t// add one methodTree to trees\n\trouter.addRoute(consts.MethodPost, \"/\", app.HandlersChain{func(_ context.Context, _ *app.RequestContext) {}})\n\trouter.GET(\"/path2\", func(c context.Context, ctx *app.RequestContext) {})\n\tw := performRequest(router, consts.MethodPost, \"/path2\")\n\tassert.DeepEqual(t, consts.StatusMethodNotAllowed, w.Code)\n}\n\nfunc TestRouteNotAllowedDisabled(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.options.HandleMethodNotAllowed = false\n\trouter.POST(\"/path\", func(c context.Context, ctx *app.RequestContext) {})\n\tw := performRequest(router, consts.MethodGet, \"/path\")\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n\n\trouter.NoMethod(func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(http.StatusTeapot, \"responseText\")\n\t})\n\tw = performRequest(router, consts.MethodGet, \"/path\")\n\tassert.DeepEqual(t, \"Not Found\", w.Body.String())\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n}\n\nfunc TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.options.RemoveExtraSlash = true\n\trouter.GET(\"/path\", func(c context.Context, ctx *app.RequestContext) {})\n\trouter.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {})\n\n\ttestRoutes := []struct {\n\t\troute    string\n\t\tcode     int\n\t\tlocation string\n\t}{\n\t\t{\"/../path\", consts.StatusOK, \"\"},    // CleanPath\n\t\t{\"/nope\", consts.StatusNotFound, \"\"}, // NotFound\n\t}\n\tfor _, tr := range testRoutes {\n\t\tw := performRequest(router, \"GET\", tr.route)\n\t\tassert.DeepEqual(t, tr.code, w.Code)\n\t\tif w.Code != consts.StatusNotFound {\n\t\t\tassert.DeepEqual(t, tr.location, fmt.Sprint(w.Header().Get(\"Location\")))\n\t\t}\n\t}\n}\n\nfunc TestRouterNotFound(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.options.RedirectFixedPath = true\n\trouter.GET(\"/path\", func(c context.Context, ctx *app.RequestContext) {})\n\trouter.GET(\"/dir/\", func(c context.Context, ctx *app.RequestContext) {})\n\trouter.GET(\"/\", func(c context.Context, ctx *app.RequestContext) {})\n\n\ttestRoutes := []struct {\n\t\troute    string\n\t\tcode     int\n\t\tlocation string\n\t}{\n\t\t{\"/path/\", consts.StatusMovedPermanently, \"/path\"},    // TSR -/\n\t\t{\"/dir\", consts.StatusMovedPermanently, \"/dir/\"},      // TSR +/\n\t\t{\"/PATH\", consts.StatusMovedPermanently, \"/path\"},     // Fixed Case\n\t\t{\"/DIR/\", consts.StatusMovedPermanently, \"/dir/\"},     // Fixed Case\n\t\t{\"/PATH/\", consts.StatusMovedPermanently, \"/path\"},    // Fixed Case -/\n\t\t{\"/DIR\", consts.StatusMovedPermanently, \"/dir/\"},      // Fixed Case +/\n\t\t{\"/../path/\", consts.StatusMovedPermanently, \"/path\"}, // Without CleanPath\n\t\t{\"/nope\", consts.StatusNotFound, \"\"},                  // NotFound\n\t}\n\tfor _, tr := range testRoutes {\n\t\tw := performRequest(router, consts.MethodGet, tr.route)\n\t\tassert.DeepEqual(t, tr.code, w.Code)\n\t\tif w.Code != consts.StatusNotFound {\n\t\t\tassert.DeepEqual(t, tr.location, fmt.Sprint(w.Header().Get(\"Location\")))\n\t\t}\n\t}\n\n\t// Test custom not found handler\n\tvar notFound bool\n\trouter.NoRoute(func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.AbortWithStatus(consts.StatusNotFound)\n\t\tnotFound = true\n\t})\n\tw := performRequest(router, consts.MethodGet, \"/nope\")\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n\tassert.True(t, notFound)\n\n\t// Test other method than GET (want 307 instead of 301)\n\trouter.PATCH(\"/path\", func(c context.Context, ctx *app.RequestContext) {})\n\tw = performRequest(router, consts.MethodPatch, \"/path/\")\n\tassert.DeepEqual(t, consts.StatusTemporaryRedirect, w.Code)\n\tassert.DeepEqual(t, \"map[Content-Type:[text/plain; charset=utf-8] Location:[/path]]\", fmt.Sprint(w.Header()))\n\n\t// Test special case where no node for the prefix \"/\" exists\n\trouter = NewEngine(config.NewOptions(nil))\n\trouter.GET(\"/a\", func(c context.Context, ctx *app.RequestContext) {})\n\tw = performRequest(router, consts.MethodGet, \"/\")\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n}\n\nfunc TestRouterStaticFSNotFound(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\trouter.StaticFS(\"/\", &app.FS{Root: \"/thisreallydoesntexist/\"})\n\trouter.NoRoute(func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.String(consts.StatusNotFound, \"non existent\")\n\t})\n\n\tw := performRequest(router, consts.MethodGet, \"/nonexistent\")\n\tassert.DeepEqual(t, \"Cannot open requested path\", w.Body.String())\n\n\tw = performRequest(router, consts.MethodHead, \"/nonexistent\")\n\tassert.DeepEqual(t, \"Cannot open requested path\", w.Body.String())\n}\n\nfunc TestRouterStaticFSFileNotFound(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\n\trouter.StaticFS(\"/\", &app.FS{Root: \".\"})\n\n\tassert.NotPanic(t, func() {\n\t\tperformRequest(router, consts.MethodGet, \"/nonexistent\")\n\t})\n}\n\nfunc TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\n\t// Middleware must be called just only once by per request.\n\tmiddlewareCalledNum := 0\n\trouter.Use(func(c context.Context, ctx *app.RequestContext) {\n\t\tmiddlewareCalledNum++\n\t})\n\n\trouter.StaticFS(\"/\", &app.FS{Root: \"/thisreallydoesntexist/\"})\n\n\t// First access\n\tperformRequest(router, consts.MethodGet, \"/nonexistent\")\n\tassert.DeepEqual(t, 1, middlewareCalledNum)\n\n\t// Second access\n\tperformRequest(router, consts.MethodHead, \"/nonexistent\")\n\tassert.DeepEqual(t, 2, middlewareCalledNum)\n}\n\nfunc TestRouteRawPath(t *testing.T) {\n\troute := NewEngine(config.NewOptions(nil))\n\troute.options.UseRawPath = true\n\n\troute.POST(\"/project/:name/build/:num\", func(c context.Context, ctx *app.RequestContext) {\n\t\tname := ctx.Params.ByName(\"name\")\n\t\tnum := ctx.Params.ByName(\"num\")\n\n\t\tassert.DeepEqual(t, name, ctx.Param(\"name\"))\n\t\tassert.DeepEqual(t, num, ctx.Param(\"num\"))\n\n\t\tassert.DeepEqual(t, \"Some/Other/Project\", name)\n\t\tassert.DeepEqual(t, \"222\", num)\n\t})\n\n\tw := performRequest(route, consts.MethodPost, \"/project/Some%2FOther%2FProject/build/222\")\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n}\n\nfunc TestRouteRawPathNoUnescape(t *testing.T) {\n\troute := NewEngine(config.NewOptions(nil))\n\troute.options.UseRawPath = true\n\troute.options.UnescapePathValues = false\n\n\troute.POST(\"/project/:name/build/:num\", func(c context.Context, ctx *app.RequestContext) {\n\t\tname := ctx.Params.ByName(\"name\")\n\t\tnum := ctx.Params.ByName(\"num\")\n\n\t\tassert.DeepEqual(t, name, ctx.Param(\"name\"))\n\t\tassert.DeepEqual(t, num, ctx.Param(\"num\"))\n\n\t\tassert.DeepEqual(t, \"Some%2FOther%2FProject\", name)\n\t\tassert.DeepEqual(t, \"333\", num)\n\t})\n\n\tw := performRequest(route, consts.MethodPost, \"/project/Some%2FOther%2FProject/build/333\")\n\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n}\n\nfunc TestRouteServeErrorWithWriteHeader(t *testing.T) {\n\troute := NewEngine(config.NewOptions(nil))\n\troute.Use(func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.SetStatusCode(421)\n\t\tctx.Next(c)\n\t})\n\n\tw := performRequest(route, consts.MethodGet, \"/NotFound\")\n\tassert.DeepEqual(t, 421, w.Code)\n\tassert.DeepEqual(t, 0, w.Body.Len())\n}\n\nfunc TestRouteContextHoldsFullPath(t *testing.T) {\n\trouter := NewEngine(config.NewOptions(nil))\n\n\t// Test routes\n\troutes := []string{\n\t\t\"/simple\",\n\t\t\"/project/:name\",\n\t\t\"/\",\n\t\t\"/news/home\",\n\t\t\"/news\",\n\t\t\"/simple-two/one\",\n\t\t\"/simple-two/one-two\",\n\t\t\"/project/:name/build/*params\",\n\t\t\"/project/:name/bui\",\n\t\t\"/user/:id/status\",\n\t\t\"/user/:id\",\n\t\t\"/user/:id/profile\",\n\t}\n\n\tfor _, route := range routes {\n\t\tactualRoute := route\n\t\trouter.GET(route, func(c context.Context, ctx *app.RequestContext) {\n\t\t\t// For each defined route context should contain its full path\n\t\t\tassert.DeepEqual(t, actualRoute, ctx.FullPath())\n\t\t\tctx.AbortWithStatus(consts.StatusOK)\n\t\t})\n\t}\n\n\tfor _, route := range routes {\n\t\tw := performRequest(router, consts.MethodGet, route)\n\t\tassert.DeepEqual(t, consts.StatusOK, w.Code)\n\t}\n\n\t// Test not found\n\trouter.Use(func(c context.Context, ctx *app.RequestContext) {\n\t\t// For not found routes full path is empty\n\t\tassert.DeepEqual(t, \"\", ctx.FullPath())\n\t})\n\n\tw := performRequest(router, consts.MethodGet, \"/not-found\")\n\tassert.DeepEqual(t, consts.StatusNotFound, w.Code)\n}\n\nfunc checkUnusedParamValues(t *testing.T, ctx *app.RequestContext, expectParam map[string]string) {\n\tfor _, p := range ctx.Params {\n\t\tif expectParam == nil {\n\t\t\tt.Errorf(\"pValue '%+v' is set for param name '%v' but we are not expecting it\", p.Value, p.Key)\n\t\t} else if val, ok := expectParam[p.Key]; !ok || val != p.Value {\n\t\t\tt.Errorf(\"'%+v' is set for param name '%v' but we are expecting it with expectParam '%+v'\", p.Value, p.Key, val)\n\t\t}\n\t}\n}\n\nvar handlerFunc = func(route string) app.HandlersChain {\n\treturn app.HandlersChain{func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Set(\"path\", route)\n\t}}\n}\n\nvar handlerHelper = func(route, key string, value int) app.HandlersChain {\n\treturn app.HandlersChain{func(c context.Context, ctx *app.RequestContext) {\n\t\tctx.Set(key, value)\n\t\tctx.Set(\"path\", route)\n\t}}\n}\n\nfunc getHelper(c *app.RequestContext, key string) interface{} {\n\tp, _ := c.Get(key)\n\treturn p\n}\n\nfunc TestRouterStatic(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\tpath := \"/folders/a/files/hertz.gif\"\n\te.addRoute(consts.MethodGet, path, handlerFunc(path))\n\tc := e.NewContext()\n\tc.Request.SetRequestURI(path)\n\tc.Request.Header.SetMethod(consts.MethodGet)\n\te.ServeHTTP(context.Background(), c)\n\tassert.DeepEqual(t, path, getHelper(c, \"path\"))\n}\n\nfunc TestRouterParam(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\te.addRoute(consts.MethodGet, \"/users/:id\", handlerFunc(\"/users/:id\"))\n\n\ttestCases := []struct {\n\t\tname        string\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t}{\n\t\t{\n\t\t\tname:        \"route /users/1 to /users/:id\",\n\t\t\twhenURL:     \"/users/1\",\n\t\t\texpectRoute: \"/users/:id\",\n\t\t\texpectParam: map[string]string{\"id\": \"1\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /users/1/ to /users/:id\",\n\t\t\twhenURL:     \"/users/1/\",\n\t\t\texpectRoute: nil,\n\t\t\texpectParam: nil,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tc := e.NewContext()\n\t\t\tc.Request.SetRequestURI(tc.whenURL)\n\t\t\tc.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), c)\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(c, \"path\"))\n\t\t\tcheckUnusedParamValues(t, c, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterTwoParam(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\te.addRoute(consts.MethodGet, \"/users/:uid/files/:fid\", handlerFunc(\"/users/:uid/files/:fid\"))\n\tctx := e.NewContext()\n\tctx.Request.SetRequestURI(\"/users/1/files/1\")\n\tctx.Request.Header.SetMethod(consts.MethodGet)\n\te.ServeHTTP(context.Background(), ctx)\n\n\tassert.DeepEqual(t, \"1\", ctx.Param(\"uid\"))\n\tassert.DeepEqual(t, \"1\", ctx.Param(\"fid\"))\n}\n\nfunc TestRouterParamWithSlash(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\te.addRoute(consts.MethodGet, \"/a/:b/c/d/:e\", handlerFunc(\"/a/:b/c/d/:e\"))\n\te.addRoute(consts.MethodGet, \"/a/:b/c/:d/:f\", handlerFunc(\"/a/:b/c/:d/:f\"))\n\n\tctx := e.NewContext()\n\tctx.Request.SetRequestURI(\"/a/1/c/d/2/3\")\n\tctx.Request.Header.SetMethod(consts.MethodGet)\n\te.ServeHTTP(context.Background(), ctx)\n\tassert.Nil(t, getHelper(ctx, \"path\"))\n\tassert.DeepEqual(t, consts.StatusNotFound, ctx.Response.StatusCode())\n}\n\nfunc TestRouteMultiLevelBacktracking(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t}{\n\t\t{\n\t\t\tname:        \"route /a/c/df to /a/c/df\",\n\t\t\twhenURL:     \"/a/c/df\",\n\t\t\texpectRoute: \"/a/c/df\",\n\t\t},\n\t\t{\n\t\t\tname:        \"route /a/x/df to /a/:b/c\",\n\t\t\twhenURL:     \"/a/x/c\",\n\t\t\texpectRoute: \"/a/:b/c\",\n\t\t\texpectParam: map[string]string{\"b\": \"x\"},\n\t\t},\n\t\t// {\n\t\t// \tname:        \"route /a/x/f to /a/*/f\",\n\t\t// \twhenURL:     \"/a/x/f\",\n\t\t// \texpectRoute: \"/a/*/f\",\n\t\t// \texpectParam: map[string]string{\"x\": \"x/f\"}, // NOTE: `x` would be probably more suitable\n\t\t// },\n\t\t{\n\t\t\tname:        \"route /b/c/f to /:e/c/f\",\n\t\t\twhenURL:     \"/b/c/f\",\n\t\t\texpectRoute: \"/:e/c/f\",\n\t\t\texpectParam: map[string]string{\"e\": \"b\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /b/c/c to /*x\",\n\t\t\twhenURL:     \"/b/c/c\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"b/c/c\"},\n\t\t},\n\t}\n\n\te := NewEngine(config.NewOptions(nil))\n\n\te.addRoute(consts.MethodGet, \"/a/:b/c\", handlerHelper(\"/a/:b/c\", \"case\", 1))\n\te.addRoute(consts.MethodGet, \"/a/c/d\", handlerHelper(\"/a/c/d\", \"case\", 2))\n\te.addRoute(consts.MethodGet, \"/a/c/df\", handlerHelper(\"/a/c/df\", \"case\", 3))\n\t// e.addRoute(consts.MethodGet, \"/a/*/f\", handlerHelper(\"case\", 4))\n\te.addRoute(consts.MethodGet, \"/:e/c/f\", handlerHelper(\"/:e/c/f\", \"case\", 5))\n\te.addRoute(consts.MethodGet, \"/*x\", handlerHelper(\"/*x\", \"case\", 6))\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouteMultiLevelBacktracking2(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\te.addRoute(consts.MethodGet, \"/a/:b/c\", handlerFunc(\"/a/:b/c\"))\n\te.addRoute(consts.MethodGet, \"/a/c/d\", handlerFunc(\"/a/c/d\"))\n\te.addRoute(consts.MethodGet, \"/a/c/df\", handlerFunc(\"/a/c/df\"))\n\te.addRoute(consts.MethodGet, \"/:e/c/f\", handlerFunc(\"/:e/c/f\"))\n\te.addRoute(consts.MethodGet, \"/*x\", handlerFunc(\"/*x\"))\n\n\ttestCases := []struct {\n\t\tname        string\n\t\twhenURL     string\n\t\texpectRoute string\n\t\texpectParam map[string]string\n\t}{\n\t\t{\n\t\t\tname:        \"route /a/c/df to /a/c/df\",\n\t\t\twhenURL:     \"/a/c/df\",\n\t\t\texpectRoute: \"/a/c/df\",\n\t\t},\n\t\t{\n\t\t\tname:        \"route /a/x/df to /a/:b/c\",\n\t\t\twhenURL:     \"/a/x/c\",\n\t\t\texpectRoute: \"/a/:b/c\",\n\t\t\texpectParam: map[string]string{\"b\": \"x\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /a/c/f to /:e/c/f\",\n\t\t\twhenURL:     \"/a/c/f\",\n\t\t\texpectRoute: \"/:e/c/f\",\n\t\t\texpectParam: map[string]string{\"e\": \"a\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /b/c/f to /:e/c/f\",\n\t\t\twhenURL:     \"/b/c/f\",\n\t\t\texpectRoute: \"/:e/c/f\",\n\t\t\texpectParam: map[string]string{\"e\": \"b\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /b/c/c to /*\",\n\t\t\twhenURL:     \"/b/c/c\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"b/c/c\"},\n\t\t},\n\t\t{ // this traverses `/a/:b/c` and `/:e/c/f` branches and eventually backtracks to `/*`\n\t\t\tname:        \"route /a/c/cf to /*\",\n\t\t\twhenURL:     \"/a/c/cf\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"a/c/cf\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /anyMatch to /*\",\n\t\t\twhenURL:     \"/anyMatch\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"anyMatch\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /anyMatch/withSlash to /*\",\n\t\t\twhenURL:     \"/anyMatch/withSlash\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"anyMatch/withSlash\"},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterBacktrackingFromMultipleParamKinds(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\te.addRoute(consts.MethodGet, \"/*x\", handlerFunc(\"/*x\")) // this can match only path that does not have slash in it\n\te.addRoute(consts.MethodGet, \"/:1/second\", handlerFunc(\"/:1/second\"))\n\te.addRoute(consts.MethodGet, \"/:1/:2\", handlerFunc(\"/:1/:2\")) // this acts as match ANY for all routes that have at least one slash\n\te.addRoute(consts.MethodGet, \"/:1/:2/third\", handlerFunc(\"/:1/:2/third\"))\n\te.addRoute(consts.MethodGet, \"/:1/:2/:3/fourth\", handlerFunc(\"/:1/:2/:3/fourth\"))\n\te.addRoute(consts.MethodGet, \"/:1/:2/:3/:4/fifth\", handlerFunc(\"/:1/:2/:3/:4/fifth\"))\n\n\ttestCases := []struct {\n\t\tname        string\n\t\twhenURL     string\n\t\texpectRoute string\n\t\texpectParam map[string]string\n\t}{\n\t\t{\n\t\t\tname:        \"route /first to /*\",\n\t\t\twhenURL:     \"/first\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"first\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /first/second to /:1/second\",\n\t\t\twhenURL:     \"/first/second\",\n\t\t\texpectRoute: \"/:1/second\",\n\t\t\texpectParam: map[string]string{\"1\": \"first\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /first/second-new to /:1/:2\",\n\t\t\twhenURL:     \"/first/second-new\",\n\t\t\texpectRoute: \"/:1/:2\",\n\t\t\texpectParam: map[string]string{\n\t\t\t\t\"1\": \"first\",\n\t\t\t\t\"2\": \"second-new\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /first/second/ to /:1/:2\",\n\t\t\twhenURL:     \"/first/second/\",\n\t\t\texpectRoute: \"/*x\", // \"/:1/:2\",\n\t\t\texpectParam: map[string]string{\"x\": \"first/second/\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /first/second/third/fourth/fifth/nope to /:1/:2\",\n\t\t\twhenURL:     \"/first/second/third/fourth/fifth/nope\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"first/second/third/fourth/fifth/nope\"},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterParamStaticConflict(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\tg := e.Group(\"/g\")\n\tg.GET(\"/skills\", handlerFunc(\"/g/skills\")...)\n\tg.GET(\"/status\", handlerFunc(\"/g/status\")...)\n\tg.GET(\"/:name\", handlerFunc(\"/g/:name\")...)\n\n\ttestCases := []struct {\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t}{\n\t\t{\n\t\t\twhenURL:     \"/g/s\",\n\t\t\texpectRoute: \"/g/:name\",\n\t\t\texpectParam: map[string]string{\"name\": \"s\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/g/status\",\n\t\t\texpectRoute: \"/g/status\",\n\t\t\texpectParam: map[string]string{\"name\": \"\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterMatchAny(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Routes\n\te.addRoute(consts.MethodGet, \"/\", handlerFunc(\"/\"))\n\te.addRoute(consts.MethodGet, \"/*x\", handlerFunc(\"/*x\"))\n\te.addRoute(consts.MethodGet, \"/users/*x\", handlerFunc(\"/users/*x\"))\n\n\ttestCases := []struct {\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t}{\n\t\t{\n\t\t\twhenURL:     \"/\",\n\t\t\texpectRoute: \"/\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/download\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"download\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/joe\",\n\t\t\texpectRoute: \"/users/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"joe\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\n// NOTE: This is to document current implementation. Last added route with `*` asterisk is always the match and no\n// backtracking or more precise matching is done to find more suitable match.\n//\n// Current behaviour might not be correct or expected.\n// But this is where we are without well defined requirements/rules how (multiple) asterisks work in route\nfunc TestRouterAnyMatchesLastAddedAnyRoute(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\te.addRoute(consts.MethodGet, \"/users/*x\", handlerHelper(\"/users/*x\", \"case\", 1))\n\t// e.addRoute(consts.MethodGet, \"/users/*x/action*y\", handlerHelper(\"/users/*x/action*y\", \"case\", 2))\n\n\tctx := e.NewContext()\n\n\tctx.Request.SetRequestURI(\"/users/xxx/action/sea\")\n\tctx.Request.Header.SetMethod(consts.MethodGet)\n\te.ServeHTTP(context.Background(), ctx)\n\tassert.DeepEqual(t, \"/users/*x\", getHelper(ctx, \"path\"))\n\tassert.DeepEqual(t, \"xxx/action/sea\", ctx.Param(\"x\"))\n\n\t// if we add another route then it is the last added and so it is matched\n\t// e.addRoute(consts.MethodGet, \"/users/*x/action/search\", handlerHelper(\"/users/*x/action/search\", \"case\", 3))\n\n\t// c.Request.SetRequestURI(\"/users/xxx/action/sea\")\n\t// c.Request.Header.SetMethod(consts.MethodGet)\n\t// e.ServeHTTP(context.Background(), c)\n\t// test.DeepEqual(t, \"/users/*x/action/search\", getHelper(c, \"path\"))\n\t// test.DeepEqual(t, \"xxx/action/sea\", ctx.Param(\"x\"))\n}\n\nfunc TestRouterMatchAnyPrefixIssue(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Routes\n\te.addRoute(consts.MethodGet, \"/*x\", handlerFunc(\"/*x\"))\n\te.addRoute(consts.MethodGet, \"/users/*x\", handlerFunc(\"/users/*x\"))\n\n\ttestCases := []struct {\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t}{\n\t\t{\n\t\t\twhenURL:     \"/\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"users\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/\",\n\t\t\texpectRoute: \"/users/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users_prefix\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"users_prefix\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users_prefix/\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"users_prefix/\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\n// TestRouterMatchAnySlash shall verify finding the best route\n// for any routes with trailing slash requests\nfunc TestRouterMatchAnySlash(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Routes\n\te.addRoute(consts.MethodGet, \"/users\", handlerFunc(\"/users\"))\n\te.addRoute(consts.MethodGet, \"/users/*x\", handlerFunc(\"/users/*x\"))\n\te.addRoute(consts.MethodGet, \"/img/*x\", handlerFunc(\"/img/*x\"))\n\te.addRoute(consts.MethodGet, \"/img/load\", handlerFunc(\"/img/load\"))\n\te.addRoute(consts.MethodGet, \"/img/load/*x\", handlerFunc(\"/img/load/*x\"))\n\te.addRoute(consts.MethodGet, \"/assets/*x\", handlerFunc(\"/assets/*x\"))\n\n\ttestCases := []struct {\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t\texpectError error\n\t}{\n\t\t{\n\t\t\twhenURL:     \"/\",\n\t\t\texpectRoute: nil,\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{ // Test trailing slash request for simple any route (see #1526)\n\t\t\twhenURL:     \"/users/\",\n\t\t\texpectRoute: \"/users/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/joe\",\n\t\t\texpectRoute: \"/users/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"joe\"},\n\t\t},\n\t\t// Test trailing slash request for nested any route (see #1526)\n\t\t{\n\t\t\twhenURL:     \"/img/load\",\n\t\t\texpectRoute: \"/img/load\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/img/load/\",\n\t\t\texpectRoute: \"/img/load/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/img/load/ben\",\n\t\t\texpectRoute: \"/img/load/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"ben\"},\n\t\t},\n\t\t// Test /assets/*x any route\n\t\t{ // ... without trailing slash must not match\n\t\t\twhenURL:     \"/assets\",\n\t\t\texpectRoute: nil,\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\n\t\t{ // ... with trailing slash must match\n\t\t\twhenURL:     \"/assets/\",\n\t\t\texpectRoute: \"/assets/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterMatchAnyMultiLevel(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Routes\n\te.addRoute(consts.MethodGet, \"/api/users/jack\", handlerFunc(\"/api/users/jack\"))\n\te.addRoute(consts.MethodGet, \"/api/users/jill\", handlerFunc(\"/api/users/jill\"))\n\te.addRoute(consts.MethodGet, \"/api/users/*x\", handlerFunc(\"/api/users/*x\"))\n\te.addRoute(consts.MethodGet, \"/api/*x\", handlerFunc(\"/api/*x\"))\n\te.addRoute(consts.MethodGet, \"/other/*x\", handlerFunc(\"/other/*x\"))\n\te.addRoute(consts.MethodGet, \"/*x\", handlerFunc(\"/*x\"))\n\n\ttestCases := []struct {\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t\texpectError error\n\t}{\n\t\t{\n\t\t\twhenURL:     \"/api/users/jack\",\n\t\t\texpectRoute: \"/api/users/jack\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/api/users/jill\",\n\t\t\texpectRoute: \"/api/users/jill\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/api/users/joe\",\n\t\t\texpectRoute: \"/api/users/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"joe\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/api/nousers/joe\",\n\t\t\texpectRoute: \"/api/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"nousers/joe\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/api/none\",\n\t\t\texpectRoute: \"/api/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"none\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/api/none\",\n\t\t\texpectRoute: \"/api/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"none\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/noapi/users/jim\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"noapi/users/jim\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterMatchAnyMultiLevelWithPost(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Routes\n\te.POST(\"/api/auth/login\", handlerFunc(\"/api/auth/login\")...)\n\te.POST(\"/api/auth/forgotPassword\", handlerFunc(\"/api/auth/forgotPassword\")...)\n\te.Any(\"/api/*x\", handlerFunc(\"/api/*x\")...)\n\te.Any(\"/*x\", handlerFunc(\"/*x\")...)\n\n\ttestCases := []struct {\n\t\twhenMethod  string\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t\texpectError error\n\t}{\n\t\t{ // POST /api/auth/login shall choose login method\n\t\t\twhenURL:     \"/api/auth/login\",\n\t\t\twhenMethod:  consts.MethodPost,\n\t\t\texpectRoute: \"/api/auth/login\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{ // POST /api/auth/logout shall choose nearest any route\n\t\t\twhenURL:     \"/api/auth/logout\",\n\t\t\twhenMethod:  consts.MethodPost,\n\t\t\texpectRoute: \"/api/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"auth/logout\"},\n\t\t},\n\t\t{ // POST to /api/other/test shall choose nearest any route\n\t\t\twhenURL:     \"/api/other/test\",\n\t\t\twhenMethod:  consts.MethodPost,\n\t\t\texpectRoute: \"/api/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"other/test\"},\n\t\t},\n\t\t{ // GET to /api/other/test shall choose nearest any route\n\t\t\twhenURL:     \"/api/other/test\",\n\t\t\twhenMethod:  consts.MethodGet,\n\t\t\texpectRoute: \"/api/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"other/test\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(tc.whenMethod)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterMicroParam(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\te.addRoute(consts.MethodGet, \"/:a/:b/:c\", handlerFunc(\"/:a/:b/:c\"))\n\tctx := e.NewContext()\n\tctx.Request.SetRequestURI(\"/1/2/3\")\n\tctx.Request.Header.SetMethod(consts.MethodGet)\n\te.ServeHTTP(context.Background(), ctx)\n\tassert.DeepEqual(t, \"1\", ctx.Param(\"a\"))\n\tassert.DeepEqual(t, \"2\", ctx.Param(\"b\"))\n\tassert.DeepEqual(t, \"3\", ctx.Param(\"c\"))\n}\n\nfunc TestRouterMixParamMatchAny(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Route\n\te.addRoute(consts.MethodGet, \"/users/:id/*x\", handlerFunc(\"/users/:id/*x\"))\n\tctx := e.NewContext()\n\n\tctx.Request.SetRequestURI(\"/users/joe/comments\")\n\tctx.Request.Header.SetMethod(consts.MethodGet)\n\te.ServeHTTP(context.Background(), ctx)\n\tassert.DeepEqual(t, \"joe\", ctx.Param(\"id\"))\n}\n\nfunc TestRouterMultiRoute(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Routes\n\te.addRoute(consts.MethodGet, \"/users\", handlerFunc(\"/users\"))\n\te.addRoute(consts.MethodGet, \"/users/:id\", handlerFunc(\"/users/:id\"))\n\n\ttestCases := []struct {\n\t\twhenMethod  string\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t\texpectError error\n\t}{\n\t\t{\n\t\t\twhenURL:     \"/users\",\n\t\t\texpectRoute: \"/users\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/1\",\n\t\t\texpectRoute: \"/users/:id\",\n\t\t\texpectParam: map[string]string{\"id\": \"1\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/user\",\n\t\t\texpectRoute: nil,\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterPriority(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Routes\n\te.addRoute(consts.MethodGet, \"/users\", handlerFunc(\"/users\"))\n\te.addRoute(consts.MethodGet, \"/users/new\", handlerFunc(\"/users/new\"))\n\te.addRoute(consts.MethodGet, \"/users/:id\", handlerFunc(\"/users/:id\"))\n\te.addRoute(consts.MethodGet, \"/users/dew\", handlerFunc(\"/users/dew\"))\n\te.addRoute(consts.MethodGet, \"/users/:id/files\", handlerFunc(\"/users/:id/files\"))\n\te.addRoute(consts.MethodGet, \"/users/newsee\", handlerFunc(\"/users/newsee\"))\n\te.addRoute(consts.MethodGet, \"/users/*x\", handlerFunc(\"/users/*x\"))\n\te.addRoute(consts.MethodGet, \"/users/new/*x\", handlerFunc(\"/users/new/*x\"))\n\te.addRoute(consts.MethodGet, \"/*x\", handlerFunc(\"/*x\"))\n\n\ttestCases := []struct {\n\t\twhenMethod  string\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t\texpectError error\n\t}{\n\t\t{\n\t\t\twhenURL:     \"/users\",\n\t\t\texpectRoute: \"/users\",\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/new\",\n\t\t\texpectRoute: \"/users/new\",\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/1\",\n\t\t\texpectRoute: \"/users/:id\",\n\t\t\texpectParam: map[string]string{\"id\": \"1\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/dew\",\n\t\t\texpectRoute: \"/users/dew\",\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/1/files\",\n\t\t\texpectRoute: \"/users/:id/files\",\n\t\t\texpectParam: map[string]string{\"id\": \"1\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/new\",\n\t\t\texpectRoute: \"/users/new\",\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/news\",\n\t\t\texpectRoute: \"/users/:id\",\n\t\t\texpectParam: map[string]string{\"id\": \"news\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/newsee\",\n\t\t\texpectRoute: \"/users/newsee\",\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/joe/books\",\n\t\t\texpectRoute: \"/users/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"joe/books\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/new/someone\",\n\t\t\texpectRoute: \"/users/new/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"someone\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/dew/someone\",\n\t\t\texpectRoute: \"/users/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"dew/someone\"},\n\t\t},\n\t\t{ // Route > /users/*x should be matched although /users/dew exists\n\t\t\twhenURL:     \"/users/notexists/someone\",\n\t\t\texpectRoute: \"/users/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"notexists/someone\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/nousers\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"nousers\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/nousers/new\",\n\t\t\texpectRoute: \"/*x\",\n\t\t\texpectParam: map[string]string{\"x\": \"nousers/new\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterIssue1348(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\te.addRoute(consts.MethodGet, \"/:lang/\", handlerFunc(\"/:lang/\"))\n\te.addRoute(consts.MethodGet, \"/:lang/dupa\", handlerFunc(\"/:lang/dupa\"))\n}\n\nfunc TestRouterPriorityNotFound(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Add\n\te.addRoute(consts.MethodGet, \"/a/foo\", handlerFunc(\"/a/foo\"))\n\te.addRoute(consts.MethodGet, \"/a/bar\", handlerFunc(\"/a/bar\"))\n\n\ttestCases := []struct {\n\t\twhenMethod  string\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t\texpectError error\n\t}{\n\t\t{\n\t\t\twhenURL:     \"/a/foo\",\n\t\t\texpectRoute: \"/a/foo\",\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/a/bar\",\n\t\t\texpectRoute: \"/a/bar\",\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/abc/def\",\n\t\t\texpectRoute: nil,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterParamNames(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Routes\n\te.addRoute(consts.MethodGet, \"/users\", handlerFunc(\"/users\"))\n\te.addRoute(consts.MethodGet, \"/users/:id\", handlerFunc(\"/users/:id\"))\n\te.addRoute(consts.MethodGet, \"/users/:uid/files/:fid\", handlerFunc(\"/users/:uid/files/:fid\"))\n\n\ttestCases := []struct {\n\t\twhenMethod  string\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t\texpectError error\n\t}{\n\t\t{\n\t\t\twhenURL:     \"/users\",\n\t\t\texpectRoute: \"/users\",\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/1\",\n\t\t\texpectRoute: \"/users/:id\",\n\t\t\texpectParam: map[string]string{\"id\": \"1\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/1/files/1\",\n\t\t\texpectRoute: \"/users/:uid/files/:fid\",\n\t\t\texpectParam: map[string]string{\n\t\t\t\t\"uid\": \"1\",\n\t\t\t\t\"fid\": \"1\",\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterStaticDynamicConflict(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\te.addRoute(consts.MethodGet, \"/dictionary/skills\", handlerHelper(\"/dictionary/skills\", \"a\", 1))\n\te.addRoute(consts.MethodGet, \"/dictionary/:name\", handlerHelper(\"/dictionary/:name\", \"b\", 2))\n\te.addRoute(consts.MethodGet, \"/users/new\", handlerHelper(\"/users/new\", \"d\", 4))\n\te.addRoute(consts.MethodGet, \"/users/:name\", handlerHelper(\"/users/:name\", \"e\", 5))\n\te.addRoute(consts.MethodGet, \"/server\", handlerHelper(\"/server\", \"c\", 3))\n\te.addRoute(consts.MethodGet, \"/\", handlerHelper(\"/\", \"f\", 6))\n\n\ttestCases := []struct {\n\t\twhenMethod  string\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t\texpectError error\n\t}{\n\t\t{\n\t\t\twhenURL:     \"/dictionary/skills\",\n\t\t\texpectRoute: \"/dictionary/skills\",\n\t\t\texpectParam: map[string]string{\"x\": \"\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/dictionary/skillsnot\",\n\t\t\texpectRoute: \"/dictionary/:name\",\n\t\t\texpectParam: map[string]string{\"name\": \"skillsnot\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/dictionary/type\",\n\t\t\texpectRoute: \"/dictionary/:name\",\n\t\t\texpectParam: map[string]string{\"name\": \"type\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/server\",\n\t\t\texpectRoute: \"/server\",\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/new\",\n\t\t\texpectRoute: \"/users/new\",\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/users/new2\",\n\t\t\texpectRoute: \"/users/:name\",\n\t\t\texpectParam: map[string]string{\"name\": \"new2\"},\n\t\t},\n\t\t{\n\t\t\twhenURL:     \"/\",\n\t\t\texpectRoute: \"/\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.whenURL, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n\nfunc TestRouterParamBacktraceNotFound(t *testing.T) {\n\te := NewEngine(config.NewOptions(nil))\n\n\t// Add\n\te.addRoute(consts.MethodGet, \"/:param1\", handlerFunc(\"/:param1\"))\n\te.addRoute(consts.MethodGet, \"/:param1/foo\", handlerFunc(\"/:param1/foo\"))\n\te.addRoute(consts.MethodGet, \"/:param1/bar\", handlerFunc(\"/:param1/bar\"))\n\te.addRoute(consts.MethodGet, \"/:param1/bar/:param2\", handlerFunc(\"/:param1/bar/:param2\"))\n\n\ttestCases := []struct {\n\t\tname        string\n\t\twhenMethod  string\n\t\twhenURL     string\n\t\texpectRoute interface{}\n\t\texpectParam map[string]string\n\t\texpectError error\n\t}{\n\t\t{\n\t\t\tname:        \"route /a to /:param1\",\n\t\t\twhenURL:     \"/a\",\n\t\t\texpectRoute: \"/:param1\",\n\t\t\texpectParam: map[string]string{\"param1\": \"a\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /a/foo to /:param1/foo\",\n\t\t\twhenURL:     \"/a/foo\",\n\t\t\texpectRoute: \"/:param1/foo\",\n\t\t\texpectParam: map[string]string{\"param1\": \"a\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /a/bar to /:param1/bar\",\n\t\t\twhenURL:     \"/a/bar\",\n\t\t\texpectRoute: \"/:param1/bar\",\n\t\t\texpectParam: map[string]string{\"param1\": \"a\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /a/bar/b to /:param1/bar/:param2\",\n\t\t\twhenURL:     \"/a/bar/b\",\n\t\t\texpectRoute: \"/:param1/bar/:param2\",\n\t\t\texpectParam: map[string]string{\n\t\t\t\t\"param1\": \"a\",\n\t\t\t\t\"param2\": \"b\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"route /a/bbbbb should return 404\",\n\t\t\twhenURL:     \"/a/bbbbb\",\n\t\t\texpectRoute: nil,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := e.NewContext()\n\t\t\tctx.Request.SetRequestURI(tc.whenURL)\n\t\t\tctx.Request.Header.SetMethod(consts.MethodGet)\n\t\t\te.ServeHTTP(context.Background(), ctx)\n\n\t\t\tassert.DeepEqual(t, tc.expectRoute, getHelper(ctx, \"path\"))\n\t\t\tfor param, expectedValue := range tc.expectParam {\n\t\t\t\tassert.DeepEqual(t, expectedValue, ctx.Param(param))\n\t\t\t}\n\t\t\tcheckUnusedParamValues(t, ctx, tc.expectParam)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/route/routes_timing_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Manuel Martínez-Almeida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors\n */\n\npackage route\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/common/config\"\n\t\"github.com/cloudwego/hertz/pkg/protocol\"\n)\n\ntype Route struct {\n\tpath string\n}\n\nfunc BenchmarkTree_FindStatic(b *testing.B) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\tstatic := []*Route{\n\t\t{\"/\"},\n\t\t{\"/cmd.html\"},\n\t\t{\"/code.html\"},\n\t\t{\"/contrib.html\"},\n\t\t{\"/contribute.html\"},\n\t\t{\"/debugging_with_gdb.html\"},\n\t\t{\"/docs.html\"},\n\t\t{\"/effective_go.html\"},\n\t\t{\"/files.log\"},\n\t\t{\"/gccgo_contribute.html\"},\n\t\t{\"/gccgo_install.html\"},\n\t\t{\"/go-logo-black.png\"},\n\t\t{\"/go-logo-blue.png\"},\n\t\t{\"/go-logo-white.png\"},\n\t\t{\"/go1.1.html\"},\n\t\t{\"/go1.2.html\"},\n\t\t{\"/go1.html\"},\n\t\t{\"/go1compat.html\"},\n\t\t{\"/go_faq.html\"},\n\t\t{\"/go_mem.html\"},\n\t\t{\"/go_spec.html\"},\n\t\t{\"/help.html\"},\n\t\t{\"/ie.css\"},\n\t\t{\"/install-source.html\"},\n\t\t{\"/install.html\"},\n\t\t{\"/logo-153x55.png\"},\n\t\t{\"/Makefile\"},\n\t\t{\"/root.html\"},\n\t\t{\"/share.png\"},\n\t\t{\"/sieve.gif\"},\n\t\t{\"/tos.html\"},\n\t\t{\"/articles/\"},\n\t\t{\"/articles/go_command.html\"},\n\t\t{\"/articles/index.html\"},\n\t\t{\"/articles/wiki/\"},\n\t\t{\"/articles/wiki/edit.html\"},\n\t\t{\"/articles/wiki/final-noclosure.go\"},\n\t\t{\"/articles/wiki/final-noerror.go\"},\n\t\t{\"/articles/wiki/final-parsetemplate.go\"},\n\t\t{\"/articles/wiki/final-template.go\"},\n\t\t{\"/articles/wiki/final.go\"},\n\t\t{\"/articles/wiki/get.go\"},\n\t\t{\"/articles/wiki/http-sample.go\"},\n\t\t{\"/articles/wiki/index.html\"},\n\t\t{\"/articles/wiki/Makefile\"},\n\t\t{\"/articles/wiki/notemplate.go\"},\n\t\t{\"/articles/wiki/part1-noerror.go\"},\n\t\t{\"/articles/wiki/part1.go\"},\n\t\t{\"/articles/wiki/part2.go\"},\n\t\t{\"/articles/wiki/part3-errorhandling.go\"},\n\t\t{\"/articles/wiki/part3.go\"},\n\t\t{\"/articles/wiki/test.bash\"},\n\t\t{\"/articles/wiki/test_edit.good\"},\n\t\t{\"/articles/wiki/test_Test.txt.good\"},\n\t\t{\"/articles/wiki/test_view.good\"},\n\t\t{\"/articles/wiki/view.html\"},\n\t\t{\"/codewalk/\"},\n\t\t{\"/codewalk/codewalk.css\"},\n\t\t{\"/codewalk/codewalk.js\"},\n\t\t{\"/codewalk/codewalk.xml\"},\n\t\t{\"/codewalk/functions.xml\"},\n\t\t{\"/codewalk/markov.go\"},\n\t\t{\"/codewalk/markov.xml\"},\n\t\t{\"/codewalk/pig.go\"},\n\t\t{\"/codewalk/popout.png\"},\n\t\t{\"/codewalk/run\"},\n\t\t{\"/codewalk/sharemem.xml\"},\n\t\t{\"/codewalk/urlpoll.go\"},\n\t\t{\"/devel/\"},\n\t\t{\"/devel/release.html\"},\n\t\t{\"/devel/weekly.html\"},\n\t\t{\"/gopher/\"},\n\t\t{\"/gopher/appenginegopher.jpg\"},\n\t\t{\"/gopher/appenginegophercolor.jpg\"},\n\t\t{\"/gopher/appenginelogo.gif\"},\n\t\t{\"/gopher/bumper.png\"},\n\t\t{\"/gopher/bumper192x108.png\"},\n\t\t{\"/gopher/bumper320x180.png\"},\n\t\t{\"/gopher/bumper480x270.png\"},\n\t\t{\"/gopher/bumper640x360.png\"},\n\t\t{\"/gopher/doc.png\"},\n\t\t{\"/gopher/frontpage.png\"},\n\t\t{\"/gopher/gopherbw.png\"},\n\t\t{\"/gopher/gophercolor.png\"},\n\t\t{\"/gopher/gophercolor16x16.png\"},\n\t\t{\"/gopher/help.png\"},\n\t\t{\"/gopher/pkg.png\"},\n\t\t{\"/gopher/project.png\"},\n\t\t{\"/gopher/ref.png\"},\n\t\t{\"/gopher/run.png\"},\n\t\t{\"/gopher/talks.png\"},\n\t\t{\"/gopher/pencil/\"},\n\t\t{\"/gopher/pencil/gopherhat.jpg\"},\n\t\t{\"/gopher/pencil/gopherhelmet.jpg\"},\n\t\t{\"/gopher/pencil/gophermega.jpg\"},\n\t\t{\"/gopher/pencil/gopherrunning.jpg\"},\n\t\t{\"/gopher/pencil/gopherswim.jpg\"},\n\t\t{\"/gopher/pencil/gopherswrench.jpg\"},\n\t\t{\"/play/\"},\n\t\t{\"/play/fib.go\"},\n\t\t{\"/play/hello.go\"},\n\t\t{\"/play/life.go\"},\n\t\t{\"/play/peano.go\"},\n\t\t{\"/play/pi.go\"},\n\t\t{\"/play/sieve.go\"},\n\t\t{\"/play/solitaire.go\"},\n\t\t{\"/play/tree.go\"},\n\t\t{\"/progs/\"},\n\t\t{\"/progs/cgo1.go\"},\n\t\t{\"/progs/cgo2.go\"},\n\t\t{\"/progs/cgo3.go\"},\n\t\t{\"/progs/cgo4.go\"},\n\t\t{\"/progs/defer.go\"},\n\t\t{\"/progs/defer.out\"},\n\t\t{\"/progs/defer2.go\"},\n\t\t{\"/progs/defer2.out\"},\n\t\t{\"/progs/eff_bytesize.go\"},\n\t\t{\"/progs/eff_bytesize.out\"},\n\t\t{\"/progs/eff_qr.go\"},\n\t\t{\"/progs/eff_sequence.go\"},\n\t\t{\"/progs/eff_sequence.out\"},\n\t\t{\"/progs/eff_unused1.go\"},\n\t\t{\"/progs/eff_unused2.go\"},\n\t\t{\"/progs/error.go\"},\n\t\t{\"/progs/error2.go\"},\n\t\t{\"/progs/error3.go\"},\n\t\t{\"/progs/error4.go\"},\n\t\t{\"/progs/go1.go\"},\n\t\t{\"/progs/gobs1.go\"},\n\t\t{\"/progs/gobs2.go\"},\n\t\t{\"/progs/image_draw.go\"},\n\t\t{\"/progs/image_package1.go\"},\n\t\t{\"/progs/image_package1.out\"},\n\t\t{\"/progs/image_package2.go\"},\n\t\t{\"/progs/image_package2.out\"},\n\t\t{\"/progs/image_package3.go\"},\n\t\t{\"/progs/image_package3.out\"},\n\t\t{\"/progs/image_package4.go\"},\n\t\t{\"/progs/image_package4.out\"},\n\t\t{\"/progs/image_package5.go\"},\n\t\t{\"/progs/image_package5.out\"},\n\t\t{\"/progs/image_package6.go\"},\n\t\t{\"/progs/image_package6.out\"},\n\t\t{\"/progs/interface.go\"},\n\t\t{\"/progs/interface2.go\"},\n\t\t{\"/progs/interface2.out\"},\n\t\t{\"/progs/json1.go\"},\n\t\t{\"/progs/json2.go\"},\n\t\t{\"/progs/json2.out\"},\n\t\t{\"/progs/json3.go\"},\n\t\t{\"/progs/json4.go\"},\n\t\t{\"/progs/json5.go\"},\n\t\t{\"/progs/run\"},\n\t\t{\"/progs/slices.go\"},\n\t\t{\"/progs/timeout1.go\"},\n\t\t{\"/progs/timeout2.go\"},\n\t\t{\"/progs/update.bash\"},\n\t}\n\n\tfor _, route := range static {\n\t\ttree.addRoute(route.path, fakeHandler(route.path))\n\t}\n\tps := getParams()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, request := range static {\n\t\t\ttree.find(request.path, ps, false)\n\t\t}\n\t}\n}\n\nfunc BenchmarkTree_FindGithub(b *testing.B) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\tstatic := []*Route{\n\t\t// OAuth Authorizations\n\t\t{\"/authorizations\"},\n\t\t{\"/authorizations/:id\"},\n\t\t//{\"/authorizations\"},\n\t\t//{\"/authorizations/clients/:client_id\"},\n\t\t//{\"/authorizations/:id\"},\n\t\t//{\"/authorizations/:id\"},\n\t\t{\"/applications/:client_id/tokens/:access_token\"},\n\t\t{\"/applications/:client_id/tokens\"},\n\t\t//{\"/applications/:client_id/tokens/:access_token\"},\n\n\t\t// Activity\n\t\t{\"/events\"},\n\t\t{\"/repos/:owner/:repo/events\"},\n\t\t{\"/networks/:owner/:repo/events\"},\n\t\t{\"/orgs/:org/events\"},\n\t\t{\"/users/:user/received_events\"},\n\t\t{\"/users/:user/received_events/public\"},\n\t\t{\"/users/:user/events\"},\n\t\t{\"/users/:user/events/public\"},\n\t\t{\"/users/:user/events/orgs/:org\"},\n\t\t{\"/feeds\"},\n\t\t//{\"/notifications\"},\n\t\t{\"/repos/:owner/:repo/notifications\"},\n\t\t{\"/notifications\"},\n\t\t//{\"/repos/:owner/:repo/notifications\"},\n\t\t{\"/notifications/threads/:id\"},\n\t\t//{\"/notifications/threads/:id\"},\n\t\t{\"/notifications/threads/:id/subscription\"},\n\t\t//{\"/notifications/threads/:id/subscription\"},\n\t\t//{\"/notifications/threads/:id/subscription\"},\n\t\t{\"/repos/:owner/:repo/stargazers\"},\n\t\t{\"/users/:user/starred\"},\n\t\t{\"/user/starred\"},\n\t\t{\"/user/starred/:owner/:repo\"},\n\t\t//{\"/user/starred/:owner/:repo\"},\n\t\t//{\"/user/starred/:owner/:repo\"},\n\t\t{\"/repos/:owner/:repo/subscribers\"},\n\t\t{\"/users/:user/subscriptions\"},\n\t\t{\"/user/subscriptions\"},\n\t\t{\"/repos/:owner/:repo/subscription\"},\n\t\t//{\"/repos/:owner/:repo/subscription\"},\n\t\t//{\"/repos/:owner/:repo/subscription\"},\n\t\t{\"/user/subscriptions/:owner/:repo\"},\n\t\t//{\"PUT\", \"/user/subscriptions/:owner/:repo\"},\n\t\t//{\"DELETE\", \"/user/subscriptions/:owner/:repo\"},\n\n\t\t// Gists\n\t\t{\"/users/:user/gists\"},\n\t\t{\"/gists\"},\n\t\t//{\"GET\", \"/gists/public\"},\n\t\t//{\"GET\", \"/gists/starred\"},\n\t\t{\"/gists/:id\"},\n\t\t//{\"POST\", \"/gists\"},\n\t\t//{\"PATCH\", \"/gists/:id\"},\n\t\t{\"/gists/:id/star\"},\n\t\t//{\"DELETE\", \"/gists/:id/star\"},\n\t\t//{\"GET\", \"/gists/:id/star\"},\n\t\t{\"/gists/:id/forks\"},\n\t\t//{\"DELETE\", \"/gists/:id\"},\n\n\t\t// Git Data\n\t\t{\"/repos/:owner/:repo/git/blobs/:sha\"},\n\t\t{\"/repos/:owner/:repo/git/blobs\"},\n\t\t{\"/repos/:owner/:repo/git/commits/:sha\"},\n\t\t{\"/repos/:owner/:repo/git/commits\"},\n\t\t//{\"GET\", \"/repos/:owner/:repo/git/refs/*ref\"},\n\t\t{\"/repos/:owner/:repo/git/refs\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/git/refs\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/git/refs/*ref\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/git/refs/*ref\"},\n\t\t{\"/repos/:owner/:repo/git/tags/:sha\"},\n\t\t{\"/repos/:owner/:repo/git/tags\"},\n\t\t{\"/repos/:owner/:repo/git/trees/:sha\"},\n\t\t{\"/repos/:owner/:repo/git/trees\"},\n\n\t\t{\"/issues\"},\n\t\t{\"/user/issues\"},\n\t\t{\"/orgs/:org/issues\"},\n\t\t{\"/repos/:owner/:repo/issues\"},\n\t\t{\"/repos/:owner/:repo/issues/:number\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/issues\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/issues/:number\"},\n\t\t{\"/repos/:owner/:repo/assignees\"},\n\t\t{\"/repos/:owner/:repo/assignees/:assignee\"},\n\t\t{\"/repos/:owner/:repo/issues/:number/comments\"},\n\t\t//{\"GET\", \"/repos/:owner/:repo/issues/comments\"},\n\t\t//{\"GET\", \"/repos/:owner/:repo/issues/comments/:id\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/issues/:number/comments\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/issues/comments/:id\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/issues/comments/:id\"},\n\t\t{\"/repos/:owner/:repo/issues/:number/events\"},\n\t\t//{\"GET\", \"/repos/:owner/:repo/issues/events\"},\n\t\t//{\"GET\", \"/repos/:owner/:repo/issues/events/:id\"},\n\t\t{\"/repos/:owner/:repo/labels\"},\n\t\t{\"/repos/:owner/:repo/labels/:name\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/labels\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/labels/:name\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/labels/:name\"},\n\t\t{\"/repos/:owner/:repo/issues/:number/labels\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/issues/:number/labels\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/issues/:number/labels/:name\"},\n\t\t//{\"PUT\", \"/repos/:owner/:repo/issues/:number/labels\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/issues/:number/labels\"},\n\t\t{\"/repos/:owner/:repo/milestones/:number/labels\"},\n\t\t{\"/repos/:owner/:repo/milestones\"},\n\t\t{\"/repos/:owner/:repo/milestones/:number\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/milestones\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/milestones/:number\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/milestones/:number\"},\n\n\t\t// Miscellaneous\n\t\t{\"/emojis\"},\n\t\t{\"/gitignore/templates\"},\n\t\t{\"/gitignore/templates/:name\"},\n\t\t{\"/markdown\"},\n\t\t{\"/markdown/raw\"},\n\t\t{\"/meta\"},\n\t\t{\"/rate_limit\"},\n\n\t\t// Organizations\n\t\t{\"/users/:user/orgs\"},\n\t\t{\"/user/orgs\"},\n\t\t{\"/orgs/:org\"},\n\t\t//{\"PATCH\", \"/orgs/:org\"},\n\t\t{\"/orgs/:org/members\"},\n\t\t{\"/orgs/:org/members/:user\"},\n\t\t//{\"DELETE\", \"/orgs/:org/members/:user\"},\n\t\t{\"/orgs/:org/public_members\"},\n\t\t{\"/orgs/:org/public_members/:user\"},\n\t\t//{\"PUT\", \"/orgs/:org/public_members/:user\"},\n\t\t//{\"DELETE\", \"/orgs/:org/public_members/:user\"},\n\t\t{\"/orgs/:org/teams\"},\n\t\t{\"/teams/:id\"},\n\t\t//{\"POST\", \"/orgs/:org/teams\"},\n\t\t//{\"PATCH\", \"/teams/:id\"},\n\t\t//{\"DELETE\", \"/teams/:id\"},\n\t\t{\"/teams/:id/members\"},\n\t\t{\"/teams/:id/members/:user\"},\n\t\t//{\"PUT\", \"/teams/:id/members/:user\"},\n\t\t//{\"DELETE\", \"/teams/:id/members/:user\"},\n\t\t{\"/teams/:id/repos\"},\n\t\t{\"/teams/:id/repos/:owner/:repo\"},\n\t\t//{\"PUT\", \"/teams/:id/repos/:owner/:repo\"},\n\t\t//{\"DELETE\", \"/teams/:id/repos/:owner/:repo\"},\n\t\t{\"/user/teams\"},\n\n\t\t// Pull Requests\n\t\t{\"/repos/:owner/:repo/pulls\"},\n\t\t{\"/repos/:owner/:repo/pulls/:number\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/pulls\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/pulls/:number\"},\n\t\t{\"/repos/:owner/:repo/pulls/:number/commits\"},\n\t\t{\"/repos/:owner/:repo/pulls/:number/files\"},\n\t\t{\"/repos/:owner/:repo/pulls/:number/merge\"},\n\t\t//{\"PUT\", \"/repos/:owner/:repo/pulls/:number/merge\"},\n\t\t{\"/repos/:owner/:repo/pulls/:number/comments\"},\n\t\t//{\"GET\", \"/repos/:owner/:repo/pulls/comments\"},\n\t\t//{\"GET\", \"/repos/:owner/:repo/pulls/comments/:number\"},\n\t\t//{\"PUT\", \"/repos/:owner/:repo/pulls/:number/comments\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/pulls/comments/:number\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/pulls/comments/:number\"},\n\n\t\t// Repositories\n\t\t{\"/user/repos\"},\n\t\t{\"/users/:user/repos\"},\n\t\t{\"/orgs/:org/repos\"},\n\t\t{\"/repositories\"},\n\t\t//{\"POST\", \"/user/repos\"},\n\t\t//{\"POST\", \"/orgs/:org/repos\"},\n\t\t{\"/repos/:owner/:repo\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo\"},\n\t\t{\"/repos/:owner/:repo/contributors\"},\n\t\t{\"/repos/:owner/:repo/languages\"},\n\t\t{\"/repos/:owner/:repo/teams\"},\n\t\t{\"/repos/:owner/:repo/tags\"},\n\t\t{\"/repos/:owner/:repo/branches\"},\n\t\t{\"/repos/:owner/:repo/branches/:branch\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo\"},\n\t\t{\"/repos/:owner/:repo/collaborators\"},\n\t\t{\"/repos/:owner/:repo/collaborators/:user\"},\n\t\t//{\"PUT\", \"/repos/:owner/:repo/collaborators/:user\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/collaborators/:user\"},\n\t\t{\"/repos/:owner/:repo/comments\"},\n\t\t{\"/repos/:owner/:repo/commits/:sha/comments\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/commits/:sha/comments\"},\n\t\t{\"/repos/:owner/:repo/comments/:id\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/comments/:id\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/comments/:id\"},\n\t\t{\"/repos/:owner/:repo/commits\"},\n\t\t{\"/repos/:owner/:repo/commits/:sha\"},\n\t\t{\"/repos/:owner/:repo/readme\"},\n\t\t//{\"GET\", \"/repos/:owner/:repo/contents/*path\"},\n\t\t//{\"PUT\", \"/repos/:owner/:repo/contents/*path\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/contents/*path\"},\n\t\t//{\"GET\", \"/repos/:owner/:repo/:archive_format/:ref\"},\n\t\t{\"/repos/:owner/:repo/keys\"},\n\t\t{\"/repos/:owner/:repo/keys/:id\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/keys\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/keys/:id\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/keys/:id\"},\n\t\t{\"/repos/:owner/:repo/downloads\"},\n\t\t{\"/repos/:owner/:repo/downloads/:id\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/downloads/:id\"},\n\t\t{\"/repos/:owner/:repo/forks\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/forks\"},\n\t\t{\"/repos/:owner/:repo/hooks\"},\n\t\t{\"/repos/:owner/:repo/hooks/:id\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/hooks\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/hooks/:id\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/hooks/:id/tests\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/hooks/:id\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/merges\"},\n\t\t{\"/repos/:owner/:repo/releases\"},\n\t\t{\"/repos/:owner/:repo/releases/:id\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/releases\"},\n\t\t//{\"PATCH\", \"/repos/:owner/:repo/releases/:id\"},\n\t\t//{\"DELETE\", \"/repos/:owner/:repo/releases/:id\"},\n\t\t{\"/repos/:owner/:repo/releases/:id/assets\"},\n\t\t{\"/repos/:owner/:repo/stats/contributors\"},\n\t\t{\"/repos/:owner/:repo/stats/commit_activity\"},\n\t\t{\"/repos/:owner/:repo/stats/code_frequency\"},\n\t\t{\"/repos/:owner/:repo/stats/participation\"},\n\t\t{\"/repos/:owner/:repo/stats/punch_card\"},\n\t\t{\"/repos/:owner/:repo/statuses/:ref\"},\n\t\t//{\"POST\", \"/repos/:owner/:repo/statuses/:ref\"},\n\n\t\t// Search\n\t\t{\"/search/repositories\"},\n\t\t{\"/search/code\"},\n\t\t{\"/search/issues\"},\n\t\t{\"/search/users\"},\n\t\t{\"/legacy/issues/search/:owner/:repository/:state/:keyword\"},\n\t\t{\"/legacy/repos/search/:keyword\"},\n\t\t{\"/legacy/user/search/:keyword\"},\n\t\t{\"/legacy/user/email/:email\"},\n\n\t\t// Users\n\t\t{\"/users/:user\"},\n\t\t{\"/user\"},\n\t\t//{\"PATCH\", \"/user\"},\n\t\t{\"/users\"},\n\t\t{\"/user/emails\"},\n\t\t//{\"POST\", \"/user/emails\"},\n\t\t//{\"DELETE\", \"/user/emails\"},\n\t\t{\"/users/:user/followers\"},\n\t\t{\"/user/followers\"},\n\t\t{\"/users/:user/following\"},\n\t\t{\"/user/following\"},\n\t\t{\"/user/following/:user\"},\n\t\t{\"/users/:user/following/:target_user\"},\n\t\t//{\"PUT\", \"/user/following/:user\"},\n\t\t//{\"DELETE\", \"/user/following/:user\"},\n\t\t{\"/users/:user/keys\"},\n\t\t{\"/user/keys\"},\n\t\t{\"/user/keys/:id\"},\n\t\t//{\"POST\", \"/user/keys\"},\n\t\t//{\"PATCH\", \"/user/keys/:id\"},\n\t\t//{\"DELETE\", \"/user/keys/:id\"},\n\t}\n\n\tfor _, route := range static {\n\t\ttree.addRoute(route.path, fakeHandler(route.path))\n\t}\n\tps := getParams()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, request := range static {\n\t\t\ttree.find(request.path, ps, false)\n\t\t}\n\t}\n}\n\nfunc BenchmarkTree_FindStaticTsr(b *testing.B) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/doc/foo/go_faq.html/\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\ttr := testRequests{\n\t\t{\"/doc/foo/go_faq.html\", false, \"/doc/foo/go_faq.html\", nil},\n\t}\n\tps := getParams()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, request := range tr {\n\t\t\ttree.find(request.path, ps, false)\n\t\t}\n\t}\n}\n\nfunc BenchmarkTree_FindParam(b *testing.B) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/hi/:key1/foo/:key2\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\ttr := testRequests{\n\t\t{\"/hi/1/foo/2\", false, \"/hi\", nil},\n\t}\n\tps := getParams()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, request := range tr {\n\t\t\ttree.find(request.path, ps, false)\n\t\t}\n\t}\n}\n\nfunc BenchmarkTree_FindParamTsr(b *testing.B) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/hi/:key1/foo/:key2/\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\ttr := testRequests{\n\t\t{\"/hi/1/foo/2\", false, \"/hi\", nil},\n\t}\n\tps := getParams()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, request := range tr {\n\t\t\ttree.find(request.path, ps, false)\n\t\t}\n\t}\n}\n\nfunc BenchmarkTree_FindAny(b *testing.B) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/hi/*key1\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\ttr := testRequests{\n\t\t{\"/hi/foo\", false, \"/hi\", nil},\n\t}\n\tps := getParams()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, request := range tr {\n\t\t\ttree.find(request.path, ps, false)\n\t\t}\n\t}\n}\n\nfunc BenchmarkTree_FindAnyFallback(b *testing.B) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/hi/a/b/c/d/e/*key1\",\n\t\t\"/*key2\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\ttr := testRequests{\n\t\t{\"/hi/a/b/c/d/f\", false, \"/*key2\", nil},\n\t}\n\tps := getParams()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, request := range tr {\n\t\t\ttree.find(request.path, ps, false)\n\t\t}\n\t}\n}\n\nfunc BenchmarkRouteStatic(b *testing.B) {\n\tr := NewEngine(config.NewOptions(nil))\n\tr.GET(\"/hi/foo\", func(c context.Context, ctx *app.RequestContext) {})\n\tctx := r.NewContext()\n\treq := protocol.NewRequest(\"GET\", \"/hi/foo\", nil)\n\treq.CopyTo(&ctx.Request)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tr.ServeHTTP(context.Background(), ctx)\n\t\t// ctx.index = -1\n\t}\n}\n\nfunc BenchmarkRouteParam(b *testing.B) {\n\tr := NewEngine(config.NewOptions(nil))\n\tr.GET(\"/hi/:user\", func(c context.Context, ctx *app.RequestContext) {})\n\tctx := r.NewContext()\n\treq := protocol.NewRequest(\"GET\", \"/hi/foo\", nil)\n\treq.CopyTo(&ctx.Request)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tr.ServeHTTP(context.Background(), ctx)\n\t\t// ctx.index = -1\n\t}\n}\n\nfunc BenchmarkRouteAny(b *testing.B) {\n\tr := NewEngine(config.NewOptions(nil))\n\tr.GET(\"/hi/*user\", func(c context.Context, ctx *app.RequestContext) {})\n\tctx := r.NewContext()\n\treq := protocol.NewRequest(\"GET\", \"/hi/foo/dy\", nil)\n\treq.CopyTo(&ctx.Request)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tr.ServeHTTP(context.Background(), ctx)\n\t\t// ctx.index = -1\n\t}\n}\n"
  },
  {
    "path": "pkg/route/tree.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2021 LabStack\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage route\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"github.com/cloudwego/hertz/internal/bytesconv\"\n\t\"github.com/cloudwego/hertz/internal/bytestr\"\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\ntype router struct {\n\tmethod string\n\troot   *node\n}\n\ntype MethodTrees []*router\n\nfunc (trees MethodTrees) get(method string) *router {\n\tfor _, tree := range trees {\n\t\tif tree.method == method {\n\t\t\treturn tree\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc countParams(path string) uint16 {\n\tvar n uint16\n\ts := bytesconv.S2b(path)\n\tn += uint16(bytes.Count(s, bytestr.StrColon))\n\tn += uint16(bytes.Count(s, bytestr.StrStar))\n\treturn n\n}\n\ntype (\n\tnode struct {\n\t\tkind     kind\n\t\tlabel    byte\n\t\tprefix   string\n\t\tparent   *node\n\t\tchildren children\n\t\t// original path\n\t\tppath string\n\t\t// param names\n\t\tpnames     []string\n\t\thandlers   app.HandlersChain\n\t\tparamChild *node\n\t\tanyChild   *node\n\t\t// isLeaf indicates that node does not have child routes\n\t\tisLeaf bool\n\t}\n\tkind     uint8\n\tchildren []*node\n)\n\nconst (\n\t// static kind\n\tskind kind = iota\n\t// param kind\n\tpkind\n\t// all kind\n\takind\n\tparamLabel = byte(':')\n\tanyLabel   = byte('*')\n\tslash      = \"/\"\n\tnilString  = \"\"\n)\n\nfunc checkPathValid(path string) {\n\tif path == nilString {\n\t\tpanic(\"empty path\")\n\t}\n\tif path[0] != '/' {\n\t\tpanic(\"path must begin with '/'\")\n\t}\n\tfor i, c := range []byte(path) {\n\t\tswitch c {\n\t\tcase ':':\n\t\t\tif (i < len(path)-1 && path[i+1] == '/') || i == (len(path)-1) {\n\t\t\t\tpanic(\"wildcards must be named with a non-empty name in path '\" + path + \"'\")\n\t\t\t}\n\t\t\ti++\n\t\t\tfor ; i < len(path) && path[i] != '/'; i++ {\n\t\t\t\tif path[i] == ':' || path[i] == '*' {\n\t\t\t\t\tpanic(\"only one wildcard per path segment is allowed, find multi in path '\" + path + \"'\")\n\t\t\t\t}\n\t\t\t}\n\t\tcase '*':\n\t\t\tif i == len(path)-1 {\n\t\t\t\tpanic(\"wildcards must be named with a non-empty name in path '\" + path + \"'\")\n\t\t\t}\n\t\t\tif i > 0 && path[i-1] != '/' {\n\t\t\t\tpanic(\" no / before wildcards in path \" + path)\n\t\t\t}\n\t\t\tfor ; i < len(path); i++ {\n\t\t\t\tif path[i] == '/' {\n\t\t\t\t\tpanic(\"catch-all routes are only allowed at the end of the path in path '\" + path + \"'\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// addRoute adds a node with the given handle to the path.\nfunc (r *router) addRoute(path string, h app.HandlersChain) {\n\tcheckPathValid(path)\n\n\tvar (\n\t\tpnames []string // Param names\n\t\tppath  = path   // Pristine path\n\t)\n\n\tif h == nil {\n\t\tpanic(fmt.Sprintf(\"Adding route without handler function: %v\", path))\n\t}\n\n\t// Add the front static route part of a non-static route\n\tfor i, lcpIndex := 0, len(path); i < lcpIndex; i++ {\n\t\t// param route\n\t\tif path[i] == paramLabel {\n\t\t\tj := i + 1\n\n\t\t\tr.insert(path[:i], nil, skind, nilString, nil)\n\t\t\tfor ; i < lcpIndex && path[i] != '/'; i++ {\n\t\t\t}\n\n\t\t\tpnames = append(pnames, path[j:i])\n\t\t\tpath = path[:j] + path[i:]\n\t\t\ti, lcpIndex = j, len(path)\n\n\t\t\tif i == lcpIndex {\n\t\t\t\t// path node is last fragment of route path. ie. `/users/:id`\n\t\t\t\tr.insert(path[:i], h, pkind, ppath, pnames)\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tr.insert(path[:i], nil, pkind, nilString, pnames)\n\t\t\t}\n\t\t} else if path[i] == anyLabel {\n\t\t\tr.insert(path[:i], nil, skind, nilString, nil)\n\t\t\tpnames = append(pnames, path[i+1:])\n\t\t\tr.insert(path[:i+1], h, akind, ppath, pnames)\n\t\t\treturn\n\t\t}\n\t}\n\n\tr.insert(path, h, skind, ppath, pnames)\n}\n\nfunc (r *router) insert(path string, h app.HandlersChain, t kind, ppath string, pnames []string) {\n\tcurrentNode := r.root\n\tif currentNode == nil {\n\t\tpanic(\"hertz: invalid node\")\n\t}\n\tsearch := path\n\n\tfor {\n\t\tsearchLen := len(search)\n\t\tprefixLen := len(currentNode.prefix)\n\t\tlcpLen := 0\n\n\t\tmax := prefixLen\n\t\tif searchLen < max {\n\t\t\tmax = searchLen\n\t\t}\n\t\tfor ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {\n\t\t}\n\n\t\tif lcpLen == 0 {\n\t\t\t// At root node\n\t\t\tcurrentNode.label = search[0]\n\t\t\tcurrentNode.prefix = search\n\t\t\tif h != nil {\n\t\t\t\tcurrentNode.kind = t\n\t\t\t\tcurrentNode.handlers = h\n\t\t\t\tcurrentNode.ppath = ppath\n\t\t\t\tcurrentNode.pnames = pnames\n\t\t\t}\n\t\t\tcurrentNode.isLeaf = currentNode.children == nil && currentNode.paramChild == nil && currentNode.anyChild == nil\n\t\t} else if lcpLen < prefixLen {\n\t\t\t// Split node\n\t\t\tn := newNode(\n\t\t\t\tcurrentNode.kind,\n\t\t\t\tcurrentNode.prefix[lcpLen:],\n\t\t\t\tcurrentNode,\n\t\t\t\tcurrentNode.children,\n\t\t\t\tcurrentNode.handlers,\n\t\t\t\tcurrentNode.ppath,\n\t\t\t\tcurrentNode.pnames,\n\t\t\t\tcurrentNode.paramChild,\n\t\t\t\tcurrentNode.anyChild,\n\t\t\t)\n\t\t\t// Update parent path for all children to new node\n\t\t\tfor _, child := range currentNode.children {\n\t\t\t\tchild.parent = n\n\t\t\t}\n\t\t\tif currentNode.paramChild != nil {\n\t\t\t\tcurrentNode.paramChild.parent = n\n\t\t\t}\n\t\t\tif currentNode.anyChild != nil {\n\t\t\t\tcurrentNode.anyChild.parent = n\n\t\t\t}\n\n\t\t\t// Reset parent node\n\t\t\tcurrentNode.kind = skind\n\t\t\tcurrentNode.label = currentNode.prefix[0]\n\t\t\tcurrentNode.prefix = currentNode.prefix[:lcpLen]\n\t\t\tcurrentNode.children = nil\n\t\t\tcurrentNode.handlers = nil\n\t\t\tcurrentNode.ppath = nilString\n\t\t\tcurrentNode.pnames = nil\n\t\t\tcurrentNode.paramChild = nil\n\t\t\tcurrentNode.anyChild = nil\n\t\t\tcurrentNode.isLeaf = false\n\n\t\t\t// Only Static children could reach here\n\t\t\tcurrentNode.children = append(currentNode.children, n)\n\n\t\t\tif lcpLen == searchLen {\n\t\t\t\t// At parent node\n\t\t\t\tcurrentNode.kind = t\n\t\t\t\tcurrentNode.handlers = h\n\t\t\t\tcurrentNode.ppath = ppath\n\t\t\t\tcurrentNode.pnames = pnames\n\t\t\t} else {\n\t\t\t\t// Create child node\n\t\t\t\tn = newNode(t, search[lcpLen:], currentNode, nil, h, ppath, pnames, nil, nil)\n\t\t\t\t// Only Static children could reach here\n\t\t\t\tcurrentNode.children = append(currentNode.children, n)\n\t\t\t}\n\t\t\tcurrentNode.isLeaf = currentNode.children == nil && currentNode.paramChild == nil && currentNode.anyChild == nil\n\t\t} else if lcpLen < searchLen {\n\t\t\tsearch = search[lcpLen:]\n\t\t\tc := currentNode.findChildWithLabel(search[0])\n\t\t\tif c != nil {\n\t\t\t\t// Go deeper\n\t\t\t\tcurrentNode = c\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Create child node\n\t\t\tn := newNode(t, search, currentNode, nil, h, ppath, pnames, nil, nil)\n\t\t\tswitch t {\n\t\t\tcase skind:\n\t\t\t\tcurrentNode.children = append(currentNode.children, n)\n\t\t\tcase pkind:\n\t\t\t\tcurrentNode.paramChild = n\n\t\t\tcase akind:\n\t\t\t\tcurrentNode.anyChild = n\n\t\t\t}\n\t\t\tcurrentNode.isLeaf = currentNode.children == nil && currentNode.paramChild == nil && currentNode.anyChild == nil\n\t\t} else {\n\t\t\t// Node already exists\n\t\t\tif currentNode.handlers != nil && h != nil {\n\t\t\t\tpanic(\"handlers are already registered for path '\" + ppath + \"'\")\n\t\t\t}\n\n\t\t\tif h != nil {\n\t\t\t\tcurrentNode.handlers = h\n\t\t\t\tcurrentNode.ppath = ppath\n\t\t\t\tcurrentNode.pnames = pnames\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n}\n\n// find finds registered handler by method and path, parses URL params and puts params to context\nfunc (r *router) find(path string, paramsPointer *param.Params, unescape bool) (res nodeValue) {\n\tvar (\n\t\tcn          = r.root // current node\n\t\tsearch      = path   // current path\n\t\tsearchIndex = 0\n\t\tbuf         []byte\n\t\tparamIndex  int\n\t)\n\n\tbacktrackToNextNodeKind := func(fromKind kind) (nextNodeKind kind, valid bool) {\n\t\tprevious := cn\n\t\tcn = previous.parent\n\t\tvalid = cn != nil\n\n\t\t// Next node type by priority\n\t\tif previous.kind == akind {\n\t\t\tnextNodeKind = skind\n\t\t} else {\n\t\t\tnextNodeKind = previous.kind + 1\n\t\t}\n\n\t\tif fromKind == skind {\n\t\t\t// when backtracking is done from static kind block we did not change search so nothing to restore\n\t\t\treturn\n\t\t}\n\n\t\t// restore search to value it was before we move to current node we are backtracking from.\n\t\tif previous.kind == skind {\n\t\t\tsearchIndex -= len(previous.prefix)\n\t\t} else {\n\t\t\tparamIndex--\n\t\t\t// for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue\n\t\t\t// for that index as it would also contain part of path we cut off before moving into node we are backtracking from\n\t\t\tsearchIndex -= len((*paramsPointer)[paramIndex].Value)\n\t\t\t(*paramsPointer) = (*paramsPointer)[:paramIndex]\n\t\t}\n\t\tsearch = path[searchIndex:]\n\t\treturn\n\t}\n\n\t// search order: static > param > any\n\tfor {\n\t\tif cn.kind == skind {\n\t\t\tif len(search) >= len(cn.prefix) && cn.prefix == search[:len(cn.prefix)] {\n\t\t\t\t// Continue search\n\t\t\t\tsearch = search[len(cn.prefix):]\n\t\t\t\tsearchIndex = searchIndex + len(cn.prefix)\n\t\t\t} else {\n\t\t\t\t// not equal\n\t\t\t\tif (len(cn.prefix) == len(search)+1) &&\n\t\t\t\t\t(cn.prefix[len(search)]) == '/' && cn.prefix[:len(search)] == search && (cn.handlers != nil || cn.anyChild != nil) {\n\t\t\t\t\tres.tsr = true\n\t\t\t\t}\n\t\t\t\t// No matching prefix, let's backtrack to the first possible alternative node of the decision path\n\t\t\t\tnk, ok := backtrackToNextNodeKind(skind)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn // No other possibilities on the decision path\n\t\t\t\t} else if nk == pkind {\n\t\t\t\t\tgoto Param\n\t\t\t\t} else {\n\t\t\t\t\t// Not found (this should never be possible for static node we are looking currently)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif search == nilString && len(cn.handlers) != 0 {\n\t\t\tres.handlers = cn.handlers\n\t\t\tbreak\n\t\t}\n\n\t\t// Static node\n\t\tif search != nilString {\n\t\t\t// If it can execute that logic, there is handler registered on the current node and search is `/`.\n\t\t\tif search == \"/\" && cn.handlers != nil {\n\t\t\t\tres.tsr = true\n\t\t\t}\n\t\t\tif child := cn.findChild(search[0]); child != nil {\n\t\t\t\tcn = child\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif search == nilString {\n\t\t\tif cd := cn.findChild('/'); cd != nil && (cd.handlers != nil || cd.anyChild != nil) {\n\t\t\t\tres.tsr = true\n\t\t\t}\n\t\t}\n\n\tParam:\n\t\t// Param node\n\t\tif child := cn.paramChild; search != nilString && child != nil {\n\t\t\tcn = child\n\t\t\ti := strings.Index(search, slash)\n\t\t\tif i == -1 {\n\t\t\t\ti = len(search)\n\t\t\t}\n\t\t\t(*paramsPointer) = (*paramsPointer)[:(paramIndex + 1)]\n\t\t\tval := search[:i]\n\t\t\tif unescape {\n\t\t\t\tif v, err := url.QueryUnescape(search[:i]); err == nil {\n\t\t\t\t\tval = v\n\t\t\t\t}\n\t\t\t}\n\t\t\t(*paramsPointer)[paramIndex].Value = val\n\t\t\tparamIndex++\n\t\t\tsearch = search[i:]\n\t\t\tsearchIndex = searchIndex + i\n\t\t\tif search == nilString {\n\t\t\t\tif cd := cn.findChild('/'); cd != nil && (cd.handlers != nil || cd.anyChild != nil) {\n\t\t\t\t\tres.tsr = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\tAny:\n\t\t// Any node\n\t\tif child := cn.anyChild; child != nil {\n\t\t\t// If any node is found, use remaining path for paramValues\n\t\t\tcn = child\n\t\t\t(*paramsPointer) = (*paramsPointer)[:(paramIndex + 1)]\n\t\t\tindex := len(cn.pnames) - 1\n\t\t\tval := search\n\t\t\tif unescape {\n\t\t\t\tif v, err := url.QueryUnescape(search); err == nil {\n\t\t\t\t\tval = v\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t(*paramsPointer)[index].Value = bytesconv.B2s(append(buf, val...))\n\t\t\t// update indexes/search in case we need to backtrack when no handler match is found\n\t\t\tparamIndex++\n\t\t\tsearchIndex += len(search)\n\t\t\tsearch = nilString\n\t\t\tres.handlers = cn.handlers\n\t\t\tbreak\n\t\t}\n\n\t\t// Let's backtrack to the first possible alternative node of the decision path\n\t\tnk, ok := backtrackToNextNodeKind(akind)\n\t\tif !ok {\n\t\t\tbreak // No other possibilities on the decision path\n\t\t} else if nk == pkind {\n\t\t\tgoto Param\n\t\t} else if nk == akind {\n\t\t\tgoto Any\n\t\t} else {\n\t\t\t// Not found\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif cn != nil {\n\t\tres.fullPath = cn.ppath\n\t\tfor i, name := range cn.pnames {\n\t\t\t(*paramsPointer)[i].Key = name\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *node) findChild(l byte) *node {\n\tfor _, c := range n.children {\n\t\tif c.label == l {\n\t\t\treturn c\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (n *node) findChildWithLabel(l byte) *node {\n\tfor _, c := range n.children {\n\t\tif c.label == l {\n\t\t\treturn c\n\t\t}\n\t}\n\tif l == paramLabel {\n\t\treturn n.paramChild\n\t}\n\tif l == anyLabel {\n\t\treturn n.anyChild\n\t}\n\treturn nil\n}\n\nfunc newNode(t kind, pre string, p *node, child children, mh app.HandlersChain, ppath string, pnames []string, paramChildren, anyChildren *node) *node {\n\treturn &node{\n\t\tkind:       t,\n\t\tlabel:      pre[0],\n\t\tprefix:     pre,\n\t\tparent:     p,\n\t\tchildren:   child,\n\t\tppath:      ppath,\n\t\tpnames:     pnames,\n\t\thandlers:   mh,\n\t\tparamChild: paramChildren,\n\t\tanyChild:   anyChildren,\n\t\tisLeaf:     child == nil && paramChildren == nil && anyChildren == nil,\n\t}\n}\n\n// nodeValue holds return values of (*Node).getValue method\ntype nodeValue struct {\n\thandlers app.HandlersChain\n\ttsr      bool\n\tfullPath string\n}\n\n// Makes a case-insensitive lookup of the given path and tries to find a handler.\n// It returns the case-corrected path and a bool indicating whether the lookup\n// was successful.\nfunc (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {\n\tciPath = make([]byte, 0, len(path)+1) // preallocate enough memory\n\t// Match paramKind.\n\tif n.label == paramLabel {\n\t\tend := 0\n\t\tfor end < len(path) && path[end] != '/' {\n\t\t\tend++\n\t\t}\n\t\tciPath = append(ciPath, path[:end]...)\n\t\tif end < len(path) {\n\t\t\tif len(n.children) > 0 {\n\t\t\t\tpath = path[end:]\n\n\t\t\t\tgoto loop\n\t\t\t}\n\n\t\t\tif fixTrailingSlash && len(path) == end+1 {\n\t\t\t\treturn ciPath, true\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif n.handlers != nil {\n\t\t\treturn ciPath, true\n\t\t}\n\t\tif fixTrailingSlash && len(n.children) == 1 {\n\t\t\t// No handle found. Check if a handle for this path with(without) a trailing slash exists\n\t\t\tn = n.children[0]\n\t\t\tif n.prefix == \"/\" && n.handlers != nil {\n\t\t\t\treturn append(ciPath, '/'), true\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\t// Match allKind.\n\tif n.label == anyLabel {\n\t\treturn append(ciPath, path...), true\n\t}\n\n\t// Match static kind.\n\tif len(path) >= len(n.prefix) && strings.EqualFold(path[:len(n.prefix)], n.prefix) {\n\t\tpath = path[len(n.prefix):]\n\t\tciPath = append(ciPath, n.prefix...)\n\n\t\tif len(path) == 0 {\n\t\t\tif n.handlers != nil {\n\t\t\t\treturn ciPath, true\n\t\t\t}\n\t\t\t// No handle found.\n\t\t\t// Try to fix the path by adding a trailing slash.\n\t\t\tif fixTrailingSlash {\n\t\t\t\tfor i := 0; i < len(n.children); i++ {\n\t\t\t\t\tif n.children[i].label == '/' {\n\t\t\t\t\t\tn = n.children[i]\n\t\t\t\t\t\tif (len(n.prefix) == 1 && n.handlers != nil) ||\n\t\t\t\t\t\t\t(n.prefix == \"*\" && n.children[0].handlers != nil) {\n\t\t\t\t\t\t\treturn append(ciPath, '/'), true\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else if fixTrailingSlash {\n\t\t// Nothing found.\n\t\t// Try to fix the path by adding / removing a trailing slash.\n\t\tif path == \"/\" {\n\t\t\treturn ciPath, true\n\t\t}\n\t\tif len(path)+1 == len(n.prefix) && n.prefix[len(path)] == '/' &&\n\t\t\tstrings.EqualFold(path, n.prefix[:len(path)]) &&\n\t\t\tn.handlers != nil {\n\t\t\treturn append(ciPath, n.prefix...), true\n\t\t}\n\t}\n\nloop:\n\t// First match static kind.\n\tfor _, node := range n.children {\n\t\tif unicode.ToLower(rune(path[0])) == unicode.ToLower(rune(node.label)) {\n\t\t\tout, found := node.findCaseInsensitivePath(path, fixTrailingSlash)\n\t\t\tif found {\n\t\t\t\treturn append(ciPath, out...), true\n\t\t\t}\n\t\t}\n\t}\n\n\tif n.paramChild != nil {\n\t\tout, found := n.paramChild.findCaseInsensitivePath(path, fixTrailingSlash)\n\t\tif found {\n\t\t\treturn append(ciPath, out...), true\n\t\t}\n\t}\n\n\tif n.anyChild != nil {\n\t\tout, found := n.anyChild.findCaseInsensitivePath(path, fixTrailingSlash)\n\t\tif found {\n\t\t\treturn append(ciPath, out...), true\n\t\t}\n\t}\n\n\t// Nothing found. We can recommend to redirect to the same URL\n\t// without a trailing slash if a leaf exists for that path\n\tfound = fixTrailingSlash && path == \"/\" && n.handlers != nil\n\treturn\n}\n"
  },
  {
    "path": "pkg/route/tree_test.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Copyright 2013 Julien Schmidt. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be found\n * at https://github.com/julienschmidt/httprouter/blob/master/LICENSE\n *\n * This file may have been modified by CloudWeGo authors. All CloudWeGo\n * Modifications are Copyright 2022 CloudWeGo Authors.\n */\n\npackage route\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cloudwego/hertz/pkg/app\"\n\t\"github.com/cloudwego/hertz/pkg/route/param\"\n)\n\n// Used as a workaround since we can't compare functions or their addresses\nvar fakeHandlerValue string\n\nfunc fakeHandler(val string) app.HandlersChain {\n\treturn app.HandlersChain{func(c context.Context, ctx *app.RequestContext) {\n\t\tfakeHandlerValue = val\n\t}}\n}\n\ntype testRequests []struct {\n\tpath       string\n\tnilHandler bool\n\troute      string\n\tps         param.Params\n}\n\nfunc getParams() *param.Params {\n\tps := make(param.Params, 0, 20)\n\treturn &ps\n}\n\nfunc checkRequests(t *testing.T, tree *router, requests testRequests, unescapes ...bool) {\n\tunescape := false\n\tif len(unescapes) >= 1 {\n\t\tunescape = unescapes[0]\n\t}\n\n\tfor _, request := range requests {\n\t\tparams := getParams()\n\t\tvalue := tree.find(request.path, params, unescape)\n\n\t\tif value.handlers == nil {\n\t\t\tif !request.nilHandler {\n\t\t\t\tt.Errorf(\"handle mismatch for route '%s': Expected non-nil handle\", request.path)\n\t\t\t}\n\t\t} else if request.nilHandler {\n\t\t\tt.Errorf(\"handle mismatch for route '%s': Expected nil handle\", request.path)\n\t\t} else {\n\t\t\tvalue.handlers[0](context.Background(), nil)\n\t\t\tif fakeHandlerValue != request.route {\n\t\t\t\tt.Errorf(\"handle mismatch for route '%s': Wrong handle (%s != %s)\", request.path, fakeHandlerValue, request.route)\n\t\t\t}\n\t\t}\n\t\tfor _, item := range request.ps {\n\t\t\tif item.Value != (*params).ByName(item.Key) {\n\t\t\t\tt.Errorf(\"mismatch params. path: %s, key: %s, expected value: %s, actual value: %s\", request.path, item.Key, item.Value, (*params).ByName(item.Key))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestCountParams(t *testing.T) {\n\tif countParams(\"/path/:param1/static/*catch-all\") != 2 {\n\t\tt.Fail()\n\t}\n\tif countParams(strings.Repeat(\"/:param\", 256)) != 256 {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestEmptyPath(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"\",\n\t\t\"user\",\n\t\t\":user\",\n\t\t\"*user\",\n\t}\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, nil)\n\t\t})\n\t\tif recv == nil {\n\t\t\tt.Fatalf(\"no panic while inserting route with empty path '%s\", route)\n\t\t}\n\t}\n}\n\nfunc TestTreeAddAndGet(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/hi\",\n\t\t\"/contact\",\n\t\t\"/co\",\n\t\t\"/c\",\n\t\t\"/a\",\n\t\t\"/ab\",\n\t\t\"/doc/\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/α\",\n\t\t\"/β\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"\", true, \"\", nil},\n\t\t{\"a\", true, \"\", nil},\n\t\t{\"/a\", false, \"/a\", nil},\n\t\t{\"/\", true, \"\", nil},\n\t\t{\"/hi\", false, \"/hi\", nil},\n\t\t{\"/contact\", false, \"/contact\", nil},\n\t\t{\"/co\", false, \"/co\", nil},\n\t\t{\"/con\", true, \"\", nil},  // key mismatch\n\t\t{\"/cona\", true, \"\", nil}, // key mismatch\n\t\t{\"/no\", true, \"\", nil},   // no matching child\n\t\t{\"/ab\", false, \"/ab\", nil},\n\t\t{\"/α\", false, \"/α\", nil},\n\t\t{\"/β\", false, \"/β\", nil},\n\t})\n}\n\nfunc TestTreeWildcard(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/\",\n\t\t\"/cmd/:tool/:sub\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/cmd/xxx/\",\n\t\t\"/src/*filepath\",\n\t\t\"/search/\",\n\t\t\"/search/:query\",\n\t\t\"/user_:name\",\n\t\t\"/user_:name/about\",\n\t\t\"/files/:dir/*filepath\",\n\t\t\"/doc/\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/info/:user/public\",\n\t\t\"/info/:user/project/:project\",\n\t\t\"/a/b/:c\",\n\t\t\"/a/:b/c/d\",\n\t\t\"/a/*b\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/\", false, \"/\", nil},\n\t\t{\"/cmd/test/\", false, \"/cmd/:tool/\", param.Params{param.Param{Key: \"tool\", Value: \"test\"}}},\n\t\t{\"/cmd/test\", true, \"\", nil},\n\t\t{\"/cmd/test/3\", false, \"/cmd/:tool/:sub\", param.Params{param.Param{Key: \"tool\", Value: \"test\"}, param.Param{Key: \"sub\", Value: \"3\"}}},\n\t\t{\"/src/\", false, \"/src/*filepath\", param.Params{param.Param{Key: \"filepath\", Value: \"\"}}},\n\t\t{\"/src/some/file.png\", false, \"/src/*filepath\", param.Params{param.Param{Key: \"filepath\", Value: \"some/file.png\"}}},\n\t\t{\"/search/\", false, \"/search/\", nil},\n\t\t{\"/search/someth!ng+in+ünìcodé\", false, \"/search/:query\", param.Params{param.Param{Key: \"query\", Value: \"someth!ng+in+ünìcodé\"}}},\n\t\t{\"/search/someth!ng+in+ünìcodé/\", true, \"\", nil},\n\t\t{\"/user_gopher\", false, \"/user_:name\", param.Params{param.Param{Key: \"name\", Value: \"gopher\"}}},\n\t\t{\"/user_gopher/about\", false, \"/user_:name/about\", param.Params{param.Param{Key: \"name\", Value: \"gopher\"}}},\n\t\t{\"/files/js/inc/framework.js\", false, \"/files/:dir/*filepath\", param.Params{param.Param{Key: \"dir\", Value: \"js\"}, param.Param{Key: \"filepath\", Value: \"inc/framework.js\"}}},\n\t\t{\"/info/gordon/public\", false, \"/info/:user/public\", param.Params{param.Param{Key: \"user\", Value: \"gordon\"}}},\n\t\t{\"/info/gordon/project/go\", false, \"/info/:user/project/:project\", param.Params{param.Param{Key: \"user\", Value: \"gordon\"}, param.Param{Key: \"project\", Value: \"go\"}}},\n\t\t{\"/a/b/c\", false, \"/a/b/:c\", param.Params{param.Param{Key: \"c\", Value: \"c\"}}},\n\t\t{\"/a/b/c/d\", false, \"/a/:b/c/d\", param.Params{param.Param{Key: \"b\", Value: \"b\"}}},\n\t\t{\"/a/b\", false, \"/a/*b\", param.Params{param.Param{Key: \"b\", Value: \"b\"}}},\n\t})\n}\n\nfunc TestUnescapeParameters(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/\",\n\t\t\"/cmd/:tool/:sub\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/search/:query\",\n\t\t\"/files/:dir/*filepath\",\n\t\t\"/info/:user/project/:project\",\n\t\t\"/info/:user\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\n\tunescape := true\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/\", false, \"/\", nil},\n\t\t{\"/cmd/test/\", false, \"/cmd/:tool/\", param.Params{param.Param{Key: \"tool\", Value: \"test\"}}},\n\t\t{\"/cmd/test\", true, \"\", nil},\n\t\t{\"/src/some/file.png\", false, \"/src/*filepath\", param.Params{param.Param{Key: \"filepath\", Value: \"some/file.png\"}}},\n\t\t{\"/src/some/file+test.png\", false, \"/src/*filepath\", param.Params{param.Param{Key: \"filepath\", Value: \"some/file test.png\"}}},\n\t\t{\"/src/some/file++++%%%%test.png\", false, \"/src/*filepath\", param.Params{param.Param{Key: \"filepath\", Value: \"some/file++++%%%%test.png\"}}},\n\t\t{\"/src/some/file%2Ftest.png\", false, \"/src/*filepath\", param.Params{param.Param{Key: \"filepath\", Value: \"some/file/test.png\"}}},\n\t\t{\"/search/someth!ng+in+ünìcodé\", false, \"/search/:query\", param.Params{param.Param{Key: \"query\", Value: \"someth!ng in ünìcodé\"}}},\n\t\t{\"/info/gordon/project/go\", false, \"/info/:user/project/:project\", param.Params{param.Param{Key: \"user\", Value: \"gordon\"}, param.Param{Key: \"project\", Value: \"go\"}}},\n\t\t{\"/info/slash%2Fgordon\", false, \"/info/:user\", param.Params{param.Param{Key: \"user\", Value: \"slash/gordon\"}}},\n\t\t{\"/info/slash%2Fgordon/project/Project%20%231\", false, \"/info/:user/project/:project\", param.Params{param.Param{Key: \"user\", Value: \"slash/gordon\"}, param.Param{Key: \"project\", Value: \"Project #1\"}}},\n\t\t{\"/info/slash%%%%\", false, \"/info/:user\", param.Params{param.Param{Key: \"user\", Value: \"slash%%%%\"}}},\n\t\t{\"/info/slash%%%%2Fgordon/project/Project%%%%20%231\", false, \"/info/:user/project/:project\", param.Params{param.Param{Key: \"user\", Value: \"slash%%%%2Fgordon\"}, param.Param{Key: \"project\", Value: \"Project%%%%20%231\"}}},\n\t}, unescape)\n}\n\nfunc catchPanic(testFunc func()) (recv interface{}) {\n\tdefer func() {\n\t\trecv = recover()\n\t}()\n\n\ttestFunc()\n\treturn\n}\n\ntype testRoute struct {\n\tpath     string\n\tconflict bool\n}\n\nfunc testRoutes(t *testing.T, routes []testRoute) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route.path, []app.HandlerFunc{\n\t\t\t\tfunc(c context.Context, ctx *app.RequestContext) {\n\t\t\t\t\tfmt.Println(\"test\")\n\t\t\t\t},\n\t\t\t})\n\t\t})\n\n\t\tif route.conflict {\n\t\t\tif recv == nil {\n\t\t\t\tt.Errorf(\"no panic for conflicting route '%s'\", route.path)\n\t\t\t}\n\t\t} else if recv != nil {\n\t\t\tt.Errorf(\"unexpected panic for route '%s': %v\", route.path, recv)\n\t\t}\n\t}\n}\n\nfunc TestTreeWildcardConflict(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/cmd/vet\", false},\n\t\t{\"/cmd/:tool/:sub\", false},\n\t\t{\"/src/*filepath\", false},\n\t\t{\"/src/*filepathx\", true},\n\t\t{\"/src/\", false},\n\t\t{\"/src1/\", false},\n\t\t{\"/src1/*filepath\", false},\n\t\t{\"/src2*filepath\", true},\n\t\t{\"/search/:query\", false},\n\t\t{\"/search/invalid\", false},\n\t\t{\"/user_:name\", false},\n\t\t{\"/user_x\", false},\n\t\t{\"/user_:name\", true},\n\t\t{\"/id:id\", false},\n\t\t{\"/id/:id\", false},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeChildConflict(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/cmd/vet\", false},\n\t\t{\"/cmd/:tool/:sub\", false},\n\t\t{\"/src/AUTHORS\", false},\n\t\t{\"/src/*filepath\", false},\n\t\t{\"/user_x\", false},\n\t\t{\"/user_:name\", false},\n\t\t{\"/id/:id\", false},\n\t\t{\"/id:id\", false},\n\t\t{\"/:id\", false},\n\t\t{\"/*filepath\", false},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeDuplicatePath(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/\",\n\t\t\"/doc/\",\n\t\t\"/src/*filepath\",\n\t\t\"/search/:query\",\n\t\t\"/user_:name\",\n\t}\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\n\t\t// Add again\n\t\trecv = catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv == nil {\n\t\t\tt.Fatalf(\"no panic while inserting duplicate route '%s\", route)\n\t\t}\n\t}\n\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/\", false, \"/\", nil},\n\t\t{\"/doc/\", false, \"/doc/\", nil},\n\t\t{\"/src/some/file.png\", false, \"/src/*filepath\", param.Params{param.Param{Key: \"filepath\", Value: \"some/file.png\"}}},\n\t\t{\"/search/someth!ng+in+ünìcodé\", false, \"/search/:query\", param.Params{param.Param{Key: \"query\", Value: \"someth!ng+in+ünìcodé\"}}},\n\t\t{\"/user_gopher\", false, \"/user_:name\", param.Params{param.Param{Key: \"name\", Value: \"gopher\"}}},\n\t})\n}\n\nfunc TestEmptyWildcardName(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/user:\",\n\t\t\"/user:/\",\n\t\t\"/cmd/:/\",\n\t\t\"/src/*\",\n\t}\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, nil)\n\t\t})\n\t\tif recv == nil {\n\t\t\tt.Fatalf(\"no panic while inserting route with empty wildcard name '%s\", route)\n\t\t}\n\t}\n}\n\nfunc TestTreeCatchAllConflict(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/src/*filepath/x\", true},\n\t\t{\"/src2/\", false},\n\t\t{\"/src2/*filepath/x\", true},\n\t\t{\"/src3/*filepath\", false},\n\t\t{\"/src3/*filepath/x\", true},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeCatchMaxParams(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\troute := \"/cmd/*filepath\"\n\ttree.addRoute(route, fakeHandler(route))\n}\n\nfunc TestTreeDoubleWildcard(t *testing.T) {\n\tconst panicMsg = \"only one wildcard per path segment is allowed\"\n\n\troutes := [...]string{\n\t\t\"/:foo:bar\",\n\t\t\"/:foo:bar/\",\n\t\t\"/:foo*bar\",\n\t}\n\n\tfor _, route := range routes {\n\t\ttree := &router{method: \"GET\", root: &node{}}\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, nil)\n\t\t})\n\n\t\tif rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) {\n\t\t\tt.Fatalf(`\"Expected panic \"%s\" for route '%s', got \"%v\"`, panicMsg, route, recv)\n\t\t}\n\t}\n}\n\nfunc TestTreeTrailingSlashRedirect2(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/api/:version/seller/locales/get\",\n\t\t\"/api/v:version/seller/permissions/get\",\n\t\t\"/api/v:version/seller/university/entrance_knowledge_list/get\",\n\t}\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\t}\n\tv := make(param.Params, 0, 1)\n\n\ttsrRoutes := [...]string{\n\t\t\"/api/v:version/seller/permissions/get/\",\n\t\t\"/api/version/seller/permissions/get/\",\n\t}\n\n\tfor _, route := range tsrRoutes {\n\t\tvalue := tree.find(route, &v, false)\n\t\tif value.handlers != nil {\n\t\t\tt.Fatalf(\"non-nil handler for TSR route '%s\", route)\n\t\t} else if !value.tsr {\n\t\t\tt.Errorf(\"expected TSR recommendation for route '%s'\", route)\n\t\t}\n\t}\n\n\tnoTsrRoutes := [...]string{\n\t\t\"/api/v:version/seller/permissions/get/a\",\n\t}\n\tfor _, route := range noTsrRoutes {\n\t\tvalue := tree.find(route, &v, false)\n\t\tif value.handlers != nil {\n\t\t\tt.Fatalf(\"non-nil handler for No-TSR route '%s\", route)\n\t\t} else if value.tsr {\n\t\t\tt.Errorf(\"expected no TSR recommendation for route '%s'\", route)\n\t\t}\n\t}\n}\n\nfunc TestTreeTrailingSlashRedirect(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\troutes := [...]string{\n\t\t\"/hi\",\n\t\t\"/b/\",\n\t\t\"/search/:query\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/x\",\n\t\t\"/x/y\",\n\t\t\"/y/\",\n\t\t\"/y/z\",\n\t\t\"/0/:id\",\n\t\t\"/0/:id/1\",\n\t\t\"/1/:id/\",\n\t\t\"/1/:id/2\",\n\t\t\"/aa\",\n\t\t\"/a/\",\n\t\t\"/admin\",\n\t\t\"/admin/:category\",\n\t\t\"/admin/:category/:page\",\n\t\t\"/doc\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/no/a\",\n\t\t\"/no/b\",\n\t\t\"/api/hello/:name\",\n\t\t\"/user/:name/*id\",\n\t\t\"/resource\",\n\t\t\"/r/*id\",\n\t\t\"/book/biz/:name\",\n\t\t\"/book/biz/abc\",\n\t\t\"/book/biz/abc/bar\",\n\t\t\"/book/:page/:name\",\n\t\t\"/book/hello/:name/biz/\",\n\t}\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\t}\n\n\ttsrRoutes := [...]string{\n\t\t\"/hi/\",\n\t\t\"/b\",\n\t\t\"/search/gopher/\",\n\t\t\"/cmd/vet\",\n\t\t\"/src\",\n\t\t\"/x/\",\n\t\t\"/y\",\n\t\t\"/0/go/\",\n\t\t\"/1/go\",\n\t\t\"/a\",\n\t\t\"/admin/\",\n\t\t\"/admin/config/\",\n\t\t\"/admin/config/permissions/\",\n\t\t\"/doc/\",\n\t\t\"/user/name\",\n\t\t\"/r\",\n\t\t\"/book/hello/a/biz\",\n\t\t\"/book/biz/foo/\",\n\t\t\"/book/biz/abc/bar/\",\n\t}\n\tv := make(param.Params, 0, 10)\n\tfor _, route := range tsrRoutes {\n\t\tvalue := tree.find(route, &v, false)\n\t\tif value.handlers != nil {\n\t\t\tt.Fatalf(\"non-nil handler for TSR route '%s\", route)\n\t\t} else if !value.tsr {\n\t\t\tt.Errorf(\"expected TSR recommendation for route '%s'\", route)\n\t\t}\n\t}\n\n\tnoTsrRoutes := [...]string{\n\t\t\"/\",\n\t\t\"/no\",\n\t\t\"/no/\",\n\t\t\"/_\",\n\t\t\"/_/\",\n\t\t\"/api/world/abc\",\n\t\t\"/book\",\n\t\t\"/book/\",\n\t\t\"/book/hello/a/abc\",\n\t\t\"/book/biz/abc/biz\",\n\t}\n\tfor _, route := range noTsrRoutes {\n\t\tvalue := tree.find(route, &v, false)\n\t\tif value.handlers != nil {\n\t\t\tt.Fatalf(\"non-nil handler for No-TSR route '%s\", route)\n\t\t} else if value.tsr {\n\t\t\tt.Errorf(\"expected no TSR recommendation for route '%s'\", route)\n\t\t}\n\t}\n}\n\nfunc TestTreeRootTrailingSlashRedirect(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\trecv := catchPanic(func() {\n\t\ttree.addRoute(\"/:test\", fakeHandler(\"/:test\"))\n\t})\n\tif recv != nil {\n\t\tt.Fatalf(\"panic inserting test route: %v\", recv)\n\t}\n\n\tvalue := tree.find(\"/\", nil, false)\n\tif value.handlers != nil {\n\t\tt.Fatalf(\"non-nil handler\")\n\t} else if value.tsr {\n\t\tt.Errorf(\"expected no TSR recommendation\")\n\t}\n}\n\nfunc TestTreeFindCaseInsensitivePath(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\n\tlongPath := \"/l\" + strings.Repeat(\"o\", 128) + \"ng\"\n\tlOngPath := \"/l\" + strings.Repeat(\"O\", 128) + \"ng/\"\n\n\troutes := [...]string{\n\t\t\"/hi\",\n\t\t\"/b/\",\n\t\t\"/ABC/\",\n\t\t\"/search/:query\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/x\",\n\t\t\"/x/y\",\n\t\t\"/y/\",\n\t\t\"/y/z\",\n\t\t\"/0/:id\",\n\t\t\"/0/:id/1\",\n\t\t\"/1/:id/\",\n\t\t\"/1/:id/2\",\n\t\t\"/aa\",\n\t\t\"/a/\",\n\t\t\"/doc\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/doc/go/away\",\n\t\t\"/no/a\",\n\t\t\"/no/b\",\n\t\t\"/z/:id/2\",\n\t\t\"/z/:id/:age\",\n\t\t\"/x/:id/3/\",\n\t\t\"/x/:id/3/4\",\n\t\t\"/x/:id/:age/5\",\n\t\tlongPath,\n\t}\n\n\tfor _, route := range routes {\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\t}\n\n\t// Check out == in for all registered routes\n\t// With fixTrailingSlash = true\n\tfor _, route := range routes {\n\t\tout, found := tree.root.findCaseInsensitivePath(route, true)\n\t\tif !found {\n\t\t\tt.Errorf(\"Route '%s' not found!\", route)\n\t\t} else if string(out) != route {\n\t\t\tt.Errorf(\"Wrong result for route '%s': %s\", route, string(out))\n\t\t}\n\t}\n\t// With fixTrailingSlash = false\n\tfor _, route := range routes {\n\t\tout, found := tree.root.findCaseInsensitivePath(route, false)\n\t\tif !found {\n\t\t\tt.Errorf(\"Route '%s' not found!\", route)\n\t\t} else if string(out) != route {\n\t\t\tt.Errorf(\"Wrong result for route '%s': %s\", route, string(out))\n\t\t}\n\t}\n\n\ttests := []struct {\n\t\tin    string\n\t\tout   string\n\t\tfound bool\n\t\tslash bool\n\t}{\n\t\t{\"/HI\", \"/hi\", true, false},\n\t\t{\"/HI/\", \"/hi\", true, true},\n\t\t{\"/B\", \"/b/\", true, true},\n\t\t{\"/B/\", \"/b/\", true, false},\n\t\t{\"/abc\", \"/ABC/\", true, true},\n\t\t{\"/abc/\", \"/ABC/\", true, false},\n\t\t{\"/aBc\", \"/ABC/\", true, true},\n\t\t{\"/aBc/\", \"/ABC/\", true, false},\n\t\t{\"/abC\", \"/ABC/\", true, true},\n\t\t{\"/abC/\", \"/ABC/\", true, false},\n\t\t{\"/SEARCH/QUERY\", \"/search/QUERY\", true, false},\n\t\t{\"/SEARCH/QUERY/\", \"/search/QUERY\", true, true},\n\t\t{\"/CMD/TOOL/\", \"/cmd/TOOL/\", true, false},\n\t\t{\"/CMD/TOOL\", \"/cmd/TOOL/\", true, true},\n\t\t{\"/SRC/FILE/PATH\", \"/src/FILE/PATH\", true, false},\n\t\t{\"/x/Y\", \"/x/y\", true, false},\n\t\t{\"/x/Y/\", \"/x/y\", true, true},\n\t\t{\"/X/y\", \"/x/y\", true, false},\n\t\t{\"/X/y/\", \"/x/y\", true, true},\n\t\t{\"/X/Y\", \"/x/y\", true, false},\n\t\t{\"/X/Y/\", \"/x/y\", true, true},\n\t\t{\"/Y/\", \"/y/\", true, false},\n\t\t{\"/Y\", \"/y/\", true, true},\n\t\t{\"/Y/z\", \"/y/z\", true, false},\n\t\t{\"/Y/z/\", \"/y/z\", true, true},\n\t\t{\"/Y/Z\", \"/y/z\", true, false},\n\t\t{\"/Y/Z/\", \"/y/z\", true, true},\n\t\t{\"/y/Z\", \"/y/z\", true, false},\n\t\t{\"/y/Z/\", \"/y/z\", true, true},\n\t\t{\"/Aa\", \"/aa\", true, false},\n\t\t{\"/Aa/\", \"/aa\", true, true},\n\t\t{\"/AA\", \"/aa\", true, false},\n\t\t{\"/AA/\", \"/aa\", true, true},\n\t\t{\"/aA\", \"/aa\", true, false},\n\t\t{\"/aA/\", \"/aa\", true, true},\n\t\t{\"/A/\", \"/a/\", true, false},\n\t\t{\"/A\", \"/a/\", true, true},\n\t\t{\"/DOC\", \"/doc\", true, false},\n\t\t{\"/DOC/\", \"/doc\", true, true},\n\t\t{\"/NO\", \"\", false, true},\n\t\t{\"/DOC/GO\", \"\", false, true},\n\t\t{\"/Z/1/2\", \"/z/1/2\", true, false},\n\t\t{\"/Z/1/3\", \"/z/1/3\", true, false},\n\t\t{\"/Z/1/2/\", \"/z/1/2\", true, true},\n\t\t{\"/Z/1/3/\", \"/z/1/3\", true, true},\n\t\t{\"/X/1/3\", \"/x/1/3/\", true, true},\n\t\t{\"/X/1/3/5\", \"/x/1/3/5\", true, false},\n\t\t{lOngPath, longPath, true, true},\n\t}\n\t// With fixTrailingSlash = true\n\tfor _, test := range tests {\n\t\tout, found := tree.root.findCaseInsensitivePath(test.in, true)\n\t\tif found != test.found || (found && (string(out) != test.out)) {\n\t\t\tt.Errorf(\"Wrong result for '%s': got %s, %t; want %s, %t\",\n\t\t\t\ttest.in, string(out), found, test.out, test.found)\n\t\t\treturn\n\t\t}\n\t}\n\t// With fixTrailingSlash = false\n\tfor _, test := range tests {\n\t\tout, found := tree.root.findCaseInsensitivePath(test.in, false)\n\t\tif test.slash {\n\t\t\tif found { // test needs a trailingSlash fix. It must not be found!\n\t\t\t\tt.Errorf(\"Found without fixTrailingSlash: %s; got %s\", test.in, string(out))\n\t\t\t}\n\t\t} else {\n\t\t\tif found != test.found || (found && (string(out) != test.out)) {\n\t\t\t\tt.Errorf(\"Wrong result for '%s': got %s, %t; want %s, %t\",\n\t\t\t\t\ttest.in, string(out), found, test.out, test.found)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTreeParamNotOptimize(t *testing.T) {\n\ttree := &router{method: \"GET\", root: &node{}}\n\troutes := [...]string{\n\t\t\"/:parama/start\",\n\t\t\"/:paramb\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/1\", false, \"/:paramb\", param.Params{param.Param{Key: \"paramb\", Value: \"1\"}}},\n\t\t{\"/1/start\", false, \"/:parama/start\", param.Params{param.Param{Key: \"parama\", Value: \"1\"}}},\n\t})\n\n\t// other sequence\n\ttree = &router{method: \"GET\", root: &node{}}\n\troutes = [...]string{\n\t\t\"/:paramb\",\n\t\t\"/:parama/start\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/1/start\", false, \"/:parama/start\", param.Params{param.Param{Key: \"parama\", Value: \"1\"}}},\n\t\t{\"/1\", false, \"/:paramb\", param.Params{param.Param{Key: \"paramb\", Value: \"1\"}}},\n\t})\n}\n"
  },
  {
    "path": "scripts/.utils/check_go_mod.sh",
    "content": "#!/bin/bash\n# Copyright 2025 CloudWeGo Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n#\n# Go Module Dependency Checker\n#\n# This script validates that all CloudWeGo dependencies in go.mod use proper\n# semantic version tags (vA.B.C format) instead of commit hashes or branch names.\n#\n# Purpose:\n#   - Ensures CloudWeGo dependencies follow proper versioning practices\n#   - Validates go.mod contains only formal release versions\n#   - Prevents release with non-standard dependency versions\n#\n# Usage:\n#   ./scripts/.utils/check_go_mod.sh\n#\n# The script:\n#   1. Searches go.mod for github.com/cloudwego/* dependencies\n#   2. Checks that each dependency uses semantic versioning (vX.Y.Z)\n#   3. Reports any dependencies using non-standard versions\n#   4. Exits with error if invalid dependencies are found\n#\n# Called by:\n#   - release.sh (before creating release tags)\n#   - release-hotfix.sh (before creating hotfix tags)\n#\n\nGO_MOD_FILE=\"go.mod\"\n\ncd $(git rev-parse --show-toplevel)\n\n# Check go.mod for cloudwego dependencies using proper tag versions\necho \"🔍 Checking go.mod for cloudwego dependencies...\"\nif [ -f \"$GO_MOD_FILE\" ]; then\n    # Find cloudwego dependencies that don't use formal tag versions (vA.B.C format)\n    invalid_deps=$(grep \"github.com/cloudwego/\" \"$GO_MOD_FILE\" | \\\n      grep -v \"// indirect\" | grep -v \"module \" | \\\n      grep -v -E \"v[0-9]+\\.[0-9]+\\.[0-9]+$\" | awk '{print $1 \" \" $2}')\n\n    if [ -n \"$invalid_deps\" ]; then\n        echo \"❌ Error: Found cloudwego dependencies not using formal tag versions:\"\n        echo\n        echo \"   $invalid_deps\"\n        echo\n        echo \"   All github.com/cloudwego/* dependencies must use formal tag versions like v0.1.2 format\"\n        echo \"   Please update go.mod to use proper tagged versions for these dependencies\"\n        exit 1\n    else\n        echo \"✅ All cloudwego dependencies use formal tag versions\"\n    fi\nelse\n    echo \"⚠️  WARNING: go.mod not found\"\nfi\n"
  },
  {
    "path": "scripts/.utils/check_version.sh",
    "content": "#!/bin/bash\n# Copyright 2025 CloudWeGo Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n#\n# Version Consistency Checker\n#\n# This script validates that the version in version.go matches the intended\n# release version to ensure consistency across the codebase.\n#\n# Purpose:\n#   - Prevents releasing with mismatched version numbers\n#   - Ensures version.go is updated before creating release tags\n#   - Provides interactive confirmation for version mismatches\n#\n# Usage:\n#   ./scripts/.utils/check_version.sh <target_version>\n#\n# Parameters:\n#   target_version - The version being released (e.g., v1.2.3)\n#\n# The script:\n#   1. Reads the Version constant from version.go\n#   2. Compares it with the target release version\n#   3. Warns if versions don't match and prompts for confirmation\n#   4. Exits with error if user chooses not to continue\n#\n# Called by:\n#   - release.sh (before creating release tags)\n#   - release-hotfix.sh (before creating hotfix tags)\n#\n\nVERSION_FILE=\"version.go\"\nnew_version=$1\n\nif [ -z \"$new_version\" ]; then\n    echo \"❌ Error: Version parameter is required\"\n    echo \"Usage: $0 <version>\"\n    exit 1\nfi\n\ncd $(git rev-parse --show-toplevel)\n\nif [ -f \"$VERSION_FILE\" ]; then\n  version_go_version=$(grep -o 'Version = \"v[^\"]*\"' $VERSION_FILE | sed 's/Version = \"\\([^\"]*\\)\"/\\1/')\n  if [ \"$version_go_version\" != \"$new_version\" ]; then\n    echo \"⚠️  WARNING: $VERSION_FILE contains $version_go_version but you're releasing $new_version\"\n    echo \"   Please update $VERSION_FILE to match the release version.\"\n    read -p \"   Continue anyway? (y/N): \" continue_anyway\n    if [ \"$continue_anyway\" != \"y\" ] && [ \"$continue_anyway\" != \"Y\" ]; then\n      echo \"❌ Release cancelled\"\n      exit 1\n    fi\n  else\n    echo \"✅ $VERSION_FILE matches release version: $new_version\"\n  fi\nelse\n  echo \"⚠️  WARNING: $VERSION_FILE not found\"\nfi\n"
  },
  {
    "path": "scripts/.utils/funcs.sh",
    "content": "#!/bin/bash\n# Copyright 2025 CloudWeGo Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n#\n# Shared utility functions for release scripts\n#\n\n# Function to show changes between two commits/tags\n# Usage: show_changes <from_ref> <to_ref> [title]\nshow_changes() {\n    local from_ref=$1\n    local to_ref=$2\n    local title=${3:-\"Changes\"}\n\n    echo\n    echo \"📋 $title:\"\n    echo \"$(printf '%*s' ${#title} | tr ' ' '=')\"\n\n    # Check if there are any commits between the references\n    local commit_count=$(git rev-list --count \"$from_ref..$to_ref\")\n    if [ \"$commit_count\" -eq 0 ]; then\n        echo \"✅ No new commits. Nothing to release.\"\n        return 1\n    fi\n\n    # Show changes grouped by author email\n    git log --pretty=format:\"%ae|%h %s\" \"$from_ref..$to_ref\" | sort | awk -F'|' '{\n        if ($1 != prev_email) {\n            if (prev_email != \"\") print \"\"\n            print \"👤 \" $1 \":\"\n            prev_email = $1\n        }\n        print \"  \" $2\n    }'\n\n    return 0\n}\n\n# Function to show changes for first release (no previous version)\n# Usage: show_first_release_changes <commit> [limit]\nshow_first_release_changes() {\n    local commit=$1\n    local limit=${2:-10}\n\n    echo\n    echo \"📋 This is the first release - showing recent commits:\"\n    echo \"====================================================\"\n\n    git log --pretty=format:\"%ae|%h %s\" -\"$limit\" \"$commit\" | sort | awk -F'|' '{\n        if ($1 != prev_email) {\n            if (prev_email != \"\") print \"\"\n            print \"👤 \" $1 \":\"\n            prev_email = $1\n        }\n        print \"  \" $2\n    }'\n}\n\n# Function to ask user for confirmation of changes\n# Usage: confirm_changes\nconfirm_changes() {\n    echo\n    echo \"📝 Please review the changes above.\"\n    read -p \"Do you want to proceed with these changes? (y/N): \" confirm_changes\n    if [ \"$confirm_changes\" != \"y\" ] && [ \"$confirm_changes\" != \"Y\" ]; then\n        echo \"❌ Release cancelled by user\"\n        return 1\n    fi\n    return 0\n}\n\n# Function to check if commit exists on origin\n# Usage: check_commit_exists <commit> <branch>\ncheck_commit_exists() {\n    local commit=$1\n    local branch=$2\n    if ! git cat-file -e \"$commit\" 2>/dev/null; then\n        echo \"❌ Error: Commit $commit does not exist locally\"\n        exit 1\n    fi\n\n    if ! git branch -r --contains \"$commit\" | grep -q \"origin/$branch$\"; then\n        echo \"❌ Error: Commit $commit does not exist on origin/$branch\"\n        exit 1\n    fi\n}\n\n# Function to get latest version tag\n# Usage: get_latest_version\nget_latest_version() {\n    local latest_tag=$(git tag -l \"v*.*.*\" | sort -V | tail -n1)\n    if [ -z \"$latest_tag\" ]; then\n        echo \"v0.0.0\"\n    else\n        echo \"$latest_tag\"\n    fi\n}\n\n# Function to get latest patch version for a given minor version\n# Usage: get_latest_patch_version <minor_version>\nget_latest_patch_version() {\n    local minor_version=$1\n    # Remove 'v' prefix and '.x' suffix if present\n    minor_version=${minor_version#v}\n    minor_version=${minor_version%.x}\n\n    local latest_patch=$(git tag -l \"v${minor_version}.*\" | sort -V | tail -n1)\n    if [ -z \"$latest_patch\" ]; then\n        echo \"No tags found for minor version v${minor_version}.x\"\n        return 1\n    else\n        echo \"$latest_patch\"\n    fi\n}\n\n# Function to increment version\n# Usage: increment_version <version> <type>\n# where type is one of: major, minor, patch\nincrement_version() {\n    local version=$1\n    local type=$2\n\n    # Remove 'v' prefix\n    version=${version#v}\n\n    IFS='.' read -r major minor patch <<< \"$version\"\n\n    case $type in\n        \"major\")\n            major=$((major + 1))\n            minor=0\n            patch=0\n            ;;\n        \"minor\")\n            minor=$((minor + 1))\n            patch=0\n            ;;\n        \"patch\")\n            patch=$((patch + 1))\n            ;;\n    esac\n\n    echo \"v$major.$minor.$patch\"\n}\n\n# Function to increment patch version (convenience wrapper)\n# Usage: increment_patch_version <version>\nincrement_patch_version() {\n    local version=$1\n    increment_version \"$version\" \"patch\"\n}\n"
  },
  {
    "path": "scripts/release-hotfix.sh",
    "content": "#!/bin/bash\n# Copyright 2025 CloudWeGo Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n#\n# Hertz Hotfix Release Script\n#\n# This script creates hotfix releases for specific minor versions.\n# It performs the following steps:\n#   1. Prompts for the minor version to create a hotfix for (e.g., v0.14.x)\n#   2. Finds the latest patch version for that minor version\n#   3. Creates or uses existing hotfix branch from the latest patch\n#   4. Shows changes in the hotfix branch\n#   5. Creates and pushes a new patch version tag\n#\n# Usage:\n#   ./release-hotfix.sh\n#\n# Prerequisites:\n#   - Git repository must be fetched with latest changes\n#   - Hotfix branch must exist (script can create it if needed)\n#   - Hotfix commits must be on origin\n#\n# The script will interactively prompt for:\n#   - Minor version to hotfix (e.g., v0.14.x)\n#   - Confirmation to create hotfix branch (if it doesn't exist)\n#   - Confirmation before creating the hotfix tag\n#\n# Example workflow:\n#   1. Run script and specify v0.14.x\n#   2. Script creates v0.14.x-hotfix branch from latest v0.14.* tag\n#   3. Create PR with hotfix changes to the hotfix branch\n#   4. Run script again to create the hotfix release\n#\n\nset -e\n\n# Parse arguments\nDRY_RUN=false\nwhile [[ $# -gt 0 ]]; do\n    case $1 in\n        --dry-run)\n            DRY_RUN=true\n            shift\n            ;;\n        -h|--help)\n            echo \"Usage: $0 [--dry-run]\"\n            echo \"  --dry-run    Show what would be done without making changes\"\n            exit 0\n            ;;\n        *)\n            echo \"Unknown option: $1\"\n            echo \"Use --help for usage information\"\n            exit 1\n            ;;\n    esac\ndone\n\nSCRIPTS_ROOT=$(git rev-parse --show-toplevel)/scripts/\nCHECK_VERSION_CMD=$SCRIPTS_ROOT/.utils/check_version.sh\nCHECK_GO_MOD_CMD=$SCRIPTS_ROOT/.utils/check_go_mod.sh\n\n# Source shared utility functions\nsource \"$SCRIPTS_ROOT/.utils/funcs.sh\"\n\necho \"🔧 Hertz Hotfix Release Script\"\necho \"===============================\"\nif [ \"$DRY_RUN\" = true ]; then\n    echo \"🧪 DRY RUN MODE - No changes will be made\"\n    echo \"=========================================\"\nfi\n\n# Fetch latest changes from origin\necho \"📥 Fetching latest changes from origin...\"\ngit fetch -p --force --tags origin\n\n\n# 1. Ask which minor version to fix\necho\nread -p \"⌨️  Enter the minor version to create hotfix for (e.g., v0.14.x): \" minor_version\nif [ -z \"$minor_version\" ]; then\n    echo \"❌ Error: Minor version is required\"\n    exit 1\nfi\n\n# Normalize minor version format\nif [[ ! \"$minor_version\" =~ ^v[0-9]+\\.[0-9]+\\.x$ ]]; then\n    # Try to fix common formats\n    if [[ \"$minor_version\" =~ ^v?([0-9]+)\\.([0-9]+)$ ]]; then\n        minor_version=\"v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.x\"\n    elif [[ \"$minor_version\" =~ ^v?([0-9]+)\\.([0-9]+)\\.x$ ]]; then\n        minor_version=\"v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.x\"\n    else\n        echo \"❌ Error: Invalid minor version format. Please use format like 'v0.14.x'\"\n        exit 1\n    fi\nfi\n\necho \"🎯 Target minor version: $minor_version\"\n\n# 2. Get the latest version of the given minor version\nlatest_patch=$(get_latest_patch_version \"$minor_version\")\nif [ $? -ne 0 ]; then\n    echo \"❌ Error: $latest_patch\"\n    exit 1\nfi\n\necho \"📌 Latest patch version: $latest_patch\"\n\n# 3. Check if hotfix branch exists\nhotfix_branch=\"hotfix/${minor_version}\"\nif ! git show-ref --verify --quiet \"refs/remotes/origin/$hotfix_branch\"; then\n    echo \"❌ Hotfix branch 'origin/$hotfix_branch' does not exist\"\n    echo\n    if [ \"$DRY_RUN\" = true ]; then\n        read -p \"🧪 DRY RUN: Would create hotfix branch '$hotfix_branch' from $latest_patch. Continue? (y/N): \" create_branch\n    else\n        read -p \"🔧 Create hotfix branch '$hotfix_branch' from $latest_patch? (y/N): \" create_branch\n    fi\n    if [ \"$create_branch\" = \"y\" ] || [ \"$create_branch\" = \"Y\" ]; then\n        if [ \"$DRY_RUN\" = true ]; then\n            echo \"🧪 DRY RUN: Would execute:\"\n            echo \"  git push origin \\\"$latest_patch:refs/heads/$hotfix_branch\\\"\"\n            echo\n            echo \"✅ DRY RUN: Hotfix branch '$hotfix_branch' would be created\"\n            echo \"🔗 In real mode, you would create a PR for your hotfix changes to this branch\"\n            exit 0\n        else\n            echo \"🌿 Creating hotfix branch $hotfix_branch...\"\n            git push origin \"$latest_patch:refs/heads/$hotfix_branch\"\n            echo\n            echo \"✅ Hotfix branch '$hotfix_branch' created successfully!\"\n            echo \"🔗 Please create a PR for your hotfix changes to this branch\"\n            echo \"   Then run this script again to create the hotfix release\"\n            exit 0\n        fi\n    else\n        echo \"❌ Cannot proceed without hotfix branch\"\n        exit 1\n    fi\nfi\n\necho \"✅ Found hotfix branch: $hotfix_branch\"\n\n# 4. Show diff between hotfix branch and latest patch version\nif ! show_changes \"$latest_patch\" \"origin/$hotfix_branch\" \"Changes in hotfix branch since $latest_patch\"; then\n    exit 0\nfi\n\n# Ask user to confirm the changes before proceeding\nif ! confirm_changes; then\n    exit 1\nfi\n\n# 5. Ask user if they want to release the new patch version\nnew_patch_version=$(increment_patch_version \"$latest_patch\")\n\necho\necho \"🔖 New patch version will be: $new_patch_version\"\n\n# Check version.go file\necho\n$CHECK_VERSION_CMD \"$new_patch_version\"\n\n# Check go.mod\necho\n$CHECK_GO_MOD_CMD\n\n# Final confirmation\necho\nif [ \"$DRY_RUN\" = true ]; then\n    read -p \"DRY RUN: Would create hotfix release tag $new_patch_version from hotfix branch $hotfix_branch. Continue? (y/N): \" confirm\nelse\n    read -p \"Create hotfix release tag $new_patch_version from hotfix branch $hotfix_branch? (y/N): \" confirm\nfi\nif [ \"$confirm\" != \"y\" ] && [ \"$confirm\" != \"Y\" ]; then\n    echo \"❌ Release cancelled\"\n    exit 1\nfi\n\n# Get the latest commit from hotfix branch\nhotfix_commit=$(git rev-parse \"origin/$hotfix_branch\")\ncheck_commit_exists \"$hotfix_commit\" \"$hotfix_branch\"\n\n# Create and push tag\nif [ \"$DRY_RUN\" = true ]; then\n    echo\n    echo \"🧪 DRY RUN: Would execute:\"\n    echo \"  git tag -a \\\"$new_patch_version\\\" \\\"$hotfix_commit\\\" -m \\\"Hotfix release $new_patch_version\\\"\"\n    echo \"  git push origin \\\"$new_patch_version\\\"\"\nelse\n    echo \"🏷️  Creating tag $new_patch_version...\"\n    git tag -a \"$new_patch_version\" \"$hotfix_commit\" -m \"Hotfix release $new_patch_version\"\n\n    echo \"📤 Pushing tag to origin...\"\n    git push origin \"$new_patch_version\"\nfi\n\necho\nif [ \"$DRY_RUN\" = true ]; then\n    echo \"🧪 DRY RUN COMPLETE - No changes were made\"\n    echo \"Tag: $new_patch_version\"\n    echo \"Commit: $hotfix_commit\"\n    echo \"Based on hotfix branch: $hotfix_branch\"\nelse\n    echo \"🎉 Hotfix release $new_patch_version created successfully!\"\n    echo \"Tag: $new_patch_version\"\n    echo \"Commit: $hotfix_commit\"\n    echo \"Based on hotfix branch: $hotfix_branch\"\nfi\n"
  },
  {
    "path": "scripts/release.sh",
    "content": "#!/bin/bash\n# Copyright 2025 CloudWeGo Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n#\n# Hertz Release Script\n#\n# This script creates a new release tag for the Hertz project.\n# It performs the following steps:\n#   1. Validates you're on the main branch and up-to-date with origin\n#   2. Prompts for the target commit to release\n#   3. Shows changes since the last version\n#   4. Allows you to choose version bump type (major/minor/patch)\n#   5. Validates version.go matches the new version\n#   6. Creates and pushes the release tag\n#\n# Usage:\n#   ./release.sh\n#\n# Prerequisites:\n#   - Must be on main branch\n#   - Local repo must be up-to-date with origin\n#   - Target commit must exist on origin\n#\n# The script will interactively prompt for:\n#   - Target commit hash to release\n#   - Version bump type (patch/minor/major)\n#   - Confirmation before creating the tag\n#\n\nset -e\n\n# Parse arguments\nDRY_RUN=false\nwhile [[ $# -gt 0 ]]; do\n    case $1 in\n        --dry-run)\n            DRY_RUN=true\n            shift\n            ;;\n        -h|--help)\n            echo \"Usage: $0 [--dry-run]\"\n            echo \"  --dry-run    Show what would be done without making changes\"\n            exit 0\n            ;;\n        *)\n            echo \"Unknown option: $1\"\n            echo \"Use --help for usage information\"\n            exit 1\n            ;;\n    esac\ndone\n\nDEFAULT_BRANCH=main\nGIT_ROOT=$(git rev-parse --show-toplevel)\nSCRIPTS_ROOT=$GIT_ROOT/scripts\nCHECK_VERSION_CMD=$SCRIPTS_ROOT/.utils/check_version.sh\nCHECK_GO_MOD_CMD=$SCRIPTS_ROOT/.utils/check_go_mod.sh\n\n# Source shared utility functions\nsource $SCRIPTS_ROOT/.utils/funcs.sh\n\necho \"🚀 Hertz Release Script\"\necho \"=======================\"\nif [ \"$DRY_RUN\" = true ]; then\n    echo \"🧪 DRY RUN MODE - No changes will be made\"\n    echo \"=========================================\"\nfi\n\n# Fetch latest changes from origin\necho \"📥 Fetching latest changes from origin...\"\ngit fetch -p --force --tags origin\n\n# 1. Ask user which commit to release\necho\nread -p \"⌨️  Enter the commit hash you want to release: \" target_commit\nif [ -z \"$target_commit\" ]; then\n    echo \"❌ Error: Commit hash is required\"\n    exit 1\nfi\n\n# Resolve to full commit hash\ntarget_commit=$(git rev-parse \"$target_commit\")\necho \"📌 Target commit: $target_commit\"\necho\n\n# 2. Check if commit exists on origin\ncheck_commit_exists \"$target_commit\" \"$DEFAULT_BRANCH\"\n\n# 3. Get latest version tag\nlatest_version=$(get_latest_version)\n\n# 4. Show diff between latest version and target commit\nif [ \"$latest_version\" = \"v0.0.0\" ]; then\n    show_first_release_changes \"$target_commit\" 10\nelse\n    if ! show_changes \"$latest_version\" \"$target_commit\" \"Changes since latest version $latest_version\"; then\n        exit 0\n    fi\nfi\n\n# Ask user to confirm the changes before proceeding\nif ! confirm_changes; then\n    exit 1\nfi\n\n# 5. Ask user for version bump type\n\n# Calculate what each version would be\nmajor_version=$(increment_version \"$latest_version\" \"major\")\nminor_version=$(increment_version \"$latest_version\" \"minor\")\npatch_version=$(increment_version \"$latest_version\" \"patch\")\n\necho\necho \"⚙️  Available version bump types:\"\necho \"  1) patch - for bug fixes ($patch_version)\"\necho \"  2) minor - for new features ($minor_version)\"\necho \"  3) major - for breaking changes ($major_version)\"\necho\n\nwhile true; do\n    read -p \"Select version bump type (1/2/3): \" bump_choice\n    case $bump_choice in\n        1) bump_type=\"patch\"; break;;\n        2) bump_type=\"minor\"; break;;\n        3) bump_type=\"major\"; break;;\n        *) echo \"Please enter 1, 2, or 3\";;\n    esac\ndone\n\nnew_version=$(increment_version \"$latest_version\" \"$bump_type\")\necho\necho \"New version will be: $new_version\"\n\n# Check version.go file\necho\n$CHECK_VERSION_CMD \"$new_version\"\n\n# Check go.mod\necho\n$CHECK_GO_MOD_CMD\n\n# Final confirmation\necho\nif [ \"$DRY_RUN\" = true ]; then\n    read -p \"DRY RUN: Would create release tag $new_version for commit $target_commit. Continue? (y/N): \" confirm\nelse\n    read -p \"Create release tag $new_version for commit $target_commit? (y/N): \" confirm\nfi\nif [ \"$confirm\" != \"y\" ] && [ \"$confirm\" != \"Y\" ]; then\n    echo \"❌ Release cancelled\"\n    exit 1\nfi\n\n# Create and push tag\nif [ \"$DRY_RUN\" = true ]; then\n    echo\n    echo \"🧪 DRY RUN: Would execute:\"\n    echo \"  git tag -a \\\"$new_version\\\" \\\"$target_commit\\\" -m \\\"Release $new_version\\\"\"\n    echo \"  git push origin \\\"$new_version\\\"\"\nelse\n    echo \"🏷️  Creating tag $new_version...\"\n    git tag -a \"$new_version\" \"$target_commit\" -m \"Release $new_version\"\n\n    echo \"📤 Pushing tag to origin...\"\n    git push origin \"$new_version\"\nfi\n\necho\nif [ \"$DRY_RUN\" = true ]; then\n    echo \"🧪 DRY RUN COMPLETE - No changes were made\"\n    echo \"Tag: $new_version\"\n    echo \"Commit: $target_commit\"\nelse\n    echo \"🎉 Release $new_version created successfully!\"\n    echo \"Tag: $new_version\"\n    echo \"Commit: $target_commit\"\nfi\n"
  },
  {
    "path": "version.go",
    "content": "/*\n * Copyright 2022 CloudWeGo Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hertz\n\n// Name and Version info of this framework, used for statistics and debug\nconst (\n\tName    = \"Hertz\"\n\tVersion = \"v0.10.4\"\n)\n"
  }
]