requestdecorator: repurpose the package and rename to useragent

Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
Tibor Vass 2015-05-15 15:03:08 -07:00
parent a01cc3ca77
commit cf8c0d0f56
7 changed files with 101 additions and 405 deletions

View file

@ -1,2 +0,0 @@
This package provides helper functions for decorating a request with user agent
versions, auth, meta headers.

View file

@ -1,172 +0,0 @@
// Package requestdecorator provides helper functions to decorate a request with
// user agent versions, auth, meta headers.
package requestdecorator
import (
"errors"
"io"
"net/http"
"strings"
"github.com/Sirupsen/logrus"
)
var (
ErrNilRequest = errors.New("request cannot be nil")
)
// UAVersionInfo is used to model UserAgent versions.
type UAVersionInfo struct {
Name string
Version string
}
func NewUAVersionInfo(name, version string) UAVersionInfo {
return UAVersionInfo{
Name: name,
Version: version,
}
}
func (vi *UAVersionInfo) isValid() bool {
const stopChars = " \t\r\n/"
name := vi.Name
vers := vi.Version
if len(name) == 0 || strings.ContainsAny(name, stopChars) {
return false
}
if len(vers) == 0 || strings.ContainsAny(vers, stopChars) {
return false
}
return true
}
// Convert versions to a string and append the string to the string base.
//
// Each UAVersionInfo will be converted to a string in the format of
// "product/version", where the "product" is get from the name field, while
// version is get from the version field. Several pieces of verson information
// will be concatinated and separated by space.
func AppendVersions(base string, versions ...UAVersionInfo) string {
if len(versions) == 0 {
return base
}
verstrs := make([]string, 0, 1+len(versions))
if len(base) > 0 {
verstrs = append(verstrs, base)
}
for _, v := range versions {
if !v.isValid() {
continue
}
verstrs = append(verstrs, v.Name+"/"+v.Version)
}
return strings.Join(verstrs, " ")
}
// Decorator is used to change an instance of
// http.Request. It could be used to add more header fields,
// change body, etc.
type Decorator interface {
// ChangeRequest() changes the request accordingly.
// The changed request will be returned or err will be non-nil
// if an error occur.
ChangeRequest(req *http.Request) (newReq *http.Request, err error)
}
// UserAgentDecorator appends the product/version to the user agent field
// of a request.
type UserAgentDecorator struct {
Versions []UAVersionInfo
}
func (h *UserAgentDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
if req == nil {
return req, ErrNilRequest
}
userAgent := AppendVersions(req.UserAgent(), h.Versions...)
if len(userAgent) > 0 {
req.Header.Set("User-Agent", userAgent)
}
return req, nil
}
type MetaHeadersDecorator struct {
Headers map[string][]string
}
func (h *MetaHeadersDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
if h.Headers == nil {
return req, ErrNilRequest
}
for k, v := range h.Headers {
req.Header[k] = v
}
return req, nil
}
type AuthDecorator struct {
login string
password string
}
func NewAuthDecorator(login, password string) Decorator {
return &AuthDecorator{
login: login,
password: password,
}
}
func (self *AuthDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
if req == nil {
return req, ErrNilRequest
}
req.SetBasicAuth(self.login, self.password)
return req, nil
}
// RequestFactory creates an HTTP request
// and applies a list of decorators on the request.
type RequestFactory struct {
decorators []Decorator
}
func NewRequestFactory(d ...Decorator) *RequestFactory {
return &RequestFactory{
decorators: d,
}
}
func (f *RequestFactory) AddDecorator(d ...Decorator) {
f.decorators = append(f.decorators, d...)
}
func (f *RequestFactory) GetDecorators() []Decorator {
return f.decorators
}
// NewRequest() creates a new *http.Request,
// applies all decorators in the Factory on the request,
// then applies decorators provided by d on the request.
func (h *RequestFactory) NewRequest(method, urlStr string, body io.Reader, d ...Decorator) (*http.Request, error) {
req, err := http.NewRequest(method, urlStr, body)
if err != nil {
return nil, err
}
// By default, a nil factory should work.
if h == nil {
return req, nil
}
for _, dec := range h.decorators {
req, _ = dec.ChangeRequest(req)
}
for _, dec := range d {
req, _ = dec.ChangeRequest(req)
}
logrus.Debugf("%v -- HEADERS: %v", req.URL, req.Header)
return req, err
}

View file

