Update the HTTP client according to the comments

This commit is contained in:
Wenkai Yin 2017-12-15 06:22:37 +08:00
parent c4dc95f4f9
commit a736cb7b09
16 changed files with 176 additions and 158 deletions

View File

@ -24,7 +24,8 @@ import (
"strings"
"github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage"
httpclient "github.com/vmware/harbor/src/common/http/client"
common_http "github.com/vmware/harbor/src/common/http"
"github.com/vmware/harbor/src/common/http/modifier/auth"
"github.com/vmware/harbor/src/common/utils"
)
@ -43,22 +44,33 @@ type Client interface {
}
// NewClient return an instance of Adminserver client
func NewClient(baseURL string, c httpclient.Client) Client {
func NewClient(baseURL string, cfg *Config) Client {
baseURL = strings.TrimRight(baseURL, "/")
if !strings.Contains(baseURL, "://") {
baseURL = "http://" + baseURL
}
return &client{
client := &client{
baseURL: baseURL,
client: c,
}
if cfg != nil {
authorizer := auth.NewSecretAuthorizer(cfg.Secret)
client.client = common_http.NewClient(nil, authorizer)
}
return client
}
type client struct {
baseURL string
client httpclient.Client
client *common_http.Client
}
// Config contains configurations needed for client
type Config struct {
Secret string
}
// TODO refactor the codes with methods of common_http.Client
// do creates request and authorizes it if authorizer is not nil
func (c *client) do(method, relativePath string, body io.Reader) (*http.Response, error) {
url := c.baseURL + relativePath

View File

@ -16,7 +16,6 @@ package client
import (
"fmt"
"net/http"
"os"
"testing"
@ -35,7 +34,7 @@ func TestMain(m *testing.M) {
os.Exit(1)
}
c = NewClient(server.URL, &http.Client{})
c = NewClient(server.URL, &Config{})
os.Exit(m.Run())
}

101
src/common/http/client.go Normal file
View File

@ -0,0 +1,101 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package http
import (
"io"
"net/http"
"github.com/vmware/harbor/src/common/http/modifier"
)
// Client wraps net/http.Client with modifiers, modifiers the request before sending it
type Client struct {
modifiers []modifier.Modifier
client *http.Client
}
// NewClient creates an instance of Client. Use net/http.Client as the default value
// if c is nil.
func NewClient(c *http.Client, modifiers ...modifier.Modifier) *Client {
client := &Client{
client: c,
}
if client.client == nil {
client.client = &http.Client{}
}
if len(modifiers) > 0 {
client.modifiers = modifiers
}
return client
}
// Do ...
func (c *Client) Do(req *http.Request) (*http.Response, error) {
for _, modifier := range c.modifiers {
if err := modifier.Modify(req); err != nil {
return nil, err
}
}
return c.client.Do(req)
}
// Get ...
func (c *Client) Get(url string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
// Head ...
func (c *Client) Head(url string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodHead, url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
// Post ...
func (c *Client) Post(url, bodyType string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", bodyType)
return c.Do(req)
}
// Put ...
func (c *Client) Put(url, bodyType string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(http.MethodPut, url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", bodyType)
return c.Do(req)
}
// Delete ...
func (c *Client) Delete(url string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}

View File

@ -1,56 +0,0 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"net/http"
"github.com/vmware/harbor/src/common/http/client/auth"
)
// Client defines the method that a HTTP client should implement
type Client interface {
Do(*http.Request) (*http.Response, error)
}
// AuthorizedClient authorizes the requests before sending them
type AuthorizedClient struct {
client *http.Client
authorizer auth.Authorizer
}
// NewAuthorizedClient returns an instance of the AuthorizedClient
func NewAuthorizedClient(authorizer auth.Authorizer, client ...*http.Client) *AuthorizedClient {
c := &AuthorizedClient{
authorizer: authorizer,
}
if len(client) > 0 {
c.client = client[0]
}
if c.client == nil {
c.client = &http.Client{}
}
return c
}
// Do authorizes the request before sending it
func (a *AuthorizedClient) Do(req *http.Request) (*http.Response, error) {
if a.authorizer != nil {
if err := a.authorizer.Authorize(req); err != nil {
return nil, err
}
}
return a.client.Do(req)
}

View File

@ -17,57 +17,38 @@ package auth
import (
"errors"
"net/http"
"github.com/vmware/harbor/src/common/http/modifier"
)
const (
secretCookieName = "secret"
)
// Authorizer authorizes the requests
type Authorizer interface {
Authorize(*http.Request) error
}
// CookieAuthorizer authorizes the requests by adding cookie specified by name and value
type CookieAuthorizer struct {
name string
value string
}
// NewCookieAuthorizer returns an instance of CookieAuthorizer
func NewCookieAuthorizer(name, value string) *CookieAuthorizer {
return &CookieAuthorizer{
name: name,
value: value,
}
}
// Authorize the request with the cookie
func (c *CookieAuthorizer) Authorize(req *http.Request) error {
if req == nil {
return errors.New("the request is null")
}
req.AddCookie(&http.Cookie{
Name: c.name,
Value: c.value,
})
return nil
}
// Authorizer is a kind of Modifier used to authorize the requests
type Authorizer modifier.Modifier
// SecretAuthorizer authorizes the requests with the specified secret
type SecretAuthorizer struct {
authorizer *CookieAuthorizer
secret string
}
// NewSecretAuthorizer returns an instance of SecretAuthorizer
func NewSecretAuthorizer(secret string) *SecretAuthorizer {
return &SecretAuthorizer{
authorizer: NewCookieAuthorizer(secretCookieName, secret),
secret: secret,
}
}
// Authorize the request with the secret
func (s *SecretAuthorizer) Authorize(req *http.Request) error {
return s.authorizer.Authorize(req)
// Modify the request by adding secret authentication information
func (s *SecretAuthorizer) Modify(req *http.Request) error {
if req == nil {
return errors.New("the request is null")
}
req.AddCookie(&http.Cookie{
Name: secretCookieName,
Value: s.secret,
})
return nil
}

View File

@ -21,34 +21,17 @@ import (
"github.com/stretchr/testify/require"
)
func TestAuthorizeOfCookieAuthorizer(t *testing.T) {
name, value := "name", "value"
authorizer := NewCookieAuthorizer(name, value)
// nil request
require.NotNil(t, authorizer.Authorize(nil))
// valid request
req, err := http.NewRequest("", "", nil)
require.Nil(t, err)
require.Nil(t, authorizer.Authorize(req))
require.Equal(t, 1, len(req.Cookies()))
v, err := req.Cookie(name)
require.Nil(t, err)
assert.Equal(t, value, v.Value)
}
func TestAuthorizeOfSecretAuthorizer(t *testing.T) {
secret := "secret"
authorizer := NewSecretAuthorizer(secret)
// nil request
require.NotNil(t, authorizer.Authorize(nil))
require.NotNil(t, authorizer.Modify(nil))
// valid request
req, err := http.NewRequest("", "", nil)
require.Nil(t, err)
require.Nil(t, authorizer.Authorize(req))
require.Nil(t, authorizer.Modify(req))
require.Equal(t, 1, len(req.Cookies()))
v, err := req.Cookie(secretCookieName)
require.Nil(t, err)

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package registry
package modifier
import (
"net/http"

View File

@ -23,9 +23,9 @@ import (
"time"
"github.com/docker/distribution/registry/auth/token"
"github.com/vmware/harbor/src/common/http/modifier"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/utils/registry"
token_util "github.com/vmware/harbor/src/ui/service/token"
)
@ -254,7 +254,7 @@ func ping(client *http.Client, endpoint string) (string, string, error) {
// from token server and add it to the origin request
// If customizedTokenService is set, the token request will be sent to it instead of the server get from authorizer
func NewStandardTokenAuthorizer(client *http.Client, credential Credential,
customizedTokenService ...string) registry.Modifier {
customizedTokenService ...string) modifier.Modifier {
generator := &standardTokenGenerator{
credential: credential,
client: client,
@ -309,7 +309,7 @@ func (s *standardTokenGenerator) generate(scopes []*token.ResourceActions, endpo
// NewRawTokenAuthorizer returns a token authorizer which calls method to create
// token directly
func NewRawTokenAuthorizer(username, service string) registry.Modifier {
func NewRawTokenAuthorizer(username, service string) modifier.Modifier {
generator := &rawTokenGenerator{
service: service,
username: username,

View File

@ -17,17 +17,18 @@ package registry
import (
"net/http"
"github.com/vmware/harbor/src/common/http/modifier"
"github.com/vmware/harbor/src/common/utils/log"
)
// Transport holds information about base transport and modifiers
type Transport struct {
transport http.RoundTripper
modifiers []Modifier
modifiers []modifier.Modifier
}
// NewTransport ...
func NewTransport(transport http.RoundTripper, modifiers ...Modifier) *Transport {
func NewTransport(transport http.RoundTripper, modifiers ...modifier.Modifier) *Transport {
return &Transport{
transport: transport,
modifiers: modifiers,

View File

@ -21,7 +21,7 @@ import (
"net/http"
commonhttp "github.com/vmware/harbor/src/common/http"
"github.com/vmware/harbor/src/common/http/client"
"github.com/vmware/harbor/src/common/http/modifier/auth"
"github.com/vmware/harbor/src/jobservice/api"
)
@ -33,21 +33,22 @@ type Client interface {
// DefaultClient provides a default implement for the interface Client
type DefaultClient struct {
endpoint string
client client.Client
client *commonhttp.Client
}
// Config contains configuration items needed for DefaultClient
type Config struct {
Secret string
}
// NewDefaultClient returns an instance of DefaultClient
func NewDefaultClient(endpoint string, client ...client.Client) *DefaultClient {
func NewDefaultClient(endpoint string, cfg *Config) *DefaultClient {
c := &DefaultClient{
endpoint: endpoint,
}
if len(client) > 0 {
c.client = client[0]
}
if c.client == nil {
c.client = &http.Client{}
if cfg != nil {
c.client = commonhttp.NewClient(nil, auth.NewSecretAuthorizer(cfg.Secret))
}
return c
@ -62,12 +63,7 @@ func (d *DefaultClient) SubmitReplicationJob(replication *api.ReplicationReq) er
return err
}
req, err := http.NewRequest(http.MethodPost, url, buffer)
if err != nil {
return err
}
resp, err := d.client.Do(req)
resp, err := d.client.Post(url, "application/json", buffer)
if err != nil {
return err
}

View File

@ -49,7 +49,7 @@ func TestMain(m *testing.M) {
}
func TestSubmitReplicationJob(t *testing.T) {
client := NewDefaultClient(url)
client := NewDefaultClient(url, &Config{})
err := client.SubmitReplicationJob(&api.ReplicationReq{})
assert.Nil(t, err)
}

View File

@ -22,8 +22,6 @@ import (
"github.com/vmware/harbor/src/adminserver/client"
"github.com/vmware/harbor/src/common"
comcfg "github.com/vmware/harbor/src/common/config"
httpclient "github.com/vmware/harbor/src/common/http/client"
"github.com/vmware/harbor/src/common/http/client/auth"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
)
@ -51,8 +49,10 @@ func Init() error {
adminServerURL = "http://adminserver"
}
log.Infof("initializing client for adminserver %s ...", adminServerURL)
authorizer := auth.NewSecretAuthorizer(UISecret())
AdminserverClient = client.NewClient(adminServerURL, httpclient.NewAuthorizedClient(authorizer))
cfg := &client.Config{
Secret: UISecret(),
}
AdminserverClient = client.NewClient(adminServerURL, cfg)
if err := AdminserverClient.Ping(); err != nil {
return fmt.Errorf("failed to ping adminserver: %v", err)
}

View File

@ -18,11 +18,10 @@ import (
"fmt"
"strings"
"github.com/vmware/harbor/src/common/http/client"
"github.com/vmware/harbor/src/common/http/client/auth"
common_models "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/jobservice/api"
"github.com/vmware/harbor/src/jobservice/client"
"github.com/vmware/harbor/src/replication"
"github.com/vmware/harbor/src/replication/models"
"github.com/vmware/harbor/src/replication/policy"
@ -85,8 +84,10 @@ func NewDefaultController(cfg ControllerConfig) *DefaultController {
// TODO read from configuration
endpoint := "http://jobservice:8080"
client := client.NewAuthorizedClient(auth.NewSecretAuthorizer(config.UISecret()))
ctl.replicator = replicator.NewDefaultReplicator(endpoint, client)
ctl.replicator = replicator.NewDefaultReplicator(endpoint,
&client.Config{
Secret: config.UISecret(),
})
return ctl
}

View File

@ -15,9 +15,8 @@
package replicator
import (
"github.com/vmware/harbor/src/common/http/client"
"github.com/vmware/harbor/src/jobservice/api"
jobserviceclient "github.com/vmware/harbor/src/jobservice/client"
"github.com/vmware/harbor/src/jobservice/client"
)
// Replicator submits the replication work to the jobservice
@ -27,13 +26,13 @@ type Replicator interface {
// DefaultReplicator provides a default implement for Replicator
type DefaultReplicator struct {
client jobserviceclient.Client
client client.Client
}
// NewDefaultReplicator returns an instance of DefaultReplicator
func NewDefaultReplicator(endpoint string, client ...client.Client) *DefaultReplicator {
func NewDefaultReplicator(endpoint string, cfg *client.Config) *DefaultReplicator {
return &DefaultReplicator{
client: jobserviceclient.NewDefaultClient(endpoint, client...),
client: client.NewDefaultClient(endpoint, cfg),
}
}

View File

@ -19,6 +19,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/src/jobservice/api"
"github.com/vmware/harbor/src/jobservice/client"
)
type fakeJobserviceClient struct{}
@ -28,7 +29,7 @@ func (f *fakeJobserviceClient) SubmitReplicationJob(replication *api.Replication
}
func TestReplicate(t *testing.T) {
replicator := NewDefaultReplicator("http://jobservice")
replicator := NewDefaultReplicator("http://jobservice", &client.Config{})
replicator.client = &fakeJobserviceClient{}
assert.Nil(t, replicator.Replicate(&api.ReplicationReq{}))
}

View File

@ -25,8 +25,6 @@ import (
"github.com/vmware/harbor/src/adminserver/client"
"github.com/vmware/harbor/src/common"
comcfg "github.com/vmware/harbor/src/common/config"
httpclient "github.com/vmware/harbor/src/common/http/client"
"github.com/vmware/harbor/src/common/http/client/auth"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/secret"
"github.com/vmware/harbor/src/common/utils/log"
@ -74,8 +72,10 @@ func Init() error {
// InitByURL Init configurations with given url
func InitByURL(adminServerURL string) error {
log.Infof("initializing client for adminserver %s ...", adminServerURL)
authorizer := auth.NewSecretAuthorizer(UISecret())
AdminserverClient = client.NewClient(adminServerURL, httpclient.NewAuthorizedClient(authorizer))
cfg := &client.Config{
Secret: UISecret(),
}
AdminserverClient = client.NewClient(adminServerURL, cfg)
if err := AdminserverClient.Ping(); err != nil {
return fmt.Errorf("failed to ping adminserver: %v", err)
}