Skip to content

Commit 00bb683

Browse files
authored
Repeat requests connection (#267)
1 parent 27e32ee commit 00bb683

File tree

4 files changed

+218
-0
lines changed

4 files changed

+218
-0
lines changed

http/authentication.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ package http
2525
import (
2626
"context"
2727
"encoding/base64"
28+
"errors"
2829
"fmt"
2930
"sync"
3031
"sync/atomic"
3132

3233
driver "github.com/arangodb/go-driver"
3334
)
3435

36+
// ErrAuthenticationNotChanged is returned when authentication is not changed.
37+
var ErrAuthenticationNotChanged = errors.New("authentication not changed")
38+
3539
// Authentication implements a kind of authentication.
3640
type httpAuthentication interface {
3741
// Prepare is called before the first request of the given connection is made.
@@ -41,6 +45,35 @@ type httpAuthentication interface {
4145
Configure(req driver.Request) error
4246
}
4347

48+
// IsAuthenticationTheSame checks whether two authentications are the same.
49+
func IsAuthenticationTheSame(auth1, auth2 driver.Authentication) bool {
50+
51+
if auth1 == nil && auth2 == nil {
52+
return true
53+
}
54+
55+
if auth1 == nil || auth2 == nil {
56+
return false
57+
}
58+
59+
if auth1.Type() != auth2.Type() {
60+
return false
61+
}
62+
63+
if auth1.Type() == driver.AuthenticationTypeRaw {
64+
if auth1.Get("value") != auth2.Get("value") {
65+
return false
66+
}
67+
} else {
68+
if auth1.Get("username") != auth2.Get("username") ||
69+
auth1.Get("password") != auth2.Get("password") {
70+
return false
71+
}
72+
}
73+
74+
return true
75+
}
76+
4477
// newBasicAuthentication creates an authentication implementation based on the given username & password.
4578
func newBasicAuthentication(userName, password string) httpAuthentication {
4679
auth := fmt.Sprintf("%s:%s", userName, password)

http/authentication_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2020 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+
// Author Tomasz Mielech <[email protected]>
21+
//
22+
23+
package http
24+
25+
import (
26+
"github.com/arangodb/go-driver"
27+
"github.com/stretchr/testify/assert"
28+
"testing"
29+
)
30+
31+
func TestIsAuthenticationTheSame(t *testing.T) {
32+
testCases := map[string]struct {
33+
auth1 driver.Authentication
34+
auth2 driver.Authentication
35+
expected bool
36+
}{
37+
"Two authentications are nil": {
38+
expected: true,
39+
},
40+
"One authentication is nil": {
41+
auth1: driver.BasicAuthentication("", ""),
42+
},
43+
"Different type of authentication": {
44+
auth1: driver.BasicAuthentication("", ""),
45+
auth2: driver.JWTAuthentication("", ""),
46+
},
47+
"Raw authentications are different": {
48+
auth1: driver.RawAuthentication(""),
49+
auth2: driver.RawAuthentication("test"),
50+
},
51+
"Raw authentications are the same": {
52+
auth1: driver.RawAuthentication("test"),
53+
auth2: driver.RawAuthentication("test"),
54+
expected: true,
55+
},
56+
"Basic authentications are different": {
57+
auth1: driver.BasicAuthentication("test", "test"),
58+
auth2: driver.BasicAuthentication("test", "test1"),
59+
},
60+
"Basic authentications are the same": {
61+
auth1: driver.BasicAuthentication("test", "test"),
62+
auth2: driver.BasicAuthentication("test", "test"),
63+
expected: true,
64+
},
65+
}
66+
67+
for testName, testCase := range testCases {
68+
t.Run(testName, func(t *testing.T) {
69+
equal := IsAuthenticationTheSame(testCase.auth1, testCase.auth2)
70+
assert.Equal(t, testCase.expected, equal)
71+
})
72+
}
73+
74+
}

http/connection.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"net/http/httptrace"
3535
"net/url"
3636
"strings"
37+
"sync"
3738
"time"
3839

3940
driver "github.com/arangodb/go-driver"
@@ -413,3 +414,78 @@ func (c *httpConnection) SetAuthentication(auth driver.Authentication) (driver.C
413414
func (c *httpConnection) Protocols() driver.ProtocolSet {
414415
return driver.ProtocolSet{driver.ProtocolHTTP}
415416
}
417+
418+
// RequestRepeater creates possibility to send the request many times.
419+
type RequestRepeater interface {
420+
Repeat(conn driver.Connection, resp driver.Response, err error) bool
421+
}
422+
423+
// RepeatConnection is responsible for sending request until request repeater gives up.
424+
type RepeatConnection struct {
425+
mutex sync.Mutex
426+
auth driver.Authentication
427+
conn driver.Connection
428+
repeat RequestRepeater
429+
}
430+
431+
func NewRepeatConnection(conn driver.Connection, repeat RequestRepeater) driver.Connection {
432+
return &RepeatConnection{
433+
conn: conn,
434+
repeat: repeat,
435+
}
436+
}
437+
438+
// NewRequest creates a new request with given method and path.
439+
func (h *RepeatConnection) NewRequest(method, path string) (driver.Request, error) {
440+
return h.conn.NewRequest(method, path)
441+
}
442+
443+
// Do performs a given request, returning its response. Repeats requests until repeat function gives up.
444+
func (h *RepeatConnection) Do(ctx context.Context, req driver.Request) (driver.Response, error) {
445+
for {
446+
resp, err := h.conn.Do(ctx, req.Clone())
447+
448+
if !h.repeat.Repeat(h, resp, err) {
449+
return resp, err
450+
}
451+
}
452+
}
453+
454+
// Unmarshal unmarshals the given raw object into the given result interface.
455+
func (h *RepeatConnection) Unmarshal(data driver.RawObject, result interface{}) error {
456+
return h.conn.Unmarshal(data, result)
457+
}
458+
459+
// Endpoints returns the endpoints used by this connection.
460+
func (h *RepeatConnection) Endpoints() []string {
461+
return h.conn.Endpoints()
462+
}
463+
464+
// UpdateEndpoints reconfigures the connection to use the given endpoints.
465+
func (h *RepeatConnection) UpdateEndpoints(endpoints []string) error {
466+
return h.conn.UpdateEndpoints(endpoints)
467+
}
468+
469+
// Configure the authentication used for this connection.
470+
// Returns ErrAuthenticationNotChanged in when the authentication is not changed.
471+
func (h *RepeatConnection) SetAuthentication(authentication driver.Authentication) (driver.Connection, error) {
472+
h.mutex.Lock()
473+
defer h.mutex.Unlock()
474+
475+
if IsAuthenticationTheSame(h.auth, authentication) {
476+
return h, ErrAuthenticationNotChanged
477+
}
478+
479+
_, err := h.conn.SetAuthentication(authentication)
480+
if err != nil {
481+
return nil, driver.WithStack(err)
482+
}
483+
h.auth = authentication
484+
485+
return h, nil
486+
}
487+
488+
// Protocols returns all protocols used by this connection.
489+
func (h RepeatConnection) Protocols() driver.ProtocolSet {
490+
return h.conn.Protocols()
491+
}

test/client_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
"context"
2727
"crypto/tls"
2828
"fmt"
29+
"github.com/stretchr/testify/assert"
30+
"github.com/stretchr/testify/require"
2931
"log"
3032
httplib "net/http"
3133
"os"
@@ -423,3 +425,36 @@ func TestResponseHeader(t *testing.T) {
423425
}
424426
}
425427
}
428+
429+
type dummyRequestRepeat struct {
430+
counter int
431+
}
432+
433+
func (r *dummyRequestRepeat) Repeat(conn driver.Connection, resp driver.Response, err error) bool {
434+
r.counter++
435+
if r.counter == 2 {
436+
return false
437+
}
438+
return true
439+
}
440+
441+
func TestCreateClientHttpRepeatConnection(t *testing.T) {
442+
if getTestMode() != testModeSingle {
443+
t.Skipf("Not a single")
444+
}
445+
createClientFromEnv(t, true)
446+
447+
requestRepeat := dummyRequestRepeat{}
448+
conn := createConnectionFromEnv(t)
449+
c, err := driver.NewClient(driver.ClientConfig{
450+
Connection: http.NewRepeatConnection(conn, &requestRepeat),
451+
Authentication: createAuthenticationFromEnv(t),
452+
})
453+
454+
_, err = c.Connection().SetAuthentication(createAuthenticationFromEnv(t))
455+
assert.Equal(t, http.ErrAuthenticationNotChanged, err)
456+
457+
_, err = c.Databases(nil)
458+
require.NoError(t, err)
459+
assert.Equal(t, 2, requestRepeat.counter)
460+
}

0 commit comments

Comments
 (0)