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  }