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..59ed3355 --- /dev/null +++ b/v2/connection/connection_configuration.go @@ -0,0 +1,143 @@ +// +// 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 ( + "context" + "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 + } +} + +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 + + 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) { + 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..67e11cd3 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,10 +31,16 @@ 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.NewHttpConnection(exampleJSONHTTPConnectionConfig(endpoint)) + 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,23 @@ 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) - } else { - fmt.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) + log.Fatalf("Failed to get version info: %v", err) } + 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.Printf("this is expected error since we are using async mode and response is not ready yet: %v", err) } 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 @@ -83,4 +87,5 @@ func ExampleNewConnectionAsyncWrapper() { 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 2c704989..ab56e1a2 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" + "log" "github.com/arangodb/go-driver/v2/arangodb" "github.com/arangodb/go-driver/v2/connection" @@ -36,34 +32,21 @@ 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)) + // 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) - } else { - 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, - }, + log.Printf("Failed to get version info: %v", err) } + 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 d6fb1da0..c85fa1d2 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. @@ -22,7 +22,7 @@ package examples import ( "context" - "fmt" + "log" "github.com/arangodb/go-driver/v2/arangodb" "github.com/arangodb/go-driver/v2/connection" @@ -35,12 +35,19 @@ 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)) + + // 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) @@ -48,8 +55,7 @@ func ExampleNewMaglevHashEndpoints() { // Ask the version of the server versionInfo, err := client.Version(context.Background()) if err != nil { - fmt.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("Failed to get version info: %v", err) } + 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 10b0af02..ba40df94 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. @@ -22,7 +22,7 @@ package examples import ( "context" - "fmt" + "log" "github.com/arangodb/go-driver/v2/arangodb" "github.com/arangodb/go-driver/v2/connection" @@ -31,8 +31,15 @@ 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)) + + // 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) @@ -40,8 +47,7 @@ func ExampleNewRoundRobinEndpoints() { // Ask the version of the server versionInfo, err := client.Version(context.Background()) if err != nil { - fmt.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("Failed to get version info: %v", err) } + log.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License) }