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

     1  package spdy2
     2  
     3  import (
     4  	"errors"
     5  	"net/http"
     6  	"net/url"
     7  	"strings"
     8  
     9  	"github.com/SlyMarbo/spdy/common"
    10  	"github.com/SlyMarbo/spdy/spdy2/frames"
    11  )
    12  
    13  // Ping is used by spdy.PingServer and spdy.PingClient to send
    14  // SPDY PINGs.
    15  func (c *Conn) Ping() (<-chan bool, error) {
    16  	if c.Closed() {
    17  		return nil, errors.New("Error: Conn has been closed.")
    18  	}
    19  
    20  	ping := new(frames.PING)
    21  	c.nextPingIDLock.Lock()
    22  	pid := c.nextPingID
    23  	if pid+2 < pid {
    24  		if pid&1 == 0 {
    25  			c.nextPingID = 2
    26  		} else {
    27  			c.nextPingID = 1
    28  		}
    29  	} else {
    30  		c.nextPingID += 2
    31  	}
    32  	c.nextPingIDLock.Unlock()
    33  	ping.PingID = pid
    34  	c.output[0] <- ping
    35  	ch := make(chan bool, 1)
    36  	c.pingsLock.Lock()
    37  	c.pings[pid] = ch
    38  	c.pingsLock.Unlock()
    39  
    40  	return ch, nil
    41  }
    42  
    43  // Push is used to issue a server push to the client. Note that this cannot be performed
    44  // by clients.
    45  func (c *Conn) Push(resource string, origin common.Stream) (common.PushStream, error) {
    46  	c.goawayLock.Lock()
    47  	goaway := c.goawayReceived || c.goawaySent
    48  	c.goawayLock.Unlock()
    49  	if goaway {
    50  		return nil, common.ErrGoaway
    51  	}
    52  
    53  	if c.server == nil {
    54  		return nil, errors.New("Error: Only servers can send pushes.")
    55  	}
    56  
    57  	// Parse and check URL.
    58  	url, err := url.Parse(resource)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	if url.Scheme == "" || url.Host == "" {
    63  		return nil, errors.New("Error: Incomplete path provided to resource.")
    64  	}
    65  	resource = url.String()
    66  
    67  	// Ensure the resource hasn't been pushed on the given stream already.
    68  	if c.pushedResources[origin] == nil {
    69  		c.pushedResources[origin] = map[string]struct{}{
    70  			resource: struct{}{},
    71  		}
    72  	} else if _, ok := c.pushedResources[origin][url.String()]; !ok {
    73  		c.pushedResources[origin][resource] = struct{}{}
    74  	} else {
    75  		return nil, errors.New("Error: Resource already pushed to this stream.")
    76  	}
    77  
    78  	// Check stream limit would allow the new stream.
    79  	if !c.pushStreamLimit.Add() {
    80  		return nil, errors.New("Error: Max concurrent streams limit exceeded.")
    81  	}
    82  
    83  	// Verify that path is prefixed with / as required by spec.
    84  	path := url.Path
    85  	if !strings.HasPrefix(path, "/") {
    86  		path = "/" + path
    87  	}
    88  
    89  	// Prepare the SYN_STREAM.
    90  	push := new(frames.SYN_STREAM)
    91  	push.Flags = common.FLAG_UNIDIRECTIONAL
    92  	push.AssocStreamID = origin.StreamID()
    93  	push.Priority = 3
    94  	push.Header = make(http.Header)
    95  	push.Header.Set("scheme", url.Scheme)
    96  	push.Header.Set("host", url.Host)
    97  	push.Header.Set("url", path)
    98  	push.Header.Set("version", "HTTP/1.1")
    99  
   100  	// Send.
   101  	c.streamCreation.Lock()
   102  	defer c.streamCreation.Unlock()
   103  
   104  	c.lastPushStreamIDLock.Lock()
   105  	c.lastPushStreamID += 2
   106  	newID := c.lastPushStreamID
   107  	c.lastPushStreamIDLock.Unlock()
   108  	if newID > common.MAX_STREAM_ID {
   109  		return nil, errors.New("Error: All server streams exhausted.")
   110  	}
   111  	push.StreamID = newID
   112  	c.output[0] <- push
   113  
   114  	// Create the PushStream.
   115  	out := NewPushStream(c, newID, origin, c.output[3])
   116  
   117  	// Store in the connection map.
   118  	c.streamsLock.Lock()
   119  	c.streams[newID] = out
   120  	c.streamsLock.Unlock()
   121  
   122  	return out, nil
   123  }