~mna/kick

0a7863635b804c966e5c0999b8c0b7938efb4567 — Martin Angers 1 year, 1 month ago 616fcfa
add DisableHTTP2 option, test HTTP/2 support
3 files changed, 121 insertions(+), 2 deletions(-)

M TODO.md
M kick.go
M kick_test.go
M TODO.md => TODO.md +1 -0
@@ 13,3 13,4 @@
11. Structured logging
12. Access path / router variables from handler
13. Disable http/2 option (maybe to support websocket)
14. Health checks, including DBs

M kick.go => kick.go +14 -2
@@ 56,8 56,6 @@ type Server struct {
	// Routes is the list of routes served by the server.
	Routes []*Route

	// TODO: validate that http2 is properly supported.

	// TLS configures the TLS settings of the server.
	TLS *TLSConfig



@@ 119,6 117,10 @@ func (s *Server) build() error {
		return err
	}

	if s.TLS != nil && s.TLS.DisableHTTP2 && srv.TLSNextProto == nil {
		srv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
	}

	h, err := s.Builders.Handler(s)
	if err != nil {
		return err


@@ 342,6 344,16 @@ type TLSConfig struct {
	CertFile string
	KeyFile  string

	// DisableHTTP2 indicates that the TLS server should not support
	// HTTP/2. By default, a Server with a TLSConfig would automatically
	// support HTTP/2, but it may be useful in certain conditions to
	// disable that automatic support (e.g. for Websockets, at least
	// until those are supported on HTTP/2).
	//
	// This is an option on TLSConfig and not directly on the Server,
	// because automatic HTTP/2 support is only enabled for TLS servers.
	DisableHTTP2 bool

	_ struct{} // prevent unkeyed struct creation
}


M kick_test.go => kick_test.go +106 -0
@@ 39,6 39,111 @@ func nextPort() int {
	return nonRandomPort
}

func TestServer_HTTP2(t *testing.T) {
	const (
		timeout      = time.Second
		requestAfter = 500 * time.Millisecond
	)

	var port = nextPort()

	s := kick.Server{
		Builders: builder.Default,
		Addr:     fmt.Sprintf(":%d", port),
		Root: &kick.Root{
			NotFoundHandler: http.NotFoundHandler(),
		},
		TLS: &kick.TLSConfig{
			CertFile: localhostCert,
			KeyFile:  localhostKey,
		},
	}

	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()

		err := s.ListenAndServe(ctx)
		if err != nil {
			t.Fatalf("want no server error, got %s", err)
		}
	}()

	time.Sleep(requestAfter)
	res, err := http.Get(fmt.Sprintf("https://localhost:%d/", port))
	if err != nil {
		t.Fatalf("want no client error, got %s", err)
	}
	defer res.Body.Close()

	if res.StatusCode != 404 {
		t.Fatalf("want status code 404, got %d", res.StatusCode)
	}
	if res.ProtoMajor < 2 {
		t.Fatalf("want http/2, got %s", res.Proto)
	}

	cancel()
	wg.Wait()
}

func TestServer_HTTP2Disabled(t *testing.T) {
	const (
		timeout      = time.Second
		requestAfter = 500 * time.Millisecond
	)

	var port = nextPort()

	s := kick.Server{
		Builders: builder.Default,
		Addr:     fmt.Sprintf(":%d", port),
		Root: &kick.Root{
			NotFoundHandler: http.NotFoundHandler(),
		},
		TLS: &kick.TLSConfig{
			CertFile:     localhostCert,
			KeyFile:      localhostKey,
			DisableHTTP2: true,
		},
	}

	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()

		err := s.ListenAndServe(ctx)
		if err != nil {
			t.Fatalf("want no server error, got %s", err)
		}
	}()

	time.Sleep(requestAfter)
	res, err := http.Get(fmt.Sprintf("https://localhost:%d/", port))
	if err != nil {
		t.Fatalf("want no client error, got %s", err)
	}
	defer res.Body.Close()

	if res.StatusCode != 404 {
		t.Fatalf("want status code 404, got %d", res.StatusCode)
	}
	if res.ProtoMajor >= 2 {
		t.Fatalf("want http/1.1, got %s", res.Proto)
	}

	cancel()
	wg.Wait()
}

func TestServer_TLS(t *testing.T) {
	const (
		timeout      = time.Second


@@ 99,6 204,7 @@ func TestServer_TLS(t *testing.T) {
			if string(b) != "ok" {
				t.Fatalf(`want response "ok", got %s`, string(b))
			}
			cancel()
			wg.Wait()
		})
	}