Skip to content

Commit

Permalink
added initial http client (#2)
Browse files Browse the repository at this point in the history
* added initial http client

* added timeout constants

* add Authentication Headers

* improved TestDefaultHTTPClientAuth with more descriptive asserts

* dropped ConfigStore from test and use SetACSToken()
  • Loading branch information
fatz authored Feb 21, 2019
1 parent c3b033a commit 7aade17
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 0 deletions.
92 changes: 92 additions & 0 deletions dcos/httpclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package dcos

import (
"crypto/tls"
"fmt"
"net"
"net/http"
"time"
)

const (
// Set a 10 seconds timeout for the connection to be established.
DefaultHTTPClientDialContextTimeout = 10 * time.Second
// Set it to 10 seconds as well for the TLS handshake when using HTTPS.
DefaultHTTPClientTLSHandshakeTimeout = 10 * time.Second
// The client will be dealing with a single host (the one in baseURL),
// set max idle connections to 30 regardless of the host.
DefaultHTTPClientMaxIdleConns = 30
DefaultHTTPClientMaxIdleConnsPerHost = 30
)

// DefaultTransport is a http.RoundTripper that adds authentication based on Config
type DefaultTransport struct {
Config *Config
Base http.RoundTripper
}

func (t *DefaultTransport) base() http.RoundTripper {
if t.Base != nil {
return t.Base
}
return http.DefaultTransport
}

// RoundTrip authorizes requests to DC/OS by adding dcos_acs_token to Authorization header
func (t *DefaultTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// meet the requirements of RoundTripper and only modify a copy
req2 := cloneRequest(req)
req2.Header.Set("Authorization", fmt.Sprintf("token=%s", t.Config.ACSToken()))

return t.base().RoundTrip(req2)
}

func cloneRequest(req *http.Request) *http.Request {
req2 := new(http.Request)
*req2 = *req

// until now we only clone headers as we only modify those.
req2.Header = make(http.Header, len(req.Header))
for k, s := range req.Header {
req2.Header[k] = append([]string(nil), s...)
}

return req2
}

// NewHTTPClient provides a http.Client able to communicate to dcos in an authenticated way
func NewHTTPClient(config *Config) *http.Client {
client := &http.Client{}
client.Transport = &http.Transport{

// Allow http_proxy, https_proxy, and no_proxy.
Proxy: http.ProxyFromEnvironment,

DialContext: (&net.Dialer{
Timeout: DefaultHTTPClientDialContextTimeout,
}).DialContext,

TLSHandshakeTimeout: DefaultHTTPClientTLSHandshakeTimeout,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: config.TLS().Insecure,
RootCAs: config.TLS().RootCAs,
},

MaxIdleConns: DefaultHTTPClientMaxIdleConns,
MaxIdleConnsPerHost: DefaultHTTPClientMaxIdleConnsPerHost,
}

return AddTransportHTTPClient(client, config)
}

// AddTransportHTTPClient adds dcos.DefaultTransport to http.Client to add dcos authentication
func AddTransportHTTPClient(client *http.Client, config *Config) *http.Client {
transport := DefaultTransport{
Config: config,
Base: client.Transport,
}

client.Transport = &transport

return client
}
54 changes: 54 additions & 0 deletions dcos/httpclient_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package dcos

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestDefaultTransportBaseNil(t *testing.T) {
c := &DefaultTransport{
Base: nil,
}
rt := c.base()

assert.ObjectsAreEqual(http.DefaultTransport, rt)
}

func TestDefaultTransportBase(t *testing.T) {
c := &DefaultTransport{
Base: &DefaultTransport{Base: nil},
}
rt := c.base()

assert.IsType(t, &DefaultTransport{}, rt)
}

func TestDefaultHTTPClientAuth(t *testing.T) {
tokenValue := "TestDefaultHTTPClientAuth-token"
config := NewConfig(nil)
config.SetACSToken(tokenValue)

s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") == "token="+tokenValue {
w.WriteHeader(http.StatusOK)
w.Write([]byte("TestDefaultHTTPClientAuth - Success"))
} else {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("TestDefaultHTTPClientAuth - Forbidden"))
}
}))
defer s.Close()

c := NewHTTPClient(config)

resp, err := c.Get(s.URL)
assert.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode, "using the dcos.NewHTTPClient should respond with 200")

respDflt, err := http.DefaultClient.Get(s.URL)
assert.NoError(t, err)
assert.Equal(t, 401, respDflt.StatusCode, "expect a forbidden state with http.DefaultClient")
}
7 changes: 7 additions & 0 deletions dcos/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dcos

// Version represents the client library version
const Version = "0.0.1"

// ClientName represents the libraries name
const ClientName = "dcos-client-go"

0 comments on commit 7aade17

Please sign in to comment.