@ -1,222 +0,0 @@
package requestdecorator
import (
"net/http"
"strings"
"testing"
)
func TestUAVersionInfo(t *testing.T) {
uavi := NewUAVersionInfo("foo", "bar")
if !uavi.isValid() {
t.Fatalf("UAVersionInfo should be valid")
}
uavi = NewUAVersionInfo("", "bar")
if uavi.isValid() {
t.Fatalf("Expected UAVersionInfo to be invalid")
}
uavi = NewUAVersionInfo("foo", "")
if uavi.isValid() {
t.Fatalf("Expected UAVersionInfo to be invalid")
}
}
func TestUserAgentDecorator(t *testing.T) {
httpVersion := make([]UAVersionInfo, 2)
httpVersion = append(httpVersion, NewUAVersionInfo("testname", "testversion"))
httpVersion = append(httpVersion, NewUAVersionInfo("name", "version"))
uad := &UserAgentDecorator{
Versions: httpVersion,
}
req, err := http.NewRequest("GET", "/something", strings.NewReader("test"))
if err != nil {
t.Fatal(err)
}
reqDecorated, err := uad.ChangeRequest(req)
if err != nil {
t.Fatal(err)
}
if reqDecorated.Header.Get("User-Agent") != "testname/testversion name/version" {
t.Fatalf("Request should have User-Agent 'testname/testversion name/version'")
}
}
func TestUserAgentDecoratorErr(t *testing.T) {
httpVersion := make([]UAVersionInfo, 0)
uad := &UserAgentDecorator{
Versions: httpVersion,
}
var req *http.Request
_, err := uad.ChangeRequest(req)
if err == nil {
t.Fatalf("Expected to get ErrNilRequest instead no error was returned")
}
}
func TestMetaHeadersDecorator(t *testing.T) {
var headers = map[string][]string{
"key1": {"value1"},
"key2": {"value2"},
}
mhd := &MetaHeadersDecorator{
Headers: headers,
}
req, err := http.NewRequest("GET", "/something", strings.NewReader("test"))
if err != nil {
t.Fatal(err)
}
reqDecorated, err := mhd.ChangeRequest(req)
if err != nil {
t.Fatal(err)
}
v, ok := reqDecorated.Header["key1"]
if !ok {
t.Fatalf("Expected to have header key1")
}
if v[0] != "value1" {
t.Fatalf("Expected value for key1 isn't value1")
}
v, ok = reqDecorated.Header["key2"]
if !ok {
t.Fatalf("Expected to have header key2")
}
if v[0] != "value2" {
t.Fatalf("Expected value for key2 isn't value2")
}
}
func TestMetaHeadersDecoratorErr(t *testing.T) {
mhd := &MetaHeadersDecorator{}
var req *http.Request
_, err := mhd.ChangeRequest(req)
if err == nil {
t.Fatalf("Expected to get ErrNilRequest instead no error was returned")
}
}
func TestAuthDecorator(t *testing.T) {
ad := NewAuthDecorator("test", "password")
req, err := http.NewRequest("GET", "/something", strings.NewReader("test"))
if err != nil {
t.Fatal(err)
}
reqDecorated, err := ad.ChangeRequest(req)
if err != nil {
t.Fatal(err)
}
username, password, ok := reqDecorated.BasicAuth()
if !ok {
t.Fatalf("Cannot retrieve basic auth info from request")
}
if username != "test" {
t.Fatalf("Expected username to be test, got %s", username)
}
if password != "password" {
t.Fatalf("Expected password to be password, got %s", password)
}
}
func TestAuthDecoratorErr(t *testing.T) {
ad := &AuthDecorator{}
var req *http.Request
_, err := ad.ChangeRequest(req)
if err == nil {
t.Fatalf("Expected to get ErrNilRequest instead no error was returned")
}
}
func TestRequestFactory(t *testing.T) {
ad := NewAuthDecorator("test", "password")
httpVersion := make([]UAVersionInfo, 2)
httpVersion = append(httpVersion, NewUAVersionInfo("testname", "testversion"))
httpVersion = append(httpVersion, NewUAVersionInfo("name", "version"))
uad := &UserAgentDecorator{
Versions: httpVersion,
}
requestFactory := NewRequestFactory(ad, uad)
if l := len(requestFactory.GetDecorators()); l != 2 {
t.Fatalf("Expected to have two decorators, got %d", l)
}
req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"))
if err != nil {
t.Fatal(err)
}
username, password, ok := req.BasicAuth()
if !ok {
t.Fatalf("Cannot retrieve basic auth info from request")
}
if username != "test" {
t.Fatalf("Expected username to be test, got %s", username)
}
if password != "password" {
t.Fatalf("Expected password to be password, got %s", password)
}
if req.Header.Get("User-Agent") != "testname/testversion name/version" {
t.Fatalf("Request should have User-Agent 'testname/testversion name/version'")
}
}
func TestRequestFactoryNewRequestWithDecorators(t *testing.T) {
ad := NewAuthDecorator("test", "password")
requestFactory := NewRequestFactory(ad)
if l := len(requestFactory.GetDecorators()); l != 1 {
t.Fatalf("Expected to have one decorators, got %d", l)
}
ad2 := NewAuthDecorator("test2", "password2")
req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"), ad2)
if err != nil {
t.Fatal(err)
}
username, password, ok := req.BasicAuth()
if !ok {
t.Fatalf("Cannot retrieve basic auth info from request")
}
if username != "test2" {
t.Fatalf("Expected username to be test, got %s", username)
}
if password != "password2" {
t.Fatalf("Expected password to be password, got %s", password)
}
}
func TestRequestFactoryAddDecorator(t *testing.T) {
requestFactory := NewRequestFactory()
if l := len(requestFactory.GetDecorators()); l != 0 {
t.Fatalf("Expected to have zero decorators, got %d", l)
}
ad := NewAuthDecorator("test", "password")
requestFactory.AddDecorator(ad)
if l := len(requestFactory.GetDecorators()); l != 1 {
t.Fatalf("Expected to have one decorators, got %d", l)
}
}
func TestRequestFactoryNil(t *testing.T) {
var requestFactory RequestFactory
_, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"))
if err != nil {
t.Fatalf("Expected not to get and error, got %s", err)
}
}

