integration: Add server push test
This commit is contained in:
parent
45a47936e0
commit
8b4291edcb
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue