├── .github └── workflows │ └── main.yml ├── .gitignore ├── .golangci.yml ├── IMPLEMENTATION.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── client.go ├── client_test.go ├── configure.go ├── conn.go ├── continuation.go ├── data.go ├── demo ├── go.mod ├── go.sum └── main.go ├── errors.go ├── examples ├── autocert │ └── main.go ├── client │ └── main.go ├── proxy │ ├── cert.go │ └── main.go ├── reverse_proxy │ ├── cert.go │ └── main.go └── simple │ ├── cert.go │ └── main.go ├── frame.go ├── frameHeader.go ├── frameHeader_test.go ├── go.mod ├── go.sum ├── goaway.go ├── h2spec └── h2spec_test.go ├── headerField.go ├── headers.go ├── hpack.go ├── hpack_test.go ├── http2.go ├── http2utils └── utils.go ├── huffman.go ├── huffman_test.go ├── ping.go ├── priority.go ├── pushpromise.go ├── rststream.go ├── server.go ├── serverConn.go ├── settings.go ├── stream.go ├── streams.go ├── strings.go └── windowUpdate.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | env: 10 | GO_VERSION: 1.17 11 | GOLANGCI_LINT_VERSION: v1.43.0 12 | 13 | jobs: 14 | main: 15 | name: Main Process 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Set up Go ${{ env.GO_VERSION }} 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: ${{ env.GO_VERSION }} 23 | 24 | - name: Check out code 25 | uses: actions/checkout@v2 26 | with: 27 | fetch-depth: 0 28 | 29 | - name: Cache Go modules 30 | uses: actions/cache@v2 31 | with: 32 | path: ~/go/pkg/mod 33 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 34 | restore-keys: | 35 | ${{ runner.os }}-go- 36 | 37 | - name: Check and get dependencies 38 | run: | 39 | go mod tidy 40 | git diff --exit-code go.mod 41 | git diff --exit-code go.sum 42 | 43 | - name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }} 44 | run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION} 45 | 46 | - name: Launch test 47 | run: go test ./ 48 | 49 | - name: Launch h2spec test 50 | run: go test ./h2spec 51 | 52 | - name: golangci-lint 53 | run: golangci-lint run 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | examples/proxy/proxy 3 | examples/simple/simple 4 | examples/client/client 5 | demo/demo 6 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | deadline: 5m 3 | skip-files: [ ] 4 | skip-dirs: [ "maybe_later", "cmd" ] 5 | 6 | linters-settings: 7 | govet: 8 | enable-all: true 9 | disable: 10 | - fieldalignment 11 | gocyclo: 12 | min-complexity: 20 13 | maligned: 14 | suggest-new: true 15 | goconst: 16 | min-len: 5 17 | min-occurrences: 3 18 | misspell: 19 | locale: US 20 | funlen: 21 | lines: -1 22 | statements: 62 23 | godox: 24 | keywords: 25 | - FIXME 26 | gofumpt: 27 | extra-rules: true 28 | depguard: 29 | list-type: blacklist 30 | include-go-root: false 31 | packages: 32 | - github.com/sirupsen/logrus 33 | - github.com/pkg/errors 34 | gocritic: 35 | enabled-tags: 36 | - diagnostic 37 | - style 38 | - performance 39 | disabled-checks: 40 | - sloppyReassign 41 | - rangeValCopy 42 | - octalLiteral 43 | - paramTypeCombine # already handle by gofumpt.extra-rules 44 | - unnamedResult 45 | settings: 46 | hugeParam: 47 | sizeThreshold: 100 48 | 49 | linters: 50 | enable-all: true 51 | disable: 52 | - maligned # deprecated 53 | - interfacer # deprecated 54 | - scopelint # deprecated 55 | - golint # deprecated 56 | - sqlclosecheck # not relevant (SQL) 57 | - rowserrcheck # not relevant (SQL) 58 | - cyclop # duplicate of gocyclo 59 | - lll 60 | - dupl 61 | - wsl 62 | - nlreturn 63 | - gomnd 64 | - goerr113 65 | - wrapcheck 66 | - exhaustive 67 | - exhaustivestruct 68 | - testpackage 69 | - tparallel 70 | - paralleltest 71 | - prealloc 72 | - ifshort 73 | - forcetypeassert 74 | - bodyclose # Too many false positives: https://github.com/timakin/bodyclose/issues/30 75 | - nestif 76 | - gochecknoglobals 77 | - errname 78 | - nakedret 79 | - gocognit 80 | - varnamelen 81 | - ireturn 82 | 83 | issues: 84 | exclude-use-default: false 85 | max-per-linter: 0 86 | max-same-issues: 0 87 | exclude: 88 | - 'ST1000: at least one file in a package should have a package comment' 89 | - 'G204: Subprocess launched with variable' 90 | - 'G304: Potential file inclusion via variable' 91 | - 'ST1020: comment on exported (.*)' 92 | - 'exported: exported (.*) be unexported' 93 | - 'exported: comment on exported (.*)' 94 | 95 | exclude-rules: 96 | - path: http2utils/utils.go 97 | text: 'G103: Use of unsafe calls should be audited' 98 | - path: hpack.go 99 | text: 'SA6002: argument should be pointer-like to avoid allocations' 100 | - path: hpack.go 101 | text: 'ifElseChain: rewrite if-else to switch statement' 102 | - path: server.go 103 | text: 'SA9003: empty branch' 104 | - path: server.go 105 | text: 'appendAssign: append result not assigned to the same slice' 106 | - path: server.go 107 | text: 'cyclomatic complexity 34 of func' 108 | - path: conn.go 109 | text: 'shadow: declaration of "fr" shadows declaration' 110 | -------------------------------------------------------------------------------- /IMPLEMENTATION.md: -------------------------------------------------------------------------------- 1 | # Implementation 2 | 3 | A document that explains in detail how the client and the server works. 4 | 5 | ## Client implementation 6 | 7 | The client holds (0, N) connections to a single host. 8 | A connection is created in the following cases: 9 | - There are no previous existing connections. 10 | - All the connections are busy (aka not able to open more streams). 11 | 12 | Connections are stored in a list because it's the easiest way to keep elements. 13 | 14 | When a connection is created 2 goroutines are spawned. One for reading 15 | and dispatching events, and another for writing (either frames and requests). 16 | 17 | The [read loop](https://github.com/dgrr/http2/blob/8cb32376c36f056fca0ec30854f3522005a777ac/conn.go#L357) 18 | will read all the frames and handling only the ones carrying a StreamID. 19 | Lower layers will handle everything related to Settings, WindowUpdate, Ping 20 | and/or disconnection. 21 | 22 | The [write loop](https://github.com/dgrr/http2/blob/8cb32376c36f056fca0ec30854f3522005a777ac/conn.go#L290) 23 | will write the requests and frames. I like to separate both terms because the request 24 | comes from fasthttp, and the `frames` is a term related to http2. 25 | 26 | Why having 2 coroutines? As HTTP/2 is a replacement of HTTP/1.1, the equivalent 27 | to opening a connection per request in HTTP/1 is the figure of the `frame` in HTTP/2. 28 | As writing to the same connection might happen concurrently and thus, can invoke 29 | errors, 2 coroutines are required, one for writing and another for reading 30 | synchronously. 31 | 32 | ### How sending a request works? 33 | 34 | When we send a request we write to a channel to the writeLoop coroutine with 35 | all the data required, in this case we make use of the [Ctx](https://github.com/dgrr/http2/blob/8cb32376c36f056fca0ec30854f3522005a777ac/client.go#L26-L33) 36 | structure. 37 | 38 | That being sent, it gets received by the writeLoop coroutine, and then 39 | it proceeds to [serialize and write](https://github.com/dgrr/http2/blob/8cb32376c36f056fca0ec30854f3522005a777ac/conn.go#L385) 40 | into the connection the required frames, and after that [registers](https://github.com/dgrr/http2/blob/8cb32376c36f056fca0ec30854f3522005a777ac/conn.go#L321) 41 | the StreamID into a shared map. This map is shared among the 'write' and 'read' loops. 42 | 43 | In the meantime, the client [waits on a channel](https://github.com/dgrr/http2/blob/8cb32376c36f056fca0ec30854f3522005a777ac/client.go#L102) 44 | for any error. 45 | 46 | When we receive the response from the server, the readLoop will check if the StreamID 47 | is on the shared map, and if so, it will [handle the response](https://github.com/dgrr/http2/blob/8cb32376c36f056fca0ec30854f3522005a777ac/conn.go#L559). 48 | After the server finished sending the request, the readLoop will end the request 49 | sending the result to the client. That result might be an error or just a `nil` 50 | over the channel provided by the client. 51 | 52 | After the request/response finished, the client will continue thus exiting the 53 | `Do` function. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test fmt 2 | 3 | test: 4 | go test -v 5 | cd h2spec && go test -v 6 | 7 | fmt: 8 | goimports -w . 9 | gofmt -w -s . 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTP2 2 | 3 | http2 is an implementation of HTTP/2 protocol for [fasthttp](https://github.com/valyala/fasthttp). 4 | 5 | # Download 6 | 7 | ```bash 8 | go get github.com/dgrr/http2@v0.2.12 9 | ``` 10 | 11 | # Help 12 | 13 | If you need any help to set up, contributing or understanding this repo, you can contact me on [gofiber's Discord](https://gofiber.io/discord). 14 | 15 | # How to use the server? 16 | 17 | The server can only be used if your server supports TLS. 18 | Then, you can call [ConfigureServer](https://pkg.go.dev/github.com/dgrr/http2#ConfigureServer). 19 | 20 | ```go 21 | package main 22 | 23 | import ( 24 | "github.com/valyala/fasthttp" 25 | "github.com/dgrr/http2" 26 | ) 27 | 28 | func main() { 29 | s := &fasthttp.Server{ 30 | Handler: yourHandler, 31 | Name: "HTTP2 test", 32 | } 33 | 34 | http2.ConfigureServer(s, http2.ServerConfig{}) 35 | 36 | s.ListenAndServeTLS(...) 37 | } 38 | ``` 39 | 40 | # How to use the client? 41 | 42 | The HTTP/2 client only works with the HostClient. 43 | 44 | ```go 45 | package main 46 | 47 | import ( 48 | "fmt" 49 | "log" 50 | 51 | "github.com/dgrr/http2" 52 | "github.com/valyala/fasthttp" 53 | ) 54 | 55 | func main() { 56 | hc := &fasthttp.HostClient{ 57 | Addr: "api.binance.com:443", 58 | } 59 | 60 | if err := http2.ConfigureClient(hc, http2.ClientOpts{}); err != nil { 61 | log.Printf("%s doesn't support http/2\n", hc.Addr) 62 | } 63 | 64 | statusCode, body, err := hc.Get(nil, "https://api.binance.com/api/v3/time") 65 | if err != nil { 66 | log.Fatalln(err) 67 | } 68 | 69 | fmt.Printf("%d: %s\n", statusCode, body) 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ### TL;DR 2 | 3 | We use a simplified version of [Golang Security Policy](https://golang.org/security). 4 | For example, for now we skip CVE assignment. 5 | 6 | ### Reporting a Security Bug 7 | 8 | Please report to us any issues you find. This document explains how to do that and what to expect in return. 9 | 10 | All security bugs in our releases should be reported by email to oss-security@highload.solutions. 11 | This mail is delivered to a small security team. 12 | Your email will be acknowledged within 24 hours, and you'll receive a more detailed response 13 | to your email within 72 hours indicating the next steps in handling your report. 14 | For critical problems, you can encrypt your report using our PGP key (listed below). 15 | 16 | Please use a descriptive subject line for your report email. 17 | After the initial reply to your report, the security team will 18 | endeavor to keep you informed of the progress being made towards a fix and full announcement. 19 | These updates will be sent at least every five days. 20 | In reality, this is more likely to be every 24-48 hours. 21 | 22 | If you have not received a reply to your email within 48 hours, or you have not heard from the security 23 | team for the past five days please contact us by email to developers@highload.solutions or by Telegram message 24 | to [our support](https://t.me/highload_support). 25 | Please note that developers@highload.solutions list includes all developers, who may be outside our opensource security team. 26 | When escalating on this list, please do not disclose the details of the issue. 27 | Simply state that you're trying to reach a member of the security team. 28 | 29 | ### Flagging Existing Issues as Security-related 30 | 31 | If you believe that an existing issue is security-related, we ask that you send an email to oss-security@highload.solutions. 32 | The email should include the issue ID and a short description of why it should be handled according to this security policy. 33 | 34 | ### Disclosure Process 35 | 36 | Our project uses the following disclosure process: 37 | 38 | - Once the security report is received it is assigned a primary handler. This person coordinates the fix and release process. 39 | - The issue is confirmed and a list of affected software is determined. 40 | - Code is audited to find any potential similar problems. 41 | - Fixes are prepared for the two most recent major releases and the head/master revision. These fixes are not yet committed to the public repository. 42 | - To notify users, a new issue without security details is submitted to our GitHub repository. 43 | - Three working days following this notification, the fixes are applied to the public repository and a new release is issued. 44 | - On the date that the fixes are applied, announcement is published in the issue. 45 | 46 | This process can take some time, especially when coordination is required with maintainers of other projects. 47 | Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we follow 48 | the process described above to ensure that disclosures are handled consistently. 49 | 50 | ### Receiving Security Updates 51 | The best way to receive security announcements is to subscribe ("Watch") to our repository. 52 | Any GitHub issues pertaining to a security issue will be prefixed with [security]. 53 | 54 | ### Comments on This Policy 55 | If you have any suggestions to improve this policy, please send an email to oss-security@highload.solutions for discussion. 56 | 57 | ### PGP Key for oss-security@highload.solutions 58 | 59 | We accept PGP-encrypted email, but the majority of the security team are not regular PGP users, 60 | so it's somewhat inconvenient. Please only use PGP for critical security reports. 61 | 62 | ``` 63 | -----BEGIN PGP PUBLIC KEY BLOCK----- 64 | 65 | mQINBFzdjYUBEACa3YN+QVSlnXofUjxr+YrmIaF+da0IUq+TRM4aqUXALsemEdGh 66 | yIl7Z6qOOy1d2kPe6t//H9l/92lJ1X7i6aEBK4n/pnPZkwbpy9gGpebgvTZFvcbe 67 | mFhF6k1FM35D8TxneJSjizPyGhJPqcr5qccqf8R64TlQx5Ud1JqT2l8P1C5N7gNS 68 | lEYXq1h4zBCvTWk1wdeLRRPx7Bn6xrgmyu/k61dLoJDvpvWNATVFDA67oTrPgzTW 69 | xtLbbk/xm0mK4a8zMzIpNyz1WkaJW9+4HFXaL+yKlsx7iHe2O7VlGoqS0kdeQup4 70 | 1HIw/P7yc0jBlNMLUzpuA6ElYUwESWsnCI71YY1x4rKgI+GqH1mWwgn7tteuXQtb 71 | Zj0vEdjK3IKIOSbzbzAvSbDt8F1+o7EMtdy1eUysjKSQgFkDlT6JRmYvEup5/IoG 72 | iknh/InQq9RmGFKii6pXWWoltC0ebfCwYOXvymyDdr/hYDqJeHS9Tenpy86Doaaf 73 | HGf5nIFAMB2G5ctNpBwzNXR2MAWkeHQgdr5a1xmog0hS125usjnUTet3QeCyo4kd 74 | gVouoOroMcqFFUXdYaMH4c3KWz0afhTmIaAsFFOv/eMdadVA4QyExTJf3TAoQ+kH 75 | lKDlbOAIxEZWRPDFxMRixaVPQC+VxhBcaQ+yNoaUkM0V2m8u8sDBpzi1OQARAQAB 76 | tDxPU1MgU2VjdXJpdHksIEhpZ2hsb2FkIExURCA8b3NzLXNlY3VyaXR5QGhpZ2hs 77 | b2FkLnNvbHV0aW9ucz6JAlQEEwEIAD4WIQRljYp380uKq2g8TeqsQcvu+Qp2TAUC 78 | XN2NhQIbAwUJB4YfgAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCsQcvu+Qp2 79 | TKmED/96YoQoOjD28blFFrigvAsiNcNNZoX9I0dX1lNpD83fBJf+/9i+x4jqUnI5 80 | 5XK/DFTDbhpw8kQBpxS9eEuIYnuo0RdLLp1ctNWTlpwfyHn92mGddl/uBdYHUuUk 81 | cjhIQcFaCcWRY+EpamDlv1wmZ83IwBr8Hu5FS+/Msyw1TBvtTRVKW1KoGYMYoXLk 82 | BzIglRPwn821B6s4BvK/RJnZkrmHMBZBfYMf+iSMSYd2yPmfT8wbcAjgjLfQa28U 83 | gbt4u9xslgKjuM83IqwFfEXBnm7su3OouGWqc+62mQTsbnK65zRFnx6GXRXC1BAi 84 | 6m9Tm1PU0IiINz66ainquspkXYeHjd9hTwfR3BdFnzBTRRM01cKMFabWbLj8j0p8 85 | fF4g9cxEdiLrzEF7Yz4WY0mI4Cpw4eJZfsHMc07Jn7QxfJhIoq+rqBOtEmTjnxMh 86 | aWeykoXMHlZN4K0ZrAytozVH1D4bugWA9Zuzi9U3F9hrVVABm11yyhd2iSqI6/FR 87 | GcCFOCBW1kEJbzoEguub+BV8LDi8ldljHalvur5k/VFhoDBxniYNsKmiCLVCmDWs 88 | /nF84hCReAOJt0vDGwqHe3E2BFFPbKwdJLRNkjxBY0c/pvaV+JxbWQmaxDZNeIFV 89 | hFcVGp48HNY3qLWZdsQIfT9m1masJFLVuq8Wx7bYs8Et5eFnH7kCDQRc3Y2FARAA 90 | 2DJWAxABydyIdCxgFNdqnYyWS46vh2DmLmRMqgasNlD0ozG4S9bszBsgnUI2Xs06 91 | J76kFRh8MMHcu9I4lUKCQzfrA4uHkiOK5wvNCaWP+C6JUYNHsqPwk/ILO3gtQ/Ws 92 | LLf/PW3rJZVOZB+WY8iaYc20l5vukTaVw4qbEi9dtLkJvVpNHt//+jayXU6s3ew1 93 | 2X5xdwyAZxaxlnzFaY/Xo/qR+bZhVFC0T9pAECnHv9TVhFGp0JE9ipPGnro5xTIS 94 | LttdAkzv4AuSVTIgWgTkh8nN8t7STJqfPEv0I12nmmYHMUyTYOurkfskF3jY2x6x 95 | 8l02NQ4d5KdC3ReV1j51swrGcZCwsWNp51jnEXKwo+B0NM5OmoRrNJgF2iDgLehs 96 | hP00ljU7cB8/1/7kdHZStYaUHICFOFqHzg415FlYm+jpY0nJp/b9BAO0d0/WYnEe 97 | Xjihw8EVBAqzEt4kay1BQonZAypeYnGBJr7vNvdiP+mnRwly5qZSGiInxGvtZZFt 98 | zL1E3osiF+muQxFcM63BeGdJeYXy+MoczkWa4WNggfcHlGAZkMYiv28zpr4PfrK9 99 | mvj4Nu8s71PE9pPpBoZcNDf9v1sHuu96jDSITsPx5YMvvKZWhzJXFKzk6YgAsNH/ 100 | MF0G+/qmKJZpCdvtHKpYM1uHX85H81CwWJFfBPthyD8AEQEAAYkCPAQYAQgAJhYh 101 | BGWNinfzS4qraDxN6qxBy+75CnZMBQJc3Y2FAhsMBQkHhh+AAAoJEKxBy+75CnZM 102 | Rn8P/RyL1bhU4Q4WpvmlkepCAwNA0G3QvnKcSZNHEPE5h7H3IyrA/qy16A9eOsgm 103 | sthsHYlo5A5lRIy4wPHkFCClMrMHdKuoS72//qgw+oOrBcwb7Te+Nas+ewhaJ7N9 104 | vAX06vDH9bLl52CPbtats5+eBpePgP3HDPxd7CWHxq9bzJTbzqsTkN7JvoovR2dP 105 | itPJDij7QYLYVEM1t7QxUVpVwAjDi/kCtC9ts5L+V0snF2n3bHZvu04EXdpvxOQI 106 | pG/7Q+/WoI8NU6Bb/FA3tJGYIhSwI3SY+5XV/TAZttZaYSh2SD8vhc+eo+gW9sAN 107 | xa+VESBQCht9+tKIwEwHs1efoRgFdbwwJ2c+33+XydQ6yjdXoX1mn2uyCr82jorZ 108 | xTzbkY04zr7oZ+0fLpouOFg/mrSL4w2bWEhdHuyoVthLBjnRme0wXCaS3g3mYdLG 109 | nSUkogOGOOvvvBtoq/vfx0Eu79piUtw5D8yQSrxLDuz8GxCrVRZ0tYIHb26aTE9G 110 | cDsW/Lg5PjcY/LgVNEWOxDQDFVurlImnlVJFb3q+NrWvPbgeIEWwJDCay/z25SEH 111 | k3bSOXLp8YGRnlkWUmoeL4g/CCK52iAAlfscZNoKMILhBnbCoD657jpa5GQKJj/U 112 | Q8kjgr7kwV/RSosNV9HCPj30mVyiCQ1xg+ZLzMKXVCuBWd+G 113 | =lnt2 114 | -----END PGP PUBLIC KEY BLOCK----- 115 | ``` 116 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "container/list" 5 | "sync" 6 | "time" 7 | 8 | "github.com/valyala/fasthttp" 9 | ) 10 | 11 | const DefaultPingInterval = time.Second * 3 12 | 13 | // ClientOpts defines the client options for the HTTP/2 connection. 14 | type ClientOpts struct { 15 | // PingInterval defines the interval in which the client will ping the server. 16 | // 17 | // An interval of 0 will make the library to use DefaultPingInterval. Because ping intervals can't be disabled. 18 | PingInterval time.Duration 19 | 20 | // OnRTT is assigned to every client after creation, and the handler 21 | // will be called after every RTT measurement (after receiving a PONG message). 22 | OnRTT func(time.Duration) 23 | } 24 | 25 | // Ctx represents a context for a stream. Every stream is related to a context. 26 | type Ctx struct { 27 | Request *fasthttp.Request 28 | Response *fasthttp.Response 29 | Err chan error 30 | } 31 | 32 | type Client struct { 33 | d *Dialer 34 | 35 | // TODO: impl rtt 36 | onRTT func(time.Duration) 37 | 38 | lck sync.Mutex 39 | conns list.List 40 | } 41 | 42 | func createClient(d *Dialer, opts ClientOpts) *Client { 43 | cl := &Client{ 44 | d: d, 45 | onRTT: opts.OnRTT, 46 | } 47 | 48 | return cl 49 | } 50 | 51 | func (cl *Client) onConnectionDropped(c *Conn) { 52 | cl.lck.Lock() 53 | defer cl.lck.Unlock() 54 | 55 | for e := cl.conns.Front(); e != nil; e = e.Next() { 56 | if e.Value.(*Conn) == c { 57 | cl.conns.Remove(e) 58 | 59 | _, _, _ = cl.createConn() 60 | 61 | break 62 | } 63 | } 64 | } 65 | 66 | func (cl *Client) createConn() (*Conn, *list.Element, error) { 67 | c, err := cl.d.Dial(ConnOpts{ 68 | PingInterval: cl.d.PingInterval, 69 | OnDisconnect: cl.onConnectionDropped, 70 | }) 71 | if err != nil { 72 | return nil, nil, err 73 | } 74 | 75 | return c, cl.conns.PushFront(c), nil 76 | } 77 | 78 | func (cl *Client) Do(req *fasthttp.Request, res *fasthttp.Response) (err error) { 79 | var c *Conn 80 | 81 | cl.lck.Lock() 82 | 83 | var next *list.Element 84 | 85 | for e := cl.conns.Front(); c == nil; e = next { 86 | if e != nil { 87 | c = e.Value.(*Conn) 88 | } else { 89 | c, e, err = cl.createConn() 90 | if err != nil { 91 | return err 92 | } 93 | } 94 | 95 | // if we can't open a stream, then move on to the next one. 96 | if !c.CanOpenStream() { 97 | c = nil 98 | next = e.Next() 99 | } 100 | 101 | // if the connection has been closed, then just remove the connection. 102 | if c != nil && c.Closed() { 103 | next = e.Next() 104 | cl.conns.Remove(e) 105 | c = nil 106 | } 107 | } 108 | 109 | cl.lck.Unlock() 110 | 111 | ch := make(chan error, 1) 112 | 113 | c.Write(&Ctx{ 114 | Request: req, 115 | Response: res, 116 | Err: ch, 117 | }) 118 | 119 | return <-ch 120 | } 121 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | // func TestClientWriteOrder(t *testing.T) { 4 | // bf := bytes.NewBuffer(nil) 5 | 6 | // c := &Conn{ 7 | // c: &net.TCPConn{}, 8 | // } 9 | // c.out = make(chan *FrameHeader, 1) 10 | // c.bw = bufio.NewWriter(bf) 11 | 12 | // go c.writeLoop() 13 | 14 | // framesToTest := 32 15 | 16 | // id := uint32(1) 17 | // frames := make([]*FrameHeader, 0, framesToTest) 18 | 19 | // for i := 0; i < framesToTest; i++ { 20 | // fr := AcquireFrameHeader() 21 | // fr.SetStream(id) 22 | // fr.SetBody(&Data{}) 23 | // id += 2 24 | // frames = append(frames, fr) 25 | // } 26 | 27 | // for len(frames) > 0 { 28 | // i := rand.Intn(len(frames)) 29 | 30 | // c.out <- frames[i] 31 | // frames = append(frames[:i], frames[i+1:]...) 32 | // } 33 | 34 | // br := bufio.NewReader(bf) 35 | // fr := AcquireFrameHeader() 36 | 37 | // expected := uint32(1) 38 | // for i := 0; i < framesToTest; i++ { 39 | // _, err := fr.ReadFrom(br) 40 | // if err != nil { 41 | // if err == io.EOF { 42 | // break 43 | // } 44 | // t.Fatalf("error reading frame: %s", err) 45 | // } 46 | 47 | // if fr.Stream() != expected { 48 | // t.Fatalf("Unexpected id: %d <> %d", fr.Stream(), expected) 49 | // } 50 | 51 | // expected += 2 52 | // } 53 | 54 | // close(c.out) 55 | // } 56 | -------------------------------------------------------------------------------- /configure.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "net" 7 | 8 | "github.com/valyala/fasthttp" 9 | ) 10 | 11 | // ErrServerSupport indicates whether the server supports HTTP/2 or not. 12 | var ErrServerSupport = errors.New("server doesn't support HTTP/2") 13 | 14 | func configureDialer(d *Dialer) { 15 | if d.TLSConfig == nil { 16 | d.TLSConfig = &tls.Config{ 17 | MinVersion: tls.VersionTLS12, 18 | MaxVersion: tls.VersionTLS13, 19 | } 20 | } 21 | 22 | tlsConfig := d.TLSConfig 23 | 24 | emptyServerName := tlsConfig.ServerName == "" 25 | if emptyServerName { 26 | host, _, err := net.SplitHostPort(d.Addr) 27 | if err != nil { 28 | host = d.Addr 29 | } 30 | 31 | tlsConfig.ServerName = host 32 | } 33 | 34 | tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") 35 | } 36 | 37 | // ConfigureClient configures the fasthttp.HostClient to run over HTTP/2. 38 | func ConfigureClient(c *fasthttp.HostClient, opts ClientOpts) error { 39 | emptyServerName := c.TLSConfig != nil && c.TLSConfig.ServerName == "" 40 | 41 | d := &Dialer{ 42 | Addr: c.Addr, 43 | TLSConfig: c.TLSConfig, 44 | PingInterval: opts.PingInterval, 45 | NetDial: c.Dial, 46 | } 47 | 48 | cl := createClient(d, opts) 49 | cl.conns.Init() 50 | 51 | _, _, err := cl.createConn() 52 | if err != nil { 53 | if errors.Is(err, ErrServerSupport) && c.TLSConfig != nil { // remove added config settings 54 | for i := range c.TLSConfig.NextProtos { 55 | if c.TLSConfig.NextProtos[i] == "h2" { 56 | c.TLSConfig.NextProtos = append(c.TLSConfig.NextProtos[:i], c.TLSConfig.NextProtos[i+1:]...) 57 | } 58 | } 59 | 60 | if emptyServerName { 61 | c.TLSConfig.ServerName = "" 62 | } 63 | } 64 | 65 | return err 66 | } 67 | 68 | c.IsTLS = true 69 | c.TLSConfig = d.TLSConfig 70 | 71 | c.Transport = cl.Do 72 | 73 | return nil 74 | } 75 | 76 | // ConfigureServer configures the fasthttp server to handle 77 | // HTTP/2 connections. The HTTP/2 connection can be only 78 | // established if the fasthttp server is using TLS. 79 | // 80 | // Future implementations may support HTTP/2 through plain TCP. 81 | func ConfigureServer(s *fasthttp.Server, cnf ServerConfig) *Server { 82 | cnf.defaults() 83 | 84 | s2 := &Server{ 85 | s: s, 86 | cnf: cnf, 87 | } 88 | 89 | s.NextProto(H2TLSProto, s2.ServeConn) 90 | 91 | return s2 92 | } 93 | 94 | // ConfigureServerAndConfig configures the fasthttp server to handle HTTP/2 connections 95 | // and your own tlsConfig file. If you are NOT using your own tls config, you may want to use ConfigureServer. 96 | func ConfigureServerAndConfig(s *fasthttp.Server, tlsConfig *tls.Config) *Server { 97 | s2 := &Server{ 98 | s: s, 99 | } 100 | 101 | s.NextProto(H2TLSProto, s2.ServeConn) 102 | tlsConfig.NextProtos = append(tlsConfig.NextProtos, H2TLSProto) 103 | 104 | return s2 105 | } 106 | 107 | var ErrNotAvailableStreams = errors.New("ran out of available streams") 108 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/tls" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net" 11 | "os" 12 | "strconv" 13 | "sync" 14 | "sync/atomic" 15 | "time" 16 | 17 | "github.com/valyala/fasthttp" 18 | ) 19 | 20 | // ConnOpts defines the connection options. 21 | type ConnOpts struct { 22 | // PingInterval defines the interval in which the client will ping the server. 23 | // 24 | // An interval of 0 will make the library to use DefaultPingInterval. Because ping intervals can't be disabled 25 | PingInterval time.Duration 26 | // DisablePingChecking ... 27 | DisablePingChecking bool 28 | // OnDisconnect is a callback that fires when the Conn disconnects. 29 | OnDisconnect func(c *Conn) 30 | } 31 | 32 | // Handshake performs an HTTP/2 handshake. That means, it will send 33 | // the preface if `preface` is true, send a settings frame and a 34 | // window update frame (for the connection's window). 35 | // TODO: explain more 36 | func Handshake(preface bool, bw *bufio.Writer, st *Settings, maxWin int32) error { 37 | if preface { 38 | err := WritePreface(bw) 39 | if err != nil { 40 | return err 41 | } 42 | } 43 | 44 | fr := AcquireFrameHeader() 45 | defer ReleaseFrameHeader(fr) 46 | 47 | // write the settings 48 | st2 := &Settings{} 49 | st.CopyTo(st2) 50 | 51 | fr.SetBody(st2) 52 | 53 | _, err := fr.WriteTo(bw) 54 | if err == nil { 55 | // then send a window update 56 | fr := AcquireFrameHeader() 57 | wu := AcquireFrame(FrameWindowUpdate).(*WindowUpdate) 58 | wu.SetIncrement(int(maxWin)) 59 | 60 | fr.SetBody(wu) 61 | 62 | _, err = fr.WriteTo(bw) 63 | if err == nil { 64 | err = bw.Flush() 65 | } 66 | 67 | ReleaseFrameHeader(fr) 68 | } 69 | 70 | return err 71 | } 72 | 73 | // Conn represents a raw HTTP/2 connection over TLS + TCP. 74 | type Conn struct { 75 | c net.Conn 76 | 77 | br *bufio.Reader 78 | bw *bufio.Writer 79 | 80 | enc *HPACK 81 | dec *HPACK 82 | 83 | nextID uint32 84 | 85 | serverWindow int32 86 | serverStreamWindow int32 87 | 88 | maxWindow int32 89 | currentWindow int32 90 | 91 | openStreams int32 92 | 93 | current Settings 94 | serverS Settings 95 | 96 | reqQueued sync.Map 97 | 98 | in chan *Ctx 99 | out chan *FrameHeader 100 | 101 | pingInterval time.Duration 102 | 103 | unacks int 104 | disableAcks bool 105 | 106 | lastErr error 107 | onDisconnect func(*Conn) 108 | 109 | closed uint64 110 | } 111 | 112 | // NewConn returns a new HTTP/2 connection. 113 | // To start using the connection you need to call Handshake. 114 | func NewConn(c net.Conn, opts ConnOpts) *Conn { 115 | nc := &Conn{ 116 | c: c, 117 | br: bufio.NewReaderSize(c, 4096), 118 | bw: bufio.NewWriterSize(c, maxFrameSize), 119 | enc: AcquireHPACK(), 120 | dec: AcquireHPACK(), 121 | nextID: 1, 122 | maxWindow: 1 << 20, 123 | currentWindow: 1 << 20, 124 | in: make(chan *Ctx, 128), 125 | out: make(chan *FrameHeader, 128), 126 | pingInterval: opts.PingInterval, 127 | disableAcks: opts.DisablePingChecking, 128 | onDisconnect: opts.OnDisconnect, 129 | } 130 | 131 | nc.current.SetMaxWindowSize(1 << 20) 132 | nc.current.SetPush(false) 133 | 134 | return nc 135 | } 136 | 137 | // Dialer allows creating HTTP/2 connections by specifying an address and tls configuration. 138 | type Dialer struct { 139 | // Addr is the server's address in the form: `host:port`. 140 | Addr string 141 | 142 | // TLSConfig is the tls configuration. 143 | // 144 | // If TLSConfig is nil, a default one will be defined on the Dial call. 145 | TLSConfig *tls.Config 146 | 147 | // PingInterval defines the interval in which the client will ping the server. 148 | // 149 | // An interval of 0 will make the library to use DefaultPingInterval. Because ping intervals can't be disabled. 150 | PingInterval time.Duration 151 | 152 | // NetDial defines the callback for establishing new connection to the host. 153 | // Default Dial is used if not set. 154 | NetDial fasthttp.DialFunc 155 | } 156 | 157 | func (d *Dialer) tryDial() (net.Conn, error) { 158 | if d.TLSConfig == nil || !func() bool { 159 | for _, proto := range d.TLSConfig.NextProtos { 160 | if proto == "h2" { 161 | return true 162 | } 163 | } 164 | 165 | return false 166 | }() { 167 | configureDialer(d) 168 | } 169 | 170 | var c net.Conn 171 | var err error 172 | 173 | if d.NetDial != nil { 174 | c, err = d.NetDial(d.Addr) 175 | if err != nil { 176 | return nil, err 177 | } 178 | } else { 179 | tcpAddr, err := net.ResolveTCPAddr("tcp", d.Addr) 180 | if err != nil { 181 | return nil, err 182 | } 183 | c, err = net.DialTCP("tcp", nil, tcpAddr) 184 | if err != nil { 185 | return nil, err 186 | } 187 | } 188 | 189 | tlsConn := tls.Client(c, d.TLSConfig) 190 | 191 | if err := tlsConn.Handshake(); err != nil { 192 | _ = c.Close() 193 | return nil, err 194 | } 195 | 196 | if tlsConn.ConnectionState().NegotiatedProtocol != "h2" { 197 | _ = c.Close() 198 | return nil, ErrServerSupport 199 | } 200 | 201 | return tlsConn, nil 202 | } 203 | 204 | // Dial creates an HTTP/2 connection or returns an error. 205 | // 206 | // An expected error is ErrServerSupport. 207 | func (d *Dialer) Dial(opts ConnOpts) (*Conn, error) { 208 | c, err := d.tryDial() 209 | if err != nil { 210 | return nil, err 211 | } 212 | 213 | nc := NewConn(c, opts) 214 | 215 | err = nc.Handshake() 216 | return nc, err 217 | } 218 | 219 | // SetOnDisconnect sets the callback that will fire when the HTTP/2 connection is closed. 220 | func (c *Conn) SetOnDisconnect(cb func(*Conn)) { 221 | c.onDisconnect = cb 222 | } 223 | 224 | // LastErr returns the last registered error in case the connection was closed by the server. 225 | func (c *Conn) LastErr() error { 226 | return c.lastErr 227 | } 228 | 229 | // Handshake will perform the necessary handshake to establish the connection 230 | // with the server. If an error is returned you can assume the TCP connection has been closed. 231 | func (c *Conn) Handshake() error { 232 | var err error 233 | 234 | if err = Handshake(true, c.bw, &c.current, c.maxWindow-65535); err != nil { 235 | _ = c.c.Close() 236 | return err 237 | } 238 | 239 | var fr *FrameHeader 240 | 241 | if fr, err = ReadFrameFrom(c.br); err == nil && fr.Type() != FrameSettings { 242 | _ = c.c.Close() 243 | return fmt.Errorf("unexpected frame, expected settings, got %s", fr.Type()) 244 | } else if err == nil { 245 | st := fr.Body().(*Settings) 246 | if !st.IsAck() { 247 | st.CopyTo(&c.serverS) 248 | 249 | c.serverStreamWindow += int32(c.serverS.MaxWindowSize()) 250 | if st.HeaderTableSize() <= defaultHeaderTableSize { 251 | c.enc.SetMaxTableSize(st.HeaderTableSize()) 252 | } 253 | 254 | // reply back 255 | fr := AcquireFrameHeader() 256 | 257 | stRes := AcquireFrame(FrameSettings).(*Settings) 258 | stRes.SetAck(true) 259 | 260 | fr.SetBody(stRes) 261 | 262 | if _, err = fr.WriteTo(c.bw); err == nil { 263 | err = c.bw.Flush() 264 | } 265 | 266 | ReleaseFrameHeader(fr) 267 | } 268 | } 269 | 270 | if err != nil { 271 | _ = c.c.Close() 272 | } else { 273 | ReleaseFrameHeader(fr) 274 | 275 | go c.writeLoop() 276 | go c.readLoop() 277 | } 278 | 279 | return err 280 | } 281 | 282 | // CanOpenStream returns whether the client will be able to open a new stream or not. 283 | func (c *Conn) CanOpenStream() bool { 284 | return atomic.LoadInt32(&c.openStreams) < int32(c.serverS.maxStreams) 285 | } 286 | 287 | // Closed indicates whether the connection is closed or not. 288 | func (c *Conn) Closed() bool { 289 | return atomic.LoadUint64(&c.closed) == 1 290 | } 291 | 292 | // Close closes the connection gracefully, sending a GoAway message 293 | // and then closing the underlying TCP connection. 294 | func (c *Conn) Close() error { 295 | if !atomic.CompareAndSwapUint64(&c.closed, 0, 1) { 296 | return io.EOF 297 | } 298 | 299 | close(c.in) 300 | 301 | fr := AcquireFrameHeader() 302 | defer ReleaseFrameHeader(fr) 303 | 304 | ga := AcquireFrame(FrameGoAway).(*GoAway) 305 | ga.SetStream(0) 306 | ga.SetCode(NoError) 307 | 308 | fr.SetBody(ga) 309 | 310 | _, err := fr.WriteTo(c.bw) 311 | if err == nil { 312 | err = c.bw.Flush() 313 | } 314 | 315 | _ = c.c.Close() 316 | 317 | if c.onDisconnect != nil { 318 | c.onDisconnect(c) 319 | } 320 | 321 | return err 322 | } 323 | 324 | // Write queues the request to be sent to the server. 325 | // 326 | // Check if `c` has been previously closed before accessing this function. 327 | func (c *Conn) Write(r *Ctx) { 328 | c.in <- r 329 | } 330 | 331 | type WriteError struct { 332 | err error 333 | } 334 | 335 | func (we WriteError) Error() string { 336 | return fmt.Sprintf("writing error: %s", we.err) 337 | } 338 | 339 | func (we WriteError) Unwrap() error { 340 | return we.err 341 | } 342 | 343 | func (we WriteError) Is(target error) bool { 344 | return errors.Is(we.err, target) 345 | } 346 | 347 | func (we WriteError) As(target interface{}) bool { 348 | return errors.As(we.err, target) 349 | } 350 | 351 | func (c *Conn) writeLoop() { 352 | var lastErr error 353 | 354 | defer func() { _ = c.Close() }() 355 | 356 | defer func() { 357 | if err := recover(); err != nil { 358 | if lastErr == nil { 359 | switch errn := err.(type) { 360 | case error: 361 | lastErr = errn 362 | case string: 363 | lastErr = errors.New(errn) 364 | } 365 | } 366 | } 367 | 368 | if lastErr == nil { 369 | lastErr = io.ErrUnexpectedEOF 370 | } 371 | 372 | c.reqQueued.Range(func(_, v interface{}) bool { 373 | r := v.(*Ctx) 374 | r.Err <- lastErr 375 | return true 376 | }) 377 | }() 378 | 379 | if c.pingInterval <= 0 { 380 | c.pingInterval = DefaultPingInterval 381 | } 382 | 383 | ticker := time.NewTicker(c.pingInterval) 384 | defer ticker.Stop() 385 | 386 | loop: 387 | for { 388 | select { 389 | case ctx, ok := <-c.in: // sending requests 390 | if !ok { 391 | break loop 392 | } 393 | 394 | err := c.writeRequest(ctx) 395 | if err != nil { 396 | ctx.Err <- err 397 | 398 | if errors.Is(err, ErrNotAvailableStreams) { 399 | continue 400 | } 401 | 402 | lastErr = WriteError{err} 403 | 404 | break loop 405 | } 406 | case fr, ok := <-c.out: // generic output 407 | if !ok { 408 | break loop 409 | } 410 | 411 | if _, err := fr.WriteTo(c.bw); err == nil { 412 | if err = c.bw.Flush(); err != nil { 413 | lastErr = WriteError{err} 414 | break loop 415 | } 416 | } else { 417 | lastErr = WriteError{err} 418 | break loop 419 | } 420 | 421 | ReleaseFrameHeader(fr) 422 | case <-ticker.C: // ping 423 | if err := c.writePing(); err != nil { 424 | lastErr = WriteError{err} 425 | break loop 426 | } 427 | } 428 | 429 | if !c.disableAcks && c.unacks >= 3 { 430 | lastErr = ErrTimeout 431 | break loop 432 | } 433 | } 434 | } 435 | 436 | func (c *Conn) finish(r *Ctx, stream uint32, err error) { 437 | atomic.AddInt32(&c.openStreams, -1) 438 | 439 | r.Err <- err 440 | 441 | c.reqQueued.Delete(stream) 442 | 443 | close(r.Err) 444 | } 445 | 446 | func (c *Conn) readLoop() { 447 | defer func() { _ = c.Close() }() 448 | 449 | for { 450 | fr, err := c.readNext() 451 | if err != nil { 452 | c.lastErr = err 453 | break 454 | } 455 | 456 | // TODO: panic otherwise? 457 | if ri, ok := c.reqQueued.Load(fr.Stream()); ok { 458 | r := ri.(*Ctx) 459 | 460 | err := c.readStream(fr, r.Response) 461 | if err == nil { 462 | if fr.Flags().Has(FlagEndStream) { 463 | c.finish(r, fr.Stream(), nil) 464 | } 465 | } else { 466 | c.finish(r, fr.Stream(), err) 467 | 468 | fmt.Fprintf(os.Stderr, "%s. payload=%v\n", err, fr.payload) 469 | 470 | if errors.Is(err, FlowControlError) { 471 | break 472 | } 473 | } 474 | } 475 | 476 | ReleaseFrameHeader(fr) 477 | } 478 | } 479 | 480 | func (c *Conn) writeRequest(ctx *Ctx) error { 481 | if !c.CanOpenStream() { 482 | return ErrNotAvailableStreams 483 | } 484 | 485 | req := ctx.Request 486 | 487 | hasBody := len(req.Body()) != 0 488 | 489 | enc := c.enc 490 | 491 | id := c.nextID 492 | c.nextID += 2 493 | 494 | fr := AcquireFrameHeader() 495 | defer ReleaseFrameHeader(fr) 496 | 497 | fr.SetStream(id) 498 | 499 | h := AcquireFrame(FrameHeaders).(*Headers) 500 | fr.SetBody(h) 501 | 502 | hf := AcquireHeaderField() 503 | 504 | hf.SetBytes(StringAuthority, req.URI().Host()) 505 | enc.AppendHeaderField(h, hf, true) 506 | 507 | hf.SetBytes(StringMethod, req.Header.Method()) 508 | enc.AppendHeaderField(h, hf, true) 509 | 510 | hf.SetBytes(StringPath, req.URI().RequestURI()) 511 | enc.AppendHeaderField(h, hf, true) 512 | 513 | hf.SetBytes(StringScheme, req.URI().Scheme()) 514 | enc.AppendHeaderField(h, hf, true) 515 | 516 | hf.SetBytes(StringUserAgent, req.Header.UserAgent()) 517 | enc.AppendHeaderField(h, hf, true) 518 | 519 | req.Header.VisitAll(func(k, v []byte) { 520 | if bytes.EqualFold(k, StringUserAgent) { 521 | return 522 | } 523 | 524 | hf.SetBytes(ToLower(k), v) 525 | enc.AppendHeaderField(h, hf, false) 526 | }) 527 | 528 | h.SetPadding(false) 529 | h.SetEndStream(!hasBody) 530 | h.SetEndHeaders(true) 531 | 532 | // store the ctx before sending the request 533 | c.reqQueued.Store(id, ctx) 534 | 535 | _, err := fr.WriteTo(c.bw) 536 | if err == nil && hasBody { 537 | // release headers bc it's going to get replaced by the data frame 538 | ReleaseFrame(h) 539 | 540 | err = writeData(c.bw, fr, req.Body()) 541 | } 542 | 543 | if err == nil { 544 | err = c.bw.Flush() 545 | if err == nil { 546 | atomic.AddInt32(&c.openStreams, 1) 547 | } 548 | } 549 | 550 | if err != nil { 551 | c.lastErr = err 552 | // if we had any error, remove it from the reqQueued. 553 | c.reqQueued.Delete(id) 554 | } 555 | 556 | ReleaseHeaderField(hf) 557 | 558 | return err 559 | } 560 | 561 | func writeData(bw *bufio.Writer, fh *FrameHeader, body []byte) (err error) { 562 | step := 1 << 14 563 | 564 | data := AcquireFrame(FrameData).(*Data) 565 | fh.SetBody(data) 566 | 567 | for i := 0; err == nil && i < len(body); i += step { 568 | if i+step >= len(body) { 569 | step = len(body) - i 570 | } 571 | 572 | data.SetEndStream(i+step == len(body)) 573 | data.SetPadding(false) 574 | data.SetData(body[i : step+i]) 575 | 576 | _, err = fh.WriteTo(bw) 577 | } 578 | 579 | return err 580 | } 581 | 582 | func (c *Conn) readNext() (fr *FrameHeader, err error) { 583 | for err == nil { 584 | fr, err = ReadFrameFrom(c.br) 585 | if err != nil { 586 | break 587 | } 588 | 589 | if fr.Stream() != 0 { 590 | break 591 | } 592 | 593 | switch fr.Type() { 594 | case FrameSettings: 595 | st := fr.Body().(*Settings) 596 | if !st.IsAck() { // if it has ack, just ignore 597 | c.handleSettings(st) 598 | } 599 | case FrameWindowUpdate: 600 | win := int32(fr.Body().(*WindowUpdate).Increment()) 601 | 602 | atomic.AddInt32(&c.serverWindow, win) 603 | case FramePing: 604 | ping := fr.Body().(*Ping) 605 | if !ping.IsAck() { 606 | c.handlePing(ping) 607 | } else { 608 | c.unacks-- 609 | } 610 | case FrameGoAway: 611 | err = fr.Body().(*GoAway) 612 | _ = c.Close() 613 | } 614 | 615 | ReleaseFrameHeader(fr) 616 | } 617 | 618 | return 619 | } 620 | 621 | var ErrTimeout = errors.New("server is not replying to pings") 622 | 623 | func (c *Conn) writePing() error { 624 | fr := AcquireFrameHeader() 625 | defer ReleaseFrameHeader(fr) 626 | 627 | ping := AcquireFrame(FramePing).(*Ping) 628 | ping.SetCurrentTime() 629 | 630 | fr.SetBody(ping) 631 | 632 | _, err := fr.WriteTo(c.bw) 633 | if err == nil { 634 | err = c.bw.Flush() 635 | if err == nil { 636 | c.unacks++ 637 | } 638 | } 639 | 640 | return err 641 | } 642 | 643 | func (c *Conn) handleSettings(st *Settings) { 644 | st.CopyTo(&c.serverS) 645 | 646 | c.serverStreamWindow += int32(c.serverS.MaxWindowSize()) 647 | c.enc.SetMaxTableSize(st.HeaderTableSize()) 648 | 649 | // reply back 650 | fr := AcquireFrameHeader() 651 | 652 | stRes := AcquireFrame(FrameSettings).(*Settings) 653 | stRes.SetAck(true) 654 | 655 | fr.SetBody(stRes) 656 | 657 | c.out <- fr 658 | } 659 | 660 | func (c *Conn) handlePing(ping *Ping) { 661 | // reply back 662 | fr := AcquireFrameHeader() 663 | 664 | ping.SetAck(true) 665 | 666 | fr.SetBody(ping) 667 | 668 | c.out <- fr 669 | } 670 | 671 | func (c *Conn) readStream(fr *FrameHeader, res *fasthttp.Response) (err error) { 672 | switch fr.Type() { 673 | case FrameHeaders, FrameContinuation: 674 | h := fr.Body().(FrameWithHeaders) 675 | err = c.readHeader(h.Headers(), res) 676 | case FrameData: 677 | c.currentWindow -= int32(fr.Len()) 678 | currentWin := c.currentWindow 679 | 680 | c.serverWindow -= int32(fr.Len()) 681 | 682 | data := fr.Body().(*Data) 683 | if data.Len() != 0 { 684 | res.AppendBody(data.Data()) 685 | 686 | // let's send the window update 687 | c.updateWindow(fr.Stream(), fr.Len()) 688 | } 689 | 690 | if currentWin < c.maxWindow/2 { 691 | nValue := c.maxWindow - currentWin 692 | 693 | c.currentWindow = c.maxWindow 694 | 695 | c.updateWindow(0, int(nValue)) 696 | } 697 | } 698 | 699 | return 700 | } 701 | 702 | func (c *Conn) updateWindow(streamID uint32, size int) { 703 | fr := AcquireFrameHeader() 704 | 705 | fr.SetStream(streamID) 706 | 707 | wu := AcquireFrame(FrameWindowUpdate).(*WindowUpdate) 708 | wu.SetIncrement(size) 709 | 710 | fr.SetBody(wu) 711 | 712 | c.out <- fr 713 | } 714 | 715 | func (c *Conn) readHeader(b []byte, res *fasthttp.Response) error { 716 | var err error 717 | hf := AcquireHeaderField() 718 | defer ReleaseHeaderField(hf) 719 | 720 | dec := c.dec 721 | 722 | for len(b) > 0 { 723 | b, err = dec.Next(hf, b) 724 | if err != nil { 725 | return err 726 | } 727 | 728 | if hf.IsPseudo() { 729 | if hf.KeyBytes()[1] == 's' { // status 730 | n, err := strconv.ParseInt(hf.Value(), 10, 64) 731 | if err != nil { 732 | return err 733 | } 734 | 735 | res.SetStatusCode(int(n)) 736 | continue 737 | } 738 | } 739 | 740 | if bytes.Equal(hf.KeyBytes(), StringContentLength) { 741 | n, _ := strconv.Atoi(hf.Value()) 742 | res.Header.SetContentLength(n) 743 | } else { 744 | res.Header.AddBytesKV(hf.KeyBytes(), hf.ValueBytes()) 745 | } 746 | } 747 | 748 | return nil 749 | } 750 | -------------------------------------------------------------------------------- /continuation.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | const FrameContinuation FrameType = 0x9 4 | 5 | var ( 6 | _ Frame = &Continuation{} 7 | _ FrameWithHeaders = &Continuation{} 8 | ) 9 | 10 | // Continuation represents the Continuation frame. 11 | // 12 | // Continuation frame can carry raw headers and/or the EndHeaders flag. 13 | // 14 | // https://tools.ietf.org/html/rfc7540#section-6.10 15 | type Continuation struct { 16 | endHeaders bool 17 | rawHeaders []byte 18 | } 19 | 20 | func (c *Continuation) Type() FrameType { 21 | return FrameContinuation 22 | } 23 | 24 | func (c *Continuation) Reset() { 25 | c.endHeaders = false 26 | c.rawHeaders = c.rawHeaders[:0] 27 | } 28 | 29 | func (c *Continuation) CopyTo(cc *Continuation) { 30 | cc.endHeaders = c.endHeaders 31 | cc.rawHeaders = append(cc.rawHeaders[:0], c.rawHeaders...) 32 | } 33 | 34 | // Headers returns Header bytes. 35 | func (c *Continuation) Headers() []byte { 36 | return c.rawHeaders 37 | } 38 | 39 | func (c *Continuation) SetEndHeaders(value bool) { 40 | c.endHeaders = value 41 | } 42 | 43 | func (c *Continuation) EndHeaders() bool { 44 | return c.endHeaders 45 | } 46 | 47 | func (c *Continuation) SetHeader(b []byte) { 48 | c.rawHeaders = append(c.rawHeaders[:0], b...) 49 | } 50 | 51 | // AppendHeader appends the contents of `b` into the header. 52 | func (c *Continuation) AppendHeader(b []byte) { 53 | c.rawHeaders = append(c.rawHeaders, b...) 54 | } 55 | 56 | // Write writes `b` into the header. Write is equivalent to AppendHeader. 57 | func (c *Continuation) Write(b []byte) (int, error) { 58 | n := len(b) 59 | c.AppendHeader(b) 60 | return n, nil 61 | } 62 | 63 | func (c *Continuation) Deserialize(fr *FrameHeader) error { 64 | c.endHeaders = fr.Flags().Has(FlagEndHeaders) 65 | c.SetHeader(fr.payload) 66 | 67 | return nil 68 | } 69 | 70 | func (c *Continuation) Serialize(fr *FrameHeader) { 71 | if c.endHeaders { 72 | fr.SetFlags( 73 | fr.Flags().Add(FlagEndHeaders)) 74 | } 75 | 76 | fr.setPayload(c.rawHeaders) 77 | } 78 | -------------------------------------------------------------------------------- /data.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "github.com/dgrr/http2/http2utils" 5 | ) 6 | 7 | const FrameData FrameType = 0x0 8 | 9 | var _ Frame = &Data{} 10 | 11 | // Data defines a FrameData 12 | // 13 | // Data frames can have the following flags: 14 | // END_STREAM 15 | // PADDED 16 | // 17 | // https://tools.ietf.org/html/rfc7540#section-6.1 18 | type Data struct { 19 | endStream bool 20 | hasPadding bool 21 | b []byte // data bytes 22 | } 23 | 24 | func (data *Data) Type() FrameType { 25 | return FrameData 26 | } 27 | 28 | func (data *Data) Reset() { 29 | data.endStream = false 30 | data.hasPadding = false 31 | data.b = data.b[:0] 32 | } 33 | 34 | // CopyTo copies data to d. 35 | func (data *Data) CopyTo(d *Data) { 36 | d.hasPadding = data.hasPadding 37 | d.endStream = data.endStream 38 | d.b = append(d.b[:0], data.b...) 39 | } 40 | 41 | func (data *Data) SetEndStream(value bool) { 42 | data.endStream = value 43 | } 44 | 45 | func (data *Data) EndStream() bool { 46 | return data.endStream 47 | } 48 | 49 | // Data returns the byte slice of the data read/to be sendStream. 50 | func (data *Data) Data() []byte { 51 | return data.b 52 | } 53 | 54 | // SetData resets data byte slice and sets b. 55 | func (data *Data) SetData(b []byte) { 56 | data.b = append(data.b[:0], b...) 57 | } 58 | 59 | // Padding returns true if the data will be/was hasPaddingded. 60 | func (data *Data) Padding() bool { 61 | return data.hasPadding 62 | } 63 | 64 | // SetPadding sets hasPaddingding to the data if true. If false the data won't be hasPaddingded. 65 | func (data *Data) SetPadding(value bool) { 66 | data.hasPadding = value 67 | } 68 | 69 | // Append appends b to data. 70 | func (data *Data) Append(b []byte) { 71 | data.b = append(data.b, b...) 72 | } 73 | 74 | func (data *Data) Len() int { 75 | return len(data.b) 76 | } 77 | 78 | // Write writes b to data. 79 | func (data *Data) Write(b []byte) (int, error) { 80 | n := len(b) 81 | data.Append(b) 82 | 83 | return n, nil 84 | } 85 | 86 | func (data *Data) Deserialize(fr *FrameHeader) error { 87 | payload := fr.payload 88 | 89 | if fr.Flags().Has(FlagPadded) { 90 | var err error 91 | payload, err = http2utils.CutPadding(payload, fr.Len()) 92 | if err != nil { 93 | return err 94 | } 95 | } 96 | 97 | data.endStream = fr.Flags().Has(FlagEndStream) 98 | data.b = append(data.b[:0], payload...) 99 | 100 | return nil 101 | } 102 | 103 | func (data *Data) Serialize(fr *FrameHeader) { 104 | // TODO: generate hasPadding and set to the frame payload 105 | if data.endStream { 106 | fr.SetFlags( 107 | fr.Flags().Add(FlagEndStream)) 108 | } 109 | 110 | if data.hasPadding { 111 | fr.SetFlags( 112 | fr.Flags().Add(FlagPadded)) 113 | data.b = http2utils.AddPadding(data.b) 114 | } 115 | 116 | fr.setPayload(data.b) 117 | } 118 | -------------------------------------------------------------------------------- /demo/go.mod: -------------------------------------------------------------------------------- 1 | module demo 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/dgrr/http2 v0.1.1 7 | github.com/dgrr/websocket v0.0.8 8 | github.com/fasthttp/router v1.4.0 9 | github.com/valyala/fasthttp v1.28.0 10 | ) 11 | -------------------------------------------------------------------------------- /demo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= 2 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/dgrr/http2 v0.1.1 h1:Xkkiv3i+nl75R9bS9+lawAL2IkQzDkHw9VT2KbzwSb8= 6 | github.com/dgrr/http2 v0.1.1/go.mod h1:534gu4ElcxFQDGimb9zgrC2cYTrQG2TdldOEnMSEPB8= 7 | github.com/dgrr/websocket v0.0.8 h1:8zQndnzaeo8wlxbbQXO/L9zWA9CNoTfD1dASdiSbJ34= 8 | github.com/dgrr/websocket v0.0.8/go.mod h1:d30hG8q3dQuz6eSwROXzIodSvPTNi52j1VvxrK7RWXc= 9 | github.com/fasthttp/router v1.4.0 h1:sWMk0q7M6Qj73eLIolh/934mKTNZIWDrEPDhZUF1pAg= 10 | github.com/fasthttp/router v1.4.0/go.mod h1:uTM3xaLINfEk/uqId8rv8tzwr47+HZuxopzUWfwD4qg= 11 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 12 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= 13 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 14 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 15 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 16 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= 17 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= 18 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= 19 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= 20 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 21 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= 22 | github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs= 23 | github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= 24 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 25 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 26 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 27 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 28 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 29 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 30 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 31 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 32 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 33 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 34 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 35 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8= 36 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 37 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 38 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 39 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 40 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 41 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 42 | github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873 h1:N3Af8f13ooDKcIhsmFT7Z05CStZWu4C7Md0uDEy4q6o= 43 | github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873/go.mod h1:dmPawKuiAeG/aFYVs2i+Dyosoo7FNcm+Pi8iK6ZUrX8= 44 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 45 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 46 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 47 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 48 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 49 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 50 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 51 | github.com/valyala/fasthttp v1.27.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= 52 | github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4= 53 | github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= 54 | github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= 55 | github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= 56 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 57 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 58 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 59 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 60 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 61 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 62 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 63 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E= 64 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 65 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 66 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 67 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 68 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 69 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 70 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 71 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 72 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 73 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 74 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 75 | nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= 76 | nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= 77 | -------------------------------------------------------------------------------- /demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "image" 10 | "image/jpeg" 11 | "io" 12 | "log" 13 | "strconv" 14 | "sync" 15 | "sync/atomic" 16 | "time" 17 | 18 | "github.com/dgrr/http2" 19 | "github.com/dgrr/websocket" 20 | "github.com/fasthttp/router" 21 | "github.com/valyala/fasthttp" 22 | ) 23 | 24 | func newBTCTiles() fasthttp.RequestHandler { 25 | const btcURL = "https://www.phneep.com/wp-content/uploads/2019/09/1-Strong-Hands-Bitcoin-web.jpg" 26 | 27 | statusCode, slurp, err := fasthttp.Get(nil, btcURL) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | if statusCode != 200 { 32 | log.Fatalf("Error fetching %s", btcURL) 33 | } 34 | 35 | im, err := jpeg.Decode(bytes.NewReader(slurp)) 36 | if err != nil { 37 | if len(slurp) > 1024 { 38 | slurp = slurp[:1024] 39 | } 40 | log.Fatalf("Failed to decode gopher image: %v (got %q)", err, slurp) 41 | } 42 | 43 | type subImager interface { 44 | SubImage(image.Rectangle) image.Image 45 | } 46 | 47 | const tileSize = 32 48 | xt := im.Bounds().Max.X / tileSize 49 | yt := im.Bounds().Max.Y / tileSize 50 | var tile [][][]byte // y -> x -> jpeg bytes 51 | for yi := 0; yi < yt; yi++ { 52 | var row [][]byte 53 | for xi := 0; xi < xt; xi++ { 54 | si := im.(subImager).SubImage(image.Rectangle{ 55 | Min: image.Point{X: xi * tileSize, Y: yi * tileSize}, 56 | Max: image.Point{X: (xi + 1) * tileSize, Y: (yi + 1) * tileSize}, 57 | }) 58 | buf := new(bytes.Buffer) 59 | if err := jpeg.Encode(buf, si, &jpeg.Options{Quality: 90}); err != nil { 60 | log.Fatal(err) 61 | } 62 | row = append(row, buf.Bytes()) 63 | } 64 | tile = append(tile, row) 65 | } 66 | 67 | return func(ctx *fasthttp.RequestCtx) { 68 | ms, _ := strconv.Atoi(string(ctx.FormValue("latency"))) 69 | const nanosPerMilli = 1e6 70 | if ctx.FormValue("x") != nil { 71 | x, _ := strconv.Atoi(string(ctx.FormValue("x"))) 72 | y, _ := strconv.Atoi(string(ctx.FormValue("y"))) 73 | if ms <= 1000 { 74 | time.Sleep(time.Duration(ms) * nanosPerMilli) 75 | } 76 | if x >= 0 && x < xt && y >= 0 && y < yt { 77 | ctx.SetContentType("image/jpeg") 78 | ctx.Write(tile[y][x]) 79 | } 80 | return 81 | } 82 | ctx.SetContentType("text/html; charset=utf-8") 83 | io.WriteString(ctx, "") 84 | fmt.Fprintf(ctx, "A grid of %d tiled images is below. Compare:

