Skip to content

Commit 1f12860

Browse files
Merge pull request #73 from kaleido-io/ffresty_struct
feat: Add configuration struct for ffresty
2 parents ed9093f + 20f5963 commit 1f12860

File tree

5 files changed

+181
-33
lines changed

5 files changed

+181
-33
lines changed

pkg/ffresty/config.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package ffresty
1818

1919
import (
20+
"context"
21+
2022
"github.com/hyperledger/firefly-common/pkg/config"
2123
"github.com/hyperledger/firefly-common/pkg/fftls"
2224
)
@@ -95,3 +97,34 @@ func InitConfig(conf config.Section) {
9597
tlsConfig := conf.SubSection("tls")
9698
fftls.InitTLSConfig(tlsConfig)
9799
}
100+
101+
func GenerateConfig(ctx context.Context, conf config.Section) (*Config, error) {
102+
ffrestyConfig := &Config{
103+
URL: conf.GetString(HTTPConfigURL),
104+
ProxyURL: conf.GetString(HTTPConfigProxyURL),
105+
HTTPHeaders: conf.GetObject(HTTPConfigHeaders),
106+
AuthUsername: conf.GetString(HTTPConfigAuthUsername),
107+
AuthPassword: conf.GetString(HTTPConfigAuthPassword),
108+
Retry: conf.GetBool(HTTPConfigRetryEnabled),
109+
RetryCount: conf.GetInt(HTTPConfigRetryCount),
110+
RetryInitialDelay: conf.GetDuration(HTTPConfigRetryInitDelay),
111+
RetryMaximumDelay: conf.GetDuration(HTTPConfigRetryMaxDelay),
112+
HTTPRequestTimeout: conf.GetDuration(HTTPConfigRequestTimeout),
113+
HTTPIdleConnTimeout: conf.GetDuration(HTTPIdleTimeout),
114+
HTTPMaxIdleConns: conf.GetInt(HTTPMaxIdleConns),
115+
HTTPConnectionTimeout: conf.GetDuration(HTTPConnectionTimeout),
116+
HTTPTLSHandshakeTimeout: conf.GetDuration(HTTPTLSHandshakeTimeout),
117+
HTTPExpectContinueTimeout: conf.GetDuration(HTTPExpectContinueTimeout),
118+
HTTPPassthroughHeadersEnabled: conf.GetBool(HTTPPassthroughHeadersEnabled),
119+
HTTPCustomClient: conf.Get(HTTPCustomClient),
120+
}
121+
tlsSection := conf.SubSection("tls")
122+
tlsClientConfig, err := fftls.ConstructTLSConfig(ctx, tlsSection, fftls.ClientType)
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
ffrestyConfig.TLSClientConfig = tlsClientConfig
128+
129+
return ffrestyConfig, nil
130+
}

