From 84534fd68c813854119cab95b78bfe1a37985997 Mon Sep 17 00:00:00 2001 From: jwierzbo Date: Wed, 16 Oct 2024 13:23:31 +0200 Subject: [PATCH 1/5] OAS-10132 Add connection configuration helper --- v2/CHANGELOG.md | 1 + v2/connection/connection_configuration.go | 138 ++++++++++++++++++ v2/examples/example_client_async_test.go | 2 +- v2/examples/example_client_basic_test.go | 26 +--- v2/examples/example_client_maglev_test.go | 6 +- v2/examples/example_client_roundrobin_test.go | 6 +- 6 files changed, 148 insertions(+), 31 deletions(-) create mode 100644 v2/connection/connection_configuration.go diff --git a/v2/CHANGELOG.md b/v2/CHANGELOG.md index 63495812..1ed9e7fd 100644 --- a/v2/CHANGELOG.md +++ b/v2/CHANGELOG.md @@ -2,6 +2,7 @@ ## [master](https://github.com/arangodb/go-driver/tree/master) (N/A) - Expose `NewType` method +- Connection configuration helper ## [2.1.1](https://github.com/arangodb/go-driver/tree/v2.1.1) (2024-09-27) - Improve backup tests stability diff --git a/v2/connection/connection_configuration.go b/v2/connection/connection_configuration.go new file mode 100644 index 00000000..ba382937 --- /dev/null +++ b/v2/connection/connection_configuration.go @@ -0,0 +1,138 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package connection + +import ( + "crypto/tls" + "net" + "net/http" + "time" + + "golang.org/x/net/http2" +) + +func DefaultHTTPConfigurationWrapper(endpoint Endpoint, insecureSkipVerify bool) HttpConfiguration { + mods := []Mod[HttpConfiguration]{ + WithHTTPEndpoint(endpoint), + } + if insecureSkipVerify { + mods = append(mods, WithHTTPTransport(DefaultHTTPTransportSettings, WithHTTPInsecureSkipVerify)) + } else { + mods = append(mods, WithHTTPTransport(DefaultHTTPTransportSettings)) + } + return New[HttpConfiguration](mods...) +} + +func DefaultHTTP2ConfigurationWrapper(endpoint Endpoint, insecureSkipVerify bool) Http2Configuration { + mods := []Mod[Http2Configuration]{ + WithHTT2PEndpoint(endpoint), + } + if insecureSkipVerify { + mods = append(mods, WithHTTP2Transport(DefaultHTTP2TransportSettings, WithHTTP2InsecureSkipVerify)) + } else { + mods = append(mods, WithHTTP2Transport(DefaultHTTP2TransportSettings)) + } + return New[Http2Configuration](mods...) +} + +type Mod[T any] func(in *T) + +func New[T any](mods ...Mod[T]) T { + var h T + for _, mod := range mods { + mod(&h) + } + return h +} + +func WithHTTPEndpoint(endpoint Endpoint) Mod[HttpConfiguration] { + return func(in *HttpConfiguration) { + in.Endpoint = endpoint + } +} + +func WithHTT2PEndpoint(endpoint Endpoint) Mod[Http2Configuration] { + return func(in *Http2Configuration) { + in.Endpoint = endpoint + in.Transport.DialTLSContext = NewHTTP2DialForEndpoint(endpoint) + } +} + +func WithHTTPTransport(mods ...Mod[http.Transport]) Mod[HttpConfiguration] { + return func(in *HttpConfiguration) { + t := New[http.Transport](mods...) + in.Transport = &t + } +} + +func WithHTTP2Transport(mods ...Mod[http2.Transport]) Mod[Http2Configuration] { + return func(in *Http2Configuration) { + t := New[http2.Transport](mods...) + in.Transport = &t + } +} + +func WithHTTPInsecureSkipVerify(in *http.Transport) { + if in.TLSClientConfig == nil { + in.TLSClientConfig = &tls.Config{} + } + in.TLSClientConfig.InsecureSkipVerify = true +} + +func WithHTTP2InsecureSkipVerify(in *http2.Transport) { + if in.TLSClientConfig == nil { + in.TLSClientConfig = &tls.Config{} + } + in.TLSClientConfig.InsecureSkipVerify = true + in.AllowHTTP = true +} + +func DefaultHTTPTransportSettings(in *http.Transport) { + in.Proxy = http.ProxyFromEnvironment + in.DialContext = (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 90 * time.Second, + }).DialContext + in.MaxIdleConns = 100 + in.IdleConnTimeout = 90 * time.Second + in.TLSHandshakeTimeout = 10 * time.Second + in.ExpectContinueTimeout = 1 * time.Second + + if in.TLSClientConfig == nil { + in.TLSClientConfig = &tls.Config{} + } + + in.TLSClientConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + } +} + +func DefaultHTTP2TransportSettings(in *http2.Transport) { + in.IdleConnTimeout = 90 * time.Second + + if in.TLSClientConfig == nil { + in.TLSClientConfig = &tls.Config{} + } + in.TLSClientConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: false, + } +} diff --git a/v2/examples/example_client_async_test.go b/v2/examples/example_client_async_test.go index fc8a17c4..32200f85 100644 --- a/v2/examples/example_client_async_test.go +++ b/v2/examples/example_client_async_test.go @@ -35,7 +35,7 @@ import ( func ExampleNewConnectionAsyncWrapper() { // Create an HTTP connection to the database endpoint := connection.NewRoundRobinEndpoints([]string{"http://localhost:8529"}) - conn := connection.NewHttpConnection(exampleJSONHTTPConnectionConfig(endpoint)) + conn := connection.NewHttp2Connection(connection.DefaultHTTP2ConfigurationWrapper(endpoint, true)) // Create ASYNC wrapper for the connection conn = connection.NewConnectionAsyncWrapper(conn) diff --git a/v2/examples/example_client_basic_test.go b/v2/examples/example_client_basic_test.go index 2c704989..7856c7c4 100644 --- a/v2/examples/example_client_basic_test.go +++ b/v2/examples/example_client_basic_test.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2023 ArangoDB GmbH, Cologne, Germany +// Copyright 2023-2024 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,11 +22,7 @@ package examples import ( "context" - "crypto/tls" "fmt" - "net" - "net/http" - "time" "github.com/arangodb/go-driver/v2/arangodb" "github.com/arangodb/go-driver/v2/connection" @@ -36,7 +32,7 @@ import ( func ExampleNewClient() { // Create an HTTP connection to the database endpoint := connection.NewRoundRobinEndpoints([]string{"http://localhost:8529"}) - conn := connection.NewHttpConnection(exampleJSONHTTPConnectionConfig(endpoint)) + conn := connection.NewHttp2Connection(connection.DefaultHTTP2ConfigurationWrapper(endpoint, true)) // Create a client client := arangodb.NewClient(conn) @@ -49,21 +45,3 @@ func ExampleNewClient() { fmt.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } } - -func exampleJSONHTTPConnectionConfig(endpoint connection.Endpoint) connection.HttpConfiguration { - return connection.HttpConfiguration{ - Endpoint: endpoint, - ContentType: connection.ApplicationJSON, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 90 * time.Second, - }).DialContext, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - }, - } -} diff --git a/v2/examples/example_client_maglev_test.go b/v2/examples/example_client_maglev_test.go index d6fb1da0..0ee361ba 100644 --- a/v2/examples/example_client_maglev_test.go +++ b/v2/examples/example_client_maglev_test.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2023 ArangoDB GmbH, Cologne, Germany +// Copyright 2023-2024 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -35,12 +35,12 @@ import ( // - all requests to _db/ will use endpoint 2 func ExampleNewMaglevHashEndpoints() { // Create an HTTP connection to the database - endpoints, err := connection.NewMaglevHashEndpoints( + endpoint, err := connection.NewMaglevHashEndpoints( []string{"https://a:8529", "https://a:8539", "https://b:8529"}, connection.RequestDBNameValueExtractor, ) - conn := connection.NewHttpConnection(exampleJSONHTTPConnectionConfig(endpoints)) + conn := connection.NewHttp2Connection(connection.DefaultHTTP2ConfigurationWrapper(endpoint, true)) // Create a client client := arangodb.NewClient(conn) diff --git a/v2/examples/example_client_roundrobin_test.go b/v2/examples/example_client_roundrobin_test.go index 10b0af02..b5e2b11c 100644 --- a/v2/examples/example_client_roundrobin_test.go +++ b/v2/examples/example_client_roundrobin_test.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2023 ArangoDB GmbH, Cologne, Germany +// Copyright 2023-2024 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,8 +31,8 @@ import ( // ExampleNewRoundRobinEndpoints shows how to create a client with round-robin endpoint list func ExampleNewRoundRobinEndpoints() { // Create an HTTP connection to the database - endpoints := connection.NewRoundRobinEndpoints([]string{"https://a:8529", "https://a:8539", "https://b:8529"}) - conn := connection.NewHttpConnection(exampleJSONHTTPConnectionConfig(endpoints)) + endpoint := connection.NewRoundRobinEndpoints([]string{"https://a:8529", "https://a:8539", "https://b:8529"}) + conn := connection.NewHttp2Connection(connection.DefaultHTTP2ConfigurationWrapper(endpoint, true)) // Create a client client := arangodb.NewClient(conn) From c789eda05df3628c4e81874ca454042feb50a41b Mon Sep 17 00:00:00 2001 From: jwierzbo Date: Wed, 16 Oct 2024 13:34:08 +0200 Subject: [PATCH 2/5] update --- v2/connection/connection_configuration.go | 1 - 1 file changed, 1 deletion(-) diff --git a/v2/connection/connection_configuration.go b/v2/connection/connection_configuration.go index ba382937..2ff6115d 100644 --- a/v2/connection/connection_configuration.go +++ b/v2/connection/connection_configuration.go @@ -72,7 +72,6 @@ func WithHTTPEndpoint(endpoint Endpoint) Mod[HttpConfiguration] { func WithHTT2PEndpoint(endpoint Endpoint) Mod[Http2Configuration] { return func(in *Http2Configuration) { in.Endpoint = endpoint - in.Transport.DialTLSContext = NewHTTP2DialForEndpoint(endpoint) } } From 06109cd9b82074c47d7c20a9cca699ad88e926cd Mon Sep 17 00:00:00 2001 From: jwierzbo Date: Wed, 16 Oct 2024 13:56:16 +0200 Subject: [PATCH 3/5] update --- v2/connection/connection_configuration.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/v2/connection/connection_configuration.go b/v2/connection/connection_configuration.go index 2ff6115d..59ed3355 100644 --- a/v2/connection/connection_configuration.go +++ b/v2/connection/connection_configuration.go @@ -21,6 +21,7 @@ package connection import ( + "context" "crypto/tls" "net" "net/http" @@ -102,6 +103,11 @@ func WithHTTP2InsecureSkipVerify(in *http2.Transport) { } in.TLSClientConfig.InsecureSkipVerify = true in.AllowHTTP = true + + in.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) { + // Use net.Dial for plain TCP connection (h2c) + return net.DialTimeout(network, addr, 30*time.Second) + } } func DefaultHTTPTransportSettings(in *http.Transport) { From 3e023df0253b32c11b45dc51763581c5e8b5804d Mon Sep 17 00:00:00 2001 From: jwierzbo Date: Wed, 16 Oct 2024 13:59:20 +0200 Subject: [PATCH 4/5] update examples --- v2/examples/example_client_async_test.go | 20 ++++++++++++------- v2/examples/example_client_basic_test.go | 12 ++++++++--- v2/examples/example_client_maglev_test.go | 13 +++++++++--- v2/examples/example_client_roundrobin_test.go | 13 +++++++++--- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/v2/examples/example_client_async_test.go b/v2/examples/example_client_async_test.go index 32200f85..6d7f7d7a 100644 --- a/v2/examples/example_client_async_test.go +++ b/v2/examples/example_client_async_test.go @@ -22,7 +22,6 @@ package examples import ( "context" - "fmt" "log" "time" @@ -32,11 +31,17 @@ import ( // ExampleNewConnectionAsyncWrapper shows how to create a connection wrapper for async requests // It lets use async requests on demand -func ExampleNewConnectionAsyncWrapper() { +func Main() { // Create an HTTP connection to the database endpoint := connection.NewRoundRobinEndpoints([]string{"http://localhost:8529"}) conn := connection.NewHttp2Connection(connection.DefaultHTTP2ConfigurationWrapper(endpoint, true)) + auth := connection.NewBasicAuth("root", "") + err := conn.SetAuthentication(auth) + if err != nil { + log.Fatalf("Failed to set authentication: %v", err) + } + // Create ASYNC wrapper for the connection conn = connection.NewConnectionAsyncWrapper(conn) @@ -46,24 +51,25 @@ func ExampleNewConnectionAsyncWrapper() { // Ask the version of the server versionInfo, err := client.Version(context.Background()) if err != nil { - fmt.Printf("Failed to get version info: %v", err) + log.Fatalf("Failed to get version info: %v", err) } else { - fmt.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) + log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } // Trigger async request info, err := client.Version(connection.WithAsync(context.Background())) if err != nil { - fmt.Printf("this is expected error since we are using async mode and response is not ready yet: %v", err) + log.Fatalf("this is expected error since we are using async mode and response is not ready yet: %v", err) + return } if info.Version != "" { - fmt.Printf("Expected empty version if async request is in progress, got %s", info.Version) + log.Printf("Expected empty version if async request is in progress, got %s", info.Version) } // Fetch an async job id from the error id, isAsyncId := connection.IsAsyncJobInProgress(err) if !isAsyncId { - fmt.Printf("Expected async job id, got %v", id) + log.Fatalf("Expected async job id, got %v", id) } // Wait for an async result diff --git a/v2/examples/example_client_basic_test.go b/v2/examples/example_client_basic_test.go index 7856c7c4..48ef82dd 100644 --- a/v2/examples/example_client_basic_test.go +++ b/v2/examples/example_client_basic_test.go @@ -22,7 +22,7 @@ package examples import ( "context" - "fmt" + "log" "github.com/arangodb/go-driver/v2/arangodb" "github.com/arangodb/go-driver/v2/connection" @@ -34,14 +34,20 @@ func ExampleNewClient() { endpoint := connection.NewRoundRobinEndpoints([]string{"http://localhost:8529"}) conn := connection.NewHttp2Connection(connection.DefaultHTTP2ConfigurationWrapper(endpoint, true)) + // Add authentication + auth := connection.NewBasicAuth("root", "") + err := conn.SetAuthentication(auth) + if err != nil { + log.Fatalf("Failed to set authentication: %v", err) + } // Create a client client := arangodb.NewClient(conn) // Ask the version of the server versionInfo, err := client.Version(context.Background()) if err != nil { - fmt.Printf("Failed to get version info: %v", err) + log.Printf("Failed to get version info: %v", err) } else { - fmt.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) + log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } } diff --git a/v2/examples/example_client_maglev_test.go b/v2/examples/example_client_maglev_test.go index 0ee361ba..7fd75566 100644 --- a/v2/examples/example_client_maglev_test.go +++ b/v2/examples/example_client_maglev_test.go @@ -22,7 +22,7 @@ package examples import ( "context" - "fmt" + "log" "github.com/arangodb/go-driver/v2/arangodb" "github.com/arangodb/go-driver/v2/connection" @@ -42,14 +42,21 @@ func ExampleNewMaglevHashEndpoints() { conn := connection.NewHttp2Connection(connection.DefaultHTTP2ConfigurationWrapper(endpoint, true)) + // Add authentication + auth := connection.NewBasicAuth("root", "") + err = conn.SetAuthentication(auth) + if err != nil { + log.Fatalf("Failed to set authentication: %v", err) + } + // Create a client client := arangodb.NewClient(conn) // Ask the version of the server versionInfo, err := client.Version(context.Background()) if err != nil { - fmt.Printf("Failed to get version info: %v", err) + log.Printf("Failed to get version info: %v", err) } else { - fmt.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) + log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } } diff --git a/v2/examples/example_client_roundrobin_test.go b/v2/examples/example_client_roundrobin_test.go index b5e2b11c..1afd4adf 100644 --- a/v2/examples/example_client_roundrobin_test.go +++ b/v2/examples/example_client_roundrobin_test.go @@ -22,7 +22,7 @@ package examples import ( "context" - "fmt" + "log" "github.com/arangodb/go-driver/v2/arangodb" "github.com/arangodb/go-driver/v2/connection" @@ -34,14 +34,21 @@ func ExampleNewRoundRobinEndpoints() { endpoint := connection.NewRoundRobinEndpoints([]string{"https://a:8529", "https://a:8539", "https://b:8529"}) conn := connection.NewHttp2Connection(connection.DefaultHTTP2ConfigurationWrapper(endpoint, true)) + // Add authentication + auth := connection.NewBasicAuth("root", "") + err := conn.SetAuthentication(auth) + if err != nil { + log.Fatalf("Failed to set authentication: %v", err) + } + // Create a client client := arangodb.NewClient(conn) // Ask the version of the server versionInfo, err := client.Version(context.Background()) if err != nil { - fmt.Printf("Failed to get version info: %v", err) + log.Printf("Failed to get version info: %v", err) } else { - fmt.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) + log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } } From bf072054ca0764c996b1103f48590c708bc4bf07 Mon Sep 17 00:00:00 2001 From: jwierzbo Date: Wed, 16 Oct 2024 14:04:00 +0200 Subject: [PATCH 5/5] update examples --- v2/examples/example_client_async_test.go | 7 +++---- v2/examples/example_client_basic_test.go | 3 +-- v2/examples/example_client_maglev_test.go | 3 +-- v2/examples/example_client_roundrobin_test.go | 3 +-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/v2/examples/example_client_async_test.go b/v2/examples/example_client_async_test.go index 6d7f7d7a..67e11cd3 100644 --- a/v2/examples/example_client_async_test.go +++ b/v2/examples/example_client_async_test.go @@ -52,15 +52,13 @@ func Main() { versionInfo, err := client.Version(context.Background()) if err != nil { log.Fatalf("Failed to get version info: %v", err) - } else { - log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } + log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) // Trigger async request info, err := client.Version(connection.WithAsync(context.Background())) if err != nil { - log.Fatalf("this is expected error since we are using async mode and response is not ready yet: %v", err) - return + log.Printf("this is expected error since we are using async mode and response is not ready yet: %v", err) } if info.Version != "" { log.Printf("Expected empty version if async request is in progress, got %s", info.Version) @@ -89,4 +87,5 @@ func Main() { if err != nil { log.Fatalf("Failed to fetch async job result: %v", err) } + log.Printf("Async job result: %s", info.Version) } diff --git a/v2/examples/example_client_basic_test.go b/v2/examples/example_client_basic_test.go index 48ef82dd..ab56e1a2 100644 --- a/v2/examples/example_client_basic_test.go +++ b/v2/examples/example_client_basic_test.go @@ -47,7 +47,6 @@ func ExampleNewClient() { versionInfo, err := client.Version(context.Background()) if err != nil { log.Printf("Failed to get version info: %v", err) - } else { - log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } + log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } diff --git a/v2/examples/example_client_maglev_test.go b/v2/examples/example_client_maglev_test.go index 7fd75566..c85fa1d2 100644 --- a/v2/examples/example_client_maglev_test.go +++ b/v2/examples/example_client_maglev_test.go @@ -56,7 +56,6 @@ func ExampleNewMaglevHashEndpoints() { versionInfo, err := client.Version(context.Background()) if err != nil { log.Printf("Failed to get version info: %v", err) - } else { - log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } + log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } diff --git a/v2/examples/example_client_roundrobin_test.go b/v2/examples/example_client_roundrobin_test.go index 1afd4adf..ba40df94 100644 --- a/v2/examples/example_client_roundrobin_test.go +++ b/v2/examples/example_client_roundrobin_test.go @@ -48,7 +48,6 @@ func ExampleNewRoundRobinEndpoints() { versionInfo, err := client.Version(context.Background()) if err != nil { log.Printf("Failed to get version info: %v", err) - } else { - log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) } + log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) }