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 }