1
pkg/useragent/README.md Normal file
View file

@ -0,0 +1 @@
This package provides helper functions to pack version information into a single User-Agent header.

View file

@ -0,0 +1,60 @@
// Package useragent provides helper functions to pack
// version information into a single User-Agent header.
package useragent
import (
"errors"
"strings"
)
var (
ErrNilRequest = errors.New("request cannot be nil")
)
// VersionInfo is used to model UserAgent versions.
type VersionInfo struct {
Name string
Version string
}
func (vi *VersionInfo) isValid() bool {
const stopChars = " \t\r\n/"
name := vi.Name
vers := vi.Version
if len(name) == 0 || strings.ContainsAny(name, stopChars) {
return false
}
if len(vers) == 0 || strings.ContainsAny(vers, stopChars) {
return false
}
return true
}
// Convert versions to a string and append the string to the string base.
//
// Each VersionInfo will be converted to a string in the format of
// "product/version", where the "product" is get from the name field, while
// version is get from the version field. Several pieces of verson information
// will be concatinated and separated by space.
//
// Example:
// AppendVersions("base", VersionInfo{"foo", "1.0"}, VersionInfo{"bar", "2.0"})
// results in "base foo/1.0 bar/2.0".
func AppendVersions(base string, versions ...VersionInfo) string {
if len(versions) == 0 {
return base
}
verstrs := make([]string, 0, 1+len(versions))
if len(base) > 0 {
verstrs = append(verstrs, base)
}
for _, v := range versions {
if !v.isValid() {
continue
}
verstrs = append(verstrs, v.Name+"/"+v.Version)
}
return strings.Join(verstrs, " ")
}

View file

@ -0,0 +1,31 @@
package useragent
import "testing"
func TestVersionInfo(t *testing.T) {
vi := VersionInfo{"foo", "bar"}
if !vi.isValid() {
t.Fatalf("VersionInfo should be valid")
}
vi = VersionInfo{"", "bar"}
if vi.isValid() {
t.Fatalf("Expected VersionInfo to be invalid")
}
vi = VersionInfo{"foo", ""}
if vi.isValid() {
t.Fatalf("Expected VersionInfo to be invalid")
}
}
func TestAppendVersions(t *testing.T) {
vis := []VersionInfo{
{"foo", "1.0"},
{"bar", "0.1"},
{"pi", "3.1.4"},
}
v := AppendVersions("base", vis...)
expect := "base foo/1.0 bar/0.1 pi/3.1.4"
if v != expect {
t.Fatalf("expected %q, got %q", expect, v)
}
}

View file

@ -18,8 +18,8 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/requestdecorator"
"github.com/docker/docker/pkg/timeoutconn"
"github.com/docker/docker/pkg/useragent"
)
var (
@ -186,17 +186,17 @@ func cloneRequest(r *http.Request) *http.Request {
func (tr *DockerHeaders) RoundTrip(req *http.Request) (*http.Response, error) {
req = cloneRequest(req)
httpVersion := make([]requestdecorator.UAVersionInfo, 0, 4)
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("docker", dockerversion.VERSION))
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("go", runtime.Version()))
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("git-commit", dockerversion.GITCOMMIT))
httpVersion := make([]useragent.VersionInfo, 0, 4)
httpVersion = append(httpVersion, useragent.VersionInfo{"docker", dockerversion.VERSION})
httpVersion = append(httpVersion, useragent.VersionInfo{"go", runtime.Version()})
httpVersion = append(httpVersion, useragent.VersionInfo{"git-commit", dockerversion.GITCOMMIT})
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("kernel", kernelVersion.String()))
httpVersion = append(httpVersion, useragent.VersionInfo{"kernel", kernelVersion.String()})
}
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("os", runtime.GOOS))
httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("arch", runtime.GOARCH))
httpVersion = append(httpVersion, useragent.VersionInfo{"os", runtime.GOOS})
httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH})
userAgent := requestdecorator.AppendVersions(req.UserAgent(), httpVersion...)
userAgent := useragent.AppendVersions(req.UserAgent(), httpVersion...)
req.Header.Set("User-Agent", userAgent)