github.com/useflyent/fhttp@v0.0.0-20211004035111-333f430cfbbf/http2/push_consume.go (about) 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/useflyent/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 }