http/spdy: improve error handling.

Create a new spdy.Error type that includes the enumerated error type and
the associated stream id (0 if not associated with a specific stream).
This will let users handle errors differently (RST_STREAM vs GOAWAY).

R=bradfitz, rsc, rogpeppe
CC=golang-dev
https://golang.org/cl/4532131
This commit is contained in:
William Chan 2011-06-14 11:31:18 -04:00 committed by Russ Cox
parent 400d825ea0
commit 5af8e53a14
4 changed files with 93 additions and 60 deletions

View File

@ -80,7 +80,7 @@ func (frame *HeadersFrame) read(h ControlFrameHeader, f *Framer) os.Error {
func newControlFrame(frameType ControlFrameType) (controlFrame, os.Error) { func newControlFrame(frameType ControlFrameType) (controlFrame, os.Error) {
ctor, ok := cframeCtor[frameType] ctor, ok := cframeCtor[frameType]
if !ok { if !ok {
return nil, InvalidControlFrame return nil, &Error{Err: InvalidControlFrame}
} }
return ctor(), nil return ctor(), nil
} }
@ -97,30 +97,12 @@ var cframeCtor = map[ControlFrameType]func() controlFrame{
// TODO(willchan): Add TypeWindowUpdate // TODO(willchan): Add TypeWindowUpdate
} }
type corkedReader struct { func (f *Framer) uncorkHeaderDecompressor(payloadSize int64) os.Error {
r io.Reader
ch chan int
n int
}
func (cr *corkedReader) Read(p []byte) (int, os.Error) {
if cr.n == 0 {
cr.n = <-cr.ch
}
if len(p) > cr.n {
p = p[:cr.n]
}
n, err := cr.r.Read(p)
cr.n -= n
return n, err
}
func (f *Framer) uncorkHeaderDecompressor(payloadSize int) os.Error {
if f.headerDecompressor != nil { if f.headerDecompressor != nil {
f.headerReader.ch <- payloadSize f.headerReader.N = payloadSize
return nil return nil
} }
f.headerReader = corkedReader{r: f.r, ch: make(chan int, 1), n: payloadSize} f.headerReader = io.LimitedReader{R: f.r, N: payloadSize}
decompressor, err := zlib.NewReaderDict(&f.headerReader, []byte(HeaderDictionary)) decompressor, err := zlib.NewReaderDict(&f.headerReader, []byte(HeaderDictionary))
if err != nil { if err != nil {
return err return err
@ -161,11 +143,12 @@ func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) (
return cframe, nil return cframe, nil
} }
func parseHeaderValueBlock(r io.Reader) (http.Header, os.Error) { func parseHeaderValueBlock(r io.Reader, streamId uint32) (http.Header, os.Error) {
var numHeaders uint16 var numHeaders uint16
if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil { if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil {
return nil, err return nil, err
} }
var e os.Error
h := make(http.Header, int(numHeaders)) h := make(http.Header, int(numHeaders))
for i := 0; i < int(numHeaders); i++ { for i := 0; i < int(numHeaders); i++ {
var length uint16 var length uint16
@ -178,10 +161,11 @@ func parseHeaderValueBlock(r io.Reader) (http.Header, os.Error) {
} }
name := string(nameBytes) name := string(nameBytes)
if name != strings.ToLower(name) { if name != strings.ToLower(name) {
return nil, UnlowercasedHeaderName e = &Error{UnlowercasedHeaderName, streamId}
name = strings.ToLower(name)
} }
if h[name] != nil { if h[name] != nil {
return nil, DuplicateHeaders e = &Error{DuplicateHeaders, streamId}
} }
if err := binary.Read(r, binary.BigEndian, &length); err != nil { if err := binary.Read(r, binary.BigEndian, &length); err != nil {
return nil, err return nil, err
@ -195,6 +179,9 @@ func parseHeaderValueBlock(r io.Reader) (http.Header, os.Error) {
h.Add(name, v) h.Add(name, v)
} }
} }
if e != nil {
return h, e
}
return h, nil return h, nil
} }
@ -214,14 +201,25 @@ func (f *Framer) readSynStreamFrame(h ControlFrameHeader, frame *SynStreamFrame)
reader := f.r reader := f.r
if !f.headerCompressionDisabled { if !f.headerCompressionDisabled {
f.uncorkHeaderDecompressor(int(h.length - 10)) f.uncorkHeaderDecompressor(int64(h.length - 10))
reader = f.headerDecompressor reader = f.headerDecompressor
} }
frame.Headers, err = parseHeaderValueBlock(reader) frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
if !f.headerCompressionDisabled && ((err == os.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
err = &Error{WrongCompressedPayloadSize, 0}
}
if err != nil { if err != nil {
return err return err
} }
// Remove this condition when we bump Version to 3.
if Version >= 3 {
for h, _ := range frame.Headers {
if invalidReqHeaders[h] {
return &Error{InvalidHeaderPresent, frame.StreamId}
}
}
}
return nil return nil
} }
@ -237,13 +235,24 @@ func (f *Framer) readSynReplyFrame(h ControlFrameHeader, frame *SynReplyFrame) o
} }
reader := f.r reader := f.r
if !f.headerCompressionDisabled { if !f.headerCompressionDisabled {
f.uncorkHeaderDecompressor(int(h.length - 6)) f.uncorkHeaderDecompressor(int64(h.length - 6))
reader = f.headerDecompressor reader = f.headerDecompressor
} }
frame.Headers, err = parseHeaderValueBlock(reader) frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
if !f.headerCompressionDisabled && ((err == os.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
err = &Error{WrongCompressedPayloadSize, 0}
}
if err != nil { if err != nil {
return err return err
} }
// Remove this condition when we bump Version to 3.
if Version >= 3 {
for h, _ := range frame.Headers {
if invalidRespHeaders[h] {
return &Error{InvalidHeaderPresent, frame.StreamId}
}
}
}
return nil return nil
} }
@ -259,13 +268,31 @@ func (f *Framer) readHeadersFrame(h ControlFrameHeader, frame *HeadersFrame) os.
} }
reader := f.r reader := f.r
if !f.headerCompressionDisabled { if !f.headerCompressionDisabled {
f.uncorkHeaderDecompressor(int(h.length - 6)) f.uncorkHeaderDecompressor(int64(h.length - 6))
reader = f.headerDecompressor reader = f.headerDecompressor
} }
frame.Headers, err = parseHeaderValueBlock(reader) frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
if !f.headerCompressionDisabled && ((err == os.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
err = &Error{WrongCompressedPayloadSize, 0}
}
if err != nil { if err != nil {
return err return err
} }
// Remove this condition when we bump Version to 3.
if Version >= 3 {
var invalidHeaders map[string]bool
if frame.StreamId%2 == 0 {
invalidHeaders = invalidReqHeaders
} else {
invalidHeaders = invalidRespHeaders
}
for h, _ := range frame.Headers {
if invalidHeaders[h] {
return &Error{InvalidHeaderPresent, frame.StreamId}
}
}
}
return nil return nil
} }
@ -279,7 +306,6 @@ func (f *Framer) parseDataFrame(streamId uint32) (*DataFrame, os.Error) {
frame.Flags = DataFlags(length >> 24) frame.Flags = DataFlags(length >> 24)
length &= 0xffffff length &= 0xffffff
frame.Data = make([]byte, length) frame.Data = make([]byte, length)
// TODO(willchan): Support compressed data frames.
if _, err := io.ReadFull(f.r, frame.Data); err != nil { if _, err := io.ReadFull(f.r, frame.Data); err != nil {
return nil, err return nil, err
} }

View File

@ -21,7 +21,8 @@ func TestHeaderParsing(t *testing.T) {
var headerValueBlockBuf bytes.Buffer var headerValueBlockBuf bytes.Buffer
writeHeaderValueBlock(&headerValueBlockBuf, headers) writeHeaderValueBlock(&headerValueBlockBuf, headers)
newHeaders, err := parseHeaderValueBlock(&headerValueBlockBuf) const bogusStreamId = 1
newHeaders, err := parseHeaderValueBlock(&headerValueBlockBuf, bogusStreamId)
if err != nil { if err != nil {
t.Fatal("parseHeaderValueBlock:", err) t.Fatal("parseHeaderValueBlock:", err)
} }

View File

@ -10,7 +10,6 @@ import (
"http" "http"
"io" "io"
"os" "os"
"strconv"
) )
// Data Frame Format // Data Frame Format
@ -302,33 +301,41 @@ const HeaderDictionary = "optionsgetheadpostputdeletetrace" +
"chunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplication/xhtmltext/plainpublicmax-age" + "chunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplication/xhtmltext/plainpublicmax-age" +
"charset=iso-8859-1utf-8gzipdeflateHTTP/1.1statusversionurl\x00" "charset=iso-8859-1utf-8gzipdeflateHTTP/1.1statusversionurl\x00"
type FramerError int // A SPDY specific error.
type ErrorCode string
const ( const (
Internal FramerError = iota UnlowercasedHeaderName ErrorCode = "header was not lowercased"
InvalidControlFrame DuplicateHeaders ErrorCode = "multiple headers with same name"
UnlowercasedHeaderName WrongCompressedPayloadSize ErrorCode = "compressed payload size was incorrect"
DuplicateHeaders UnknownFrameType ErrorCode = "unknown frame type"
UnknownFrameType InvalidControlFrame ErrorCode = "invalid control frame"
InvalidDataFrame InvalidDataFrame ErrorCode = "invalid data frame"
InvalidHeaderPresent ErrorCode = "frame contained invalid header"
) )
func (e FramerError) String() string { // Error contains both the type of error and additional values. StreamId is 0
switch e { // if Error is not associated with a stream.
case Internal: type Error struct {
return "Internal" Err ErrorCode
case InvalidControlFrame: StreamId uint32
return "InvalidControlFrame" }
case UnlowercasedHeaderName:
return "UnlowercasedHeaderName" func (e *Error) String() string {
case DuplicateHeaders: return string(e.Err)
return "DuplicateHeaders" }
case UnknownFrameType:
return "UnknownFrameType" var invalidReqHeaders = map[string]bool{
case InvalidDataFrame: "Connection": true,
return "InvalidDataFrame" "Keep-Alive": true,
} "Proxy-Connection": true,
return "Error(" + strconv.Itoa(int(e)) + ")" "Transfer-Encoding": true,
}
var invalidRespHeaders = map[string]bool{
"Connection": true,
"Keep-Alive": true,
"Transfer-Encoding": true,
} }
// Framer handles serializing/deserializing SPDY frames, including compressing/ // Framer handles serializing/deserializing SPDY frames, including compressing/
@ -339,7 +346,7 @@ type Framer struct {
headerBuf *bytes.Buffer headerBuf *bytes.Buffer
headerCompressor *zlib.Writer headerCompressor *zlib.Writer
r io.Reader r io.Reader
headerReader corkedReader headerReader io.LimitedReader
headerDecompressor io.ReadCloser headerDecompressor io.ReadCloser
} }

View File

@ -267,10 +267,9 @@ func (f *Framer) writeHeadersFrame(frame *HeadersFrame) (err os.Error) {
func (f *Framer) writeDataFrame(frame *DataFrame) (err os.Error) { func (f *Framer) writeDataFrame(frame *DataFrame) (err os.Error) {
// Validate DataFrame // Validate DataFrame
if frame.StreamId&0x80000000 != 0 || len(frame.Data) >= 0x0f000000 { if frame.StreamId&0x80000000 != 0 || len(frame.Data) >= 0x0f000000 {
return InvalidDataFrame return &Error{InvalidDataFrame, frame.StreamId}
} }
// TODO(willchan): Support data compression.
// Serialize frame to Writer // Serialize frame to Writer
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
return return