@ -9,6 +9,7 @@ import (
"fmt"
"log"
"net/http"
"net/url"
"time"
"golang.org/x/net/http2/hpack"
@ -44,9 +45,10 @@ type writeContext interface {
HeaderEncoder ( ) ( * hpack . Encoder , * bytes . Buffer )
}
// endsStream reports whether the given frame writer w will locally
// close the stream.
func endsStream ( w writeFramer ) bool {
// writeEndsStream reports whether w writes a frame that will transition
// the stream to a half-closed local state. This returns false for RST_STREAM,
// which closes the entire stream (not just the local half).
func writeEndsStream ( w writeFramer ) bool {
switch v := w . ( type ) {
case * writeData :
return v . endStream
@ -56,7 +58,7 @@ func endsStream(w writeFramer) bool {
// This can only happen if the caller reuses w after it's
// been intentionally nil'ed out to prevent use. Keep this
// here to catch future refactoring breaking it.
panic ( "endsStream called on nil writeFramer" )
panic ( "writ eE ndsStream called on nil writeFramer" )
}
return false
}
@ -150,6 +152,33 @@ func (writeSettingsAck) writeFrame(ctx writeContext) error {
func ( writeSettingsAck ) staysWithinBuffer ( max int ) bool { return frameHeaderLen <= max }
// splitHeaderBlock splits headerBlock into fragments so that each fragment fits
// in a single frame, then calls fn for each fragment. firstFrag/lastFrag are true
// for the first/last fragment, respectively.
func splitHeaderBlock ( ctx writeContext , headerBlock [ ] byte , fn func ( ctx writeContext , frag [ ] byte , firstFrag , lastFrag bool ) error ) error {
// For now we're lazy and just pick the minimum MAX_FRAME_SIZE
// that all peers must support (16KB). Later we could care
// more and send larger frames if the peer advertised it, but
// there's little point. Most headers are small anyway (so we
// generally won't have CONTINUATION frames), and extra frames
// only waste 9 bytes anyway.
const maxFrameSize = 16384
first := true
for len ( headerBlock ) > 0 {
frag := headerBlock
if len ( frag ) > maxFrameSize {
frag = frag [ : maxFrameSize ]
}
headerBlock = headerBlock [ len ( frag ) : ]
if err := fn ( ctx , frag , first , len ( headerBlock ) == 0 ) ; err != nil {
return err
}
first = false
}
return nil
}
// writeResHeaders is a request to write a HEADERS and 0+ CONTINUATION frames
// for HTTP response headers or trailers from a server handler.
type writeResHeaders struct {
@ -207,39 +236,69 @@ func (w *writeResHeaders) writeFrame(ctx writeContext) error {
panic ( "unexpected empty hpack" )
}
// For now we're lazy and just pick the minimum MAX_FRAME_SIZE
// that all peers must support (16KB). Later we could care
// more and send larger frames if the peer advertised it, but
// there's little point. Most headers are small anyway (so we
// generally won't have CONTINUATION frames), and extra frames
// only waste 9 bytes anyway.
const maxFrameSize = 16384
return splitHeaderBlock ( ctx , headerBlock , w . writeHeaderBlock )
}
first := true
for len ( headerBlock ) > 0 {
frag := headerBlock
if len ( frag ) > maxFrameSize {
frag = frag [ : maxFrameSize ]
}
headerBlock = headerBlock [ len ( frag ) : ]
endHeaders := len ( headerBlock ) == 0
var err error
if first {
first = false
err = ctx . Framer ( ) . WriteHeaders ( HeadersFrameParam {
StreamID : w . streamID ,
BlockFragment : frag ,
EndStream : w . endStream ,
EndHeaders : endHeaders ,
} )
} else {
err = ctx . Framer ( ) . WriteContinuation ( w . streamID , endHeaders , frag )
}
if err != nil {
return err
}
func ( w * writeResHeaders ) writeHeaderBlock ( ctx writeContext , frag [ ] byte , firstFrag , lastFrag bool ) error {
if firstFrag {
return ctx . Framer ( ) . WriteHeaders ( HeadersFrameParam {
StreamID : w . streamID ,
BlockFragment : frag ,
EndStream : w . endStream ,
EndHeaders : lastFrag ,
} )
} else {
return ctx . Framer ( ) . WriteContinuation ( w . streamID , lastFrag , frag )
}
}
// writePushPromise is a request to write a PUSH_PROMISE and 0+ CONTINUATION frames.
type writePushPromise struct {
streamID uint32 // pusher stream
method string // for :method
url * url . URL // for :scheme, :authority, :path
h http . Header
// Creates an ID for a pushed stream. This runs on serveG just before
// the frame is written. The returned ID is copied to promisedID.
allocatePromisedID func ( ) ( uint32 , error )
promisedID uint32
}
func ( w * writePushPromise ) staysWithinBuffer ( max int ) bool {
// TODO: see writeResHeaders.staysWithinBuffer
return false
}
func ( w * writePushPromise ) writeFrame ( ctx writeContext ) error {
enc , buf := ctx . HeaderEncoder ( )
buf . Reset ( )
encKV ( enc , ":method" , w . method )
encKV ( enc , ":scheme" , w . url . Scheme )
encKV ( enc , ":authority" , w . url . Host )
encKV ( enc , ":path" , w . url . RequestURI ( ) )
encodeHeaders ( enc , w . h , nil )
headerBlock := buf . Bytes ( )
if len ( headerBlock ) == 0 {
panic ( "unexpected empty hpack" )
}
return splitHeaderBlock ( ctx , headerBlock , w . writeHeaderBlock )
}
func ( w * writePushPromise ) writeHeaderBlock ( ctx writeContext , frag [ ] byte , firstFrag , lastFrag bool ) error {
if firstFrag {
return ctx . Framer ( ) . WritePushPromise ( PushPromiseParam {
StreamID : w . streamID ,
PromiseID : w . promisedID ,
BlockFragment : frag ,
EndHeaders : lastFrag ,
} )
} else {
return ctx . Framer ( ) . WriteContinuation ( w . streamID , lastFrag , frag )
}
return nil
}
type write100ContinueHeadersFrame struct {
@ -274,6 +333,8 @@ func (wu writeWindowUpdate) writeFrame(ctx writeContext) error {
return ctx . Framer ( ) . WriteWindowUpdate ( wu . streamID , wu . n )
}
// encodeHeaders encodes an http.Header. If keys is not nil, then (k, h[k])
// is encoded only only if k is in keys.
func encodeHeaders ( enc * hpack . Encoder , h http . Header , keys [ ] string ) {
if keys == nil {
sorter := sorterPool . Get ( ) . ( * sorter )