├── .gitignore ├── .idea └── .gitignore ├── .tool-versions ├── CONTRIBUTING.md ├── README.md ├── alpn_test.go ├── cgi ├── child.go ├── child_test.go ├── host.go ├── host_test.go ├── integration_test.go ├── plan9_test.go ├── posix_test.go └── testdata │ └── test.cgi ├── client.go ├── client_test.go ├── clientserver_test.go ├── clone.go ├── cookie.go ├── cookie_test.go ├── cookiejar ├── dummy_publicsuffix_test.go ├── example_test.go ├── jar.go ├── jar_test.go ├── punycode.go └── punycode_test.go ├── doc.go ├── example_client_test.go ├── example_filesystem_test.go ├── example_handle_test.go ├── example_test.go ├── export_test.go ├── fcgi ├── child.go ├── fcgi.go └── fcgi_test.go ├── filetransport.go ├── filetransport_test.go ├── fs.go ├── fs_test.go ├── go.mod ├── go.sum ├── h2_bundle.go ├── header.go ├── header_test.go ├── http.go ├── http2 ├── .gitignore ├── README ├── ciphers.go ├── ciphers_test.go ├── client_conn_pool.go ├── databuffer.go ├── databuffer_test.go ├── errors.go ├── errors_test.go ├── fhttp_test.go ├── flow.go ├── flow_test.go ├── frame.go ├── frame_test.go ├── go111.go ├── gotrack.go ├── gotrack_test.go ├── h2c │ ├── h2c.go │ └── h2c_test.go ├── h2demo │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── README │ ├── deployment-prod.yaml │ ├── go.mod │ ├── go.sum │ ├── h2demo.go │ ├── rootCA.key │ ├── rootCA.pem │ ├── rootCA.srl │ ├── server.crt │ ├── server.key │ ├── service.yaml │ └── tmpl.go ├── h2i │ ├── README.md │ └── h2i.go ├── header_order_test.go ├── headermap.go ├── hpack │ ├── encode.go │ ├── encode_test.go │ ├── hpack.go │ ├── hpack_test.go │ ├── huffman.go │ ├── tables.go │ └── tables_test.go ├── http2.go ├── http2_test.go ├── not_go111.go ├── pipe.go ├── pipe_test.go ├── push_consume.go ├── push_consume_test.go ├── server.go ├── server_push_test.go ├── server_test.go ├── testdata │ └── draft-ietf-httpbis-http2.xml ├── transport.go ├── transport_test.go ├── write.go ├── writesched.go ├── writesched_priority.go ├── writesched_priority_test.go ├── writesched_random.go ├── writesched_random_test.go ├── writesched_test.go └── z_spec_test.go ├── http_test.go ├── httptest ├── example_test.go ├── httptest.go ├── httptest_test.go ├── recorder.go ├── recorder_test.go ├── server.go └── server_test.go ├── httptrace ├── example_test.go ├── trace.go └── trace_test.go ├── httputil ├── dump.go ├── dump_test.go ├── example_test.go ├── httputil.go ├── persist.go ├── reverseproxy.go └── reverseproxy_test.go ├── internal ├── cfg │ └── cfg.go ├── chunked.go ├── chunked_test.go ├── nettrace │ └── nettrace.go ├── profile │ ├── encode.go │ ├── filter.go │ ├── legacy_profile.go │ ├── merge.go │ ├── profile.go │ ├── profile_test.go │ ├── proto.go │ ├── proto_test.go │ └── prune.go ├── race │ ├── doc.go │ ├── norace.go │ └── race.go ├── testcert.go └── testenv │ ├── testenv.go │ ├── testenv_cgo.go │ ├── testenv_notwin.go │ └── testenv_windows.go ├── jar.go ├── main_test.go ├── method.go ├── omithttp2.go ├── pprof ├── pprof.go └── pprof_test.go ├── proxy_test.go ├── range_test.go ├── readrequest_test.go ├── request.go ├── request_test.go ├── requestwrite_test.go ├── response.go ├── response_test.go ├── responsewrite_test.go ├── roundtrip.go ├── roundtrip_js.go ├── serve_test.go ├── server.go ├── server_test.go ├── sniff.go ├── sniff_test.go ├── socks_bundle.go ├── status.go ├── testdata ├── file ├── index.html └── style.css ├── transfer.go ├── transfer_test.go ├── transport.go ├── transport_internal_test.go ├── transport_test.go └── triv.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | .idea -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | golang 1.22.3 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | This serves as a todo list for the library, anybody can contribute. For a branch to be merged, first open a pull request. For issues not on this page, open an issue first to see if the issue is within scope of this library. 3 | 4 | ## gzip, deflate, br 5 | The `gzip, deflate, br` encoding should be implemented as an opt-in way for the client to use this instead of the standard gzip encoding. A helpful example can be found [here](https://play.golang.org/p/80HukFxfs4). 6 | 7 | ## Writing better tests 8 | * Test fingerprinting bypass with this [site](https://privacycheck.sec.lrz.de/passive/fp_h2/fp_http2.html#fpDemoHttp2) 9 | * Test for ENABLE_PUSH implementation, from [here](https://go-review.googlesource.com/c/net/+/181497/) 10 | * Test for all features mentioned in [README](README.md), such as header order and pheader order with httptrace 11 | 12 | ## Create a server fingerprint implementation 13 | Will be able to use library in order to check fingerprint of incoming requests, and see what http2 setting is missing or wrong 14 | 15 | ## Fix push handler errors 16 | The push handler has errors with reading responses, specifically, it will sometimes fail because it read to EOF, or `Client closed connection before receiving entire response`. Someone with knowledge of how pushed requests are sent and read should fix this issue, or see if something was copied wrong when implementing [the pull request](https://go-review.googlesource.com/c/net/+/181497/) 17 | 18 | ## Merging upstream 19 | When changes are made by the golang team on the [http]() or [http2](https://pkg.go.dev/golang.org/x/net/http2) library as a release branch, 20 | ``` 21 | git remote add -f golang git@github.com:golang/go.git 22 | git checkout -b golang-upstream golang/master 23 | git subtree split -P src/crypto/tls/ -b golang-tls-upstream 24 | git checkout master 25 | git merge --no-commit golang--upstream 26 | ``` 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fhttp 2 | 3 | 11 | 12 | ## Features 13 | 14 | ### Ordered Headers 15 | 16 | The package allows for both pseudo header order and normal header order. Most of the code is taken from [this Pull Request](https://go-review.googlesource.com/c/go/+/105755/). 17 | 18 | **Note on HTTP/1.1 header order** 19 | Although the header key is capitalized, the header order slice must be in lowercase. 20 | 21 | ```go 22 | req.Header = http.Header{ 23 | "X-NewRelic-ID": {"12345"}, 24 | "x-api-key": {"ABCDE12345"}, 25 | "MESH-Commerce-Channel": {"android-app-phone"}, 26 | "mesh-version": {"cart=4"}, 27 | "X-Request-Auth": {"hawkHeader"}, 28 | "X-acf-sensor-data": {"3456"}, 29 | "Content-Type": {"application/json; charset=UTF-8"}, 30 | "Accept": {"application/json"}, 31 | "Transfer-Encoding": {"chunked"}, 32 | "Host": {"example.com"}, 33 | "Connection": {"Keep-Alive"}, 34 | "Accept-Encoding": {"gzip"}, 35 | HeaderOrderKey: { 36 | "x-newrelic-id", 37 | "x-api-key", 38 | "mesh-commerce-channel", 39 | "mesh-version", 40 | "user-agent", 41 | "x-request-auth", 42 | "x-acf-sensor-data", 43 | "transfer-encoding", 44 | "content-type", 45 | "accept", 46 | "host", 47 | "connection", 48 | "accept-encoding", 49 | }, 50 | PHeaderOrderKey: { 51 | ":method", 52 | ":path", 53 | ":authority", 54 | ":scheme", 55 | }, 56 | } 57 | ``` 58 | 59 | ### Connection settings 60 | 61 | fhhtp has Chrome-like connection settings, as shown below: 62 | 63 | ```text 64 | SETTINGS_HEADER_TABLE_SIZE = 65536 (2^16) 65 | SETTINGS_ENABLE_PUSH = 1 66 | SETTINGS_MAX_CONCURRENT_STREAMS = 1000 67 | SETTINGS_INITIAL_WINDOW_SIZE = 6291456 68 | SETTINGS_MAX_FRAME_SIZE = 16384 (2^14) 69 | SETTINGS_MAX_HEADER_LIST_SIZE = 262144 (2^18) 70 | ``` 71 | 72 | The default net/http settings, on the other hand, are the following: 73 | 74 | ```text 75 | SETTINGS_HEADER_TABLE_SIZE = 4096 76 | SETTINGS_ENABLE_PUSH = 0 77 | SETTINGS_MAX_CONCURRENT_STREAMS = unlimited 78 | SETTINGS_INITIAL_WINDOW_SIZE = 4194304 79 | SETTINGS_MAX_FRAME_SIZE = 16384 80 | SETTINGS_MAX_HEADER_LIST_SIZE = 10485760 81 | ``` 82 | 83 | The ENABLE_PUSH implementation was merged from [this Pull Request](https://go-review.googlesource.com/c/net/+/181497/). 84 | 85 | ### gzip, deflate, and br encoding 86 | 87 | `gzip`, `deflate`, and `br` encoding are all supported by the package. 88 | 89 | ### Pseudo header order 90 | 91 | fhttp supports pseudo header order for http2, helping mitigate fingerprinting. You can read more about how it works [here](https://www.akamai.com/uk/en/multimedia/documents/white-paper/passive-fingerprinting-of-http2-clients-white-paper.pdf). 92 | 93 | ### Backward compatible with net/http 94 | 95 | Although this library is an extension of `net/http`, it is also meant to be backward compatible. Replacing 96 | 97 | ```go 98 | import ( 99 | "net/http" 100 | ) 101 | ``` 102 | 103 | with 104 | 105 | ```go 106 | import ( 107 | http "github.com/bogdanfinn/fhttp" 108 | ) 109 | ``` 110 | 111 | SHOULD not break anything. 112 | 113 | ## Credits 114 | 115 | Special thanks to the following people for helping me with this project. 116 | 117 | - [cc](https://github.com/x04/) for guiding me when I first started this project and inspiring me with [cclient](https://github.com/x04/cclient) 118 | 119 | - [umasi](https://github.com/umasii) for being good rubber ducky and giving me tips for http2 headers 120 | -------------------------------------------------------------------------------- /alpn_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http_test 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "crypto/x509" 11 | "fmt" 12 | "io" 13 | "strings" 14 | "testing" 15 | 16 | tls "github.com/bogdanfinn/utls" 17 | 18 | . "github.com/bogdanfinn/fhttp" 19 | "github.com/bogdanfinn/fhttp/httptest" 20 | ) 21 | 22 | func TestNextProtoUpgrade(t *testing.T) { 23 | setParallel(t) 24 | defer afterTest(t) 25 | ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) { 26 | fmt.Fprintf(w, "path=%s,proto=", r.URL.Path) 27 | if r.TLS != nil { 28 | w.Write([]byte(r.TLS.NegotiatedProtocol)) 29 | } 30 | if r.RemoteAddr == "" { 31 | t.Error("request with no RemoteAddr") 32 | } 33 | if r.Body == nil { 34 | t.Errorf("request with nil Body") 35 | } 36 | })) 37 | ts.TLS = &tls.Config{ 38 | NextProtos: []string{"unhandled-proto", "tls-0.9"}, 39 | } 40 | ts.Config.TLSNextProto = map[string]func(*Server, *tls.Conn, Handler){ 41 | "tls-0.9": handleTLSProtocol09, 42 | } 43 | ts.StartTLS() 44 | defer ts.Close() 45 | 46 | // Normal request, without NPN. 47 | { 48 | c := ts.Client() 49 | res, err := c.Get(ts.URL) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | body, err := io.ReadAll(res.Body) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | if want := "path=/,proto="; string(body) != want { 58 | t.Errorf("plain request = %q; want %q", body, want) 59 | } 60 | } 61 | 62 | // Request to an advertised but unhandled NPN protocol. 63 | // Server will hang up. 64 | { 65 | certPool := x509.NewCertPool() 66 | certPool.AddCert(ts.Certificate()) 67 | tr := &Transport{ 68 | TLSClientConfig: &tls.Config{ 69 | RootCAs: certPool, 70 | NextProtos: []string{"unhandled-proto"}, 71 | }, 72 | } 73 | defer tr.CloseIdleConnections() 74 | c := &Client{ 75 | Transport: tr, 76 | } 77 | res, err := c.Get(ts.URL) 78 | if err == nil { 79 | defer res.Body.Close() 80 | var buf bytes.Buffer 81 | res.Write(&buf) 82 | t.Errorf("expected error on unhandled-proto request; got: %s", buf.Bytes()) 83 | } 84 | } 85 | 86 | // Request using the "tls-0.9" protocol, which we register here. 87 | // It is HTTP/0.9 over TLS. 88 | { 89 | c := ts.Client() 90 | tlsConfig := c.Transport.(*Transport).TLSClientConfig 91 | tlsConfig.NextProtos = []string{"tls-0.9"} 92 | conn, err := tls.Dial("tcp", ts.Listener.Addr().String(), tlsConfig) 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | conn.Write([]byte("GET /foo\n")) 97 | body, err := io.ReadAll(conn) 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | if want := "path=/foo,proto=tls-0.9"; string(body) != want { 102 | t.Errorf("plain request = %q; want %q", body, want) 103 | } 104 | } 105 | } 106 | 107 | // handleTLSProtocol09 implements the HTTP/0.9 protocol over TLS, for the 108 | // TestNextProtoUpgrade test. 109 | func handleTLSProtocol09(srv *Server, conn *tls.Conn, h Handler) { 110 | br := bufio.NewReader(conn) 111 | line, err := br.ReadString('\n') 112 | if err != nil { 113 | return 114 | } 115 | line = strings.TrimSpace(line) 116 | path := strings.TrimPrefix(line, "GET ") 117 | if path == line { 118 | return 119 | } 120 | req, _ := NewRequest("GET", path, nil) 121 | req.Proto = "HTTP/0.9" 122 | req.ProtoMajor = 0 123 | req.ProtoMinor = 9 124 | rw := &http09Writer{conn, make(Header)} 125 | h.ServeHTTP(rw, req) 126 | } 127 | 128 | type http09Writer struct { 129 | io.Writer 130 | h Header 131 | } 132 | 133 | func (w http09Writer) Header() Header { return w.h } 134 | func (w http09Writer) WriteHeader(int) {} // no headers 135 | -------------------------------------------------------------------------------- /cgi/child.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file implements CGI from the perspective of a child 6 | // process. 7 | 8 | package cgi 9 | 10 | import ( 11 | "bufio" 12 | "errors" 13 | "fmt" 14 | "io" 15 | "net" 16 | "net/url" 17 | "os" 18 | "strconv" 19 | "strings" 20 | 21 | tls "github.com/bogdanfinn/utls" 22 | 23 | http "github.com/bogdanfinn/fhttp" 24 | ) 25 | 26 | // Request returns the HTTP request as represented in the current 27 | // environment. This assumes the current program is being run 28 | // by a web server in a CGI environment. 29 | // The returned Request's Body is populated, if applicable. 30 | func Request() (*http.Request, error) { 31 | r, err := RequestFromMap(envMap(os.Environ())) 32 | if err != nil { 33 | return nil, err 34 | } 35 | if r.ContentLength > 0 { 36 | r.Body = io.NopCloser(io.LimitReader(os.Stdin, r.ContentLength)) 37 | } 38 | return r, nil 39 | } 40 | 41 | func envMap(env []string) map[string]string { 42 | m := make(map[string]string) 43 | for _, kv := range env { 44 | if idx := strings.Index(kv, "="); idx != -1 { 45 | m[kv[:idx]] = kv[idx+1:] 46 | } 47 | } 48 | return m 49 | } 50 | 51 | // RequestFromMap creates an http.Request from CGI variables. 52 | // The returned Request's Body field is not populated. 53 | func RequestFromMap(params map[string]string) (*http.Request, error) { 54 | r := new(http.Request) 55 | r.Method = params["REQUEST_METHOD"] 56 | if r.Method == "" { 57 | return nil, errors.New("cgi: no REQUEST_METHOD in environment") 58 | } 59 | 60 | r.Proto = params["SERVER_PROTOCOL"] 61 | var ok bool 62 | r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto) 63 | if !ok { 64 | return nil, errors.New("cgi: invalid SERVER_PROTOCOL version") 65 | } 66 | 67 | r.Close = true 68 | r.Trailer = http.Header{} 69 | r.Header = http.Header{} 70 | 71 | r.Host = params["HTTP_HOST"] 72 | 73 | if lenstr := params["CONTENT_LENGTH"]; lenstr != "" { 74 | clen, err := strconv.ParseInt(lenstr, 10, 64) 75 | if err != nil { 76 | return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + lenstr) 77 | } 78 | r.ContentLength = clen 79 | } 80 | 81 | if ct := params["CONTENT_TYPE"]; ct != "" { 82 | r.Header.Set("Content-Type", ct) 83 | } 84 | 85 | // Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers 86 | for k, v := range params { 87 | if !strings.HasPrefix(k, "HTTP_") || k == "HTTP_HOST" { 88 | continue 89 | } 90 | r.Header.Add(strings.ReplaceAll(k[5:], "_", "-"), v) 91 | } 92 | 93 | uriStr := params["REQUEST_URI"] 94 | if uriStr == "" { 95 | // Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING. 96 | uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"] 97 | s := params["QUERY_STRING"] 98 | if s != "" { 99 | uriStr += "?" + s 100 | } 101 | } 102 | 103 | // There's apparently a de-facto standard for this. 104 | // https://web.archive.org/web/20170105004655/http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636 105 | if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" { 106 | r.TLS = &tls.ConnectionState{HandshakeComplete: true} 107 | } 108 | 109 | if r.Host != "" { 110 | // Hostname is provided, so we can reasonably construct a URL. 111 | rawurl := r.Host + uriStr 112 | if r.TLS == nil { 113 | rawurl = "http://" + rawurl 114 | } else { 115 | rawurl = "https://" + rawurl 116 | } 117 | url, err := url.Parse(rawurl) 118 | if err != nil { 119 | return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl) 120 | } 121 | r.URL = url 122 | } 123 | // Fallback logic if we don't have a Host header or the URL 124 | // failed to parse 125 | if r.URL == nil { 126 | url, err := url.Parse(uriStr) 127 | if err != nil { 128 | return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr) 129 | } 130 | r.URL = url 131 | } 132 | 133 | // Request.RemoteAddr has its port set by Go's standard http 134 | // server, so we do here too. 135 | remotePort, _ := strconv.Atoi(params["REMOTE_PORT"]) // zero if unset or invalid 136 | r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], strconv.Itoa(remotePort)) 137 | 138 | return r, nil 139 | } 140 | 141 | // Serve executes the provided Handler on the currently active CGI 142 | // request, if any. If there's no current CGI environment 143 | // an error is returned. The provided handler may be nil to use 144 | // http.DefaultServeMux. 145 | func Serve(handler http.Handler) error { 146 | req, err := Request() 147 | if err != nil { 148 | return err 149 | } 150 | if req.Body == nil { 151 | req.Body = http.NoBody 152 | } 153 | if handler == nil { 154 | handler = http.DefaultServeMux 155 | } 156 | rw := &response{ 157 | req: req, 158 | header: make(http.Header), 159 | bufw: bufio.NewWriter(os.Stdout), 160 | } 161 | handler.ServeHTTP(rw, req) 162 | rw.Write(nil) // make sure a response is sent 163 | if err = rw.bufw.Flush(); err != nil { 164 | return err 165 | } 166 | return nil 167 | } 168 | 169 | type response struct { 170 | req *http.Request 171 | header http.Header 172 | code int 173 | wroteHeader bool 174 | wroteCGIHeader bool 175 | bufw *bufio.Writer 176 | } 177 | 178 | func (r *response) Flush() { 179 | r.bufw.Flush() 180 | } 181 | 182 | func (r *response) Header() http.Header { 183 | return r.header 184 | } 185 | 186 | func (r *response) Write(p []byte) (n int, err error) { 187 | if !r.wroteHeader { 188 | r.WriteHeader(http.StatusOK) 189 | } 190 | if !r.wroteCGIHeader { 191 | r.writeCGIHeader(p) 192 | } 193 | return r.bufw.Write(p) 194 | } 195 | 196 | func (r *response) WriteHeader(code int) { 197 | if r.wroteHeader { 198 | // Note: explicitly using Stderr, as Stdout is our HTTP output. 199 | fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL) 200 | return 201 | } 202 | r.wroteHeader = true 203 | r.code = code 204 | } 205 | 206 | // writeCGIHeader finalizes the header sent to the client and writes it to the output. 207 | // p is not written by writeHeader, but is the first chunk of the body 208 | // that will be written. It is sniffed for a Content-Type if none is 209 | // set explicitly. 210 | func (r *response) writeCGIHeader(p []byte) { 211 | if r.wroteCGIHeader { 212 | return 213 | } 214 | r.wroteCGIHeader = true 215 | fmt.Fprintf(r.bufw, "Status: %d %s\r\n", r.code, http.StatusText(r.code)) 216 | if _, hasType := r.header["Content-Type"]; !hasType { 217 | r.header.Set("Content-Type", http.DetectContentType(p)) 218 | } 219 | r.header.Write(r.bufw) 220 | r.bufw.WriteString("\r\n") 221 | r.bufw.Flush() 222 | } 223 | -------------------------------------------------------------------------------- /cgi/child_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Tests for CGI (the child process perspective) 6 | 7 | package cgi 8 | 9 | import ( 10 | "bufio" 11 | "bytes" 12 | "net/http/httptest" 13 | "strings" 14 | "testing" 15 | 16 | http "github.com/bogdanfinn/fhttp" 17 | ) 18 | 19 | func TestRequest(t *testing.T) { 20 | env := map[string]string{ 21 | "SERVER_PROTOCOL": "HTTP/1.1", 22 | "REQUEST_METHOD": "GET", 23 | "HTTP_HOST": "example.com", 24 | "HTTP_REFERER": "elsewhere", 25 | "HTTP_USER_AGENT": "goclient", 26 | "HTTP_FOO_BAR": "baz", 27 | "REQUEST_URI": "/path?a=b", 28 | "CONTENT_LENGTH": "123", 29 | "CONTENT_TYPE": "text/xml", 30 | "REMOTE_ADDR": "5.6.7.8", 31 | "REMOTE_PORT": "54321", 32 | } 33 | req, err := RequestFromMap(env) 34 | if err != nil { 35 | t.Fatalf("RequestFromMap: %v", err) 36 | } 37 | if g, e := req.UserAgent(), "goclient"; e != g { 38 | t.Errorf("expected UserAgent %q; got %q", e, g) 39 | } 40 | if g, e := req.Method, "GET"; e != g { 41 | t.Errorf("expected Method %q; got %q", e, g) 42 | } 43 | if g, e := req.Header.Get("Content-Type"), "text/xml"; e != g { 44 | t.Errorf("expected Content-Type %q; got %q", e, g) 45 | } 46 | if g, e := req.ContentLength, int64(123); e != g { 47 | t.Errorf("expected ContentLength %d; got %d", e, g) 48 | } 49 | if g, e := req.Referer(), "elsewhere"; e != g { 50 | t.Errorf("expected Referer %q; got %q", e, g) 51 | } 52 | if req.Header == nil { 53 | t.Fatalf("unexpected nil Header") 54 | } 55 | if g, e := req.Header.Get("Foo-Bar"), "baz"; e != g { 56 | t.Errorf("expected Foo-Bar %q; got %q", e, g) 57 | } 58 | if g, e := req.URL.String(), "http://example.com/path?a=b"; e != g { 59 | t.Errorf("expected URL %q; got %q", e, g) 60 | } 61 | if g, e := req.FormValue("a"), "b"; e != g { 62 | t.Errorf("expected FormValue(a) %q; got %q", e, g) 63 | } 64 | if req.Trailer == nil { 65 | t.Errorf("unexpected nil Trailer") 66 | } 67 | if req.TLS != nil { 68 | t.Errorf("expected nil TLS") 69 | } 70 | if e, g := "5.6.7.8:54321", req.RemoteAddr; e != g { 71 | t.Errorf("RemoteAddr: got %q; want %q", g, e) 72 | } 73 | } 74 | 75 | func TestRequestWithTLS(t *testing.T) { 76 | env := map[string]string{ 77 | "SERVER_PROTOCOL": "HTTP/1.1", 78 | "REQUEST_METHOD": "GET", 79 | "HTTP_HOST": "example.com", 80 | "HTTP_REFERER": "elsewhere", 81 | "REQUEST_URI": "/path?a=b", 82 | "CONTENT_TYPE": "text/xml", 83 | "HTTPS": "1", 84 | "REMOTE_ADDR": "5.6.7.8", 85 | } 86 | req, err := RequestFromMap(env) 87 | if err != nil { 88 | t.Fatalf("RequestFromMap: %v", err) 89 | } 90 | if g, e := req.URL.String(), "https://example.com/path?a=b"; e != g { 91 | t.Errorf("expected URL %q; got %q", e, g) 92 | } 93 | if req.TLS == nil { 94 | t.Errorf("expected non-nil TLS") 95 | } 96 | } 97 | 98 | func TestRequestWithoutHost(t *testing.T) { 99 | env := map[string]string{ 100 | "SERVER_PROTOCOL": "HTTP/1.1", 101 | "HTTP_HOST": "", 102 | "REQUEST_METHOD": "GET", 103 | "REQUEST_URI": "/path?a=b", 104 | "CONTENT_LENGTH": "123", 105 | } 106 | req, err := RequestFromMap(env) 107 | if err != nil { 108 | t.Fatalf("RequestFromMap: %v", err) 109 | } 110 | if req.URL == nil { 111 | t.Fatalf("unexpected nil URL") 112 | } 113 | if g, e := req.URL.String(), "/path?a=b"; e != g { 114 | t.Errorf("URL = %q; want %q", g, e) 115 | } 116 | } 117 | 118 | func TestRequestWithoutRequestURI(t *testing.T) { 119 | env := map[string]string{ 120 | "SERVER_PROTOCOL": "HTTP/1.1", 121 | "HTTP_HOST": "example.com", 122 | "REQUEST_METHOD": "GET", 123 | "SCRIPT_NAME": "/dir/scriptname", 124 | "PATH_INFO": "/p1/p2", 125 | "QUERY_STRING": "a=1&b=2", 126 | "CONTENT_LENGTH": "123", 127 | } 128 | req, err := RequestFromMap(env) 129 | if err != nil { 130 | t.Fatalf("RequestFromMap: %v", err) 131 | } 132 | if req.URL == nil { 133 | t.Fatalf("unexpected nil URL") 134 | } 135 | if g, e := req.URL.String(), "http://example.com/dir/scriptname/p1/p2?a=1&b=2"; e != g { 136 | t.Errorf("URL = %q; want %q", g, e) 137 | } 138 | } 139 | 140 | func TestRequestWithoutRemotePort(t *testing.T) { 141 | env := map[string]string{ 142 | "SERVER_PROTOCOL": "HTTP/1.1", 143 | "HTTP_HOST": "example.com", 144 | "REQUEST_METHOD": "GET", 145 | "REQUEST_URI": "/path?a=b", 146 | "CONTENT_LENGTH": "123", 147 | "REMOTE_ADDR": "5.6.7.8", 148 | } 149 | req, err := RequestFromMap(env) 150 | if err != nil { 151 | t.Fatalf("RequestFromMap: %v", err) 152 | } 153 | if e, g := "5.6.7.8:0", req.RemoteAddr; e != g { 154 | t.Errorf("RemoteAddr: got %q; want %q", g, e) 155 | } 156 | } 157 | 158 | func TestResponse(t *testing.T) { 159 | var tests = []struct { 160 | name string 161 | body string 162 | wantCT string 163 | }{ 164 | { 165 | name: "no body", 166 | wantCT: "text/plain; charset=utf-8", 167 | }, 168 | { 169 | name: "html", 170 | body: "test pageThis is a body", 171 | wantCT: "text/html; charset=utf-8", 172 | }, 173 | { 174 | name: "text", 175 | body: strings.Repeat("gopher", 86), 176 | wantCT: "text/plain; charset=utf-8", 177 | }, 178 | { 179 | name: "jpg", 180 | body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024), 181 | wantCT: "image/jpeg", 182 | }, 183 | } 184 | for _, tt := range tests { 185 | t.Run(tt.name, func(t *testing.T) { 186 | var buf bytes.Buffer 187 | resp := response{ 188 | req: httptest.NewRequest("GET", "/", nil), 189 | header: http.Header{}, 190 | bufw: bufio.NewWriter(&buf), 191 | } 192 | n, err := resp.Write([]byte(tt.body)) 193 | if err != nil { 194 | t.Errorf("Write: unexpected %v", err) 195 | } 196 | if want := len(tt.body); n != want { 197 | t.Errorf("reported short Write: got %v want %v", n, want) 198 | } 199 | resp.writeCGIHeader(nil) 200 | resp.Flush() 201 | if got := resp.Header().Get("Content-Type"); got != tt.wantCT { 202 | t.Errorf("wrong content-type: got %q, want %q", got, tt.wantCT) 203 | } 204 | if !bytes.HasSuffix(buf.Bytes(), []byte(tt.body)) { 205 | t.Errorf("body was not correctly written") 206 | } 207 | }) 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /cgi/plan9_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build plan9 6 | 7 | package cgi 8 | 9 | import ( 10 | "os" 11 | "strconv" 12 | ) 13 | 14 | func isProcessRunning(pid int) bool { 15 | _, err := os.Stat("/proc/" + strconv.Itoa(pid)) 16 | return err == nil 17 | } 18 | -------------------------------------------------------------------------------- /cgi/posix_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !plan9 6 | 7 | package cgi 8 | 9 | import ( 10 | "os" 11 | "syscall" 12 | ) 13 | 14 | func isProcessRunning(pid int) bool { 15 | p, err := os.FindProcess(pid) 16 | if err != nil { 17 | return false 18 | } 19 | return p.Signal(syscall.Signal(0)) == nil 20 | } 21 | -------------------------------------------------------------------------------- /cgi/testdata/test.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # Copyright 2011 The Go Authors. All rights reserved. 3 | # Use of this source code is governed by a BSD-style 4 | # license that can be found in the LICENSE file. 5 | # 6 | # Test script run as a child process under cgi_test.go 7 | 8 | use strict; 9 | use Cwd; 10 | 11 | binmode STDOUT; 12 | 13 | my $q = MiniCGI->new; 14 | my $params = $q->Vars; 15 | 16 | if ($params->{"loc"}) { 17 | print "Location: $params->{loc}\r\n\r\n"; 18 | exit(0); 19 | } 20 | 21 | print "Content-Type: text/html\r\n"; 22 | print "X-CGI-Pid: $$\r\n"; 23 | print "X-Test-Header: X-Test-Value\r\n"; 24 | print "\r\n"; 25 | 26 | if ($params->{"writestderr"}) { 27 | print STDERR "Hello, stderr!\n"; 28 | } 29 | 30 | if ($params->{"bigresponse"}) { 31 | # 17 MB, for OS X: golang.org/issue/4958 32 | for (1..(17 * 1024)) { 33 | print "A" x 1024, "\r\n"; 34 | } 35 | exit 0; 36 | } 37 | 38 | print "test=Hello CGI\r\n"; 39 | 40 | foreach my $k (sort keys %$params) { 41 | print "param-$k=$params->{$k}\r\n"; 42 | } 43 | 44 | foreach my $k (sort keys %ENV) { 45 | my $clean_env = $ENV{$k}; 46 | $clean_env =~ s/[\n\r]//g; 47 | print "env-$k=$clean_env\r\n"; 48 | } 49 | 50 | # NOTE: msys perl returns /c/go/src/... not C:\go\.... 51 | my $dir = getcwd(); 52 | if ($^O eq 'MSWin32' || $^O eq 'msys' || $^O eq 'cygwin') { 53 | if ($dir =~ /^.:/) { 54 | $dir =~ s!/!\\!g; 55 | } else { 56 | my $cmd = $ENV{'COMSPEC'} || 'c:\\windows\\system32\\cmd.exe'; 57 | $cmd =~ s!\\!/!g; 58 | $dir = `$cmd /c cd`; 59 | chomp $dir; 60 | } 61 | } 62 | print "cwd=$dir\r\n"; 63 | 64 | # A minimal version of CGI.pm, for people without the perl-modules 65 | # package installed. (CGI.pm used to be part of the Perl core, but 66 | # some distros now bundle perl-base and perl-modules separately...) 67 | package MiniCGI; 68 | 69 | sub new { 70 | my $class = shift; 71 | return bless {}, $class; 72 | } 73 | 74 | sub Vars { 75 | my $self = shift; 76 | my $pairs; 77 | if ($ENV{CONTENT_LENGTH}) { 78 | $pairs = do { local $/; }; 79 | } else { 80 | $pairs = $ENV{QUERY_STRING}; 81 | } 82 | my $vars = {}; 83 | foreach my $kv (split(/&/, $pairs)) { 84 | my ($k, $v) = split(/=/, $kv, 2); 85 | $vars->{_urldecode($k)} = _urldecode($v); 86 | } 87 | return $vars; 88 | } 89 | 90 | sub _urldecode { 91 | my $v = shift; 92 | $v =~ tr/+/ /; 93 | $v =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; 94 | return $v; 95 | } 96 | -------------------------------------------------------------------------------- /clone.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "mime/multipart" 9 | "net/textproto" 10 | "net/url" 11 | ) 12 | 13 | func cloneURLValues(v url.Values) url.Values { 14 | if v == nil { 15 | return nil 16 | } 17 | // http.Header and url.Values have the same representation, so temporarily 18 | // treat it like http.Header, which does have a clone: 19 | return url.Values(Header(v).Clone()) 20 | } 21 | 22 | func cloneURL(u *url.URL) *url.URL { 23 | if u == nil { 24 | return nil 25 | } 26 | u2 := new(url.URL) 27 | *u2 = *u 28 | if u.User != nil { 29 | u2.User = new(url.Userinfo) 30 | *u2.User = *u.User 31 | } 32 | return u2 33 | } 34 | 35 | func cloneMultipartForm(f *multipart.Form) *multipart.Form { 36 | if f == nil { 37 | return nil 38 | } 39 | f2 := &multipart.Form{ 40 | Value: (map[string][]string)(Header(f.Value).Clone()), 41 | } 42 | if f.File != nil { 43 | m := make(map[string][]*multipart.FileHeader) 44 | for k, vv := range f.File { 45 | vv2 := make([]*multipart.FileHeader, len(vv)) 46 | for i, v := range vv { 47 | vv2[i] = cloneMultipartFileHeader(v) 48 | } 49 | m[k] = vv2 50 | } 51 | f2.File = m 52 | } 53 | return f2 54 | } 55 | 56 | func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader { 57 | if fh == nil { 58 | return nil 59 | } 60 | fh2 := new(multipart.FileHeader) 61 | *fh2 = *fh 62 | fh2.Header = textproto.MIMEHeader(Header(fh.Header).Clone()) 63 | return fh2 64 | } 65 | 66 | // cloneOrMakeHeader invokes Header.Clone but if the 67 | // result is nil, it'll instead make and return a non-nil Header. 68 | func cloneOrMakeHeader(hdr Header) Header { 69 | clone := hdr.Clone() 70 | if clone == nil { 71 | clone = make(Header) 72 | } 73 | return clone 74 | } 75 | -------------------------------------------------------------------------------- /cookiejar/dummy_publicsuffix_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package cookiejar_test 6 | 7 | import "github.com/bogdanfinn/fhttp/cookiejar" 8 | 9 | type dummypsl struct { 10 | List cookiejar.PublicSuffixList 11 | } 12 | 13 | func (dummypsl) PublicSuffix(domain string) string { 14 | return domain 15 | } 16 | 17 | func (dummypsl) String() string { 18 | return "dummy" 19 | } 20 | 21 | var publicsuffix = dummypsl{} 22 | -------------------------------------------------------------------------------- /cookiejar/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package cookiejar_test 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "net/url" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | "github.com/bogdanfinn/fhttp/cookiejar" 14 | "github.com/bogdanfinn/fhttp/httptest" 15 | ) 16 | 17 | func ExampleNew() { 18 | // Start a server to give us cookies. 19 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 | if cookie, err := r.Cookie("Flavor"); err != nil { 21 | http.SetCookie(w, &http.Cookie{Name: "Flavor", Value: "Chocolate Chip"}) 22 | } else { 23 | cookie.Value = "Oatmeal Raisin" 24 | http.SetCookie(w, cookie) 25 | } 26 | })) 27 | defer ts.Close() 28 | 29 | u, err := url.Parse(ts.URL) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | // All users of cookiejar should import "golang.org/x/net/publicsuffix" 35 | jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | client := &http.Client{ 41 | Jar: jar, 42 | } 43 | 44 | if _, err = client.Get(u.String()); err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | fmt.Println("After 1st request:") 49 | for _, cookie := range jar.Cookies(u) { 50 | fmt.Printf(" %s: %s\n", cookie.Name, cookie.Value) 51 | } 52 | 53 | if _, err = client.Get(u.String()); err != nil { 54 | log.Fatal(err) 55 | } 56 | 57 | fmt.Println("After 2nd request:") 58 | for _, cookie := range jar.Cookies(u) { 59 | fmt.Printf(" %s: %s\n", cookie.Name, cookie.Value) 60 | } 61 | // Output: 62 | // After 1st request: 63 | // Flavor: Chocolate Chip 64 | // After 2nd request: 65 | // Flavor: Oatmeal Raisin 66 | } 67 | -------------------------------------------------------------------------------- /cookiejar/punycode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package cookiejar 6 | 7 | // This file implements the Punycode algorithm from RFC 3492. 8 | 9 | import ( 10 | "fmt" 11 | "strings" 12 | "unicode/utf8" 13 | ) 14 | 15 | // These parameter values are specified in section 5. 16 | // 17 | // All computation is done with int32s, so that overflow behavior is identical 18 | // regardless of whether int is 32-bit or 64-bit. 19 | const ( 20 | base int32 = 36 21 | damp int32 = 700 22 | initialBias int32 = 72 23 | initialN int32 = 128 24 | skew int32 = 38 25 | tmax int32 = 26 26 | tmin int32 = 1 27 | ) 28 | 29 | // encode encodes a string as specified in section 6.3 and prepends prefix to 30 | // the result. 31 | // 32 | // The "while h < length(input)" line in the specification becomes "for 33 | // remaining != 0" in the Go code, because len(s) in Go is in bytes, not runes. 34 | func encode(prefix, s string) (string, error) { 35 | output := make([]byte, len(prefix), len(prefix)+1+2*len(s)) 36 | copy(output, prefix) 37 | delta, n, bias := int32(0), initialN, initialBias 38 | b, remaining := int32(0), int32(0) 39 | for _, r := range s { 40 | if r < utf8.RuneSelf { 41 | b++ 42 | output = append(output, byte(r)) 43 | } else { 44 | remaining++ 45 | } 46 | } 47 | h := b 48 | if b > 0 { 49 | output = append(output, '-') 50 | } 51 | for remaining != 0 { 52 | m := int32(0x7fffffff) 53 | for _, r := range s { 54 | if m > r && r >= n { 55 | m = r 56 | } 57 | } 58 | delta += (m - n) * (h + 1) 59 | if delta < 0 { 60 | return "", fmt.Errorf("cookiejar: invalid label %q", s) 61 | } 62 | n = m 63 | for _, r := range s { 64 | if r < n { 65 | delta++ 66 | if delta < 0 { 67 | return "", fmt.Errorf("cookiejar: invalid label %q", s) 68 | } 69 | continue 70 | } 71 | if r > n { 72 | continue 73 | } 74 | q := delta 75 | for k := base; ; k += base { 76 | t := k - bias 77 | if t < tmin { 78 | t = tmin 79 | } else if t > tmax { 80 | t = tmax 81 | } 82 | if q < t { 83 | break 84 | } 85 | output = append(output, encodeDigit(t+(q-t)%(base-t))) 86 | q = (q - t) / (base - t) 87 | } 88 | output = append(output, encodeDigit(q)) 89 | bias = adapt(delta, h+1, h == b) 90 | delta = 0 91 | h++ 92 | remaining-- 93 | } 94 | delta++ 95 | n++ 96 | } 97 | return string(output), nil 98 | } 99 | 100 | func encodeDigit(digit int32) byte { 101 | switch { 102 | case 0 <= digit && digit < 26: 103 | return byte(digit + 'a') 104 | case 26 <= digit && digit < 36: 105 | return byte(digit + ('0' - 26)) 106 | } 107 | panic("cookiejar: internal error in punycode encoding") 108 | } 109 | 110 | // adapt is the bias adaptation function specified in section 6.1. 111 | func adapt(delta, numPoints int32, firstTime bool) int32 { 112 | if firstTime { 113 | delta /= damp 114 | } else { 115 | delta /= 2 116 | } 117 | delta += delta / numPoints 118 | k := int32(0) 119 | for delta > ((base-tmin)*tmax)/2 { 120 | delta /= base - tmin 121 | k += base 122 | } 123 | return k + (base-tmin+1)*delta/(delta+skew) 124 | } 125 | 126 | // Strictly speaking, the remaining code below deals with IDNA (RFC 5890 and 127 | // friends) and not Punycode (RFC 3492) per se. 128 | 129 | // acePrefix is the ASCII Compatible Encoding prefix. 130 | const acePrefix = "xn--" 131 | 132 | // toASCII converts a domain or domain label to its ASCII form. For example, 133 | // toASCII("bücher.example.com") is "xn--bcher-kva.example.com", and 134 | // toASCII("golang") is "golang". 135 | func toASCII(s string) (string, error) { 136 | if ascii(s) { 137 | return s, nil 138 | } 139 | labels := strings.Split(s, ".") 140 | for i, label := range labels { 141 | if !ascii(label) { 142 | a, err := encode(acePrefix, label) 143 | if err != nil { 144 | return "", err 145 | } 146 | labels[i] = a 147 | } 148 | } 149 | return strings.Join(labels, "."), nil 150 | } 151 | 152 | func ascii(s string) bool { 153 | for i := 0; i < len(s); i++ { 154 | if s[i] >= utf8.RuneSelf { 155 | return false 156 | } 157 | } 158 | return true 159 | } 160 | -------------------------------------------------------------------------------- /cookiejar/punycode_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package cookiejar 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | var punycodeTestCases = [...]struct { 12 | s, encoded string 13 | }{ 14 | {"", ""}, 15 | {"-", "--"}, 16 | {"-a", "-a-"}, 17 | {"-a-", "-a--"}, 18 | {"a", "a-"}, 19 | {"a-", "a--"}, 20 | {"a-b", "a-b-"}, 21 | {"books", "books-"}, 22 | {"bücher", "bcher-kva"}, 23 | {"Hello世界", "Hello-ck1hg65u"}, 24 | {"ü", "tda"}, 25 | {"üý", "tdac"}, 26 | 27 | // The test cases below come from RFC 3492 section 7.1 with Errata 3026. 28 | { 29 | // (A) Arabic (Egyptian). 30 | "\u0644\u064A\u0647\u0645\u0627\u0628\u062A\u0643\u0644" + 31 | "\u0645\u0648\u0634\u0639\u0631\u0628\u064A\u061F", 32 | "egbpdaj6bu4bxfgehfvwxn", 33 | }, 34 | { 35 | // (B) Chinese (simplified). 36 | "\u4ED6\u4EEC\u4E3A\u4EC0\u4E48\u4E0D\u8BF4\u4E2D\u6587", 37 | "ihqwcrb4cv8a8dqg056pqjye", 38 | }, 39 | { 40 | // (C) Chinese (traditional). 41 | "\u4ED6\u5011\u7232\u4EC0\u9EBD\u4E0D\u8AAA\u4E2D\u6587", 42 | "ihqwctvzc91f659drss3x8bo0yb", 43 | }, 44 | { 45 | // (D) Czech. 46 | "\u0050\u0072\u006F\u010D\u0070\u0072\u006F\u0073\u0074" + 47 | "\u011B\u006E\u0065\u006D\u006C\u0075\u0076\u00ED\u010D" + 48 | "\u0065\u0073\u006B\u0079", 49 | "Proprostnemluvesky-uyb24dma41a", 50 | }, 51 | { 52 | // (E) Hebrew. 53 | "\u05DC\u05DE\u05D4\u05D4\u05DD\u05E4\u05E9\u05D5\u05D8" + 54 | "\u05DC\u05D0\u05DE\u05D3\u05D1\u05E8\u05D9\u05DD\u05E2" + 55 | "\u05D1\u05E8\u05D9\u05EA", 56 | "4dbcagdahymbxekheh6e0a7fei0b", 57 | }, 58 | { 59 | // (F) Hindi (Devanagari). 60 | "\u092F\u0939\u0932\u094B\u0917\u0939\u093F\u0928\u094D" + 61 | "\u0926\u0940\u0915\u094D\u092F\u094B\u0902\u0928\u0939" + 62 | "\u0940\u0902\u092C\u094B\u0932\u0938\u0915\u0924\u0947" + 63 | "\u0939\u0948\u0902", 64 | "i1baa7eci9glrd9b2ae1bj0hfcgg6iyaf8o0a1dig0cd", 65 | }, 66 | { 67 | // (G) Japanese (kanji and hiragana). 68 | "\u306A\u305C\u307F\u3093\u306A\u65E5\u672C\u8A9E\u3092" + 69 | "\u8A71\u3057\u3066\u304F\u308C\u306A\u3044\u306E\u304B", 70 | "n8jok5ay5dzabd5bym9f0cm5685rrjetr6pdxa", 71 | }, 72 | { 73 | // (H) Korean (Hangul syllables). 74 | "\uC138\uACC4\uC758\uBAA8\uB4E0\uC0AC\uB78C\uB4E4\uC774" + 75 | "\uD55C\uAD6D\uC5B4\uB97C\uC774\uD574\uD55C\uB2E4\uBA74" + 76 | "\uC5BC\uB9C8\uB098\uC88B\uC744\uAE4C", 77 | "989aomsvi5e83db1d2a355cv1e0vak1dwrv93d5xbh15a0dt30a5j" + 78 | "psd879ccm6fea98c", 79 | }, 80 | { 81 | // (I) Russian (Cyrillic). 82 | "\u043F\u043E\u0447\u0435\u043C\u0443\u0436\u0435\u043E" + 83 | "\u043D\u0438\u043D\u0435\u0433\u043E\u0432\u043E\u0440" + 84 | "\u044F\u0442\u043F\u043E\u0440\u0443\u0441\u0441\u043A" + 85 | "\u0438", 86 | "b1abfaaepdrnnbgefbadotcwatmq2g4l", 87 | }, 88 | { 89 | // (J) Spanish. 90 | "\u0050\u006F\u0072\u0071\u0075\u00E9\u006E\u006F\u0070" + 91 | "\u0075\u0065\u0064\u0065\u006E\u0073\u0069\u006D\u0070" + 92 | "\u006C\u0065\u006D\u0065\u006E\u0074\u0065\u0068\u0061" + 93 | "\u0062\u006C\u0061\u0072\u0065\u006E\u0045\u0073\u0070" + 94 | "\u0061\u00F1\u006F\u006C", 95 | "PorqunopuedensimplementehablarenEspaol-fmd56a", 96 | }, 97 | { 98 | // (K) Vietnamese. 99 | "\u0054\u1EA1\u0069\u0073\u0061\u006F\u0068\u1ECD\u006B" + 100 | "\u0068\u00F4\u006E\u0067\u0074\u0068\u1EC3\u0063\u0068" + 101 | "\u1EC9\u006E\u00F3\u0069\u0074\u0069\u1EBF\u006E\u0067" + 102 | "\u0056\u0069\u1EC7\u0074", 103 | "TisaohkhngthchnitingVit-kjcr8268qyxafd2f1b9g", 104 | }, 105 | { 106 | // (L) 3B. 107 | "\u0033\u5E74\u0042\u7D44\u91D1\u516B\u5148\u751F", 108 | "3B-ww4c5e180e575a65lsy2b", 109 | }, 110 | { 111 | // (M) -with-SUPER-MONKEYS. 112 | "\u5B89\u5BA4\u5948\u7F8E\u6075\u002D\u0077\u0069\u0074" + 113 | "\u0068\u002D\u0053\u0055\u0050\u0045\u0052\u002D\u004D" + 114 | "\u004F\u004E\u004B\u0045\u0059\u0053", 115 | "-with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n", 116 | }, 117 | { 118 | // (N) Hello-Another-Way-. 119 | "\u0048\u0065\u006C\u006C\u006F\u002D\u0041\u006E\u006F" + 120 | "\u0074\u0068\u0065\u0072\u002D\u0057\u0061\u0079\u002D" + 121 | "\u305D\u308C\u305E\u308C\u306E\u5834\u6240", 122 | "Hello-Another-Way--fc4qua05auwb3674vfr0b", 123 | }, 124 | { 125 | // (O) 2. 126 | "\u3072\u3068\u3064\u5C4B\u6839\u306E\u4E0B\u0032", 127 | "2-u9tlzr9756bt3uc0v", 128 | }, 129 | { 130 | // (P) MajiKoi5 131 | "\u004D\u0061\u006A\u0069\u3067\u004B\u006F\u0069\u3059" + 132 | "\u308B\u0035\u79D2\u524D", 133 | "MajiKoi5-783gue6qz075azm5e", 134 | }, 135 | { 136 | // (Q) de 137 | "\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0", 138 | "de-jg4avhby1noc0d", 139 | }, 140 | { 141 | // (R) 142 | "\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067", 143 | "d9juau41awczczp", 144 | }, 145 | { 146 | // (S) -> $1.00 <- 147 | "\u002D\u003E\u0020\u0024\u0031\u002E\u0030\u0030\u0020" + 148 | "\u003C\u002D", 149 | "-> $1.00 <--", 150 | }, 151 | } 152 | 153 | func TestPunycode(t *testing.T) { 154 | for _, tc := range punycodeTestCases { 155 | if got, err := encode("", tc.s); err != nil { 156 | t.Errorf(`encode("", %q): %v`, tc.s, err) 157 | } else if got != tc.encoded { 158 | t.Errorf(`encode("", %q): got %q, want %q`, tc.s, got, tc.encoded) 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package http provides HTTP client and server implementations. 7 | 8 | Get, Head, Post, and PostForm make HTTP (or HTTPS) requests: 9 | 10 | resp, err := http.Get("http://example.com/") 11 | ... 12 | resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf) 13 | ... 14 | resp, err := http.PostForm("http://example.com/form", 15 | url.Values{"Key": {"Value"}, "id": {"123"}}) 16 | 17 | The client must close the response body when finished with it: 18 | 19 | resp, err := http.Get("http://example.com/") 20 | if err != nil { 21 | // handle error 22 | } 23 | defer resp.Body.Close() 24 | body, err := io.ReadAll(resp.Body) 25 | // ... 26 | 27 | For control over HTTP client headers, redirect policy, and other 28 | settings, create a Client: 29 | 30 | client := &http.Client{ 31 | CheckRedirect: redirectPolicyFunc, 32 | } 33 | 34 | resp, err := client.Get("http://example.com") 35 | // ... 36 | 37 | req, err := http.NewRequest("GET", "http://example.com", nil) 38 | // ... 39 | req.Header.Add("If-None-Match", `W/"wyzzy"`) 40 | resp, err := client.Do(req) 41 | // ... 42 | 43 | For control over proxies, TLS configuration, keep-alives, 44 | compression, and other settings, create a Transport: 45 | 46 | tr := &http.Transport{ 47 | MaxIdleConns: 10, 48 | IdleConnTimeout: 30 * time.Second, 49 | DisableCompression: true, 50 | } 51 | client := &http.Client{Transport: tr} 52 | resp, err := client.Get("https://example.com") 53 | 54 | Clients and Transports are safe for concurrent use by multiple 55 | goroutines and for efficiency should only be created once and re-used. 56 | 57 | ListenAndServe starts an HTTP server with a given address and handler. 58 | The handler is usually nil, which means to use DefaultServeMux. 59 | Handle and HandleFunc add handlers to DefaultServeMux: 60 | 61 | http.Handle("/foo", fooHandler) 62 | 63 | http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { 64 | fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) 65 | }) 66 | 67 | log.Fatal(http.ListenAndServe(":8080", nil)) 68 | 69 | More control over the server's behavior is available by creating a 70 | custom Server: 71 | 72 | s := &http.Server{ 73 | Addr: ":8080", 74 | Handler: myHandler, 75 | ReadTimeout: 10 * time.Second, 76 | WriteTimeout: 10 * time.Second, 77 | MaxHeaderBytes: 1 << 20, 78 | } 79 | log.Fatal(s.ListenAndServe()) 80 | 81 | Starting with Go 1.6, the http package has transparent support for the 82 | HTTP/2 protocol when using HTTPS. Programs that must disable HTTP/2 83 | can do so by setting Transport.TLSNextProto (for clients) or 84 | Server.TLSNextProto (for servers) to a non-nil, empty 85 | map. Alternatively, the following GODEBUG environment variables are 86 | currently supported: 87 | 88 | GODEBUG=http2client=0 # disable HTTP/2 client support 89 | GODEBUG=http2server=0 # disable HTTP/2 server support 90 | GODEBUG=http2debug=1 # enable verbose HTTP/2 debug logs 91 | GODEBUG=http2debug=2 # ... even more verbose, with frame dumps 92 | 93 | The GODEBUG variables are not covered by Go's API compatibility 94 | promise. Please report any issues before disabling HTTP/2 95 | support: https://golang.org/s/http2bug 96 | 97 | The http package's Transport and Server both automatically enable 98 | HTTP/2 support for simple configurations. To enable HTTP/2 for more 99 | complex configurations, to use lower-level HTTP/2 features, or to use 100 | a newer version of Go's http2 package, import "golang.org/x/net/http2" 101 | directly and use its ConfigureTransport and/or ConfigureServer 102 | functions. Manually configuring HTTP/2 via the golang.org/x/net/http2 103 | package takes precedence over the net/http package's built-in HTTP/2 104 | support. 105 | 106 | */ 107 | package http 108 | -------------------------------------------------------------------------------- /example_filesystem_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http_test 6 | 7 | import ( 8 | "io/fs" 9 | "log" 10 | "strings" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | ) 14 | 15 | // containsDotFile reports whether name contains a path element starting with a period. 16 | // The name is assumed to be a delimited by forward slashes, as guaranteed 17 | // by the http.FileSystem interface. 18 | func containsDotFile(name string) bool { 19 | parts := strings.Split(name, "/") 20 | for _, part := range parts { 21 | if strings.HasPrefix(part, ".") { 22 | return true 23 | } 24 | } 25 | return false 26 | } 27 | 28 | // dotFileHidingFile is the http.File use in dotFileHidingFileSystem. 29 | // It is used to wrap the Readdir method of http.File so that we can 30 | // remove files and directories that start with a period from its output. 31 | type dotFileHidingFile struct { 32 | http.File 33 | } 34 | 35 | // Readdir is a wrapper around the Readdir method of the embedded File 36 | // that filters out all files that start with a period in their name. 37 | func (f dotFileHidingFile) Readdir(n int) (fis []fs.FileInfo, err error) { 38 | files, err := f.File.Readdir(n) 39 | for _, file := range files { // Filters out the dot files 40 | if !strings.HasPrefix(file.Name(), ".") { 41 | fis = append(fis, file) 42 | } 43 | } 44 | return 45 | } 46 | 47 | // dotFileHidingFileSystem is an http.FileSystem that hides 48 | // hidden "dot files" from being served. 49 | type dotFileHidingFileSystem struct { 50 | http.FileSystem 51 | } 52 | 53 | // Open is a wrapper around the Open method of the embedded FileSystem 54 | // that serves a 403 permission error when name has a file or directory 55 | // with whose name starts with a period in its path. 56 | func (fsys dotFileHidingFileSystem) Open(name string) (http.File, error) { 57 | if containsDotFile(name) { // If dot file, return 403 response 58 | return nil, fs.ErrPermission 59 | } 60 | 61 | file, err := fsys.FileSystem.Open(name) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return dotFileHidingFile{file}, err 66 | } 67 | 68 | func ExampleFileServer_dotFileHiding() { 69 | fsys := dotFileHidingFileSystem{http.Dir(".")} 70 | http.Handle("/", http.FileServer(fsys)) 71 | log.Fatal(http.ListenAndServe(":8080", nil)) 72 | } 73 | -------------------------------------------------------------------------------- /example_handle_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http_test 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "sync" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | ) 14 | 15 | type countHandler struct { 16 | mu sync.Mutex // guards n 17 | n int 18 | } 19 | 20 | func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 21 | h.mu.Lock() 22 | defer h.mu.Unlock() 23 | h.n++ 24 | fmt.Fprintf(w, "count is %d\n", h.n) 25 | } 26 | 27 | func ExampleHandle() { 28 | http.Handle("/count", new(countHandler)) 29 | log.Fatal(http.ListenAndServe(":8080", nil)) 30 | } 31 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http_test 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "io" 11 | "log" 12 | "os" 13 | "os/signal" 14 | 15 | http "github.com/bogdanfinn/fhttp" 16 | ) 17 | 18 | func ExampleHijacker() { 19 | http.HandleFunc("/hijack", func(w http.ResponseWriter, r *http.Request) { 20 | hj, ok := w.(http.Hijacker) 21 | if !ok { 22 | http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError) 23 | return 24 | } 25 | conn, bufrw, err := hj.Hijack() 26 | if err != nil { 27 | http.Error(w, err.Error(), http.StatusInternalServerError) 28 | return 29 | } 30 | // Don't forget to close the connection: 31 | defer conn.Close() 32 | bufrw.WriteString("Now we're speaking raw TCP. Say hi: ") 33 | bufrw.Flush() 34 | s, err := bufrw.ReadString('\n') 35 | if err != nil { 36 | log.Printf("error reading string: %v", err) 37 | return 38 | } 39 | fmt.Fprintf(bufrw, "You said: %q\nBye.\n", s) 40 | bufrw.Flush() 41 | }) 42 | } 43 | 44 | func ExampleGet() { 45 | res, err := http.Get("http://www.google.com/robots.txt") 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | robots, err := io.ReadAll(res.Body) 50 | res.Body.Close() 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | fmt.Printf("%s", robots) 55 | } 56 | 57 | func ExampleFileServer() { 58 | // Simple static webserver: 59 | log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc")))) 60 | } 61 | 62 | func ExampleFileServer_stripPrefix() { 63 | // To serve a directory on disk (/tmp) under an alternate URL 64 | // path (/tmpfiles/), use StripPrefix to modify the request 65 | // URL's path before the FileServer sees it: 66 | http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp")))) 67 | } 68 | 69 | func ExampleStripPrefix() { 70 | // To serve a directory on disk (/tmp) under an alternate URL 71 | // path (/tmpfiles/), use StripPrefix to modify the request 72 | // URL's path before the FileServer sees it: 73 | http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp")))) 74 | } 75 | 76 | type apiHandler struct{} 77 | 78 | func (apiHandler) ServeHTTP(http.ResponseWriter, *http.Request) {} 79 | 80 | func ExampleServeMux_Handle() { 81 | mux := http.NewServeMux() 82 | mux.Handle("/api/", apiHandler{}) 83 | mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 84 | // The "/" pattern matches everything, so we need to check 85 | // that we're at the root here. 86 | if req.URL.Path != "/" { 87 | http.NotFound(w, req) 88 | return 89 | } 90 | fmt.Fprintf(w, "Welcome to the home page!") 91 | }) 92 | } 93 | 94 | // HTTP Trailers are a set of Key/value pairs like headers that come 95 | // after the HTTP response, instead of before. 96 | func ExampleResponseWriter_trailers() { 97 | mux := http.NewServeMux() 98 | mux.HandleFunc("/sendstrailers", func(w http.ResponseWriter, req *http.Request) { 99 | // Before any call to WriteHeader or Write, declare 100 | // the trailers you will set during the HTTP 101 | // response. These three headers are actually sent in 102 | // the trailer. 103 | w.Header().Set("Trailer", "AtEnd1, AtEnd2") 104 | w.Header().Add("Trailer", "AtEnd3") 105 | 106 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") // normal header 107 | w.WriteHeader(http.StatusOK) 108 | 109 | w.Header().Set("AtEnd1", "value 1") 110 | io.WriteString(w, "This HTTP response has both headers before this text and trailers at the end.\n") 111 | w.Header().Set("AtEnd2", "value 2") 112 | w.Header().Set("AtEnd3", "value 3") // These will appear as trailers. 113 | }) 114 | } 115 | 116 | func ExampleServer_Shutdown() { 117 | var srv http.Server 118 | 119 | idleConnsClosed := make(chan struct{}) 120 | go func() { 121 | sigint := make(chan os.Signal, 1) 122 | signal.Notify(sigint, os.Interrupt) 123 | <-sigint 124 | 125 | // We received an interrupt signal, shut down. 126 | if err := srv.Shutdown(context.Background()); err != nil { 127 | // Error from closing listeners, or context timeout: 128 | log.Printf("HTTP server Shutdown: %v", err) 129 | } 130 | close(idleConnsClosed) 131 | }() 132 | 133 | if err := srv.ListenAndServe(); err != http.ErrServerClosed { 134 | // Error starting or closing listener: 135 | log.Fatalf("HTTP server ListenAndServe: %v", err) 136 | } 137 | 138 | <-idleConnsClosed 139 | } 140 | 141 | func ExampleListenAndServeTLS() { 142 | http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 143 | io.WriteString(w, "Hello, TLS!\n") 144 | }) 145 | 146 | // One can use generate_cert.go in crypto/tls to generate cert.pem and Key.pem. 147 | log.Printf("About to listen on 8443. Go to https://127.0.0.1:8443/") 148 | err := http.ListenAndServeTLS(":8443", "cert.pem", "Key.pem", nil) 149 | log.Fatal(err) 150 | } 151 | 152 | func ExampleListenAndServe() { 153 | // Hello world, the web server 154 | 155 | helloHandler := func(w http.ResponseWriter, req *http.Request) { 156 | io.WriteString(w, "Hello, world!\n") 157 | } 158 | 159 | http.HandleFunc("/hello", helloHandler) 160 | log.Fatal(http.ListenAndServe(":8080", nil)) 161 | } 162 | 163 | func ExampleHandleFunc() { 164 | h1 := func(w http.ResponseWriter, _ *http.Request) { 165 | io.WriteString(w, "Hello from a HandleFunc #1!\n") 166 | } 167 | h2 := func(w http.ResponseWriter, _ *http.Request) { 168 | io.WriteString(w, "Hello from a HandleFunc #2!\n") 169 | } 170 | 171 | http.HandleFunc("/", h1) 172 | http.HandleFunc("/endpoint", h2) 173 | 174 | log.Fatal(http.ListenAndServe(":8080", nil)) 175 | } 176 | 177 | func newPeopleHandler() http.Handler { 178 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 179 | fmt.Fprintln(w, "This is the people handler.") 180 | }) 181 | } 182 | 183 | func ExampleNotFoundHandler() { 184 | mux := http.NewServeMux() 185 | 186 | // Create sample handler to returns 404 187 | mux.Handle("/resources", http.NotFoundHandler()) 188 | 189 | // Create sample handler that returns 200 190 | mux.Handle("/resources/people/", newPeopleHandler()) 191 | 192 | log.Fatal(http.ListenAndServe(":8080", mux)) 193 | } 194 | -------------------------------------------------------------------------------- /filetransport.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | // fileTransport implements RoundTripper for the 'file' protocol. 13 | type fileTransport struct { 14 | fh fileHandler 15 | } 16 | 17 | // NewFileTransport returns a new RoundTripper, serving the provided 18 | // FileSystem. The returned RoundTripper ignores the URL host in its 19 | // incoming requests, as well as most other properties of the 20 | // request. 21 | // 22 | // The typical use case for NewFileTransport is to register the "file" 23 | // protocol with a Transport, as in: 24 | // 25 | // t := &http.Transport{} 26 | // t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/"))) 27 | // c := &http.Client{Transport: t} 28 | // res, err := c.Get("file:///etc/passwd") 29 | // ... 30 | func NewFileTransport(fs FileSystem) RoundTripper { 31 | return fileTransport{fileHandler{fs}} 32 | } 33 | 34 | func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) { 35 | // We start ServeHTTP in a goroutine, which may take a long 36 | // time if the file is large. The newPopulateResponseWriter 37 | // call returns a channel which either ServeHTTP or finish() 38 | // sends our *Response on, once the *Response itself has been 39 | // populated (even if the body itself is still being 40 | // written to the res.Body, a pipe) 41 | rw, resc := newPopulateResponseWriter() 42 | go func() { 43 | t.fh.ServeHTTP(rw, req) 44 | rw.finish() 45 | }() 46 | return <-resc, nil 47 | } 48 | 49 | func newPopulateResponseWriter() (*populateResponse, <-chan *Response) { 50 | pr, pw := io.Pipe() 51 | rw := &populateResponse{ 52 | ch: make(chan *Response), 53 | pw: pw, 54 | res: &Response{ 55 | Proto: "HTTP/1.0", 56 | ProtoMajor: 1, 57 | Header: make(Header), 58 | Close: true, 59 | Body: pr, 60 | }, 61 | } 62 | return rw, rw.ch 63 | } 64 | 65 | // populateResponse is a ResponseWriter that populates the *Response 66 | // in res, and writes its body to a pipe connected to the response 67 | // body. Once writes begin or finish() is called, the response is sent 68 | // on ch. 69 | type populateResponse struct { 70 | res *Response 71 | ch chan *Response 72 | wroteHeader bool 73 | hasContent bool 74 | sentResponse bool 75 | pw *io.PipeWriter 76 | } 77 | 78 | func (pr *populateResponse) finish() { 79 | if !pr.wroteHeader { 80 | pr.WriteHeader(500) 81 | } 82 | if !pr.sentResponse { 83 | pr.sendResponse() 84 | } 85 | pr.pw.Close() 86 | } 87 | 88 | func (pr *populateResponse) sendResponse() { 89 | if pr.sentResponse { 90 | return 91 | } 92 | pr.sentResponse = true 93 | 94 | if pr.hasContent { 95 | pr.res.ContentLength = -1 96 | } 97 | pr.ch <- pr.res 98 | } 99 | 100 | func (pr *populateResponse) Header() Header { 101 | return pr.res.Header 102 | } 103 | 104 | func (pr *populateResponse) WriteHeader(code int) { 105 | if pr.wroteHeader { 106 | return 107 | } 108 | pr.wroteHeader = true 109 | 110 | pr.res.StatusCode = code 111 | pr.res.Status = fmt.Sprintf("%d %s", code, StatusText(code)) 112 | } 113 | 114 | func (pr *populateResponse) Write(p []byte) (n int, err error) { 115 | if !pr.wroteHeader { 116 | pr.WriteHeader(StatusOK) 117 | } 118 | pr.hasContent = true 119 | if !pr.sentResponse { 120 | pr.sendResponse() 121 | } 122 | return pr.pw.Write(p) 123 | } 124 | -------------------------------------------------------------------------------- /filetransport_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "io" 9 | "os" 10 | "path/filepath" 11 | "testing" 12 | ) 13 | 14 | func checker(t *testing.T) func(string, error) { 15 | return func(call string, err error) { 16 | if err == nil { 17 | return 18 | } 19 | t.Fatalf("%s: %v", call, err) 20 | } 21 | } 22 | 23 | func TestFileTransport(t *testing.T) { 24 | check := checker(t) 25 | 26 | dname, err := os.MkdirTemp("", "") 27 | check("TempDir", err) 28 | fname := filepath.Join(dname, "foo.txt") 29 | err = os.WriteFile(fname, []byte("Bar"), 0644) 30 | check("WriteFile", err) 31 | defer os.Remove(dname) 32 | defer os.Remove(fname) 33 | 34 | tr := &Transport{} 35 | tr.RegisterProtocol("file", NewFileTransport(Dir(dname))) 36 | c := &Client{Transport: tr} 37 | 38 | fooURLs := []string{"file:///foo.txt", "file://../foo.txt"} 39 | for _, urlstr := range fooURLs { 40 | res, err := c.Get(urlstr) 41 | check("Get "+urlstr, err) 42 | if res.StatusCode != 200 { 43 | t.Errorf("for %s, StatusCode = %d, want 200", urlstr, res.StatusCode) 44 | } 45 | if res.ContentLength != -1 { 46 | t.Errorf("for %s, ContentLength = %d, want -1", urlstr, res.ContentLength) 47 | } 48 | if res.Body == nil { 49 | t.Fatalf("for %s, nil Body", urlstr) 50 | } 51 | slurp, err := io.ReadAll(res.Body) 52 | res.Body.Close() 53 | check("ReadAll "+urlstr, err) 54 | if string(slurp) != "Bar" { 55 | t.Errorf("for %s, got content %q, want %q", urlstr, string(slurp), "Bar") 56 | } 57 | } 58 | 59 | const badURL = "file://../no-exist.txt" 60 | res, err := c.Get(badURL) 61 | check("Get "+badURL, err) 62 | if res.StatusCode != 404 { 63 | t.Errorf("for %s, StatusCode = %d, want 404", badURL, res.StatusCode) 64 | } 65 | res.Body.Close() 66 | } 67 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bogdanfinn/fhttp 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/andybalholm/brotli v1.1.1 7 | github.com/bogdanfinn/utls v1.6.5 8 | github.com/klauspost/compress v1.17.11 9 | golang.org/x/net v0.31.0 10 | golang.org/x/term v0.26.0 11 | ) 12 | 13 | require ( 14 | github.com/cloudflare/circl v1.5.0 // indirect 15 | github.com/quic-go/quic-go v0.48.1 // indirect 16 | golang.org/x/crypto v0.29.0 // indirect 17 | golang.org/x/sys v0.27.0 // indirect 18 | golang.org/x/text v0.20.0 // indirect 19 | ) 20 | 21 | // replace github.com/bogdanfinn/utls => ../utls 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 2 | github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 3 | github.com/bogdanfinn/utls v1.6.5 h1:rVMQvhyN3zodLxKFWMRLt19INGBCZ/OM2/vBWPNIt1w= 4 | github.com/bogdanfinn/utls v1.6.5/go.mod h1:czcHxHGsc1q9NjgWSeSinQZzn6MR76zUmGVIGanSXO0= 5 | github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= 6 | github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 10 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA= 14 | github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= 15 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 16 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 17 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 18 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 19 | golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= 20 | golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= 21 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= 22 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= 23 | golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= 24 | golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= 25 | golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= 26 | golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 27 | golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= 28 | golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= 29 | golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= 30 | golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= 31 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 32 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 33 | -------------------------------------------------------------------------------- /http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:generate bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 github.com/bogdanfinn/fhttp/http2 6 | 7 | package http 8 | 9 | import ( 10 | "io" 11 | "strconv" 12 | "strings" 13 | "time" 14 | "unicode/utf8" 15 | 16 | "golang.org/x/net/http/httpguts" 17 | ) 18 | 19 | // incomparable is a zero-width, non-comparable type. Adding it to a struct 20 | // makes that struct also non-comparable, and generally doesn't add 21 | // any size (as long as it's first). 22 | type incomparable [0]func() 23 | 24 | // maxInt64 is the effective "infinite" value for the Server and 25 | // Transport's byte-limiting readers. 26 | const maxInt64 = 1<<63 - 1 27 | 28 | // aLongTimeAgo is a non-zero time, far in the past, used for 29 | // immediate cancellation of network operations. 30 | var aLongTimeAgo = time.Unix(1, 0) 31 | 32 | // omitBundledHTTP2 is set by omithttp2.go when the nethttpomithttp2 33 | // build tag is set. That means h2_bundle.go isn't compiled in and we 34 | // shouldn't try to use it. 35 | var omitBundledHTTP2 bool 36 | 37 | // TODO(bradfitz): move common stuff here. The other files have accumulated 38 | // generic http stuff in random places. 39 | 40 | // contextKey is a value for use with context.WithValue. It's used as 41 | // a pointer so it fits in an interface{} without allocation. 42 | type contextKey struct { 43 | name string 44 | } 45 | 46 | func (k *contextKey) String() string { return "net/http context value " + k.name } 47 | 48 | // Given a string of the form "host", "host:port", or "[ipv6::address]:port", 49 | // return true if the string includes a port. 50 | func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } 51 | 52 | // removeEmptyPort strips the empty port in ":port" to "" 53 | // as mandated by RFC 3986 Section 6.2.3. 54 | func removeEmptyPort(host string) string { 55 | if hasPort(host) { 56 | return strings.TrimSuffix(host, ":") 57 | } 58 | return host 59 | } 60 | 61 | func isNotToken(r rune) bool { 62 | return !httpguts.IsTokenRune(r) 63 | } 64 | 65 | func isASCII(s string) bool { 66 | for i := 0; i < len(s); i++ { 67 | if s[i] >= utf8.RuneSelf { 68 | return false 69 | } 70 | } 71 | return true 72 | } 73 | 74 | // stringContainsCTLByte reports whether s contains any ASCII control character. 75 | func stringContainsCTLByte(s string) bool { 76 | for i := 0; i < len(s); i++ { 77 | b := s[i] 78 | if b < ' ' || b == 0x7f { 79 | return true 80 | } 81 | } 82 | return false 83 | } 84 | 85 | func hexEscapeNonASCII(s string) string { 86 | newLen := 0 87 | for i := 0; i < len(s); i++ { 88 | if s[i] >= utf8.RuneSelf { 89 | newLen += 3 90 | } else { 91 | newLen++ 92 | } 93 | } 94 | if newLen == len(s) { 95 | return s 96 | } 97 | b := make([]byte, 0, newLen) 98 | for i := 0; i < len(s); i++ { 99 | if s[i] >= utf8.RuneSelf { 100 | b = append(b, '%') 101 | b = strconv.AppendInt(b, int64(s[i]), 16) 102 | } else { 103 | b = append(b, s[i]) 104 | } 105 | } 106 | return string(b) 107 | } 108 | 109 | // NoBody is an io.ReadCloser with no bytes. Read always returns EOF 110 | // and Close always returns nil. It can be used in an outgoing client 111 | // request to explicitly signal that a request has zero bytes. 112 | // An alternative, however, is to simply set Request.Body to nil. 113 | var NoBody = noBody{} 114 | 115 | type noBody struct{} 116 | 117 | func (noBody) Read([]byte) (int, error) { return 0, io.EOF } 118 | func (noBody) Close() error { return nil } 119 | func (noBody) WriteTo(io.Writer) (int64, error) { return 0, nil } 120 | 121 | var ( 122 | // verify that an io.Copy from NoBody won't require a buffer: 123 | _ io.WriterTo = NoBody 124 | _ io.ReadCloser = NoBody 125 | ) 126 | 127 | // PushOptions describes options for Pusher.Push. 128 | type PushOptions struct { 129 | // Method specifies the HTTP method for the promised request. 130 | // If set, it must be "GET" or "HEAD". Empty means "GET". 131 | Method string 132 | 133 | // Header specifies additional promised request headers. This cannot 134 | // include HTTP/2 pseudo header fields like ":path" and ":scheme", 135 | // which will be added automatically. 136 | Header Header 137 | } 138 | 139 | // Pusher is the interface implemented by ResponseWriters that support 140 | // HTTP/2 server push. For more background, see 141 | // https://tools.ietf.org/html/rfc7540#section-8.2. 142 | type Pusher interface { 143 | // Push initiates an HTTP/2 server push. This constructs a synthetic 144 | // request using the given target and options, serializes that request 145 | // into a PUSH_PROMISE frame, then dispatches that request using the 146 | // server's request handler. If opts is nil, default options are used. 147 | // 148 | // The target must either be an absolute path (like "/path") or an absolute 149 | // URL that contains a valid host and the same scheme as the parent request. 150 | // If the target is a path, it will inherit the scheme and host of the 151 | // parent request. 152 | // 153 | // The HTTP/2 spec disallows recursive pushes and cross-authority pushes. 154 | // Push may or may not detect these invalid pushes; however, invalid 155 | // pushes will be detected and canceled by conforming clients. 156 | // 157 | // Handlers that wish to push URL X should call Push before sending any 158 | // data that may trigger a request for URL X. This avoids a race where the 159 | // client issues requests for X before receiving the PUSH_PROMISE for X. 160 | // 161 | // Push will run in a separate goroutine making the order of arrival 162 | // non-deterministic. Any required synchronization needs to be implemented 163 | // by the caller. 164 | // 165 | // Push returns ErrNotSupported if the client has disabled push or if push 166 | // is not supported on the underlying connection. 167 | Push(target string, opts *PushOptions) error 168 | } 169 | -------------------------------------------------------------------------------- /http2/.gitignore: -------------------------------------------------------------------------------- 1 | *.txt -------------------------------------------------------------------------------- /http2/README: -------------------------------------------------------------------------------- 1 | This is a work-in-progress HTTP/2 implementation for Go. 2 | 3 | It will eventually live in the Go standard library and won't require 4 | any changes to your code to use. It will just be automatic. 5 | 6 | Status: 7 | 8 | * The server support is pretty good. A few things are missing 9 | but are being worked on. 10 | * The client work has just started but shares a lot of code 11 | is coming along much quicker. 12 | 13 | Docs are at https://godoc.org/golang.org/x/net/http2 14 | 15 | Demo test server at https://http2.golang.org/ 16 | 17 | Help & bug reports welcome! 18 | 19 | Contributing: https://golang.org/doc/contribute.html 20 | Bugs: https://golang.org/issue/new?title=x/net/http2:+ 21 | -------------------------------------------------------------------------------- /http2/databuffer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "sync" 11 | ) 12 | 13 | // Buffer chunks are allocated from a pool to reduce pressure on GC. 14 | // The maximum wasted space per dataBuffer is 2x the largest size class, 15 | // which happens when the dataBuffer has multiple chunks and there is 16 | // one unread byte in both the first and last chunks. We use a few size 17 | // classes to minimize overheads for servers that typically receive very 18 | // small request bodies. 19 | // 20 | // TODO: Benchmark to determine if the pools are necessary. The GC may have 21 | // improved enough that we can instead allocate chunks like this: 22 | // make([]byte, max(16<<10, expectedBytesRemaining)) 23 | var ( 24 | dataChunkSizeClasses = []int{ 25 | 1 << 10, 26 | 2 << 10, 27 | 4 << 10, 28 | 8 << 10, 29 | 16 << 10, 30 | } 31 | dataChunkPools = [...]sync.Pool{ 32 | {New: func() interface{} { return make([]byte, 1<<10) }}, 33 | {New: func() interface{} { return make([]byte, 2<<10) }}, 34 | {New: func() interface{} { return make([]byte, 4<<10) }}, 35 | {New: func() interface{} { return make([]byte, 8<<10) }}, 36 | {New: func() interface{} { return make([]byte, 16<<10) }}, 37 | } 38 | ) 39 | 40 | func getDataBufferChunk(size int64) []byte { 41 | i := 0 42 | for ; i < len(dataChunkSizeClasses)-1; i++ { 43 | if size <= int64(dataChunkSizeClasses[i]) { 44 | break 45 | } 46 | } 47 | return dataChunkPools[i].Get().([]byte) 48 | } 49 | 50 | func putDataBufferChunk(p []byte) { 51 | for i, n := range dataChunkSizeClasses { 52 | if len(p) == n { 53 | dataChunkPools[i].Put(p) 54 | return 55 | } 56 | } 57 | panic(fmt.Sprintf("unexpected buffer len=%v", len(p))) 58 | } 59 | 60 | // dataBuffer is an io.ReadWriter backed by a list of data chunks. 61 | // Each dataBuffer is used to read DATA frames on a single stream. 62 | // The buffer is divided into chunks so the server can limit the 63 | // total memory used by a single connection without limiting the 64 | // request body size on any single stream. 65 | type dataBuffer struct { 66 | chunks [][]byte 67 | r int // next byte to read is chunks[0][r] 68 | w int // next byte to write is chunks[len(chunks)-1][w] 69 | size int // total buffered bytes 70 | expected int64 // we expect at least this many bytes in future Write calls (ignored if <= 0) 71 | } 72 | 73 | var errReadEmpty = errors.New("read from empty dataBuffer") 74 | 75 | // Read copies bytes from the buffer into p. 76 | // It is an error to read when no data is available. 77 | func (b *dataBuffer) Read(p []byte) (int, error) { 78 | if b.size == 0 { 79 | return 0, errReadEmpty 80 | } 81 | var ntotal int 82 | for len(p) > 0 && b.size > 0 { 83 | readFrom := b.bytesFromFirstChunk() 84 | n := copy(p, readFrom) 85 | p = p[n:] 86 | ntotal += n 87 | b.r += n 88 | b.size -= n 89 | // If the first chunk has been consumed, advance to the next chunk. 90 | if b.r == len(b.chunks[0]) { 91 | putDataBufferChunk(b.chunks[0]) 92 | end := len(b.chunks) - 1 93 | copy(b.chunks[:end], b.chunks[1:]) 94 | b.chunks[end] = nil 95 | b.chunks = b.chunks[:end] 96 | b.r = 0 97 | } 98 | } 99 | return ntotal, nil 100 | } 101 | 102 | func (b *dataBuffer) bytesFromFirstChunk() []byte { 103 | if len(b.chunks) == 1 { 104 | return b.chunks[0][b.r:b.w] 105 | } 106 | return b.chunks[0][b.r:] 107 | } 108 | 109 | // Len returns the number of bytes of the unread portion of the buffer. 110 | func (b *dataBuffer) Len() int { 111 | return b.size 112 | } 113 | 114 | // Write appends p to the buffer. 115 | func (b *dataBuffer) Write(p []byte) (int, error) { 116 | ntotal := len(p) 117 | for len(p) > 0 { 118 | // If the last chunk is empty, allocate a new chunk. Try to allocate 119 | // enough to fully copy p plus any additional bytes we expect to 120 | // receive. However, this may allocate less than len(p). 121 | want := int64(len(p)) 122 | if b.expected > want { 123 | want = b.expected 124 | } 125 | chunk := b.lastChunkOrAlloc(want) 126 | n := copy(chunk[b.w:], p) 127 | p = p[n:] 128 | b.w += n 129 | b.size += n 130 | b.expected -= int64(n) 131 | } 132 | return ntotal, nil 133 | } 134 | 135 | func (b *dataBuffer) lastChunkOrAlloc(want int64) []byte { 136 | if len(b.chunks) != 0 { 137 | last := b.chunks[len(b.chunks)-1] 138 | if b.w < len(last) { 139 | return last 140 | } 141 | } 142 | chunk := getDataBufferChunk(want) 143 | b.chunks = append(b.chunks, chunk) 144 | b.w = 0 145 | return chunk 146 | } 147 | -------------------------------------------------------------------------------- /http2/databuffer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "reflect" 11 | "testing" 12 | ) 13 | 14 | func fmtDataChunk(chunk []byte) string { 15 | out := "" 16 | var last byte 17 | var count int 18 | for _, c := range chunk { 19 | if c != last { 20 | if count > 0 { 21 | out += fmt.Sprintf(" x %d ", count) 22 | count = 0 23 | } 24 | out += string([]byte{c}) 25 | last = c 26 | } 27 | count++ 28 | } 29 | if count > 0 { 30 | out += fmt.Sprintf(" x %d", count) 31 | } 32 | return out 33 | } 34 | 35 | func fmtDataChunks(chunks [][]byte) string { 36 | var out string 37 | for _, chunk := range chunks { 38 | out += fmt.Sprintf("{%q}", fmtDataChunk(chunk)) 39 | } 40 | return out 41 | } 42 | 43 | func testDataBuffer(t *testing.T, wantBytes []byte, setup func(t *testing.T) *dataBuffer) { 44 | // Run setup, then read the remaining bytes from the dataBuffer and check 45 | // that they match wantBytes. We use different read sizes to check corner 46 | // cases in Read. 47 | for _, readSize := range []int{1, 2, 1 * 1024, 32 * 1024} { 48 | t.Run(fmt.Sprintf("ReadSize=%d", readSize), func(t *testing.T) { 49 | b := setup(t) 50 | buf := make([]byte, readSize) 51 | var gotRead bytes.Buffer 52 | for { 53 | n, err := b.Read(buf) 54 | gotRead.Write(buf[:n]) 55 | if err == errReadEmpty { 56 | break 57 | } 58 | if err != nil { 59 | t.Fatalf("error after %v bytes: %v", gotRead.Len(), err) 60 | } 61 | } 62 | if got, want := gotRead.Bytes(), wantBytes; !bytes.Equal(got, want) { 63 | t.Errorf("FinalRead=%q, want %q", fmtDataChunk(got), fmtDataChunk(want)) 64 | } 65 | }) 66 | } 67 | } 68 | 69 | func TestDataBufferAllocation(t *testing.T) { 70 | writes := [][]byte{ 71 | bytes.Repeat([]byte("a"), 1*1024-1), 72 | []byte("a"), 73 | bytes.Repeat([]byte("b"), 4*1024-1), 74 | []byte("b"), 75 | bytes.Repeat([]byte("c"), 8*1024-1), 76 | []byte("c"), 77 | bytes.Repeat([]byte("d"), 16*1024-1), 78 | []byte("d"), 79 | bytes.Repeat([]byte("e"), 32*1024), 80 | } 81 | var wantRead bytes.Buffer 82 | for _, p := range writes { 83 | wantRead.Write(p) 84 | } 85 | 86 | testDataBuffer(t, wantRead.Bytes(), func(t *testing.T) *dataBuffer { 87 | b := &dataBuffer{} 88 | for _, p := range writes { 89 | if n, err := b.Write(p); n != len(p) || err != nil { 90 | t.Fatalf("Write(%q x %d)=%v,%v want %v,nil", p[:1], len(p), n, err, len(p)) 91 | } 92 | } 93 | want := [][]byte{ 94 | bytes.Repeat([]byte("a"), 1*1024), 95 | bytes.Repeat([]byte("b"), 4*1024), 96 | bytes.Repeat([]byte("c"), 8*1024), 97 | bytes.Repeat([]byte("d"), 16*1024), 98 | bytes.Repeat([]byte("e"), 16*1024), 99 | bytes.Repeat([]byte("e"), 16*1024), 100 | } 101 | if !reflect.DeepEqual(b.chunks, want) { 102 | t.Errorf("dataBuffer.chunks\ngot: %s\nwant: %s", fmtDataChunks(b.chunks), fmtDataChunks(want)) 103 | } 104 | return b 105 | }) 106 | } 107 | 108 | func TestDataBufferAllocationWithExpected(t *testing.T) { 109 | writes := [][]byte{ 110 | bytes.Repeat([]byte("a"), 1*1024), // allocates 16KB 111 | bytes.Repeat([]byte("b"), 14*1024), 112 | bytes.Repeat([]byte("c"), 15*1024), // allocates 16KB more 113 | bytes.Repeat([]byte("d"), 2*1024), 114 | bytes.Repeat([]byte("e"), 1*1024), // overflows 32KB expectation, allocates just 1KB 115 | } 116 | var wantRead bytes.Buffer 117 | for _, p := range writes { 118 | wantRead.Write(p) 119 | } 120 | 121 | testDataBuffer(t, wantRead.Bytes(), func(t *testing.T) *dataBuffer { 122 | b := &dataBuffer{expected: 32 * 1024} 123 | for _, p := range writes { 124 | if n, err := b.Write(p); n != len(p) || err != nil { 125 | t.Fatalf("Write(%q x %d)=%v,%v want %v,nil", p[:1], len(p), n, err, len(p)) 126 | } 127 | } 128 | want := [][]byte{ 129 | append(bytes.Repeat([]byte("a"), 1*1024), append(bytes.Repeat([]byte("b"), 14*1024), bytes.Repeat([]byte("c"), 1*1024)...)...), 130 | append(bytes.Repeat([]byte("c"), 14*1024), bytes.Repeat([]byte("d"), 2*1024)...), 131 | bytes.Repeat([]byte("e"), 1*1024), 132 | } 133 | if !reflect.DeepEqual(b.chunks, want) { 134 | t.Errorf("dataBuffer.chunks\ngot: %s\nwant: %s", fmtDataChunks(b.chunks), fmtDataChunks(want)) 135 | } 136 | return b 137 | }) 138 | } 139 | 140 | func TestDataBufferWriteAfterPartialRead(t *testing.T) { 141 | testDataBuffer(t, []byte("cdxyz"), func(t *testing.T) *dataBuffer { 142 | b := &dataBuffer{} 143 | if n, err := b.Write([]byte("abcd")); n != 4 || err != nil { 144 | t.Fatalf("Write(\"abcd\")=%v,%v want 4,nil", n, err) 145 | } 146 | p := make([]byte, 2) 147 | if n, err := b.Read(p); n != 2 || err != nil || !bytes.Equal(p, []byte("ab")) { 148 | t.Fatalf("Read()=%q,%v,%v want \"ab\",2,nil", p, n, err) 149 | } 150 | if n, err := b.Write([]byte("xyz")); n != 3 || err != nil { 151 | t.Fatalf("Write(\"xyz\")=%v,%v want 3,nil", n, err) 152 | } 153 | return b 154 | }) 155 | } 156 | -------------------------------------------------------------------------------- /http2/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | ) 11 | 12 | // An ErrCode is an unsigned 32-bit error code as defined in the HTTP/2 spec. 13 | type ErrCode uint32 14 | 15 | const ( 16 | ErrCodeNo ErrCode = 0x0 17 | ErrCodeProtocol ErrCode = 0x1 18 | ErrCodeInternal ErrCode = 0x2 19 | ErrCodeFlowControl ErrCode = 0x3 20 | ErrCodeSettingsTimeout ErrCode = 0x4 21 | ErrCodeStreamClosed ErrCode = 0x5 22 | ErrCodeFrameSize ErrCode = 0x6 23 | ErrCodeRefusedStream ErrCode = 0x7 24 | ErrCodeCancel ErrCode = 0x8 25 | ErrCodeCompression ErrCode = 0x9 26 | ErrCodeConnect ErrCode = 0xa 27 | ErrCodeEnhanceYourCalm ErrCode = 0xb 28 | ErrCodeInadequateSecurity ErrCode = 0xc 29 | ErrCodeHTTP11Required ErrCode = 0xd 30 | ) 31 | 32 | var errCodeName = map[ErrCode]string{ 33 | ErrCodeNo: "NO_ERROR", 34 | ErrCodeProtocol: "PROTOCOL_ERROR", 35 | ErrCodeInternal: "INTERNAL_ERROR", 36 | ErrCodeFlowControl: "FLOW_CONTROL_ERROR", 37 | ErrCodeSettingsTimeout: "SETTINGS_TIMEOUT", 38 | ErrCodeStreamClosed: "STREAM_CLOSED", 39 | ErrCodeFrameSize: "FRAME_SIZE_ERROR", 40 | ErrCodeRefusedStream: "REFUSED_STREAM", 41 | ErrCodeCancel: "CANCEL", 42 | ErrCodeCompression: "COMPRESSION_ERROR", 43 | ErrCodeConnect: "CONNECT_ERROR", 44 | ErrCodeEnhanceYourCalm: "ENHANCE_YOUR_CALM", 45 | ErrCodeInadequateSecurity: "INADEQUATE_SECURITY", 46 | ErrCodeHTTP11Required: "HTTP_1_1_REQUIRED", 47 | } 48 | 49 | func (e ErrCode) String() string { 50 | if s, ok := errCodeName[e]; ok { 51 | return s 52 | } 53 | return fmt.Sprintf("unknown error code 0x%x", uint32(e)) 54 | } 55 | 56 | // ConnectionError is an error that results in the termination of the 57 | // entire connection. 58 | type ConnectionError ErrCode 59 | 60 | func (e ConnectionError) Error() string { return fmt.Sprintf("connection error: %s", ErrCode(e)) } 61 | 62 | // StreamError is an error that only affects one stream within an 63 | // HTTP/2 connection. 64 | type StreamError struct { 65 | StreamID uint32 66 | Code ErrCode 67 | Cause error // optional additional detail 68 | } 69 | 70 | func streamError(id uint32, code ErrCode) StreamError { 71 | return StreamError{StreamID: id, Code: code} 72 | } 73 | 74 | func (e StreamError) Error() string { 75 | if e.Cause != nil { 76 | return fmt.Sprintf("stream error: stream ID %d; %v; %v", e.StreamID, e.Code, e.Cause) 77 | } 78 | return fmt.Sprintf("stream error: stream ID %d; %v", e.StreamID, e.Code) 79 | } 80 | 81 | // 6.9.1 The Flow Control Window 82 | // "If a sender receives a WINDOW_UPDATE that causes a flow control 83 | // window to exceed this maximum it MUST terminate either the stream 84 | // or the connection, as appropriate. For streams, [...]; for the 85 | // connection, a GOAWAY frame with a FLOW_CONTROL_ERROR code." 86 | type goAwayFlowError struct{} 87 | 88 | func (goAwayFlowError) Error() string { return "connection exceeded flow control window size" } 89 | 90 | // connError represents an HTTP/2 ConnectionError error code, along 91 | // with a string (for debugging) explaining why. 92 | // 93 | // Errors of this type are only returned by the frame parser functions 94 | // and converted into ConnectionError(Code), after stashing away 95 | // the Reason into the Framer's errDetail field, accessible via 96 | // the (*Framer).ErrorDetail method. 97 | type connError struct { 98 | Code ErrCode // the ConnectionError error code 99 | Reason string // additional reason 100 | } 101 | 102 | func (e connError) Error() string { 103 | return fmt.Sprintf("http2: connection error: %v: %v", e.Code, e.Reason) 104 | } 105 | 106 | type pseudoHeaderError string 107 | 108 | func (e pseudoHeaderError) Error() string { 109 | return fmt.Sprintf("invalid pseudo-header %q", string(e)) 110 | } 111 | 112 | type duplicatePseudoHeaderError string 113 | 114 | func (e duplicatePseudoHeaderError) Error() string { 115 | return fmt.Sprintf("duplicate pseudo-header %q", string(e)) 116 | } 117 | 118 | type headerFieldNameError string 119 | 120 | func (e headerFieldNameError) Error() string { 121 | return fmt.Sprintf("invalid header field name %q", string(e)) 122 | } 123 | 124 | type headerFieldValueError string 125 | 126 | func (e headerFieldValueError) Error() string { 127 | return fmt.Sprintf("invalid header field value %q", string(e)) 128 | } 129 | 130 | var ( 131 | errMixPseudoHeaderTypes = errors.New("mix of request and response pseudo headers") 132 | errPseudoAfterRegular = errors.New("pseudo header field after regular") 133 | ) 134 | -------------------------------------------------------------------------------- /http2/errors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import "testing" 8 | 9 | func TestErrCodeString(t *testing.T) { 10 | tests := []struct { 11 | err ErrCode 12 | want string 13 | }{ 14 | {ErrCodeProtocol, "PROTOCOL_ERROR"}, 15 | {0xd, "HTTP_1_1_REQUIRED"}, 16 | {0xf, "unknown error code 0xf"}, 17 | } 18 | for i, tt := range tests { 19 | got := tt.err.String() 20 | if got != tt.want { 21 | t.Errorf("%d. Error = %q; want %q", i, got, tt.want) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /http2/flow.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Flow control 6 | 7 | package http2 8 | 9 | // flow is the flow control window's size. 10 | type flow struct { 11 | _ incomparable 12 | 13 | // n is the number of DATA bytes we're allowed to send. 14 | // A flow is kept both on a conn and a per-stream. 15 | n int32 16 | 17 | // conn points to the shared connection-level flow that is 18 | // shared by all streams on that conn. It is nil for the flow 19 | // that's on the conn directly. 20 | conn *flow 21 | } 22 | 23 | func (f *flow) setConnFlow(cf *flow) { f.conn = cf } 24 | 25 | func (f *flow) available() int32 { 26 | n := f.n 27 | if f.conn != nil && f.conn.n < n { 28 | n = f.conn.n 29 | } 30 | return n 31 | } 32 | 33 | func (f *flow) take(n int32) { 34 | if n > f.available() { 35 | panic("internal error: took too much") 36 | } 37 | f.n -= n 38 | if f.conn != nil { 39 | f.conn.n -= n 40 | } 41 | } 42 | 43 | // add adds n bytes (positive or negative) to the flow control window. 44 | // It returns false if the sum would exceed 2^31-1. 45 | func (f *flow) add(n int32) bool { 46 | sum := f.n + n 47 | if (sum > n) == (f.n > 0) { 48 | f.n = sum 49 | return true 50 | } 51 | return false 52 | } 53 | -------------------------------------------------------------------------------- /http2/flow_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import "testing" 8 | 9 | func TestFlow(t *testing.T) { 10 | var st flow 11 | var conn flow 12 | st.add(3) 13 | conn.add(2) 14 | 15 | if got, want := st.available(), int32(3); got != want { 16 | t.Errorf("available = %d; want %d", got, want) 17 | } 18 | st.setConnFlow(&conn) 19 | if got, want := st.available(), int32(2); got != want { 20 | t.Errorf("after parent setup, available = %d; want %d", got, want) 21 | } 22 | 23 | st.take(2) 24 | if got, want := conn.available(), int32(0); got != want { 25 | t.Errorf("after taking 2, conn = %d; want %d", got, want) 26 | } 27 | if got, want := st.available(), int32(0); got != want { 28 | t.Errorf("after taking 2, stream = %d; want %d", got, want) 29 | } 30 | } 31 | 32 | func TestFlowAdd(t *testing.T) { 33 | var f flow 34 | if !f.add(1) { 35 | t.Fatal("failed to add 1") 36 | } 37 | if !f.add(-1) { 38 | t.Fatal("failed to add -1") 39 | } 40 | if got, want := f.available(), int32(0); got != want { 41 | t.Fatalf("size = %d; want %d", got, want) 42 | } 43 | if !f.add(1<<31 - 1) { 44 | t.Fatal("failed to add 2^31-1") 45 | } 46 | if got, want := f.available(), int32(1<<31-1); got != want { 47 | t.Fatalf("size = %d; want %d", got, want) 48 | } 49 | if f.add(1) { 50 | t.Fatal("adding 1 to max shouldn't be allowed") 51 | } 52 | } 53 | 54 | func TestFlowAddOverflow(t *testing.T) { 55 | var f flow 56 | if !f.add(0) { 57 | t.Fatal("failed to add 0") 58 | } 59 | if !f.add(-1) { 60 | t.Fatal("failed to add -1") 61 | } 62 | if !f.add(0) { 63 | t.Fatal("failed to add 0") 64 | } 65 | if !f.add(1) { 66 | t.Fatal("failed to add 1") 67 | } 68 | if !f.add(1) { 69 | t.Fatal("failed to add 1") 70 | } 71 | if !f.add(0) { 72 | t.Fatal("failed to add 0") 73 | } 74 | if !f.add(-3) { 75 | t.Fatal("failed to add -3") 76 | } 77 | if got, want := f.available(), int32(-2); got != want { 78 | t.Fatalf("size = %d; want %d", got, want) 79 | } 80 | if !f.add(1<<31 - 1) { 81 | t.Fatal("failed to add 2^31-1") 82 | } 83 | if got, want := f.available(), int32(1+-3+(1<<31-1)); got != want { 84 | t.Fatalf("size = %d; want %d", got, want) 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /http2/go111.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build go1.11 6 | // +build go1.11 7 | 8 | package http2 9 | 10 | import ( 11 | "net/textproto" 12 | 13 | "github.com/bogdanfinn/fhttp/httptrace" 14 | ) 15 | 16 | func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool { 17 | return trace != nil && trace.WroteHeaderField != nil 18 | } 19 | 20 | func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) { 21 | if trace != nil && trace.WroteHeaderField != nil { 22 | trace.WroteHeaderField(k, []string{v}) 23 | } 24 | } 25 | 26 | func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error { 27 | if trace != nil { 28 | return trace.Got1xxResponse 29 | } 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /http2/gotrack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Defensive debug-only utility to track that functions run on the 6 | // goroutine that they're supposed to. 7 | 8 | package http2 9 | 10 | import ( 11 | "bytes" 12 | "errors" 13 | "fmt" 14 | "os" 15 | "runtime" 16 | "strconv" 17 | "sync" 18 | ) 19 | 20 | var DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1" 21 | 22 | type goroutineLock uint64 23 | 24 | func newGoroutineLock() goroutineLock { 25 | if !DebugGoroutines { 26 | return 0 27 | } 28 | return goroutineLock(curGoroutineID()) 29 | } 30 | 31 | func (g goroutineLock) check() { 32 | if !DebugGoroutines { 33 | return 34 | } 35 | if curGoroutineID() != uint64(g) { 36 | panic("running on the wrong goroutine") 37 | } 38 | } 39 | 40 | func (g goroutineLock) checkNotOn() { 41 | if !DebugGoroutines { 42 | return 43 | } 44 | if curGoroutineID() == uint64(g) { 45 | panic("running on the wrong goroutine") 46 | } 47 | } 48 | 49 | var goroutineSpace = []byte("goroutine ") 50 | 51 | func curGoroutineID() uint64 { 52 | bp := littleBuf.Get().(*[]byte) 53 | defer littleBuf.Put(bp) 54 | b := *bp 55 | b = b[:runtime.Stack(b, false)] 56 | // Parse the 4707 out of "goroutine 4707 [" 57 | b = bytes.TrimPrefix(b, goroutineSpace) 58 | i := bytes.IndexByte(b, ' ') 59 | if i < 0 { 60 | panic(fmt.Sprintf("No space found in %q", b)) 61 | } 62 | b = b[:i] 63 | n, err := parseUintBytes(b, 10, 64) 64 | if err != nil { 65 | panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) 66 | } 67 | return n 68 | } 69 | 70 | var littleBuf = sync.Pool{ 71 | New: func() interface{} { 72 | buf := make([]byte, 64) 73 | return &buf 74 | }, 75 | } 76 | 77 | // parseUintBytes is like strconv.ParseUint, but using a []byte. 78 | func parseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) { 79 | var cutoff, maxVal uint64 80 | 81 | if bitSize == 0 { 82 | bitSize = int(strconv.IntSize) 83 | } 84 | 85 | s0 := s 86 | switch { 87 | case len(s) < 1: 88 | err = strconv.ErrSyntax 89 | goto Error 90 | 91 | case 2 <= base && base <= 36: 92 | // valid base; nothing to do 93 | 94 | case base == 0: 95 | // Look for octal, hex prefix. 96 | switch { 97 | case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'): 98 | base = 16 99 | s = s[2:] 100 | if len(s) < 1 { 101 | err = strconv.ErrSyntax 102 | goto Error 103 | } 104 | case s[0] == '0': 105 | base = 8 106 | default: 107 | base = 10 108 | } 109 | 110 | default: 111 | err = errors.New("invalid base " + strconv.Itoa(base)) 112 | goto Error 113 | } 114 | 115 | n = 0 116 | cutoff = cutoff64(base) 117 | maxVal = 1<= base { 135 | n = 0 136 | err = strconv.ErrSyntax 137 | goto Error 138 | } 139 | 140 | if n >= cutoff { 141 | // n*base overflows 142 | n = 1<<64 - 1 143 | err = strconv.ErrRange 144 | goto Error 145 | } 146 | n *= uint64(base) 147 | 148 | n1 := n + uint64(v) 149 | if n1 < n || n1 > maxVal { 150 | // n+v overflows 151 | n = 1<<64 - 1 152 | err = strconv.ErrRange 153 | goto Error 154 | } 155 | n = n1 156 | } 157 | 158 | return n, nil 159 | 160 | Error: 161 | return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err} 162 | } 163 | 164 | // Return the first number n such that n*base >= 1<<64. 165 | func cutoff64(base int) uint64 { 166 | if base < 2 { 167 | return 0 168 | } 169 | return (1<<64-1)/uint64(base) + 1 170 | } 171 | -------------------------------------------------------------------------------- /http2/gotrack_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestGoroutineLock(t *testing.T) { 14 | oldDebug := DebugGoroutines 15 | DebugGoroutines = true 16 | defer func() { DebugGoroutines = oldDebug }() 17 | 18 | g := newGoroutineLock() 19 | g.check() 20 | 21 | sawPanic := make(chan interface{}) 22 | go func() { 23 | defer func() { sawPanic <- recover() }() 24 | g.check() // should panic 25 | }() 26 | e := <-sawPanic 27 | if e == nil { 28 | t.Fatal("did not see panic from check in other goroutine") 29 | } 30 | if !strings.Contains(fmt.Sprint(e), "wrong goroutine") { 31 | t.Errorf("expected on see panic about running on the wrong goroutine; got %v", e) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /http2/h2c/h2c_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package h2c 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "fmt" 11 | "log" 12 | "testing" 13 | 14 | http "github.com/bogdanfinn/fhttp" 15 | "github.com/bogdanfinn/fhttp/http2" 16 | ) 17 | 18 | func TestSettingsAckSwallowWriter(t *testing.T) { 19 | var buf bytes.Buffer 20 | swallower := newSettingsAckSwallowWriter(bufio.NewWriter(&buf)) 21 | fw := http2.NewFramer(swallower, nil) 22 | fw.WriteSettings(http2.Setting{http2.SettingMaxFrameSize, 2}) 23 | fw.WriteSettingsAck() 24 | fw.WriteData(1, true, []byte{}) 25 | swallower.Flush() 26 | 27 | fr := http2.NewFramer(nil, bufio.NewReader(&buf)) 28 | 29 | f, err := fr.ReadFrame() 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | if f.Header().Type != http2.FrameSettings { 34 | t.Fatalf("Expected first frame to be SETTINGS. Got: %v", f.Header().Type) 35 | } 36 | 37 | f, err = fr.ReadFrame() 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | if f.Header().Type != http2.FrameData { 42 | t.Fatalf("Expected first frame to be DATA. Got: %v", f.Header().Type) 43 | } 44 | } 45 | 46 | func ExampleNewHandler() { 47 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 48 | fmt.Fprint(w, "Hello world") 49 | }) 50 | h2s := &http2.Server{ 51 | // ... 52 | } 53 | h1s := &http.Server{ 54 | Addr: ":8080", 55 | Handler: NewHandler(handler, h2s), 56 | } 57 | log.Fatal(h1s.ListenAndServe()) 58 | } 59 | -------------------------------------------------------------------------------- /http2/h2demo/.gitignore: -------------------------------------------------------------------------------- 1 | h2demo 2 | h2demo.linux 3 | client-id.dat 4 | client-secret.dat 5 | token.dat 6 | ca-certificates.crt 7 | -------------------------------------------------------------------------------- /http2/h2demo/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Go Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | FROM golang:1.13 AS build 6 | LABEL maintainer "golang-dev@googlegroups.com" 7 | 8 | ENV GO111MODULE=on 9 | 10 | RUN mkdir /gocache 11 | ENV GOCACHE /gocache 12 | 13 | COPY go.mod /go/src/golang.org/x/build/go.mod 14 | COPY go.sum /go/src/golang.org/x/build/go.sum 15 | 16 | # Optimization for iterative docker build speed, not necessary for correctness: 17 | # TODO: write a tool to make writing Go module-friendly Dockerfiles easier. 18 | RUN go install cloud.google.com/go/storage 19 | RUN go install golang.org/x/build/autocertcache 20 | RUN go install go4.org/syncutil/singleflight 21 | RUN go install golang.org/x/crypto/acme/autocert 22 | 23 | COPY . /go/src/golang.org/x/net 24 | 25 | # Install binary to /go/bin: 26 | WORKDIR /go/src/golang.org/x/net/http2/h2demo 27 | RUN go install -tags "netgo" 28 | 29 | 30 | FROM debian:stretch 31 | RUN apt-get update && apt-get install -y --no-install-recommends \ 32 | ca-certificates netbase \ 33 | && rm -rf /var/lib/apt/lists/* 34 | 35 | COPY --from=build /go/bin/h2demo /h2demo 36 | 37 | -------------------------------------------------------------------------------- /http2/h2demo/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Go Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | MUTABLE_VERSION ?= latest 6 | VERSION ?= $(shell git rev-parse --short HEAD) 7 | IMAGE_NAME=h2demo 8 | 9 | docker: Dockerfile 10 | go install golang.org/x/build/cmd/xb 11 | xb docker build -t golang/$(IMAGE_NAME) -f Dockerfile ../.. 12 | 13 | push-staging: docker 14 | go install golang.org/x/build/cmd/xb 15 | xb --staging docker tag golang/$(IMAGE_NAME) REPO/$(IMAGE_NAME):$(VERSION) 16 | xb --staging docker tag golang/$(IMAGE_NAME) REPO/$(IMAGE_NAME):latest 17 | xb --staging docker push REPO/$(IMAGE_NAME):$(VERSION) 18 | xb --staging docker push REPO/$(IMAGE_NAME):latest 19 | 20 | push-prod: docker 21 | go install golang.org/x/build/cmd/xb 22 | xb --prod docker tag golang/$(IMAGE_NAME) REPO/$(IMAGE_NAME):$(VERSION) 23 | xb --prod docker tag golang/$(IMAGE_NAME) REPO/$(IMAGE_NAME):latest 24 | xb --prod docker push REPO/$(IMAGE_NAME):$(VERSION) 25 | xb --prod docker push REPO/$(IMAGE_NAME):latest 26 | 27 | # TODO(bradfitz): add REPO subsitution in xb for the kubectl command. 28 | deploy-prod: push-prod 29 | xb --prod kubectl set image deployment/h2demo-deployment h2demo=gcr.io/symbolic-datum-552/h2demo:$(VERSION) 30 | deploy-staging: push-staging 31 | xb --staging kubectl set image deployment/h2demo-deployment h2demo=gcr.io/go-dashboard-dev/h2demo:$(VERSION) 32 | 33 | FORCE: 34 | -------------------------------------------------------------------------------- /http2/h2demo/README: -------------------------------------------------------------------------------- 1 | This is a demo webserver that shows off Go's HTTP/2 support. 2 | 3 | It runs at https://http2.golang.org/ so people can hit our 4 | implementation with their HTTP/2 clients, etc. We intentionally do not 5 | run it behind any other HTTP implementation so clients (including 6 | people demonstrating attacks, etc) can hit our server directly. It 7 | just runs behind a TCP load balancer. 8 | 9 | When running locally, you'll need to click through TLS cert warnings. 10 | The dev cert was initially made like: 11 | 12 | Make CA: 13 | $ openssl genrsa -out rootCA.key 2048 14 | $ openssl req -x509 -new -nodes -key rootCA.key -days 1024 -out rootCA.pem 15 | 16 | Make cert: 17 | $ openssl genrsa -out server.key 2048 18 | $ openssl req -new -key server.key -out server.csr 19 | $ openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 500 20 | -------------------------------------------------------------------------------- /http2/h2demo/deployment-prod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: h2demo-deployment 5 | spec: 6 | replicas: 1 7 | template: 8 | metadata: 9 | labels: 10 | app: h2demo 11 | annotations: 12 | container.seccomp.security.alpha.kubernetes.io/h2demo: docker/default 13 | container.apparmor.security.beta.kubernetes.io/h2demo: runtime/default 14 | spec: 15 | containers: 16 | - name: h2demo 17 | image: gcr.io/symbolic-datum-552/h2demo:latest 18 | imagePullPolicy: Always 19 | command: ["/h2demo", "-prod"] 20 | ports: 21 | - containerPort: 80 22 | - containerPort: 443 23 | resources: 24 | requests: 25 | cpu: "1" 26 | memory: "1Gi" 27 | limits: 28 | memory: "2Gi" 29 | -------------------------------------------------------------------------------- /http2/h2demo/go.mod: -------------------------------------------------------------------------------- 1 | module golang.org/x/net/http2/h2demo 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.3 6 | 7 | require ( 8 | cloud.google.com/go v0.110.2 9 | go4.org v0.0.0-20190218023631-ce4c26f7be8e 10 | golang.org/x/build v0.0.0-20190509182522-45de920fc22c 11 | golang.org/x/crypto v0.29.0 12 | golang.org/x/net v0.31.0 13 | ) 14 | 15 | replace golang.org/x/net => ../.. 16 | -------------------------------------------------------------------------------- /http2/h2demo/rootCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAt5fAjp4fTcekWUTfzsp0kyih1OYbsGL0KX1eRbSSR8Od0+9Q 3 | 62Hyny+GFwMTb4A/KU8mssoHvcceSAAbwfbxFK/+s51TobqUnORZrOoTZjkUygby 4 | XDSK99YBbcR1Pip8vwMTm4XKuLtCigeBBdjjAQdgUO28LENGlsMnmeYkJfODVGnV 5 | mr5Ltb9ANA8IKyTfsnHJ4iOCS/PlPbUj2q7YnoVLposUBMlgUb/CykX3mOoLb4yJ 6 | JQyA/iST6ZxiIEj36D4yWZ5lg7YJl+UiiBQHGCnPdGyipqV06ex0heYWcaiW8LWZ 7 | SUQ93jQ+WVCH8hT7DQO1dmsvUmXlq/JeAlwQ/QIDAQABAoIBAFFHV7JMAqPWnMYA 8 | nezY6J81v9+XN+7xABNWM2Q8uv4WdksbigGLTXR3/680Z2hXqJ7LMeC5XJACFT/e 9 | /Gr0vmpgOCygnCPfjGehGKpavtfksXV3edikUlnCXsOP1C//c1bFL+sMYmFCVgTx 10 | qYdDK8yKzXNGrKYT6q5YG7IglyRNV1rsQa8lM/5taFYiD1Ck/3tQi3YIq8Lcuser 11 | hrxsMABcQ6mi+EIvG6Xr4mfJug0dGJMHG4RG1UGFQn6RXrQq2+q53fC8ZbVUSi0j 12 | NQ918aKFzktwv+DouKU0ME4I9toks03gM860bAL7zCbKGmwR3hfgX/TqzVCWpG9E 13 | LDVfvekCgYEA8fk9N53jbBRmULUGEf4qWypcLGiZnNU0OeXWpbPV9aa3H0VDytA7 14 | 8fCN2dPAVDPqlthMDdVe983NCNwp2Yo8ZimDgowyIAKhdC25s1kejuaiH9OAPj3c 15 | 0f8KbriYX4n8zNHxFwK6Ae3pQ6EqOLJVCUsziUaZX9nyKY5aZlyX6xcCgYEAwjws 16 | K62PjC64U5wYddNLp+kNdJ4edx+a7qBb3mEgPvSFT2RO3/xafJyG8kQB30Mfstjd 17 | bRxyUV6N0vtX1zA7VQtRUAvfGCecpMo+VQZzcHXKzoRTnQ7eZg4Lmj5fQ9tOAKAo 18 | QCVBoSW/DI4PZL26CAMDcAba4Pa22ooLapoRIQsCgYA6pIfkkbxLNkpxpt2YwLtt 19 | Kr/590O7UaR9n6k8sW/aQBRDXNsILR1KDl2ifAIxpf9lnXgZJiwE7HiTfCAcW7c1 20 | nzwDCI0hWuHcMTS/NYsFYPnLsstyyjVZI3FY0h4DkYKV9Q9z3zJLQ2hz/nwoD3gy 21 | b2pHC7giFcTts1VPV4Nt8wKBgHeFn4ihHJweg76vZz3Z78w7VNRWGFklUalVdDK7 22 | gaQ7w2y/ROn/146mo0OhJaXFIFRlrpvdzVrU3GDf2YXJYDlM5ZRkObwbZADjksev 23 | WInzcgDy3KDg7WnPasRXbTfMU4t/AkW2p1QKbi3DnSVYuokDkbH2Beo45vxDxhKr 24 | C69RAoGBAIyo3+OJenoZmoNzNJl2WPW5MeBUzSh8T/bgyjFTdqFHF5WiYRD/lfHj 25 | x9Glyw2nutuT4hlOqHvKhgTYdDMsF2oQ72fe3v8Q5FU7FuKndNPEAyvKNXZaShVA 26 | hnlhv5DjXKb0wFWnt5PCCiQLtzG0yyHaITrrEme7FikkIcTxaX/Y 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /http2/h2demo/rootCA.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEWjCCA0KgAwIBAgIJALfRlWsI8YQHMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNV 3 | BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEUMBIG 4 | A1UEChMLQnJhZGZpdHppbmMxEjAQBgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3 5 | DQEJARYOYnJhZEBkYW5nYS5jb20wHhcNMTQwNzE1MjA0NjA1WhcNMTcwNTA0MjA0 6 | NjA1WjB7MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBG 7 | cmFuY2lzY28xFDASBgNVBAoTC0JyYWRmaXR6aW5jMRIwEAYDVQQDEwlsb2NhbGhv 8 | c3QxHTAbBgkqhkiG9w0BCQEWDmJyYWRAZGFuZ2EuY29tMIIBIjANBgkqhkiG9w0B 9 | AQEFAAOCAQ8AMIIBCgKCAQEAt5fAjp4fTcekWUTfzsp0kyih1OYbsGL0KX1eRbSS 10 | R8Od0+9Q62Hyny+GFwMTb4A/KU8mssoHvcceSAAbwfbxFK/+s51TobqUnORZrOoT 11 | ZjkUygbyXDSK99YBbcR1Pip8vwMTm4XKuLtCigeBBdjjAQdgUO28LENGlsMnmeYk 12 | JfODVGnVmr5Ltb9ANA8IKyTfsnHJ4iOCS/PlPbUj2q7YnoVLposUBMlgUb/CykX3 13 | mOoLb4yJJQyA/iST6ZxiIEj36D4yWZ5lg7YJl+UiiBQHGCnPdGyipqV06ex0heYW 14 | caiW8LWZSUQ93jQ+WVCH8hT7DQO1dmsvUmXlq/JeAlwQ/QIDAQABo4HgMIHdMB0G 15 | A1UdDgQWBBRcAROthS4P4U7vTfjByC569R7E6DCBrQYDVR0jBIGlMIGigBRcAROt 16 | hS4P4U7vTfjByC569R7E6KF/pH0wezELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNB 17 | MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRQwEgYDVQQKEwtCcmFkZml0emluYzES 18 | MBAGA1UEAxMJbG9jYWxob3N0MR0wGwYJKoZIhvcNAQkBFg5icmFkQGRhbmdhLmNv 19 | bYIJALfRlWsI8YQHMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAG6h 20 | U9f9sNH0/6oBbGGy2EVU0UgITUQIrFWo9rFkrW5k/XkDjQm+3lzjT0iGR4IxE/Ao 21 | eU6sQhua7wrWeFEn47GL98lnCsJdD7oZNhFmQ95Tb/LnDUjs5Yj9brP0NWzXfYU4 22 | UK2ZnINJRcJpB8iRCaCxE8DdcUF0XqIEq6pA272snoLmiXLMvNl3kYEdm+je6voD 23 | 58SNVEUsztzQyXmJEhCpwVI0A6QCjzXj+qvpmw3ZZHi8JwXei8ZZBLTSFBki8Z7n 24 | sH9BBH38/SzUmAN4QHSPy1gjqm00OAE8NaYDkh/bzE4d7mLGGMWp/WE3KPSu82HF 25 | kPe6XoSbiLm/kxk32T0= 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /http2/h2demo/rootCA.srl: -------------------------------------------------------------------------------- 1 | E2CE26BF3285059C 2 | -------------------------------------------------------------------------------- /http2/h2demo/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDPjCCAiYCCQDizia/MoUFnDANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xFDASBgNVBAoT 4 | C0JyYWRmaXR6aW5jMRIwEAYDVQQDEwlsb2NhbGhvc3QxHTAbBgkqhkiG9w0BCQEW 5 | DmJyYWRAZGFuZ2EuY29tMB4XDTE0MDcxNTIwNTAyN1oXDTE1MTEyNzIwNTAyN1ow 6 | RzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQHEwJTRjEeMBwGA1UE 7 | ChMVYnJhZGZpdHogaHR0cDIgc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 8 | MIIBCgKCAQEAs1Y9CyLFrdL8VQWN1WaifDqaZFnoqjHhCMlc1TfG2zA+InDifx2l 9 | gZD3o8FeNnAcfM2sPlk3+ZleOYw9P/CklFVDlvqmpCv9ss/BEp/dDaWvy1LmJ4c2 10 | dbQJfmTxn7CV1H3TsVJvKdwFmdoABb41NoBp6+NNO7OtDyhbIMiCI0pL3Nefb3HL 11 | A7hIMo3DYbORTtJLTIH9W8YKrEWL0lwHLrYFx/UdutZnv+HjdmO6vCN4na55mjws 12 | /vjKQUmc7xeY7Xe20xDEG2oDKVkL2eD7FfyrYMS3rO1ExP2KSqlXYG/1S9I/fz88 13 | F0GK7HX55b5WjZCl2J3ERVdnv/0MQv+sYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IB 14 | AQC0zL+n/YpRZOdulSu9tS8FxrstXqGWoxfe+vIUgqfMZ5+0MkjJ/vW0FqlLDl2R 15 | rn4XaR3e7FmWkwdDVbq/UB6lPmoAaFkCgh9/5oapMaclNVNnfF3fjCJfRr+qj/iD 16 | EmJStTIN0ZuUjAlpiACmfnpEU55PafT5Zx+i1yE4FGjw8bJpFoyD4Hnm54nGjX19 17 | KeCuvcYFUPnBm3lcL0FalF2AjqV02WTHYNQk7YF/oeO7NKBoEgvGvKG3x+xaOeBI 18 | dwvdq175ZsGul30h+QjrRlXhH/twcuaT3GSdoysDl9cCYE8f1Mk8PD6gan3uBCJU 19 | 90p6/CbU71bGbfpM2PHot2fm 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /http2/h2demo/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAs1Y9CyLFrdL8VQWN1WaifDqaZFnoqjHhCMlc1TfG2zA+InDi 3 | fx2lgZD3o8FeNnAcfM2sPlk3+ZleOYw9P/CklFVDlvqmpCv9ss/BEp/dDaWvy1Lm 4 | J4c2dbQJfmTxn7CV1H3TsVJvKdwFmdoABb41NoBp6+NNO7OtDyhbIMiCI0pL3Nef 5 | b3HLA7hIMo3DYbORTtJLTIH9W8YKrEWL0lwHLrYFx/UdutZnv+HjdmO6vCN4na55 6 | mjws/vjKQUmc7xeY7Xe20xDEG2oDKVkL2eD7FfyrYMS3rO1ExP2KSqlXYG/1S9I/ 7 | fz88F0GK7HX55b5WjZCl2J3ERVdnv/0MQv+sYQIDAQABAoIBADQ2spUwbY+bcz4p 8 | 3M66ECrNQTBggP40gYl2XyHxGGOu2xhZ94f9ELf1hjRWU2DUKWco1rJcdZClV6q3 9 | qwmXvcM2Q/SMS8JW0ImkNVl/0/NqPxGatEnj8zY30d/L8hGFb0orzFu/XYA5gCP4 10 | NbN2WrXgk3ZLeqwcNxHHtSiJWGJ/fPyeDWAu/apy75u9Xf2GlzBZmV6HYD9EfK80 11 | LTlI60f5FO487CrJnboL7ovPJrIHn+k05xRQqwma4orpz932rTXnTjs9Lg6KtbQN 12 | a7PrqfAntIISgr11a66Mng3IYH1lYqJsWJJwX/xHT4WLEy0EH4/0+PfYemJekz2+ 13 | Co62drECgYEA6O9zVJZXrLSDsIi54cfxA7nEZWm5CAtkYWeAHa4EJ+IlZ7gIf9sL 14 | W8oFcEfFGpvwVqWZ+AsQ70dsjXAv3zXaG0tmg9FtqWp7pzRSMPidifZcQwWkKeTO 15 | gJnFmnVyed8h6GfjTEu4gxo1/S5U0V+mYSha01z5NTnN6ltKx1Or3b0CgYEAxRgm 16 | S30nZxnyg/V7ys61AZhst1DG2tkZXEMcA7dYhabMoXPJAP/EfhlWwpWYYUs/u0gS 17 | Wwmf5IivX5TlYScgmkvb/NYz0u4ZmOXkLTnLPtdKKFXhjXJcHjUP67jYmOxNlJLp 18 | V4vLRnFxTpffAV+OszzRxsXX6fvruwZBANYJeXUCgYBVouLFsFgfWGYp2rpr9XP4 19 | KK25kvrBqF6JKOIDB1zjxNJ3pUMKrl8oqccCFoCyXa4oTM2kUX0yWxHfleUjrMq4 20 | yimwQKiOZmV7fVLSSjSw6e/VfBd0h3gb82ygcplZkN0IclkwTY5SNKqwn/3y07V5 21 | drqdhkrgdJXtmQ6O5YYECQKBgATERcDToQ1USlI4sKrB/wyv1AlG8dg/IebiVJ4e 22 | ZAyvcQmClFzq0qS+FiQUnB/WQw9TeeYrwGs1hxBHuJh16srwhLyDrbMvQP06qh8R 23 | 48F8UXXSRec22dV9MQphaROhu2qZdv1AC0WD3tqov6L33aqmEOi+xi8JgbT/PLk5 24 | c/c1AoGBAI1A/02ryksW6/wc7/6SP2M2rTy4m1sD/GnrTc67EHnRcVBdKO6qH2RY 25 | nqC8YcveC2ZghgPTDsA3VGuzuBXpwY6wTyV99q6jxQJ6/xcrD9/NUG6Uwv/xfCxl 26 | IJLeBYEqQundSSny3VtaAUK8Ul1nxpTvVRNwtcyWTo8RHAAyNPWd 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /http2/h2demo/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: h2demo 5 | spec: 6 | externalTrafficPolicy: Local 7 | ports: 8 | - port: 80 9 | targetPort: 80 10 | name: http 11 | - port: 443 12 | targetPort: 443 13 | name: https 14 | selector: 15 | app: h2demo 16 | type: LoadBalancer 17 | loadBalancerIP: 130.211.116.44 18 | -------------------------------------------------------------------------------- /http2/h2i/README.md: -------------------------------------------------------------------------------- 1 | # h2i 2 | 3 | **h2i** is an interactive HTTP/2 ("h2") console debugger. Miss the good ol' 4 | days of telnetting to your HTTP/1.n servers? We're bringing you 5 | back. 6 | 7 | Features: 8 | - send raw HTTP/2 frames 9 | - PING 10 | - SETTINGS 11 | - HEADERS 12 | - etc 13 | - type in HTTP/1.n and have it auto-HPACK/frame-ify it for HTTP/2 14 | - pretty print all received HTTP/2 frames from the peer (including HPACK decoding) 15 | - tab completion of commands, options 16 | 17 | Not yet features, but soon: 18 | - unnecessary CONTINUATION frames on short boundaries, to test peer implementations 19 | - request bodies (DATA frames) 20 | - send invalid frames for testing server implementations (supported by underlying Framer) 21 | 22 | Later: 23 | - act like a server 24 | 25 | ## Installation 26 | 27 | ``` 28 | $ go get golang.org/x/net/http2/h2i 29 | $ h2i 30 | ``` 31 | 32 | ## Demo 33 | 34 | ``` 35 | $ h2i 36 | Usage: h2i 37 | 38 | -insecure 39 | Whether to skip TLS cert validation 40 | -nextproto string 41 | Comma-separated list of NPN/ALPN protocol names to negotiate. (default "h2,h2-14") 42 | 43 | $ h2i google.com 44 | Connecting to google.com:443 ... 45 | Connected to 74.125.224.41:443 46 | Negotiated protocol "h2-14" 47 | [FrameHeader SETTINGS len=18] 48 | [MAX_CONCURRENT_STREAMS = 100] 49 | [INITIAL_WINDOW_SIZE = 1048576] 50 | [MAX_FRAME_SIZE = 16384] 51 | [FrameHeader WINDOW_UPDATE len=4] 52 | Window-Increment = 983041 53 | 54 | h2i> PING h2iSayHI 55 | [FrameHeader PING flags=ACK len=8] 56 | Data = "h2iSayHI" 57 | h2i> headers 58 | (as HTTP/1.1)> GET / HTTP/1.1 59 | (as HTTP/1.1)> Host: ip.appspot.com 60 | (as HTTP/1.1)> User-Agent: h2i/brad-n-blake 61 | (as HTTP/1.1)> 62 | Opening Stream-ID 1: 63 | :authority = ip.appspot.com 64 | :method = GET 65 | :path = / 66 | :scheme = https 67 | user-agent = h2i/brad-n-blake 68 | [FrameHeader HEADERS flags=END_HEADERS stream=1 len=77] 69 | :status = "200" 70 | alternate-protocol = "443:quic,p=1" 71 | content-length = "15" 72 | content-type = "text/html" 73 | date = "Fri, 01 May 2015 23:06:56 GMT" 74 | server = "Google Frontend" 75 | [FrameHeader DATA flags=END_STREAM stream=1 len=15] 76 | "173.164.155.78\n" 77 | [FrameHeader PING len=8] 78 | Data = "\x00\x00\x00\x00\x00\x00\x00\x00" 79 | h2i> ping 80 | [FrameHeader PING flags=ACK len=8] 81 | Data = "h2i_ping" 82 | h2i> ping 83 | [FrameHeader PING flags=ACK len=8] 84 | Data = "h2i_ping" 85 | h2i> ping 86 | [FrameHeader GOAWAY len=22] 87 | Last-Stream-ID = 1; Error-Code = PROTOCOL_ERROR (1) 88 | 89 | ReadFrame: EOF 90 | ``` 91 | 92 | ## Status 93 | 94 | Quick few hour hack. So much yet to do. Feel free to file issues for 95 | bugs or wishlist items, but [@bmizerany](https://github.com/bmizerany/) 96 | and I aren't yet accepting pull requests until things settle down. 97 | 98 | -------------------------------------------------------------------------------- /http2/header_order_test.go: -------------------------------------------------------------------------------- 1 | package http2 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "strings" 7 | "testing" 8 | 9 | http "github.com/bogdanfinn/fhttp" 10 | "github.com/bogdanfinn/fhttp/httptrace" 11 | ) 12 | 13 | func TestHeaderOrder(t *testing.T) { 14 | req, err := http.NewRequest("POST", "https://www.httpbin.org/headers", nil) 15 | if err != nil { 16 | t.Fatalf(err.Error()) 17 | } 18 | req.Header = http.Header{ 19 | "sec-ch-ua": {"\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\""}, 20 | "accept": {"*/*"}, 21 | "x-requested-with": {"XMLHttpRequest"}, 22 | "sec-ch-ua-mobile": {"?0"}, 23 | "user-agent": {"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36\", \"I shouldn't be here"}, 24 | "content-type": {"application/json"}, 25 | "origin": {"https://www.size.co.uk/"}, 26 | "sec-fetch-site": {"same-origin"}, 27 | "sec-fetch-mode": {"cors"}, 28 | "sec-fetch-dest": {"empty"}, 29 | "accept-language": {"en-US,en;q=0.9"}, 30 | "accept-encoding": {"gzip, deflate, br"}, 31 | "referer": {"https://www.size.co.uk/product/white-jordan-air-1-retro-high/16077886/"}, 32 | http.HeaderOrderKey: { 33 | "sec-ch-ua", 34 | "accept", 35 | "x-requested-with", 36 | "sec-ch-ua-mobile", 37 | "user-agent", 38 | "content-type", 39 | "sec-fetch-site", 40 | "sec-fetch-mode", 41 | "sec-fetch-dest", 42 | "referer", 43 | "accept-encoding", 44 | "accept-language", 45 | }, 46 | http.PHeaderOrderKey: { 47 | ":method", 48 | ":authority", 49 | ":scheme", 50 | ":path", 51 | }, 52 | } 53 | var b []byte 54 | buf := bytes.NewBuffer(b) 55 | err = req.Header.Write(buf) 56 | if err != nil { 57 | t.Fatalf(err.Error()) 58 | } 59 | arr := strings.Split(buf.String(), "\n") 60 | var hdrs []string 61 | for _, v := range arr { 62 | a := strings.Split(v, ":") 63 | if a[0] == "" { 64 | continue 65 | } 66 | hdrs = append(hdrs, a[0]) 67 | } 68 | 69 | for i := range req.Header[http.HeaderOrderKey] { 70 | if hdrs[i] != req.Header[http.HeaderOrderKey][i] { 71 | t.Errorf("want: %s\ngot: %s\n", req.Header[http.HeaderOrderKey][i], hdrs[i]) 72 | } 73 | } 74 | } 75 | 76 | func TestHeaderOrder2(t *testing.T) { 77 | hk := "" 78 | trace := &httptrace.ClientTrace{ 79 | WroteHeaderField: func(key string, values []string) { 80 | hk += key + " " 81 | }, 82 | } 83 | req, err := http.NewRequest("GET", "https://httpbin.org/#/Request_inspection/get_headers", nil) 84 | if err != nil { 85 | t.Fatalf(err.Error()) 86 | } 87 | req.Header.Add("experience", "pain") 88 | req.Header.Add("grind", "harder") 89 | req.Header.Add("live", "mas") 90 | req.Header[http.HeaderOrderKey] = []string{"grind", "experience", "live"} 91 | req.Header[http.PHeaderOrderKey] = []string{":method", ":authority", ":scheme", ":path"} 92 | req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) 93 | tr := &Transport{} 94 | resp, err := tr.RoundTrip(req) 95 | if err != nil { 96 | t.Fatal(err.Error()) 97 | } 98 | defer resp.Body.Close() 99 | 100 | eq := strings.EqualFold(hk, ":method :authority :scheme :path grind experience live accept-encoding user-agent ") 101 | if !eq { 102 | t.Fatalf("Header order not set properly, \n Got %v \n Want: %v", hk, ":method :authority :scheme :path grind experience live accept-encoding user-agent") 103 | } 104 | } 105 | 106 | func TestHeaderOrder3(t *testing.T) { 107 | req, err := http.NewRequest("GET", "https://google.com", nil) 108 | if err != nil { 109 | t.Fatalf(err.Error()) 110 | } 111 | req.Header = http.Header{ 112 | http.HeaderOrderKey: { 113 | "sec-ch-ua", 114 | "accept", 115 | "x-requested-with", 116 | "sec-ch-ua-mobile", 117 | "user-agent", 118 | "sec-fetch-site", 119 | "sec-fetch-mode", 120 | "sec-fetch-dest", 121 | "referer", 122 | "accept-encoding", 123 | "accept-language", 124 | "cookie", 125 | }, 126 | } 127 | req.Header.Add("accept", "text / html, application/xhtml + xml, application / xml;q = 0.9, image/avif, image/webp, image/apng, * /*;q=0.8,application/signed-exchange;v=b3;q=0.9") 128 | req.Header.Add("accept-encoding", "gzip, deflate, br") 129 | req.Header.Add("accept-language", "en-GB,en-US;q=0.9,en;q=0.8") 130 | req.Header.Add("cache-control", "no-cache") 131 | req.Header.Add("pragma", "no-cache") 132 | req.Header.Add("referer", "https://www.offspring.co.uk/") 133 | req.Header.Add("sec-ch-ua", `" Not A;Brand"; v = "99", "Chromium"; v = "90", "Google Chrome"; v = "90"`) 134 | req.Header.Add("sec-ch-ua-mobile", "?0") 135 | req.Header.Add("sec-fetch-dest", "document") 136 | req.Header.Add("sec-fetch-mode", "navigate") 137 | req.Header.Add("sec-fetch-site", "same-origin") 138 | req.Header.Add("sec-fetch-user", "?1") 139 | req.Header.Add("upgrade-insecure-requests", "1") 140 | req.Header.Add("user-agent", "Mozilla") 141 | var hdrs string 142 | trace := &httptrace.ClientTrace{WroteHeaderField: func(key string, value []string) { 143 | hdrs += key + "\n" 144 | }} 145 | req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) 146 | tr := Transport{} 147 | resp, err := tr.RoundTrip(req) 148 | if err != nil { 149 | t.Fatalf(err.Error()) 150 | } 151 | defer resp.Body.Close() 152 | log.Println(hdrs) 153 | } 154 | -------------------------------------------------------------------------------- /http2/headermap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import ( 8 | "strings" 9 | "sync" 10 | 11 | http "github.com/bogdanfinn/fhttp" 12 | ) 13 | 14 | var ( 15 | commonBuildOnce sync.Once 16 | commonLowerHeader map[string]string // Go-Canonical-Case -> lower-case 17 | commonCanonHeader map[string]string // lower-case -> Go-Canonical-Case 18 | ) 19 | 20 | func buildCommonHeaderMapsOnce() { 21 | commonBuildOnce.Do(buildCommonHeaderMaps) 22 | } 23 | 24 | func buildCommonHeaderMaps() { 25 | common := []string{ 26 | "accept", 27 | "accept-charset", 28 | "accept-encoding", 29 | "accept-language", 30 | "accept-ranges", 31 | "age", 32 | "access-control-allow-origin", 33 | "allow", 34 | "authorization", 35 | "cache-control", 36 | "content-disposition", 37 | "content-encoding", 38 | "content-language", 39 | "content-length", 40 | "content-location", 41 | "content-range", 42 | "content-type", 43 | "cookie", 44 | "date", 45 | "etag", 46 | "expect", 47 | "expires", 48 | "from", 49 | "host", 50 | "if-match", 51 | "if-modified-since", 52 | "if-none-match", 53 | "if-unmodified-since", 54 | "last-modified", 55 | "link", 56 | "location", 57 | "max-forwards", 58 | "proxy-authenticate", 59 | "proxy-authorization", 60 | "range", 61 | "referer", 62 | "refresh", 63 | "retry-after", 64 | "server", 65 | "set-cookie", 66 | "strict-transport-security", 67 | "trailer", 68 | "transfer-encoding", 69 | "user-agent", 70 | "vary", 71 | "via", 72 | "www-authenticate", 73 | } 74 | commonLowerHeader = make(map[string]string, len(common)) 75 | commonCanonHeader = make(map[string]string, len(common)) 76 | for _, v := range common { 77 | chk := http.CanonicalHeaderKey(v) 78 | commonLowerHeader[chk] = v 79 | commonCanonHeader[v] = chk 80 | } 81 | } 82 | 83 | func lowerHeader(v string) string { 84 | buildCommonHeaderMapsOnce() 85 | if s, ok := commonLowerHeader[v]; ok { 86 | return s 87 | } 88 | return strings.ToLower(v) 89 | } 90 | -------------------------------------------------------------------------------- /http2/hpack/huffman.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package hpack 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "io" 11 | "sync" 12 | ) 13 | 14 | var bufPool = sync.Pool{ 15 | New: func() interface{} { return new(bytes.Buffer) }, 16 | } 17 | 18 | // HuffmanDecode decodes the string in v and writes the expanded 19 | // result to w, returning the number of bytes written to w and the 20 | // Write call's return value. At most one Write call is made. 21 | func HuffmanDecode(w io.Writer, v []byte) (int, error) { 22 | buf := bufPool.Get().(*bytes.Buffer) 23 | buf.Reset() 24 | defer bufPool.Put(buf) 25 | if err := huffmanDecode(buf, 0, v); err != nil { 26 | return 0, err 27 | } 28 | return w.Write(buf.Bytes()) 29 | } 30 | 31 | // HuffmanDecodeToString decodes the string in v. 32 | func HuffmanDecodeToString(v []byte) (string, error) { 33 | buf := bufPool.Get().(*bytes.Buffer) 34 | buf.Reset() 35 | defer bufPool.Put(buf) 36 | if err := huffmanDecode(buf, 0, v); err != nil { 37 | return "", err 38 | } 39 | return buf.String(), nil 40 | } 41 | 42 | // ErrInvalidHuffman is returned for errors found decoding 43 | // Huffman-encoded strings. 44 | var ErrInvalidHuffman = errors.New("hpack: invalid Huffman-encoded data") 45 | 46 | // huffmanDecode decodes v to buf. 47 | // If maxLen is greater than 0, attempts to write more to buf than 48 | // maxLen bytes will return ErrStringLength. 49 | func huffmanDecode(buf *bytes.Buffer, maxLen int, v []byte) error { 50 | rootHuffmanNode := getRootHuffmanNode() 51 | n := rootHuffmanNode 52 | // cur is the bit buffer that has not been fed into n. 53 | // cbits is the number of low order bits in cur that are valid. 54 | // sbits is the number of bits of the symbol prefix being decoded. 55 | cur, cbits, sbits := uint(0), uint8(0), uint8(0) 56 | for _, b := range v { 57 | cur = cur<<8 | uint(b) 58 | cbits += 8 59 | sbits += 8 60 | for cbits >= 8 { 61 | idx := byte(cur >> (cbits - 8)) 62 | n = n.children[idx] 63 | if n == nil { 64 | return ErrInvalidHuffman 65 | } 66 | if n.children == nil { 67 | if maxLen != 0 && buf.Len() == maxLen { 68 | return ErrStringLength 69 | } 70 | buf.WriteByte(n.sym) 71 | cbits -= n.codeLen 72 | n = rootHuffmanNode 73 | sbits = cbits 74 | } else { 75 | cbits -= 8 76 | } 77 | } 78 | } 79 | for cbits > 0 { 80 | n = n.children[byte(cur<<(8-cbits))] 81 | if n == nil { 82 | return ErrInvalidHuffman 83 | } 84 | if n.children != nil || n.codeLen > cbits { 85 | break 86 | } 87 | if maxLen != 0 && buf.Len() == maxLen { 88 | return ErrStringLength 89 | } 90 | buf.WriteByte(n.sym) 91 | cbits -= n.codeLen 92 | n = rootHuffmanNode 93 | sbits = cbits 94 | } 95 | if sbits > 7 { 96 | // Either there was an incomplete symbol, or overlong padding. 97 | // Both are decoding errors per RFC 7541 section 5.2. 98 | return ErrInvalidHuffman 99 | } 100 | if mask := uint(1< 8 { 151 | codeLen -= 8 152 | i := uint8(code >> codeLen) 153 | if cur.children[i] == nil { 154 | cur.children[i] = newInternalNode() 155 | } 156 | cur = cur.children[i] 157 | } 158 | shift := 8 - codeLen 159 | start, end := int(uint8(code<> (nbits - rembits)) 183 | dst[len(dst)-1] |= t 184 | } 185 | 186 | return dst 187 | } 188 | 189 | // HuffmanEncodeLength returns the number of bytes required to encode 190 | // s in Huffman codes. The result is round up to byte boundary. 191 | func HuffmanEncodeLength(s string) uint64 { 192 | n := uint64(0) 193 | for i := 0; i < len(s); i++ { 194 | n += uint64(huffmanCodeLen[s[i]]) 195 | } 196 | return (n + 7) / 8 197 | } 198 | 199 | // appendByteToHuffmanCode appends Huffman code for c to dst and 200 | // returns the extended buffer and the remaining bits in the last 201 | // element. The appending is not byte aligned and the remaining bits 202 | // in the last element of dst is given in rembits. 203 | func appendByteToHuffmanCode(dst []byte, rembits uint8, c byte) ([]byte, uint8) { 204 | code := huffmanCodes[c] 205 | nbits := huffmanCodeLen[c] 206 | 207 | for { 208 | if rembits > nbits { 209 | t := uint8(code << (rembits - nbits)) 210 | dst[len(dst)-1] |= t 211 | rembits -= nbits 212 | break 213 | } 214 | 215 | t := uint8(code >> (nbits - rembits)) 216 | dst[len(dst)-1] |= t 217 | 218 | nbits -= rembits 219 | rembits = 8 220 | 221 | if nbits == 0 { 222 | break 223 | } 224 | 225 | dst = append(dst, 0) 226 | } 227 | 228 | return dst, rembits 229 | } 230 | -------------------------------------------------------------------------------- /http2/not_go111.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !go1.11 6 | // +build !go1.11 7 | 8 | package http2 9 | 10 | import ( 11 | "net/textproto" 12 | 13 | "github.com/bogdanfinn/fhttp/httptrace" 14 | ) 15 | 16 | func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool { return false } 17 | 18 | func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {} 19 | 20 | func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error { 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /http2/pipe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import ( 8 | "errors" 9 | "io" 10 | "sync" 11 | ) 12 | 13 | // pipe is a goroutine-safe io.Reader/io.Writer pair. It's like 14 | // io.Pipe except there are no PipeReader/PipeWriter halves, and the 15 | // underlying buffer is an interface. (io.Pipe is always unbuffered) 16 | type pipe struct { 17 | mu sync.Mutex 18 | c sync.Cond // c.L lazily initialized to &p.mu 19 | b pipeBuffer // nil when done reading 20 | unread int // bytes unread when done 21 | err error // read error once empty. non-nil means closed. 22 | breakErr error // immediate read error (caller doesn't see rest of b) 23 | donec chan struct{} // closed on error 24 | readFn func() // optional code to run in Read before error 25 | } 26 | 27 | type pipeBuffer interface { 28 | Len() int 29 | io.Writer 30 | io.Reader 31 | } 32 | 33 | func (p *pipe) Len() int { 34 | p.mu.Lock() 35 | defer p.mu.Unlock() 36 | if p.b == nil { 37 | return p.unread 38 | } 39 | return p.b.Len() 40 | } 41 | 42 | // Read waits until data is available and copies bytes 43 | // from the buffer into p. 44 | func (p *pipe) Read(d []byte) (n int, err error) { 45 | p.mu.Lock() 46 | defer p.mu.Unlock() 47 | if p.c.L == nil { 48 | p.c.L = &p.mu 49 | } 50 | for { 51 | if p.breakErr != nil { 52 | return 0, p.breakErr 53 | } 54 | if p.b != nil && p.b.Len() > 0 { 55 | return p.b.Read(d) 56 | } 57 | if p.err != nil { 58 | if p.readFn != nil { 59 | p.readFn() // e.g. copy trailers 60 | p.readFn = nil // not sticky like p.err 61 | } 62 | p.b = nil 63 | return 0, p.err 64 | } 65 | p.c.Wait() 66 | } 67 | } 68 | 69 | var ( 70 | errClosedPipeWrite = errors.New("write on closed buffer") 71 | http2errUninitializedPipeWrite = errors.New("write on uninitialized buffer") 72 | ) 73 | 74 | // Write copies bytes from p into the buffer and wakes a reader. 75 | // It is an error to write more data than the buffer can hold. 76 | func (p *pipe) Write(d []byte) (n int, err error) { 77 | p.mu.Lock() 78 | defer p.mu.Unlock() 79 | if p.c.L == nil { 80 | p.c.L = &p.mu 81 | } 82 | defer p.c.Signal() 83 | if p.err != nil { 84 | return 0, errClosedPipeWrite 85 | } 86 | if p.breakErr != nil { 87 | p.unread += len(d) 88 | return len(d), nil // discard when there is no reader 89 | } 90 | // pipe.setBuffer is never invoked, leaving the buffer uninitialized. 91 | // We shouldn't try to write to an uninitialized pipe, 92 | // but returning an error is better than panicking. 93 | if p.b == nil { 94 | return 0, http2errUninitializedPipeWrite 95 | } 96 | 97 | return p.b.Write(d) 98 | } 99 | 100 | // CloseWithError causes the next Read (waking up a current blocked 101 | // Read if needed) to return the provided err after all data has been 102 | // read. 103 | // 104 | // The error must be non-nil. 105 | func (p *pipe) CloseWithError(err error) { p.closeWithError(&p.err, err, nil) } 106 | 107 | // BreakWithError causes the next Read (waking up a current blocked 108 | // Read if needed) to return the provided err immediately, without 109 | // waiting for unread data. 110 | func (p *pipe) BreakWithError(err error) { p.closeWithError(&p.breakErr, err, nil) } 111 | 112 | // closeWithErrorAndCode is like CloseWithError but also sets some code to run 113 | // in the caller's goroutine before returning the error. 114 | func (p *pipe) closeWithErrorAndCode(err error, fn func()) { p.closeWithError(&p.err, err, fn) } 115 | 116 | func (p *pipe) closeWithError(dst *error, err error, fn func()) { 117 | if err == nil { 118 | panic("err must be non-nil") 119 | } 120 | p.mu.Lock() 121 | defer p.mu.Unlock() 122 | if p.c.L == nil { 123 | p.c.L = &p.mu 124 | } 125 | defer p.c.Signal() 126 | if *dst != nil { 127 | // Already been done. 128 | return 129 | } 130 | p.readFn = fn 131 | if dst == &p.breakErr { 132 | if p.b != nil { 133 | p.unread += p.b.Len() 134 | } 135 | p.b = nil 136 | } 137 | *dst = err 138 | p.closeDoneLocked() 139 | } 140 | 141 | // requires p.mu be held. 142 | func (p *pipe) closeDoneLocked() { 143 | if p.donec == nil { 144 | return 145 | } 146 | // Close if unclosed. This isn't racy since we always 147 | // hold p.mu while closing. 148 | select { 149 | case <-p.donec: 150 | default: 151 | close(p.donec) 152 | } 153 | } 154 | 155 | // Err returns the error (if any) first set by BreakWithError or CloseWithError. 156 | func (p *pipe) Err() error { 157 | p.mu.Lock() 158 | defer p.mu.Unlock() 159 | if p.breakErr != nil { 160 | return p.breakErr 161 | } 162 | return p.err 163 | } 164 | 165 | // Done returns a channel which is closed if and when this pipe is closed 166 | // with CloseWithError. 167 | func (p *pipe) Done() <-chan struct{} { 168 | p.mu.Lock() 169 | defer p.mu.Unlock() 170 | if p.donec == nil { 171 | p.donec = make(chan struct{}) 172 | if p.err != nil || p.breakErr != nil { 173 | // Already hit an error. 174 | p.closeDoneLocked() 175 | } 176 | } 177 | return p.donec 178 | } 179 | -------------------------------------------------------------------------------- /http2/pipe_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "io" 11 | "io/ioutil" 12 | "testing" 13 | ) 14 | 15 | func TestPipeClose(t *testing.T) { 16 | var p pipe 17 | p.b = new(bytes.Buffer) 18 | a := errors.New("a") 19 | b := errors.New("b") 20 | p.CloseWithError(a) 21 | p.CloseWithError(b) 22 | _, err := p.Read(make([]byte, 1)) 23 | if err != a { 24 | t.Errorf("err = %v want %v", err, a) 25 | } 26 | } 27 | 28 | func TestPipeDoneChan(t *testing.T) { 29 | var p pipe 30 | done := p.Done() 31 | select { 32 | case <-done: 33 | t.Fatal("done too soon") 34 | default: 35 | } 36 | p.CloseWithError(io.EOF) 37 | select { 38 | case <-done: 39 | default: 40 | t.Fatal("should be done") 41 | } 42 | } 43 | 44 | func TestPipeDoneChan_ErrFirst(t *testing.T) { 45 | var p pipe 46 | p.CloseWithError(io.EOF) 47 | done := p.Done() 48 | select { 49 | case <-done: 50 | default: 51 | t.Fatal("should be done") 52 | } 53 | } 54 | 55 | func TestPipeDoneChan_Break(t *testing.T) { 56 | var p pipe 57 | done := p.Done() 58 | select { 59 | case <-done: 60 | t.Fatal("done too soon") 61 | default: 62 | } 63 | p.BreakWithError(io.EOF) 64 | select { 65 | case <-done: 66 | default: 67 | t.Fatal("should be done") 68 | } 69 | } 70 | 71 | func TestPipeDoneChan_Break_ErrFirst(t *testing.T) { 72 | var p pipe 73 | p.BreakWithError(io.EOF) 74 | done := p.Done() 75 | select { 76 | case <-done: 77 | default: 78 | t.Fatal("should be done") 79 | } 80 | } 81 | 82 | func TestPipeCloseWithError(t *testing.T) { 83 | p := &pipe{b: new(bytes.Buffer)} 84 | const body = "foo" 85 | io.WriteString(p, body) 86 | a := errors.New("test error") 87 | p.CloseWithError(a) 88 | all, err := ioutil.ReadAll(p) 89 | if string(all) != body { 90 | t.Errorf("read bytes = %q; want %q", all, body) 91 | } 92 | if err != a { 93 | t.Logf("read error = %v, %v", err, a) 94 | } 95 | if p.Len() != 0 { 96 | t.Errorf("pipe should have 0 unread bytes") 97 | } 98 | // Read and Write should fail. 99 | if n, err := p.Write([]byte("abc")); err != errClosedPipeWrite || n != 0 { 100 | t.Errorf("Write(abc) after close\ngot %v, %v\nwant 0, %v", n, err, errClosedPipeWrite) 101 | } 102 | if n, err := p.Read(make([]byte, 1)); err == nil || n != 0 { 103 | t.Errorf("Read() after close\ngot %v, nil\nwant 0, %v", n, errClosedPipeWrite) 104 | } 105 | if p.Len() != 0 { 106 | t.Errorf("pipe should have 0 unread bytes") 107 | } 108 | } 109 | 110 | func TestPipeBreakWithError(t *testing.T) { 111 | p := &pipe{b: new(bytes.Buffer)} 112 | io.WriteString(p, "foo") 113 | a := errors.New("test err") 114 | p.BreakWithError(a) 115 | all, err := ioutil.ReadAll(p) 116 | if string(all) != "" { 117 | t.Errorf("read bytes = %q; want empty string", all) 118 | } 119 | if err != a { 120 | t.Logf("read error = %v, %v", err, a) 121 | } 122 | if p.b != nil { 123 | t.Errorf("buffer should be nil after BreakWithError") 124 | } 125 | if p.Len() != 3 { 126 | t.Errorf("pipe should have 3 unread bytes") 127 | } 128 | // Write should succeed silently. 129 | if n, err := p.Write([]byte("abc")); err != nil || n != 3 { 130 | t.Errorf("Write(abc) after break\ngot %v, %v\nwant 0, nil", n, err) 131 | } 132 | if p.b != nil { 133 | t.Errorf("buffer should be nil after Write") 134 | } 135 | if p.Len() != 6 { 136 | t.Errorf("pipe should have 6 unread bytes") 137 | } 138 | // Read should fail. 139 | if n, err := p.Read(make([]byte, 1)); err == nil || n != 0 { 140 | t.Errorf("Read() after close\ngot %v, nil\nwant 0, not nil", n) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /http2/push_consume.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package http2 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "net/url" 10 | 11 | http "github.com/bogdanfinn/fhttp" 12 | ) 13 | 14 | var ( 15 | errMissingHeaderMethod = errors.New("http2: missing required request pseudo-header :method") 16 | errMissingHeaderScheme = errors.New("http2: missing required request pseudo-header :scheme") 17 | errMissingHeaderPath = errors.New("http2: missing required request pseudo-header :path") 18 | errMissingHeaderAuthority = errors.New("http2: missing required request pseudo-header :authority") 19 | errInvalidMethod = errors.New("http2: method must be GET or HEAD") 20 | errInvalidScheme = errors.New("http2: scheme must be http or https") 21 | ) 22 | 23 | // DefaultPushHandler is a simple push handler for reading pushed responses 24 | type DefaultPushHandler struct { 25 | promise *http.Request 26 | origReqURL *url.URL 27 | origReqHeader http.Header 28 | push *http.Response 29 | pushErr error 30 | done chan struct{} 31 | } 32 | 33 | func (ph *DefaultPushHandler) HandlePush(r *PushedRequest) { 34 | ph.promise = r.Promise 35 | ph.origReqURL = r.OriginalRequestURL 36 | ph.origReqHeader = r.OriginalRequestHeader 37 | ph.push, ph.pushErr = r.ReadResponse(r.Promise.Context()) 38 | if ph.done != nil { 39 | close(ph.done) 40 | } 41 | } 42 | 43 | // PushHandler consumes a pushed response. 44 | type PushHandler interface { 45 | // HandlePush will be called once for every PUSH_PROMISE received 46 | // from the server. If HandlePush returns before the pushed stream 47 | // has completed, the pushed stream will be canceled. 48 | HandlePush(r *PushedRequest) 49 | } 50 | 51 | // PushedRequest describes a request that was pushed from the server. 52 | type PushedRequest struct { 53 | // Promise is the HTTP/2 PUSH_PROMISE message. The promised 54 | // request does not have a body. Handlers should not modify Promise. 55 | // 56 | // Promise.RemoteAddr is the address of the server that started this push request. 57 | Promise *http.Request 58 | 59 | // OriginalRequestURL is the URL of the original client request that triggered the push. 60 | OriginalRequestURL *url.URL 61 | 62 | // OriginalRequestHeader contains the headers of the original client request that triggered the push. 63 | OriginalRequestHeader http.Header 64 | pushedStream *clientStream 65 | } 66 | 67 | // ReadResponse reads the pushed response. If ctx is done before the 68 | // response headers are fully received, ReadResponse will fail and PushedRequest 69 | // will be cancelled. 70 | func (pr *PushedRequest) ReadResponse(ctx context.Context) (*http.Response, error) { 71 | select { 72 | case <-ctx.Done(): 73 | pr.Cancel() 74 | pr.pushedStream.bufPipe.CloseWithError(ctx.Err()) 75 | return nil, ctx.Err() 76 | case <-pr.pushedStream.peerReset: 77 | return nil, pr.pushedStream.resetErr 78 | case resErr := <-pr.pushedStream.resc: 79 | if resErr.err != nil { 80 | pr.Cancel() 81 | pr.pushedStream.bufPipe.CloseWithError(resErr.err) 82 | return nil, resErr.err 83 | } 84 | resErr.res.Request = pr.Promise 85 | resErr.res.TLS = pr.pushedStream.cc.tlsState 86 | return resErr.res, resErr.err 87 | } 88 | } 89 | 90 | // Cancel tells the server that the pushed response stream should be terminated. 91 | // See: https://tools.ietf.org/html/rfc7540#section-8.2.2 92 | func (pr *PushedRequest) Cancel() { 93 | pr.pushedStream.cancelStream() 94 | } 95 | 96 | func pushedRequestToHTTPRequest(mppf *MetaPushPromiseFrame) (*http.Request, error) { 97 | method := mppf.PseudoValue("method") 98 | scheme := mppf.PseudoValue("scheme") 99 | authority := mppf.PseudoValue("authority") 100 | path := mppf.PseudoValue("path") 101 | // pseudo-headers required in all http2 requests 102 | if method == "" { 103 | return nil, errMissingHeaderMethod 104 | } 105 | if scheme == "" { 106 | return nil, errMissingHeaderScheme 107 | } 108 | if path == "" { 109 | return nil, errMissingHeaderPath 110 | } 111 | // authority is required for PUSH_PROMISE requests per RFC 7540 Section 8.2 112 | if authority == "" { 113 | return nil, errMissingHeaderAuthority 114 | } 115 | // "Promised requests MUST be cacheable (see [RFC7231], Section 4.2.3), 116 | // MUST be safe (see [RFC7231], Section 4.2.1)" 117 | // https://tools.ietf.org/html/rfc7540#section-8.2 118 | if method != "GET" && method != "HEAD" { 119 | return nil, errInvalidMethod 120 | } 121 | if scheme != "http" && scheme != "https" { 122 | return nil, errInvalidScheme 123 | } 124 | var headers http.Header 125 | for _, header := range mppf.RegularFields() { 126 | if len(headers) == 0 { 127 | headers = http.Header{} 128 | } 129 | headers.Add(header.Name, header.Value) 130 | } 131 | if err := checkValidPushPromiseRequestHeaders(headers); err != nil { 132 | return nil, err 133 | } 134 | if err := checkValidHTTP2RequestHeaders(headers); err != nil { 135 | return nil, err 136 | } 137 | reqUrl, err := url.ParseRequestURI(path) 138 | if err != nil { 139 | return nil, err 140 | } 141 | reqUrl.Host = authority 142 | reqUrl.Scheme = scheme 143 | return &http.Request{ 144 | Method: method, 145 | Proto: "HTTP/2.0", 146 | ProtoMajor: 2, 147 | URL: reqUrl, 148 | Header: headers, 149 | }, nil 150 | } 151 | 152 | // handlePushEarlyReturnCancel handles the pushed request with the push handler. 153 | // If PushHandler.HandlePush returns before the pushed stream has completed, the pushed 154 | // stream is canceled. 155 | func handlePushEarlyReturnCancel(pushHandler PushHandler, pushedRequest *PushedRequest) { 156 | handleReturned := make(chan struct{}) 157 | go func() { 158 | defer close(handleReturned) 159 | pushHandler.HandlePush(pushedRequest) 160 | }() 161 | select { 162 | case <-handleReturned: 163 | pushedRequest.Cancel() 164 | case <-pushedRequest.pushedStream.done: 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /http2/push_consume_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package http2 5 | 6 | import ( 7 | "errors" 8 | "net/url" 9 | "reflect" 10 | "testing" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | "github.com/bogdanfinn/fhttp/http2/hpack" 14 | ) 15 | 16 | func TestPushPromiseHeadersToHTTPRequest(t *testing.T) { 17 | headers := http.Header{} 18 | headers.Add("X", "y") 19 | getUrl := func(path, authority, scheme string) *url.URL { 20 | reqUrl, err := url.ParseRequestURI(path) 21 | if err != nil { 22 | t.Error(err) 23 | return nil 24 | } 25 | reqUrl.Host = authority 26 | reqUrl.Scheme = scheme 27 | return reqUrl 28 | } 29 | 30 | requiredHeaders := []hpack.HeaderField{ 31 | {Name: ":method", Value: "GET"}, 32 | {Name: ":scheme", Value: "https"}, 33 | {Name: ":authority", Value: "foo.org"}, 34 | {Name: ":path", Value: "/hello"}, 35 | } 36 | 37 | tests := []struct { 38 | name string 39 | headers []hpack.HeaderField 40 | expectedReq *http.Request 41 | expectedErr error 42 | }{ 43 | { 44 | "NoErrors_IncludeNonRequiredHeaders", 45 | append(requiredHeaders, 46 | hpack.HeaderField{Name: "X", Value: "y"}, 47 | ), 48 | &http.Request{ 49 | Method: "GET", 50 | Proto: "HTTP/2.0", 51 | ProtoMajor: 2, 52 | URL: getUrl("/hello", "foo.org", "https"), 53 | Header: headers, 54 | }, 55 | nil, 56 | }, 57 | { 58 | "NoErrors_OnlyRequiredHeaders", 59 | requiredHeaders, 60 | &http.Request{ 61 | Method: "GET", 62 | Proto: "HTTP/2.0", 63 | ProtoMajor: 2, 64 | URL: getUrl("/hello", "foo.org", "https"), 65 | }, 66 | nil, 67 | }, 68 | { 69 | "Missing_Method", 70 | []hpack.HeaderField{ 71 | {Name: ":scheme", Value: "https"}, 72 | {Name: ":authority", Value: "foo.org"}, 73 | {Name: ":path", Value: "/hello"}, 74 | }, 75 | nil, 76 | errMissingHeaderMethod, 77 | }, 78 | { 79 | "Missing_Scheme", 80 | []hpack.HeaderField{ 81 | {Name: ":method", Value: "GET"}, 82 | {Name: ":authority", Value: "foo.org"}, 83 | {Name: ":path", Value: "/hello"}, 84 | }, 85 | nil, 86 | errMissingHeaderScheme, 87 | }, 88 | { 89 | "Missing_Authority", 90 | []hpack.HeaderField{ 91 | {Name: ":scheme", Value: "https"}, 92 | {Name: ":method", Value: "GET"}, 93 | {Name: ":path", Value: "/hello"}, 94 | }, 95 | nil, 96 | errMissingHeaderAuthority, 97 | }, 98 | { 99 | "Missing_Path", 100 | []hpack.HeaderField{ 101 | {Name: ":scheme", Value: "https"}, 102 | {Name: ":method", Value: "GET"}, 103 | {Name: ":authority", Value: "foo.org"}, 104 | }, 105 | nil, 106 | errMissingHeaderPath, 107 | }, 108 | { 109 | "Invalid_Method", 110 | []hpack.HeaderField{ 111 | {Name: ":method", Value: "POST"}, 112 | {Name: ":scheme", Value: "https"}, 113 | {Name: ":authority", Value: "foo.org"}, 114 | {Name: ":path", Value: "/hello"}, 115 | }, 116 | nil, 117 | errInvalidMethod, 118 | }, 119 | { 120 | "Invalid_Scheme", 121 | []hpack.HeaderField{ 122 | {Name: ":method", Value: "GET"}, 123 | {Name: ":scheme", Value: "ftp"}, 124 | {Name: ":authority", Value: "foo.org"}, 125 | {Name: ":path", Value: "/hello"}, 126 | }, 127 | nil, 128 | errInvalidScheme, 129 | }, 130 | { 131 | "Cannot_Have_Body", 132 | append(requiredHeaders, 133 | hpack.HeaderField{Name: "Content-Length", Value: "100"}, 134 | ), 135 | nil, 136 | errors.New(`promised request cannot include body related header "Content-Length"`), 137 | }, 138 | { 139 | "Invalid_HTTP2_Header", 140 | append(requiredHeaders, 141 | hpack.HeaderField{Name: "Connection", Value: "close"}, 142 | ), 143 | nil, 144 | errors.New(`request header "Connection" is not valid in HTTP/2`), 145 | }, 146 | } 147 | 148 | for _, tt := range tests { 149 | t.Run(tt.name, func(t *testing.T) { 150 | mpp := &MetaPushPromiseFrame{nil, tt.headers, false} 151 | req, err := pushedRequestToHTTPRequest(mpp) 152 | if !reflect.DeepEqual(err, tt.expectedErr) { 153 | t.Fatalf("expected error %q but got error %q", tt.expectedErr, err) 154 | } 155 | if !reflect.DeepEqual(req, tt.expectedReq) { 156 | t.Fatalf("expected %v, but got %v", tt.expectedReq, req) 157 | } 158 | }) 159 | } 160 | } 161 | 162 | type testPushHandlerRecordHandled struct { 163 | messageDone bool 164 | requestHandled bool 165 | } 166 | 167 | func (ph *testPushHandlerRecordHandled) HandlePush(r *PushedRequest) { 168 | ph.requestHandled = true 169 | if ph.messageDone { 170 | r.pushedStream.done <- struct{}{} 171 | } 172 | } 173 | 174 | func TestHandlePushNoActionCancel(t *testing.T) { 175 | tests := []struct { 176 | name string 177 | returnBeforeComplete bool 178 | expectCancel bool 179 | }{ 180 | { 181 | "ReturnBeforeComplete", 182 | true, 183 | true, 184 | }, 185 | { 186 | "ReturnAfterComplete", 187 | false, 188 | false, 189 | }, 190 | } 191 | 192 | for _, tt := range tests { 193 | t.Run(tt.name, func(t *testing.T) { 194 | st := newServerTester(t, nil) 195 | defer st.Close() 196 | tr := &Transport{TLSClientConfig: tlsConfigInsecure} 197 | defer tr.CloseIdleConnections() 198 | cc, err := tr.dialClientConn(st.ts.Listener.Addr().String(), false) 199 | if err != nil { 200 | t.Fatal(err) 201 | } 202 | cs := cc.newStreamWithID(2, false) 203 | pr := &PushedRequest{pushedStream: cs} 204 | ph := &testPushHandlerRecordHandled{messageDone: !tt.returnBeforeComplete} 205 | handlePushEarlyReturnCancel(ph, pr) 206 | if cs.didReset && !tt.expectCancel { 207 | t.Error("expected pushed stream to be cancelled but it was not") 208 | } else if !cs.didReset && tt.expectCancel { 209 | t.Error("expected pushed stream to not be cancelled but it was") 210 | } 211 | }) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /http2/writesched_random.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import "math" 8 | 9 | // NewRandomWriteScheduler constructs a WriteScheduler that ignores HTTP/2 10 | // priorities. Control frames like SETTINGS and PING are written before DATA 11 | // frames, but if no control frames are queued and multiple streams have queued 12 | // HEADERS or DATA frames, Pop selects a ready stream arbitrarily. 13 | func NewRandomWriteScheduler() WriteScheduler { 14 | return &randomWriteScheduler{sq: make(map[uint32]*writeQueue)} 15 | } 16 | 17 | type randomWriteScheduler struct { 18 | // zero are frames not associated with a specific stream. 19 | zero writeQueue 20 | 21 | // sq contains the stream-specific queues, keyed by stream ID. 22 | // When a stream is idle, closed, or emptied, it's deleted 23 | // from the map. 24 | sq map[uint32]*writeQueue 25 | 26 | // pool of empty queues for reuse. 27 | queuePool writeQueuePool 28 | } 29 | 30 | func (ws *randomWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) { 31 | // no-op: idle streams are not tracked 32 | } 33 | 34 | func (ws *randomWriteScheduler) CloseStream(streamID uint32) { 35 | q, ok := ws.sq[streamID] 36 | if !ok { 37 | return 38 | } 39 | delete(ws.sq, streamID) 40 | ws.queuePool.put(q) 41 | } 42 | 43 | func (ws *randomWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) { 44 | // no-op: priorities are ignored 45 | } 46 | 47 | func (ws *randomWriteScheduler) Push(wr FrameWriteRequest) { 48 | id := wr.StreamID() 49 | if id == 0 { 50 | ws.zero.push(wr) 51 | return 52 | } 53 | q, ok := ws.sq[id] 54 | if !ok { 55 | q = ws.queuePool.get() 56 | ws.sq[id] = q 57 | } 58 | q.push(wr) 59 | } 60 | 61 | func (ws *randomWriteScheduler) Pop() (FrameWriteRequest, bool) { 62 | // Control frames first. 63 | if !ws.zero.empty() { 64 | return ws.zero.shift(), true 65 | } 66 | // Iterate over all non-idle streams until finding one that can be consumed. 67 | for streamID, q := range ws.sq { 68 | if wr, ok := q.consume(math.MaxInt32); ok { 69 | if q.empty() { 70 | delete(ws.sq, streamID) 71 | ws.queuePool.put(q) 72 | } 73 | return wr, true 74 | } 75 | } 76 | return FrameWriteRequest{}, false 77 | } 78 | -------------------------------------------------------------------------------- /http2/writesched_random_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import "testing" 8 | 9 | func TestRandomScheduler(t *testing.T) { 10 | ws := NewRandomWriteScheduler() 11 | ws.Push(makeWriteHeadersRequest(3)) 12 | ws.Push(makeWriteHeadersRequest(4)) 13 | ws.Push(makeWriteHeadersRequest(1)) 14 | ws.Push(makeWriteHeadersRequest(2)) 15 | ws.Push(makeWriteNonStreamRequest()) 16 | ws.Push(makeWriteNonStreamRequest()) 17 | 18 | // Pop all frames. Should get the non-stream requests first, 19 | // followed by the stream requests in any order. 20 | var order []FrameWriteRequest 21 | for { 22 | wr, ok := ws.Pop() 23 | if !ok { 24 | break 25 | } 26 | order = append(order, wr) 27 | } 28 | t.Logf("got frames: %v", order) 29 | if len(order) != 6 { 30 | t.Fatalf("got %d frames, expected 6", len(order)) 31 | } 32 | if order[0].StreamID() != 0 || order[1].StreamID() != 0 { 33 | t.Fatal("expected non-stream frames first", order[0], order[1]) 34 | } 35 | got := make(map[uint32]bool) 36 | for _, wr := range order[2:] { 37 | got[wr.StreamID()] = true 38 | } 39 | for id := uint32(1); id <= 4; id++ { 40 | if !got[id] { 41 | t.Errorf("frame not found for stream %d", id) 42 | } 43 | } 44 | 45 | // Verify that we clean up maps for empty queues in all cases (golang.org/issue/33812) 46 | const arbitraryStreamID = 123 47 | ws.Push(makeHandlerPanicRST(arbitraryStreamID)) 48 | rws := ws.(*randomWriteScheduler) 49 | if got, want := len(rws.sq), 1; got != want { 50 | t.Fatalf("len of 123 stream = %v; want %v", got, want) 51 | } 52 | _, ok := ws.Pop() 53 | if !ok { 54 | t.Fatal("expected to be able to Pop") 55 | } 56 | if got, want := len(rws.sq), 0; got != want { 57 | t.Fatalf("len of 123 stream = %v; want %v", got, want) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /http2/writesched_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http2 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "reflect" 11 | "testing" 12 | ) 13 | 14 | func makeWriteNonStreamRequest() FrameWriteRequest { 15 | return FrameWriteRequest{writeSettingsAck{}, nil, nil} 16 | } 17 | 18 | func makeWriteHeadersRequest(streamID uint32) FrameWriteRequest { 19 | st := &stream{id: streamID} 20 | return FrameWriteRequest{&writeResHeaders{streamID: streamID, httpResCode: 200}, st, nil} 21 | } 22 | 23 | func makeHandlerPanicRST(streamID uint32) FrameWriteRequest { 24 | st := &stream{id: streamID} 25 | return FrameWriteRequest{&handlerPanicRST{StreamID: streamID}, st, nil} 26 | } 27 | 28 | func checkConsume(wr FrameWriteRequest, nbytes int32, want []FrameWriteRequest) error { 29 | consumed, rest, n := wr.Consume(nbytes) 30 | var wantConsumed, wantRest FrameWriteRequest 31 | switch len(want) { 32 | case 0: 33 | case 1: 34 | wantConsumed = want[0] 35 | case 2: 36 | wantConsumed = want[0] 37 | wantRest = want[1] 38 | } 39 | if !reflect.DeepEqual(consumed, wantConsumed) || !reflect.DeepEqual(rest, wantRest) || n != len(want) { 40 | return fmt.Errorf("got %v, %v, %v\nwant %v, %v, %v", consumed, rest, n, wantConsumed, wantRest, len(want)) 41 | } 42 | return nil 43 | } 44 | 45 | func TestFrameWriteRequestNonData(t *testing.T) { 46 | wr := makeWriteNonStreamRequest() 47 | if got, want := wr.DataSize(), 0; got != want { 48 | t.Errorf("DataSize: got %v, want %v", got, want) 49 | } 50 | 51 | // Non-DATA frames are always consumed whole. 52 | if err := checkConsume(wr, 0, []FrameWriteRequest{wr}); err != nil { 53 | t.Errorf("Consume:\n%v", err) 54 | } 55 | } 56 | 57 | func TestFrameWriteRequestData(t *testing.T) { 58 | st := &stream{ 59 | id: 1, 60 | sc: &serverConn{maxFrameSize: 16}, 61 | } 62 | const size = 32 63 | wr := FrameWriteRequest{&writeData{st.id, make([]byte, size), true}, st, make(chan error)} 64 | if got, want := wr.DataSize(), size; got != want { 65 | t.Errorf("DataSize: got %v, want %v", got, want) 66 | } 67 | 68 | // No flow-control bytes available: cannot consume anything. 69 | if err := checkConsume(wr, math.MaxInt32, []FrameWriteRequest{}); err != nil { 70 | t.Errorf("Consume(limited by flow control):\n%v", err) 71 | } 72 | 73 | // Add enough flow-control bytes to consume the entire frame, 74 | // but we're now restricted by st.sc.maxFrameSize. 75 | st.flow.add(size) 76 | want := []FrameWriteRequest{ 77 | { 78 | write: &writeData{st.id, make([]byte, st.sc.maxFrameSize), false}, 79 | stream: st, 80 | done: nil, 81 | }, 82 | { 83 | write: &writeData{st.id, make([]byte, size-st.sc.maxFrameSize), true}, 84 | stream: st, 85 | done: wr.done, 86 | }, 87 | } 88 | if err := checkConsume(wr, math.MaxInt32, want); err != nil { 89 | t.Errorf("Consume(limited by maxFrameSize):\n%v", err) 90 | } 91 | rest := want[1] 92 | 93 | // Consume 8 bytes from the remaining frame. 94 | want = []FrameWriteRequest{ 95 | { 96 | write: &writeData{st.id, make([]byte, 8), false}, 97 | stream: st, 98 | done: nil, 99 | }, 100 | { 101 | write: &writeData{st.id, make([]byte, size-st.sc.maxFrameSize-8), true}, 102 | stream: st, 103 | done: wr.done, 104 | }, 105 | } 106 | if err := checkConsume(rest, 8, want); err != nil { 107 | t.Errorf("Consume(8):\n%v", err) 108 | } 109 | rest = want[1] 110 | 111 | // Consume all remaining bytes. 112 | want = []FrameWriteRequest{ 113 | { 114 | write: &writeData{st.id, make([]byte, size-st.sc.maxFrameSize-8), true}, 115 | stream: st, 116 | done: wr.done, 117 | }, 118 | } 119 | if err := checkConsume(rest, math.MaxInt32, want); err != nil { 120 | t.Errorf("Consume(remainder):\n%v", err) 121 | } 122 | } 123 | 124 | func TestFrameWriteRequest_StreamID(t *testing.T) { 125 | const streamID = 123 126 | wr := FrameWriteRequest{write: streamError(streamID, ErrCodeNo)} 127 | if got := wr.StreamID(); got != streamID { 128 | t.Errorf("FrameWriteRequest(StreamError) = %v; want %v", got, streamID) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /http_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Tests of internal functions and things with no better homes. 6 | 7 | package http 8 | 9 | import ( 10 | "bytes" 11 | "net/url" 12 | "os/exec" 13 | "reflect" 14 | "testing" 15 | 16 | "github.com/bogdanfinn/fhttp/internal/testenv" 17 | ) 18 | 19 | func TestForeachHeaderElement(t *testing.T) { 20 | tests := []struct { 21 | in string 22 | want []string 23 | }{ 24 | {"Foo", []string{"Foo"}}, 25 | {" Foo", []string{"Foo"}}, 26 | {"Foo ", []string{"Foo"}}, 27 | {" Foo ", []string{"Foo"}}, 28 | 29 | {"foo", []string{"foo"}}, 30 | {"anY-cAsE", []string{"anY-cAsE"}}, 31 | 32 | {"", nil}, 33 | {",,,, , ,, ,,, ,", nil}, 34 | 35 | {" Foo,Bar, Baz,lower,,Quux ", []string{"Foo", "Bar", "Baz", "lower", "Quux"}}, 36 | } 37 | for _, tt := range tests { 38 | var got []string 39 | foreachHeaderElement(tt.in, func(v string) { 40 | got = append(got, v) 41 | }) 42 | if !reflect.DeepEqual(got, tt.want) { 43 | t.Errorf("foreachHeaderElement(%q) = %q; want %q", tt.in, got, tt.want) 44 | } 45 | } 46 | } 47 | 48 | func TestCleanHost(t *testing.T) { 49 | tests := []struct { 50 | in, want string 51 | }{ 52 | {"www.google.com", "www.google.com"}, 53 | {"www.google.com foo", "www.google.com"}, 54 | {"www.google.com/foo", "www.google.com"}, 55 | {" first character is a space", ""}, 56 | {"[1::6]:8080", "[1::6]:8080"}, 57 | 58 | // Punycode: 59 | {"гофер.рф/foo", "xn--c1ae0ajs.xn--p1ai"}, 60 | {"bücher.de", "xn--bcher-kva.de"}, 61 | {"bücher.de:8080", "xn--bcher-kva.de:8080"}, 62 | // Verify we convert to lowercase before punycode: 63 | {"BÜCHER.de", "xn--bcher-kva.de"}, 64 | {"BÜCHER.de:8080", "xn--bcher-kva.de:8080"}, 65 | // Verify we normalize to NFC before punycode: 66 | {"gophér.nfc", "xn--gophr-esa.nfc"}, // NFC input; no work needed 67 | {"goph\u0065\u0301r.nfd", "xn--gophr-esa.nfd"}, // NFD input 68 | } 69 | for _, tt := range tests { 70 | got := cleanHost(tt.in) 71 | if tt.want != got { 72 | t.Errorf("cleanHost(%q) = %q, want %q", tt.in, got, tt.want) 73 | } 74 | } 75 | } 76 | 77 | // Test that cmd/go doesn't link in the HTTP server. 78 | // 79 | // This catches accidental dependencies between the HTTP transport and 80 | // server code. 81 | func TestCmdGoNoHTTPServer(t *testing.T) { 82 | t.Parallel() 83 | goBin := testenv.GoToolPath(t) 84 | out, err := exec.Command(goBin, "tool", "nm", goBin).CombinedOutput() 85 | if err != nil { 86 | t.Fatalf("go tool nm: %v: %s", err, out) 87 | } 88 | wantSym := map[string]bool{ 89 | // Verify these exist: (sanity checking this test) 90 | "net/http.(*Client).do": true, 91 | "net/http.(*Transport).RoundTrip": true, 92 | 93 | // Verify these don't exist: 94 | "net/http.http2Server": false, 95 | "net/http.(*Server).Serve": false, 96 | "net/http.(*ServeMux).ServeHTTP": false, 97 | "net/http.DefaultServeMux": false, 98 | } 99 | for sym, want := range wantSym { 100 | got := bytes.Contains(out, []byte(sym)) 101 | if !want && got { 102 | t.Errorf("cmd/go unexpectedly links in HTTP server code; found symbol %q in cmd/go", sym) 103 | } 104 | if want && !got { 105 | t.Errorf("expected to find symbol %q in cmd/go; not found", sym) 106 | } 107 | } 108 | } 109 | 110 | // Tests that the nethttpomithttp2 build tag doesn't rot too much, 111 | // even if there's not a regular builder on it. 112 | func TestOmitHTTP2(t *testing.T) { 113 | if testing.Short() { 114 | t.Skip("skipping in short mode") 115 | } 116 | t.Parallel() 117 | goTool := testenv.GoToolPath(t) 118 | out, err := exec.Command(goTool, "test", "-short", "-tags=nethttpomithttp2", "net/http").CombinedOutput() 119 | if err != nil { 120 | t.Fatalf("go test -short failed: %v, %s", err, out) 121 | } 122 | } 123 | 124 | // Tests that the nethttpomithttp2 build tag at least type checks 125 | // in short mode. 126 | // The TestOmitHTTP2 test above actually runs tests (in long mode). 127 | func TestOmitHTTP2Vet(t *testing.T) { 128 | t.Parallel() 129 | goTool := testenv.GoToolPath(t) 130 | out, err := exec.Command(goTool, "vet", "-tags=nethttpomithttp2", "net/http").CombinedOutput() 131 | if err != nil { 132 | t.Fatalf("go vet failed: %v, %s", err, out) 133 | } 134 | } 135 | 136 | var valuesCount int 137 | 138 | func BenchmarkCopyValues(b *testing.B) { 139 | b.ReportAllocs() 140 | src := url.Values{ 141 | "a": {"1", "2", "3", "4", "5"}, 142 | "b": {"2", "2", "3", "4", "5"}, 143 | "c": {"3", "2", "3", "4", "5"}, 144 | "d": {"4", "2", "3", "4", "5"}, 145 | "e": {"1", "1", "2", "3", "4", "5", "6", "7", "abcdef", "l", "a", "b", "c", "d", "z"}, 146 | "j": {"1", "2"}, 147 | "m": nil, 148 | } 149 | for i := 0; i < b.N; i++ { 150 | dst := url.Values{"a": {"b"}, "b": {"2"}, "c": {"3"}, "d": {"4"}, "j": nil, "m": {"x"}} 151 | copyValues(dst, src) 152 | if valuesCount = len(dst["a"]); valuesCount != 6 { 153 | b.Fatalf(`%d items in dst["a"] but expected 6`, valuesCount) 154 | } 155 | } 156 | if valuesCount == 0 { 157 | b.Fatal("Benchmark wasn't run") 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /httptest/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package httptest_test 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "log" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | "github.com/bogdanfinn/fhttp/httptest" 14 | ) 15 | 16 | func ExampleResponseRecorder() { 17 | handler := func(w http.ResponseWriter, r *http.Request) { 18 | io.WriteString(w, "Hello World!") 19 | } 20 | 21 | req := httptest.NewRequest("GET", "http://example.com/foo", nil) 22 | w := httptest.NewRecorder() 23 | handler(w, req) 24 | 25 | resp := w.Result() 26 | body, _ := io.ReadAll(resp.Body) 27 | 28 | fmt.Println(resp.StatusCode) 29 | fmt.Println(resp.Header.Get("Content-Type")) 30 | fmt.Println(string(body)) 31 | 32 | // Output: 33 | // 200 34 | // text/html; charset=utf-8 35 | // Hello World! 36 | } 37 | 38 | func ExampleServer() { 39 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 | fmt.Fprintln(w, "Hello, client") 41 | })) 42 | defer ts.Close() 43 | 44 | res, err := http.Get(ts.URL) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | greeting, err := io.ReadAll(res.Body) 49 | res.Body.Close() 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | fmt.Printf("%s", greeting) 55 | // Output: Hello, client 56 | } 57 | 58 | func ExampleServer_hTTP2() { 59 | ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 60 | fmt.Fprintf(w, "Hello, %s", r.Proto) 61 | })) 62 | ts.EnableHTTP2 = true 63 | ts.StartTLS() 64 | defer ts.Close() 65 | 66 | res, err := ts.Client().Get(ts.URL) 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | greeting, err := io.ReadAll(res.Body) 71 | res.Body.Close() 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | fmt.Printf("%s", greeting) 76 | 77 | // Output: Hello, HTTP/2.0 78 | } 79 | 80 | func ExampleNewTLSServer() { 81 | ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 82 | fmt.Fprintln(w, "Hello, client") 83 | })) 84 | defer ts.Close() 85 | 86 | client := ts.Client() 87 | res, err := client.Get(ts.URL) 88 | if err != nil { 89 | log.Fatal(err) 90 | } 91 | 92 | greeting, err := io.ReadAll(res.Body) 93 | res.Body.Close() 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | 98 | fmt.Printf("%s", greeting) 99 | // Output: Hello, client 100 | } 101 | -------------------------------------------------------------------------------- /httptest/httptest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package httptest provides utilities for HTTP testing. 6 | package httptest 7 | 8 | import ( 9 | "bufio" 10 | "bytes" 11 | "io" 12 | "strings" 13 | 14 | tls "github.com/bogdanfinn/utls" 15 | 16 | http "github.com/bogdanfinn/fhttp" 17 | ) 18 | 19 | // NewRequest returns a new incoming server Request, suitable 20 | // for passing to an http.Handler for testing. 21 | // 22 | // The target is the RFC 7230 "request-target": it may be either a 23 | // path or an absolute URL. If target is an absolute URL, the host name 24 | // from the URL is used. Otherwise, "example.com" is used. 25 | // 26 | // The TLS field is set to a non-nil dummy value if target has scheme 27 | // "https". 28 | // 29 | // The Request.Proto is always HTTP/1.1. 30 | // 31 | // An empty method means "GET". 32 | // 33 | // The provided body may be nil. If the body is of type *bytes.Reader, 34 | // *strings.Reader, or *bytes.Buffer, the Request.ContentLength is 35 | // set. 36 | // 37 | // NewRequest panics on error for ease of use in testing, where a 38 | // panic is acceptable. 39 | // 40 | // To generate a client HTTP request instead of a server request, see 41 | // the NewRequest function in the net/http package. 42 | func NewRequest(method, target string, body io.Reader) *http.Request { 43 | if method == "" { 44 | method = "GET" 45 | } 46 | req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(method + " " + target + " HTTP/1.0\r\n\r\n"))) 47 | if err != nil { 48 | panic("invalid NewRequest arguments; " + err.Error()) 49 | } 50 | 51 | // HTTP/1.0 was used above to avoid needing a Host field. Change it to 1.1 here. 52 | req.Proto = "HTTP/1.1" 53 | req.ProtoMinor = 1 54 | req.Close = false 55 | 56 | if body != nil { 57 | switch v := body.(type) { 58 | case *bytes.Buffer: 59 | req.ContentLength = int64(v.Len()) 60 | case *bytes.Reader: 61 | req.ContentLength = int64(v.Len()) 62 | case *strings.Reader: 63 | req.ContentLength = int64(v.Len()) 64 | default: 65 | req.ContentLength = -1 66 | } 67 | if rc, ok := body.(io.ReadCloser); ok { 68 | req.Body = rc 69 | } else { 70 | req.Body = io.NopCloser(body) 71 | } 72 | } 73 | 74 | // 192.0.2.0/24 is "TEST-NET" in RFC 5737 for use solely in 75 | // documentation and example source code and should not be 76 | // used publicly. 77 | req.RemoteAddr = "192.0.2.1:1234" 78 | 79 | if req.Host == "" { 80 | req.Host = "example.com" 81 | } 82 | 83 | if strings.HasPrefix(target, "https://") { 84 | req.TLS = &tls.ConnectionState{ 85 | Version: tls.VersionTLS12, 86 | HandshakeComplete: true, 87 | ServerName: req.Host, 88 | } 89 | } 90 | 91 | return req 92 | } 93 | -------------------------------------------------------------------------------- /httptest/httptest_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package httptest 6 | 7 | import ( 8 | "io" 9 | "net/url" 10 | "reflect" 11 | "strings" 12 | "testing" 13 | 14 | tls "github.com/bogdanfinn/utls" 15 | 16 | http "github.com/bogdanfinn/fhttp" 17 | ) 18 | 19 | func TestNewRequest(t *testing.T) { 20 | for _, tt := range [...]struct { 21 | name string 22 | 23 | method, uri string 24 | body io.Reader 25 | 26 | want *http.Request 27 | wantBody string 28 | }{ 29 | { 30 | name: "Empty method means GET", 31 | method: "", 32 | uri: "/", 33 | body: nil, 34 | want: &http.Request{ 35 | Method: "GET", 36 | Host: "example.com", 37 | URL: &url.URL{Path: "/"}, 38 | Header: http.Header{}, 39 | Proto: "HTTP/1.1", 40 | ProtoMajor: 1, 41 | ProtoMinor: 1, 42 | RemoteAddr: "192.0.2.1:1234", 43 | RequestURI: "/", 44 | }, 45 | wantBody: "", 46 | }, 47 | 48 | { 49 | name: "GET with full URL", 50 | method: "GET", 51 | uri: "http://foo.com/path/%2f/bar/", 52 | body: nil, 53 | want: &http.Request{ 54 | Method: "GET", 55 | Host: "foo.com", 56 | URL: &url.URL{ 57 | Scheme: "http", 58 | Path: "/path///bar/", 59 | RawPath: "/path/%2f/bar/", 60 | Host: "foo.com", 61 | }, 62 | Header: http.Header{}, 63 | Proto: "HTTP/1.1", 64 | ProtoMajor: 1, 65 | ProtoMinor: 1, 66 | RemoteAddr: "192.0.2.1:1234", 67 | RequestURI: "http://foo.com/path/%2f/bar/", 68 | }, 69 | wantBody: "", 70 | }, 71 | 72 | { 73 | name: "GET with full https URL", 74 | method: "GET", 75 | uri: "https://foo.com/path/", 76 | body: nil, 77 | want: &http.Request{ 78 | Method: "GET", 79 | Host: "foo.com", 80 | URL: &url.URL{ 81 | Scheme: "https", 82 | Path: "/path/", 83 | Host: "foo.com", 84 | }, 85 | Header: http.Header{}, 86 | Proto: "HTTP/1.1", 87 | ProtoMajor: 1, 88 | ProtoMinor: 1, 89 | RemoteAddr: "192.0.2.1:1234", 90 | RequestURI: "https://foo.com/path/", 91 | TLS: &tls.ConnectionState{ 92 | Version: tls.VersionTLS12, 93 | HandshakeComplete: true, 94 | ServerName: "foo.com", 95 | }, 96 | }, 97 | wantBody: "", 98 | }, 99 | 100 | { 101 | name: "Post with known length", 102 | method: "POST", 103 | uri: "/", 104 | body: strings.NewReader("foo"), 105 | want: &http.Request{ 106 | Method: "POST", 107 | Host: "example.com", 108 | URL: &url.URL{Path: "/"}, 109 | Header: http.Header{}, 110 | Proto: "HTTP/1.1", 111 | ContentLength: 3, 112 | ProtoMajor: 1, 113 | ProtoMinor: 1, 114 | RemoteAddr: "192.0.2.1:1234", 115 | RequestURI: "/", 116 | }, 117 | wantBody: "foo", 118 | }, 119 | 120 | { 121 | name: "Post with unknown length", 122 | method: "POST", 123 | uri: "/", 124 | body: struct{ io.Reader }{strings.NewReader("foo")}, 125 | want: &http.Request{ 126 | Method: "POST", 127 | Host: "example.com", 128 | URL: &url.URL{Path: "/"}, 129 | Header: http.Header{}, 130 | Proto: "HTTP/1.1", 131 | ContentLength: -1, 132 | ProtoMajor: 1, 133 | ProtoMinor: 1, 134 | RemoteAddr: "192.0.2.1:1234", 135 | RequestURI: "/", 136 | }, 137 | wantBody: "foo", 138 | }, 139 | 140 | { 141 | name: "OPTIONS *", 142 | method: "OPTIONS", 143 | uri: "*", 144 | want: &http.Request{ 145 | Method: "OPTIONS", 146 | Host: "example.com", 147 | URL: &url.URL{Path: "*"}, 148 | Header: http.Header{}, 149 | Proto: "HTTP/1.1", 150 | ProtoMajor: 1, 151 | ProtoMinor: 1, 152 | RemoteAddr: "192.0.2.1:1234", 153 | RequestURI: "*", 154 | }, 155 | }, 156 | } { 157 | t.Run(tt.name, func(t *testing.T) { 158 | got := NewRequest(tt.method, tt.uri, tt.body) 159 | slurp, err := io.ReadAll(got.Body) 160 | if err != nil { 161 | t.Errorf("ReadAll: %v", err) 162 | } 163 | if string(slurp) != tt.wantBody { 164 | t.Errorf("Body = %q; want %q", slurp, tt.wantBody) 165 | } 166 | got.Body = nil // before DeepEqual 167 | if !reflect.DeepEqual(got.URL, tt.want.URL) { 168 | t.Errorf("Request.URL mismatch:\n got: %#v\nwant: %#v", got.URL, tt.want.URL) 169 | } 170 | if !reflect.DeepEqual(got.Header, tt.want.Header) { 171 | t.Errorf("Request.Header mismatch:\n got: %#v\nwant: %#v", got.Header, tt.want.Header) 172 | } 173 | if !reflect.DeepEqual(got.TLS, tt.want.TLS) { 174 | t.Errorf("Request.TLS mismatch:\n got: %#v\nwant: %#v", got.TLS, tt.want.TLS) 175 | } 176 | if !reflect.DeepEqual(got, tt.want) { 177 | t.Errorf("Request mismatch:\n got: %#v\nwant: %#v", got, tt.want) 178 | } 179 | }) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /httptrace/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package httptrace_test 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "net/http" 11 | 12 | "github.com/bogdanfinn/fhttp/httptrace" 13 | ) 14 | 15 | func Example() { 16 | req, _ := http.NewRequest("GET", "http://example.com", nil) 17 | trace := &httptrace.ClientTrace{ 18 | GotConn: func(connInfo httptrace.GotConnInfo) { 19 | fmt.Printf("Got Conn: %+v\n", connInfo) 20 | }, 21 | DNSDone: func(dnsInfo httptrace.DNSDoneInfo) { 22 | fmt.Printf("DNS Info: %+v\n", dnsInfo) 23 | }, 24 | } 25 | req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) 26 | _, err := http.DefaultTransport.RoundTrip(req) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /httptrace/trace_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package httptrace 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "testing" 11 | ) 12 | 13 | func TestWithClientTrace(t *testing.T) { 14 | var buf bytes.Buffer 15 | connectStart := func(b byte) func(network, addr string) { 16 | return func(network, addr string) { 17 | buf.WriteByte(b) 18 | } 19 | } 20 | 21 | ctx := context.Background() 22 | oldtrace := &ClientTrace{ 23 | ConnectStart: connectStart('O'), 24 | } 25 | ctx = WithClientTrace(ctx, oldtrace) 26 | newtrace := &ClientTrace{ 27 | ConnectStart: connectStart('N'), 28 | } 29 | ctx = WithClientTrace(ctx, newtrace) 30 | trace := ContextClientTrace(ctx) 31 | 32 | buf.Reset() 33 | trace.ConnectStart("net", "addr") 34 | if got, want := buf.String(), "NO"; got != want { 35 | t.Errorf("got %q; want %q", got, want) 36 | } 37 | } 38 | 39 | func TestCompose(t *testing.T) { 40 | var buf bytes.Buffer 41 | var testNum int 42 | 43 | connectStart := func(b byte) func(network, addr string) { 44 | return func(network, addr string) { 45 | if addr != "addr" { 46 | t.Errorf(`%d. args for %q case = %q, %q; want addr of "addr"`, testNum, b, network, addr) 47 | } 48 | buf.WriteByte(b) 49 | } 50 | } 51 | 52 | tests := [...]struct { 53 | trace, old *ClientTrace 54 | want string 55 | }{ 56 | 0: { 57 | want: "T", 58 | trace: &ClientTrace{ 59 | ConnectStart: connectStart('T'), 60 | }, 61 | }, 62 | 1: { 63 | want: "TO", 64 | trace: &ClientTrace{ 65 | ConnectStart: connectStart('T'), 66 | }, 67 | old: &ClientTrace{ConnectStart: connectStart('O')}, 68 | }, 69 | 2: { 70 | want: "O", 71 | trace: &ClientTrace{}, 72 | old: &ClientTrace{ConnectStart: connectStart('O')}, 73 | }, 74 | } 75 | for i, tt := range tests { 76 | testNum = i 77 | buf.Reset() 78 | 79 | tr := *tt.trace 80 | tr.compose(tt.old) 81 | if tr.ConnectStart != nil { 82 | tr.ConnectStart("net", "addr") 83 | } 84 | if got := buf.String(); got != tt.want { 85 | t.Errorf("%d. got = %q; want %q", i, got, tt.want) 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /httputil/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package httputil_test 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "log" 11 | "net/url" 12 | "strings" 13 | 14 | http "github.com/bogdanfinn/fhttp" 15 | "github.com/bogdanfinn/fhttp/httptest" 16 | "github.com/bogdanfinn/fhttp/httputil" 17 | ) 18 | 19 | func ExampleDumpRequest() { 20 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 21 | dump, err := httputil.DumpRequest(r, true) 22 | if err != nil { 23 | http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 24 | return 25 | } 26 | 27 | fmt.Fprintf(w, "%q", dump) 28 | })) 29 | defer ts.Close() 30 | 31 | const body = "Go is a general-purpose language designed with systems programming in mind." 32 | req, err := http.NewRequest("POST", ts.URL, strings.NewReader(body)) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | req.Host = "www.example.org" 37 | resp, err := http.DefaultClient.Do(req) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | defer resp.Body.Close() 42 | 43 | b, err := io.ReadAll(resp.Body) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | fmt.Printf("%s", b) 49 | 50 | // Output: 51 | // "POST / HTTP/1.1\r\nHost: www.example.org\r\nAccept-Encoding: gzip\r\nContent-Length: 75\r\nUser-Agent: Go-http-client/1.1\r\n\r\nGo is a general-purpose language designed with systems programming in mind." 52 | } 53 | 54 | func ExampleDumpRequestOut() { 55 | const body = "Go is a general-purpose language designed with systems programming in mind." 56 | req, err := http.NewRequest("PUT", "http://www.example.org", strings.NewReader(body)) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | dump, err := httputil.DumpRequestOut(req, true) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | 66 | fmt.Printf("%q", dump) 67 | 68 | // Output: 69 | // "PUT / HTTP/1.1\r\nHost: www.example.org\r\nUser-Agent: Go-http-client/1.1\r\nContent-Length: 75\r\nAccept-Encoding: gzip\r\n\r\nGo is a general-purpose language designed with systems programming in mind." 70 | } 71 | 72 | func ExampleDumpResponse() { 73 | const body = "Go is a general-purpose language designed with systems programming in mind." 74 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 75 | w.Header().Set("Date", "Wed, 19 Jul 1972 19:00:00 GMT") 76 | fmt.Fprintln(w, body) 77 | })) 78 | defer ts.Close() 79 | 80 | resp, err := http.Get(ts.URL) 81 | if err != nil { 82 | log.Fatal(err) 83 | } 84 | defer resp.Body.Close() 85 | 86 | dump, err := httputil.DumpResponse(resp, true) 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | 91 | fmt.Printf("%q", dump) 92 | 93 | // Output: 94 | // "HTTP/1.1 200 OK\r\nContent-Length: 76\r\nContent-Type: text/plain; charset=utf-8\r\nDate: Wed, 19 Jul 1972 19:00:00 GMT\r\n\r\nGo is a general-purpose language designed with systems programming in mind.\n" 95 | } 96 | 97 | func ExampleReverseProxy() { 98 | backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 99 | fmt.Fprintln(w, "this call was relayed by the reverse proxy") 100 | })) 101 | defer backendServer.Close() 102 | 103 | rpURL, err := url.Parse(backendServer.URL) 104 | if err != nil { 105 | log.Fatal(err) 106 | } 107 | frontendProxy := httptest.NewServer(httputil.NewSingleHostReverseProxy(rpURL)) 108 | defer frontendProxy.Close() 109 | 110 | resp, err := http.Get(frontendProxy.URL) 111 | if err != nil { 112 | log.Fatal(err) 113 | } 114 | 115 | b, err := io.ReadAll(resp.Body) 116 | if err != nil { 117 | log.Fatal(err) 118 | } 119 | 120 | fmt.Printf("%s", b) 121 | 122 | // Output: 123 | // this call was relayed by the reverse proxy 124 | } 125 | -------------------------------------------------------------------------------- /httputil/httputil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package httputil provides HTTP utility functions, complementing the 6 | // more common ones in the net/http package. 7 | package httputil 8 | 9 | import ( 10 | "io" 11 | 12 | "github.com/bogdanfinn/fhttp/internal" 13 | ) 14 | 15 | // NewChunkedReader returns a new chunkedReader that translates the data read from r 16 | // out of HTTP "chunked" format before returning it. 17 | // The chunkedReader returns io.EOF when the final 0-length chunk is read. 18 | // 19 | // NewChunkedReader is not needed by normal applications. The http package 20 | // automatically decodes chunking when reading response bodies. 21 | func NewChunkedReader(r io.Reader) io.Reader { 22 | return internal.NewChunkedReader(r) 23 | } 24 | 25 | // NewChunkedWriter returns a new chunkedWriter that translates writes into HTTP 26 | // "chunked" format before writing them to w. Closing the returned chunkedWriter 27 | // sends the final 0-length chunk that marks the end of the stream but does 28 | // not send the final CRLF that appears after trailers; trailers and the last 29 | // CRLF must be written separately. 30 | // 31 | // NewChunkedWriter is not needed by normal applications. The http 32 | // package adds chunking automatically if handlers don't set a 33 | // Content-Length header. Using NewChunkedWriter inside a handler 34 | // would result in double chunking or chunking with a Content-Length 35 | // length, both of which are wrong. 36 | func NewChunkedWriter(w io.Writer) io.WriteCloser { 37 | return internal.NewChunkedWriter(w) 38 | } 39 | 40 | // ErrLineTooLong is returned when reading malformed chunked data 41 | // with lines that are too long. 42 | var ErrLineTooLong = internal.ErrLineTooLong 43 | -------------------------------------------------------------------------------- /internal/cfg/cfg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package cfg holds configuration shared by the Go command and internal/testenv. 6 | // Definitions that don't need to be exposed outside of cmd/go should be in 7 | // cmd/go/internal/cfg instead of this package. 8 | package cfg 9 | 10 | // KnownEnv is a list of environment variables that affect the operation 11 | // of the Go command. 12 | const KnownEnv = ` 13 | AR 14 | CC 15 | CGO_CFLAGS 16 | CGO_CFLAGS_ALLOW 17 | CGO_CFLAGS_DISALLOW 18 | CGO_CPPFLAGS 19 | CGO_CPPFLAGS_ALLOW 20 | CGO_CPPFLAGS_DISALLOW 21 | CGO_CXXFLAGS 22 | CGO_CXXFLAGS_ALLOW 23 | CGO_CXXFLAGS_DISALLOW 24 | CGO_ENABLED 25 | CGO_FFLAGS 26 | CGO_FFLAGS_ALLOW 27 | CGO_FFLAGS_DISALLOW 28 | CGO_LDFLAGS 29 | CGO_LDFLAGS_ALLOW 30 | CGO_LDFLAGS_DISALLOW 31 | CXX 32 | FC 33 | GCCGO 34 | GO111MODULE 35 | GO386 36 | GOARCH 37 | GOARM 38 | GOBIN 39 | GOCACHE 40 | GOENV 41 | GOEXE 42 | GOFLAGS 43 | GOGCCFLAGS 44 | GOHOSTARCH 45 | GOHOSTOS 46 | GOINSECURE 47 | GOMIPS 48 | GOMIPS64 49 | GOMODCACHE 50 | GONOPROXY 51 | GONOSUMDB 52 | GOOS 53 | GOPATH 54 | GOPPC64 55 | GOPRIVATE 56 | GOPROXY 57 | GOROOT 58 | GOSUMDB 59 | GOTMPDIR 60 | GOTOOLDIR 61 | GOVCS 62 | GOWASM 63 | GO_EXTLINK_ENABLED 64 | PKG_CONFIG 65 | ` 66 | -------------------------------------------------------------------------------- /internal/nettrace/nettrace.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package nettrace contains internal hooks for tracing activity in 6 | // the net package. This package is purely internal for use by the 7 | // net/http/httptrace package and has no stable API exposed to end 8 | // users. 9 | package nettrace 10 | 11 | // TraceKey is a context.Context Value key. Its associated value should 12 | // be a *Trace struct. 13 | type TraceKey struct{} 14 | 15 | // LookupIPAltResolverKey is a context.Context Value key used by tests to 16 | // specify an alternate resolver func. 17 | // It is not exposed to outsider users. (But see issue 12503) 18 | // The value should be the same type as lookupIP: 19 | // func lookupIP(ctx context.Context, host string) ([]IPAddr, error) 20 | type LookupIPAltResolverKey struct{} 21 | 22 | // Trace contains a set of hooks for tracing events within 23 | // the net package. Any specific hook may be nil. 24 | type Trace struct { 25 | // DNSStart is called with the hostname of a DNS lookup 26 | // before it begins. 27 | DNSStart func(name string) 28 | 29 | // DNSDone is called after a DNS lookup completes (or fails). 30 | // The coalesced parameter is whether singleflight de-dupped 31 | // the call. The addrs are of type net.IPAddr but can't 32 | // actually be for circular dependency reasons. 33 | DNSDone func(netIPs []interface{}, coalesced bool, err error) 34 | 35 | // ConnectStart is called before a Dial, excluding Dials made 36 | // during DNS lookups. In the case of DualStack (Happy Eyeballs) 37 | // dialing, this may be called multiple times, from multiple 38 | // goroutines. 39 | ConnectStart func(network, addr string) 40 | 41 | // ConnectStart is called after a Dial with the results, excluding 42 | // Dials made during DNS lookups. It may also be called multiple 43 | // times, like ConnectStart. 44 | ConnectDone func(network, addr string, err error) 45 | } 46 | -------------------------------------------------------------------------------- /internal/profile/filter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Implements methods to filter samples from profiles. 6 | 7 | package profile 8 | 9 | import "regexp" 10 | 11 | // FilterSamplesByName filters the samples in a profile and only keeps 12 | // samples where at least one frame matches focus but none match ignore. 13 | // Returns true is the corresponding regexp matched at least one sample. 14 | func (p *Profile) FilterSamplesByName(focus, ignore, hide *regexp.Regexp) (fm, im, hm bool) { 15 | focusOrIgnore := make(map[uint64]bool) 16 | hidden := make(map[uint64]bool) 17 | for _, l := range p.Location { 18 | if ignore != nil && l.matchesName(ignore) { 19 | im = true 20 | focusOrIgnore[l.ID] = false 21 | } else if focus == nil || l.matchesName(focus) { 22 | fm = true 23 | focusOrIgnore[l.ID] = true 24 | } 25 | if hide != nil && l.matchesName(hide) { 26 | hm = true 27 | l.Line = l.unmatchedLines(hide) 28 | if len(l.Line) == 0 { 29 | hidden[l.ID] = true 30 | } 31 | } 32 | } 33 | 34 | s := make([]*Sample, 0, len(p.Sample)) 35 | for _, sample := range p.Sample { 36 | if focusedAndNotIgnored(sample.Location, focusOrIgnore) { 37 | if len(hidden) > 0 { 38 | var locs []*Location 39 | for _, loc := range sample.Location { 40 | if !hidden[loc.ID] { 41 | locs = append(locs, loc) 42 | } 43 | } 44 | if len(locs) == 0 { 45 | // Remove sample with no locations (by not adding it to s). 46 | continue 47 | } 48 | sample.Location = locs 49 | } 50 | s = append(s, sample) 51 | } 52 | } 53 | p.Sample = s 54 | 55 | return 56 | } 57 | 58 | // matchesName reports whether the function name or file in the 59 | // location matches the regular expression. 60 | func (loc *Location) matchesName(re *regexp.Regexp) bool { 61 | for _, ln := range loc.Line { 62 | if fn := ln.Function; fn != nil { 63 | if re.MatchString(fn.Name) { 64 | return true 65 | } 66 | if re.MatchString(fn.Filename) { 67 | return true 68 | } 69 | } 70 | } 71 | return false 72 | } 73 | 74 | // unmatchedLines returns the lines in the location that do not match 75 | // the regular expression. 76 | func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line { 77 | var lines []Line 78 | for _, ln := range loc.Line { 79 | if fn := ln.Function; fn != nil { 80 | if re.MatchString(fn.Name) { 81 | continue 82 | } 83 | if re.MatchString(fn.Filename) { 84 | continue 85 | } 86 | } 87 | lines = append(lines, ln) 88 | } 89 | return lines 90 | } 91 | 92 | // focusedAndNotIgnored looks up a slice of ids against a map of 93 | // focused/ignored locations. The map only contains locations that are 94 | // explicitly focused or ignored. Returns whether there is at least 95 | // one focused location but no ignored locations. 96 | func focusedAndNotIgnored(locs []*Location, m map[uint64]bool) bool { 97 | var f bool 98 | for _, loc := range locs { 99 | if focus, focusOrIgnore := m[loc.ID]; focusOrIgnore { 100 | if focus { 101 | // Found focused location. Must keep searching in case there 102 | // is an ignored one as well. 103 | f = true 104 | } else { 105 | // Found ignored location. Can return false right away. 106 | return false 107 | } 108 | } 109 | } 110 | return f 111 | } 112 | 113 | // TagMatch selects tags for filtering 114 | type TagMatch func(key, val string, nval int64) bool 115 | 116 | // FilterSamplesByTag removes all samples from the profile, except 117 | // those that match focus and do not match the ignore regular 118 | // expression. 119 | func (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) { 120 | samples := make([]*Sample, 0, len(p.Sample)) 121 | for _, s := range p.Sample { 122 | focused, ignored := focusedSample(s, focus, ignore) 123 | fm = fm || focused 124 | im = im || ignored 125 | if focused && !ignored { 126 | samples = append(samples, s) 127 | } 128 | } 129 | p.Sample = samples 130 | return 131 | } 132 | 133 | // focusedTag checks a sample against focus and ignore regexps. 134 | // Returns whether the focus/ignore regexps match any tags 135 | func focusedSample(s *Sample, focus, ignore TagMatch) (fm, im bool) { 136 | fm = focus == nil 137 | for key, vals := range s.Label { 138 | for _, val := range vals { 139 | if ignore != nil && ignore(key, val, 0) { 140 | im = true 141 | } 142 | if !fm && focus(key, val, 0) { 143 | fm = true 144 | } 145 | } 146 | } 147 | for key, vals := range s.NumLabel { 148 | for _, val := range vals { 149 | if ignore != nil && ignore(key, "", val) { 150 | im = true 151 | } 152 | if !fm && focus(key, "", val) { 153 | fm = true 154 | } 155 | } 156 | } 157 | return fm, im 158 | } 159 | -------------------------------------------------------------------------------- /internal/profile/profile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package profile 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | func TestEmptyProfile(t *testing.T) { 13 | var buf bytes.Buffer 14 | p, err := Parse(&buf) 15 | if err != nil { 16 | t.Error("Want no error, got", err) 17 | } 18 | if p == nil { 19 | t.Fatal("Want a valid profile, got ") 20 | } 21 | if !p.Empty() { 22 | t.Errorf("Profile should be empty, got %#v", p) 23 | } 24 | } 25 | 26 | func TestParseContention(t *testing.T) { 27 | tests := []struct { 28 | name string 29 | in string 30 | wantErr bool 31 | }{ 32 | { 33 | name: "valid", 34 | in: `--- mutex: 35 | cycles/second=3491920901 36 | sampling period=1 37 | 43227965305 1659640 @ 0x45e851 0x45f764 0x4a2be1 0x44ea31 38 | 34035731690 15760 @ 0x45e851 0x45f764 0x4a2b17 0x44ea31 39 | `, 40 | }, 41 | { 42 | name: "valid with comment", 43 | in: `--- mutex: 44 | cycles/second=3491920901 45 | sampling period=1 46 | 43227965305 1659640 @ 0x45e851 0x45f764 0x4a2be1 0x44ea31 47 | # 0x45e850 sync.(*Mutex).Unlock+0x80 /go/src/sync/mutex.go:126 48 | # 0x45f763 sync.(*RWMutex).Unlock+0x83 /go/src/sync/rwmutex.go:125 49 | # 0x4a2be0 main.main.func3+0x70 /go/src/internal/pprof/profile/a_binary.go:58 50 | 51 | 34035731690 15760 @ 0x45e851 0x45f764 0x4a2b17 0x44ea31 52 | # 0x45e850 sync.(*Mutex).Unlock+0x80 /go/src/sync/mutex.go:126 53 | # 0x45f763 sync.(*RWMutex).Unlock+0x83 /go/src/sync/rwmutex.go:125 54 | # 0x4a2b16 main.main.func2+0xd6 /go/src/internal/pprof/profile/a_binary.go:48 55 | `, 56 | }, 57 | { 58 | name: "empty", 59 | in: `--- mutex:`, 60 | wantErr: true, 61 | }, 62 | { 63 | name: "invalid header", 64 | in: `--- channel: 65 | 43227965305 1659640 @ 0x45e851 0x45f764 0x4a2be1 0x44ea31`, 66 | wantErr: true, 67 | }, 68 | } 69 | for _, tc := range tests { 70 | _, err := parseContention([]byte(tc.in)) 71 | if tc.wantErr && err == nil { 72 | t.Errorf("parseContention(%q) succeeded unexpectedly", tc.name) 73 | } 74 | if !tc.wantErr && err != nil { 75 | t.Errorf("parseContention(%q) failed unexpectedly: %v", tc.name, err) 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /internal/profile/proto_test.go: -------------------------------------------------------------------------------- 1 | package profile 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPackedEncoding(t *testing.T) { 9 | 10 | type testcase struct { 11 | uint64s []uint64 12 | int64s []int64 13 | encoded []byte 14 | } 15 | for i, tc := range []testcase{ 16 | { 17 | []uint64{0, 1, 10, 100, 1000, 10000}, 18 | []int64{1000, 0, 1000}, 19 | []byte{10, 8, 0, 1, 10, 100, 232, 7, 144, 78, 18, 5, 232, 7, 0, 232, 7}, 20 | }, 21 | { 22 | []uint64{10000}, 23 | nil, 24 | []byte{8, 144, 78}, 25 | }, 26 | { 27 | nil, 28 | []int64{-10000}, 29 | []byte{16, 240, 177, 255, 255, 255, 255, 255, 255, 255, 1}, 30 | }, 31 | } { 32 | source := &packedInts{tc.uint64s, tc.int64s} 33 | if got, want := marshal(source), tc.encoded; !reflect.DeepEqual(got, want) { 34 | t.Errorf("failed encode %d, got %v, want %v", i, got, want) 35 | } 36 | 37 | dest := new(packedInts) 38 | if err := unmarshal(tc.encoded, dest); err != nil { 39 | t.Errorf("failed decode %d: %v", i, err) 40 | continue 41 | } 42 | if got, want := dest.uint64s, tc.uint64s; !reflect.DeepEqual(got, want) { 43 | t.Errorf("failed decode uint64s %d, got %v, want %v", i, got, want) 44 | } 45 | if got, want := dest.int64s, tc.int64s; !reflect.DeepEqual(got, want) { 46 | t.Errorf("failed decode int64s %d, got %v, want %v", i, got, want) 47 | } 48 | } 49 | } 50 | 51 | type packedInts struct { 52 | uint64s []uint64 53 | int64s []int64 54 | } 55 | 56 | func (u *packedInts) decoder() []decoder { 57 | return []decoder{ 58 | nil, 59 | func(b *buffer, m message) error { return decodeUint64s(b, &m.(*packedInts).uint64s) }, 60 | func(b *buffer, m message) error { return decodeInt64s(b, &m.(*packedInts).int64s) }, 61 | } 62 | } 63 | 64 | func (u *packedInts) encode(b *buffer) { 65 | encodeUint64s(b, 1, u.uint64s) 66 | encodeInt64s(b, 2, u.int64s) 67 | } 68 | -------------------------------------------------------------------------------- /internal/profile/prune.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Implements methods to remove frames from profiles. 6 | 7 | package profile 8 | 9 | import ( 10 | "fmt" 11 | "regexp" 12 | ) 13 | 14 | // Prune removes all nodes beneath a node matching dropRx, and not 15 | // matching keepRx. If the root node of a Sample matches, the sample 16 | // will have an empty stack. 17 | func (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) { 18 | prune := make(map[uint64]bool) 19 | pruneBeneath := make(map[uint64]bool) 20 | 21 | for _, loc := range p.Location { 22 | var i int 23 | for i = len(loc.Line) - 1; i >= 0; i-- { 24 | if fn := loc.Line[i].Function; fn != nil && fn.Name != "" { 25 | funcName := fn.Name 26 | // Account for leading '.' on the PPC ELF v1 ABI. 27 | if funcName[0] == '.' { 28 | funcName = funcName[1:] 29 | } 30 | if dropRx.MatchString(funcName) { 31 | if keepRx == nil || !keepRx.MatchString(funcName) { 32 | break 33 | } 34 | } 35 | } 36 | } 37 | 38 | if i >= 0 { 39 | // Found matching entry to prune. 40 | pruneBeneath[loc.ID] = true 41 | 42 | // Remove the matching location. 43 | if i == len(loc.Line)-1 { 44 | // Matched the top entry: prune the whole location. 45 | prune[loc.ID] = true 46 | } else { 47 | loc.Line = loc.Line[i+1:] 48 | } 49 | } 50 | } 51 | 52 | // Prune locs from each Sample 53 | for _, sample := range p.Sample { 54 | // Scan from the root to the leaves to find the prune location. 55 | // Do not prune frames before the first user frame, to avoid 56 | // pruning everything. 57 | foundUser := false 58 | for i := len(sample.Location) - 1; i >= 0; i-- { 59 | id := sample.Location[i].ID 60 | if !prune[id] && !pruneBeneath[id] { 61 | foundUser = true 62 | continue 63 | } 64 | if !foundUser { 65 | continue 66 | } 67 | if prune[id] { 68 | sample.Location = sample.Location[i+1:] 69 | break 70 | } 71 | if pruneBeneath[id] { 72 | sample.Location = sample.Location[i:] 73 | break 74 | } 75 | } 76 | } 77 | } 78 | 79 | // RemoveUninteresting prunes and elides profiles using built-in 80 | // tables of uninteresting function names. 81 | func (p *Profile) RemoveUninteresting() error { 82 | var keep, drop *regexp.Regexp 83 | var err error 84 | 85 | if p.DropFrames != "" { 86 | if drop, err = regexp.Compile("^(" + p.DropFrames + ")$"); err != nil { 87 | return fmt.Errorf("failed to compile regexp %s: %v", p.DropFrames, err) 88 | } 89 | if p.KeepFrames != "" { 90 | if keep, err = regexp.Compile("^(" + p.KeepFrames + ")$"); err != nil { 91 | return fmt.Errorf("failed to compile regexp %s: %v", p.KeepFrames, err) 92 | } 93 | } 94 | p.Prune(drop, keep) 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /internal/race/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package race contains helper functions for manually instrumenting code for the race detector. 7 | 8 | The runtime package intentionally exports these functions only in the race build; 9 | this package exports them unconditionally but without the "race" build tag they are no-ops. 10 | */ 11 | package race 12 | -------------------------------------------------------------------------------- /internal/race/norace.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !race 6 | 7 | package race 8 | 9 | import ( 10 | "unsafe" 11 | ) 12 | 13 | const Enabled = false 14 | 15 | func Acquire(addr unsafe.Pointer) { 16 | } 17 | 18 | func Release(addr unsafe.Pointer) { 19 | } 20 | 21 | func ReleaseMerge(addr unsafe.Pointer) { 22 | } 23 | 24 | func Disable() { 25 | } 26 | 27 | func Enable() { 28 | } 29 | 30 | func Read(addr unsafe.Pointer) { 31 | } 32 | 33 | func Write(addr unsafe.Pointer) { 34 | } 35 | 36 | func ReadRange(addr unsafe.Pointer, len int) { 37 | } 38 | 39 | func WriteRange(addr unsafe.Pointer, len int) { 40 | } 41 | 42 | func Errors() int { return 0 } 43 | -------------------------------------------------------------------------------- /internal/race/race.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build race 6 | 7 | package race 8 | 9 | import ( 10 | "runtime" 11 | "unsafe" 12 | ) 13 | 14 | const Enabled = true 15 | 16 | func Acquire(addr unsafe.Pointer) { 17 | runtime.RaceAcquire(addr) 18 | } 19 | 20 | func Release(addr unsafe.Pointer) { 21 | runtime.RaceRelease(addr) 22 | } 23 | 24 | func ReleaseMerge(addr unsafe.Pointer) { 25 | runtime.RaceReleaseMerge(addr) 26 | } 27 | 28 | func Disable() { 29 | runtime.RaceDisable() 30 | } 31 | 32 | func Enable() { 33 | runtime.RaceEnable() 34 | } 35 | 36 | func Read(addr unsafe.Pointer) { 37 | runtime.RaceRead(addr) 38 | } 39 | 40 | func Write(addr unsafe.Pointer) { 41 | runtime.RaceWrite(addr) 42 | } 43 | 44 | func ReadRange(addr unsafe.Pointer, len int) { 45 | runtime.RaceReadRange(addr, len) 46 | } 47 | 48 | func WriteRange(addr unsafe.Pointer, len int) { 49 | runtime.RaceWriteRange(addr, len) 50 | } 51 | 52 | func Errors() int { 53 | return runtime.RaceErrors() 54 | } 55 | -------------------------------------------------------------------------------- /internal/testcert.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package internal 6 | 7 | import "strings" 8 | 9 | // LocalhostCert is a PEM-encoded TLS cert with SAN IPs 10 | // "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT. 11 | // generated from src/crypto/tls: 12 | // go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h 13 | var LocalhostCert = []byte(`-----BEGIN CERTIFICATE----- 14 | MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS 15 | MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw 16 | MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB 17 | iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4 18 | iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul 19 | rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO 20 | BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw 21 | AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA 22 | AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9 23 | tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs 24 | h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM 25 | fblo6RBxUQ== 26 | -----END CERTIFICATE-----`) 27 | 28 | // LocalhostKey is the private key for localhostCert. 29 | var LocalhostKey = []byte(testingKey(`-----BEGIN RSA TESTING KEY----- 30 | MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9 31 | SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZB 32 | l2+XsDulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB 33 | AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet 34 | 3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb 35 | uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H 36 | qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp 37 | jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY 38 | fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U 39 | fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU 40 | y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIX 41 | qyUBnu3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo 42 | f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA== 43 | -----END RSA TESTING KEY-----`)) 44 | 45 | func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } 46 | -------------------------------------------------------------------------------- /internal/testenv/testenv_cgo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build cgo 6 | 7 | package testenv 8 | 9 | func init() { 10 | haveCGO = true 11 | } 12 | -------------------------------------------------------------------------------- /internal/testenv/testenv_notwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !windows 6 | 7 | package testenv 8 | 9 | import ( 10 | "runtime" 11 | ) 12 | 13 | func hasSymlink() (ok bool, reason string) { 14 | switch runtime.GOOS { 15 | case "android", "plan9": 16 | return false, "" 17 | } 18 | 19 | return true, "" 20 | } 21 | -------------------------------------------------------------------------------- /internal/testenv/testenv_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package testenv 6 | 7 | import ( 8 | "os" 9 | "path/filepath" 10 | "sync" 11 | "syscall" 12 | ) 13 | 14 | var symlinkOnce sync.Once 15 | var winSymlinkErr error 16 | 17 | func initWinHasSymlink() { 18 | tmpdir, err := os.MkdirTemp("", "symtest") 19 | if err != nil { 20 | panic("failed to create temp directory: " + err.Error()) 21 | } 22 | defer os.RemoveAll(tmpdir) 23 | 24 | err = os.Symlink("target", filepath.Join(tmpdir, "symlink")) 25 | if err != nil { 26 | err = err.(*os.LinkError).Err 27 | switch err { 28 | case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD: 29 | winSymlinkErr = err 30 | } 31 | } 32 | } 33 | 34 | func hasSymlink() (ok bool, reason string) { 35 | symlinkOnce.Do(initWinHasSymlink) 36 | 37 | switch winSymlinkErr { 38 | case nil: 39 | return true, "" 40 | case syscall.EWINDOWS: 41 | return false, ": symlinks are not supported on your version of Windows" 42 | case syscall.ERROR_PRIVILEGE_NOT_HELD: 43 | return false, ": you don't have enough privileges to create symlinks" 44 | } 45 | 46 | return false, "" 47 | } 48 | -------------------------------------------------------------------------------- /jar.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "net/url" 9 | ) 10 | 11 | // A CookieJar manages storage and use of cookies in HTTP requests. 12 | // 13 | // Implementations of CookieJar must be safe for concurrent use by multiple 14 | // goroutines. 15 | // 16 | // The net/http/cookiejar package provides a CookieJar implementation. 17 | type CookieJar interface { 18 | // SetCookies handles the receipt of the cookies in a reply for the 19 | // given URL. It may or may not choose to save the cookies, depending 20 | // on the jar's policy and implementation. 21 | SetCookies(u *url.URL, cookies []*Cookie) 22 | 23 | // Cookies returns the cookies to send in a request for the given URL. 24 | // It is up to the implementation to honor the standard cookie use 25 | // restrictions such as in RFC 6265. 26 | Cookies(u *url.URL) []*Cookie 27 | } 28 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http_test 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "log" 11 | "os" 12 | "runtime" 13 | "sort" 14 | "strings" 15 | "testing" 16 | "time" 17 | 18 | http "github.com/bogdanfinn/fhttp" 19 | ) 20 | 21 | var quietLog = log.New(io.Discard, "", 0) 22 | 23 | func TestMain(m *testing.M) { 24 | v := m.Run() 25 | if v == 0 && goroutineLeaked() { 26 | os.Exit(1) 27 | } 28 | os.Exit(v) 29 | } 30 | 31 | func interestingGoroutines() (gs []string) { 32 | buf := make([]byte, 2<<20) 33 | buf = buf[:runtime.Stack(buf, true)] 34 | for _, g := range strings.Split(string(buf), "\n\n") { 35 | sl := strings.SplitN(g, "\n", 2) 36 | if len(sl) != 2 { 37 | continue 38 | } 39 | stack := strings.TrimSpace(sl[1]) 40 | if stack == "" || 41 | strings.Contains(stack, "testing.(*M).before.func1") || 42 | strings.Contains(stack, "os/signal.signal_recv") || 43 | strings.Contains(stack, "created by net.startServer") || 44 | strings.Contains(stack, "created by testing.RunTests") || 45 | strings.Contains(stack, "closeWriteAndWait") || 46 | strings.Contains(stack, "testing.Main(") || 47 | // These only show up with GOTRACEBACK=2; Issue 5005 (comment 28) 48 | strings.Contains(stack, "runtime.goexit") || 49 | strings.Contains(stack, "created by runtime.gc") || 50 | strings.Contains(stack, "net/http_test.interestingGoroutines") || 51 | strings.Contains(stack, "runtime.MHeap_Scavenger") { 52 | continue 53 | } 54 | gs = append(gs, stack) 55 | } 56 | sort.Strings(gs) 57 | return 58 | } 59 | 60 | // Verify the other tests didn't leave any goroutines running. 61 | func goroutineLeaked() bool { 62 | if testing.Short() || runningBenchmarks() { 63 | // Don't worry about goroutine leaks in -short mode or in 64 | // benchmark mode. Too distracting when there are false positives. 65 | return false 66 | } 67 | 68 | var stackCount map[string]int 69 | for i := 0; i < 5; i++ { 70 | n := 0 71 | stackCount = make(map[string]int) 72 | gs := interestingGoroutines() 73 | for _, g := range gs { 74 | stackCount[g]++ 75 | n++ 76 | } 77 | if n == 0 { 78 | return false 79 | } 80 | // Wait for goroutines to schedule and die off: 81 | time.Sleep(100 * time.Millisecond) 82 | } 83 | fmt.Fprintf(os.Stderr, "Too many goroutines running after net/http test(s).\n") 84 | for stack, count := range stackCount { 85 | fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack) 86 | } 87 | return true 88 | } 89 | 90 | // setParallel marks t as a parallel test if we're in short mode 91 | // (all.bash), but as a serial test otherwise. Using t.Parallel isn't 92 | // compatible with the afterTest func in non-short mode. 93 | func setParallel(t *testing.T) { 94 | if strings.Contains(t.Name(), "HTTP2") { 95 | http.CondSkipHTTP2(t) 96 | } 97 | if testing.Short() { 98 | t.Parallel() 99 | } 100 | } 101 | 102 | func runningBenchmarks() bool { 103 | for i, arg := range os.Args { 104 | if strings.HasPrefix(arg, "-test.bench=") && !strings.HasSuffix(arg, "=") { 105 | return true 106 | } 107 | if arg == "-test.bench" && i < len(os.Args)-1 && os.Args[i+1] != "" { 108 | return true 109 | } 110 | } 111 | return false 112 | } 113 | 114 | func afterTest(t testing.TB) { 115 | http.DefaultTransport.(*http.Transport).CloseIdleConnections() 116 | if testing.Short() { 117 | return 118 | } 119 | var bad string 120 | badSubstring := map[string]string{ 121 | ").readLoop(": "a Transport", 122 | ").writeLoop(": "a Transport", 123 | "created by net/http/httptest.(*Server).Start": "an httptest.Server", 124 | "timeoutHandler": "a TimeoutHandler", 125 | "net.(*netFD).connect(": "a timing out dial", 126 | ").noteClientGone(": "a closenotifier sender", 127 | } 128 | var stacks string 129 | for i := 0; i < 10; i++ { 130 | bad = "" 131 | stacks = strings.Join(interestingGoroutines(), "\n\n") 132 | for substr, what := range badSubstring { 133 | if strings.Contains(stacks, substr) { 134 | bad = what 135 | } 136 | } 137 | if bad == "" { 138 | return 139 | } 140 | // Bad stuff found, but goroutines might just still be 141 | // shutting down, so give it some time. 142 | time.Sleep(250 * time.Millisecond) 143 | } 144 | t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks) 145 | } 146 | 147 | // waitCondition reports whether fn eventually returned true, 148 | // checking immediately and then every checkEvery amount, 149 | // until waitFor has elapsed, at which point it returns false. 150 | func waitCondition(waitFor, checkEvery time.Duration, fn func() bool) bool { 151 | deadline := time.Now().Add(waitFor) 152 | for time.Now().Before(deadline) { 153 | if fn() { 154 | return true 155 | } 156 | time.Sleep(checkEvery) 157 | } 158 | return false 159 | } 160 | 161 | // waitErrCondition is like waitCondition but with errors instead of bools. 162 | func waitErrCondition(waitFor, checkEvery time.Duration, fn func() error) error { 163 | deadline := time.Now().Add(waitFor) 164 | var err error 165 | for time.Now().Before(deadline) { 166 | if err = fn(); err == nil { 167 | return nil 168 | } 169 | time.Sleep(checkEvery) 170 | } 171 | return err 172 | } 173 | -------------------------------------------------------------------------------- /method.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | // Common HTTP methods. 8 | // 9 | // Unless otherwise noted, these are defined in RFC 7231 section 4.3. 10 | const ( 11 | MethodGet = "GET" 12 | MethodHead = "HEAD" 13 | MethodPost = "POST" 14 | MethodPut = "PUT" 15 | MethodPatch = "PATCH" // RFC 5789 16 | MethodDelete = "DELETE" 17 | MethodConnect = "CONNECT" 18 | MethodOptions = "OPTIONS" 19 | MethodTrace = "TRACE" 20 | ) 21 | -------------------------------------------------------------------------------- /omithttp2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build nethttpomithttp2 6 | 7 | package http 8 | 9 | import ( 10 | "errors" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | func init() { 16 | omitBundledHTTP2 = true 17 | } 18 | 19 | const noHTTP2 = "no bundled HTTP/2" // should never see this 20 | 21 | var http2errRequestCanceled = errors.New("net/http: request canceled") 22 | 23 | var http2goAwayTimeout = 1 * time.Second 24 | 25 | const http2NextProtoTLS = "h2" 26 | 27 | type http2Transport struct { 28 | MaxHeaderListSize uint32 29 | ConnPool interface{} 30 | } 31 | 32 | func (*http2Transport) RoundTrip(*Request) (*Response, error) { panic(noHTTP2) } 33 | func (*http2Transport) CloseIdleConnections() {} 34 | 35 | type http2noDialH2RoundTripper struct{} 36 | 37 | func (http2noDialH2RoundTripper) RoundTrip(*Request) (*Response, error) { panic(noHTTP2) } 38 | 39 | type http2noDialClientConnPool struct { 40 | http2clientConnPool http2clientConnPool 41 | } 42 | 43 | type http2clientConnPool struct { 44 | mu *sync.Mutex 45 | conns map[string][]struct{} 46 | } 47 | 48 | func http2configureTransports(*Transport) (*http2Transport, error) { panic(noHTTP2) } 49 | 50 | func http2isNoCachedConnError(err error) bool { 51 | _, ok := err.(interface{ IsHTTP2NoCachedConnError() }) 52 | return ok 53 | } 54 | 55 | type http2Server struct { 56 | NewWriteScheduler func() http2WriteScheduler 57 | } 58 | 59 | type http2WriteScheduler interface{} 60 | 61 | func http2NewPriorityWriteScheduler(interface{}) http2WriteScheduler { panic(noHTTP2) } 62 | 63 | func http2ConfigureServer(s *Server, conf *http2Server) error { panic(noHTTP2) } 64 | 65 | var http2ErrNoCachedConn = http2noCachedConnError{} 66 | 67 | type http2noCachedConnError struct{} 68 | 69 | func (http2noCachedConnError) IsHTTP2NoCachedConnError() {} 70 | 71 | func (http2noCachedConnError) Error() string { return "http2: no cached connection was available" } 72 | -------------------------------------------------------------------------------- /proxy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "net/url" 9 | "os" 10 | "testing" 11 | ) 12 | 13 | // TODO(mattn): 14 | // test ProxyAuth 15 | 16 | var cacheKeysTests = []struct { 17 | proxy string 18 | scheme string 19 | addr string 20 | key string 21 | }{ 22 | {"", "http", "foo.com", "|http|foo.com"}, 23 | {"", "https", "foo.com", "|https|foo.com"}, 24 | {"http://foo.com", "http", "foo.com", "http://foo.com|http|"}, 25 | {"http://foo.com", "https", "foo.com", "http://foo.com|https|foo.com"}, 26 | } 27 | 28 | func TestCacheKeys(t *testing.T) { 29 | for _, tt := range cacheKeysTests { 30 | var proxy *url.URL 31 | if tt.proxy != "" { 32 | u, err := url.Parse(tt.proxy) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | proxy = u 37 | } 38 | cm := connectMethod{proxyURL: proxy, targetScheme: tt.scheme, targetAddr: tt.addr} 39 | if got := cm.key().String(); got != tt.key { 40 | t.Fatalf("{%q, %q, %q} cache Key = %q; want %q", tt.proxy, tt.scheme, tt.addr, got, tt.key) 41 | } 42 | } 43 | } 44 | 45 | func ResetProxyEnv() { 46 | for _, v := range []string{"HTTP_PROXY", "http_proxy", "NO_PROXY", "no_proxy", "REQUEST_METHOD"} { 47 | os.Unsetenv(v) 48 | } 49 | ResetCachedEnvironment() 50 | } 51 | -------------------------------------------------------------------------------- /range_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | var ParseRangeTests = []struct { 12 | s string 13 | length int64 14 | r []httpRange 15 | }{ 16 | {"", 0, nil}, 17 | {"", 1000, nil}, 18 | {"foo", 0, nil}, 19 | {"bytes=", 0, nil}, 20 | {"bytes=7", 10, nil}, 21 | {"bytes= 7 ", 10, nil}, 22 | {"bytes=1-", 0, nil}, 23 | {"bytes=5-4", 10, nil}, 24 | {"bytes=0-2,5-4", 10, nil}, 25 | {"bytes=2-5,4-3", 10, nil}, 26 | {"bytes=--5,4--3", 10, nil}, 27 | {"bytes=A-", 10, nil}, 28 | {"bytes=A- ", 10, nil}, 29 | {"bytes=A-Z", 10, nil}, 30 | {"bytes= -Z", 10, nil}, 31 | {"bytes=5-Z", 10, nil}, 32 | {"bytes=Ran-dom, garbage", 10, nil}, 33 | {"bytes=0x01-0x02", 10, nil}, 34 | {"bytes= ", 10, nil}, 35 | {"bytes= , , , ", 10, nil}, 36 | 37 | {"bytes=0-9", 10, []httpRange{{0, 10}}}, 38 | {"bytes=0-", 10, []httpRange{{0, 10}}}, 39 | {"bytes=5-", 10, []httpRange{{5, 5}}}, 40 | {"bytes=0-20", 10, []httpRange{{0, 10}}}, 41 | {"bytes=15-,0-5", 10, []httpRange{{0, 6}}}, 42 | {"bytes=1-2,5-", 10, []httpRange{{1, 2}, {5, 5}}}, 43 | {"bytes=-2 , 7-", 11, []httpRange{{9, 2}, {7, 4}}}, 44 | {"bytes=0-0 ,2-2, 7-", 11, []httpRange{{0, 1}, {2, 1}, {7, 4}}}, 45 | {"bytes=-5", 10, []httpRange{{5, 5}}}, 46 | {"bytes=-15", 10, []httpRange{{0, 10}}}, 47 | {"bytes=0-499", 10000, []httpRange{{0, 500}}}, 48 | {"bytes=500-999", 10000, []httpRange{{500, 500}}}, 49 | {"bytes=-500", 10000, []httpRange{{9500, 500}}}, 50 | {"bytes=9500-", 10000, []httpRange{{9500, 500}}}, 51 | {"bytes=0-0,-1", 10000, []httpRange{{0, 1}, {9999, 1}}}, 52 | {"bytes=500-600,601-999", 10000, []httpRange{{500, 101}, {601, 399}}}, 53 | {"bytes=500-700,601-999", 10000, []httpRange{{500, 201}, {601, 399}}}, 54 | 55 | // Match Apache laxity: 56 | {"bytes= 1 -2 , 4- 5, 7 - 8 , ,,", 11, []httpRange{{1, 2}, {4, 2}, {7, 2}}}, 57 | } 58 | 59 | func TestParseRange(t *testing.T) { 60 | for _, test := range ParseRangeTests { 61 | r := test.r 62 | ranges, err := parseRange(test.s, test.length) 63 | if err != nil && r != nil { 64 | t.Errorf("parseRange(%q) returned error %q", test.s, err) 65 | } 66 | if len(ranges) != len(r) { 67 | t.Errorf("len(parseRange(%q)) = %d, want %d", test.s, len(ranges), len(r)) 68 | continue 69 | } 70 | for i := range r { 71 | if ranges[i].start != r[i].start { 72 | t.Errorf("parseRange(%q)[%d].start = %d, want %d", test.s, i, ranges[i].start, r[i].start) 73 | } 74 | if ranges[i].length != r[i].length { 75 | t.Errorf("parseRange(%q)[%d].length = %d, want %d", test.s, i, ranges[i].length, r[i].length) 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /roundtrip.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !js !wasm 6 | 7 | package http 8 | 9 | // RoundTrip implements the RoundTripper interface. 10 | // 11 | // For higher-level HTTP client support (such as handling of cookies 12 | // and redirects), see Get, Post, and the Client type. 13 | // 14 | // Like the RoundTripper interface, the error types returned 15 | // by RoundTrip are unspecified. 16 | func (t *Transport) RoundTrip(req *Request) (*Response, error) { 17 | return t.roundTrip(req) 18 | } 19 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Server unit tests 6 | 7 | package http 8 | 9 | import ( 10 | "fmt" 11 | "testing" 12 | ) 13 | 14 | func BenchmarkServerMatch(b *testing.B) { 15 | fn := func(w ResponseWriter, r *Request) { 16 | fmt.Fprintf(w, "OK") 17 | } 18 | mux := NewServeMux() 19 | mux.HandleFunc("/", fn) 20 | mux.HandleFunc("/index", fn) 21 | mux.HandleFunc("/home", fn) 22 | mux.HandleFunc("/about", fn) 23 | mux.HandleFunc("/contact", fn) 24 | mux.HandleFunc("/robots.txt", fn) 25 | mux.HandleFunc("/products/", fn) 26 | mux.HandleFunc("/products/1", fn) 27 | mux.HandleFunc("/products/2", fn) 28 | mux.HandleFunc("/products/3", fn) 29 | mux.HandleFunc("/products/3/image.jpg", fn) 30 | mux.HandleFunc("/admin", fn) 31 | mux.HandleFunc("/admin/products/", fn) 32 | mux.HandleFunc("/admin/products/create", fn) 33 | mux.HandleFunc("/admin/products/update", fn) 34 | mux.HandleFunc("/admin/products/delete", fn) 35 | 36 | paths := []string{"/", "/notfound", "/admin/", "/admin/foo", "/contact", "/products", 37 | "/products/", "/products/3/image.jpg"} 38 | b.StartTimer() 39 | for i := 0; i < b.N; i++ { 40 | if h, p := mux.match(paths[i%len(paths)]); h != nil && p == "" { 41 | b.Error("impossible") 42 | } 43 | } 44 | b.StopTimer() 45 | } 46 | -------------------------------------------------------------------------------- /testdata/file: -------------------------------------------------------------------------------- 1 | 0123456789 2 | -------------------------------------------------------------------------------- /testdata/index.html: -------------------------------------------------------------------------------- 1 | index.html says hello 2 | -------------------------------------------------------------------------------- /testdata/style.css: -------------------------------------------------------------------------------- 1 | body {} 2 | -------------------------------------------------------------------------------- /triv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build ignore 6 | // +build ignore 7 | 8 | package main 9 | 10 | import ( 11 | "bytes" 12 | "expvar" 13 | "flag" 14 | "fmt" 15 | "io" 16 | "log" 17 | "os" 18 | "os/exec" 19 | "strconv" 20 | "sync" 21 | 22 | http "github.com/bogdanfinn/fhttp" 23 | ) 24 | 25 | // hello world, the web server 26 | var helloRequests = expvar.NewInt("hello-requests") 27 | 28 | func HelloServer(w http.ResponseWriter, req *http.Request) { 29 | helloRequests.Add(1) 30 | io.WriteString(w, "hello, world!\n") 31 | } 32 | 33 | // Simple counter server. POSTing to it will set the value. 34 | type Counter struct { 35 | mu sync.Mutex // protects n 36 | n int 37 | } 38 | 39 | // This makes Counter satisfy the expvar.Var interface, so we can export 40 | // it directly. 41 | func (ctr *Counter) String() string { 42 | ctr.mu.Lock() 43 | defer ctr.mu.Unlock() 44 | return fmt.Sprintf("%d", ctr.n) 45 | } 46 | 47 | func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) { 48 | ctr.mu.Lock() 49 | defer ctr.mu.Unlock() 50 | switch req.Method { 51 | case "GET": 52 | ctr.n++ 53 | case "POST": 54 | buf := new(bytes.Buffer) 55 | io.Copy(buf, req.Body) 56 | body := buf.String() 57 | if n, err := strconv.Atoi(body); err != nil { 58 | fmt.Fprintf(w, "bad POST: %v\nbody: [%v]\n", err, body) 59 | } else { 60 | ctr.n = n 61 | fmt.Fprint(w, "counter reset\n") 62 | } 63 | } 64 | fmt.Fprintf(w, "counter = %d\n", ctr.n) 65 | } 66 | 67 | // simple flag server 68 | var booleanflag = flag.Bool("boolean", true, "another flag for testing") 69 | 70 | func FlagServer(w http.ResponseWriter, req *http.Request) { 71 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") 72 | fmt.Fprint(w, "Flags:\n") 73 | flag.VisitAll(func(f *flag.Flag) { 74 | if f.Value.String() != f.DefValue { 75 | fmt.Fprintf(w, "%s = %s [default = %s]\n", f.Name, f.Value.String(), f.DefValue) 76 | } else { 77 | fmt.Fprintf(w, "%s = %s\n", f.Name, f.Value.String()) 78 | } 79 | }) 80 | } 81 | 82 | // simple argument server 83 | func ArgServer(w http.ResponseWriter, req *http.Request) { 84 | for _, s := range os.Args { 85 | fmt.Fprint(w, s, " ") 86 | } 87 | } 88 | 89 | // a channel (just for the fun of it) 90 | type Chan chan int 91 | 92 | func ChanCreate() Chan { 93 | c := make(Chan) 94 | go func(c Chan) { 95 | for x := 0; ; x++ { 96 | c <- x 97 | } 98 | }(c) 99 | return c 100 | } 101 | 102 | func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) { 103 | io.WriteString(w, fmt.Sprintf("channel send #%d\n", <-ch)) 104 | } 105 | 106 | // exec a program, redirecting output 107 | func DateServer(rw http.ResponseWriter, req *http.Request) { 108 | rw.Header().Set("Content-Type", "text/plain; charset=utf-8") 109 | 110 | date, err := exec.Command("/bin/date").Output() 111 | if err != nil { 112 | http.Error(rw, err.Error(), http.StatusInternalServerError) 113 | return 114 | } 115 | rw.Write(date) 116 | } 117 | 118 | func Logger(w http.ResponseWriter, req *http.Request) { 119 | log.Print(req.URL) 120 | http.Error(w, "oops", http.StatusNotFound) 121 | } 122 | 123 | var webroot = flag.String("root", os.Getenv("HOME"), "web root directory") 124 | 125 | func main() { 126 | flag.Parse() 127 | 128 | // The counter is published as a variable directly. 129 | ctr := new(Counter) 130 | expvar.Publish("counter", ctr) 131 | http.Handle("/counter", ctr) 132 | http.Handle("/", http.HandlerFunc(Logger)) 133 | http.Handle("/go/", http.StripPrefix("/go/", http.FileServer(http.Dir(*webroot)))) 134 | http.Handle("/chan", ChanCreate()) 135 | http.HandleFunc("/flags", FlagServer) 136 | http.HandleFunc("/args", ArgServer) 137 | http.HandleFunc("/go/hello", HelloServer) 138 | http.HandleFunc("/date", DateServer) 139 | log.Fatal(http.ListenAndServe(":12345", nil)) 140 | } 141 | --------------------------------------------------------------------------------