9 "github.com/bradfitz/http2"
10 "github.com/bradfitz/http2/hpack"
11 "github.com/tatsuhiro-t/go-nghttp2"
12 "golang.org/x/net/spdy"
28 serverBin = buildDir + "/src/nghttpx"
30 testDir = buildDir + "/integration-tests"
33 func pair(name, value string) hpack.HeaderField {
34 return hpack.HeaderField{
40 type serverTester struct {
41 args []string // command-line arguments
42 cmd *exec.Cmd // test frontend server process, which is test subject
43 url string // test frontend server URL
45 ts *httptest.Server // backend server
46 frontendHost string // frontend server host
47 backendHost string // backend server host
48 conn net.Conn // connection to frontend server
49 h2PrefaceSent bool // HTTP/2 preface was sent in conn
50 nextStreamID uint32 // next stream ID
51 fr *http2.Framer // HTTP/2 framer
52 spdyFr *spdy.Framer // SPDY/3.1 framer
53 headerBlkBuf bytes.Buffer // buffer to store encoded header block
54 enc *hpack.Encoder // HTTP/2 HPACK encoder
55 header http.Header // received header fields
56 dec *hpack.Decoder // HTTP/2 HPACK decoder
57 authority string // server's host:port
58 frCh chan http2.Frame // used for incoming HTTP/2 frame
59 spdyFrCh chan spdy.Frame // used for incoming SPDY frame
63 // newServerTester creates test context for plain TCP frontend
65 func newServerTester(args []string, t *testing.T, handler http.HandlerFunc) *serverTester {
66 return newServerTesterInternal(args, t, handler, false, nil)
69 // newServerTester creates test context for TLS frontend connection.
70 func newServerTesterTLS(args []string, t *testing.T, handler http.HandlerFunc) *serverTester {
71 return newServerTesterInternal(args, t, handler, true, nil)
74 // newServerTester creates test context for TLS frontend connection
75 // with given clientConfig
76 func newServerTesterTLSConfig(args []string, t *testing.T, handler http.HandlerFunc, clientConfig *tls.Config) *serverTester {
77 return newServerTesterInternal(args, t, handler, true, clientConfig)
80 // newServerTesterInternal creates test context. If frontendTLS is
81 // true, set up TLS frontend connection.
82 func newServerTesterInternal(args []string, t *testing.T, handler http.HandlerFunc, frontendTLS bool, clientConfig *tls.Config) *serverTester {
83 ts := httptest.NewUnstartedServer(handler)
86 for _, k := range args {
88 case "--http2-bridge":
93 nghttp2.ConfigureServer(ts.Config, &nghttp2.Server{})
94 // According to httptest/server.go, we have to set
95 // NextProtos separately for ts.TLS. NextProtos set
96 // in nghttp2.ConfigureServer is effectively ignored.
97 ts.TLS = new(tls.Config)
98 ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2-14")
100 args = append(args, "-k")
107 args = append(args, testDir+"/server.key", testDir+"/server.crt")
109 args = append(args, "--frontend-no-tls")
112 backendURL, err := url.Parse(ts.URL)
114 t.Fatalf("Error parsing URL from httptest.Server: %v", err)
117 // URL.Host looks like "127.0.0.1:8080", but we want
119 b := "-b" + strings.Replace(backendURL.Host, ":", ",", -1)
120 args = append(args, fmt.Sprintf("-f127.0.0.1,%v", serverPort), b,
121 "--errorlog-file="+testDir+"/log.txt", "-LINFO")
123 authority := fmt.Sprintf("127.0.0.1:%v", serverPort)
126 cmd: exec.Command(serverBin, args...),
129 url: fmt.Sprintf("%v://%v", scheme, authority),
130 frontendHost: fmt.Sprintf("127.0.0.1:%v", serverPort),
131 backendHost: backendURL.Host,
133 authority: authority,
134 frCh: make(chan http2.Frame),
135 spdyFrCh: make(chan spdy.Frame),
136 errCh: make(chan error),
139 if err := st.cmd.Start(); err != nil {
140 st.t.Fatalf("Error starting %v: %v", serverBin, err)
148 var tlsConfig *tls.Config
149 if clientConfig == nil {
150 tlsConfig = new(tls.Config)
152 tlsConfig = clientConfig
154 tlsConfig.InsecureSkipVerify = true
155 tlsConfig.NextProtos = []string{"h2-14", "spdy/3.1"}
156 conn, err = tls.Dial("tcp", authority, tlsConfig)
158 conn, err = net.Dial("tcp", authority)
164 st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid")
166 time.Sleep(150 * time.Millisecond)
170 tlsConn := conn.(*tls.Conn)
171 cs := tlsConn.ConnectionState()
172 if !cs.NegotiatedProtocolIsMutual {
174 st.t.Fatalf("Error negotiated next protocol is not mutual")
181 st.fr = http2.NewFramer(st.conn, st.conn)
182 spdyFr, err := spdy.NewFramer(st.conn, st.conn)
185 st.t.Fatalf("Error spdy.NewFramer: %v", err)
188 st.enc = hpack.NewEncoder(&st.headerBlkBuf)
189 st.dec = hpack.NewDecoder(4096, func(f hpack.HeaderField) {
190 st.header.Add(f.Name, f.Value)
196 func (st *serverTester) Close() {
201 st.cmd.Process.Kill()
209 func (st *serverTester) readFrame() (http2.Frame, error) {
211 f, err := st.fr.ReadFrame()
222 case err := <-st.errCh:
224 case <-time.After(5 * time.Second):
225 return nil, errors.New("timeout waiting for frame")
229 func (st *serverTester) readSpdyFrame() (spdy.Frame, error) {
231 f, err := st.spdyFr.ReadFrame()
240 case f := <-st.spdyFrCh:
242 case err := <-st.errCh:
244 case <-time.After(2 * time.Second):
245 return nil, errors.New("timeout waiting for frame")
249 type requestParam struct {
250 name string // name for this request to identify the request in log easily
251 streamID uint32 // stream ID, automatically assigned if 0
252 method string // method, defaults to GET
253 scheme string // scheme, defaults to http
254 authority string // authority, defaults to backend server address
255 path string // path, defaults to /
256 header []hpack.HeaderField // additional request header fields
257 body []byte // request body
258 trailer []hpack.HeaderField // trailer part
259 httpUpgrade bool // true if upgraded to HTTP/2 through HTTP Upgrade
262 // wrapper for request body to set trailer part
263 type chunkedBodyReader struct {
264 trailer []hpack.HeaderField
270 func (cbr *chunkedBodyReader) Read(p []byte) (n int, err error) {
271 // document says that we have to set http.Request.Trailer
272 // after request was sent and before body returns EOF.
273 if !cbr.trailerWritten {
274 cbr.trailerWritten = true
275 for _, h := range cbr.trailer {
276 cbr.req.Trailer.Set(h.Name, h.Value)
279 return cbr.body.Read(p)
282 func (st *serverTester) http1(rp requestParam) (*serverResponse, error) {
289 var cbr *chunkedBodyReader
291 body = bytes.NewBuffer(rp.body)
292 if len(rp.trailer) != 0 {
293 cbr = &chunkedBodyReader{
304 u, err := url.Parse(st.url)
306 st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err)
312 req, err := http.NewRequest(method, reqURL, body)
316 for _, h := range rp.header {
317 req.Header.Add(h.Name, h.Value)
319 req.Header.Add("Test-Case", rp.name)
322 // this makes request use chunked encoding
323 req.ContentLength = -1
324 req.Trailer = make(http.Header)
325 for _, h := range cbr.trailer {
326 req.Trailer.Set(h.Name, "")
329 if err := req.Write(st.conn); err != nil {
332 resp, err := http.ReadResponse(bufio.NewReader(st.conn), req)
336 respBody, err := ioutil.ReadAll(resp.Body)
342 res := &serverResponse{
343 status: resp.StatusCode,
346 connClose: resp.Close,
352 func (st *serverTester) spdy(rp requestParam) (*serverResponse, error) {
353 res := &serverResponse{}
356 if rp.streamID != 0 {
357 id = spdy.StreamId(rp.streamID)
358 if id >= spdy.StreamId(st.nextStreamID) && id%2 == 1 {
359 st.nextStreamID = uint32(id) + 2
362 id = spdy.StreamId(st.nextStreamID)
377 if rp.authority != "" {
386 header := make(http.Header)
387 header.Add(":method", method)
388 header.Add(":scheme", scheme)
389 header.Add(":host", host)
390 header.Add(":path", path)
391 header.Add(":version", "HTTP/1.1")
392 header.Add("test-case", rp.name)
393 for _, h := range rp.header {
394 header.Add(h.Name, h.Value)
397 var synStreamFlags spdy.ControlFlags
398 if len(rp.body) == 0 {
399 synStreamFlags = spdy.ControlFlagFin
401 if err := st.spdyFr.WriteFrame(&spdy.SynStreamFrame{
402 CFHeader: spdy.ControlFrameHeader{
403 Flags: synStreamFlags,
411 if len(rp.body) != 0 {
412 if err := st.spdyFr.WriteFrame(&spdy.DataFrame{
414 Flags: spdy.DataFlagFin,
423 fr, err := st.readSpdyFrame()
427 switch f := fr.(type) {
428 case *spdy.SynReplyFrame:
429 if f.StreamId != id {
432 res.header = cloneHeader(f.Headers)
433 if _, err := fmt.Sscan(res.header.Get(":status"), &res.status); err != nil {
434 return res, fmt.Errorf("Error parsing status code: %v", err)
436 if f.CFHeader.Flags&spdy.ControlFlagFin != 0 {
439 case *spdy.DataFrame:
440 if f.StreamId != id {
443 res.body = append(res.body, f.Data...)
444 if f.Flags&spdy.DataFlagFin != 0 {
447 case *spdy.RstStreamFrame:
448 if f.StreamId != id {
451 res.spdyRstErrCode = f.Status
453 case *spdy.GoAwayFrame:
454 if f.Status == spdy.GoAwayOK {
457 res.spdyGoAwayErrCode = f.Status
464 func (st *serverTester) http2(rp requestParam) (*serverResponse, error) {
465 st.headerBlkBuf.Reset()
466 st.header = make(http.Header)
469 if rp.streamID != 0 {
471 if id >= st.nextStreamID && id%2 == 1 {
472 st.nextStreamID = id + 2
479 if !st.h2PrefaceSent {
480 st.h2PrefaceSent = true
481 fmt.Fprint(st.conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
482 if err := st.fr.WriteSettings(); err != nil {
487 res := &serverResponse{
491 streams := make(map[uint32]*serverResponse)
499 _ = st.enc.WriteField(pair(":method", method))
505 _ = st.enc.WriteField(pair(":scheme", scheme))
507 authority := st.authority
508 if rp.authority != "" {
509 authority = rp.authority
511 _ = st.enc.WriteField(pair(":authority", authority))
517 _ = st.enc.WriteField(pair(":path", path))
519 _ = st.enc.WriteField(pair("test-case", rp.name))
521 for _, h := range rp.header {
522 _ = st.enc.WriteField(h)
525 err := st.fr.WriteHeaders(http2.HeadersFrameParam{
527 EndStream: len(rp.body) == 0 && len(rp.trailer) == 0,
529 BlockFragment: st.headerBlkBuf.Bytes(),
535 if len(rp.body) != 0 {
536 // TODO we assume rp.body fits in 1 frame
537 if err := st.fr.WriteData(id, len(rp.trailer) == 0, rp.body); err != nil {
542 if len(rp.trailer) != 0 {
543 st.headerBlkBuf.Reset()
544 for _, h := range rp.trailer {
545 _ = st.enc.WriteField(h)
547 err := st.fr.WriteHeaders(http2.HeadersFrameParam{
551 BlockFragment: st.headerBlkBuf.Bytes(),
560 fr, err := st.readFrame()
564 switch f := fr.(type) {
565 case *http2.HeadersFrame:
566 _, err := st.dec.Write(f.HeaderBlockFragment())
570 sr, ok := streams[f.FrameHeader.StreamID]
572 st.header = make(http.Header)
575 sr.header = cloneHeader(st.header)
577 status, err = strconv.Atoi(sr.header.Get(":status"))
579 return res, fmt.Errorf("Error parsing status code: %v", err)
583 if streamEnded(res, streams, sr) {
587 case *http2.PushPromiseFrame:
588 _, err := st.dec.Write(f.HeaderBlockFragment())
592 sr := &serverResponse{
593 streamID: f.PromiseID,
594 reqHeader: cloneHeader(st.header),
596 streams[sr.streamID] = sr
597 case *http2.DataFrame:
598 sr, ok := streams[f.FrameHeader.StreamID]
602 sr.body = append(sr.body, f.Data()...)
604 if streamEnded(res, streams, sr) {
608 case *http2.RSTStreamFrame:
609 sr, ok := streams[f.FrameHeader.StreamID]
613 sr.errCode = f.ErrCode
614 if streamEnded(res, streams, sr) {
617 case *http2.GoAwayFrame:
618 if f.ErrCode == http2.ErrCodeNo {
621 res.errCode = f.ErrCode
624 case *http2.SettingsFrame:
628 if err := st.fr.WriteSettingsAck(); err != nil {
633 sort.Sort(ByStreamID(res.pushResponse))
637 func streamEnded(mainSr *serverResponse, streams map[uint32]*serverResponse, sr *serverResponse) bool {
638 delete(streams, sr.streamID)
639 if mainSr.streamID != sr.streamID {
640 mainSr.pushResponse = append(mainSr.pushResponse, sr)
642 return len(streams) == 0
645 type serverResponse struct {
646 status int // HTTP status code
647 header http.Header // response header fields
648 body []byte // response body
649 streamID uint32 // stream ID in HTTP/2
650 errCode http2.ErrCode // error code received in HTTP/2 RST_STREAM or GOAWAY
651 connErr bool // true if HTTP/2 connection error
652 spdyGoAwayErrCode spdy.GoAwayStatus // status code received in SPDY RST_STREAM
653 spdyRstErrCode spdy.RstStreamStatus // status code received in SPDY GOAWAY
654 connClose bool // Conection: close is included in response header in HTTP/1 test
655 reqHeader http.Header // http request header, currently only sotres pushed request header
656 pushResponse []*serverResponse // pushed response
659 type ByStreamID []*serverResponse
661 func (b ByStreamID) Len() int {
665 func (b ByStreamID) Swap(i, j int) {
666 b[i], b[j] = b[j], b[i]
669 func (b ByStreamID) Less(i, j int) bool {
670 return b[i].streamID < b[j].streamID
673 func cloneHeader(h http.Header) http.Header {
674 h2 := make(http.Header, len(h))
675 for k, vv := range h {
676 vv2 := make([]string, len(vv))
683 func noopHandler(w http.ResponseWriter, r *http.Request) {}