Skip to content

Commit 7aade17

Browse files
authored
added initial http client (#2)
* added initial http client * added timeout constants * add Authentication Headers * improved TestDefaultHTTPClientAuth with more descriptive asserts * dropped ConfigStore from test and use SetACSToken()
1 parent c3b033a commit 7aade17

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed

dcos/httpclient.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package dcos
2+
3+
import (
4+
"crypto/tls"
5+
"fmt"
6+
"net"
7+
"net/http"
8+
"time"
9+
)
10+
11+
const (
12+
// Set a 10 seconds timeout for the connection to be established.
13+
DefaultHTTPClientDialContextTimeout = 10 * time.Second
14+
// Set it to 10 seconds as well for the TLS handshake when using HTTPS.
15+
DefaultHTTPClientTLSHandshakeTimeout = 10 * time.Second
16+
// The client will be dealing with a single host (the one in baseURL),
17+
// set max idle connections to 30 regardless of the host.
18+
DefaultHTTPClientMaxIdleConns = 30
19+
DefaultHTTPClientMaxIdleConnsPerHost = 30
20+
)
21+
22+
// DefaultTransport is a http.RoundTripper that adds authentication based on Config
23+
type DefaultTransport struct {
24+
Config *Config
25+
Base http.RoundTripper
26+
}
27+
28+
func (t *DefaultTransport) base() http.RoundTripper {
29+
if t.Base != nil {
30+
return t.Base
31+
}
32+
return http.DefaultTransport
33+
}
34+
35+
// RoundTrip authorizes requests to DC/OS by adding dcos_acs_token to Authorization header
36+
func (t *DefaultTransport) RoundTrip(req *http.Request) (*http.Response, error) {
37+
// meet the requirements of RoundTripper and only modify a copy
38+
req2 := cloneRequest(req)
39+
req2.Header.Set("Authorization", fmt.Sprintf("token=%s", t.Config.ACSToken()))
40+
41+
return t.base().RoundTrip(req2)
42+
}
43+
44+
func cloneRequest(req *http.Request) *http.Request {
45+
req2 := new(http.Request)
46+
*req2 = *req
47+
48+
// until now we only clone headers as we only modify those.
49+
req2.Header = make(http.Header, len(req.Header))
50+
for k, s := range req.Header {
51+
req2.Header[k] = append([]string(nil), s...)
52+
}
53+
54+
return req2
55+
}
56+
57+
// NewHTTPClient provides a http.Client able to communicate to dcos in an authenticated way
58+
func NewHTTPClient(config *Config) *http.Client {
59+
client := &http.Client{}
60+
client.Transport = &http.Transport{
61+
62+
// Allow http_proxy, https_proxy, and no_proxy.
63+
Proxy: http.ProxyFromEnvironment,
64+
65+
DialContext: (&net.Dialer{
66+
Timeout: DefaultHTTPClientDialContextTimeout,
67+
}).DialContext,
68+
69+
TLSHandshakeTimeout: DefaultHTTPClientTLSHandshakeTimeout,
70+
TLSClientConfig: &tls.Config{
71+
InsecureSkipVerify: config.TLS().Insecure,
72+
RootCAs: config.TLS().RootCAs,
73+
},
74+
75+
MaxIdleConns: DefaultHTTPClientMaxIdleConns,
76+
MaxIdleConnsPerHost: DefaultHTTPClientMaxIdleConnsPerHost,
77+
}
78+
79+
return AddTransportHTTPClient(client, config)
80+
}
81+
82+
// AddTransportHTTPClient adds dcos.DefaultTransport to http.Client to add dcos authentication
83+
func AddTransportHTTPClient(client *http.Client, config *Config) *http.Client {
84+
transport := DefaultTransport{
85+
Config: config,
86+
Base: client.Transport,
87+
}
88+
89+
client.Transport = &transport
90+
91+
return client
92+
}

dcos/httpclient_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package dcos
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestDefaultTransportBaseNil(t *testing.T) {
12+
c := &DefaultTransport{
13+
Base: nil,
14+
}
15+
rt := c.base()
16+
17+
assert.ObjectsAreEqual(http.DefaultTransport, rt)
18+
}
19+
20+
func TestDefaultTransportBase(t *testing.T) {
21+
c := &DefaultTransport{
22+
Base: &DefaultTransport{Base: nil},
23+
}
24+
rt := c.base()
25+
26+
assert.IsType(t, &DefaultTransport{}, rt)
27+
}
28+
29+
func TestDefaultHTTPClientAuth(t *testing.T) {
30+
tokenValue := "TestDefaultHTTPClientAuth-token"
31+
config := NewConfig(nil)
32+
config.SetACSToken(tokenValue)
33+
34+
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
35+
if r.Header.Get("Authorization") == "token="+tokenValue {
36+
w.WriteHeader(http.StatusOK)
37+
w.Write([]byte("TestDefaultHTTPClientAuth - Success"))
38+
} else {
39+
w.WriteHeader(http.StatusUnauthorized)
40+
w.Write([]byte("TestDefaultHTTPClientAuth - Forbidden"))
41+
}
42+
}))
43+
defer s.Close()
44+
45+
c := NewHTTPClient(config)
46+
47+
resp, err := c.Get(s.URL)
48+
assert.NoError(t, err)
49+
assert.Equal(t, 200, resp.StatusCode, "using the dcos.NewHTTPClient should respond with 200")
50+
51+
respDflt, err := http.DefaultClient.Get(s.URL)
52+
assert.NoError(t, err)
53+
assert.Equal(t, 401, respDflt.StatusCode, "expect a forbidden state with http.DefaultClient")
54+
}

dcos/version.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dcos
2+
3+
// Version represents the client library version
4+
const Version = "0.0.1"
5+
6+
// ClientName represents the libraries name
7+
const ClientName = "dcos-client-go"

0 commit comments

Comments
 (0)