github.com/alejandroesc/spdy@v0.0.0-20200317064415-01a02f0eb389/spdy3/requests.go (about)

     1  package spdy3
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"github.com/SlyMarbo/spdy/common"
    11  	"github.com/SlyMarbo/spdy/spdy3/frames"
    12  )
    13  
    14  // Request is used to make a client request.
    15  func (c *Conn) Request(request *http.Request, receiver common.Receiver, priority common.Priority) (common.Stream, error) {
    16  	if c.Closed() {
    17  		return nil, common.ErrConnClosed
    18  	}
    19  	c.goawayLock.Lock()
    20  	goaway := c.goawayReceived || c.goawaySent
    21  	c.goawayLock.Unlock()
    22  	if goaway {
    23  		return nil, common.ErrGoaway
    24  	}
    25  
    26  	if c.server != nil {
    27  		return nil, errors.New("Error: Only clients can send requests.")
    28  	}
    29  
    30  	// Check stream limit would allow the new stream.
    31  	if !c.requestStreamLimit.Add() {
    32  		return nil, errors.New("Error: Max concurrent streams limit exceeded.")
    33  	}
    34  
    35  	if !priority.Valid(3) {
    36  		return nil, errors.New("Error: Priority must be in the range 0 - 7.")
    37  	}
    38  
    39  	url := request.URL
    40  	if url == nil || url.Scheme == "" || url.Host == "" {
    41  		return nil, errors.New("Error: Incomplete path provided to resource.")
    42  	}
    43  
    44  	// Prepare the SYN_STREAM.
    45  	path := url.Path
    46  	if url.RawQuery != "" {
    47  		path += "?" + url.RawQuery
    48  	}
    49  	if url.Fragment != "" {
    50  		path += "#" + url.Fragment
    51  	}
    52  	if !strings.HasPrefix(path, "/") {
    53  		path = "/" + path
    54  	}
    55  
    56  	host := url.Host
    57  	if len(request.Host) > 0 {
    58  		host = request.Host
    59  	}
    60  	syn := new(frames.SYN_STREAM)
    61  	syn.Priority = priority
    62  	syn.Header = request.Header
    63  	syn.Header.Set(":method", request.Method)
    64  	syn.Header.Set(":path", path)
    65  	syn.Header.Set(":version", "HTTP/1.1")
    66  	syn.Header.Set(":host", host)
    67  	syn.Header.Set(":scheme", url.Scheme)
    68  
    69  	// Prepare the request body, if any.
    70  	body := make([]*frames.DATA, 0, 1)
    71  	if request.Body != nil {
    72  		buf := make([]byte, 32*1024)
    73  		n, err := request.Body.Read(buf)
    74  		if err != nil && err != io.EOF {
    75  			return nil, err
    76  		}
    77  		total := n
    78  		for n > 0 {
    79  			data := new(frames.DATA)
    80  			data.Data = make([]byte, n)
    81  			copy(data.Data, buf[:n])
    82  			body = append(body, data)
    83  			n, err = request.Body.Read(buf)
    84  			if err != nil && err != io.EOF {
    85  				return nil, err
    86  			}
    87  			total += n
    88  		}
    89  
    90  		// Half-close the stream.
    91  		if len(body) == 0 {
    92  			syn.Flags = common.FLAG_FIN
    93  		} else {
    94  			syn.Header.Set("Content-Length", fmt.Sprint(total))
    95  			body[len(body)-1].Flags = common.FLAG_FIN
    96  		}
    97  		request.Body.Close()
    98  	} else {
    99  		syn.Flags = common.FLAG_FIN
   100  	}
   101  
   102  	// Send.
   103  	c.streamCreation.Lock()
   104  	defer c.streamCreation.Unlock()
   105  
   106  	c.lastRequestStreamIDLock.Lock()
   107  	if c.lastRequestStreamID == 0 {
   108  		c.lastRequestStreamID = 1
   109  	} else {
   110  		c.lastRequestStreamID += 2
   111  	}
   112  	syn.StreamID = c.lastRequestStreamID
   113  	c.lastRequestStreamIDLock.Unlock()
   114  	if syn.StreamID > common.MAX_STREAM_ID {
   115  		return nil, errors.New("Error: All client streams exhausted.")
   116  	}
   117  	c.output[0] <- syn
   118  	for _, frame := range body {
   119  		frame.StreamID = syn.StreamID
   120  		c.output[0] <- frame
   121  	}
   122  
   123  	// Create the request stream.
   124  	out := NewRequestStream(c, syn.StreamID, c.output[0])
   125  	out.Request = request
   126  	out.Receiver = receiver
   127  	out.AddFlowControl(c.flowControl)
   128  	c.streamsLock.Lock()
   129  	c.streams[syn.StreamID] = out // Store in the connection map.
   130  	c.streamsLock.Unlock()
   131  
   132  	return out, nil
   133  }
   134  
   135  func (c *Conn) RequestResponse(request *http.Request, receiver common.Receiver, priority common.Priority) (*http.Response, error) {
   136  	res := common.NewResponse(request, receiver)
   137  
   138  	// Send the request.
   139  	stream, err := c.Request(request, res, priority)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	// Let the request run its course.
   145  	stream.Run()
   146  
   147  	return res.Response(), c.shutdownError
   148  }