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 }