Skip to content

Commit 84e454c

Browse files
authored
[Bugfix] Fix HTTP Client NPE (#1683)
1 parent dc9782a commit 84e454c

File tree

6 files changed

+203
-43
lines changed

6 files changed

+203
-43
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- (Bugfix) Adjust Prometheus Monitor labels
77
- (Feature) Expose HTTP Client Config
88
- (Bugfix) MarkedToRemove Condition Check
9+
- (Bugfix) Fix HTTP Client NPE
910

1011
## [1.2.41](https://github.com/arangodb/kube-arangodb/tree/1.2.41) (2024-05-24)
1112
- (Maintenance) Bump Prometheus API Version

pkg/util/http/client.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,20 @@ import (
2424
"crypto/tls"
2525
"crypto/x509"
2626
"net/http"
27+
28+
"github.com/arangodb/kube-arangodb/pkg/util/mod"
2729
)
2830

29-
func RoundTripper(mods ...TransportMod) http.RoundTripper {
30-
df := append([]TransportMod{
31+
func RoundTripper(mods ...mod.Mod[*http.Transport]) http.RoundTripper {
32+
df := append([]mod.Mod[*http.Transport]{
3133
configuration.DefaultTransport,
3234
}, mods...)
3335

3436
return Transport(df...)
3537
}
3638

37-
func RoundTripperWithShortTransport(mods ...TransportMod) http.RoundTripper {
38-
df := append([]TransportMod{
39+
func RoundTripperWithShortTransport(mods ...mod.Mod[*http.Transport]) http.RoundTripper {
40+
df := append([]mod.Mod[*http.Transport]{
3941
configuration.ShortTransport,
4042
}, mods...)
4143

@@ -46,7 +48,7 @@ func Insecure(in *tls.Config) {
4648
in.InsecureSkipVerify = true
4749
}
4850

49-
func WithRootCA(ca *x509.CertPool) TransportTLSMod {
51+
func WithRootCA(ca *x509.CertPool) mod.Mod[*tls.Config] {
5052
return func(in *tls.Config) {
5153
in.RootCAs = ca
5254
}

pkg/util/http/client_configuration.go

+25-21
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,23 @@ const (
4747
defaultTransportExpectContinueTimeout = 1 * time.Second
4848
)
4949

50-
var configuration = configurationObject{
51-
TransportKeepAlive: defaultTransportKeepAlive,
52-
TransportForceAttemptHTTP2: defaultTransportForceAttemptHTTP2,
53-
TransportMaxIdleConns: defaultTransportMaxIdleConns,
54-
TransportDialTimeout: defaultTransportDialTimeout,
55-
TransportKeepAliveTimeout: defaultTransportKeepAliveTimeout,
56-
TransportKeepAliveTimeoutShort: defaultTransportKeepAliveTimeoutShort,
57-
TransportIdleConnTimeout: defaultTransportIdleConnTimeout,
58-
TransportIdleConnTimeoutShort: defaultTransportIdleConnTimeoutShort,
59-
TransportTLSHandshakeTimeout: defaultTransportTLSHandshakeTimeout,
60-
TransportExpectContinueTimeout: defaultTransportExpectContinueTimeout,
50+
func newConfiguration() configurationObject {
51+
return configurationObject{
52+
TransportKeepAlive: defaultTransportKeepAlive,
53+
TransportForceAttemptHTTP2: defaultTransportForceAttemptHTTP2,
54+
TransportMaxIdleConns: defaultTransportMaxIdleConns,
55+
TransportDialTimeout: defaultTransportDialTimeout,
56+
TransportKeepAliveTimeout: defaultTransportKeepAliveTimeout,
57+
TransportKeepAliveTimeoutShort: defaultTransportKeepAliveTimeoutShort,
58+
TransportIdleConnTimeout: defaultTransportIdleConnTimeout,
59+
TransportIdleConnTimeoutShort: defaultTransportIdleConnTimeoutShort,
60+
TransportTLSHandshakeTimeout: defaultTransportTLSHandshakeTimeout,
61+
TransportExpectContinueTimeout: defaultTransportExpectContinueTimeout,
62+
}
6163
}
6264

65+
var configuration = newConfiguration()
66+
6367
type configurationObject struct {
6468
TransportKeepAlive bool
6569
TransportForceAttemptHTTP2 bool
@@ -82,19 +86,19 @@ func (c *configurationObject) Init(cmd *cobra.Command) error {
8286

8387
f := cmd.PersistentFlags()
8488

85-
f.BoolVar(&configuration.TransportKeepAlive, "http1.keep-alive", defaultTransportKeepAlive, "If false, disables HTTP keep-alives and will only use the connection to the server for a single HTTP request")
86-
f.BoolVar(&configuration.TransportForceAttemptHTTP2, "http1.force-attempt-http2", defaultTransportForceAttemptHTTP2, "controls whether HTTP/2 is enabled")
89+
f.BoolVar(&c.TransportKeepAlive, "http1.keep-alive", defaultTransportKeepAlive, "If false, disables HTTP keep-alives and will only use the connection to the server for a single HTTP request")
90+
f.BoolVar(&c.TransportForceAttemptHTTP2, "http1.force-attempt-http2", defaultTransportForceAttemptHTTP2, "controls whether HTTP/2 is enabled")
8791

88-
f.IntVar(&configuration.TransportMaxIdleConns, "http1.transport.max-idle-conns", defaultTransportMaxIdleConns, "Maximum number of idle (keep-alive) connections across all hosts. Zero means no limit")
92+
f.IntVar(&c.TransportMaxIdleConns, "http1.transport.max-idle-conns", defaultTransportMaxIdleConns, "Maximum number of idle (keep-alive) connections across all hosts. Zero means no limit")
8993

90-
f.DurationVar(&configuration.TransportDialTimeout, "http1.transport.dial-timeout", defaultTransportDialTimeout, "Maximum amount of time a dial will wait for a connect to complete")
91-
f.DurationVar(&configuration.TransportKeepAliveTimeout, "http1.transport.keep-alive-timeout", defaultTransportKeepAliveTimeout, "Interval between keep-alive probes for an active network connection")
92-
f.DurationVar(&configuration.TransportKeepAliveTimeoutShort, "http1.transport.keep-alive-timeout-short", defaultTransportKeepAliveTimeoutShort, "Interval between keep-alive probes for an active network connection")
94+
f.DurationVar(&c.TransportDialTimeout, "http1.transport.dial-timeout", defaultTransportDialTimeout, "Maximum amount of time a dial will wait for a connect to complete")
95+
f.DurationVar(&c.TransportKeepAliveTimeout, "http1.transport.keep-alive-timeout", defaultTransportKeepAliveTimeout, "Interval between keep-alive probes for an active network connection")
96+
f.DurationVar(&c.TransportKeepAliveTimeoutShort, "http1.transport.keep-alive-timeout-short", defaultTransportKeepAliveTimeoutShort, "Interval between keep-alive probes for an active network connection")
9397

94-
f.DurationVar(&configuration.TransportIdleConnTimeout, "http1.transport.idle-conn-timeout", defaultTransportIdleConnTimeout, "Maximum amount of time an idle (keep-alive) connection will remain idle before closing itself. Zero means no limit")
95-
f.DurationVar(&configuration.TransportIdleConnTimeoutShort, "http1.transport.idle-conn-timeout-short", defaultTransportIdleConnTimeoutShort, "Maximum amount of time an idle (keep-alive) connection will remain idle before closing itself. Zero means no limit")
96-
f.DurationVar(&configuration.TransportTLSHandshakeTimeout, "http1.transport.tls-handshake-timeout", defaultTransportTLSHandshakeTimeout, "Maximum amount of time to wait for a TLS handshake. Zero means no timeout")
97-
f.DurationVar(&configuration.TransportExpectContinueTimeout, "http1.transport.except-continue-timeout", defaultTransportExpectContinueTimeout, "")
98+
f.DurationVar(&c.TransportIdleConnTimeout, "http1.transport.idle-conn-timeout", defaultTransportIdleConnTimeout, "Maximum amount of time an idle (keep-alive) connection will remain idle before closing itself. Zero means no limit")
99+
f.DurationVar(&c.TransportIdleConnTimeoutShort, "http1.transport.idle-conn-timeout-short", defaultTransportIdleConnTimeoutShort, "Maximum amount of time an idle (keep-alive) connection will remain idle before closing itself. Zero means no limit")
100+
f.DurationVar(&c.TransportTLSHandshakeTimeout, "http1.transport.tls-handshake-timeout", defaultTransportTLSHandshakeTimeout, "Maximum amount of time to wait for a TLS handshake. Zero means no timeout")
101+
f.DurationVar(&c.TransportExpectContinueTimeout, "http1.transport.except-continue-timeout", defaultTransportExpectContinueTimeout, "")
98102

99103
if err := f.MarkHidden("http1.transport.except-continue-timeout"); err != nil {
100104
return err

pkg/util/http/client_mod.go

+6-17
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,24 @@ package http
2323
import (
2424
"crypto/tls"
2525
"net/http"
26-
)
27-
28-
type Mod[T any] func(in T)
2926

30-
type TransportMod Mod[*http.Transport]
31-
32-
type TransportTLSMod Mod[*tls.Config]
27+
"github.com/arangodb/kube-arangodb/pkg/util/mod"
28+
)
3329

34-
func Transport(mods ...TransportMod) http.RoundTripper {
30+
func Transport(mods ...mod.Mod[*http.Transport]) http.RoundTripper {
3531
var c http.Transport
3632

37-
for _, m := range mods {
38-
if m == nil {
39-
continue
40-
}
41-
m(&c)
42-
}
33+
mod.Exec[*http.Transport](&c, mods...)
4334

4435
return &c
4536
}
4637

47-
func WithTransportTLS(mods ...TransportTLSMod) TransportMod {
38+
func WithTransportTLS(mods ...mod.Mod[*tls.Config]) mod.Mod[*http.Transport] {
4839
return func(in *http.Transport) {
4940
if in.TLSClientConfig == nil {
5041
in.TLSClientConfig = &tls.Config{}
5142
}
5243

53-
for _, mod := range mods {
54-
mod(in.TLSClientConfig)
55-
}
44+
mod.Exec(in.TLSClientConfig, mods...)
5645
}
5746
}

pkg/util/http/client_test.go

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
21+
package http
22+
23+
import (
24+
"net/http"
25+
"testing"
26+
"time"
27+
28+
"github.com/spf13/cobra"
29+
"github.com/stretchr/testify/require"
30+
)
31+
32+
func resetConfig() {
33+
configuration = newConfiguration()
34+
}
35+
36+
func execCommand(t *testing.T, args ...string) configurationObject {
37+
config := newConfiguration()
38+
39+
cmd := &cobra.Command{
40+
Run: func(cmd *cobra.Command, args []string) {
41+
42+
},
43+
}
44+
45+
require.NoError(t, config.Init(cmd))
46+
47+
cmd.SetArgs(args)
48+
49+
require.NoError(t, cmd.Execute())
50+
51+
return config
52+
}
53+
54+
func Test_ClientSettings(t *testing.T) {
55+
t.Run("Ensure nil function is handled", func(t *testing.T) {
56+
defer resetConfig()
57+
58+
require.NotNil(t, RoundTripper())
59+
})
60+
61+
t.Run("Ensure default settings", func(t *testing.T) {
62+
defer resetConfig()
63+
64+
def := newConfiguration()
65+
66+
evaluated := execCommand(t)
67+
68+
require.Equal(t, def, evaluated)
69+
})
70+
71+
t.Run("Ensure default settings overriden", func(t *testing.T) {
72+
defer resetConfig()
73+
74+
def := newConfiguration()
75+
76+
evaluated := execCommand(t, "--http1.keep-alive=false")
77+
78+
require.NotEqual(t, def, evaluated)
79+
80+
evaluated.TransportKeepAlive = true
81+
require.Equal(t, def, evaluated)
82+
})
83+
84+
t.Run("Ensure normal client", func(t *testing.T) {
85+
evaluated := execCommand(t)
86+
87+
transport := Transport(evaluated.DefaultTransport)
88+
89+
c, ok := transport.(*http.Transport)
90+
require.True(t, ok)
91+
92+
require.Equal(t, c.DisableKeepAlives, !evaluated.TransportKeepAlive)
93+
require.Equal(t, c.IdleConnTimeout, evaluated.TransportIdleConnTimeout)
94+
})
95+
96+
t.Run("Ensure short client", func(t *testing.T) {
97+
evaluated := execCommand(t)
98+
99+
transport := Transport(evaluated.ShortTransport)
100+
101+
c, ok := transport.(*http.Transport)
102+
require.True(t, ok)
103+
104+
require.Equal(t, c.DisableKeepAlives, !evaluated.TransportKeepAlive)
105+
require.Equal(t, c.IdleConnTimeout, evaluated.TransportIdleConnTimeoutShort)
106+
})
107+
108+
t.Run("Ensure normal client with mods", func(t *testing.T) {
109+
evaluated := execCommand(t, "--http1.transport.idle-conn-timeout=1h")
110+
111+
transport := Transport(evaluated.DefaultTransport)
112+
113+
c, ok := transport.(*http.Transport)
114+
require.True(t, ok)
115+
116+
require.Equal(t, c.DisableKeepAlives, !evaluated.TransportKeepAlive)
117+
require.Equal(t, c.IdleConnTimeout, evaluated.TransportIdleConnTimeout)
118+
require.Equal(t, c.IdleConnTimeout, time.Hour)
119+
})
120+
121+
t.Run("Ensure short client with mods", func(t *testing.T) {
122+
evaluated := execCommand(t, "--http1.transport.idle-conn-timeout-short=6h")
123+
124+
transport := Transport(evaluated.ShortTransport)
125+
126+
c, ok := transport.(*http.Transport)
127+
require.True(t, ok)
128+
129+
require.Equal(t, c.DisableKeepAlives, !evaluated.TransportKeepAlive)
130+
require.Equal(t, c.IdleConnTimeout, evaluated.TransportIdleConnTimeoutShort)
131+
require.Equal(t, c.IdleConnTimeout, 6*time.Hour)
132+
})
133+
}

pkg/util/mod/mod.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
21+
package mod
22+
23+
type Mod[T any] func(in T)
24+
25+
func Exec[T any](in T, mods ...Mod[T]) {
26+
for _, mod := range mods {
27+
if mod != nil {
28+
mod(in)
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)