lib/http: Add HTTP/2 cleartext support in server configuration

Enable HTTP/2 cleartext support for non-TLS listeners.
This commit is contained in:
TheBabu
2026-04-02 16:13:51 -06:00
committed by Nick Craig-Wood
parent a2ce8b04f3
commit e863f751f0
2 changed files with 60 additions and 1 deletions

View File

@@ -23,6 +23,8 @@ import (
"github.com/rclone/rclone/lib/atexit"
sdActivation "github.com/rclone/rclone/lib/sdactivation"
"github.com/spf13/pflag"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
// Help returns text describing the http server to add to the command
@@ -61,6 +63,12 @@ identically.
` + "`--{{ .Prefix }}disable-zip`" + ` may be set to disable the zipping download option.
#### Protocol
The server supports HTTP/1.1 and HTTP/2. HTTP/2 is used automatically
for TLS connections. For non-TLS connections, HTTP/2 cleartext (h2c)
is supported, allowing HTTP/2 without encryption.
#### TLS (SSL)
By default this will serve over http. If you want you can serve over
@@ -273,11 +281,18 @@ func newInstance(ctx context.Context, s *Server, listener net.Listener, tlsCfg *
listener = tls.NewListener(listener, tlsCfg)
}
var handler http.Handler = s.mux
// Enable h2c (HTTP/2 cleartext) for non-TLS listeners
if tlsCfg == nil {
h2s := &http2.Server{}
handler = h2c.NewHandler(s.mux, h2s)
}
return &instance{
url: url,
listener: listener,
httpServer: &http.Server{
Handler: s.mux,
Handler: handler,
ReadTimeout: time.Duration(s.cfg.ServerReadTimeout),
WriteTimeout: time.Duration(s.cfg.ServerWriteTimeout),
MaxHeaderBytes: s.cfg.MaxHeaderBytes,

View File

@@ -13,6 +13,7 @@ import (
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/net/http2"
)
func testEmptyHandler() http.Handler {
@@ -550,6 +551,49 @@ func TestNewServerTLS(t *testing.T) {
}
}
func TestH2CServer(t *testing.T) {
ctx := context.Background()
cfg := DefaultCfg()
cfg.ListenAddr = []string{"127.0.0.1:0"}
s, err := NewServer(ctx, WithConfig(cfg))
require.NoError(t, err)
defer func() {
require.NoError(t, s.Shutdown())
}()
expected := []byte("h2c-test-response")
s.Router().Mount("/", testEchoHandler(expected))
s.Serve()
url := testGetServerURL(t, s)
require.True(t, strings.HasPrefix(url, "http://"), "url should have http scheme (no TLS)")
// Create an HTTP/2 cleartext client
client := &http.Client{
Transport: &http2.Transport{
AllowHTTP: true,
DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
},
}
req, err := http.NewRequest("GET", url, nil)
require.NoError(t, err)
resp, err := client.Do(req)
require.NoError(t, err)
defer func() {
_ = resp.Body.Close()
}()
require.Equal(t, http.StatusOK, resp.StatusCode)
require.Equal(t, 2, resp.ProtoMajor, "response should be HTTP/2")
testExpectRespBody(t, resp, expected)
}
func TestHelpPrefixServer(t *testing.T) {
// This test assumes template variables are placed correctly.
const testPrefix = "server-help-test"