", xt*yt) 85 | for _, ms := range []int{0, 30, 200, 1000} { 86 | d := time.Duration(ms) * nanosPerMilli 87 | fmt.Fprintf(ctx, "[HTTP/2, %v latency] [HTTP/1, %v latency]
\n", 88 | "http2.gofiber.io", ms, d, 89 | "not_found.hehe", ms, d, 90 | ) 91 | } 92 | io.WriteString(ctx, "

\n") 93 | cacheBust := time.Now().UnixNano() 94 | for y := 0; y < yt; y++ { 95 | for x := 0; x < xt; x++ { 96 | fmt.Fprintf(ctx, "", 97 | tileSize, tileSize, x, y, cacheBust, ms) 98 | } 99 | io.WriteString(ctx, "
\n") 100 | } 101 | io.WriteString(ctx, `


102 | 120 |
<< Back to Go HTTP/2 demo server`) 121 | } 122 | } 123 | 124 | type WebSocketService struct { 125 | connCount int64 126 | conns sync.Map 127 | once sync.Once 128 | } 129 | 130 | func (ws *WebSocketService) OnOpen(c *websocket.Conn) { 131 | ws.conns.Store(c.ID(), c) 132 | 133 | log.Printf("New connection %s. Total connections %d\n", 134 | c.RemoteAddr(), atomic.AddInt64(&ws.connCount, 1)) 135 | 136 | ws.once.Do(ws.Run) 137 | } 138 | 139 | func (ws *WebSocketService) OnClose(c *websocket.Conn, err error) { 140 | if err != nil { 141 | log.Printf("Closing %s with error %s\n", c.RemoteAddr(), err) 142 | } else { 143 | log.Printf("Closing %s\n", c.RemoteAddr()) 144 | } 145 | 146 | log.Printf("Connections left %d\n", atomic.AddInt64(&ws.connCount, -1)) 147 | 148 | ws.conns.Delete(c.ID()) 149 | } 150 | 151 | type rttMessage struct { 152 | RTTInMs int64 `json:"rtt_in_ms"` 153 | } 154 | 155 | func (ws *WebSocketService) OnPong(c *websocket.Conn, data []byte) { 156 | if len(data) != 8 { 157 | return 158 | } 159 | 160 | ts := time.Now().Sub( 161 | time.Unix(0, int64( 162 | binary.BigEndian.Uint64(data))), 163 | ) 164 | 165 | data, _ = json.Marshal( 166 | rttMessage{ 167 | RTTInMs: ts.Milliseconds(), 168 | }) 169 | 170 | c.Write(data) 171 | } 172 | 173 | func (ws *WebSocketService) Run() { 174 | time.AfterFunc(time.Millisecond*500, func() { 175 | var tsData [8]byte 176 | 177 | binary.BigEndian.PutUint64( 178 | tsData[:], uint64(time.Now().UnixNano())) 179 | 180 | ws.conns.Range(func(_, v interface{}) bool { 181 | c := v.(*websocket.Conn) 182 | 183 | c.Ping(tsData[:]) 184 | 185 | log.Printf("Sending ping to %d: %s\n", c.ID(), c.RemoteAddr()) 186 | 187 | return true 188 | }) 189 | 190 | ws.Run() 191 | }) 192 | } 193 | 194 | var ( 195 | certArg = flag.String("cert", "", "idk") 196 | keyArg = flag.String("key", "", "idk") 197 | listenArg = flag.String("addr", ":8443", "idk") 198 | ) 199 | 200 | func init() { 201 | flag.Parse() 202 | } 203 | 204 | func main() { 205 | service := &WebSocketService{} 206 | 207 | ws := websocket.Server{} 208 | 209 | ws.HandleOpen(service.OnOpen) 210 | ws.HandlePong(service.OnPong) 211 | ws.HandleClose(service.OnClose) 212 | 213 | r := router.New() 214 | r.GET("/ws", ws.Upgrade) 215 | 216 | r.NotFound = newBTCTiles() 217 | 218 | s := &fasthttp.Server{ 219 | Handler: r.Handler, 220 | Name: "HTTP2 Demo", 221 | } 222 | 223 | http2.ConfigureServer(s) 224 | 225 | err := s.ListenAndServeTLS(*listenArg, *certArg, *keyArg) 226 | if err != nil { 227 | log.Fatalln(err) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | ) 8 | 9 | // ErrorCode defines the HTTP/2 error codes: 10 | // 11 | // Error codes are defined here http://httpwg.org/specs/rfc7540.html#ErrorCodes 12 | // 13 | // Errors must be uint32 because of FrameReset. 14 | type ErrorCode uint32 15 | 16 | const ( 17 | NoError ErrorCode = 0x0 18 | ProtocolError ErrorCode = 0x1 19 | InternalError ErrorCode = 0x2 20 | FlowControlError ErrorCode = 0x3 21 | SettingsTimeoutError ErrorCode = 0x4 22 | StreamClosedError ErrorCode = 0x5 23 | FrameSizeError ErrorCode = 0x6 24 | RefusedStreamError ErrorCode = 0x7 25 | StreamCanceled ErrorCode = 0x8 26 | CompressionError ErrorCode = 0x9 27 | ConnectionError ErrorCode = 0xa 28 | EnhanceYourCalm ErrorCode = 0xb 29 | InadequateSecurity ErrorCode = 0xc 30 | HTTP11Required ErrorCode = 0xd 31 | ) 32 | 33 | var errStr = [...]string{ 34 | NoError: "NoError", 35 | ProtocolError: "ProtocolError", 36 | InternalError: "InternalError", 37 | FlowControlError: "FlowControlError", 38 | SettingsTimeoutError: "SettingsTimeoutError", 39 | StreamClosedError: "StreamClosedError", 40 | FrameSizeError: "FrameSizeError", 41 | RefusedStreamError: "RefusedStreamError", 42 | StreamCanceled: "StreamCanceled", 43 | CompressionError: "CompressionError", 44 | ConnectionError: "ConnectionError", 45 | EnhanceYourCalm: "EnhanceYourCalm", 46 | InadequateSecurity: "InadequateSecurity", 47 | HTTP11Required: "HTTP11Required", 48 | } 49 | 50 | func (e ErrorCode) String() string { 51 | if int(e) >= len(errStr) { 52 | return "Unknown" 53 | } 54 | 55 | return errStr[e] 56 | } 57 | 58 | // Error implements the error interface. 59 | func (e ErrorCode) Error() string { 60 | if int(e) < len(errParser) { 61 | return errParser[e] 62 | } 63 | 64 | return strconv.Itoa(int(e)) 65 | } 66 | 67 | // Error defines the HTTP/2 errors, composed by the code and debug data. 68 | type Error struct { 69 | code ErrorCode 70 | frameType FrameType 71 | debug string 72 | } 73 | 74 | // Is implements the interface for errors.Is. 75 | func (e Error) Is(target error) bool { 76 | return errors.Is(e.code, target) 77 | } 78 | 79 | // Code returns the error code. 80 | func (e Error) Code() ErrorCode { 81 | return e.code 82 | } 83 | 84 | // Debug returns the debug string. 85 | func (e Error) Debug() string { 86 | return e.debug 87 | } 88 | 89 | // NewError creates a new Error. 90 | func NewError(e ErrorCode, debug string) Error { 91 | return Error{ 92 | code: e, 93 | debug: debug, 94 | frameType: FrameResetStream, 95 | } 96 | } 97 | 98 | func NewGoAwayError(e ErrorCode, debug string) Error { 99 | return Error{ 100 | code: e, 101 | debug: debug, 102 | frameType: FrameGoAway, 103 | } 104 | } 105 | 106 | func NewResetStreamError(e ErrorCode, debug string) Error { 107 | return Error{ 108 | code: e, 109 | debug: debug, 110 | frameType: FrameResetStream, 111 | } 112 | } 113 | 114 | // Error implements the error interface. 115 | func (e Error) Error() string { 116 | return fmt.Sprintf("%s: %s", e.code, e.debug) 117 | } 118 | 119 | var ( 120 | errParser = []string{ 121 | NoError: "No errors", 122 | ProtocolError: "Protocol error", 123 | InternalError: "Internal error", 124 | FlowControlError: "Flow control error", 125 | SettingsTimeoutError: "Settings timeout", 126 | StreamClosedError: "Stream have been closed", 127 | FrameSizeError: "FrameHeader size error", 128 | RefusedStreamError: "Refused Stream", 129 | StreamCanceled: "Stream canceled", 130 | CompressionError: "Compression error", 131 | ConnectionError: "Connection error", 132 | EnhanceYourCalm: "Enhance your calm", 133 | InadequateSecurity: "Inadequate security", 134 | HTTP11Required: "HTTP/1.1 required", 135 | } 136 | 137 | // ErrUnknownFrameType This error codes must be used with FrameGoAway. 138 | ErrUnknownFrameType = NewError( 139 | ProtocolError, "unknown frame type") 140 | ErrMissingBytes = NewError( 141 | ProtocolError, "missing payload bytes. Need more") 142 | ErrPayloadExceeds = NewError( 143 | FrameSizeError, "FrameHeader payload exceeds the negotiated maximum size") 144 | ErrCompression = NewGoAwayError( 145 | CompressionError, "Compression error") 146 | ) 147 | -------------------------------------------------------------------------------- /examples/autocert/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "encoding/pem" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | "time" 11 | 12 | "github.com/dgrr/http2" 13 | "github.com/valyala/fasthttp" 14 | "golang.org/x/crypto/acme" 15 | "golang.org/x/crypto/acme/autocert" 16 | ) 17 | 18 | func main() { 19 | hostName := "example.com" 20 | 21 | cert, priv, err := configureCert(hostName) 22 | if err != nil { 23 | log.Fatalln(err) 24 | } 25 | 26 | s := &fasthttp.Server{ 27 | Handler: requestHandler, 28 | Name: "http2 test", 29 | } 30 | 31 | http2.ConfigureServer(s) 32 | 33 | log.Println("fasthttp", s.ListenAndServeTLSEmbed(":443", cert, priv)) 34 | } 35 | 36 | func configureCert(hostName string) ([]byte, []byte, error) { 37 | m := &autocert.Manager{ 38 | Prompt: autocert.AcceptTOS, 39 | HostPolicy: autocert.HostWhitelist(hostName), 40 | Cache: autocert.DirCache("./certs"), 41 | } 42 | 43 | cfg := &tls.Config{ 44 | GetCertificate: m.GetCertificate, 45 | NextProtos: []string{ 46 | acme.ALPNProto, 47 | }, 48 | } 49 | 50 | s := &http.Server{ 51 | Addr: ":80", 52 | Handler: m.HTTPHandler(nil), 53 | TLSConfig: cfg, 54 | } 55 | go s.ListenAndServe() 56 | 57 | time.Sleep(time.Second * 10) 58 | s.Shutdown(context.Background()) 59 | 60 | data, err := m.Cache.Get(context.Background(), hostName) 61 | if err != nil { 62 | return nil, nil, err 63 | } 64 | 65 | priv, restBytes := pem.Decode(data) 66 | cert, _ := pem.Decode(restBytes) 67 | 68 | return pem.EncodeToMemory(cert), pem.EncodeToMemory(priv), nil 69 | } 70 | 71 | func requestHandler(ctx *fasthttp.RequestCtx) { 72 | fmt.Printf("%s\n", ctx.Request.Header.Header()) 73 | if ctx.Request.Header.IsPost() { 74 | fmt.Fprintf(ctx, "%s\n", ctx.Request.Body()) 75 | } else { 76 | fmt.Fprintf(ctx, "Hello 21th century!\n") 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "sync" 8 | "sync/atomic" 9 | "time" 10 | 11 | "github.com/dgrr/http2" 12 | "github.com/valyala/fasthttp" 13 | ) 14 | 15 | func main() { 16 | c := &fasthttp.HostClient{ 17 | Addr: "api.binance.com:443", 18 | IsTLS: true, 19 | } 20 | 21 | if err := http2.ConfigureClient(c, http2.ClientOpts{}); err != nil { 22 | panic(err) 23 | } 24 | 25 | var wg sync.WaitGroup 26 | 27 | reqs := 0 28 | 29 | max := int32(0) 30 | for i := 0; i < 20; i++ { 31 | wg.Add(1) 32 | 33 | for atomic.LoadInt32(&max) == 8 { 34 | time.Sleep(time.Millisecond * 20) 35 | } 36 | atomic.AddInt32(&max, 1) 37 | 38 | reqs++ 39 | 40 | fmt.Println("reqs:", reqs) 41 | 42 | go func() { 43 | defer atomic.AddInt32(&max, -1) 44 | defer wg.Done() 45 | 46 | req := fasthttp.AcquireRequest() 47 | res := fasthttp.AcquireResponse() 48 | 49 | res.Reset() 50 | 51 | req.Header.SetMethod("GET") 52 | req.URI().Update("https://api.binance.com/api/v3/exchangeInfo") 53 | 54 | err := c.Do(req, res) 55 | if err != nil { 56 | log.Println(err) 57 | 58 | res.Header.VisitAll(func(k, v []byte) { 59 | fmt.Printf("%s: %s\n", k, v) 60 | }) 61 | 62 | return 63 | } 64 | 65 | a := make(map[string]interface{}) 66 | if err = json.Unmarshal(res.Body(), &a); err != nil { 67 | panic(err) 68 | } 69 | 70 | fmt.Println(len(res.Body())) 71 | }() 72 | } 73 | 74 | fmt.Printf("total reqs: %d\n", reqs) 75 | 76 | wg.Wait() 77 | } 78 | -------------------------------------------------------------------------------- /examples/proxy/cert.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "crypto/x509/pkix" 8 | "encoding/pem" 9 | "math/big" 10 | "time" 11 | ) 12 | 13 | // GenerateTestCertificate generates a test certificate and private key based on the given host. 14 | func GenerateTestCertificate(host string) ([]byte, []byte, error) { 15 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 16 | if err != nil { 17 | return nil, nil, err 18 | } 19 | 20 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 21 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 22 | if err != nil { 23 | return nil, nil, err 24 | } 25 | 26 | cert := &x509.Certificate{ 27 | SerialNumber: serialNumber, 28 | Subject: pkix.Name{ 29 | Organization: []string{"fasthttp test"}, 30 | }, 31 | NotBefore: time.Now(), 32 | NotAfter: time.Now().Add(365 * 24 * time.Hour), 33 | KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, 34 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 35 | SignatureAlgorithm: x509.SHA256WithRSA, 36 | DNSNames: []string{host}, 37 | BasicConstraintsValid: true, 38 | IsCA: true, 39 | } 40 | 41 | certBytes, err := x509.CreateCertificate( 42 | rand.Reader, cert, cert, &priv.PublicKey, priv, 43 | ) 44 | 45 | p := pem.EncodeToMemory( 46 | &pem.Block{ 47 | Type: "PRIVATE KEY", 48 | Bytes: x509.MarshalPKCS1PrivateKey(priv), 49 | }, 50 | ) 51 | 52 | b := pem.EncodeToMemory( 53 | &pem.Block{ 54 | Type: "CERTIFICATE", 55 | Bytes: certBytes, 56 | }, 57 | ) 58 | 59 | return b, p, err 60 | } 61 | -------------------------------------------------------------------------------- /examples/proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net" 11 | "net/http" 12 | 13 | fasthttp2 "github.com/dgrr/http2" 14 | "github.com/valyala/fasthttp" 15 | "golang.org/x/net/http2" 16 | ) 17 | 18 | var ( 19 | useFastHTTP2 = flag.Bool("fast", false, "Fasthttp backend") 20 | ) 21 | 22 | func main() { 23 | certData, priv, err := GenerateTestCertificate("localhost:8080") 24 | if err != nil { 25 | log.Fatalln(err) 26 | } 27 | 28 | cert, err := tls.X509KeyPair(certData, priv) 29 | if err != nil { 30 | log.Fatalln(err) 31 | } 32 | 33 | tlsConfig := &tls.Config{ 34 | Certificates: []tls.Certificate{cert}, 35 | NextProtos: []string{"h2"}, 36 | } 37 | 38 | proxy := &Proxy{ 39 | Backend: "localhost:8081", 40 | } 41 | 42 | if !*useFastHTTP2 { 43 | go startSlowBackend() // hehe 44 | } else { 45 | go startFastBackend() 46 | } 47 | 48 | ln, err := tls.Listen("tcp", ":8443", tlsConfig) 49 | if err != nil { 50 | log.Fatalln(err) 51 | } 52 | 53 | for { 54 | c, err := ln.Accept() 55 | if err != nil { 56 | log.Fatalln(err) 57 | } 58 | 59 | go proxy.handleConn(c) 60 | } 61 | } 62 | 63 | type Proxy struct { 64 | Backend string 65 | } 66 | 67 | func (px *Proxy) handleConn(c net.Conn) { 68 | defer c.Close() 69 | 70 | bc, err := tls.Dial("tcp", px.Backend, &tls.Config{ 71 | NextProtos: []string{"h2"}, 72 | InsecureSkipVerify: true, 73 | }) 74 | if err != nil { 75 | log.Fatalln(err) 76 | } 77 | defer bc.Close() 78 | 79 | if !fasthttp2.ReadPreface(c) { 80 | log.Fatalln("error reading preface") 81 | } 82 | 83 | err = fasthttp2.WritePreface(bc) 84 | if err != nil { 85 | log.Fatalln(err) 86 | } 87 | 88 | go readFramesFrom(bc, c, false) 89 | readFramesFrom(c, bc, true) 90 | } 91 | 92 | func readFramesFrom(c, c2 net.Conn, primaryIsProxy bool) { 93 | fr := fasthttp2.AcquireFrame() 94 | defer fasthttp2.ReleaseFrame(fr) 95 | 96 | symbol := byte('>') 97 | if !primaryIsProxy { 98 | symbol = '<' 99 | } 100 | 101 | fr.SetMaxLen(0) 102 | 103 | var err error 104 | for err == nil { 105 | _, err = fr.ReadFrom(c) // TODO: Use ReadFromLimitPayload? 106 | if err != nil { 107 | if err == io.EOF { 108 | err = nil 109 | } 110 | break 111 | } 112 | 113 | debugFrame(c, fr, symbol) 114 | 115 | _, err = fr.WriteTo(c2) 116 | } 117 | } 118 | 119 | func debugFrame(c net.Conn, fr *fasthttp2.FrameHeader, symbol byte) { 120 | bf := bytes.NewBuffer(nil) 121 | 122 | fmt.Fprintf(bf, "%c %d - %s\n", symbol, fr.Stream(), c.RemoteAddr()) 123 | fmt.Fprintf(bf, "%c %d\n", symbol, fr.Len()) 124 | fmt.Fprintf(bf, "%c EndStream: %v\n", symbol, fr.HasFlag(fasthttp2.FlagEndStream)) 125 | 126 | switch fr.Type() { 127 | case fasthttp2.FrameHeaders: 128 | fmt.Fprintf(bf, "%c [HEADERS]\n", symbol) 129 | h := fasthttp2.AcquireHeaders() 130 | h.ReadFrame(fr) 131 | debugHeaders(bf, h, symbol) 132 | fasthttp2.ReleaseHeaders(h) 133 | case fasthttp2.FrameContinuation: 134 | println("continuation") 135 | case fasthttp2.FrameData: 136 | fmt.Fprintf(bf, "%c [DATA]\n", symbol) 137 | data := fasthttp2.AcquireData() 138 | data.ReadFrame(fr) 139 | debugData(bf, data, symbol) 140 | fasthttp2.ReleaseData(data) 141 | case fasthttp2.FramePriority: 142 | println("priority") 143 | // TODO: If a PRIORITY frame is received with a stream identifier of 0x0, the recipient MUST respond with a connection error 144 | case fasthttp2.FrameResetStream: 145 | println("reset") 146 | case fasthttp2.FrameSettings: 147 | fmt.Fprintf(bf, "%c [SETTINGS]\n", symbol) 148 | st := fasthttp2.AcquireSettings() 149 | st.ReadFrame(fr) 150 | debugSettings(bf, st, symbol) 151 | fasthttp2.ReleaseSettings(st) 152 | case fasthttp2.FramePushPromise: 153 | println("pp") 154 | case fasthttp2.FramePing: 155 | println("ping") 156 | case fasthttp2.FrameGoAway: 157 | println("away") 158 | case fasthttp2.FrameWindowUpdate: 159 | fmt.Fprintf(bf, "%c [WINDOW_UPDATE]\n", symbol) 160 | wu := fasthttp2.AcquireWindowUpdate() 161 | wu.ReadFrame(fr) 162 | fmt.Fprintf(bf, "%c Increment: %d\n", symbol, wu.Increment()) 163 | fasthttp2.ReleaseWindowUpdate(wu) 164 | } 165 | 166 | fmt.Println(bf.String()) 167 | } 168 | 169 | func debugSettings(bf *bytes.Buffer, st *fasthttp2.Settings, symbol byte) { 170 | fmt.Fprintf(bf, "%c ACK: %v\n", symbol, st.IsAck()) 171 | if !st.IsAck() { 172 | fmt.Fprintf(bf, "%c TableSize: %d\n", symbol, st.HeaderTableSize()) 173 | fmt.Fprintf(bf, "%c EnablePush: %v\n", symbol, st.Push()) 174 | fmt.Fprintf(bf, "%c MaxStreams: %d\n", symbol, st.MaxConcurrentStreams()) 175 | fmt.Fprintf(bf, "%c WindowSize: %d\n", symbol, st.MaxWindowSize()) 176 | fmt.Fprintf(bf, "%c FrameSize: %d\n", symbol, st.MaxFrameSize()) 177 | fmt.Fprintf(bf, "%c HeaderSize: %d\n", symbol, st.MaxHeaderListSize()) 178 | } 179 | } 180 | 181 | func debugHeaders(bf *bytes.Buffer, fr *fasthttp2.Headers, symbol byte) { 182 | hp := fasthttp2.AcquireHPACK() 183 | defer fasthttp2.ReleaseHPACK(hp) 184 | 185 | hf := fasthttp2.AcquireHeaderField() 186 | defer fasthttp2.ReleaseHeaderField(hf) 187 | 188 | fmt.Fprintf(bf, "%c EndHeaders: %v\n", symbol, fr.EndHeaders()) 189 | fmt.Fprintf(bf, "%c HasPadding: %v\n", symbol, fr.Padding()) 190 | fmt.Fprintf(bf, "%c Dependency: %d\n", symbol, fr.Stream()) 191 | 192 | var err error 193 | b := fr.Headers() 194 | 195 | for len(b) > 0 { 196 | b, err = hp.Next(hf, b) 197 | if err != nil { 198 | log.Println(err) 199 | return 200 | } 201 | 202 | fmt.Fprintf(bf, "%c %s: %s\n", symbol, hf.Key(), hf.Value()) 203 | } 204 | } 205 | 206 | func debugData(bf *bytes.Buffer, fr *fasthttp2.Data, symbol byte) { 207 | fmt.Fprintf(bf, "%c Data: %s\n", symbol, fr.Data()) 208 | } 209 | 210 | var ( 211 | hostArg = flag.String("host", "localhost:8081", "host") 212 | ) 213 | 214 | func init() { 215 | flag.Parse() 216 | } 217 | 218 | func startSlowBackend() { 219 | certData, priv, err := GenerateTestCertificate(*hostArg) 220 | if err != nil { 221 | log.Fatalln(err) 222 | } 223 | 224 | cert, err := tls.X509KeyPair(certData, priv) 225 | if err != nil { 226 | log.Fatalln(err) 227 | } 228 | 229 | tlsConfig := &tls.Config{ 230 | ServerName: *hostArg, 231 | Certificates: []tls.Certificate{cert}, 232 | MinVersion: tls.VersionTLS12, 233 | MaxVersion: tls.VersionTLS13, 234 | } 235 | 236 | _, port, _ := net.SplitHostPort(*hostArg) 237 | 238 | s := &http.Server{ 239 | Addr: ":" + port, 240 | TLSConfig: tlsConfig, 241 | Handler: &ReqHandler{}, 242 | } 243 | s2 := &http2.Server{} 244 | 245 | err = http2.ConfigureServer(s, s2) 246 | if err != nil { 247 | log.Fatalln(err) 248 | } 249 | 250 | ln, err := tls.Listen("tcp", ":"+port, tlsConfig) 251 | if err != nil { 252 | log.Fatalln(err) 253 | } 254 | defer ln.Close() 255 | 256 | err = s.Serve(ln) 257 | if err != nil { 258 | log.Fatalln(err) 259 | } 260 | } 261 | 262 | type ReqHandler struct{} 263 | 264 | func (rh *ReqHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 265 | if r.FormValue("long") == "" { 266 | fmt.Fprintf(w, "Hello 21th century!\n") 267 | } else { 268 | bf := bytes.NewBuffer(nil) 269 | for i := 0; i < 1<<24; i++ { 270 | io.WriteString(bf, "A") 271 | } 272 | w.Write(bf.Bytes()) 273 | } 274 | } 275 | 276 | func startFastBackend() { 277 | certData, priv, err := GenerateTestCertificate(*hostArg) 278 | if err != nil { 279 | log.Fatalln(err) 280 | } 281 | 282 | s := &fasthttp.Server{ 283 | Name: "idk", 284 | Handler: fastHandler, 285 | } 286 | s.AppendCertEmbed(certData, priv) 287 | 288 | fasthttp2.ConfigureServer(s) 289 | 290 | _, port, _ := net.SplitHostPort(*hostArg) 291 | 292 | err = s.ListenAndServeTLS(":"+port, "", "") 293 | if err != nil { 294 | log.Fatalln(err) 295 | } 296 | } 297 | 298 | func fastHandler(ctx *fasthttp.RequestCtx) { 299 | if ctx.FormValue("long") == nil { 300 | fmt.Fprintf(ctx, "Hello 21th century!\n") 301 | } else { 302 | for i := 0; i < 1<<24; i++ { 303 | ctx.Response.AppendBodyString("A") 304 | } 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /examples/reverse_proxy/cert.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "crypto/x509/pkix" 8 | "encoding/pem" 9 | "math/big" 10 | "time" 11 | ) 12 | 13 | // GenerateTestCertificate generates a test certificate and private key based on the given host. 14 | func GenerateTestCertificate(host string) ([]byte, []byte, error) { 15 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 16 | if err != nil { 17 | return nil, nil, err 18 | } 19 | 20 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 21 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 22 | if err != nil { 23 | return nil, nil, err 24 | } 25 | 26 | cert := &x509.Certificate{ 27 | SerialNumber: serialNumber, 28 | Subject: pkix.Name{ 29 | Organization: []string{"fasthttp test"}, 30 | }, 31 | NotBefore: time.Now(), 32 | NotAfter: time.Now().Add(365 * 24 * time.Hour), 33 | KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, 34 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 35 | SignatureAlgorithm: x509.SHA256WithRSA, 36 | DNSNames: []string{host}, 37 | BasicConstraintsValid: true, 38 | IsCA: true, 39 | } 40 | 41 | certBytes, err := x509.CreateCertificate( 42 | rand.Reader, cert, cert, &priv.PublicKey, priv, 43 | ) 44 | 45 | p := pem.EncodeToMemory( 46 | &pem.Block{ 47 | Type: "PRIVATE KEY", 48 | Bytes: x509.MarshalPKCS1PrivateKey(priv), 49 | }, 50 | ) 51 | 52 | b := pem.EncodeToMemory( 53 | &pem.Block{ 54 | Type: "CERTIFICATE", 55 | Bytes: certBytes, 56 | }, 57 | ) 58 | 59 | return b, p, err 60 | } 61 | -------------------------------------------------------------------------------- /examples/reverse_proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/dgrr/http2" 7 | "github.com/valyala/fasthttp" 8 | ) 9 | 10 | func main() { 11 | cert, priv, err := GenerateTestCertificate("localhost:8443") 12 | if err != nil { 13 | log.Fatalln(err) 14 | } 15 | 16 | s := &fasthttp.Server{ 17 | Handler: requestHandler, 18 | Name: "http2 test", 19 | } 20 | err = s.AppendCertEmbed(cert, priv) 21 | if err != nil { 22 | log.Fatalln(err) 23 | } 24 | 25 | http2.ConfigureServer(s) 26 | 27 | err = s.ListenAndServeTLS(":8443", "", "") 28 | if err != nil { 29 | log.Fatalln(err) 30 | } 31 | } 32 | 33 | func requestHandler(ctx *fasthttp.RequestCtx) { 34 | req := fasthttp.AcquireRequest() 35 | res := fasthttp.AcquireResponse() 36 | 37 | defer fasthttp.ReleaseRequest(req) 38 | defer fasthttp.ReleaseResponse(res) 39 | 40 | ctx.Request.CopyTo(req) 41 | 42 | req.Header.SetProtocol("HTTP/1.1") 43 | req.SetRequestURI("http://localhost:8080" + string(ctx.RequestURI())) 44 | 45 | if err := fasthttp.Do(req, res); err != nil { 46 | ctx.Error("gateway error", fasthttp.StatusBadGateway) 47 | return 48 | } 49 | 50 | res.CopyTo(&ctx.Response) 51 | } 52 | -------------------------------------------------------------------------------- /examples/simple/cert.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "crypto/x509/pkix" 8 | "encoding/pem" 9 | "math/big" 10 | "time" 11 | ) 12 | 13 | // GenerateTestCertificate generates a test certificate and private key based on the given host. 14 | func GenerateTestCertificate(host string) ([]byte, []byte, error) { 15 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 16 | if err != nil { 17 | return nil, nil, err 18 | } 19 | 20 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 21 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 22 | if err != nil { 23 | return nil, nil, err 24 | } 25 | 26 | cert := &x509.Certificate{ 27 | SerialNumber: serialNumber, 28 | Subject: pkix.Name{ 29 | Organization: []string{"fasthttp test"}, 30 | }, 31 | NotBefore: time.Now(), 32 | NotAfter: time.Now().Add(365 * 24 * time.Hour), 33 | KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, 34 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 35 | SignatureAlgorithm: x509.SHA256WithRSA, 36 | DNSNames: []string{host}, 37 | BasicConstraintsValid: true, 38 | IsCA: true, 39 | } 40 | 41 | certBytes, err := x509.CreateCertificate( 42 | rand.Reader, cert, cert, &priv.PublicKey, priv, 43 | ) 44 | 45 | p := pem.EncodeToMemory( 46 | &pem.Block{ 47 | Type: "PRIVATE KEY", 48 | Bytes: x509.MarshalPKCS1PrivateKey(priv), 49 | }, 50 | ) 51 | 52 | b := pem.EncodeToMemory( 53 | &pem.Block{ 54 | Type: "CERTIFICATE", 55 | Bytes: certBytes, 56 | }, 57 | ) 58 | 59 | return b, p, err 60 | } 61 | -------------------------------------------------------------------------------- /examples/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/dgrr/http2" 9 | "github.com/valyala/fasthttp" 10 | ) 11 | 12 | func main() { 13 | cert, priv, err := GenerateTestCertificate("localhost:8443") 14 | if err != nil { 15 | log.Fatalln(err) 16 | } 17 | 18 | s := &fasthttp.Server{ 19 | ReadTimeout: time.Second * 3, 20 | Handler: requestHandler, 21 | Name: "http2 test", 22 | } 23 | err = s.AppendCertEmbed(cert, priv) 24 | if err != nil { 25 | log.Fatalln(err) 26 | } 27 | 28 | http2.ConfigureServer(s, http2.ServerConfig{ 29 | Debug: true, 30 | }) 31 | 32 | err = s.ListenAndServeTLS(":8443", "", "") 33 | if err != nil { 34 | log.Fatalln(err) 35 | } 36 | } 37 | 38 | func requestHandler(ctx *fasthttp.RequestCtx) { 39 | if ctx.Request.Header.IsPost() { 40 | fmt.Fprintf(ctx, "%s\n", ctx.Request.Body()) 41 | return 42 | } 43 | 44 | if ctx.FormValue("long") == nil { 45 | fmt.Fprintf(ctx, "Hello 21th century!\n") 46 | } else { 47 | for i := 0; i < 1<<16; i++ { 48 | ctx.Response.AppendBodyString("A") 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frame.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | ) 7 | 8 | type FrameType int8 9 | 10 | func (ft FrameType) String() string { 11 | switch ft { 12 | case FrameData: 13 | return "FrameData" 14 | case FrameHeaders: 15 | return "FrameHeaders" 16 | case FramePriority: 17 | return "FramePriority" 18 | case FrameResetStream: 19 | return "FrameResetStream" 20 | case FrameSettings: 21 | return "FrameSettings" 22 | case FramePushPromise: 23 | return "FramePushPromise" 24 | case FramePing: 25 | return "FramePing" 26 | case FrameGoAway: 27 | return "FrameGoAway" 28 | case FrameWindowUpdate: 29 | return "FrameWindowUpdate" 30 | case FrameContinuation: 31 | return "FrameContinuation" 32 | } 33 | 34 | return strconv.Itoa(int(ft)) 35 | } 36 | 37 | type FrameFlags int8 38 | 39 | // Has returns if `f` is in the frame flags or not. 40 | func (flags FrameFlags) Has(f FrameFlags) bool { 41 | return flags&f == f 42 | } 43 | 44 | // Add adds a flag to frame flags. 45 | func (flags FrameFlags) Add(f FrameFlags) FrameFlags { 46 | return flags | f 47 | } 48 | 49 | // Del deletes f from frame flags. 50 | func (flags FrameFlags) Del(f FrameFlags) FrameFlags { 51 | return flags ^ f 52 | } 53 | 54 | type Frame interface { 55 | Type() FrameType 56 | Reset() 57 | 58 | Serialize(*FrameHeader) 59 | Deserialize(*FrameHeader) error 60 | } 61 | 62 | var framePools = func() [FrameContinuation + 1]*sync.Pool { 63 | var pools [FrameContinuation + 1]*sync.Pool 64 | 65 | pools[FrameData] = &sync.Pool{ 66 | New: func() interface{} { 67 | return &Data{} 68 | }, 69 | } 70 | pools[FrameHeaders] = &sync.Pool{ 71 | New: func() interface{} { 72 | return &Headers{} 73 | }, 74 | } 75 | pools[FramePriority] = &sync.Pool{ 76 | New: func() interface{} { 77 | return &Priority{} 78 | }, 79 | } 80 | pools[FrameResetStream] = &sync.Pool{ 81 | New: func() interface{} { 82 | return &RstStream{} 83 | }, 84 | } 85 | pools[FrameSettings] = &sync.Pool{ 86 | New: func() interface{} { 87 | return &Settings{} 88 | }, 89 | } 90 | pools[FramePushPromise] = &sync.Pool{ 91 | New: func() interface{} { 92 | return &PushPromise{} 93 | }, 94 | } 95 | pools[FramePing] = &sync.Pool{ 96 | New: func() interface{} { 97 | return &Ping{} 98 | }, 99 | } 100 | pools[FrameGoAway] = &sync.Pool{ 101 | New: func() interface{} { 102 | return &GoAway{} 103 | }, 104 | } 105 | pools[FrameWindowUpdate] = &sync.Pool{ 106 | New: func() interface{} { 107 | return &WindowUpdate{} 108 | }, 109 | } 110 | pools[FrameContinuation] = &sync.Pool{ 111 | New: func() interface{} { 112 | return &Continuation{} 113 | }, 114 | } 115 | 116 | return pools 117 | }() 118 | 119 | func AcquireFrame(ftype FrameType) Frame { 120 | fr := framePools[ftype].Get().(Frame) 121 | fr.Reset() 122 | 123 | return fr 124 | } 125 | 126 | func ReleaseFrame(fr Frame) { 127 | framePools[fr.Type()].Put(fr) 128 | } 129 | -------------------------------------------------------------------------------- /frameHeader.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "sync" 8 | 9 | "github.com/dgrr/http2/http2utils" 10 | ) 11 | 12 | const ( 13 | // DefaultFrameSize FrameHeader default size 14 | // http://httpwg.org/specs/rfc7540.html#FrameHeader 15 | DefaultFrameSize = 9 16 | // https://httpwg.org/specs/rfc7540.html#SETTINGS_MAX_FRAME_SIZE 17 | defaultMaxLen = 1 << 14 18 | ) 19 | 20 | // Frame Flag (described along the frame types) 21 | // More flags have been ignored due to redundancy. 22 | const ( 23 | FlagAck FrameFlags = 0x1 24 | FlagEndStream FrameFlags = 0x1 25 | FlagEndHeaders FrameFlags = 0x4 26 | FlagPadded FrameFlags = 0x8 27 | FlagPriority FrameFlags = 0x20 28 | ) 29 | 30 | // TODO: Develop methods for FrameFlags 31 | 32 | var frameHeaderPool = sync.Pool{ 33 | New: func() interface{} { 34 | return &FrameHeader{} 35 | }, 36 | } 37 | 38 | // FrameHeader is frame representation of HTTP2 protocol 39 | // 40 | // Use AcquireFrameHeader instead of creating FrameHeader every time 41 | // if you are going to use FrameHeader as your own and ReleaseFrameHeader to 42 | // delete the FrameHeader 43 | // 44 | // FrameHeader instance MUST NOT be used from different goroutines. 45 | // 46 | // https://tools.ietf.org/html/rfc7540#section-4.1 47 | type FrameHeader struct { 48 | length int // 24 bits 49 | kind FrameType // 8 bits 50 | flags FrameFlags // 8 bits 51 | stream uint32 // 31 bits 52 | 53 | maxLen uint32 54 | 55 | rawHeader [DefaultFrameSize]byte 56 | payload []byte 57 | 58 | fr Frame 59 | } 60 | 61 | // AcquireFrameHeader gets a FrameHeader from pool. 62 | func AcquireFrameHeader() *FrameHeader { 63 | fr := frameHeaderPool.Get().(*FrameHeader) 64 | fr.Reset() 65 | return fr 66 | } 67 | 68 | // ReleaseFrameHeader reset and puts fr to the pool. 69 | func ReleaseFrameHeader(fr *FrameHeader) { 70 | ReleaseFrame(fr.Body()) 71 | frameHeaderPool.Put(fr) 72 | } 73 | 74 | // Reset resets header values. 75 | func (f *FrameHeader) Reset() { 76 | f.kind = 0 77 | f.flags = 0 78 | f.stream = 0 79 | f.length = 0 80 | f.maxLen = defaultMaxLen 81 | f.fr = nil 82 | f.payload = f.payload[:0] 83 | } 84 | 85 | // Type returns the frame type (https://httpwg.org/specs/rfc7540.html#Frame_types) 86 | func (f *FrameHeader) Type() FrameType { 87 | return f.kind 88 | } 89 | 90 | func (f *FrameHeader) Flags() FrameFlags { 91 | return f.flags 92 | } 93 | 94 | func (f *FrameHeader) SetFlags(flags FrameFlags) { 95 | f.flags = flags 96 | } 97 | 98 | // Stream returns the stream id of the current frame. 99 | func (f *FrameHeader) Stream() uint32 { 100 | return f.stream 101 | } 102 | 103 | // SetStream sets the stream id on the current frame. 104 | // 105 | // This function DOESN'T delete the reserved bit (first bit) 106 | // in order to support personalized implementations of the protocol. 107 | func (f *FrameHeader) SetStream(stream uint32) { 108 | f.stream = stream 109 | } 110 | 111 | // Len returns the payload length. 112 | func (f *FrameHeader) Len() int { 113 | return f.length 114 | } 115 | 116 | // MaxLen returns max negotiated payload length. 117 | func (f *FrameHeader) MaxLen() uint32 { 118 | return f.maxLen 119 | } 120 | 121 | func (f *FrameHeader) parseValues(header []byte) { 122 | f.length = int(http2utils.BytesToUint24(header[:3])) // & (1<<24 - 1) // 3 123 | f.kind = FrameType(header[3]) // 1 124 | f.flags = FrameFlags(header[4]) // 1 125 | f.stream = http2utils.BytesToUint32(header[5:]) & (1<<31 - 1) // 4 126 | } 127 | 128 | func (f *FrameHeader) parseHeader(header []byte) { 129 | http2utils.Uint24ToBytes(header[:3], uint32(f.length)) // 2 130 | header[3] = byte(f.kind) // 1 131 | header[4] = byte(f.flags) // 1 132 | http2utils.Uint32ToBytes(header[5:], f.stream) // 4 133 | } 134 | 135 | func ReadFrameFrom(br *bufio.Reader) (*FrameHeader, error) { 136 | fr := AcquireFrameHeader() 137 | 138 | _, err := fr.ReadFrom(br) 139 | if err != nil { 140 | if fr.Body() != nil { 141 | ReleaseFrameHeader(fr) 142 | } else { 143 | frameHeaderPool.Put(fr) 144 | } 145 | 146 | fr = nil 147 | } 148 | 149 | return fr, err 150 | } 151 | 152 | func ReadFrameFromWithSize(br *bufio.Reader, max uint32) (*FrameHeader, error) { 153 | fr := AcquireFrameHeader() 154 | fr.maxLen = max 155 | 156 | _, err := fr.ReadFrom(br) 157 | if err != nil { 158 | if fr.Body() != nil { 159 | ReleaseFrameHeader(fr) 160 | } else { 161 | frameHeaderPool.Put(fr) 162 | } 163 | 164 | fr = nil 165 | } 166 | 167 | return fr, err 168 | } 169 | 170 | // ReadFrom reads frame from Reader. 171 | // 172 | // This function returns read bytes and/or error. 173 | // 174 | // Unlike io.ReaderFrom this method does not read until io.EOF. 175 | func (f *FrameHeader) ReadFrom(br *bufio.Reader) (int64, error) { 176 | return f.readFrom(br) 177 | } 178 | 179 | // TODO: Delete rb? 180 | func (f *FrameHeader) readFrom(br *bufio.Reader) (int64, error) { 181 | header, err := br.Peek(DefaultFrameSize) 182 | if err != nil { 183 | return -1, err 184 | } 185 | 186 | _, _ = br.Discard(DefaultFrameSize) 187 | 188 | rn := int64(DefaultFrameSize) 189 | 190 | // Parsing FrameHeader's Header field. 191 | f.parseValues(header) 192 | if err = f.checkLen(); err != nil { 193 | return 0, err 194 | } 195 | 196 | if f.kind > FrameContinuation { 197 | _, _ = br.Discard(f.length) 198 | return 0, ErrUnknownFrameType 199 | } 200 | f.fr = AcquireFrame(f.kind) 201 | 202 | // if max > 0 && frh.length > max { 203 | // TODO: Discard bytes and return an error 204 | if f.length > 0 { 205 | n := f.length 206 | if n < 0 { 207 | panic(fmt.Sprintf("length is less than 0 (%d). Overflow? (%d)", n, f.length)) 208 | } 209 | 210 | f.payload = http2utils.Resize(f.payload, n) 211 | 212 | n, err = io.ReadFull(br, f.payload[:n]) 213 | if err != nil { 214 | ReleaseFrame(f.fr) 215 | return 0, err 216 | } 217 | 218 | rn += int64(n) 219 | } 220 | 221 | return rn, f.fr.Deserialize(f) 222 | } 223 | 224 | // WriteTo writes frame to the Writer. 225 | // 226 | // This function returns FrameHeader bytes written and/or error. 227 | func (f *FrameHeader) WriteTo(w *bufio.Writer) (wb int64, err error) { 228 | f.fr.Serialize(f) 229 | 230 | f.length = len(f.payload) 231 | f.parseHeader(f.rawHeader[:]) 232 | 233 | n, err := w.Write(f.rawHeader[:]) 234 | if err == nil { 235 | wb += int64(n) 236 | 237 | n, err = w.Write(f.payload) 238 | wb += int64(n) 239 | } 240 | 241 | return wb, err 242 | } 243 | 244 | func (f *FrameHeader) Body() Frame { 245 | return f.fr 246 | } 247 | 248 | func (f *FrameHeader) SetBody(fr Frame) { 249 | if fr == nil { 250 | panic("Body cannot be nil") 251 | } 252 | 253 | f.kind = fr.Type() 254 | f.fr = fr 255 | } 256 | 257 | func (f *FrameHeader) setPayload(payload []byte) { 258 | f.payload = append(f.payload[:0], payload...) 259 | } 260 | 261 | func (f *FrameHeader) checkLen() error { 262 | if f.maxLen != 0 && f.length > int(f.maxLen) { 263 | return ErrPayloadExceeds 264 | } 265 | return nil 266 | } 267 | -------------------------------------------------------------------------------- /frameHeader_test.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "io" 7 | "testing" 8 | 9 | "github.com/dgrr/http2/http2utils" 10 | ) 11 | 12 | const ( 13 | testStr = "make fasthttp great again" 14 | ) 15 | 16 | func TestFrameWrite(t *testing.T) { 17 | fr := AcquireFrameHeader() 18 | defer ReleaseFrameHeader(fr) 19 | 20 | data := AcquireFrame(FrameData).(*Data) 21 | 22 | fr.SetBody(data) 23 | 24 | n, err := io.WriteString(data, testStr) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | if nn := len(testStr); n != nn { 29 | t.Fatalf("unexpected size %d<>%d", n, nn) 30 | } 31 | 32 | var bf = bytes.NewBuffer(nil) 33 | var bw = bufio.NewWriter(bf) 34 | fr.WriteTo(bw) 35 | bw.Flush() 36 | 37 | b := bf.Bytes() 38 | if str := string(b[9:]); str != testStr { 39 | t.Fatalf("mismatch %s<>%s", str, testStr) 40 | } 41 | } 42 | 43 | func TestFrameRead(t *testing.T) { 44 | var h [9]byte 45 | bf := bytes.NewBuffer(nil) 46 | br := bufio.NewReader(bf) 47 | 48 | http2utils.Uint24ToBytes(h[:3], uint32(len(testStr))) 49 | 50 | n, err := bf.Write(h[:9]) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | if n != 9 { 55 | t.Fatalf("unexpected written bytes %d<>9", n) 56 | } 57 | 58 | n, err = io.WriteString(bf, testStr) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | if n != len(testStr) { 63 | t.Fatalf("unexpected written bytes %d<>%d", n, len(testStr)) 64 | } 65 | 66 | fr := AcquireFrameHeader() 67 | defer ReleaseFrameHeader(fr) 68 | 69 | nn, err := fr.ReadFrom(br) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | n = int(nn) 74 | if n != len(testStr)+9 { 75 | t.Fatalf("unexpected read bytes %d<>%d", n, len(testStr)+9) 76 | } 77 | 78 | if fr.Type() != FrameData { 79 | t.Fatalf("unexpected frame type: %s. Expected Data", fr.Type()) 80 | } 81 | 82 | data := fr.Body().(*Data) 83 | 84 | if str := string(data.Data()); str != testStr { 85 | t.Fatalf("mismatch %s<>%s", str, testStr) 86 | } 87 | } 88 | 89 | // TODO: continue 90 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dgrr/http2 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/fatih/color v1.13.0 // indirect 7 | github.com/stretchr/testify v1.7.0 8 | github.com/summerwind/h2spec v2.2.1+incompatible 9 | github.com/valyala/fasthttp v1.28.0 10 | github.com/valyala/fastrand v1.0.0 11 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a 12 | golang.org/x/net v0.0.0-20210510120150-4163338589ed 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= 2 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 6 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 7 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 8 | github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8= 9 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 10 | github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= 11 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 12 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 13 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 14 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 18 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 19 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 20 | github.com/summerwind/h2spec v2.2.1+incompatible h1:Ex8kpG4LjIeudEtfbM892Os2PawIZBsEvukHJcvZHho= 21 | github.com/summerwind/h2spec v2.2.1+incompatible/go.mod h1:eP7IHGVDEe9cbCxRNtmGfII77lBvLgJLNfJjTaKa9sI= 22 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 23 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 24 | github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4= 25 | github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= 26 | github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= 27 | github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= 28 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 29 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= 30 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 31 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 32 | golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= 33 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 34 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 35 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 37 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= 40 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 42 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 43 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 44 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 45 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 46 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 48 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 49 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 50 | -------------------------------------------------------------------------------- /goaway.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dgrr/http2/http2utils" 7 | ) 8 | 9 | const FrameGoAway FrameType = 0x7 10 | 11 | var _ Frame = &GoAway{} 12 | 13 | // GoAway https://tools.ietf.org/html/rfc7540#section-6.8 14 | type GoAway struct { 15 | stream uint32 16 | code ErrorCode 17 | data []byte // additional data 18 | } 19 | 20 | func (ga *GoAway) Error() string { 21 | return fmt.Sprintf("stream=%d, code=%s, data=%s", ga.stream, ga.code, ga.data) 22 | } 23 | 24 | func (ga *GoAway) Type() FrameType { 25 | return FrameGoAway 26 | } 27 | 28 | func (ga *GoAway) Reset() { 29 | ga.stream = 0 30 | ga.code = 0 31 | ga.data = ga.data[:0] 32 | } 33 | 34 | func (ga *GoAway) CopyTo(other *GoAway) { 35 | other.stream = ga.stream 36 | other.code = ga.code 37 | other.data = append(other.data[:0], ga.data...) 38 | } 39 | 40 | func (ga *GoAway) Copy() *GoAway { 41 | other := new(GoAway) 42 | other.stream = ga.stream 43 | other.code = ga.code 44 | other.data = append(other.data[:0], ga.data...) 45 | return other 46 | } 47 | 48 | func (ga *GoAway) Code() ErrorCode { 49 | return ga.code 50 | } 51 | 52 | func (ga *GoAway) SetCode(code ErrorCode) { 53 | ga.code = code & (1<<31 - 1) 54 | // TODO: Set error description as a debug data? 55 | } 56 | 57 | func (ga *GoAway) Stream() uint32 { 58 | return ga.stream 59 | } 60 | 61 | func (ga *GoAway) SetStream(stream uint32) { 62 | ga.stream = stream & (1<<31 - 1) 63 | } 64 | 65 | func (ga *GoAway) Data() []byte { 66 | return ga.data 67 | } 68 | 69 | func (ga *GoAway) SetData(b []byte) { 70 | ga.data = append(ga.data[:0], b...) 71 | } 72 | 73 | func (ga *GoAway) Deserialize(fr *FrameHeader) (err error) { 74 | if len(fr.payload) < 8 { // 8 is the min number of bytes 75 | err = ErrMissingBytes 76 | } else { 77 | ga.stream = http2utils.BytesToUint32(fr.payload) 78 | ga.code = ErrorCode(http2utils.BytesToUint32(fr.payload[4:])) 79 | // TODO: what? 80 | 81 | if len(fr.payload[8:]) != 0 { 82 | ga.data = append(ga.data[:0], fr.payload[8:]...) 83 | } 84 | } 85 | 86 | return 87 | } 88 | 89 | func (ga *GoAway) Serialize(fr *FrameHeader) { 90 | fr.payload = http2utils.AppendUint32Bytes(fr.payload[:0], ga.stream) 91 | fr.payload = http2utils.AppendUint32Bytes(fr.payload[:4], uint32(ga.code)) 92 | 93 | fr.payload = append(fr.payload, ga.data...) 94 | } 95 | -------------------------------------------------------------------------------- /h2spec/h2spec_test.go: -------------------------------------------------------------------------------- 1 | package h2spec 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "crypto/x509/pkix" 8 | "encoding/pem" 9 | "log" 10 | "math/big" 11 | "net" 12 | "os" 13 | "strconv" 14 | "strings" 15 | "testing" 16 | "time" 17 | 18 | "github.com/dgrr/http2" 19 | "github.com/stretchr/testify/require" 20 | "github.com/summerwind/h2spec/config" 21 | "github.com/summerwind/h2spec/generic" 22 | h2spec "github.com/summerwind/h2spec/http2" 23 | "github.com/valyala/fasthttp" 24 | ) 25 | 26 | func TestH2Spec(t *testing.T) { 27 | port := launchLocalServer(t) 28 | 29 | testCases := []struct { 30 | desc string 31 | }{ 32 | {desc: "generic/1/1"}, 33 | {desc: "generic/2/1"}, 34 | {desc: "generic/2/2"}, 35 | {desc: "generic/2/3"}, 36 | {desc: "generic/2/4"}, 37 | {desc: "generic/2/5"}, 38 | {desc: "generic/3.1/1"}, 39 | {desc: "generic/3.1/2"}, 40 | {desc: "generic/3.1/3"}, 41 | {desc: "generic/3.2/1"}, 42 | {desc: "generic/3.2/2"}, 43 | {desc: "generic/3.2/3"}, 44 | {desc: "generic/3.3/1"}, 45 | {desc: "generic/3.3/2"}, 46 | {desc: "generic/3.3/3"}, 47 | {desc: "generic/3.3/4"}, 48 | {desc: "generic/3.3/5"}, 49 | {desc: "generic/3.4/1"}, 50 | {desc: "generic/3.5/1"}, 51 | {desc: "generic/3.7/1"}, 52 | {desc: "generic/3.8/1"}, 53 | {desc: "generic/3.9/1"}, 54 | {desc: "generic/3.9/2"}, 55 | {desc: "generic/3.10/1"}, 56 | {desc: "generic/3.10/2"}, 57 | {desc: "generic/4/1"}, 58 | {desc: "generic/4/2"}, 59 | {desc: "generic/4/3"}, 60 | {desc: "generic/4/4"}, 61 | {desc: "generic/5/1"}, 62 | {desc: "generic/5/2"}, 63 | {desc: "generic/5/3"}, 64 | {desc: "generic/5/4"}, 65 | {desc: "generic/5/5"}, 66 | {desc: "generic/5/6"}, 67 | {desc: "generic/5/7"}, 68 | {desc: "generic/5/8"}, 69 | {desc: "generic/5/9"}, 70 | {desc: "generic/5/10"}, 71 | {desc: "generic/5/11"}, 72 | {desc: "generic/5/12"}, 73 | {desc: "generic/5/13"}, 74 | {desc: "generic/5/14"}, 75 | {desc: "generic/5/15"}, 76 | 77 | {desc: "http2/3.5/1"}, 78 | {desc: "http2/3.5/2"}, 79 | {desc: "http2/4.1/1"}, 80 | {desc: "http2/4.1/2"}, 81 | {desc: "http2/4.1/3"}, 82 | {desc: "http2/4.2/1"}, 83 | {desc: "http2/4.2/2"}, 84 | {desc: "http2/4.2/3"}, 85 | {desc: "http2/4.3/1"}, 86 | {desc: "http2/4.3/2"}, 87 | {desc: "http2/4.3/3"}, 88 | {desc: "http2/5.1.1/1"}, 89 | {desc: "http2/5.1.1/2"}, 90 | // {desc: "http2/5.1.2/1"}, 91 | {desc: "http2/5.1/1"}, 92 | {desc: "http2/5.1/2"}, 93 | {desc: "http2/5.1/3"}, 94 | {desc: "http2/5.1/4"}, 95 | {desc: "http2/5.1/5"}, 96 | {desc: "http2/5.1/6"}, 97 | {desc: "http2/5.1/7"}, 98 | {desc: "http2/5.1/8"}, 99 | {desc: "http2/5.1/9"}, 100 | {desc: "http2/5.1/10"}, 101 | {desc: "http2/5.1/11"}, 102 | {desc: "http2/5.1/12"}, 103 | {desc: "http2/5.1/13"}, 104 | {desc: "http2/5.3.1/1"}, 105 | {desc: "http2/5.3.1/2"}, 106 | // About(dario): In this one we send a GOAWAY, 107 | // but the spec is expecting a connection close. 108 | // 109 | // {desc: "http2/5.4.1/1"}, 110 | {desc: "http2/5.4.1/2"}, 111 | {desc: "http2/5.5/1"}, 112 | {desc: "http2/5.5/2"}, 113 | {desc: "http2/6.1/1"}, 114 | {desc: "http2/6.1/2"}, 115 | {desc: "http2/6.1/3"}, 116 | {desc: "http2/6.2/1"}, 117 | {desc: "http2/6.2/2"}, 118 | {desc: "http2/6.2/3"}, 119 | {desc: "http2/6.2/4"}, 120 | {desc: "http2/6.3/1"}, 121 | {desc: "http2/6.3/2"}, 122 | {desc: "http2/6.4/1"}, 123 | {desc: "http2/6.4/2"}, 124 | {desc: "http2/6.4/3"}, 125 | {desc: "http2/6.5.2/1"}, 126 | {desc: "http2/6.5.2/2"}, 127 | {desc: "http2/6.5.2/3"}, 128 | {desc: "http2/6.5.2/4"}, 129 | {desc: "http2/6.5.2/5"}, 130 | {desc: "http2/6.5.3/1"}, 131 | {desc: "http2/6.5.3/2"}, 132 | {desc: "http2/6.5/1"}, 133 | {desc: "http2/6.5/2"}, 134 | {desc: "http2/6.5/3"}, 135 | {desc: "http2/6.7/1"}, 136 | {desc: "http2/6.7/2"}, 137 | {desc: "http2/6.7/3"}, 138 | {desc: "http2/6.7/4"}, 139 | {desc: "http2/6.8/1"}, 140 | {desc: "http2/6.9.1/1"}, 141 | {desc: "http2/6.9.1/2"}, 142 | {desc: "http2/6.9.1/3"}, 143 | // {desc: "http2/6.9.2/1"}, 144 | // {desc: "http2/6.9.2/2"}, 145 | {desc: "http2/6.9.2/3"}, 146 | {desc: "http2/6.9/1"}, 147 | {desc: "http2/6.9/2"}, 148 | {desc: "http2/6.9/3"}, 149 | {desc: "http2/6.10/1"}, 150 | {desc: "http2/6.10/2"}, 151 | {desc: "http2/6.10/3"}, 152 | // About(dario): In this one the client sends a HEADERS with END_HEADERS 153 | // and then sending a CONTINUATION frame. 154 | // The thing is that we process the request just after reading 155 | // the END_HEADERS, so we don't know about the next continuation. 156 | // 157 | // {desc: "http2/6.10/4"}, 158 | // {desc: "http2/6.10/5"}, 159 | {desc: "http2/6.10/6"}, 160 | {desc: "http2/7/1"}, 161 | {desc: "http2/7/2"}, 162 | // About(dario): Sends a HEADERS frame that contains the header 163 | // field name in uppercase letters. 164 | // In this case, fasthttp is case-insensitive, so we can ignore it. 165 | // {desc: "http2/8.1.2.1/1"}, 166 | // {desc: "http2/8.1.2.1/2"}, 167 | {desc: "http2/8.1.2.1/3"}, 168 | // {desc: "http2/8.1.2.1/4"}, 169 | // {desc: "http2/8.1.2.2/1"}, 170 | // {desc: "http2/8.1.2.2/2"}, 171 | // {desc: "http2/8.1.2.3/1"}, 172 | // {desc: "http2/8.1.2.3/2"}, 173 | // {desc: "http2/8.1.2.3/3"}, 174 | // {desc: "http2/8.1.2.3/4"}, 175 | // {desc: "http2/8.1.2.3/5"}, 176 | // {desc: "http2/8.1.2.3/6"}, 177 | // {desc: "http2/8.1.2.3/7"}, 178 | // {desc: "http2/8.1.2.6/1"}, 179 | // {desc: "http2/8.1.2.6/2"}, 180 | // {desc: "http2/8.1.2/1"}, 181 | {desc: "http2/8.1/1"}, 182 | {desc: "http2/8.2/1"}, 183 | } 184 | 185 | // Disable logs from h2spec 186 | oldout := os.Stdout 187 | os.Stdout = nil 188 | t.Cleanup(func() { 189 | os.Stdout = oldout 190 | }) 191 | 192 | for _, test := range testCases { 193 | test := test 194 | t.Run(test.desc, func(t *testing.T) { 195 | t.Parallel() 196 | 197 | conf := &config.Config{ 198 | Host: "127.0.0.1", 199 | Port: port, 200 | Path: "/", 201 | Timeout: time.Second, 202 | MaxHeaderLen: 4000, 203 | TLS: true, 204 | Insecure: true, 205 | Sections: []string{test.desc}, 206 | } 207 | 208 | tg := h2spec.Spec() 209 | if strings.HasPrefix(test.desc, "generic") { 210 | tg = generic.Spec() 211 | } 212 | 213 | tg.Test(conf) 214 | require.Equal(t, 0, tg.FailedCount) 215 | }) 216 | } 217 | } 218 | 219 | func launchLocalServer(t *testing.T) int { 220 | t.Helper() 221 | 222 | certPEM, keyPEM, err := KeyPair("test.default", time.Time{}) 223 | if err != nil { 224 | log.Fatalf("Unable to generate certificate: %v", err) 225 | } 226 | 227 | server := &fasthttp.Server{ 228 | Handler: func(ctx *fasthttp.RequestCtx) { 229 | ctx.Response.AppendBodyString("Test HTTP2") 230 | }, 231 | } 232 | http2.ConfigureServer(server, http2.ServerConfig{}) 233 | 234 | ln, err := net.Listen("tcp4", "127.0.0.1:0") 235 | require.NoError(t, err) 236 | go func() { 237 | log.Println(server.ServeTLSEmbed(ln, certPEM, keyPEM)) 238 | }() 239 | 240 | _, port, err := net.SplitHostPort(ln.Addr().String()) 241 | require.NoError(t, err) 242 | portInt, err := strconv.Atoi(port) 243 | require.NoError(t, err) 244 | 245 | return portInt 246 | } 247 | 248 | // DefaultDomain domain for the default certificate. 249 | const DefaultDomain = "TEST DEFAULT CERT" 250 | 251 | // KeyPair generates cert and key files. 252 | func KeyPair(domain string, expiration time.Time) ([]byte, []byte, error) { 253 | rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048) 254 | if err != nil { 255 | return nil, nil, err 256 | } 257 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)}) 258 | 259 | certPEM, err := PemCert(rsaPrivKey, domain, expiration) 260 | if err != nil { 261 | return nil, nil, err 262 | } 263 | return certPEM, keyPEM, nil 264 | } 265 | 266 | // PemCert generates PEM cert file. 267 | func PemCert(privKey *rsa.PrivateKey, domain string, expiration time.Time) ([]byte, error) { 268 | derBytes, err := derCert(privKey, expiration, domain) 269 | if err != nil { 270 | return nil, err 271 | } 272 | 273 | return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil 274 | } 275 | 276 | func derCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) { 277 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 278 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 279 | if err != nil { 280 | return nil, err 281 | } 282 | 283 | if expiration.IsZero() { 284 | expiration = time.Now().Add(365 * (24 * time.Hour)) 285 | } 286 | 287 | template := x509.Certificate{ 288 | SerialNumber: serialNumber, 289 | Subject: pkix.Name{ 290 | CommonName: DefaultDomain, 291 | }, 292 | NotBefore: time.Now(), 293 | NotAfter: expiration, 294 | 295 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageDataEncipherment, 296 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 297 | BasicConstraintsValid: true, 298 | DNSNames: []string{domain}, 299 | } 300 | 301 | return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey) 302 | } 303 | 304 | // func printTest(tg *spec.TestGroup) { 305 | // for _, group := range tg.Groups { 306 | // printTest(group) 307 | // } 308 | // for i := range tg.Tests { 309 | // fmt.Printf("{desc: \"%s/%d\"},\n", tg.ID(), i+1) 310 | // } 311 | // } 312 | -------------------------------------------------------------------------------- /headerField.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // HeaderField represents a field in HPACK tables. 8 | // 9 | // Use AcquireHeaderField to acquire HeaderField. 10 | type HeaderField struct { 11 | key, value []byte 12 | sensible bool 13 | } 14 | 15 | // String returns a string representation of the header field. 16 | func (hf *HeaderField) String() string { 17 | return string(hf.AppendBytes(nil)) 18 | } 19 | 20 | var headerPool = sync.Pool{ 21 | New: func() interface{} { 22 | return &HeaderField{} 23 | }, 24 | } 25 | 26 | // AcquireHeaderField gets HeaderField from the pool. 27 | func AcquireHeaderField() *HeaderField { 28 | return headerPool.Get().(*HeaderField) 29 | } 30 | 31 | // ReleaseHeaderField puts HeaderField to the pool. 32 | func ReleaseHeaderField(hf *HeaderField) { 33 | hf.Reset() 34 | headerPool.Put(hf) 35 | } 36 | 37 | // Empty returns true if `hf` doesn't contain any key nor value. 38 | func (hf *HeaderField) Empty() bool { 39 | return len(hf.key) == 0 && len(hf.value) == 0 40 | } 41 | 42 | // Reset resets header field values. 43 | func (hf *HeaderField) Reset() { 44 | hf.key = hf.key[:0] 45 | hf.value = hf.value[:0] 46 | hf.sensible = false 47 | } 48 | 49 | // AppendBytes appends header representation of hf to dst and returns the new dst. 50 | func (hf *HeaderField) AppendBytes(dst []byte) []byte { 51 | dst = append(dst, hf.key...) 52 | dst = append(dst, ':', ' ') 53 | dst = append(dst, hf.value...) 54 | return dst 55 | } 56 | 57 | // Size returns the header field size as RFC specifies. 58 | // 59 | // https://tools.ietf.org/html/rfc7541#section-4.1 60 | func (hf *HeaderField) Size() uint32 { 61 | return uint32(len(hf.key) + len(hf.value) + 32) 62 | } 63 | 64 | // CopyTo copies the HeaderField to `other`. 65 | func (hf *HeaderField) CopyTo(other *HeaderField) { 66 | other.key = append(other.key[:0], hf.key...) 67 | other.value = append(other.value[:0], hf.value...) 68 | other.sensible = hf.sensible 69 | } 70 | 71 | func (hf *HeaderField) Set(k, v string) { 72 | hf.SetKey(k) 73 | hf.SetValue(v) 74 | } 75 | 76 | func (hf *HeaderField) SetBytes(k, v []byte) { 77 | hf.SetKeyBytes(k) 78 | hf.SetValueBytes(v) 79 | } 80 | 81 | // Key returns the key of the field. 82 | func (hf *HeaderField) Key() string { 83 | return string(hf.key) 84 | } 85 | 86 | // Value returns the value of the field. 87 | func (hf *HeaderField) Value() string { 88 | return string(hf.value) 89 | } 90 | 91 | // KeyBytes returns the key bytes of the field. 92 | func (hf *HeaderField) KeyBytes() []byte { 93 | return hf.key 94 | } 95 | 96 | // ValueBytes returns the value bytes of the field. 97 | func (hf *HeaderField) ValueBytes() []byte { 98 | return hf.value 99 | } 100 | 101 | // SetKey sets key to the field. 102 | func (hf *HeaderField) SetKey(key string) { 103 | hf.key = append(hf.key[:0], key...) 104 | } 105 | 106 | // SetValue sets value to the field. 107 | func (hf *HeaderField) SetValue(value string) { 108 | hf.value = append(hf.value[:0], value...) 109 | } 110 | 111 | // SetKeyBytes sets key to the field. 112 | func (hf *HeaderField) SetKeyBytes(key []byte) { 113 | hf.key = append(hf.key[:0], key...) 114 | } 115 | 116 | // SetValueBytes sets value to the field. 117 | func (hf *HeaderField) SetValueBytes(value []byte) { 118 | hf.value = append(hf.value[:0], value...) 119 | } 120 | 121 | // IsPseudo returns true if field is pseudo header. 122 | func (hf *HeaderField) IsPseudo() bool { 123 | return len(hf.key) > 0 && hf.key[0] == ':' 124 | } 125 | 126 | // IsSensible returns if header field have been marked as sensible. 127 | func (hf *HeaderField) IsSensible() bool { 128 | return hf.sensible 129 | } 130 | -------------------------------------------------------------------------------- /headers.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "github.com/dgrr/http2/http2utils" 5 | ) 6 | 7 | const FrameHeaders FrameType = 0x1 8 | 9 | var ( 10 | _ Frame = &Headers{} 11 | _ FrameWithHeaders = &Headers{} 12 | ) 13 | 14 | type FrameWithHeaders interface { 15 | Headers() []byte 16 | } 17 | 18 | // Headers defines a FrameHeaders 19 | // 20 | // https://tools.ietf.org/html/rfc7540#section-6.2 21 | type Headers struct { 22 | hasPadding bool 23 | stream uint32 24 | weight uint8 25 | endStream bool 26 | endHeaders bool 27 | priority bool 28 | rawHeaders []byte // this field is used to store uncompleted headers. 29 | } 30 | 31 | func (h *Headers) Reset() { 32 | h.hasPadding = false 33 | h.stream = 0 34 | h.weight = 0 35 | h.endStream = false 36 | h.endHeaders = false 37 | h.priority = false 38 | h.rawHeaders = h.rawHeaders[:0] 39 | } 40 | 41 | // CopyTo copies h fields to h2. 42 | func (h *Headers) CopyTo(h2 *Headers) { 43 | h2.hasPadding = h.hasPadding 44 | h2.stream = h.stream 45 | h2.weight = h.weight 46 | h2.endStream = h.endStream 47 | h2.endHeaders = h.endHeaders 48 | h2.rawHeaders = append(h2.rawHeaders[:0], h.rawHeaders...) 49 | } 50 | 51 | func (h *Headers) Type() FrameType { 52 | return FrameHeaders 53 | } 54 | 55 | func (h *Headers) Headers() []byte { 56 | return h.rawHeaders 57 | } 58 | 59 | func (h *Headers) SetHeaders(b []byte) { 60 | h.rawHeaders = append(h.rawHeaders[:0], b...) 61 | } 62 | 63 | // AppendRawHeaders appends b to the raw headers. 64 | func (h *Headers) AppendRawHeaders(b []byte) { 65 | h.rawHeaders = append(h.rawHeaders, b...) 66 | } 67 | 68 | func (h *Headers) AppendHeaderField(hp *HPACK, hf *HeaderField, store bool) { 69 | h.rawHeaders = hp.AppendHeader(h.rawHeaders, hf, store) 70 | } 71 | 72 | func (h *Headers) EndStream() bool { 73 | return h.endStream 74 | } 75 | 76 | func (h *Headers) SetEndStream(value bool) { 77 | h.endStream = value 78 | } 79 | 80 | func (h *Headers) EndHeaders() bool { 81 | return h.endHeaders 82 | } 83 | 84 | func (h *Headers) SetEndHeaders(value bool) { 85 | h.endHeaders = value 86 | } 87 | 88 | func (h *Headers) Stream() uint32 { 89 | return h.stream 90 | } 91 | 92 | func (h *Headers) SetStream(stream uint32) { 93 | h.stream = stream 94 | } 95 | 96 | func (h *Headers) Weight() byte { 97 | return h.weight 98 | } 99 | 100 | func (h *Headers) SetWeight(w byte) { 101 | h.weight = w 102 | } 103 | 104 | func (h *Headers) Padding() bool { 105 | return h.hasPadding 106 | } 107 | 108 | func (h *Headers) SetPadding(value bool) { 109 | h.hasPadding = value 110 | } 111 | 112 | func (h *Headers) Deserialize(frh *FrameHeader) error { 113 | flags := frh.Flags() 114 | payload := frh.payload 115 | 116 | if flags.Has(FlagPadded) { 117 | var err error 118 | payload, err = http2utils.CutPadding(payload, len(payload)) 119 | if err != nil { 120 | return err 121 | } 122 | } 123 | 124 | if flags.Has(FlagPriority) { 125 | if len(payload) < 5 { // 4 (stream) + 1 (weight) 126 | return ErrMissingBytes 127 | } 128 | h.priority = true 129 | h.stream = http2utils.BytesToUint32(payload) & (1<<31 - 1) 130 | h.weight = payload[4] 131 | payload = payload[5:] 132 | } 133 | 134 | h.endStream = flags.Has(FlagEndStream) 135 | h.endHeaders = flags.Has(FlagEndHeaders) 136 | h.rawHeaders = append(h.rawHeaders, payload...) 137 | 138 | return nil 139 | } 140 | 141 | func (h *Headers) Serialize(frh *FrameHeader) { 142 | if h.endStream { 143 | frh.SetFlags( 144 | frh.Flags().Add(FlagEndStream)) 145 | } 146 | 147 | if h.endHeaders { 148 | frh.SetFlags( 149 | frh.Flags().Add(FlagEndHeaders)) 150 | } 151 | 152 | if h.priority { 153 | frh.SetFlags( 154 | frh.Flags().Add(FlagPriority)) 155 | 156 | // prepend stream and weight to rawHeaders 157 | h.rawHeaders = append(h.rawHeaders, 0, 0, 0, 0, 0) 158 | copy(h.rawHeaders[5:], h.rawHeaders) 159 | http2utils.Uint32ToBytes(h.rawHeaders[0:4], frh.stream) 160 | h.rawHeaders[4] = h.weight 161 | } 162 | 163 | if h.hasPadding { 164 | frh.SetFlags( 165 | frh.Flags().Add(FlagPadded)) 166 | h.rawHeaders = http2utils.AddPadding(h.rawHeaders) 167 | } 168 | 169 | frh.payload = append(frh.payload[:0], h.rawHeaders...) 170 | } 171 | -------------------------------------------------------------------------------- /hpack.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "sync" 8 | ) 9 | 10 | // HPACK represents header compression methods to 11 | // encode and decode header fields in HTTP/2. 12 | // 13 | // HPACK is equivalent to an HTTP/1 header. 14 | // 15 | // Use AcquireHPACK to acquire new HPACK structure. 16 | type HPACK struct { 17 | // DisableCompression disables compression for literal header fields. 18 | DisableCompression bool 19 | 20 | // DisableDynamicTable disables the usage of the dynamic table for 21 | // the HPACK structure. If this option is true the HPACK won't add any 22 | // field to the dynamic table unless it was sent by the peer. 23 | // 24 | // This field was implemented because in many ways the server could modify 25 | // the fields established by the client losing performance calculated by client. 26 | DisableDynamicTable bool 27 | 28 | // the dynamic table is in an inverse order. 29 | // 30 | // the insertion point should be the beginning. But we are going to do 31 | // the opposite, insert on the end and drop on the beginning. 32 | // 33 | // To get the original index then we need to do the following: 34 | // dynamic_length - (input_index - 62) - 1 35 | dynamic []*HeaderField 36 | 37 | maxTableSize uint32 38 | } 39 | 40 | func headerFieldsToString(hfs []*HeaderField, indexOffset int) string { 41 | s := "" 42 | 43 | for i := len(hfs) - 1; i >= 0; i-- { 44 | s += fmt.Sprintf("%d - %s\n", (len(hfs)-i)+indexOffset-1, hfs[i]) 45 | } 46 | 47 | return s 48 | } 49 | 50 | var hpackPool = sync.Pool{ 51 | New: func() interface{} { 52 | return &HPACK{ 53 | maxTableSize: defaultHeaderTableSize, 54 | dynamic: make([]*HeaderField, 0, 16), 55 | } 56 | }, 57 | } 58 | 59 | // AcquireHPACK gets HPACK from pool. 60 | func AcquireHPACK() *HPACK { 61 | // TODO: Change the name 62 | hp := hpackPool.Get().(*HPACK) 63 | hp.Reset() 64 | 65 | return hp 66 | } 67 | 68 | // ReleaseHPACK puts HPACK to the pool. 69 | func ReleaseHPACK(hp *HPACK) { 70 | hpackPool.Put(hp) 71 | } 72 | 73 | func (hp *HPACK) releaseDynamic() { 74 | for _, hf := range hp.dynamic { 75 | ReleaseHeaderField(hf) 76 | } 77 | 78 | hp.dynamic = hp.dynamic[:0] 79 | } 80 | 81 | // Reset deletes and releases all dynamic header fields. 82 | func (hp *HPACK) Reset() { 83 | hp.releaseDynamic() 84 | hp.maxTableSize = defaultHeaderTableSize 85 | hp.DisableCompression = false 86 | } 87 | 88 | // SetMaxTableSize sets the maximum dynamic table size. 89 | func (hp *HPACK) SetMaxTableSize(size uint32) { 90 | hp.maxTableSize = size 91 | } 92 | 93 | // DynamicSize returns the size of the dynamic table. 94 | // 95 | // https://tools.ietf.org/html/rfc7541#section-4.1 96 | func (hp *HPACK) DynamicSize() (n uint32) { 97 | for _, hf := range hp.dynamic { 98 | n += hf.Size() 99 | } 100 | return 101 | } 102 | 103 | // add header field to the dynamic table. 104 | func (hp *HPACK) addDynamic(hf *HeaderField) { 105 | // TODO: Optimize using reverse indexes. 106 | 107 | // append a copy 108 | hf2 := AcquireHeaderField() 109 | hf.CopyTo(hf2) 110 | 111 | hp.dynamic = append(hp.dynamic, hf2) 112 | 113 | // checking table size 114 | hp.shrink() 115 | } 116 | 117 | // shrink the dynamic table if needed. 118 | func (hp *HPACK) shrink() { 119 | var n int // elements to remove 120 | tableSize := hp.DynamicSize() 121 | 122 | for n = 0; n < len(hp.dynamic) && tableSize > hp.maxTableSize; n++ { 123 | tableSize -= hp.dynamic[n].Size() 124 | } 125 | 126 | if n != 0 { 127 | for i := 0; i < n; i++ { 128 | // release the header field 129 | ReleaseHeaderField(hp.dynamic[i]) 130 | // shrinking slice 131 | } 132 | 133 | hp.dynamic = append(hp.dynamic[:0], hp.dynamic[n:]...) 134 | } 135 | } 136 | 137 | // peek returns HeaderField from static or dynamic table. 138 | // 139 | // n must be the index in the table. 140 | func (hp *HPACK) peek(n uint64) *HeaderField { 141 | var ( 142 | index int 143 | table []*HeaderField 144 | ) 145 | 146 | if n < maxIndex { 147 | index, table = int(n-1), staticTable 148 | } else { // search in dynamic table 149 | nn := len(hp.dynamic) - int(n-maxIndex) - 1 150 | // dynamic_len = 11 151 | // n = 64 152 | // nn = 11 - (64 - 62) - 1 = 8 153 | index, table = nn, hp.dynamic 154 | } 155 | 156 | if index < 0 { 157 | return nil 158 | } 159 | 160 | return table[index] 161 | } 162 | 163 | // find gets the index of existent key in static or dynamic tables. 164 | func (hp *HPACK) search(hf *HeaderField) (n uint64, fullMatch bool) { 165 | // start searching in the dynamic table (probably it contains fewer fields than the static). 166 | for i, hf2 := range hp.dynamic { 167 | if fullMatch = bytes.Equal(hf.key, hf2.key) && bytes.Equal(hf.value, hf2.value); fullMatch { 168 | n = uint64(maxIndex + len(hp.dynamic) - i - 1) 169 | break 170 | } 171 | } 172 | 173 | if n == 0 { 174 | for i, hf2 := range staticTable { 175 | if bytes.Equal(hf.key, hf2.key) { 176 | if fullMatch = bytes.Equal(hf.value, hf2.value); fullMatch { 177 | n = uint64(i + 1) 178 | break 179 | } 180 | 181 | if n == 0 { 182 | n = uint64(i + 1) 183 | } 184 | } 185 | } 186 | } 187 | 188 | return 189 | } 190 | 191 | const ( 192 | indexByte = 128 // 10000000 193 | literalByte = 64 // 01000000 194 | noIndexByte = 240 // 11110000 195 | ) 196 | 197 | var bytePool = sync.Pool{ 198 | New: func() interface{} { 199 | return make([]byte, 128) 200 | }, 201 | } 202 | 203 | // Next reads and process the contents of `b`. If `b` contains a valid HTTP/2 header 204 | // the content will be parsed into `hf`. 205 | // 206 | // This function returns the next byte slice that should be read. 207 | // `b` must be a valid payload coming from a Header frame. 208 | func (hp *HPACK) Next(hf *HeaderField, b []byte) ([]byte, error) { 209 | var ( 210 | n uint64 211 | c byte 212 | err error 213 | ) 214 | 215 | loop: 216 | if len(b) == 0 { 217 | return b, nil 218 | } 219 | 220 | c = b[0] 221 | 222 | switch { 223 | // Indexed Header Field. 224 | // The value must be indexed in the static or the dynamic table. 225 | // https://httpwg.org/specs/rfc7541.html#indexed.header.representation 226 | case c&indexByte == indexByte: // 1000 0000 227 | b, n = readInt(7, b) 228 | hf2 := hp.peek(n) 229 | if hf2 == nil { 230 | return b, NewError(FlowControlError, fmt.Sprintf("index field not found: %d. table:\n%s", n, 231 | headerFieldsToString(hp.dynamic, maxIndex))) 232 | } 233 | 234 | hf2.CopyTo(hf) 235 | 236 | // Literal Header Field with Incremental Indexing. 237 | // Key can be indexed or not. Then appended to the table 238 | // https://tools.ietf.org/html/rfc7541#section-6.1 239 | case c&literalByte == literalByte: // 0100 0000 240 | // Reading key 241 | if c != 64 { // Read key as index 242 | b, n = readInt(6, b) 243 | 244 | hf2 := hp.peek(n) 245 | if hf2 == nil { 246 | return b, NewError(FlowControlError, fmt.Sprintf("literal indexed field not found: %d. table:\n%s", 247 | n, headerFieldsToString(hp.dynamic, maxIndex))) 248 | } 249 | 250 | hf.SetKeyBytes(hf2.KeyBytes()) 251 | } else { // Read key literal string 252 | b = b[1:] 253 | dst := bytePool.Get().([]byte) 254 | 255 | b, dst, err = readString(dst[:0], b) 256 | if err == nil { 257 | hf.SetKeyBytes(dst) 258 | } 259 | 260 | bytePool.Put(dst) 261 | } 262 | 263 | // Reading value 264 | if err == nil { 265 | if b[0] == c { 266 | b = b[1:] 267 | } 268 | 269 | dst := bytePool.Get().([]byte) 270 | 271 | b, dst, err = readString(dst[:0], b) 272 | if err == nil { 273 | hf.SetValueBytes(dst) 274 | // add to the table as RFC specifies. 275 | hp.addDynamic(hf) 276 | } 277 | 278 | bytePool.Put(dst) 279 | } 280 | 281 | // Literal Header Field Never Indexed. 282 | // The value of this field must not be encoded 283 | // https://tools.ietf.org/html/rfc7541#section-6.2.3 284 | case c&noIndexByte == 16: // 0001 0000 285 | hf.sensible = true 286 | fallthrough 287 | // Header Field without Indexing. 288 | // This header field must not be appended to the dynamic table. 289 | // https://tools.ietf.org/html/rfc7541#section-6.2.2 290 | case c&noIndexByte == 0: // 0000 0000 291 | // Reading key 292 | if c&15 != 0 { // Reading key as index 293 | b, n = readInt(4, b) 294 | hf2 := hp.peek(n) 295 | if hf2 == nil { 296 | return b, NewError(FlowControlError, fmt.Sprintf("non indexed field not found: %d. table:\n%s", n, 297 | headerFieldsToString(hp.dynamic, maxIndex))) 298 | } 299 | 300 | hf.SetKeyBytes(hf2.key) 301 | } else { // Reading key as string literal 302 | b = b[1:] 303 | dst := bytePool.Get().([]byte) 304 | 305 | b, dst, err = readString(dst[:0], b) 306 | if err == nil { 307 | hf.SetKeyBytes(dst) 308 | } 309 | 310 | bytePool.Put(dst) 311 | } 312 | 313 | // Reading value 314 | if err == nil { 315 | if b[0] == c { 316 | b = b[1:] 317 | } 318 | 319 | dst := bytePool.Get().([]byte) 320 | 321 | b, dst, err = readString(dst[:0], b) 322 | if err == nil { 323 | hf.SetValueBytes(dst) 324 | } 325 | 326 | bytePool.Put(dst) 327 | } 328 | 329 | // Dynamic Table Size Update 330 | // Changes the size of the dynamic table. 331 | // https://tools.ietf.org/html/rfc7541#section-6.3 332 | case c&32 == 32: // 001- ---- 333 | b, n = readInt(5, b) 334 | hp.maxTableSize = uint32(n) 335 | hp.shrink() 336 | goto loop 337 | } 338 | 339 | return b, err 340 | } 341 | 342 | // readInt reads int type from header field. 343 | // https://tools.ietf.org/html/rfc7541#section-5.1 344 | func readInt(n int, b []byte) ([]byte, uint64) { 345 | // 1<<7 - 1 = 0111 1111 346 | b0 := byte(1<>= 7 386 | } 387 | 388 | dst[len(dst)-1] &= 127 389 | 390 | return dst 391 | } 392 | 393 | // readString reads string from a header field. 394 | // returns the b pointing to the next address, dst and/or error 395 | // 396 | // if error is returned b won't change the pointer address 397 | // 398 | // https://tools.ietf.org/html/rfc7541#section-5.2 399 | func readString(dst, b []byte) ([]byte, []byte, error) { 400 | var n uint64 401 | 402 | if len(b) == 0 { 403 | return b, dst, errors.New("no bytes left reading a string. Malformed data?") 404 | } 405 | 406 | mustDecode := b[0]&128 == 128 // huffman encoded 407 | 408 | b, n = readInt(7, b) 409 | if uint64(len(b)) < n { 410 | return b, dst, ErrUnexpectedSize 411 | } 412 | 413 | if mustDecode { 414 | dst = HuffmanDecode(dst, b[:n]) 415 | } else { 416 | dst = append(dst, b[:n]...) 417 | } 418 | 419 | b = b[n:] 420 | 421 | return b, dst, nil 422 | } 423 | 424 | var ErrUnexpectedSize = errors.New("unexpected size") 425 | 426 | // appendString writes bytes slice to dst and returns it. 427 | // https://tools.ietf.org/html/rfc7541#section-5.2 428 | func appendString(dst, src []byte, encode bool) []byte { 429 | var b []byte 430 | if !encode { 431 | b = src 432 | } else { 433 | b = bytePool.Get().([]byte) 434 | b = HuffmanEncode(b[:0], src) 435 | } 436 | // TODO: Encode only if length is lower with the string encoded 437 | 438 | n := uint64(len(b)) 439 | nn := len(dst) - 1 // peek last byte 440 | if nn >= 0 && dst[nn] != 0 { 441 | dst = append(dst, 0) 442 | nn++ 443 | } 444 | 445 | dst = appendInt(dst, 7, n) 446 | dst = append(dst, b...) 447 | 448 | if encode { 449 | bytePool.Put(b) 450 | dst[nn] |= 128 // setting H bit 451 | } 452 | 453 | return dst 454 | } 455 | 456 | // TODO: Change naming. 457 | func (hp *HPACK) AppendHeaderField(h *Headers, hf *HeaderField, store bool) { 458 | h.rawHeaders = hp.AppendHeader(h.rawHeaders, hf, store) 459 | } 460 | 461 | // AppendHeader appends the content of an encoded HeaderField to dst. 462 | func (hp *HPACK) AppendHeader(dst []byte, hf *HeaderField, store bool) []byte { 463 | var ( 464 | c bool 465 | bits uint8 466 | index uint64 467 | fullMatch bool 468 | ) 469 | 470 | c = !hp.DisableCompression 471 | bits = 6 472 | 473 | index, fullMatch = hp.search(hf) 474 | if hf.sensible { 475 | c = false 476 | dst = append(dst, 16) 477 | } else { 478 | if index > 0 { // key and/or value can be used as index 479 | if fullMatch { 480 | bits, dst = 7, append(dst, indexByte) // can be indexed 481 | } else if !store { // must be used as literal index 482 | bits, dst = 4, append(dst, 0) 483 | } else { 484 | dst = append(dst, literalByte) 485 | // append this field to the dynamic table. 486 | if index < maxIndex { 487 | hp.addDynamic(hf) 488 | } 489 | } 490 | } else if !store || hp.DisableDynamicTable { // with or without indexing 491 | dst = append(dst, 0, 0) 492 | } else { 493 | dst = append(dst, literalByte) 494 | hp.addDynamic(hf) 495 | } 496 | } 497 | 498 | // the only requirement to write the index is that the idx must be 499 | // greater than zero. Any Header Field Representation can use indexes. 500 | if index > 0 { 501 | dst = appendInt(dst, bits, index) 502 | } else { 503 | dst = appendString(dst, hf.key, c) 504 | } 505 | 506 | // Only writes the value if the prefix is lower than 7. So if the 507 | // Header Field Representation is not indexed. 508 | if bits != 7 { 509 | dst = appendString(dst, hf.value, c) 510 | } 511 | 512 | return dst 513 | } 514 | 515 | var staticTable = []*HeaderField{ // entry + 1 516 | {key: []byte(":authority")}, // 1 517 | {key: []byte(":method"), value: []byte("GET")}, // 2 518 | {key: []byte(":method"), value: []byte("POST")}, // 3 519 | {key: []byte(":path"), value: []byte("/")}, // 4 520 | {key: []byte(":path"), value: []byte("/index.html")}, // 5 521 | {key: []byte(":scheme"), value: []byte("http")}, // 6 522 | {key: []byte(":scheme"), value: []byte("https")}, // 7 523 | {key: []byte(":status"), value: []byte("200")}, // 8 524 | {key: []byte(":status"), value: []byte("204")}, 525 | {key: []byte(":status"), value: []byte("206")}, 526 | {key: []byte(":status"), value: []byte("304")}, 527 | {key: []byte(":status"), value: []byte("400")}, 528 | {key: []byte(":status"), value: []byte("404")}, 529 | {key: []byte(":status"), value: []byte("500")}, 530 | {key: []byte("accept-charset")}, 531 | {key: []byte("accept-encoding"), value: []byte("gzip, deflate")}, 532 | {key: []byte("accept-language")}, 533 | {key: []byte("accept-ranges")}, 534 | {key: []byte("accept")}, 535 | {key: []byte("access-control-allow-origin")}, 536 | {key: []byte("age")}, 537 | {key: []byte("allow")}, 538 | {key: []byte("authorization")}, 539 | {key: []byte("cache-control")}, 540 | {key: []byte("content-disposition")}, 541 | {key: []byte("content-encoding")}, 542 | {key: []byte("content-language")}, 543 | {key: []byte("content-length")}, 544 | {key: []byte("content-location")}, 545 | {key: []byte("content-range")}, 546 | {key: []byte("content-type")}, 547 | {key: []byte("cookie")}, 548 | {key: []byte("date")}, 549 | {key: []byte("etag")}, 550 | {key: []byte("expect")}, 551 | {key: []byte("expires")}, 552 | {key: []byte("from")}, 553 | {key: []byte("host")}, 554 | {key: []byte("if-match")}, 555 | {key: []byte("if-modified-since")}, 556 | {key: []byte("if-none-match")}, 557 | {key: []byte("if-range")}, 558 | {key: []byte("if-unmodified-since")}, 559 | {key: []byte("last-modified")}, 560 | {key: []byte("link")}, 561 | {key: []byte("location")}, 562 | {key: []byte("max-forwards")}, 563 | {key: []byte("proxy-authenticate")}, 564 | {key: []byte("proxy-authorization")}, 565 | {key: []byte("range")}, 566 | {key: []byte("referer")}, 567 | {key: []byte("refresh")}, 568 | {key: []byte("retry-after")}, 569 | {key: []byte("server")}, 570 | {key: []byte("set-cookie")}, 571 | {key: []byte("strict-transport-security")}, 572 | {key: []byte("transfer-encoding")}, 573 | {key: []byte("user-agent")}, 574 | {key: []byte("vary")}, 575 | {key: []byte("via")}, 576 | {key: []byte("www-authenticate")}, // 61 577 | } 578 | 579 | // maxIndex defines the maximum index number of the static table. 580 | const maxIndex = 62 581 | -------------------------------------------------------------------------------- /hpack_test.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/dgrr/http2/http2utils" 11 | ) 12 | 13 | var hfs = []*HeaderField{ 14 | {key: []byte("cookie"), value: []byte("testcookie")}, 15 | {key: []byte("context-type"), value: []byte("text/plain")}, 16 | } 17 | 18 | func TestHeaderFieldsToString(t *testing.T) { 19 | http2utils.AssertEqual(t, "0 - context-type: text/plain\n1 - cookie: testcookie\n", 20 | headerFieldsToString(hfs, 0)) 21 | } 22 | 23 | func TestAcquireHPACKAndReleaseHPACK(t *testing.T) { 24 | hp := &HPACK{} 25 | ReleaseHPACK(hp) 26 | http2utils.AssertEqual(t, hp, AcquireHPACK()) 27 | } 28 | 29 | func TestHPACKAppendInt(t *testing.T) { 30 | n := uint64(15) 31 | nn := uint64(1337) 32 | nnn := uint64(122) 33 | b15 := []byte{15} 34 | b1337 := []byte{31, 154, 10} 35 | b122 := []byte{122} 36 | var dst []byte 37 | 38 | dst = appendInt(dst, 5, n) 39 | if !bytes.Equal(dst, b15) { 40 | t.Fatalf("got %v. Expects %v", dst[:1], b15) 41 | } 42 | 43 | dst = appendInt(dst, 5, nn) 44 | if !bytes.Equal(dst, b1337) { 45 | t.Fatalf("got %v. Expects %v", dst, b1337) 46 | } 47 | 48 | dst[0] = 0 49 | dst = appendInt(dst[:1], 7, nnn) 50 | if !bytes.Equal(dst[:1], b122) { 51 | t.Fatalf("got %v. Expects %v", dst[:1], b122) 52 | } 53 | } 54 | 55 | func checkInt(t *testing.T, err error, n, e uint64, elen int, b []byte) { 56 | t.Helper() 57 | 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | if n != e { 62 | t.Fatalf("%d <> %d", n, e) 63 | } 64 | if b != nil && len(b) != elen { 65 | t.Fatalf("bad length. Got %d. Expected %d", len(b), elen) 66 | } 67 | } 68 | 69 | func TestHPACKReadInt(t *testing.T) { 70 | var err error 71 | var n uint64 72 | b := []byte{15, 31, 154, 10, 122} 73 | 74 | b, n = readInt(5, b) 75 | checkInt(t, err, n, 15, 4, b) 76 | 77 | b, n = readInt(5, b) 78 | checkInt(t, err, n, 1337, 1, b) 79 | 80 | b, n = readInt(7, b) 81 | checkInt(t, err, n, 122, 0, b) 82 | } 83 | 84 | func TestHPACKWriteTwoStrings(t *testing.T) { 85 | var dstA []byte 86 | var dstB []byte 87 | var err error 88 | 89 | strA := []byte(":status") 90 | strB := []byte("200") 91 | 92 | dst := appendString(nil, strA, false) 93 | dst = appendString(dst, strB, false) 94 | 95 | dst, dstA, err = readString(nil, dst) 96 | if err != nil { 97 | t.Fatal(err) 98 | } 99 | 100 | _, dstB, err = readString(nil, dst) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | if !bytes.Equal(strA, dstA) { 106 | t.Fatalf("%s<>%s", dstA, strA) 107 | } 108 | 109 | if !bytes.Equal(strB, dstB) { 110 | t.Fatalf("%s<>%s", dstB, strB) 111 | } 112 | } 113 | 114 | func check(t *testing.T, slice []*HeaderField, i int, k, v string) { 115 | t.Helper() 116 | 117 | if len(slice) <= i { 118 | t.Fatalf("fields len exceeded. %d <> %d", len(slice), i) 119 | } 120 | 121 | hf := slice[i] 122 | if string(hf.key) != k { 123 | t.Fatalf("unexpected key: %s<>%s", hf.key, k) 124 | } 125 | 126 | if string(hf.value) != v { 127 | t.Fatalf("unexpected value: %s<>%s", hf.value, v) 128 | } 129 | } 130 | 131 | func readHPACKAndCheck(t *testing.T, hpack *HPACK, b []byte, fields, table []string, tableSize uint32) { 132 | t.Helper() 133 | 134 | var err error 135 | var lck sync.Mutex 136 | var ok bool 137 | 138 | go func() { 139 | // timeout in case a header has any error 140 | time.Sleep(time.Second * 2) 141 | lck.Lock() 142 | ok = true 143 | lck.Unlock() 144 | }() 145 | 146 | hfields := make([]*HeaderField, len(fields)/2) 147 | for i := 0; len(b) > 0 && !ok; i++ { 148 | hfields[i] = AcquireHeaderField() 149 | b, err = hpack.Next(hfields[i], b) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | } 154 | lck.Lock() 155 | ok = true 156 | lck.Unlock() 157 | 158 | if len(b) > 0 { 159 | t.Fatal("error reading headers: timeout") 160 | } 161 | 162 | n := 0 163 | for i := 0; i < len(fields); i += 2 { 164 | check(t, hfields, n, fields[i], fields[i+1]) 165 | n++ 166 | } 167 | n = 0 168 | for i := len(table) - 1; i >= 0; i -= 2 { 169 | check(t, hpack.dynamic, n, table[i-1], table[i]) 170 | n++ 171 | } 172 | 173 | if hpack.DynamicSize() != tableSize { 174 | t.Fatalf("Unexpected table size: %d<>%d", hpack.DynamicSize(), tableSize) 175 | } 176 | } 177 | 178 | func TestHPACKReadRequestWithoutHuffman(t *testing.T) { 179 | b := []byte{ 180 | 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 181 | 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 182 | 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 183 | 0x6f, 0x6d, 184 | } 185 | hpack := AcquireHPACK() 186 | 187 | readHPACKAndCheck(t, hpack, b, []string{ 188 | ":method", "GET", 189 | ":scheme", "http", 190 | ":path", "/", 191 | ":authority", "www.example.com", 192 | }, []string{ 193 | ":authority", "www.example.com", 194 | }, 57) 195 | 196 | b = []byte{ 197 | 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 198 | 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 199 | 0x68, 0x65, 200 | } 201 | readHPACKAndCheck(t, hpack, b, []string{ 202 | ":method", "GET", 203 | ":scheme", "http", 204 | ":path", "/", 205 | ":authority", "www.example.com", 206 | "cache-control", "no-cache", 207 | }, []string{ 208 | "cache-control", "no-cache", 209 | ":authority", "www.example.com", 210 | }, 110) 211 | 212 | b = []byte{ 213 | 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 214 | 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 215 | 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, 216 | 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 217 | 0x76, 0x61, 0x6c, 0x75, 0x65, 218 | } 219 | readHPACKAndCheck(t, hpack, b, []string{ 220 | ":method", "GET", 221 | ":scheme", "https", 222 | ":path", "/index.html", 223 | ":authority", "www.example.com", 224 | "custom-key", "custom-value", 225 | }, []string{ 226 | "custom-key", "custom-value", 227 | "cache-control", "no-cache", 228 | ":authority", "www.example.com", 229 | }, 164) 230 | ReleaseHPACK(hpack) 231 | } 232 | 233 | func TestHPACKReadRequestWithHuffman(t *testing.T) { 234 | b := []byte{ 235 | 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 236 | 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 237 | 0xa0, 0xab, 0x90, 0xf4, 0xff, 238 | } 239 | hpack := AcquireHPACK() 240 | 241 | readHPACKAndCheck(t, hpack, b, []string{ 242 | ":method", "GET", 243 | ":scheme", "http", 244 | ":path", "/", 245 | ":authority", "www.example.com", 246 | }, []string{ 247 | ":authority", "www.example.com", 248 | }, 57) 249 | 250 | b = []byte{ 251 | 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 252 | 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf, 253 | } 254 | readHPACKAndCheck(t, hpack, b, []string{ 255 | ":method", "GET", 256 | ":scheme", "http", 257 | ":path", "/", 258 | ":authority", "www.example.com", 259 | "cache-control", "no-cache", 260 | }, []string{ 261 | "cache-control", "no-cache", 262 | ":authority", "www.example.com", 263 | }, 110) 264 | 265 | b = []byte{ 266 | 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 267 | 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 268 | 0x7d, 0x7f, 0x89, 0x25, 0xa8, 0x49, 269 | 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf, 270 | } 271 | readHPACKAndCheck(t, hpack, b, []string{ 272 | ":method", "GET", 273 | ":scheme", "https", 274 | ":path", "/index.html", 275 | ":authority", "www.example.com", 276 | "custom-key", "custom-value", 277 | }, []string{ 278 | "custom-key", "custom-value", 279 | "cache-control", "no-cache", 280 | ":authority", "www.example.com", 281 | }, 164) 282 | ReleaseHPACK(hpack) 283 | } 284 | 285 | func TestHPACKReadResponseWithoutHuffman(t *testing.T) { 286 | b := []byte{ 287 | 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 288 | 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 289 | 0x74, 0x65, 0x61, 0x1d, 0x4d, 0x6f, 290 | 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 291 | 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 292 | 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 293 | 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20, 294 | 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, 295 | 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 296 | 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 297 | 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 298 | 0x2e, 0x63, 0x6f, 0x6d, 299 | } 300 | hpack := AcquireHPACK() 301 | hpack.SetMaxTableSize(256) 302 | 303 | readHPACKAndCheck(t, hpack, b, []string{ 304 | ":status", "302", 305 | "cache-control", "private", 306 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 307 | "location", "https://www.example.com", 308 | }, []string{ 309 | "location", "https://www.example.com", 310 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 311 | "cache-control", "private", 312 | ":status", "302", 313 | }, 222) 314 | 315 | b = []byte{0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf} 316 | 317 | readHPACKAndCheck(t, hpack, b, []string{ 318 | ":status", "307", 319 | "cache-control", "private", 320 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 321 | "location", "https://www.example.com", 322 | }, []string{ 323 | ":status", "307", 324 | "location", "https://www.example.com", 325 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 326 | "cache-control", "private", 327 | }, 222) 328 | 329 | b = []byte{ 330 | 0x88, 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 331 | 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 332 | 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 333 | 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 334 | 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 335 | 0x47, 0x4d, 0x54, 0xc0, 0x5a, 0x04, 336 | 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38, 337 | 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, 338 | 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 339 | 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 340 | 0x45, 0x4f, 0x50, 0x49, 0x55, 0x41, 341 | 0x58, 0x51, 0x57, 0x45, 0x4f, 0x49, 342 | 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 343 | 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x33, 344 | 0x36, 0x30, 0x30, 0x3b, 0x20, 0x76, 345 | 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 346 | 0x3d, 0x31, 347 | } 348 | 349 | readHPACKAndCheck(t, hpack, b, []string{ 350 | ":status", "200", 351 | "cache-control", "private", 352 | "date", "Mon, 21 Oct 2013 20:13:22 GMT", 353 | "location", "https://www.example.com", 354 | "content-encoding", "gzip", 355 | "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", 356 | }, []string{ 357 | "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", 358 | "content-encoding", "gzip", 359 | "date", "Mon, 21 Oct 2013 20:13:22 GMT", 360 | }, 215) 361 | 362 | ReleaseHPACK(hpack) 363 | } 364 | 365 | func TestHPACKReadResponseWithHuffman(t *testing.T) { 366 | b := []byte{ 367 | 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 368 | 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, 369 | 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 370 | 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 371 | 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 372 | 0x82, 0xa6, 0x2d, 0x1b, 0xff, 0x6e, 373 | 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 374 | 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 375 | 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3, 376 | } 377 | hpack := AcquireHPACK() 378 | hpack.SetMaxTableSize(256) 379 | 380 | readHPACKAndCheck(t, hpack, b, []string{ 381 | ":status", "302", 382 | "cache-control", "private", 383 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 384 | "location", "https://www.example.com", 385 | }, []string{ 386 | "location", "https://www.example.com", 387 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 388 | "cache-control", "private", 389 | ":status", "302", 390 | }, 222) 391 | 392 | b = []byte{0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf} 393 | 394 | readHPACKAndCheck(t, hpack, b, []string{ 395 | ":status", "307", 396 | "cache-control", "private", 397 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 398 | "location", "https://www.example.com", 399 | }, []string{ 400 | ":status", "307", 401 | "location", "https://www.example.com", 402 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 403 | "cache-control", "private", 404 | }, 222) 405 | 406 | b = []byte{ 407 | 0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 408 | 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 409 | 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 410 | 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 411 | 0x1b, 0xff, 0xc0, 0x5a, 0x83, 0x9b, 412 | 0xd9, 0xab, 0x77, 0xad, 0x94, 0xe7, 413 | 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 414 | 0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, 415 | 0x39, 0x60, 0xd5, 0xaf, 0x27, 0x08, 416 | 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, 417 | 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 418 | 0x31, 0x60, 0x65, 0xc0, 0x03, 0xed, 419 | 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07, 420 | } 421 | 422 | readHPACKAndCheck(t, hpack, b, []string{ 423 | ":status", "200", 424 | "cache-control", "private", 425 | "date", "Mon, 21 Oct 2013 20:13:22 GMT", 426 | "location", "https://www.example.com", 427 | "content-encoding", "gzip", 428 | "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", 429 | }, []string{ 430 | "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", 431 | "content-encoding", "gzip", 432 | "date", "Mon, 21 Oct 2013 20:13:22 GMT", 433 | }, 215) 434 | 435 | ReleaseHPACK(hpack) 436 | } 437 | 438 | func compare(b, r []byte) int { 439 | for i, c := range b { 440 | if c != r[i] { 441 | return i 442 | } 443 | } 444 | return -1 445 | } 446 | 447 | func writeHPACKAndCheck(t *testing.T, hpack *HPACK, r []byte, fields, table []string, tableSize uint32) { 448 | t.Helper() 449 | 450 | n := 0 451 | hfs := make([]*HeaderField, 0, len(fields)/2) 452 | for i := 0; i < len(fields); i += 2 { 453 | hf := AcquireHeaderField() 454 | hf.Set(fields[i], fields[i+1]) 455 | hfs = append(hfs, hf) 456 | n++ 457 | } 458 | 459 | var b []byte 460 | 461 | for _, hf := range hfs { 462 | b = hpack.AppendHeader(b, hf, true) 463 | } 464 | 465 | if i := compare(b, r); i != -1 { 466 | t.Fatalf("failed in %d (%d): %s", i, tableSize, hexComparison(b[i:], r[i:])) 467 | } 468 | 469 | n = 0 470 | for i := len(table) - 1; i >= 0; i -= 2 { 471 | check(t, hpack.dynamic, n, table[i-1], table[i]) 472 | n++ 473 | } 474 | 475 | if hpack.DynamicSize() != tableSize { 476 | t.Fatalf("Unexpected table size: %d<>%d", hpack.DynamicSize(), tableSize) 477 | } 478 | } 479 | 480 | func TestHPACKWriteRequestWithoutHuffman(t *testing.T) { 481 | r := []byte{ 482 | 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 483 | 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 484 | 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 485 | 0x6f, 0x6d, 486 | } 487 | hpack := AcquireHPACK() 488 | hpack.DisableCompression = true 489 | 490 | writeHPACKAndCheck(t, hpack, r, []string{ 491 | ":method", "GET", 492 | ":scheme", "http", 493 | ":path", "/", 494 | ":authority", "www.example.com", 495 | }, []string{ 496 | ":authority", "www.example.com", 497 | }, 57) 498 | 499 | r = []byte{ 500 | 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 501 | 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 502 | 0x68, 0x65, 503 | } 504 | writeHPACKAndCheck(t, hpack, r, []string{ 505 | ":method", "GET", 506 | ":scheme", "http", 507 | ":path", "/", 508 | ":authority", "www.example.com", 509 | "cache-control", "no-cache", 510 | }, []string{ 511 | "cache-control", "no-cache", 512 | ":authority", "www.example.com", 513 | }, 110) 514 | 515 | r = []byte{ 516 | 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 517 | 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 518 | 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, 519 | 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 520 | 0x76, 0x61, 0x6c, 0x75, 0x65, 521 | } 522 | 523 | writeHPACKAndCheck(t, hpack, r, []string{ 524 | ":method", "GET", 525 | ":scheme", "https", 526 | ":path", "/index.html", 527 | ":authority", "www.example.com", 528 | "custom-key", "custom-value", 529 | }, []string{ 530 | "custom-key", "custom-value", 531 | "cache-control", "no-cache", 532 | ":authority", "www.example.com", 533 | }, 164) 534 | 535 | ReleaseHPACK(hpack) 536 | } 537 | 538 | func TestHPACKWriteRequestWithHuffman(t *testing.T) { 539 | r := []byte{ 540 | 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 541 | 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 542 | 0xa0, 0xab, 0x90, 0xf4, 0xff, 543 | } 544 | hpack := AcquireHPACK() 545 | 546 | writeHPACKAndCheck(t, hpack, r, []string{ 547 | ":method", "GET", 548 | ":scheme", "http", 549 | ":path", "/", 550 | ":authority", "www.example.com", 551 | }, []string{ 552 | ":authority", "www.example.com", 553 | }, 57) 554 | 555 | r = []byte{ 556 | 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 557 | 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf, 558 | } 559 | writeHPACKAndCheck(t, hpack, r, []string{ 560 | ":method", "GET", 561 | ":scheme", "http", 562 | ":path", "/", 563 | ":authority", "www.example.com", 564 | "cache-control", "no-cache", 565 | }, []string{ 566 | "cache-control", "no-cache", 567 | ":authority", "www.example.com", 568 | }, 110) 569 | 570 | r = []byte{ 571 | 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 572 | 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 573 | 0x7d, 0x7f, 0x89, 0x25, 0xa8, 0x49, 574 | 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf, 575 | } 576 | writeHPACKAndCheck(t, hpack, r, []string{ 577 | ":method", "GET", 578 | ":scheme", "https", 579 | ":path", "/index.html", 580 | ":authority", "www.example.com", 581 | "custom-key", "custom-value", 582 | }, []string{ 583 | "custom-key", "custom-value", 584 | "cache-control", "no-cache", 585 | ":authority", "www.example.com", 586 | }, 164) 587 | ReleaseHPACK(hpack) 588 | } 589 | 590 | func TestHPACKWriteResponseWithoutHuffman(t *testing.T) { // without huffman 591 | r := []byte{ 592 | 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 593 | 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 594 | 0x74, 0x65, 0x61, 0x1d, 0x4d, 0x6f, 595 | 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 596 | 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 597 | 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 598 | 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20, 599 | 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, 600 | 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 601 | 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 602 | 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 603 | 0x2e, 0x63, 0x6f, 0x6d, 604 | } 605 | hpack := AcquireHPACK() 606 | hpack.DisableCompression = true 607 | hpack.SetMaxTableSize(256) 608 | 609 | writeHPACKAndCheck(t, hpack, r, []string{ 610 | ":status", "302", 611 | "cache-control", "private", 612 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 613 | "location", "https://www.example.com", 614 | }, []string{ 615 | "location", "https://www.example.com", 616 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 617 | "cache-control", "private", 618 | ":status", "302", 619 | }, 222) 620 | 621 | r = []byte{0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf} 622 | writeHPACKAndCheck(t, hpack, r, []string{ 623 | ":status", "307", 624 | "cache-control", "private", 625 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 626 | "location", "https://www.example.com", 627 | }, []string{ 628 | ":status", "307", 629 | "location", "https://www.example.com", 630 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 631 | "cache-control", "private", 632 | }, 222) 633 | 634 | r = []byte{ 635 | 0x88, 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 636 | 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 637 | 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 638 | 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 639 | 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 640 | 0x47, 0x4d, 0x54, 0xc0, 0x5a, 0x04, 641 | 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38, 642 | 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, 643 | 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 644 | 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 645 | 0x45, 0x4f, 0x50, 0x49, 0x55, 0x41, 646 | 0x58, 0x51, 0x57, 0x45, 0x4f, 0x49, 647 | 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 648 | 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x33, 649 | 0x36, 0x30, 0x30, 0x3b, 0x20, 0x76, 650 | 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 651 | 0x3d, 0x31, 652 | } 653 | 654 | writeHPACKAndCheck(t, hpack, r, []string{ 655 | ":status", "200", 656 | "cache-control", "private", 657 | "date", "Mon, 21 Oct 2013 20:13:22 GMT", 658 | "location", "https://www.example.com", 659 | "content-encoding", "gzip", 660 | "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", 661 | }, []string{ 662 | "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", 663 | "content-encoding", "gzip", 664 | "date", "Mon, 21 Oct 2013 20:13:22 GMT", 665 | }, 215) 666 | 667 | ReleaseHPACK(hpack) 668 | } 669 | 670 | func TestHPACKWriteResponseWithHuffman(t *testing.T) { // WithHuffman 671 | r := []byte{ 672 | 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 673 | 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, 674 | 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 675 | 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 676 | 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 677 | 0x82, 0xa6, 0x2d, 0x1b, 0xff, 0x6e, 678 | 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 679 | 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 680 | 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3, 681 | } 682 | 683 | hpack := AcquireHPACK() 684 | hpack.SetMaxTableSize(256) 685 | writeHPACKAndCheck(t, hpack, r, []string{ 686 | ":status", "302", 687 | "cache-control", "private", 688 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 689 | "location", "https://www.example.com", 690 | }, []string{ 691 | "location", "https://www.example.com", 692 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 693 | "cache-control", "private", 694 | ":status", "302", 695 | }, 222) 696 | 697 | r = []byte{0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf} 698 | writeHPACKAndCheck(t, hpack, r, []string{ 699 | ":status", "307", 700 | "cache-control", "private", 701 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 702 | "location", "https://www.example.com", 703 | }, []string{ 704 | ":status", "307", 705 | "location", "https://www.example.com", 706 | "date", "Mon, 21 Oct 2013 20:13:21 GMT", 707 | "cache-control", "private", 708 | }, 222) 709 | 710 | r = []byte{ 711 | 0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 712 | 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 713 | 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 714 | 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 715 | 0x1b, 0xff, 0xc0, 0x5a, 0x83, 0x9b, 716 | 0xd9, 0xab, 0x77, 0xad, 0x94, 0xe7, 717 | 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 718 | 0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, 719 | 0x39, 0x60, 0xd5, 0xaf, 0x27, 0x08, 720 | 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, 721 | 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 722 | 0x31, 0x60, 0x65, 0xc0, 0x03, 0xed, 723 | 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07, 724 | } 725 | writeHPACKAndCheck(t, hpack, r, []string{ 726 | ":status", "200", 727 | "cache-control", "private", 728 | "date", "Mon, 21 Oct 2013 20:13:22 GMT", 729 | "location", "https://www.example.com", 730 | "content-encoding", "gzip", 731 | "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", 732 | }, []string{ 733 | "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", 734 | "content-encoding", "gzip", 735 | "date", "Mon, 21 Oct 2013 20:13:22 GMT", 736 | }, 215) 737 | 738 | ReleaseHPACK(hpack) 739 | } 740 | 741 | func hexComparison(b, r []byte) (s string) { 742 | s += "\n" 743 | for i := range b { 744 | s += fmt.Sprintf("%x", b[i]) + " " 745 | } 746 | s += "\n" 747 | for i := range r { 748 | s += fmt.Sprintf("%x", r[i]) + " " 749 | } 750 | return 751 | } 752 | -------------------------------------------------------------------------------- /http2.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | ) 7 | 8 | // Byteorder must be big endian 9 | // Values are unsigned unless otherwise indicated 10 | 11 | var ( 12 | // http://httpwg.org/specs/rfc7540.html#ConnectionHeader 13 | http2Preface = []byte("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") 14 | prefaceLen = len(http2Preface) 15 | ) 16 | 17 | // ReadPreface reads the connection initialisation preface. 18 | func ReadPreface(br io.Reader) bool { 19 | b := make([]byte, prefaceLen) 20 | 21 | n, err := br.Read(b[:prefaceLen]) 22 | if err == nil && n == prefaceLen { 23 | if bytes.Equal(b, http2Preface) { 24 | return true 25 | } 26 | } 27 | 28 | return false 29 | } 30 | 31 | // WritePreface writes HTTP/2 preface to the wr. 32 | func WritePreface(wr io.Writer) error { 33 | _, err := wr.Write(http2Preface) 34 | return err 35 | } 36 | -------------------------------------------------------------------------------- /http2utils/utils.go: -------------------------------------------------------------------------------- 1 | package http2utils 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "fmt" 7 | "log" 8 | "path/filepath" 9 | "reflect" 10 | "runtime" 11 | "testing" 12 | "text/tabwriter" 13 | "unsafe" 14 | 15 | "github.com/valyala/fastrand" 16 | ) 17 | 18 | func Uint24ToBytes(b []byte, n uint32) { 19 | _ = b[2] // bound checking 20 | b[0] = byte(n >> 16) 21 | b[1] = byte(n >> 8) 22 | b[2] = byte(n) 23 | } 24 | 25 | func BytesToUint24(b []byte) uint32 { 26 | _ = b[2] // bound checking 27 | return uint32(b[0])<<16 | 28 | uint32(b[1])<<8 | 29 | uint32(b[2]) 30 | } 31 | 32 | func AppendUint32Bytes(dst []byte, n uint32) []byte { 33 | return append(dst, byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) 34 | } 35 | 36 | func Uint32ToBytes(b []byte, n uint32) { 37 | _ = b[3] // bound checking 38 | b[0] = byte(n >> 24) 39 | b[1] = byte(n >> 16) 40 | b[2] = byte(n >> 8) 41 | b[3] = byte(n) 42 | } 43 | 44 | func BytesToUint32(b []byte) uint32 { 45 | _ = b[3] // bound checking 46 | n := uint32(b[0])<<24 | 47 | uint32(b[1])<<16 | 48 | uint32(b[2])<<8 | 49 | uint32(b[3]) 50 | return n 51 | } 52 | 53 | func EqualsFold(a, b []byte) bool { 54 | n := len(a) 55 | if n != len(b) { 56 | return false 57 | } 58 | for i := 0; i < n; i++ { 59 | if a[i]|0x20 != b[i]|0x20 { 60 | return false 61 | } 62 | } 63 | return true 64 | } 65 | 66 | func Resize(b []byte, neededLen int) []byte { 67 | b = b[:cap(b)] 68 | 69 | if n := neededLen - len(b); n > 0 { 70 | b = append(b, make([]byte, n)...) 71 | } 72 | 73 | return b[:neededLen] 74 | } 75 | 76 | // CutPadding cuts the padding if the frame has FlagPadded 77 | // from the payload and returns the new payload as byte slice. 78 | func CutPadding(payload []byte, length int) ([]byte, error) { 79 | pad := int(payload[0]) 80 | 81 | if len(payload) < length-pad-1 || length-pad < 1 { 82 | return nil, fmt.Errorf("out of range: %d < %d", len(payload), length-pad-1) 83 | } 84 | payload = payload[1 : length-pad] 85 | 86 | return payload, nil 87 | } 88 | 89 | func AddPadding(b []byte) []byte { 90 | n := int(fastrand.Uint32n(256-9)) + 9 91 | nn := len(b) 92 | 93 | b = Resize(b, nn+n) 94 | b = append(b[:1], b...) 95 | 96 | b[0] = uint8(n) 97 | 98 | _, _ = rand.Read(b[nn+1 : nn+n]) 99 | 100 | return b 101 | } 102 | 103 | func FastBytesToString(b []byte) string { 104 | return *(*string)(unsafe.Pointer(&b)) 105 | } 106 | 107 | // AssertEqual checks if values are equal. 108 | func AssertEqual(tb testing.TB, expected, actual interface{}, description ...string) { 109 | tb.Helper() 110 | 111 | if reflect.DeepEqual(expected, actual) { 112 | return 113 | } 114 | 115 | aType := "" 116 | bType := "" 117 | 118 | if expected != nil { 119 | aType = reflect.TypeOf(expected).String() 120 | } 121 | if actual != nil { 122 | bType = reflect.TypeOf(actual).String() 123 | } 124 | 125 | testName := "AssertEqual" 126 | if tb != nil { 127 | testName = tb.Name() 128 | } 129 | 130 | _, file, line, _ := runtime.Caller(1) 131 | 132 | var buf bytes.Buffer 133 | w := tabwriter.NewWriter(&buf, 0, 0, 5, ' ', 0) 134 | fmt.Fprintf(w, "\nTest:\t%s", testName) 135 | fmt.Fprintf(w, "\nTrace:\t%s:%d", filepath.Base(file), line) 136 | if len(description) > 0 { 137 | fmt.Fprintf(w, "\nDescription:\t%s", description[0]) 138 | } 139 | fmt.Fprintf(w, "\nExpect:\t%v\t(%s)", expected, aType) 140 | fmt.Fprintf(w, "\nResult:\t%v\t(%s)", actual, bType) 141 | 142 | var result string 143 | if err := w.Flush(); err != nil { 144 | result = err.Error() 145 | } else { 146 | result = buf.String() 147 | } 148 | 149 | if tb != nil { 150 | tb.Fatal(result) 151 | } else { 152 | log.Fatal(result) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /huffman.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | // HuffmanEncode encodes src into dst using Huffman algorithm. 4 | // 5 | // src and dst must not point to the same address. 6 | func HuffmanEncode(dst, src []byte) []byte { 7 | var code uint64 8 | var length uint8 9 | // TODO: I'd be nice to implement this lookup using SSE. 10 | // But since you need to use the Golang's ASM I don't know 11 | // how much will that take. 12 | for _, b := range src { 13 | n := huffmanCodeLen[b] 14 | c := uint64(huffmanCodes[b]) 15 | length += n 16 | code = code<= 8 { 18 | length -= 8 19 | dst = append(dst, byte(code>>length)) 20 | } 21 | } 22 | 23 | if length > 0 { 24 | n := 8 - length 25 | code = code<= 8 { 44 | root = root.sub[byte(cum>>(bits-8))] 45 | if root == nil { 46 | return dst 47 | } 48 | 49 | if root.sub != nil { 50 | bits -= 8 51 | } else { 52 | bits -= root.codeLen 53 | dst = append(dst, root.sym) 54 | root = rootHuffmanNode 55 | } 56 | } 57 | } 58 | 59 | for bits > 0 { 60 | root = root.sub[byte(cum<<(8-bits))] 61 | if root == nil { 62 | break 63 | } 64 | 65 | if root.sub != nil || root.codeLen > bits { 66 | break 67 | } 68 | 69 | dst = append(dst, root.sym) 70 | bits -= root.codeLen 71 | root = rootHuffmanNode 72 | } 73 | 74 | return dst 75 | } 76 | 77 | var rootHuffmanNode = func() *huffmanNode { 78 | node := &huffmanNode{ 79 | sub: make([]*huffmanNode, 256), 80 | } 81 | 82 | for i, code := range huffmanCodes { 83 | node.add(byte(i), code, huffmanCodeLen[i]) 84 | } 85 | 86 | return node 87 | }() 88 | 89 | type huffmanNode struct { 90 | sub []*huffmanNode 91 | codeLen uint8 92 | sym byte 93 | } 94 | 95 | func (node *huffmanNode) add(sym byte, code uint32, length uint8) { 96 | for length > 8 { 97 | length -= 8 98 | i := uint8(code >> length) 99 | if node.sub[i] == nil { 100 | node.sub[i] = &huffmanNode{ 101 | sub: make([]*huffmanNode, 256), 102 | } 103 | } 104 | 105 | node = node.sub[i] 106 | } 107 | 108 | n := 8 - length 109 | start, end := int(uint8(code<%d", len(b), len(bb)) 19 | } 20 | for i := 0; i < len(b); i++ { 21 | if b[i] != bb[i] { 22 | return fmt.Errorf("different bytes at %d: %v<>%v", i, b[i], bb[i]) 23 | } 24 | } 25 | return nil 26 | } 27 | 28 | func decodeHuffman(t *testing.T, b, bb, toCompare []byte) { 29 | t.Helper() 30 | 31 | b = HuffmanDecode(b[:0], bb) 32 | if err := compareBytes(b, toCompare); err != nil { 33 | t.Fatal(err) 34 | } 35 | } 36 | 37 | func TestHuffmanDecodeHuge(t *testing.T) { 38 | t.Helper() 39 | 40 | decodeHuffman(t, nil, encodedBytes, decodedBytes) 41 | } 42 | 43 | func TestHuffmanDecode(t *testing.T) { 44 | t.Helper() 45 | 46 | decodeHuffman(t, nil, littleEncodedBytes, littleDecodedBytes) 47 | } 48 | 49 | /* This algorithm cannot reuse bytes 50 | func TestHuffmanDecodeReusing(t *testing.T) { 51 | b := makeCopy(encodedBytes) 52 | decodeHuffman(t, b, b, decodedBytes) 53 | } 54 | 55 | func TestHuffmanDecodeReusingLittle(t *testing.T) { 56 | b := makeCopy(littleEncodedBytes) 57 | decodeHuffman(t, b, b, littleDecodedBytes) 58 | } 59 | */ 60 | 61 | func encodeHuffman(t *testing.T, b, bb, toCompare []byte) { 62 | t.Helper() 63 | 64 | b = HuffmanEncode(b[:0], bb) 65 | if err := compareBytes(b, toCompare); err != nil { 66 | t.Fatal(err) 67 | } 68 | } 69 | 70 | func TestHuffmanEncodeHuge(t *testing.T) { 71 | encodeHuffman(t, nil, decodedBytes, encodedBytes) 72 | } 73 | 74 | func TestHuffmanEncode(t *testing.T) { 75 | encodeHuffman(t, nil, littleDecodedBytes, littleEncodedBytes) 76 | } 77 | 78 | /* This algorithm cannot reuse bytes 79 | func TestHuffmanEncodeReusing(t *testing.T) { 80 | b := makeCopy(decodedBytes) 81 | encodeHuffman(t, b, b, encodedBytes) 82 | } 83 | 84 | func TestHuffmanEncodeReusingLittle(t *testing.T) { 85 | b := makeCopy(littleDecodedBytes) 86 | encodeHuffman(t, b, b, littleEncodedBytes) 87 | } 88 | */ 89 | -------------------------------------------------------------------------------- /ping.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "encoding/binary" 5 | "time" 6 | ) 7 | 8 | const FramePing FrameType = 0x6 9 | 10 | var _ Frame = &Ping{} 11 | 12 | // Ping https://tools.ietf.org/html/rfc7540#section-6.7 13 | type Ping struct { 14 | ack bool 15 | data [8]byte 16 | } 17 | 18 | func (p *Ping) IsAck() bool { 19 | return p.ack 20 | } 21 | 22 | func (p *Ping) SetAck(ack bool) { 23 | p.ack = ack 24 | } 25 | 26 | func (p *Ping) Type() FrameType { 27 | return FramePing 28 | } 29 | 30 | func (p *Ping) Reset() { 31 | p.ack = false 32 | } 33 | 34 | func (p *Ping) CopyTo(other *Ping) { 35 | p.ack = other.ack 36 | } 37 | 38 | func (p *Ping) Write(b []byte) (n int, err error) { 39 | copy(p.data[:], b) 40 | return 41 | } 42 | 43 | func (p *Ping) SetData(b []byte) { 44 | copy(p.data[:], b) 45 | } 46 | 47 | func (p *Ping) SetCurrentTime() { 48 | ts := time.Now().UnixNano() 49 | binary.BigEndian.PutUint64(p.data[:], uint64(ts)) 50 | } 51 | 52 | func (p *Ping) DataAsTime() time.Time { 53 | return time.Unix( 54 | 0, int64(binary.BigEndian.Uint64(p.data[:])), 55 | ) 56 | } 57 | 58 | func (p *Ping) Deserialize(frh *FrameHeader) error { 59 | p.ack = frh.Flags().Has(FlagAck) 60 | if len(frh.payload) != 8 { 61 | return NewGoAwayError(FrameSizeError, "invalid ping payload") 62 | } 63 | p.SetData(frh.payload) 64 | return nil 65 | } 66 | 67 | func (p *Ping) Data() []byte { 68 | return p.data[:] 69 | } 70 | 71 | func (p *Ping) Serialize(fr *FrameHeader) { 72 | if p.ack { 73 | fr.SetFlags(fr.Flags().Add(FlagAck)) 74 | } 75 | 76 | fr.setPayload(p.data[:]) 77 | } 78 | -------------------------------------------------------------------------------- /priority.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "github.com/dgrr/http2/http2utils" 5 | ) 6 | 7 | const FramePriority FrameType = 0x2 8 | 9 | var _ Frame = &Priority{} 10 | 11 | // Priority represents the Priority frame. 12 | // 13 | // https://tools.ietf.org/html/rfc7540#section-6.3 14 | type Priority struct { 15 | stream uint32 16 | weight byte 17 | } 18 | 19 | func (pry *Priority) Type() FrameType { 20 | return FramePriority 21 | } 22 | 23 | // Reset resets priority fields. 24 | func (pry *Priority) Reset() { 25 | pry.stream = 0 26 | pry.weight = 0 27 | } 28 | 29 | func (pry *Priority) CopyTo(p *Priority) { 30 | p.stream = pry.stream 31 | p.weight = pry.weight 32 | } 33 | 34 | // Stream returns the Priority frame stream. 35 | func (pry *Priority) Stream() uint32 { 36 | return pry.stream 37 | } 38 | 39 | // SetStream sets the Priority frame stream. 40 | func (pry *Priority) SetStream(stream uint32) { 41 | pry.stream = stream & (1<<31 - 1) 42 | } 43 | 44 | // Weight returns the Priority frame weight. 45 | func (pry *Priority) Weight() byte { 46 | return pry.weight 47 | } 48 | 49 | // SetWeight sets the Priority frame weight. 50 | func (pry *Priority) SetWeight(w byte) { 51 | pry.weight = w 52 | } 53 | 54 | func (pry *Priority) Deserialize(fr *FrameHeader) (err error) { 55 | if len(fr.payload) < 5 { 56 | err = ErrMissingBytes 57 | } else { 58 | pry.stream = http2utils.BytesToUint32(fr.payload) & (1<<31 - 1) 59 | pry.weight = fr.payload[4] 60 | } 61 | 62 | return 63 | } 64 | 65 | func (pry *Priority) Serialize(fr *FrameHeader) { 66 | fr.payload = http2utils.AppendUint32Bytes(fr.payload[:0], pry.stream) 67 | fr.payload = append(fr.payload, pry.weight) 68 | } 69 | -------------------------------------------------------------------------------- /pushpromise.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "github.com/dgrr/http2/http2utils" 5 | ) 6 | 7 | const FramePushPromise FrameType = 0x5 8 | 9 | var _ Frame = &PushPromise{} 10 | 11 | // PushPromise https://tools.ietf.org/html/rfc7540#section-6.6 12 | type PushPromise struct { 13 | pad bool 14 | ended bool 15 | stream uint32 16 | header []byte // header block fragment 17 | } 18 | 19 | func (pp *PushPromise) Type() FrameType { 20 | return FramePushPromise 21 | } 22 | 23 | func (pp *PushPromise) Reset() { 24 | pp.pad = false 25 | pp.ended = false 26 | pp.stream = 0 27 | pp.header = pp.header[:0] 28 | } 29 | 30 | func (pp *PushPromise) SetHeader(h []byte) { 31 | pp.header = append(pp.header[:0], h...) 32 | } 33 | 34 | func (pp *PushPromise) Write(b []byte) (int, error) { 35 | n := len(b) 36 | pp.header = append(pp.header, b...) 37 | return n, nil 38 | } 39 | 40 | func (pp *PushPromise) Deserialize(fr *FrameHeader) error { 41 | payload := fr.payload 42 | 43 | if fr.Flags().Has(FlagPadded) { 44 | var err error 45 | payload, err = http2utils.CutPadding(payload, fr.Len()) 46 | if err != nil { 47 | return err 48 | } 49 | } 50 | 51 | if len(fr.payload) < 4 { 52 | return ErrMissingBytes 53 | } 54 | 55 | pp.stream = http2utils.BytesToUint32(payload) & (1<<31 - 1) 56 | pp.header = append(pp.header, payload[4:]...) 57 | pp.ended = fr.Flags().Has(FlagEndHeaders) 58 | 59 | return nil 60 | } 61 | 62 | func (pp *PushPromise) Serialize(fr *FrameHeader) { 63 | fr.payload = fr.payload[:0] 64 | 65 | // if pp.pad { 66 | // fr.Flags().Add(FlagPadded) 67 | // // TODO: Write padding flag 68 | // } 69 | 70 | fr.payload = append(fr.payload, pp.header...) 71 | // TODO: write padding 72 | } 73 | -------------------------------------------------------------------------------- /rststream.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "github.com/dgrr/http2/http2utils" 5 | ) 6 | 7 | const FrameResetStream FrameType = 0x3 8 | 9 | var _ Frame = &RstStream{} 10 | 11 | // RstStream https://tools.ietf.org/html/rfc7540#section-6.4 12 | type RstStream struct { 13 | code ErrorCode 14 | } 15 | 16 | func (rst *RstStream) Type() FrameType { 17 | return FrameResetStream 18 | } 19 | 20 | func (rst *RstStream) Code() ErrorCode { 21 | return rst.code 22 | } 23 | 24 | func (rst *RstStream) SetCode(code ErrorCode) { 25 | rst.code = code 26 | } 27 | 28 | func (rst *RstStream) Reset() { 29 | rst.code = 0 30 | } 31 | 32 | func (rst *RstStream) CopyTo(r *RstStream) { 33 | r.code = rst.code 34 | } 35 | 36 | func (rst *RstStream) Error() error { 37 | return rst.code 38 | } 39 | 40 | func (rst *RstStream) Deserialize(fr *FrameHeader) error { 41 | if len(fr.payload) < 4 { 42 | return ErrMissingBytes 43 | } 44 | 45 | rst.code = ErrorCode(http2utils.BytesToUint32(fr.payload)) 46 | 47 | return nil 48 | } 49 | 50 | func (rst *RstStream) Serialize(fr *FrameHeader) { 51 | fr.payload = http2utils.AppendUint32Bytes(fr.payload[:0], uint32(rst.code)) 52 | fr.length = 4 53 | } 54 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "net" 7 | "time" 8 | 9 | "github.com/valyala/fasthttp" 10 | ) 11 | 12 | // ServerConfig ... 13 | type ServerConfig struct { 14 | // PingInterval is the interval at which the server will send a 15 | // ping message to a client. 16 | // 17 | // To disable pings set the PingInterval to a negative value. 18 | PingInterval time.Duration 19 | 20 | // ... 21 | MaxConcurrentStreams int 22 | 23 | // Debug is a flag that will allow the library to print debugging information. 24 | Debug bool 25 | } 26 | 27 | func (sc *ServerConfig) defaults() { 28 | if sc.PingInterval == 0 { 29 | sc.PingInterval = time.Second * 10 30 | } 31 | 32 | if sc.MaxConcurrentStreams <= 0 { 33 | sc.MaxConcurrentStreams = 1024 34 | } 35 | } 36 | 37 | // Server defines an HTTP/2 entity that can handle HTTP/2 connections. 38 | type Server struct { 39 | s *fasthttp.Server 40 | 41 | cnf ServerConfig 42 | } 43 | 44 | // ServeConn starts serving a net.Conn as HTTP/2. 45 | // 46 | // This function will fail if the connection does not support the HTTP/2 protocol. 47 | func (s *Server) ServeConn(c net.Conn) error { 48 | defer func() { _ = c.Close() }() 49 | 50 | if !ReadPreface(c) { 51 | return errors.New("wrong preface") 52 | } 53 | 54 | sc := &serverConn{ 55 | c: c, 56 | h: s.s.Handler, 57 | br: bufio.NewReader(c), 58 | bw: bufio.NewWriterSize(c, 1<<14*10), 59 | lastID: 0, 60 | writer: make(chan *FrameHeader, 128), 61 | reader: make(chan *FrameHeader, 128), 62 | maxRequestTime: s.s.ReadTimeout, 63 | pingInterval: s.cnf.PingInterval, 64 | logger: s.s.Logger, 65 | debug: s.cnf.Debug, 66 | } 67 | 68 | if sc.logger == nil { 69 | sc.logger = logger 70 | } 71 | 72 | sc.enc.Reset() 73 | sc.dec.Reset() 74 | 75 | sc.maxWindow = 1 << 22 76 | sc.currentWindow = sc.maxWindow 77 | 78 | sc.st.Reset() 79 | sc.st.SetMaxWindowSize(uint32(sc.maxWindow)) 80 | sc.st.SetMaxConcurrentStreams(uint32(s.cnf.MaxConcurrentStreams)) 81 | 82 | if err := sc.Handshake(); err != nil { 83 | return err 84 | } 85 | 86 | return sc.Serve() 87 | } 88 | -------------------------------------------------------------------------------- /settings.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | const FrameSettings FrameType = 0x4 4 | 5 | var _ Frame = &Settings{} 6 | 7 | // default Settings parameters. 8 | const ( 9 | defaultHeaderTableSize uint32 = 4096 10 | defaultConcurrentStreams uint32 = 100 11 | defaultWindowSize uint32 = 1<<16 - 1 12 | defaultDataFrameSize uint32 = 1 << 14 13 | 14 | maxFrameSize = 1<<24 - 1 15 | ) 16 | 17 | // FrameSettings string values (https://httpwg.org/specs/rfc7540.html#SettingValues) 18 | const ( 19 | HeaderTableSize uint16 = 0x1 20 | EnablePush uint16 = 0x2 21 | MaxConcurrentStreams uint16 = 0x3 22 | MaxWindowSize uint16 = 0x4 23 | MaxFrameSize uint16 = 0x5 24 | MaxHeaderListSize uint16 = 0x6 25 | ) 26 | 27 | // Settings is the options to establish between endpoints 28 | // when starting the connection. 29 | // 30 | // These options have been humanized. 31 | type Settings struct { 32 | ack bool 33 | rawSettings []byte 34 | tableSize uint32 35 | enablePush bool 36 | maxStreams uint32 37 | windowSize uint32 38 | frameSize uint32 39 | headerSize uint32 40 | } 41 | 42 | func (st *Settings) Type() FrameType { 43 | return FrameSettings 44 | } 45 | 46 | // Reset resets settings to default values. 47 | func (st *Settings) Reset() { 48 | // default settings 49 | st.tableSize = defaultHeaderTableSize 50 | st.maxStreams = defaultConcurrentStreams 51 | st.windowSize = defaultWindowSize 52 | st.frameSize = defaultDataFrameSize 53 | st.enablePush = false 54 | st.headerSize = 0 55 | st.rawSettings = st.rawSettings[:0] 56 | st.ack = false 57 | } 58 | 59 | // CopyTo copies st fields to st2. 60 | func (st *Settings) CopyTo(st2 *Settings) { 61 | st2.ack = st.ack 62 | st2.rawSettings = append(st2.rawSettings[:0], st.rawSettings...) 63 | st2.tableSize = st.tableSize 64 | st2.enablePush = st.enablePush 65 | st2.maxStreams = st.maxStreams 66 | st2.windowSize = st.windowSize 67 | st2.frameSize = st.frameSize 68 | st2.headerSize = st.headerSize 69 | } 70 | 71 | // SetHeaderTableSize sets the maximum size of the header 72 | // compression table used to decode header blocks. 73 | // 74 | // Default value is 4096. 75 | func (st *Settings) SetHeaderTableSize(size uint32) { 76 | st.tableSize = size 77 | } 78 | 79 | // HeaderTableSize returns the maximum size of the header 80 | // compression table used to decode header blocks. 81 | // 82 | // Default value is 4096. 83 | func (st *Settings) HeaderTableSize() uint32 { 84 | return st.tableSize 85 | } 86 | 87 | // SetPush allows to set the PushPromise settings. 88 | // 89 | // If value is true the Push Promise will be enabled. 90 | // if not the Push Promise will be disabled. 91 | func (st *Settings) SetPush(value bool) { 92 | st.enablePush = value 93 | } 94 | 95 | func (st *Settings) Push() bool { 96 | return st.enablePush 97 | } 98 | 99 | // SetMaxConcurrentStreams sets the maximum number of 100 | // concurrent Streams that the sender will allow. 101 | // 102 | // Default value is 100. This value does not have max limit. 103 | func (st *Settings) SetMaxConcurrentStreams(streams uint32) { 104 | st.maxStreams = streams 105 | } 106 | 107 | // MaxConcurrentStreams returns the maximum number of 108 | // concurrent Streams that the sender will allow. 109 | // 110 | // Default value is 100. This value does not have max limit. 111 | func (st *Settings) MaxConcurrentStreams() uint32 { 112 | return st.maxStreams 113 | } 114 | 115 | // SetMaxWindowSize sets the sender's initial window size 116 | // for Stream-level flow control. 117 | // 118 | // Default value is 1 << 16 - 1 119 | // Maximum value is 1 << 31 - 1. 120 | func (st *Settings) SetMaxWindowSize(size uint32) { 121 | st.windowSize = size 122 | } 123 | 124 | // MaxWindowSize returns the sender's initial window size 125 | // for Stream-level flow control. 126 | // 127 | // Default value is 1 << 16 - 1 128 | // Maximum value is 1 << 31 - 1. 129 | func (st *Settings) MaxWindowSize() uint32 { 130 | return st.windowSize 131 | } 132 | 133 | // SetMaxFrameSize sets the size of the largest frame 134 | // Payload that the sender is willing to receive. 135 | // 136 | // Default value is 1 << 14 137 | // Maximum value is 1 << 24 - 1. 138 | func (st *Settings) SetMaxFrameSize(size uint32) { 139 | st.frameSize = size 140 | } 141 | 142 | // MaxFrameSize returns the size of the largest frame 143 | // Payload that the sender is willing to receive. 144 | // 145 | // Default value is 1 << 14 146 | // Maximum value is 1 << 24 - 1. 147 | func (st *Settings) MaxFrameSize() uint32 { 148 | return st.frameSize 149 | } 150 | 151 | // SetMaxHeaderListSize sets maximum size of header list uncompressed. 152 | // 153 | // If this value is 0 indicates that there are no limit. 154 | func (st *Settings) SetMaxHeaderListSize(size uint32) { 155 | st.headerSize = size 156 | } 157 | 158 | // MaxHeaderListSize returns maximum size of header list uncompressed. 159 | // 160 | // If this value is 0 indicates that there are no limit. 161 | func (st *Settings) MaxHeaderListSize() uint32 { 162 | return st.headerSize 163 | } 164 | 165 | // Read reads from d and decodes the read values into st. 166 | func (st *Settings) Read(d []byte) error { 167 | var b []byte 168 | var key uint16 169 | var value uint32 170 | 171 | last, i, n := 0, 6, len(d) 172 | 173 | for i <= n { 174 | b = d[last:i] 175 | key = uint16(b[0])<<8 | uint16(b[1]) 176 | value = uint32(b[2])<<24 | uint32(b[3])<<16 | uint32(b[4])<<8 | uint32(b[5]) 177 | 178 | switch key { 179 | case HeaderTableSize: 180 | st.tableSize = value 181 | case EnablePush: 182 | if value != 0 && value != 1 { 183 | return NewGoAwayError(ProtocolError, "wrong value for SETTINGS_ENABLE_PUSH") 184 | } 185 | st.enablePush = value != 0 186 | case MaxConcurrentStreams: 187 | st.maxStreams = value 188 | case MaxWindowSize: 189 | if value > 1<<31-1 { 190 | return NewGoAwayError(FlowControlError, "SETTINGS_INITIAL_WINDOW_SIZE above maximum") 191 | } 192 | st.windowSize = value 193 | case MaxFrameSize: 194 | if value < 1<<14 || value > 1<<24-1 { 195 | return NewGoAwayError(ProtocolError, "wrong value for SETTINGS_MAX_FRAME_SIZE") 196 | } 197 | st.frameSize = value 198 | case MaxHeaderListSize: 199 | st.headerSize = value 200 | } 201 | 202 | last = i 203 | i += 6 204 | } 205 | return nil 206 | } 207 | 208 | // Encode encodes settings to be sent through the wire. 209 | func (st *Settings) Encode() { 210 | st.rawSettings = st.rawSettings[:0] 211 | 212 | if st.tableSize != 0 { 213 | st.rawSettings = append(st.rawSettings, 214 | byte(HeaderTableSize>>8), byte(HeaderTableSize), 215 | byte(st.tableSize>>24), byte(st.tableSize>>16), 216 | byte(st.tableSize>>8), byte(st.tableSize), 217 | ) 218 | } 219 | 220 | if st.enablePush { 221 | st.rawSettings = append(st.rawSettings, 222 | byte(EnablePush>>8), byte(EnablePush), 223 | 0, 0, 0, 1, 224 | ) 225 | } 226 | 227 | if st.maxStreams != 0 { 228 | st.rawSettings = append(st.rawSettings, 229 | byte(MaxConcurrentStreams>>8), byte(MaxConcurrentStreams), 230 | byte(st.maxStreams>>24), byte(st.maxStreams>>16), 231 | byte(st.maxStreams>>8), byte(st.maxStreams), 232 | ) 233 | } 234 | 235 | if st.windowSize != 0 { 236 | st.rawSettings = append(st.rawSettings, 237 | byte(MaxWindowSize>>8), byte(MaxWindowSize), 238 | byte(st.windowSize>>24), byte(st.windowSize>>16), 239 | byte(st.windowSize>>8), byte(st.windowSize), 240 | ) 241 | } 242 | 243 | if st.frameSize != 0 { 244 | st.rawSettings = append(st.rawSettings, 245 | byte(MaxFrameSize>>8), byte(MaxFrameSize), 246 | byte(st.frameSize>>24), byte(st.frameSize>>16), 247 | byte(st.frameSize>>8), byte(st.frameSize), 248 | ) 249 | } 250 | 251 | if st.headerSize != 0 { 252 | st.rawSettings = append(st.rawSettings, 253 | byte(MaxHeaderListSize>>8), byte(MaxHeaderListSize), 254 | byte(st.headerSize>>24), byte(st.headerSize>>16), 255 | byte(st.headerSize>>8), byte(st.headerSize), 256 | ) 257 | } 258 | } 259 | 260 | // IsAck returns true if settings has FlagAck set. 261 | func (st *Settings) IsAck() bool { 262 | return st.ack 263 | } 264 | 265 | // SetAck sets FlagAck when WriteTo is called. 266 | func (st *Settings) SetAck(ack bool) { 267 | st.ack = ack 268 | } 269 | 270 | func (st *Settings) Deserialize(fr *FrameHeader) error { 271 | if len(fr.payload)%6 != 0 { 272 | return NewGoAwayError(FrameSizeError, "wrong payload for settings") 273 | } 274 | 275 | st.ack = fr.Flags().Has(FlagAck) 276 | 277 | if st.IsAck() && len(fr.payload) > 0 { 278 | return NewGoAwayError(FrameSizeError, "settings with ack and payload") 279 | } 280 | 281 | return st.Read(fr.payload) 282 | } 283 | 284 | func (st *Settings) Serialize(fr *FrameHeader) { 285 | if st.ack { // ACK should be empty 286 | fr.SetFlags( 287 | fr.Flags().Add(FlagAck)) 288 | 289 | fr.payload = fr.payload[:0] 290 | } else { 291 | st.Encode() 292 | 293 | fr.setPayload(st.rawSettings) 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /stream.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/valyala/fasthttp" 8 | ) 9 | 10 | type StreamState int8 11 | 12 | const ( 13 | StreamStateIdle StreamState = iota 14 | StreamStateReserved 15 | StreamStateOpen 16 | StreamStateHalfClosed 17 | StreamStateClosed 18 | ) 19 | 20 | func (ss StreamState) String() string { 21 | switch ss { 22 | case StreamStateIdle: 23 | return "Idle" 24 | case StreamStateReserved: 25 | return "Reserved" 26 | case StreamStateOpen: 27 | return "Open" 28 | case StreamStateHalfClosed: 29 | return "HalfClosed" 30 | case StreamStateClosed: 31 | return "Closed" 32 | } 33 | 34 | return "IDK" 35 | } 36 | 37 | type Stream struct { 38 | id uint32 39 | window int64 40 | state StreamState 41 | ctx *fasthttp.RequestCtx 42 | scheme []byte 43 | previousHeaderBytes []byte 44 | 45 | // original type 46 | origType FrameType 47 | startedAt time.Time 48 | headersFinished bool 49 | } 50 | 51 | var streamPool = sync.Pool{ 52 | New: func() interface{} { 53 | return &Stream{} 54 | }, 55 | } 56 | 57 | func NewStream(id uint32, win int32) *Stream { 58 | strm := streamPool.Get().(*Stream) 59 | strm.id = id 60 | strm.window = int64(win) 61 | strm.state = StreamStateIdle 62 | strm.headersFinished = false 63 | strm.startedAt = time.Time{} 64 | strm.previousHeaderBytes = strm.previousHeaderBytes[:0] 65 | strm.ctx = nil 66 | strm.scheme = []byte("https") 67 | 68 | return strm 69 | } 70 | 71 | func (s *Stream) ID() uint32 { 72 | return s.id 73 | } 74 | 75 | func (s *Stream) SetID(id uint32) { 76 | s.id = id 77 | } 78 | 79 | func (s *Stream) State() StreamState { 80 | return s.state 81 | } 82 | 83 | func (s *Stream) SetState(state StreamState) { 84 | s.state = state 85 | } 86 | 87 | func (s *Stream) Window() int32 { 88 | return int32(s.window) 89 | } 90 | 91 | func (s *Stream) SetWindow(win int32) { 92 | s.window = int64(win) 93 | } 94 | 95 | func (s *Stream) IncrWindow(win int32) { 96 | s.window += int64(win) 97 | } 98 | 99 | func (s *Stream) Ctx() *fasthttp.RequestCtx { 100 | return s.ctx 101 | } 102 | 103 | func (s *Stream) SetData(ctx *fasthttp.RequestCtx) { 104 | s.ctx = ctx 105 | } 106 | -------------------------------------------------------------------------------- /streams.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | type Streams []*Stream 4 | 5 | func (strms *Streams) Search(id uint32) *Stream { 6 | for _, strm := range *strms { 7 | if strm.ID() == id { 8 | return strm 9 | } 10 | } 11 | return nil 12 | } 13 | 14 | func (strms *Streams) Del(id uint32) { 15 | if len(*strms) == 1 && (*strms)[0].ID() == id { 16 | *strms = (*strms)[:0] 17 | return 18 | } 19 | 20 | for i, strm := range *strms { 21 | if strm.ID() == id { 22 | *strms = append((*strms)[:i], (*strms)[i+1:]...) 23 | return 24 | } 25 | } 26 | } 27 | 28 | func (strms Streams) GetFirstOf(frameType FrameType) *Stream { 29 | for _, strm := range strms { 30 | if strm.origType == frameType { 31 | return strm 32 | } 33 | } 34 | return nil 35 | } 36 | 37 | func (strms Streams) getPrevious(frameType FrameType) *Stream { 38 | cnt := 0 39 | for i := len(strms) - 1; i >= 0; i-- { 40 | if strms[i].origType == frameType { 41 | if cnt != 0 { 42 | return strms[i] 43 | } 44 | cnt++ 45 | } 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /strings.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | var ( 4 | StringPath = []byte(":path") 5 | StringStatus = []byte(":status") 6 | StringAuthority = []byte(":authority") 7 | StringScheme = []byte(":scheme") 8 | StringMethod = []byte(":method") 9 | StringServer = []byte("server") 10 | StringContentLength = []byte("content-length") 11 | StringContentType = []byte("content-type") 12 | StringUserAgent = []byte("user-agent") 13 | StringGzip = []byte("gzip") 14 | StringGET = []byte("GET") 15 | StringHEAD = []byte("HEAD") 16 | StringPOST = []byte("POST") 17 | StringHTTP2 = []byte("HTTP/2") 18 | ) 19 | 20 | func ToLower(b []byte) []byte { 21 | for i := range b { 22 | b[i] |= 32 23 | } 24 | 25 | return b 26 | } 27 | 28 | const ( 29 | // H2TLSProto is the string used in ALPN-TLS negotiation. 30 | H2TLSProto = "h2" 31 | // H2Clean is the string used in HTTP headers by the client to upgrade the connection. 32 | H2Clean = "h2c" 33 | ) 34 | -------------------------------------------------------------------------------- /windowUpdate.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "github.com/dgrr/http2/http2utils" 5 | ) 6 | 7 | const FrameWindowUpdate FrameType = 0x8 8 | 9 | var _ Frame = &WindowUpdate{} 10 | 11 | // WindowUpdate https://tools.ietf.org/html/rfc7540#section-6.9 12 | type WindowUpdate struct { 13 | increment int 14 | } 15 | 16 | func (wu *WindowUpdate) Type() FrameType { 17 | return FrameWindowUpdate 18 | } 19 | 20 | func (wu *WindowUpdate) Reset() { 21 | wu.increment = 0 22 | } 23 | 24 | func (wu *WindowUpdate) CopyTo(w *WindowUpdate) { 25 | w.increment = wu.increment 26 | } 27 | 28 | func (wu *WindowUpdate) Increment() int { 29 | return wu.increment 30 | } 31 | 32 | func (wu *WindowUpdate) SetIncrement(increment int) { 33 | wu.increment = increment 34 | } 35 | 36 | func (wu *WindowUpdate) Deserialize(fr *FrameHeader) error { 37 | if len(fr.payload) < 4 { 38 | wu.increment = 0 39 | return ErrMissingBytes 40 | } 41 | 42 | wu.increment = int(http2utils.BytesToUint32(fr.payload) & (1<<31 - 1)) 43 | 44 | return nil 45 | } 46 | 47 | func (wu *WindowUpdate) Serialize(fr *FrameHeader) { 48 | fr.payload = http2utils.AppendUint32Bytes( 49 | fr.payload[:0], uint32(wu.increment)) 50 | fr.length = 4 51 | } 52 | --------------------------------------------------------------------------------