gotsrpc/client.go
2022-03-02 15:18:09 +01:00

140 lines
3.7 KiB
Go

package gotsrpc
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
"github.com/pkg/errors"
"github.com/ugorji/go/codec"
)
const (
HeaderServiceToService = "X-Foomo-S2S"
)
// ClientTransport to use for calls
// var ClientTransport = &http.Transport{}
var _ Client = &bufferedClient{}
type Client interface {
Call(ctx context.Context, url string, endpoint string, method string, args []interface{}, reply []interface{}) (err error)
SetClientEncoding(encoding ClientEncoding)
SetTransportHttpClient(client *http.Client)
SetDefaultHeaders(headers http.Header)
}
func NewClient() Client {
return &bufferedClient{client: defaultHttpFactory(), handle: getHandleForEncoding(EncodingMsgpack), headers: nil}
}
func NewClientWithHttpClient(client *http.Client) Client {
if client != nil {
return &bufferedClient{client: client, handle: getHandleForEncoding(EncodingMsgpack), headers: nil}
} else {
return &bufferedClient{client: defaultHttpFactory(), handle: getHandleForEncoding(EncodingMsgpack), headers: nil}
}
}
func newRequest(ctx context.Context, url string, contentType string, buffer *bytes.Buffer, headers http.Header) (r *http.Request, err error) {
if buffer == nil {
buffer = &bytes.Buffer{}
}
request, errRequest := http.NewRequestWithContext(ctx, "POST", url, buffer)
if errRequest != nil {
return nil, errors.Wrap(errRequest, "could not create a request")
}
if len(headers) > 0 {
request.Header = headers
}
request.Header.Set("Content-Type", contentType)
request.Header.Set("Accept", contentType)
request.Header.Set(HeaderServiceToService, "true")
return request, nil
}
type bufferedClient struct {
client *http.Client
handle *clientHandle
headers http.Header
}
func (c *bufferedClient) SetDefaultHeaders(headers http.Header) {
c.headers = headers
}
func (c *bufferedClient) SetClientEncoding(encoding ClientEncoding) {
c.handle = getHandleForEncoding(encoding)
}
func (c *bufferedClient) SetTransportHttpClient(client *http.Client) {
c.client = client
}
// Call calls a method on the remove service
func (c *bufferedClient) Call(ctx context.Context, url string, endpoint string, method string, args []interface{}, reply []interface{}) error {
// Marshall args
b := new(bytes.Buffer)
// If no arguments are set, remove
if len(args) > 0 {
if err := codec.NewEncoder(b, c.handle.handle).Encode(args); err != nil {
return NewClientError(errors.Wrap(err, "failed to encode arguments"))
}
}
// Create request
// Create post url
postURL := fmt.Sprintf("%s%s/%s", url, endpoint, method)
request, errRequest := newRequest(ctx, postURL, c.handle.contentType, b, c.headers.Clone())
if errRequest != nil {
return NewClientError(errors.Wrap(errRequest, "failed to create request"))
}
resp, errDo := c.client.Do(request)
if errDo != nil {
return NewClientError(errors.Wrap(errDo, "failed to send request"))
}
defer resp.Body.Close()
// Check status
if resp.StatusCode != http.StatusOK {
body := "request failed"
if value, err := ioutil.ReadAll(resp.Body); err == nil {
body = string(value)
}
return NewClientError(NewHTTPError(body, resp.StatusCode))
}
wrappedReply := make([]interface{}, len(reply))
for k, v := range reply {
if _, ok := v.(*error); ok {
var e *Error
wrappedReply[k] = e
} else {
wrappedReply[k] = v
}
}
responseHandle := getHandlerForContentType(resp.Header.Get("Content-Type")).handle
if err := codec.NewDecoder(resp.Body, responseHandle).Decode(wrappedReply); err != nil {
return NewClientError(errors.Wrap(err, "failed to decode response"))
}
// replace error
for k, v := range wrappedReply {
if x, ok := v.(*Error); ok && x != nil {
if y, ok := reply[k].(*error); ok {
*y = x
}
}
}
return nil
}