pkg/ffresty/config_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright © 2023 Kaleido, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package ffresty
18+
19+
import (
20+
"context"
21+
"testing"
22+
"time"
23+
24+
"github.com/hyperledger/firefly-common/pkg/fftls"
25+
"github.com/stretchr/testify/assert"
26+
)
27+
28+
func TestWSConfigGeneration(t *testing.T) {
29+
resetConf()
30+
31+
utConf.Set(HTTPConfigURL, "http://test:12345")
32+
utConf.Set(HTTPConfigProxyURL, "http://proxy:12345")
33+
utConf.Set(HTTPConfigHeaders, map[string]interface{}{
34+
"custom-header": "custom value",
35+
})
36+
utConf.Set(HTTPConfigAuthUsername, "user")
37+
utConf.Set(HTTPConfigAuthPassword, "pass")
38+
utConf.Set(HTTPConfigRetryEnabled, true)
39+
utConf.Set(HTTPConfigRetryInitDelay, 1)
40+
utConf.Set(HTTPConfigRetryMaxDelay, 1)
41+
utConf.Set(HTTPConfigRetryCount, 1)
42+
utConf.Set(HTTPConfigRequestTimeout, 1)
43+
utConf.Set(HTTPIdleTimeout, 1)
44+
utConf.Set(HTTPMaxIdleConns, 1)
45+
utConf.Set(HTTPConnectionTimeout, 1)
46+
utConf.Set(HTTPTLSHandshakeTimeout, 1)
47+
utConf.Set(HTTPExpectContinueTimeout, 1)
48+
utConf.Set(HTTPPassthroughHeadersEnabled, true)
49+
50+
ctx := context.Background()
51+
config, err := GenerateConfig(ctx, utConf)
52+
assert.NoError(t, err)
53+
54+
assert.Equal(t, "http://test:12345", config.URL)
55+
assert.Equal(t, "http://proxy:12345", config.ProxyURL)
56+
assert.Equal(t, "user", config.AuthUsername)
57+
assert.Equal(t, "pass", config.AuthPassword)
58+
assert.Equal(t, time.Duration(1000000), config.RetryInitialDelay)
59+
assert.Equal(t, time.Duration(1000000), config.RetryMaximumDelay)
60+
assert.Equal(t, 1, config.RetryCount)
61+
assert.Equal(t, true, config.Retry)
62+
assert.Equal(t, true, config.HTTPPassthroughHeadersEnabled)
63+
assert.Equal(t, time.Duration(1000000), config.HTTPExpectContinueTimeout)
64+
assert.Equal(t, time.Duration(1000000), config.HTTPIdleConnTimeout)
65+
assert.Equal(t, time.Duration(1000000), config.HTTPTLSHandshakeTimeout)
66+
assert.Equal(t, time.Duration(1000000), config.HTTPConnectionTimeout)
67+
assert.Equal(t, 1, config.HTTPMaxIdleConns)
68+
assert.Equal(t, "custom value", config.HTTPHeaders.GetString("custom-header"))
69+
}
70+
71+
func TestWSConfigTLSGenerationFail(t *testing.T) {
72+
resetConf()
73+
74+
tlsSection := utConf.SubSection("tls")
75+
tlsSection.Set(fftls.HTTPConfTLSEnabled, true)
76+
tlsSection.Set(fftls.HTTPConfTLSCAFile, "bad-ca")
77+
78+
ctx := context.Background()
79+
_, err := GenerateConfig(ctx, utConf)
80+
assert.Regexp(t, "FF00153", err)
81+
}

pkg/ffresty/ffresty.go

Lines changed: 60 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package ffresty
1818

