github.com/alejandroesc/spdy@v0.0.0-20200317064415-01a02f0eb389/spdy3/spdy_api.go (about) 1 package spdy3 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/spdy3/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 34 ping.PingID = pid 35 c.output[0] <- ping 36 ch := make(chan bool, 1) 37 c.pingsLock.Lock() 38 c.pings[pid] = ch 39 c.pingsLock.Unlock() 40 41 return ch, nil 42 } 43 44 // Push is used to issue a server push to the client. Note that this cannot be performed 45 // by clients. 46 func (c *Conn) Push(resource string, origin common.Stream) (common.PushStream, error) { 47 c.goawayLock.Lock() 48 goaway := c.goawayReceived || c.goawaySent 49 c.goawayLock.Unlock() 50 if goaway { 51 return nil, common.ErrGoaway 52 } 53 54 if c.server == nil { 55 return nil, errors.New("Error: Only servers can send pushes.") 56 } 57 58 // Parse and check URL. 59 url, err := url.Parse(resource) 60 if err != nil { 61 return nil, err 62 } 63 if url.Scheme == "" || url.Host == "" { 64 return nil, errors.New("Error: Incomplete path provided to resource.") 65 } 66 resource = url.String() 67 68 // Ensure the resource hasn't been pushed on the given stream already. 69 if c.pushedResources[origin] == nil { 70 c.pushedResources[origin] = map[string]struct{}{ 71 resource: struct{}{}, 72 } 73 } else if _, ok := c.pushedResources[origin][url.String()]; !ok { 74 c.pushedResources[origin][resource] = struct{}{} 75 } else { 76 return nil, errors.New("Error: Resource already pushed to this stream.") 77 } 78 79 // Check stream limit would allow the new stream. 80 if !c.pushStreamLimit.Add() { 81 return nil, errors.New("Error: Max concurrent streams limit exceeded.") 82 } 83 84 // Verify that path is prefixed with / as required by spec. 85 path := url.Path 86 if !strings.HasPrefix(path, "/") { 87 path = "/" + path 88 } 89 90 // Prepare the SYN_STREAM. 91 push := new(frames.SYN_STREAM) 92 push.Flags = common.FLAG_UNIDIRECTIONAL 93 push.AssocStreamID = origin.StreamID() 94 push.Priority = 7 95 push.Header = make(http.Header) 96 push.Header.Set(":scheme", url.Scheme) 97 push.Header.Set(":host", url.Host) 98 push.Header.Set(":path", path) 99 push.Header.Set(":version", "HTTP/1.1") 100 101 // Send. 102 c.streamCreation.Lock() 103 defer c.streamCreation.Unlock() 104 105 c.lastPushStreamIDLock.Lock() 106 c.lastPushStreamID += 2 107 newID := c.lastPushStreamID 108 c.lastPushStreamIDLock.Unlock() 109 if newID > common.MAX_STREAM_ID { 110 return nil, errors.New("Error: All server streams exhausted.") 111 } 112 push.StreamID = newID 113 c.output[0] <- push 114 115 // Create the pushStream. 116 out := NewPushStream(c, newID, origin, c.output[7]) 117 out.AddFlowControl(c.flowControl) 118 119 // Store in the connection map. 120 c.streamsLock.Lock() 121 c.streams[newID] = out 122 c.streamsLock.Unlock() 123 124 return out, nil 125 } 126 127 func (c *Conn) SetFlowControl(f common.FlowControl) { 128 c.flowControlLock.Lock() 129 c.flowControl = f 130 c.flowControlLock.Unlock() 131 }