integration: Add server push test

This commit is contained in:
Tatsuhiro Tsujikawa 2015-02-08 15:56:34 +09:00
parent 45a47936e0
commit 8b4291edcb
2 changed files with 95 additions and 13 deletions

View File

@ -491,6 +491,37 @@ func TestH2H1SNI(t *testing.T) {
} }
} }
// TestH2H1ServerPush tests server push using Link header field from
// backend server.
func TestH2H1ServerPush(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
// only resources marked as rel=preload are pushed
w.Header().Add("Link", "</css/main.css>; rel=preload, </foo>, </css/theme.css>; rel=preload")
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1ServerPush",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("res.status: %v; want %v", got, want)
}
if got, want := len(res.pushResponse), 2; got != want {
t.Fatalf("len(res.pushResponse): %v; want %v", got, want)
}
mainCSS := res.pushResponse[0]
if got, want := mainCSS.status, 200; got != want {
t.Errorf("mainCSS.status: %v; want %v", got, want)
}
themeCSS := res.pushResponse[1]
if got, want := themeCSS.status, 200; got != want {
t.Errorf("themeCSS.status: %v; want %v", got, want)
}
}
// TestH2H1GracefulShutdown tests graceful shutdown. // TestH2H1GracefulShutdown tests graceful shutdown.
func TestH2H1GracefulShutdown(t *testing.T) { func TestH2H1GracefulShutdown(t *testing.T) {
st := newServerTester(nil, t, noopHandler) st := newServerTester(nil, t, noopHandler)

View File

@ -17,6 +17,7 @@ import (
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"os/exec" "os/exec"
"sort"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@ -411,7 +412,6 @@ loop:
} }
func (st *serverTester) http2(rp requestParam) (*serverResponse, error) { func (st *serverTester) http2(rp requestParam) (*serverResponse, error) {
res := &serverResponse{}
st.headerBlkBuf.Reset() st.headerBlkBuf.Reset()
st.header = make(http.Header) st.header = make(http.Header)
@ -434,6 +434,13 @@ func (st *serverTester) http2(rp requestParam) (*serverResponse, error) {
} }
} }
res := &serverResponse{
streamID: id,
}
streams := make(map[uint32]*serverResponse)
streams[id] = res
method := "GET" method := "GET"
if rp.method != "" { if rp.method != "" {
method = rp.method method = rp.method
@ -493,34 +500,53 @@ loop:
if err != nil { if err != nil {
return res, err return res, err
} }
if f.FrameHeader.StreamID != id { sr, ok := streams[f.FrameHeader.StreamID]
if !ok {
st.header = make(http.Header) st.header = make(http.Header)
break break
} }
res.header = cloneHeader(st.header) sr.header = cloneHeader(st.header)
var status int var status int
status, err = strconv.Atoi(res.header.Get(":status")) status, err = strconv.Atoi(sr.header.Get(":status"))
if err != nil { if err != nil {
return res, fmt.Errorf("Error parsing status code: %v", err) return res, fmt.Errorf("Error parsing status code: %v", err)
} }
res.status = status sr.status = status
if f.StreamEnded() { if f.StreamEnded() {
break loop if streamEnded(res, streams, sr) {
break loop
}
} }
case *http2.PushPromiseFrame:
_, err := st.dec.Write(f.HeaderBlockFragment())
if err != nil {
return res, err
}
sr := &serverResponse{
streamID: f.PromiseID,
reqHeader: cloneHeader(st.header),
}
streams[sr.streamID] = sr
case *http2.DataFrame: case *http2.DataFrame:
if f.FrameHeader.StreamID != id { sr, ok := streams[f.FrameHeader.StreamID]
if !ok {
break break
} }
res.body = append(res.body, f.Data()...) sr.body = append(sr.body, f.Data()...)
if f.StreamEnded() { if f.StreamEnded() {
break loop if streamEnded(res, streams, sr) {
break loop
}
} }
case *http2.RSTStreamFrame: case *http2.RSTStreamFrame:
if f.FrameHeader.StreamID != id { sr, ok := streams[f.FrameHeader.StreamID]
if !ok {
break break
} }
res.errCode = f.ErrCode sr.errCode = f.ErrCode
break loop if streamEnded(res, streams, sr) {
break loop
}
case *http2.GoAwayFrame: case *http2.GoAwayFrame:
if f.ErrCode == http2.ErrCodeNo { if f.ErrCode == http2.ErrCodeNo {
break break
@ -535,21 +561,46 @@ loop:
if err := st.fr.WriteSettingsAck(); err != nil { if err := st.fr.WriteSettingsAck(); err != nil {
return res, err return res, err
} }
// TODO handle PUSH_PROMISE as well, since it alters HPACK context
} }
} }
sort.Sort(ByStreamID(res.pushResponse))
return res, nil return res, nil
} }
func streamEnded(mainSr *serverResponse, streams map[uint32]*serverResponse, sr *serverResponse) bool {
delete(streams, sr.streamID)
if mainSr.streamID != sr.streamID {
mainSr.pushResponse = append(mainSr.pushResponse, sr)
}
return len(streams) == 0
}
type serverResponse struct { type serverResponse struct {
status int // HTTP status code status int // HTTP status code
header http.Header // response header fields header http.Header // response header fields
body []byte // response body body []byte // response body
streamID uint32 // stream ID in HTTP/2
errCode http2.ErrCode // error code received in HTTP/2 RST_STREAM or GOAWAY errCode http2.ErrCode // error code received in HTTP/2 RST_STREAM or GOAWAY
connErr bool // true if HTTP/2 connection error connErr bool // true if HTTP/2 connection error
spdyGoAwayErrCode spdy.GoAwayStatus // status code received in SPDY RST_STREAM spdyGoAwayErrCode spdy.GoAwayStatus // status code received in SPDY RST_STREAM
spdyRstErrCode spdy.RstStreamStatus // status code received in SPDY GOAWAY spdyRstErrCode spdy.RstStreamStatus // status code received in SPDY GOAWAY
connClose bool // Conection: close is included in response header in HTTP/1 test connClose bool // Conection: close is included in response header in HTTP/1 test
reqHeader http.Header // http request header, currently only sotres pushed request header
pushResponse []*serverResponse // pushed response
}
type ByStreamID []*serverResponse
func (b ByStreamID) Len() int {
return len(b)
}
func (b ByStreamID) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
func (b ByStreamID) Less(i, j int) bool {
return b[i].streamID < b[j].streamID
} }
func cloneHeader(h http.Header) http.Header { func cloneHeader(h http.Header) http.Header {