1919
import (
2020
"context"
21+
"crypto/tls"
2122
"encoding/base64"
2223
"fmt"
2324
"io"
@@ -29,7 +30,6 @@ import (
2930
"github.com/go-resty/resty/v2"
3031
"github.com/hyperledger/firefly-common/pkg/config"
3132
"github.com/hyperledger/firefly-common/pkg/ffapi"
32-
"github.com/hyperledger/firefly-common/pkg/fftls"
3333
"github.com/hyperledger/firefly-common/pkg/fftypes"
3434
"github.com/hyperledger/firefly-common/pkg/i18n"
3535
"github.com/hyperledger/firefly-common/pkg/log"
@@ -44,6 +44,28 @@ type retryCtx struct {
4444
attempts uint
4545
}
4646

47+
type Config struct {
48+
URL string `json:"httpURL,omitempty"`
49+
ProxyURL string `json:"proxyURL,omitempty"`
50+
HTTPRequestTimeout time.Duration `json:"requestTimeout,omitempty"`
51+
HTTPIdleConnTimeout time.Duration `json:"idleTimeout,omitempty"`
52+
HTTPMaxIdleTimeout time.Duration `json:"maxIdleTimeout,omitempty"`
53+
HTTPConnectionTimeout time.Duration `json:"connectionTimeout,omitempty"`
54+
HTTPExpectContinueTimeout time.Duration `json:"expectContinueTimeout,omitempty"`
55+
AuthUsername string `json:"authUsername,omitempty"`
56+
AuthPassword string `json:"authPassword,omitempty"`
57+
Retry bool `json:"retry,omitempty"`
58+
RetryCount int `json:"retryCount,omitempty"`
59+
RetryInitialDelay time.Duration `json:"retryInitialDelay,omitempty"`
60+
RetryMaximumDelay time.Duration `json:"retryMaximumDelay,omitempty"`
61+
HTTPMaxIdleConns int `json:"maxIdleConns,omitempty"`
62+
HTTPPassthroughHeadersEnabled bool `json:"httpPassthroughHeadersEnabled,omitempty"`
63+
HTTPHeaders fftypes.JSONObject `json:"headers,omitempty"`
64+
TLSClientConfig *tls.Config `json:"tlsClientConfig,omitempty"`
65+
HTTPTLSHandshakeTimeout time.Duration `json:"tlsHandshakeTimeout,omitempty"`
66+
HTTPCustomClient interface{} `json:"httpCustomClient,omitempty"`
67+
}
68+
4769
// OnAfterResponse when using SetDoNotParseResponse(true) for streaming binary replies,
4870
// the caller should invoke ffresty.OnAfterResponse on the response manually.
4971
// The middleware is disabled on this path :-(
@@ -69,54 +91,62 @@ func OnAfterResponse(c *resty.Client, resp *resty.Response) {
6991
// You can use the normal Resty builder pattern, to set per-instance configuration
7092
// as required.
7193
func New(ctx context.Context, staticConfig config.Section) (client *resty.Client, err error) {
72-
passthroughHeadersEnabled := staticConfig.GetBool(HTTPPassthroughHeadersEnabled)
94+
ffrestyConfig, err := GenerateConfig(ctx, staticConfig)
95+
if err != nil {
96+
return nil, err
97+
}
7398

74-
iHTTPClient := staticConfig.Get(HTTPCustomClient)
75-
if iHTTPClient != nil {
76-
if httpClient, ok := iHTTPClient.(*http.Client); ok {
99+
return NewWithConfig(ctx, *ffrestyConfig), nil
100+
}
101+
102+
// New creates a new Resty client, using static configuration (from the config file)
103+
// from a given section in the static configuration
104+
//
105+
// You can use the normal Resty builder pattern, to set per-instance configuration
106+
// as required.
107+
func NewWithConfig(ctx context.Context, ffrestyConfig Config) (client *resty.Client) {
108+
if ffrestyConfig.HTTPCustomClient != nil {
109+
if httpClient, ok := ffrestyConfig.HTTPCustomClient.(*http.Client); ok {
77110
client = resty.NewWithClient(httpClient)
78111
}
79112
}
113+
80114
if client == nil {
81115

82116
httpTransport := &http.Transport{
83117
Proxy: http.ProxyFromEnvironment,
84118
DialContext: (&net.Dialer{
85-
Timeout: staticConfig.GetDuration(HTTPConnectionTimeout),
86-
KeepAlive: staticConfig.GetDuration(HTTPConnectionTimeout),
119+
Timeout: ffrestyConfig.HTTPConnectionTimeout,
120+
KeepAlive: ffrestyConfig.HTTPConnectionTimeout,
87121
}).DialContext,
88122
ForceAttemptHTTP2: true,
89-
MaxIdleConns: staticConfig.GetInt(HTTPMaxIdleConns),
90-
IdleConnTimeout: staticConfig.GetDuration(HTTPIdleTimeout),
91-
TLSHandshakeTimeout: staticConfig.GetDuration(HTTPTLSHandshakeTimeout),
92-
ExpectContinueTimeout: staticConfig.GetDuration(HTTPExpectContinueTimeout),
123+
MaxIdleConns: ffrestyConfig.HTTPMaxIdleConns,
124+
IdleConnTimeout: ffrestyConfig.HTTPIdleConnTimeout,
125+
TLSHandshakeTimeout: ffrestyConfig.HTTPTLSHandshakeTimeout,
126+
ExpectContinueTimeout: ffrestyConfig.HTTPExpectContinueTimeout,
93127
}
94128

95-
tlsConfig, err := fftls.ConstructTLSConfig(ctx, staticConfig.SubSection("tls"), "client")
96-
if err != nil {
97-
return nil, err
129+
if ffrestyConfig.TLSClientConfig != nil {
130+
httpTransport.TLSClientConfig = ffrestyConfig.TLSClientConfig
98131
}
99132

100-
httpTransport.TLSClientConfig = tlsConfig
101-
102133
httpClient := &http.Client{
103134
Transport: httpTransport,
104135
}
105136
client = resty.NewWithClient(httpClient)
106137
}
107138

108-
url := strings.TrimSuffix(staticConfig.GetString(HTTPConfigURL), "/")
139+
url := strings.TrimSuffix(ffrestyConfig.URL, "/")
109140
if url != "" {
110141
client.SetBaseURL(url)
111142
log.L(ctx).Debugf("Created REST client to %s", url)
112143
}
113144

114-
proxy := staticConfig.GetString(HTTPConfigProxyURL)
115-
if proxy != "" {
116-
client.SetProxy(proxy)
145+
if ffrestyConfig.ProxyURL != "" {
146+
client.SetProxy(ffrestyConfig.ProxyURL)
117147
}
118148

119-
client.SetTimeout(staticConfig.GetDuration(HTTPConfigRequestTimeout))
149+
client.SetTimeout(ffrestyConfig.HTTPRequestTimeout)
120150

121151
client.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
122152
rCtx := req.Context()
@@ -135,7 +165,7 @@ func New(ctx context.Context, staticConfig config.Section) (client *resty.Client
135165
}
136166

137167
// If passthroughHeaders: true for this rest client, pass any of the allowed headers on the original req
138-
if passthroughHeadersEnabled {
168+
if ffrestyConfig.HTTPPassthroughHeadersEnabled {
139169
ctxHeaders := rCtx.Value(ffapi.CtxHeadersKey{})
140170
if ctxHeaders != nil {
141171
passthroughHeaders := ctxHeaders.(http.Header)
@@ -159,22 +189,19 @@ func New(ctx context.Context, staticConfig config.Section) (client *resty.Client
159189

160190
client.OnAfterResponse(func(c *resty.Client, r *resty.Response) error { OnAfterResponse(c, r); return nil })
161191

162-
headers := staticConfig.GetObject(HTTPConfigHeaders)
163-
for k, v := range headers {
192+
for k, v := range ffrestyConfig.HTTPHeaders {
164193
if vs, ok := v.(string); ok {
165194
client.SetHeader(k, vs)
166195
}
167196
}
168-
authUsername := staticConfig.GetString((HTTPConfigAuthUsername))
169-
authPassword := staticConfig.GetString((HTTPConfigAuthPassword))
170-
if authUsername != "" && authPassword != "" {
171-
client.SetHeader("Authorization", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", authUsername, authPassword)))))
197+
if ffrestyConfig.AuthUsername != "" && ffrestyConfig.AuthPassword != "" {
198+
client.SetHeader("Authorization", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", ffrestyConfig.AuthUsername, ffrestyConfig.AuthPassword)))))
172199
}
173200

174-
if staticConfig.GetBool(HTTPConfigRetryEnabled) {
175-
retryCount := staticConfig.GetInt(HTTPConfigRetryCount)
176-
minTimeout := staticConfig.GetDuration(HTTPConfigRetryInitDelay)
177-
maxTimeout := staticConfig.GetDuration(HTTPConfigRetryMaxDelay)
201+
if ffrestyConfig.Retry {
202+
retryCount := ffrestyConfig.RetryCount
203+
minTimeout := ffrestyConfig.RetryInitialDelay
204+
maxTimeout := ffrestyConfig.RetryMaximumDelay
178205
client.
179206
SetRetryCount(retryCount).
180207
SetRetryWaitTime(minTimeout).
@@ -191,7 +218,7 @@ func New(ctx context.Context, staticConfig config.Section) (client *resty.Client
191218
})
192219
}
193220

194-
return client, nil
221+
return client
195222
}
196223

197224
func WrapRestErr(ctx context.Context, res *resty.Response, err error, key i18n.ErrorMessageKey) error {

pkg/wsclient/wsclient_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func TestWSClientE2ETLS(t *testing.T) {
5656
}
5757
return nil
5858
}
59+
5960
afterConnect := func(ctx context.Context, w WSClient) error {
6061
return w.Send(ctx, []byte(`after connect message`))
6162
}
@@ -94,6 +95,7 @@ func TestWSClientE2ETLS(t *testing.T) {
9495
wsc2.SetURL(wsc2.URL() + "/updated")
9596
err = wsc2.Connect()
9697
assert.Error(t, err)
98+
wsc2.Close()
9799

98100
// Receive the message automatically sent in afterConnect
99101
message1 := <-toServer

pkg/wsclient/wstestserver.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"net/http"
3030
"net/http/httptest"
3131
"os"
32+
"sync"
3233
"testing"
3334
"time"
3435

@@ -77,14 +78,17 @@ func NewTestTLSWSServer(testReq func(req *http.Request), publicKeyFile *os.File,
7778
sendDone := make(chan struct{})
7879
receiveDone := make(chan struct{})
7980
connected := false
81+
mu := sync.Mutex{}
8082

8183
handlerFunc := http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
84+
mu.Lock()
8285
if testReq != nil {
8386
testReq(req)
8487
}
8588
if connected {
8689
// test server only handles one open connection, as it only has one set of channels
8790
res.WriteHeader(409)
91+
mu.Unlock()
8892
return
8993
}
9094
ws, _ := upgrader.Upgrade(res, req, http.Header{})
@@ -106,6 +110,7 @@ func NewTestTLSWSServer(testReq func(req *http.Request), publicKeyFile *os.File,
106110
}
107111
}()
108112
connected = true
113+
mu.Unlock()
109114
})
110115

111116
svr := httptest.NewUnstartedServer(handlerFunc)

0 commit comments

Comments
 (0)