|
|
@@ -575,10 +575,15 @@ func (ct *clientTester) greet() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func (ct *clientTester) cleanup() {
|
|
|
+ ct.tr.CloseIdleConnections()
|
|
|
+}
|
|
|
+
|
|
|
func (ct *clientTester) run() {
|
|
|
errc := make(chan error, 2)
|
|
|
ct.start("client", errc, ct.client)
|
|
|
ct.start("server", errc, ct.server)
|
|
|
+ defer ct.cleanup()
|
|
|
for i := 0; i < 2; i++ {
|
|
|
if err := <-errc; err != nil {
|
|
|
ct.t.Error(err)
|
|
|
@@ -819,3 +824,181 @@ func TestTransportConnectRequest(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+type headerType int
|
|
|
+
|
|
|
+const (
|
|
|
+ noHeader headerType = iota // omitted
|
|
|
+ oneHeader
|
|
|
+ splitHeader // broken into continuation on purpose
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ f0 = noHeader
|
|
|
+ f1 = oneHeader
|
|
|
+ f2 = splitHeader
|
|
|
+ d0 = false
|
|
|
+ d1 = true
|
|
|
+)
|
|
|
+
|
|
|
+// Test all 36 combinations of response frame orders:
|
|
|
+// (3 ways of 100-continue) * (2 ways of headers) * (2 ways of data) * (3 ways of trailers):func TestTransportResponsePattern_00f0(t *testing.T) { testTransportResponsePattern(h0, h1, false, h0) }
|
|
|
+// Generated by http://play.golang.org/p/SScqYKJYXd
|
|
|
+func TestTransportResPattern_c0h1d0t0(t *testing.T) { testTransportResPattern(t, f0, f1, d0, f0) }
|
|
|
+func TestTransportResPattern_c0h1d0t1(t *testing.T) { testTransportResPattern(t, f0, f1, d0, f1) }
|
|
|
+func TestTransportResPattern_c0h1d0t2(t *testing.T) { testTransportResPattern(t, f0, f1, d0, f2) }
|
|
|
+func TestTransportResPattern_c0h1d1t0(t *testing.T) { testTransportResPattern(t, f0, f1, d1, f0) }
|
|
|
+func TestTransportResPattern_c0h1d1t1(t *testing.T) { testTransportResPattern(t, f0, f1, d1, f1) }
|
|
|
+func TestTransportResPattern_c0h1d1t2(t *testing.T) { testTransportResPattern(t, f0, f1, d1, f2) }
|
|
|
+func TestTransportResPattern_c0h2d0t0(t *testing.T) { testTransportResPattern(t, f0, f2, d0, f0) }
|
|
|
+func TestTransportResPattern_c0h2d0t1(t *testing.T) { testTransportResPattern(t, f0, f2, d0, f1) }
|
|
|
+func TestTransportResPattern_c0h2d0t2(t *testing.T) { testTransportResPattern(t, f0, f2, d0, f2) }
|
|
|
+func TestTransportResPattern_c0h2d1t0(t *testing.T) { testTransportResPattern(t, f0, f2, d1, f0) }
|
|
|
+func TestTransportResPattern_c0h2d1t1(t *testing.T) { testTransportResPattern(t, f0, f2, d1, f1) }
|
|
|
+func TestTransportResPattern_c0h2d1t2(t *testing.T) { testTransportResPattern(t, f0, f2, d1, f2) }
|
|
|
+func TestTransportResPattern_c1h1d0t0(t *testing.T) { testTransportResPattern(t, f1, f1, d0, f0) }
|
|
|
+func TestTransportResPattern_c1h1d0t1(t *testing.T) { testTransportResPattern(t, f1, f1, d0, f1) }
|
|
|
+func TestTransportResPattern_c1h1d0t2(t *testing.T) { testTransportResPattern(t, f1, f1, d0, f2) }
|
|
|
+func TestTransportResPattern_c1h1d1t0(t *testing.T) { testTransportResPattern(t, f1, f1, d1, f0) }
|
|
|
+func TestTransportResPattern_c1h1d1t1(t *testing.T) { testTransportResPattern(t, f1, f1, d1, f1) }
|
|
|
+func TestTransportResPattern_c1h1d1t2(t *testing.T) { testTransportResPattern(t, f1, f1, d1, f2) }
|
|
|
+func TestTransportResPattern_c1h2d0t0(t *testing.T) { testTransportResPattern(t, f1, f2, d0, f0) }
|
|
|
+func TestTransportResPattern_c1h2d0t1(t *testing.T) { testTransportResPattern(t, f1, f2, d0, f1) }
|
|
|
+func TestTransportResPattern_c1h2d0t2(t *testing.T) { testTransportResPattern(t, f1, f2, d0, f2) }
|
|
|
+func TestTransportResPattern_c1h2d1t0(t *testing.T) { testTransportResPattern(t, f1, f2, d1, f0) }
|
|
|
+func TestTransportResPattern_c1h2d1t1(t *testing.T) { testTransportResPattern(t, f1, f2, d1, f1) }
|
|
|
+func TestTransportResPattern_c1h2d1t2(t *testing.T) { testTransportResPattern(t, f1, f2, d1, f2) }
|
|
|
+func TestTransportResPattern_c2h1d0t0(t *testing.T) { testTransportResPattern(t, f2, f1, d0, f0) }
|
|
|
+func TestTransportResPattern_c2h1d0t1(t *testing.T) { testTransportResPattern(t, f2, f1, d0, f1) }
|
|
|
+func TestTransportResPattern_c2h1d0t2(t *testing.T) { testTransportResPattern(t, f2, f1, d0, f2) }
|
|
|
+func TestTransportResPattern_c2h1d1t0(t *testing.T) { testTransportResPattern(t, f2, f1, d1, f0) }
|
|
|
+func TestTransportResPattern_c2h1d1t1(t *testing.T) { testTransportResPattern(t, f2, f1, d1, f1) }
|
|
|
+func TestTransportResPattern_c2h1d1t2(t *testing.T) { testTransportResPattern(t, f2, f1, d1, f2) }
|
|
|
+func TestTransportResPattern_c2h2d0t0(t *testing.T) { testTransportResPattern(t, f2, f2, d0, f0) }
|
|
|
+func TestTransportResPattern_c2h2d0t1(t *testing.T) { testTransportResPattern(t, f2, f2, d0, f1) }
|
|
|
+func TestTransportResPattern_c2h2d0t2(t *testing.T) { testTransportResPattern(t, f2, f2, d0, f2) }
|
|
|
+func TestTransportResPattern_c2h2d1t0(t *testing.T) { testTransportResPattern(t, f2, f2, d1, f0) }
|
|
|
+func TestTransportResPattern_c2h2d1t1(t *testing.T) { testTransportResPattern(t, f2, f2, d1, f1) }
|
|
|
+func TestTransportResPattern_c2h2d1t2(t *testing.T) { testTransportResPattern(t, f2, f2, d1, f2) }
|
|
|
+
|
|
|
+func testTransportResPattern(t *testing.T, expect100Continue, resHeader headerType, withData bool, trailers headerType) {
|
|
|
+ const reqBody = "some request body"
|
|
|
+ const resBody = "some response body"
|
|
|
+
|
|
|
+ if resHeader == noHeader {
|
|
|
+ // TODO: test 100-continue followed by immediate
|
|
|
+ // server stream reset, without headers in the middle?
|
|
|
+ panic("invalid combination")
|
|
|
+ }
|
|
|
+
|
|
|
+ ct := newClientTester(t)
|
|
|
+ ct.client = func() error {
|
|
|
+ req, _ := http.NewRequest("POST", "https://dummy.tld/", strings.NewReader(reqBody))
|
|
|
+ if expect100Continue != noHeader {
|
|
|
+ req.Header.Set("Expect", "100-continue")
|
|
|
+ }
|
|
|
+ res, err := ct.tr.RoundTrip(req)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("RoundTrip: %v", err)
|
|
|
+ }
|
|
|
+ defer res.Body.Close()
|
|
|
+ if res.StatusCode != 200 {
|
|
|
+ return fmt.Errorf("status code = %v; want 200", res.StatusCode)
|
|
|
+ }
|
|
|
+ slurp, err := ioutil.ReadAll(res.Body)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("Slurp: %v", err)
|
|
|
+ }
|
|
|
+ wantBody := resBody
|
|
|
+ if !withData {
|
|
|
+ wantBody = ""
|
|
|
+ }
|
|
|
+ if string(slurp) != wantBody {
|
|
|
+ return fmt.Errorf("body = %q; want %q", slurp, wantBody)
|
|
|
+ }
|
|
|
+ if trailers == noHeader {
|
|
|
+ if len(res.Trailer) > 0 {
|
|
|
+ t.Errorf("Trailer = %v; want none", res.Trailer)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ want := http.Header{"Some-Trailer": {"some-value"}}
|
|
|
+ if !reflect.DeepEqual(res.Trailer, want) {
|
|
|
+ t.Errorf("Trailer = %v; want %v", res.Trailer, want)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ ct.server = func() error {
|
|
|
+ ct.greet()
|
|
|
+ var buf bytes.Buffer
|
|
|
+ enc := hpack.NewEncoder(&buf)
|
|
|
+
|
|
|
+ for {
|
|
|
+ f, err := ct.fr.ReadFrame()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ switch f := f.(type) {
|
|
|
+ case *WindowUpdateFrame, *SettingsFrame:
|
|
|
+ case *DataFrame:
|
|
|
+ // ignore for now.
|
|
|
+ case *HeadersFrame:
|
|
|
+ endStream := false
|
|
|
+ send := func(mode headerType) {
|
|
|
+ hbf := buf.Bytes()
|
|
|
+ switch mode {
|
|
|
+ case oneHeader:
|
|
|
+ ct.fr.WriteHeaders(HeadersFrameParam{
|
|
|
+ StreamID: f.StreamID,
|
|
|
+ EndHeaders: true,
|
|
|
+ EndStream: endStream,
|
|
|
+ BlockFragment: hbf,
|
|
|
+ })
|
|
|
+ case splitHeader:
|
|
|
+ if len(hbf) < 2 {
|
|
|
+ panic("too small")
|
|
|
+ }
|
|
|
+ ct.fr.WriteHeaders(HeadersFrameParam{
|
|
|
+ StreamID: f.StreamID,
|
|
|
+ EndHeaders: false,
|
|
|
+ EndStream: endStream,
|
|
|
+ BlockFragment: hbf[:1],
|
|
|
+ })
|
|
|
+ ct.fr.WriteContinuation(f.StreamID, true, hbf[1:])
|
|
|
+ default:
|
|
|
+ panic("bogus mode")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if expect100Continue != noHeader {
|
|
|
+ buf.Reset()
|
|
|
+ enc.WriteField(hpack.HeaderField{Name: ":status", Value: "100"})
|
|
|
+ send(expect100Continue)
|
|
|
+ }
|
|
|
+ // Response headers (1+ frames; 1 or 2 in this test, but never 0)
|
|
|
+ {
|
|
|
+ buf.Reset()
|
|
|
+ enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
|
|
|
+ enc.WriteField(hpack.HeaderField{Name: "x-foo", Value: "blah"})
|
|
|
+ enc.WriteField(hpack.HeaderField{Name: "x-bar", Value: "more"})
|
|
|
+ if trailers != noHeader {
|
|
|
+ enc.WriteField(hpack.HeaderField{Name: "trailer", Value: "some-trailer"})
|
|
|
+ }
|
|
|
+ endStream = withData == false && trailers == noHeader
|
|
|
+ send(resHeader)
|
|
|
+ }
|
|
|
+ if withData {
|
|
|
+ endStream = trailers == noHeader
|
|
|
+ ct.fr.WriteData(f.StreamID, endStream, []byte(resBody))
|
|
|
+ }
|
|
|
+ if trailers != noHeader {
|
|
|
+ endStream = true
|
|
|
+ buf.Reset()
|
|
|
+ enc.WriteField(hpack.HeaderField{Name: "some-trailer", Value: "some-value"})
|
|
|
+ send(trailers)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ct.run()
|
|
|
+}
|