// Request is used to make a client request. func (c *Conn) Request(request *http.Request, receiver common.Receiver, priority common.Priority) (common.Stream, error) { if c.Closed() { return nil, common.ErrConnClosed } c.goawayLock.Lock() goaway := c.goawayReceived || c.goawaySent c.goawayLock.Unlock() if goaway { return nil, common.ErrGoaway } if c.server != nil { return nil, errors.New("Error: Only clients can send requests.") } // Check stream limit would allow the new stream. if !c.requestStreamLimit.Add() { return nil, errors.New("Error: Max concurrent streams limit exceeded.") } if !priority.Valid(2) { return nil, errors.New("Error: Priority must be in the range 0 - 7.") } url := request.URL if url == nil || url.Scheme == "" || url.Host == "" { return nil, errors.New("Error: Incomplete path provided to resource.") } // Prepare the SYN_STREAM. path := url.Path if url.RawQuery != "" { path += "?" + url.RawQuery } if url.Fragment != "" { path += "#" + url.Fragment } if !strings.HasPrefix(path, "/") { path = "/" + path } host := url.Host if request.Host != "" { host = request.Host } syn := new(frames.SYN_STREAM) syn.Priority = priority syn.Header = request.Header syn.Header.Set("method", request.Method) syn.Header.Set("url", path) syn.Header.Set("version", "HTTP/1.1") syn.Header.Set("host", host) syn.Header.Set("scheme", url.Scheme) // Prepare the request body, if any. body := make([]*frames.DATA, 0, 1) if request.Body != nil { buf := make([]byte, 32*1024) n, err := request.Body.Read(buf) if err != nil && err != io.EOF { return nil, err } total := n for n > 0 { data := new(frames.DATA) data.Data = make([]byte, n) copy(data.Data, buf[:n]) body = append(body, data) n, err = request.Body.Read(buf) if err != nil && err != io.EOF { return nil, err } total += n } // Half-close the stream. if len(body) == 0 { syn.Flags = common.FLAG_FIN } else { syn.Header.Set("Content-Length", fmt.Sprint(total)) body[len(body)-1].Flags = common.FLAG_FIN } request.Body.Close() } else { syn.Flags = common.FLAG_FIN } // Send. c.streamCreation.Lock() defer c.streamCreation.Unlock() c.lastRequestStreamIDLock.Lock() if c.lastRequestStreamID == 0 { c.lastRequestStreamID = 1 } else { c.lastRequestStreamID += 2 } syn.StreamID = c.lastRequestStreamID c.lastRequestStreamIDLock.Unlock() if syn.StreamID > common.MAX_STREAM_ID { return nil, errors.New("Error: All client streams exhausted.") } c.output[0] <- syn for _, frame := range body { frame.StreamID = syn.StreamID c.output[0] <- frame } // // Create the request stream. out := NewRequestStream(c, syn.StreamID, c.output[0]) out.Request = request out.Receiver = receiver // Store in the connection map. c.streamsLock.Lock() c.streams[syn.StreamID] = out c.streamsLock.Unlock() return out, nil }
// Push is used to issue a server push to the client. Note that this cannot be performed // by clients. func (c *Conn) Push(resource string, origin common.Stream) (common.PushStream, error) { c.goawayLock.Lock() goaway := c.goawayReceived || c.goawaySent c.goawayLock.Unlock() if goaway { return nil, common.ErrGoaway } if c.server == nil { return nil, errors.New("Error: Only servers can send pushes.") } // Parse and check URL. url, err := url.Parse(resource) if err != nil { return nil, err } if url.Scheme == "" || url.Host == "" { return nil, errors.New("Error: Incomplete path provided to resource.") } resource = url.String() // Ensure the resource hasn't been pushed on the given stream already. if c.pushedResources[origin] == nil { c.pushedResources[origin] = map[string]struct{}{ resource: struct{}{}, } } else if _, ok := c.pushedResources[origin][url.String()]; !ok { c.pushedResources[origin][resource] = struct{}{} } else { return nil, errors.New("Error: Resource already pushed to this stream.") } // Check stream limit would allow the new stream. if !c.pushStreamLimit.Add() { return nil, errors.New("Error: Max concurrent streams limit exceeded.") } // Verify that path is prefixed with / as required by spec. path := url.Path if !strings.HasPrefix(path, "/") { path = "/" + path } // Prepare the SYN_STREAM. push := new(frames.SYN_STREAM) push.Flags = common.FLAG_UNIDIRECTIONAL push.AssocStreamID = origin.StreamID() push.Priority = 3 push.Header = make(http.Header) push.Header.Set("scheme", url.Scheme) push.Header.Set("host", url.Host) push.Header.Set("url", path) push.Header.Set("version", "HTTP/1.1") // Send. c.streamCreation.Lock() defer c.streamCreation.Unlock() c.lastPushStreamIDLock.Lock() c.lastPushStreamID += 2 newID := c.lastPushStreamID c.lastPushStreamIDLock.Unlock() if newID > common.MAX_STREAM_ID { return nil, errors.New("Error: All server streams exhausted.") } push.StreamID = newID c.output[0] <- push // Create the PushStream. out := NewPushStream(c, newID, origin, c.output[3]) // Store in the connection map. c.streamsLock.Lock() c.streams[newID] = out c.streamsLock.Unlock() return out, nil }