0a7863635b804c966e5c0999b8c0b7938efb4567 — Martin Angers 7 months 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 @@
 	// 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 @@
 		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 @@
 	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 @@
 	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 @@
 			if string(b) != "ok" {
 				t.Fatalf(`want response "ok", got %s`, string(b))
 			}
+			cancel()
 			wg.Wait()
 		})
 	}