From 71a14721c564c5847bfb0561dec37a33364a2a8f Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Mon, 3 Nov 2025 23:40:04 +0000 Subject: [PATCH] fixup! Add support of h2c over cleartext http --- README.md | 6 ++- http.go | 76 ++++++++++++++++++++++--------------- http_test.go | 40 ++++++++++--------- request_compression_test.go | 23 ++++++----- 4 files changed, 87 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 7f692a9..96eb5fc 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,11 @@ Therefore, I present you the simple client to which you can copy & paste your gr ## Features * Executing GraphQL queries as they are, without types declaration -* HTTP2 support! +* HTTP/2 support for HTTPS endpoints (automatic) +* HTTP/1.1 for HTTP endpoints (compatible with all servers) +* Automatic retry with configurable error patterns * Support for additional headers -* Query cache +* Query cache with compression ## Usage example diff --git a/http.go b/http.go index cad2034..45fdb12 100644 --- a/http.go +++ b/http.go @@ -11,42 +11,58 @@ import ( ) func (b *BaseClient) createHttpClient() (http_client *http.Client) { - // Create TLS config for HTTPS endpoints - var tlsClientConfig *tls.Config - if strings.HasPrefix(b.endpoint, "https://") { - tlsClientConfig = &tls.Config{ - InsecureSkipVerify: true, // TODO: Make this configurable via environment variable - } - } - - // Use HTTP/2 transport for both http:// (h2c) and https:// endpoints - // AllowHTTP=true enables HTTP/2 Cleartext (h2c) for unencrypted connections - http2Transport := &http2.Transport{ - AllowHTTP: true, // Enable h2c for http:// endpoints - TLSClientConfig: tlsClientConfig, // nil for http://, configured for https:// - ReadIdleTimeout: 30 * time.Second, // Increased for better connection reuse - PingTimeout: 10 * time.Second, // Reduced for faster dead connection detection - WriteByteTimeout: 10 * time.Second, // Write timeout for better error handling - DisableCompression: true, // Disable automatic compression to prevent trailing garbage errors - } - - http_client = &http.Client{ - Timeout: 30 * time.Second, // Overall request timeout - Transport: http2Transport, - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse // Don't follow redirects automatically - }, - } - - // Log which protocol is being used if strings.HasPrefix(b.endpoint, "http://") { + // Use HTTP/1.1 for http:// endpoints + // Note: h2c (HTTP/2 Cleartext) is rarely supported by servers + httpTransport := &http.Transport{ + MaxIdleConns: 100, + MaxConnsPerHost: 50, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 30 * time.Second, + ResponseHeaderTimeout: 10 * time.Second, + DisableKeepAlives: false, + DisableCompression: true, // Disable automatic compression to prevent trailing garbage errors + WriteBufferSize: 4096, + ReadBufferSize: 4096, + } + + http_client = &http.Client{ + Timeout: 30 * time.Second, + Transport: httpTransport, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + b.Logger.Debug(&libpack_logger.LogMessage{ - Message: "Using HTTP/2 Cleartext (h2c) over http", + Message: "Using HTTP/1.1 for http:// endpoint", Pairs: nil, }) } else if strings.HasPrefix(b.endpoint, "https://") { + // Use HTTP/2 for https:// endpoints + tlsClientConfig := &tls.Config{ + InsecureSkipVerify: true, // TODO: Make this configurable via environment variable + } + + http2Transport := &http2.Transport{ + AllowHTTP: true, + TLSClientConfig: tlsClientConfig, + ReadIdleTimeout: 30 * time.Second, + PingTimeout: 10 * time.Second, + WriteByteTimeout: 10 * time.Second, + DisableCompression: true, // Disable automatic compression to prevent trailing garbage errors + } + + http_client = &http.Client{ + Timeout: 30 * time.Second, + Transport: http2Transport, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + b.Logger.Debug(&libpack_logger.LogMessage{ - Message: "Using HTTP/2 over TLS (https)", + Message: "Using HTTP/2 for https:// endpoint", Pairs: nil, }) } else { diff --git a/http_test.go b/http_test.go index a728a61..e5d0fa6 100644 --- a/http_test.go +++ b/http_test.go @@ -11,7 +11,7 @@ import ( // Note: http.Transport import is kept for http.ErrUseLastResponse usage in redirect tests func (suite *Tests) TestBaseClient_createHttpClient() { - suite.T().Run("should create HTTP/2 client for http endpoints (h2c)", func(t *testing.T) { + suite.T().Run("should create HTTP/1.1 client for http endpoints", func(t *testing.T) { client := NewConnection() client.SetEndpoint("http://example.com/graphql") @@ -20,15 +20,18 @@ func (suite *Tests) TestBaseClient_createHttpClient() { assert.Equal(30*time.Second, httpClient.Timeout) assert.NotNil(httpClient.Transport) - // Check that it's using HTTP/2 transport with h2c (HTTP/2 Cleartext) - transport, ok := httpClient.Transport.(*http2.Transport) + // Check that it's using HTTP/1.1 transport (h2c is rarely supported by servers) + transport, ok := httpClient.Transport.(*http.Transport) assert.True(ok) - assert.True(transport.AllowHTTP) // Required for h2c - assert.Nil(transport.TLSClientConfig) // No TLS for http:// - assert.Equal(30*time.Second, transport.ReadIdleTimeout) - assert.Equal(10*time.Second, transport.PingTimeout) - assert.Equal(10*time.Second, transport.WriteByteTimeout) + assert.Equal(100, transport.MaxIdleConns) + assert.Equal(50, transport.MaxConnsPerHost) + assert.Equal(10, transport.MaxIdleConnsPerHost) + assert.Equal(30*time.Second, transport.IdleConnTimeout) + assert.Equal(10*time.Second, transport.ResponseHeaderTimeout) + assert.False(transport.DisableKeepAlives) assert.True(transport.DisableCompression) // Request compression disabled to prevent "trailing garbage" errors + assert.Equal(4096, transport.WriteBufferSize) + assert.Equal(4096, transport.ReadBufferSize) }) suite.T().Run("should create HTTP/2 client for https endpoints", func(t *testing.T) { @@ -91,20 +94,23 @@ func (suite *Tests) TestBaseClient_createHttpClient() { } func (suite *Tests) TestBaseClient_createHttpClient_transportSettings() { - suite.T().Run("should configure HTTP/2 transport correctly for http://", func(t *testing.T) { + suite.T().Run("should configure HTTP/1.1 transport correctly for http://", func(t *testing.T) { client := NewConnection() client.SetEndpoint("http://example.com/graphql") httpClient := client.createHttpClient() - transport := httpClient.Transport.(*http2.Transport) + transport := httpClient.Transport.(*http.Transport) - // Verify all HTTP/2 transport settings for h2c - assert.True(transport.AllowHTTP) - assert.Nil(transport.TLSClientConfig) // No TLS for http:// - assert.Equal(30*time.Second, transport.ReadIdleTimeout) - assert.Equal(10*time.Second, transport.PingTimeout) - assert.Equal(10*time.Second, transport.WriteByteTimeout) + // Verify all HTTP/1.1 transport settings + assert.Equal(100, transport.MaxIdleConns) + assert.Equal(50, transport.MaxConnsPerHost) + assert.Equal(10, transport.MaxIdleConnsPerHost) + assert.Equal(30*time.Second, transport.IdleConnTimeout) + assert.Equal(10*time.Second, transport.ResponseHeaderTimeout) + assert.False(transport.DisableKeepAlives) assert.True(transport.DisableCompression) // Request compression disabled to prevent "trailing garbage" errors + assert.Equal(4096, transport.WriteBufferSize) + assert.Equal(4096, transport.ReadBufferSize) }) suite.T().Run("should configure HTTP/2 transport correctly", func(t *testing.T) { @@ -125,7 +131,7 @@ func (suite *Tests) TestBaseClient_createHttpClient_transportSettings() { } func (suite *Tests) TestBaseClient_createHttpClient_logging() { - suite.T().Run("should log HTTP/2 h2c usage", func(t *testing.T) { + suite.T().Run("should log HTTP/1.1 usage", func(t *testing.T) { client := NewConnection() client.SetEndpoint("http://example.com/graphql") diff --git a/request_compression_test.go b/request_compression_test.go index 98d2fce..05adea3 100644 --- a/request_compression_test.go +++ b/request_compression_test.go @@ -75,15 +75,20 @@ func (suite *RequestCompressionTestSuite) TestRequestCompression() { httpClient := client.createHttpClient() assert.NotNil(httpClient, "HTTP client should be created") - // Verify transport settings - now using HTTP/2 for all endpoints - transport, ok := httpClient.Transport.(*http2.Transport) - assert.True(ok, "Should be http2.Transport for all endpoints (h2c for http://, TLS for https://)") - if ok { - assert.True(transport.DisableCompression, "DisableCompression should be true to prevent trailing garbage") - if strings.HasPrefix(endpoint, "http://") { - assert.True(transport.AllowHTTP, "AllowHTTP should be true for h2c (HTTP/2 Cleartext)") - assert.Nil(transport.TLSClientConfig, "TLSClientConfig should be nil for http:// endpoints") - } else { + // Verify transport settings based on protocol + if strings.HasPrefix(endpoint, "http://") { + // HTTP/1.1 transport for http:// (h2c rarely supported) + transport, ok := httpClient.Transport.(*http.Transport) + assert.True(ok, "Should be http.Transport for http:// endpoints") + if ok { + assert.True(transport.DisableCompression, "DisableCompression should be true to prevent trailing garbage") + } + } else { + // HTTP/2 transport for https:// + transport, ok := httpClient.Transport.(*http2.Transport) + assert.True(ok, "Should be http2.Transport for https:// endpoints") + if ok { + assert.True(transport.DisableCompression, "DisableCompression should be true to prevent trailing garbage") assert.True(transport.AllowHTTP, "AllowHTTP should be true") assert.NotNil(transport.TLSClientConfig, "TLSClientConfig should be set for https:// endpoints") }