├── .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 |
--------------------------------------------------------------------------------