mirror of
https://github.com/kevin-DL/m3o-go.git
synced 2026-01-22 14:55:16 +00:00
First
This commit is contained in:
97
README.md
Normal file
97
README.md
Normal 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
207
client/client.go
Normal 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
22
client/client_test.go
Normal 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
5
client/go.mod
Normal 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
2
client/go.sum
Normal 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=
|
||||||
Reference in New Issue
Block a user