gitee.com/sasukebo/go-micro/v4@v4.7.1/api/client/client.go (about)

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