1
0
mirror of https://github.com/go-micro/go-micro.git synced 2024-12-30 10:10:44 +02:00
go-micro/api/client/client.go
2024-06-04 21:40:43 +01:00

234 lines
5.3 KiB
Go

// Package client provides an api client
package client
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/gorilla/websocket"
"go-micro.dev/v5/logger"
)
const (
// local address for api.
localAddress = "http://localhost:8080"
)
// Options of the Client.
type Options struct {
// Token for authentication
Token string
// Address of the micro platform.
// By default it connects to live. Change it or use the local flag
// to connect to your local installation.
Address string
// Helper flag to help users connect to the default local address
Local bool
// set a timeout
Timeout time.Duration
}
// Request is the request of the generic `api-client` call.
type Request struct {
// eg. "go.micro.srv.greeter"
Service string `json:"service"`
// eg. "Say.Hello"
Endpoint string `json:"endpoint"`
// json and then base64 encoded body
Body string `json:"body"`
}
// Response is the response of the generic `api-client` call.
type Response struct {
// json and base64 encoded response body
Body string `json:"body"`
ID string `json:"id"`
Detail string `json:"detail"`
Status string `json:"status"`
// error fields. Error json example
// {"id":"go.micro.client","code":500,"detail":"malformed method name: \"\"","status":"Internal Server Error"}
Code int `json:"code"`
}
// Client enables generic calls to micro.
type Client struct {
options Options
}
// Stream is a websockets stream.
type Stream struct {
conn *websocket.Conn
service, endpoint string
}
// NewClient returns a generic micro client that connects to live by default.
func NewClient(options *Options) *Client {
ret := new(Client)
ret.options = Options{
Address: localAddress,
}
// no options provided
if options == nil {
return ret
}
if options.Token != "" {
ret.options.Token = options.Token
}
if options.Local {
ret.options.Address = localAddress
ret.options.Local = true
}
if options.Timeout > 0 {
ret.options.Timeout = options.Timeout
}
return ret
}
// SetToken sets the api auth token.
func (client *Client) SetToken(t string) {
client.options.Token = t
}
// SetTimeout sets the http client's timeout.
func (client *Client) SetTimeout(d time.Duration) {
client.options.Timeout = d
}
// Call enables you to access any endpoint of any service on Micro.
func (client *Client) Call(service, endpoint string, request, response interface{}) error {
// example curl: curl -XPOST -d '{"service": "go.micro.srv.greeter", "endpoint": "Say.Hello"}'
// -H 'Content-Type: application/json' http://localhost:8080/client {"body":"eyJtc2ciOiJIZWxsbyAifQ=="}
uri, err := url.Parse(client.options.Address)
if err != nil {
return err
}
// set the url to go through the v1 api
uri.Path = "/" + service + "/" + endpoint
b, err := marshalRequest(endpoint, request)
if err != nil {
return err
}
req, err := http.NewRequest("POST", uri.String(), bytes.NewBuffer(b))
if err != nil {
return err
}
// set the token if it exists
if len(client.options.Token) > 0 {
req.Header.Set("Authorization", "Bearer "+client.options.Token)
}
req.Header.Set("Content-Type", "application/json")
// if user didn't specify Timeout the default is 0 i.e no timeout
httpClient := &http.Client{
Timeout: client.options.Timeout,
}
resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer func() {
if err = resp.Body.Close(); err != nil {
logger.DefaultLogger.Log(logger.ErrorLevel, err)
}
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode <= 200 || resp.StatusCode > 300 {
return errors.New(string(body))
}
return unmarshalResponse(body, response)
}
// Stream enables the ability to stream via websockets.
func (client *Client) Stream(service, endpoint string, request interface{}) (*Stream, error) {
bytes, err := marshalRequest(endpoint, request)
if err != nil {
return nil, err
}
uri, err := url.Parse(client.options.Address)
if err != nil {
return nil, err
}
// set the url to go through the v1 api
uri.Path = "/" + service + "/" + endpoint
// replace http with websocket
uri.Scheme = strings.Replace(uri.Scheme, "http", "ws", 1)
// create the headers
header := make(http.Header)
// set the token if it exists
if len(client.options.Token) > 0 {
header.Set("Authorization", "Bearer "+client.options.Token)
}
header.Set("Content-Type", "application/json")
// dial the connection, connection not closed as conn is returned
conn, _, err := websocket.DefaultDialer.Dial(uri.String(), header)
if err != nil {
return nil, err
}
// send the first request
if err := conn.WriteMessage(websocket.TextMessage, bytes); err != nil {
return nil, err
}
return &Stream{conn, service, endpoint}, nil
}
// Recv will receive a message from a stream and unmarshal it.
func (s *Stream) Recv(v interface{}) error {
// read response
_, message, err := s.conn.ReadMessage()
if err != nil {
return err
}
return unmarshalResponse(message, v)
}
// Send will send a message into the stream.
func (s *Stream) Send(v interface{}) error {
b, err := marshalRequest(s.endpoint, v)
if err != nil {
return err
}
return s.conn.WriteMessage(websocket.TextMessage, b)
}
func marshalRequest(_ string, v interface{}) ([]byte, error) {
return json.Marshal(v)
}
func unmarshalResponse(body []byte, v interface{}) error {
return json.Unmarshal(body, &v)
}