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 }