Skip to content

Commit c680b93

Browse files
authored
create bytes decoder (#328)
1 parent 9f1169c commit c680b93

14 files changed

+468
-120
lines changed

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ run-tests: run-unit-tests run-tests-single run-tests-resilientsingle run-tests-c
146146
# The below rule exists only for backward compatibility.
147147
run-tests-http: run-unit-tests
148148

149-
run-unit-tests:
149+
run-unit-tests: run-v2-unit-tests
150150
@$(DOCKER_CMD) \
151151
--rm \
152152
-v "${ROOTDIR}":/usr/code \
@@ -155,6 +155,15 @@ run-unit-tests:
155155
$(GOIMAGE) \
156156
go test $(TESTOPTIONS) $(REPOPATH)/http $(REPOPATH)/agency
157157

158+
run-v2-unit-tests:
159+
@$(DOCKER_CMD) \
160+
--rm \
161+
-v "${ROOTDIR}"/v2:/usr/code \
162+
-e CGO_ENABLED=$(CGO_ENABLED) \
163+
-w /usr/code/ \
164+
$(GOIMAGE) \
165+
go test $(TESTOPTIONS) $(REPOPATH)/v2/connection
166+
158167
# Single server tests
159168
run-tests-single: run-tests-single-json run-tests-single-vpack run-tests-single-vst-1.0 $(VST11_SINGLE_TESTS)
160169

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2021 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
21+
//
22+
23+
package connection
24+
25+
const (
26+
PlainText = "text/plain"
27+
ApplicationOctetStream = "application/octet-stream"
28+
ApplicationZip = "application/zip"
29+
)

v2/connection/connection_http_internal.go

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// DISCLAIMER
33
//
4-
// Copyright 2020 ArangoDB GmbH, Cologne, Germany
4+
// Copyright 2020-2021 ArangoDB GmbH, Cologne, Germany
55
//
66
// Licensed under the Apache License, Version 2.0 (the "License");
77
// you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818
// Copyright holder is ArangoDB GmbH, Cologne, Germany
1919
//
2020
// Author Adam Janikowski
21+
// Author Tomasz Mielech
2122
//
2223

2324
package connection
@@ -102,22 +103,21 @@ func (j *httpConnection) SetAuthentication(a Authentication) error {
102103
return nil
103104
}
104105

105-
func (j httpConnection) Decoder(content string) Decoder {
106-
switch content {
107-
case ApplicationVPack:
108-
return getVPackDecoder()
109-
case ApplicationJSON:
110-
return getJsonDecoder()
111-
default:
112-
switch j.contentType {
113-
case ApplicationVPack:
114-
return getVPackDecoder()
115-
case ApplicationJSON:
116-
return getJsonDecoder()
117-
default:
118-
return getJsonDecoder()
119-
}
106+
// Decoder returns the decoder according to the response content type or HTTP connection request content type.
107+
// If the content type is unknown then it returns default JSON decoder.
108+
func (j httpConnection) Decoder(contentType string) Decoder {
109+
// First try to get decoder by the content type of the response.
110+
if decoder := getDecoderByContentType(contentType); decoder != nil {
111+
return decoder
112+
}
113+
114+
// Next try to get decoder by the content type of the HTTP connection.
115+
if decoder := getDecoderByContentType(j.contentType); decoder != nil {
116+
return decoder
120117
}
118+
119+
// Return the default decoder.
120+
return getJsonDecoder()
121121
}
122122

123123
func (j httpConnection) DoWithReader(ctx context.Context, request Request) (Response, io.ReadCloser, error) {
@@ -184,17 +184,16 @@ func (j httpConnection) doWithOutput(ctx context.Context, request *httpRequest,
184184
return nil, err
185185
}
186186

187-
if output != nil {
188-
defer dropBodyData(body) // In case if there is data drop it all
187+
// The body should be closed at the end of the function.
188+
defer dropBodyData(body)
189189

190+
if output != nil {
191+
// The output should be stored in the output variable.
190192
if err = j.Decoder(resp.Content()).Decode(body, output); err != nil {
191193
if err != io.EOF {
192194
return nil, errors.WithStack(err)
193195
}
194196
}
195-
} else {
196-
// We still need to read data from request, but we can do this in background and ignore output
197-
defer dropBodyData(body)
198197
}
199198

200199
return resp, nil
@@ -222,6 +221,7 @@ func (j httpConnection) do(ctx context.Context, req *httpRequest) (*httpResponse
222221
ctx = context.Background()
223222
}
224223

224+
var bodyReader io.Reader
225225
if req.Method() == http.MethodPost || req.Method() == http.MethodPut || req.Method() == http.MethodPatch {
226226
decoder := j.Decoder(j.contentType)
227227
if !j.streamSender {
@@ -230,12 +230,7 @@ func (j httpConnection) do(ctx context.Context, req *httpRequest) (*httpResponse
230230
return nil, nil, err
231231
}
232232

233-
r, err := req.asRequest(ctx, b)
234-
if err != nil {
235-
return nil, nil, errors.WithStack(err)
236-
}
237-
238-
httpReq = r
233+
bodyReader = b
239234
} else {
240235
reader, writer := io.Pipe()
241236
go func() {
@@ -245,21 +240,16 @@ func (j httpConnection) do(ctx context.Context, req *httpRequest) (*httpResponse
245240
}
246241
}()
247242

248-
r, err := req.asRequest(ctx, reader)
249-
if err != nil {
250-
return nil, nil, errors.WithStack(err)
251-
}
252-
253-
httpReq = r
243+
bodyReader = reader
254244
}
255-
} else {
256-
r, err := req.asRequest(ctx, nil)
257-
if err != nil {
258-
return nil, nil, errors.WithStack(err)
259-
}
260-
httpReq = r
261245
}
262246

247+
r, err := req.asRequest(ctx, bodyReader)
248+
if err != nil {
249+
return nil, nil, errors.WithStack(err)
250+
}
251+
httpReq = r
252+
263253
resp, err := j.client.Do(httpReq)
264254
if err != nil {
265255
log.Debugf("(%s) Request failed: %s", id, err.Error())
@@ -276,3 +266,18 @@ func (j httpConnection) do(ctx context.Context, req *httpRequest) (*httpResponse
276266

277267
return &httpResponse{response: resp, request: req}, nil, nil
278268
}
269+
270+
// getDecoderByContentType returns the decoder according to the content type.
271+
// If content type is unknown then nil is returned.
272+
func getDecoderByContentType(contentType string) Decoder {
273+
switch contentType {
274+
case ApplicationVPack:
275+
return getVPackDecoder()
276+
case ApplicationJSON:
277+
return getJsonDecoder()
278+
case PlainText, ApplicationOctetStream, ApplicationZip:
279+
return getBytesDecoder()
280+
default:
281+
return nil
282+
}
283+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2021 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
21+
//
22+
23+
package connection
24+
25+
import (
26+
"testing"
27+
28+
"github.com/stretchr/testify/assert"
29+
"github.com/stretchr/testify/require"
30+
)
31+
32+
func Test_httpConnection_Decoder(t *testing.T) {
33+
tests := map[string]struct {
34+
contentType string
35+
conn httpConnection
36+
wantDecoder Decoder
37+
}{
38+
"JSON response decoder": {
39+
contentType: ApplicationJSON,
40+
wantDecoder: getJsonDecoder(),
41+
},
42+
"VPack response decoder": {
43+
contentType: ApplicationVPack,
44+
wantDecoder: getVPackDecoder(),
45+
},
46+
"Bytes response decoder": {
47+
contentType: PlainText,
48+
wantDecoder: getBytesDecoder(),
49+
},
50+
"JSON HTTP connection decoder": {
51+
conn: httpConnection{
52+
contentType: ApplicationJSON,
53+
},
54+
wantDecoder: getJsonDecoder(),
55+
},
56+
"VPack HTTP connection decoder": {
57+
conn: httpConnection{
58+
contentType: ApplicationVPack,
59+
},
60+
wantDecoder: getVPackDecoder(),
61+
},
62+
"Bytes HTTP connection decoder": {
63+
conn: httpConnection{
64+
contentType: PlainText,
65+
},
66+
wantDecoder: getBytesDecoder(),
67+
},
68+
"default decoder": {
69+
wantDecoder: getJsonDecoder(),
70+
},
71+
}
72+
73+
for testName, test := range tests {
74+
t.Run(testName, func(t *testing.T) {
75+
decoder := test.conn.Decoder(test.contentType)
76+
77+
require.NotNil(t, decoder)
78+
assert.Equal(t, test.wantDecoder, decoder)
79+
})
80+
}
81+
}

v2/connection/connection_http_response.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// DISCLAIMER
33
//
4-
// Copyright 2020 ArangoDB GmbH, Cologne, Germany
4+
// Copyright 2020-2021 ArangoDB GmbH, Cologne, Germany
55
//
66
// Licensed under the Apache License, Version 2.0 (the "License");
77
// you may not use this file except in compliance with the License.
@@ -18,12 +18,14 @@
1818
// Copyright holder is ArangoDB GmbH, Cologne, Germany
1919
//
2020
// Author Adam Janikowski
21+
// Author Tomasz Mielech
2122
//
2223

2324
package connection
2425

2526
import (
2627
"net/http"
28+
"strings"
2729
)
2830

2931
type httpResponse struct {
@@ -44,5 +46,11 @@ func (j httpResponse) Code() int {
4446
}
4547

4648
func (j httpResponse) Content() string {
47-
return j.response.Header.Get(ContentType)
49+
value := strings.Split(j.response.Header.Get(ContentType), ";")
50+
if len(value) > 0 {
51+
// The header can be returned with arguments, e.g.: "Content-Type: text/html; charset=UTF-8".
52+
return value[0]
53+
}
54+
55+
return ""
4856
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2021 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
21+
//
22+
23+
package connection
24+
25+
import (
26+
"net/http"
27+
"testing"
28+
29+
"github.com/stretchr/testify/assert"
30+
)
31+
32+
func Test_httpResponse_Content(t *testing.T) {
33+
34+
t.Run("pure content type", func(t *testing.T) {
35+
j := httpResponse{
36+
response: &http.Response{
37+
Header: map[string][]string{},
38+
},
39+
}
40+
j.response.Header.Set(ContentType, "application/json")
41+
42+
assert.Equal(t, "application/json", j.Content())
43+
})
44+
45+
t.Run("content type with the arguments", func(t *testing.T) {
46+
j := httpResponse{
47+
response: &http.Response{
48+
Header: map[string][]string{},
49+
},
50+
}
51+
j.response.Header.Set(ContentType, "text/plain; charset=UTF-8")
52+
53+
assert.Equal(t, "text/plain", j.Content())
54+
})
55+
56+
t.Run("empty content type", func(t *testing.T) {
57+
j := httpResponse{
58+
response: &http.Response{
59+
Header: map[string][]string{},
60+
},
61+
}
62+
j.response.Header.Set(ContentType, "")
63+
64+
assert.Equal(t, "", j.Content())
65+
})
66+
67+
t.Run("content type header does not exist", func(t *testing.T) {
68+
j := httpResponse{
69+
response: &http.Response{
70+
Header: map[string][]string{},
71+
},
72+
}
73+
74+
assert.Equal(t, "", j.Content())
75+
})
76+
}

0 commit comments

Comments
 (0)