github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/client/api/api.go (about)

     1  // Package api provides a micro api client
     2  package api
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/json"
     7  	"errors"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/url"
    11  	"path"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/gorilla/websocket"
    16  )
    17  
    18  const (
    19  	// Address for api
    20  	DefaultAddress = "http://localhost:8080"
    21  )
    22  
    23  // Options of the Client
    24  type Options struct {
    25  	// JWT token for authentication
    26  	Token string
    27  	// Address of the micro api
    28  	Address string
    29  	// set a request timeout
    30  	Timeout time.Duration
    31  }
    32  
    33  // Request is the request of the generic `api-client` call
    34  type Request struct {
    35  	// eg. "helloworld"
    36  	Service string `json:"service"`
    37  	// eg. "Call"
    38  	Endpoint string `json:"endpoint"`
    39  	// json and then base64 encoded body
    40  	Body string `json:"body"`
    41  }
    42  
    43  // Response is the response of the generic `api-client` call.
    44  type Response struct {
    45  	// json and base64 encoded response body
    46  	Body string `json:"body"`
    47  	// error fields. Error json example
    48  	// {"id":"go.micro.client","code":500,"detail":"malformed method name: \"\"","status":"Internal Server Error"}
    49  	Code   int    `json:"code"`
    50  	ID     string `json:"id"`
    51  	Detail string `json:"detail"`
    52  	Status string `json:"status"`
    53  }
    54  
    55  // Client enables generic calls to micro
    56  type Client struct {
    57  	options Options
    58  }
    59  
    60  type Stream struct {
    61  	conn              *websocket.Conn
    62  	service, endpoint string
    63  }
    64  
    65  // NewClient returns a generic micro client that connects to live by default
    66  func NewClient(options *Options) *Client {
    67  	ret := new(Client)
    68  	ret.options = Options{
    69  		Address: DefaultAddress,
    70  	}
    71  
    72  	// no options provided
    73  	if options == nil {
    74  		return ret
    75  	}
    76  
    77  	if options.Token != "" {
    78  		ret.options.Token = options.Token
    79  	}
    80  
    81  	if options.Timeout > 0 {
    82  		ret.options.Timeout = options.Timeout
    83  	}
    84  
    85  	return ret
    86  }
    87  
    88  // SetAddress sets the api address
    89  func (client *Client) SetAddress(a string) {
    90  	client.options.Address = a
    91  }
    92  
    93  // SetToken sets the api auth token
    94  func (client *Client) SetToken(t string) {
    95  	client.options.Token = t
    96  }
    97  
    98  // SetTimeout sets the http client's timeout
    99  func (client *Client) SetTimeout(d time.Duration) {
   100  	client.options.Timeout = d
   101  }
   102  
   103  // Handle is a http handler for serving requests to the API
   104  func (client *Client) Handle(service, endpoint string) http.Handler {
   105  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   106  		r.ParseForm()
   107  		b, err := ioutil.ReadAll(r.Body)
   108  		if err != nil {
   109  			http.Error(w, "Error reading body", 500)
   110  			return
   111  		}
   112  		var resp json.RawMessage
   113  		err = client.Call(service, endpoint, json.RawMessage(b), &resp)
   114  		if err != nil {
   115  			http.Error(w, "Error reading body", 500)
   116  			return
   117  		}
   118  		w.Write(resp)
   119  	})
   120  }
   121  
   122  // Call enables you to access any endpoint of any service on Micro
   123  func (client *Client) Call(service, endpoint string, request, response interface{}) error {
   124  	// example curl: curl -XPOST -d '{"service": "helloworld", "endpoint": "Call"}'
   125  	//  -H 'Content-Type: application/json' http://localhost:8080/helloworld/Call
   126  	uri, err := url.Parse(client.options.Address)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	// set the url to go through the v1 api
   132  	uri.Path = path.Join(uri.Path, service, endpoint)
   133  
   134  	b, err := marshalRequest(service, endpoint, request)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	req, err := http.NewRequest("POST", uri.String(), bytes.NewBuffer(b))
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	// set the token if it exists
   145  	if len(client.options.Token) > 0 {
   146  		req.Header.Set("Authorization", "Bearer "+client.options.Token)
   147  	}
   148  
   149  	req.Header.Set("Content-Type", "application/json")
   150  
   151  	// if user didn't specify Timeout the default is 0 i.e no timeout
   152  	httpClient := &http.Client{
   153  		Timeout: client.options.Timeout,
   154  	}
   155  
   156  	resp, err := httpClient.Do(req)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	defer resp.Body.Close()
   161  
   162  	body, err := ioutil.ReadAll(resp.Body)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	if !(resp.StatusCode >= 200 && resp.StatusCode < 300) {
   167  		return errors.New(string(body))
   168  	}
   169  	return unmarshalResponse(body, response)
   170  }
   171  
   172  // Stream enables the ability to stream via websockets
   173  func (client *Client) Stream(service, endpoint string, request interface{}) (*Stream, error) {
   174  	b, err := marshalRequest(service, endpoint, request)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	uri, err := url.Parse(client.options.Address)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	// set the url to go through the v1 api
   185  	uri.Path = path.Join(uri.Path, service, endpoint)
   186  
   187  	// replace http with websocket
   188  	uri.Scheme = strings.Replace(uri.Scheme, "http", "ws", 1)
   189  
   190  	// create the headers
   191  	header := make(http.Header)
   192  	// set the token if it exists
   193  	if len(client.options.Token) > 0 {
   194  		header.Set("Authorization", "Bearer "+client.options.Token)
   195  	}
   196  	header.Set("Content-Type", "application/json")
   197  
   198  	// dial the connection
   199  	conn, _, err := websocket.DefaultDialer.Dial(uri.String(), header)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	// send the first request
   205  	if err := conn.WriteMessage(websocket.TextMessage, b); err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	return &Stream{conn, service, endpoint}, nil
   210  }
   211  
   212  func (s *Stream) Recv(v interface{}) error {
   213  	// read response
   214  	_, message, err := s.conn.ReadMessage()
   215  	if err != nil {
   216  		return err
   217  	}
   218  	return unmarshalResponse(message, v)
   219  }
   220  
   221  func (s *Stream) Send(v interface{}) error {
   222  	b, err := marshalRequest(s.service, s.endpoint, v)
   223  	if err != nil {
   224  		return err
   225  	}
   226  	return s.conn.WriteMessage(websocket.TextMessage, b)
   227  }
   228  
   229  func marshalRequest(service, endpoint string, v interface{}) ([]byte, error) {
   230  	return json.Marshal(v)
   231  }
   232  
   233  func unmarshalResponse(body []byte, v interface{}) error {
   234  	return json.Unmarshal(body, &v)
   235  }