Skip to content
This repository has been archived by the owner on Dec 20, 2024. It is now read-only.

Commit

Permalink
add functional options to define opts for NewProtocolClient.
Browse files Browse the repository at this point in the history
Signed-off-by: allen.wq <[email protected]>
  • Loading branch information
wangforthinker committed Jun 3, 2020
1 parent e8a6514 commit 3b1c257
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 70 deletions.
153 changes: 84 additions & 69 deletions pkg/protocol/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,10 @@ const (
func init() {
protocol.RegisterProtocol(ProtocolHTTPName, &ClientBuilder{})
protocol.RegisterProtocol(ProtocolHTTPSName, &ClientBuilder{supportHTTPS: true})
}

const (
HTTPTransport = "http.transport"
TLSConfig = "tls.config"
)
protocol.RegisterMapInterfaceOptFunc(ProtocolHTTPName, WithMapInterface)
protocol.RegisterMapInterfaceOptFunc(ProtocolHTTPSName, WithMapInterface)
}

func newDefaultTransport() *http.Transport {
// copy from http.DefaultTransport
Expand All @@ -72,48 +70,6 @@ func newDefaultTransport() *http.Transport {
}
}

// ClientOpt is the argument of NewProtocolClient.
// ClientOpt supports some opt by key, such as "http.transport", "tls.config".
// if not set, default opt will be used.
type ClientOpt struct {
opt map[string]interface{}
}

func NewClientOpt() *ClientOpt {
return &ClientOpt{
opt: make(map[string]interface{}),
}
}

func (opt *ClientOpt) Set(key string, value interface{}) error {
switch key {
case HTTPTransport:
if _, ok := value.(*http.Transport); !ok {
return errortypes.ErrConvertFailed
}
break
case TLSConfig:
if _, ok := value.(*tls.Config); !ok {
return errortypes.ErrConvertFailed
}
break
default:
return fmt.Errorf("not support")
}

opt.opt[key] = value
return nil
}

func (opt *ClientOpt) Get(key string) interface{} {
v, ok := opt.opt[key]
if !ok {
return nil
}

return v
}

var _ protocol.Client = &Client{}

// Client is an implementation of protocol.Client for http protocol.
Expand Down Expand Up @@ -146,46 +102,105 @@ type ClientBuilder struct {
supportHTTPS bool
}

func (cb *ClientBuilder) NewProtocolClient(clientOpt interface{}) (protocol.Client, error) {
var (
transport = DefaultTransport
tlsConfig *tls.Config
)
// WithTransport allows to set transport for http protocol of protocol.Client.
func WithTransport(transport *http.Transport) func(protocol.Client) {
return func(client protocol.Client) {
cli := client.(*Client)
cli.transport = transport
}
}

if clientOpt != nil {
opt, ok := clientOpt.(*ClientOpt)
// WithTLS allows to set tls config for http protocol of protocol.Client.
func WithTLS(config *tls.Config) func(protocol.Client) error {
return func(client protocol.Client) error {
cli := client.(*Client)
if cli.transport == DefaultTransport {
cli.transport = newDefaultTransport()
}

tran, ok := cli.transport.(*http.Transport)
if !ok {
return nil, errortypes.ErrConvertFailed
return fmt.Errorf("transport could not be converted to http.Transport")
}

tran := opt.Get(HTTPTransport)
if tran != nil {
transport = tran.(*http.Transport)
tran.TLSClientConfig = config
return nil
}
}

// WithMapInterface allows to set some options by map interface.
// Supported:
// key: "tls.config", value: *tls.Config
// key: "http.transport" value: *http.Transport
func WithMapInterface(opt map[string]interface{}) func(client protocol.Client) error {
return func(client protocol.Client) error {
var (
transport *http.Transport
config *tls.Config
ok bool
)

cli := client.(*Client)
for k, v := range opt {
switch k {
case "tls.config":
config, ok = v.(*tls.Config)
if !ok {
return errortypes.ErrConvertFailed
}
case "http.transport":
transport, ok = v.(*http.Transport)
if !ok {
return errortypes.ErrConvertFailed
}
default:
}
}

config := opt.Get(TLSConfig)
if config != nil {
tlsConfig = config.(*tls.Config)
if transport == nil {
transport = DefaultTransport
}

// set tls config to transport
if config != nil {
if transport == DefaultTransport {
transport = newDefaultTransport()
}

transport.TLSClientConfig = tlsConfig
transport.TLSClientConfig = config
}

cli.transport = transport
cli.client = &http.Client{
Transport: transport,
}

return nil
}
}

func (cb *ClientBuilder) NewProtocolClient(opts ...func(client protocol.Client) error) (protocol.Client, error) {
cli := &Client{
transport: DefaultTransport,
}

for _, opt := range opts {
if err := opt(cli); err != nil {
return nil, err
}
}

cli.client = &http.Client{
Transport: cli.transport,
}

if cb.supportHTTPS {
if transport.TLSClientConfig == nil || transport.DialTLS == nil {
return nil, fmt.Errorf("in https mode, tls should be set")
tran, ok := cli.transport.(*http.Transport)
if ok {
if tran.TLSClientConfig == nil && tran.DialTLS == nil {
return nil, fmt.Errorf("in https mode, tls should be set")
}
}
}

return &Client{
client: &http.Client{Transport: transport},
transport: transport,
}, nil
return cli, nil
}
42 changes: 41 additions & 1 deletion pkg/protocol/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ type Client interface {
// ClientBuilder defines how to create an instance of Client.
type ClientBuilder interface {
// NewProtocolClient creates an instance of Client.
NewProtocolClient(clientOpt interface{}) (Client, error)
// Here have a suggestion that every implementation should have
// opts with WithMapInterface(map[string]interface),
// which may be easier for configuration with config file.
NewProtocolClient(opts ...func(client Client) error) (Client, error)
}

// ClientRegister defines how to register pair <protocol, ClientBuilder>.
Expand Down Expand Up @@ -123,3 +126,40 @@ func (cliRegister *defaultClientRegister) GetClientBuilder(protocol string) (Cli

return builder, nil
}

// In order to be easier for configuration with config file, we provide Register for functional options
// in which argument is map[string]interface. On the other handle, we can easy to get functional options with map interface.
// By the way, developer could create protocol client easily by protocol name and no need to care about instance of
// protocol client.
var (
newOptMapMutex sync.RWMutex
newOptMap = map[string]MapInterfaceOptFunc{}
)

type MapInterfaceOptFunc func(map[string]interface{}) func(Client) error

// RegisterMapInterfaceOptFunc registers MapInterfaceOptFunc by protocol name.
func RegisterMapInterfaceOptFunc(protocol string, withMapInterfaceOpt MapInterfaceOptFunc) {
newOptMapMutex.Lock()
defer newOptMapMutex.Unlock()

_, ok := newOptMap[protocol]
if ok {
panic(fmt.Sprintf("protocol %s has been register", protocol))
}

newOptMap[protocol] = withMapInterfaceOpt
}

// GetRegisteredMapInterfaceOptFunc get MapInterfaceOptFunc by protocol name.
func GetRegisteredMapInterfaceOptFunc(protocol string) (MapInterfaceOptFunc, error) {
newOptMapMutex.Lock()
defer newOptMapMutex.Unlock()

opt, ok := newOptMap[protocol]
if !ok {
return nil, ErrProtocolNotRegister
}

return opt, nil
}

0 comments on commit 3b1c257

Please sign in to comment.