Ejemplo 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 = 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
}