Exemplo n.º 1
0
// 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 = 7
	push.Header = make(http.Header)
	push.Header.Set(":scheme", url.Scheme)
	push.Header.Set(":host", url.Host)
	push.Header.Set(":path", 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[7])
	out.AddFlowControl(c.flowControl)

	// Store in the connection map.
	c.streamsLock.Lock()
	c.streams[newID] = out
	c.streamsLock.Unlock()

	return out, nil
}
Exemplo n.º 2
0
// processFrame handles the initial processing of the given
// frame, before passing it on to the relevant helper func,
// if necessary. The returned boolean indicates whether the
// connection is closing.
func (c *Conn) processFrame(frame common.Frame) bool {
	switch frame := frame.(type) {

	case *frames.SYN_STREAM:
		if c.server == nil {
			c.handlePush(frame)
		} else {
			c.handleRequest(frame)
		}
	case *frames.SYN_STREAMV3_1:
		f3 := new(frames.SYN_STREAM)
		f3.Flags = frame.Flags
		f3.StreamID = frame.StreamID
		f3.AssocStreamID = frame.AssocStreamID
		f3.Priority = frame.Priority
		f3.Slot = 0
		f3.Header = frame.Header
		if c.server == nil {
			c.handlePush(f3)
		} else {
			c.handleRequest(f3)
		}

	case *frames.SYN_REPLY:
		c.handleSynReply(frame)

	case *frames.RST_STREAM:
		if frame.Status.IsFatal() {
			code := frame.Status.String()
			log.Printf("Warning: Received %s on stream %d. Closing connection.\n", code, frame.StreamID)
			c.shutdownError = frame
			c.Close()
			return true
		}
		c.handleRstStream(frame)

	case *frames.SETTINGS:
		for _, setting := range frame.Settings {
			c.receivedSettings[setting.ID] = setting
			switch setting.ID {
			case common.SETTINGS_INITIAL_WINDOW_SIZE:
				c.initialWindowSizeLock.Lock()
				initial := int64(c.initialWindowSize)
				current := c.connectionWindowSize
				inbound := int64(setting.Value)
				if initial != inbound {
					if initial > inbound {
						c.connectionWindowSize = inbound - (initial - current)
					} else {
						c.connectionWindowSize += (inbound - initial)
					}
					c.initialWindowSize = setting.Value
				}
				c.initialWindowSizeLock.Unlock()

			case common.SETTINGS_MAX_CONCURRENT_STREAMS:
				if c.server == nil {
					c.requestStreamLimit.SetLimit(setting.Value)
				} else {
					c.pushStreamLimit.SetLimit(setting.Value)
				}
			}
		}

	case *frames.PING:
		// Check whether Ping ID is a response.
		c.nextPingIDLock.Lock()
		next := c.nextPingID
		c.nextPingIDLock.Unlock()
		if frame.PingID&1 == next&1 {
			c.pingsLock.Lock()
			if c.check(c.pings[frame.PingID] == nil, "Ignored unrequested PING %d", frame.PingID) {
				c.pingsLock.Unlock()
				return false
			}
			c.pings[frame.PingID] <- true
			close(c.pings[frame.PingID])
			delete(c.pings, frame.PingID)
			c.pingsLock.Unlock()
		} else {
			debug.Println("Received PING. Replying...")
			c.output[0] <- frame
		}

	case *frames.GOAWAY:
		lastProcessed := frame.LastGoodStreamID
		c.streamsLock.Lock()
		for streamID, stream := range c.streams {
			if streamID&1 == c.oddity && streamID > lastProcessed {
				// Stream is locally-sent and has not been processed.
				// TODO: Inform the server that the push has not been successful.
				stream.Close()
			}
		}
		c.streamsLock.Unlock()
		c.shutdownError = frame
		c.goawayLock.Lock()
		c.goawayReceived = true
		c.goawayLock.Unlock()

	case *frames.HEADERS:
		c.handleHeaders(frame)

	case *frames.WINDOW_UPDATE:
		c.handleWindowUpdate(frame)

	case *frames.CREDENTIAL:
		if c.Subversion > 0 {
			return false
		}
		if c.server == nil || c.certificates == nil {
			log.Println("Ignored unexpected CREDENTIAL.")
			return false
		}
		if frame.Slot >= c.vectorIndex {
			setting := new(frames.SETTINGS)
			setting.Settings = common.Settings{
				common.SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE: &common.Setting{
					ID:    common.SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE,
					Value: uint32(frame.Slot + 4),
				},
			}
			c.output[0] <- setting
			c.vectorIndex += 4
		}
		c.certificates[frame.Slot] = frame.Certificates

	case *frames.DATA:
		if c.Subversion > 0 {
			// The transfer window shouldn't already be negative.
			if c.connectionWindowSizeThere < 0 {
				c._GOAWAY(common.GOAWAY_FLOW_CONTROL_ERROR)
				return false
			}

			c.connectionWindowSizeThere -= int64(len(frame.Data))

			c.flowControlLock.Lock()
			f := c.flowControl
			c.flowControlLock.Unlock()
			delta := f.ReceiveData(0, c.initialWindowSizeThere, c.connectionWindowSizeThere)
			if delta != 0 {
				grow := new(frames.WINDOW_UPDATE)
				grow.StreamID = 0
				grow.DeltaWindowSize = delta
				c.output[0] <- grow
				c.connectionWindowSizeThere += int64(grow.DeltaWindowSize)
			}
		}
		if c.server == nil {
			c.handleServerData(frame)
		} else {
			c.handleClientData(frame)
		}

	default:
		log.Println(fmt.Sprintf("Ignored unexpected frame type %T", frame))
	}
	return false
}
Exemplo n.º 3
0
// 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(3) {
		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 len(request.Host) > 0 {
		host = request.Host
	}
	syn := new(frames.SYN_STREAM)
	syn.Priority = priority
	syn.Header = request.Header
	syn.Header.Set(":method", request.Method)
	syn.Header.Set(":path", 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
	out.AddFlowControl(c.flowControl)
	c.streamsLock.Lock()
	c.streams[syn.StreamID] = out // Store in the connection map.
	c.streamsLock.Unlock()

	return out, nil
}