This commit is contained in:
Asim Aslam
2020-07-23 23:03:27 +01:00
commit 5a96bf5ef0
5 changed files with 333 additions and 0 deletions

97
README.md Normal file
View File

@@ -0,0 +1,97 @@
# Go Micro Client
By default the client connects to api.micro.mu/client
```go
package main
import (
"fmt"
"os"
"github.com/micro/clients/go/client"
)
type Request struct {
Name string `json:"name"`
}
type Response struct {
Msg string `json:"msg"`
}
var (
token, _ = os.Getenv("TOKEN")
)
func main() {
c := client.NewClient(nil)
// set your api token
c.SetToken(token)
req := &Request{
Name: "John",
}
var rsp Response
if err := c.Call("go.micro.srv.greeter", "Say.Hello", req, &rsp); err != nil {
fmt.Println(err)
return
}
fmt.Println(rsp)
}
```
If you want to access your local micro:
```go
c := client.NewClient(client.Options{Local: true})
```
You can also set the api address explicitly:
```go
c := client.NewClient(client.Options{Address: "https://api.yourdomain.com/client"})
```
## Streaming
The client supports streaming
```go
package main
import (
"fmt"
"github.com/micro/clients/go/client"
)
type Request struct {
Count string `json:"count"`
}
type Response struct {
Count string `json:"count"`
}
func main() {
c := client.NewClient(&client.Options{Local: true})
stream, err := c.Stream("go.micro.srv.stream", "Streamer.ServerStream", Request{Count: "10"})
if err != nil {
fmt.Println(err)
return
}
for {
var rsp Response
if err := stream.Recv(&rsp); err != nil {
fmt.Println(err)
return
}
fmt.Println("got", rsp.Count)
}
}
```

207
client/client.go Normal file
View File

@@ -0,0 +1,207 @@
package client
import (
"bytes"
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/gorilla/websocket"
)
const (
// local address for api
localAddress = "http://localhost:8080/"
// public address for api
liveAddress = "https://api.micro.mu/"
)
// 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
}
// 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"`
// error fields. Error json example
// {"id":"go.micro.client","code":500,"detail":"malformed method name: \"\"","status":"Internal Server Error"}
Code int `json:"code"`
ID string `json:"id"`
Detail string `json:"detail"`
Status string `json:"status"`
}
// Client enables generic calls to micro
type Client struct {
options Options
}
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)
if options != nil {
ret.options = *options
} else {
ret.options = Options{
Address: liveAddress,
}
}
if options != nil && options.Local {
ret.options.Address = localAddress
}
return ret
}
// SetToken sets the api auth token
func (client *Client) SetToken(t string) {
client.options.Token = t
}
// 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
}
// TODO: make optional
uri.Path = "/client"
b, err := marshalRequest(service, 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")
httpClient := &http.Client{}
resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return unmarshalResponse(body, response)
}
// Stream enables the ability to stream via websockets
func (client *Client) Stream(service, endpoint string, request interface{}) (*Stream, error) {
b, err := marshalRequest(service, endpoint, request)
if err != nil {
return nil, err
}
uri, err := url.Parse(client.options.Address)
if err != nil {
return nil, err
}
// TODO: make optional
uri.Path = "/client/stream"
// 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
conn, _, err := websocket.DefaultDialer.Dial(uri.String(), header)
if err != nil {
return nil, err
}
// send the first request
if err := conn.WriteMessage(websocket.TextMessage, b); err != nil {
return nil, err
}
return &Stream{conn, service, endpoint}, nil
}
func (s *Stream) Recv(v interface{}) error {
// read response
_, message, err := s.conn.ReadMessage()
if err != nil {
return err
}
return unmarshalResponse(message, v)
}
func (s *Stream) Send(v interface{}) error {
b, err := marshalRequest(s.service, s.endpoint, v)
if err != nil {
return err
}
return s.conn.WriteMessage(websocket.TextMessage, b)
}
func marshalRequest(service, endpoint string, v interface{}) ([]byte, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
return json.Marshal(&Request{
Service: service,
Endpoint: endpoint,
Body: base64.StdEncoding.EncodeToString(b),
})
}
func unmarshalResponse(body []byte, v interface{}) error {
rsp := new(Response)
if err := json.Unmarshal(body, rsp); err != nil {
return err
}
b, err := base64.StdEncoding.DecodeString(rsp.Body)
if err != nil {
return err
}
return json.Unmarshal(b, v)
}

22
client/client_test.go Normal file
View File

@@ -0,0 +1,22 @@
package client
import "testing"
type req struct {
Name string `json:"name"`
}
type rsp struct {
Msg string `json:"msg"`
}
func TestBasicCall(t *testing.T) {
response := rsp{}
if err := NewClient(nil).Call("go.micro.srv.greeter", "Say.Hello", req{Name: "John"}, &response); err != nil {
t.Fail()
}
if response.Msg != "Hello John" {
t.Logf("Message is not as expected: %v", response.Msg)
t.Fail()
}
}

5
client/go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/micro/clients/go/client
go 1.13
require github.com/gorilla/websocket v1.4.1

2
client/go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=