First
This commit is contained in:
commit
8e55cde513
3
README.me
Normal file
3
README.me
Normal file
@ -0,0 +1,3 @@
|
||||
# Go Micro - a microservices client/server library
|
||||
|
||||
This a minimalistic step into microservices using HTTP/RPC and protobuf.
|
13
client/buffer.go
Normal file
13
client/buffer.go
Normal file
@ -0,0 +1,13 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type buffer struct {
|
||||
io.ReadWriter
|
||||
}
|
||||
|
||||
func (b *buffer) Close() error {
|
||||
return nil
|
||||
}
|
33
client/client.go
Normal file
33
client/client.go
Normal file
@ -0,0 +1,33 @@
|
||||
package client
|
||||
|
||||
type Client interface {
|
||||
NewRequest(string, string, interface{}) Request
|
||||
NewProtoRequest(string, string, interface{}) Request
|
||||
NewJsonRequest(string, string, interface{}) Request
|
||||
Call(interface{}, interface{}) error
|
||||
CallRemote(string, string, interface{}, interface{}) error
|
||||
}
|
||||
|
||||
var (
|
||||
client = NewRpcClient()
|
||||
)
|
||||
|
||||
func Call(request Request, response interface{}) error {
|
||||
return client.Call(request, response)
|
||||
}
|
||||
|
||||
func CallRemote(address, path string, request Request, response interface{}) error {
|
||||
return client.CallRemote(address, path, request, response)
|
||||
}
|
||||
|
||||
func NewRequest(service, method string, request interface{}) Request {
|
||||
return client.NewRequest(service, method, request)
|
||||
}
|
||||
|
||||
func NewProtoRequest(service, method string, request interface{}) Request {
|
||||
return client.NewProtoRequest(service, method, request)
|
||||
}
|
||||
|
||||
func NewJsonRequest(service, method string, request interface{}) Request {
|
||||
return client.NewJsonRequest(service, method, request)
|
||||
}
|
8
client/headers.go
Normal file
8
client/headers.go
Normal file
@ -0,0 +1,8 @@
|
||||
package client
|
||||
|
||||
type Headers interface {
|
||||
Add(string, string)
|
||||
Del(string)
|
||||
Get(string) string
|
||||
Set(string, string)
|
||||
}
|
9
client/request.go
Normal file
9
client/request.go
Normal file
@ -0,0 +1,9 @@
|
||||
package client
|
||||
|
||||
type Request interface {
|
||||
Service() string
|
||||
Method() string
|
||||
ContentType() string
|
||||
Request() interface{}
|
||||
Headers() Headers
|
||||
}
|
157
client/rpc_client.go
Normal file
157
client/rpc_client.go
Normal file
@ -0,0 +1,157 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/asim/go-micro/errors"
|
||||
"github.com/asim/go-micro/registry"
|
||||
rpc "github.com/youtube/vitess/go/rpcplus"
|
||||
js "github.com/youtube/vitess/go/rpcplus/jsonrpc"
|
||||
pb "github.com/youtube/vitess/go/rpcplus/pbrpc"
|
||||
)
|
||||
|
||||
type headerRoundTripper struct {
|
||||
r http.RoundTripper
|
||||
}
|
||||
|
||||
type RpcClient struct{}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func (t *headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
r.Header.Set("X-Client-Version", "1.0")
|
||||
return t.r.RoundTrip(r)
|
||||
}
|
||||
|
||||
func (r *RpcClient) call(address, path string, request Request, response interface{}) error {
|
||||
pReq := &rpc.Request{
|
||||
ServiceMethod: request.Method(),
|
||||
}
|
||||
|
||||
reqB := bytes.NewBuffer(nil)
|
||||
defer reqB.Reset()
|
||||
buf := &buffer{
|
||||
reqB,
|
||||
}
|
||||
|
||||
var cc rpc.ClientCodec
|
||||
switch request.ContentType() {
|
||||
case "application/octet-stream":
|
||||
cc = pb.NewClientCodec(buf)
|
||||
case "application/json":
|
||||
cc = js.NewClientCodec(buf)
|
||||
default:
|
||||
return errors.InternalServerError("go.micro.client", fmt.Sprintf("Unsupported request type: %s", request.ContentType()))
|
||||
}
|
||||
|
||||
err := cc.WriteRequest(pReq, request.Request())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error writing request: %v", err))
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
client.Transport = &headerRoundTripper{http.DefaultTransport}
|
||||
|
||||
request.Headers().Set("Content-Type", request.ContentType())
|
||||
|
||||
hreq := &http.Request{
|
||||
Method: "POST",
|
||||
URL: &url.URL{
|
||||
Scheme: "http",
|
||||
Host: address,
|
||||
Path: path,
|
||||
},
|
||||
Header: request.Headers().(http.Header),
|
||||
Body: buf,
|
||||
ContentLength: int64(reqB.Len()),
|
||||
Host: address,
|
||||
}
|
||||
|
||||
rsp, err := client.Do(hreq)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
|
||||
}
|
||||
defer rsp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(rsp.Body)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error reading response: %v", err))
|
||||
}
|
||||
|
||||
rspB := bytes.NewBuffer(b)
|
||||
defer rspB.Reset()
|
||||
rBuf := &buffer{
|
||||
rspB,
|
||||
}
|
||||
|
||||
switch rsp.Header.Get("Content-Type") {
|
||||
case "application/octet-stream":
|
||||
cc = pb.NewClientCodec(rBuf)
|
||||
case "application/json":
|
||||
cc = js.NewClientCodec(rBuf)
|
||||
default:
|
||||
return errors.InternalServerError("go.micro.client", string(b))
|
||||
}
|
||||
|
||||
pRsp := &rpc.Response{}
|
||||
err = cc.ReadResponseHeader(pRsp)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error reading response headers: %v", err))
|
||||
}
|
||||
|
||||
if len(pRsp.Error) > 0 {
|
||||
return errors.Parse(pRsp.Error)
|
||||
}
|
||||
|
||||
err = cc.ReadResponseBody(response)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error reading response body: %v", err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RpcClient) CallRemote(address, path string, request Request, response interface{}) error {
|
||||
return r.call(address, path, request, response)
|
||||
}
|
||||
|
||||
// TODO: Call(..., opts *Options) error {
|
||||
func (r *RpcClient) Call(request Request, response interface{}) error {
|
||||
service, err := registry.GetService(request.Service())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
|
||||
if len(service.Nodes()) == 0 {
|
||||
return errors.NotFound("go.micro.client", "Service not found")
|
||||
}
|
||||
|
||||
n := rand.Int() % len(service.Nodes())
|
||||
node := service.Nodes()[n]
|
||||
address := fmt.Sprintf("%s:%d", node.Address(), node.Port())
|
||||
return r.call(address, "/_rpc", request, response)
|
||||
}
|
||||
|
||||
func (r *RpcClient) NewRequest(service, method string, request interface{}) *RpcRequest {
|
||||
return r.NewProtoRequest(service, method, request)
|
||||
}
|
||||
|
||||
func (r *RpcClient) NewProtoRequest(service, method string, request interface{}) *RpcRequest {
|
||||
return newRpcRequest(service, method, request, "application/octet-stream")
|
||||
}
|
||||
|
||||
func (r *RpcClient) NewJsonRequest(service, method string, request interface{}) *RpcRequest {
|
||||
return newRpcRequest(service, method, request, "application/json")
|
||||
}
|
||||
|
||||
func NewRpcClient() *RpcClient {
|
||||
return &RpcClient{}
|
||||
}
|
45
client/rpc_request.go
Normal file
45
client/rpc_request.go
Normal file
@ -0,0 +1,45 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type RpcRequest struct {
|
||||
service, method, contentType string
|
||||
request interface{}
|
||||
headers http.Header
|
||||
}
|
||||
|
||||
func newRpcRequest(service, method string, request interface{}, contentType string) *RpcRequest {
|
||||
return &RpcRequest{
|
||||
service: service,
|
||||
method: method,
|
||||
request: request,
|
||||
contentType: contentType,
|
||||
headers: make(http.Header),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RpcRequest) ContentType() string {
|
||||
return r.contentType
|
||||
}
|
||||
|
||||
func (r *RpcRequest) Headers() Headers {
|
||||
return r.headers
|
||||
}
|
||||
|
||||
func (r *RpcRequest) Service() string {
|
||||
return r.service
|
||||
}
|
||||
|
||||
func (r *RpcRequest) Method() string {
|
||||
return r.method
|
||||
}
|
||||
|
||||
func (r *RpcRequest) Request() interface{} {
|
||||
return r.request
|
||||
}
|
||||
|
||||
func NewRpcRequest(service, method string, request interface{}, contentType string) *RpcRequest {
|
||||
return newRpcRequest(service, method, request, contentType)
|
||||
}
|
81
errors/errors.go
Normal file
81
errors/errors.go
Normal file
@ -0,0 +1,81 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Id string `json:"id"`
|
||||
Code int32 `json:"code"`
|
||||
Detail string `json:"detail"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
b, _ := json.Marshal(e)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func New(id, detail string, code int32) error {
|
||||
return &Error{
|
||||
Id: id,
|
||||
Code: code,
|
||||
Detail: detail,
|
||||
Status: http.StatusText(int(code)),
|
||||
}
|
||||
}
|
||||
|
||||
func Parse(err string) *Error {
|
||||
var e *Error
|
||||
errr := json.Unmarshal([]byte(err), &e)
|
||||
if errr != nil {
|
||||
e.Detail = err
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func BadRequest(id, detail string) error {
|
||||
return &Error{
|
||||
Id: id,
|
||||
Code: 400,
|
||||
Detail: detail,
|
||||
Status: http.StatusText(400),
|
||||
}
|
||||
}
|
||||
|
||||
func Unauthorized(id, detail string) error {
|
||||
return &Error{
|
||||
Id: id,
|
||||
Code: 401,
|
||||
Detail: detail,
|
||||
Status: http.StatusText(401),
|
||||
}
|
||||
}
|
||||
|
||||
func Forbidden(id, detail string) error {
|
||||
return &Error{
|
||||
Id: id,
|
||||
Code: 403,
|
||||
Detail: detail,
|
||||
Status: http.StatusText(403),
|
||||
}
|
||||
}
|
||||
|
||||
func NotFound(id, detail string) error {
|
||||
return &Error{
|
||||
Id: id,
|
||||
Code: 404,
|
||||
Detail: detail,
|
||||
Status: http.StatusText(404),
|
||||
}
|
||||
}
|
||||
|
||||
func InternalServerError(id, detail string) error {
|
||||
return &Error{
|
||||
Id: id,
|
||||
Code: 500,
|
||||
Detail: detail,
|
||||
Status: http.StatusText(500),
|
||||
}
|
||||
}
|
30
examples/service_client.go
Normal file
30
examples/service_client.go
Normal file
@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.google.com/p/goprotobuf/proto"
|
||||
"github.com/asim/go-micro/client"
|
||||
example "github.com/asim/go-micro/template/proto/example"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create new request to service go.micro.service.go-template, method Example.Call
|
||||
req := client.NewRequest("go.micro.service.template", "Example.Call", &example.Request{
|
||||
Name: proto.String("John"),
|
||||
})
|
||||
|
||||
// Set arbitrary headers
|
||||
req.Headers().Set("X-User-Id", "john")
|
||||
req.Headers().Set("X-From-Id", "script")
|
||||
|
||||
rsp := &example.Response{}
|
||||
|
||||
// Call service
|
||||
if err := client.Call(req, rsp); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(rsp.GetMsg())
|
||||
}
|
20
registry/consul_node.go
Normal file
20
registry/consul_node.go
Normal file
@ -0,0 +1,20 @@
|
||||
package registry
|
||||
|
||||
type ConsulNode struct {
|
||||
Node string
|
||||
NodeId string
|
||||
NodeAddress string
|
||||
NodePort int
|
||||
}
|
||||
|
||||
func (c *ConsulNode) Id() string {
|
||||
return c.NodeId
|
||||
}
|
||||
|
||||
func (c *ConsulNode) Address() string {
|
||||
return c.NodeAddress
|
||||
}
|
||||
|
||||
func (c *ConsulNode) Port() int {
|
||||
return c.NodePort
|
||||
}
|
108
registry/consul_registry.go
Normal file
108
registry/consul_registry.go
Normal file
@ -0,0 +1,108 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
consul "github.com/armon/consul-api"
|
||||
)
|
||||
|
||||
type ConsulRegistry struct {
|
||||
Client *consul.Client
|
||||
}
|
||||
|
||||
var (
|
||||
ConsulCheckTTL = "30s"
|
||||
)
|
||||
|
||||
func (c *ConsulRegistry) Deregister(s Service) error {
|
||||
if len(s.Nodes()) == 0 {
|
||||
return errors.New("Require at least one node")
|
||||
}
|
||||
|
||||
node := s.Nodes()[0]
|
||||
|
||||
_, err := c.Client.Catalog().Deregister(&consul.CatalogDeregistration{
|
||||
Node: node.Id(),
|
||||
Address: node.Address(),
|
||||
ServiceID: node.Id(),
|
||||
}, nil)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ConsulRegistry) Register(s Service) error {
|
||||
if len(s.Nodes()) == 0 {
|
||||
return errors.New("Require at least one node")
|
||||
}
|
||||
|
||||
node := s.Nodes()[0]
|
||||
|
||||
_, err := c.Client.Catalog().Register(&consul.CatalogRegistration{
|
||||
Node: node.Id(),
|
||||
Address: node.Address(),
|
||||
Service: &consul.AgentService{
|
||||
ID: node.Id(),
|
||||
Service: s.Name(),
|
||||
Port: node.Port(),
|
||||
},
|
||||
}, nil)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ConsulRegistry) GetService(name string) (Service, error) {
|
||||
rsp, _, err := c.Client.Catalog().Service(name, "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs := &ConsulService{}
|
||||
|
||||
for _, s := range rsp {
|
||||
if s.ServiceName != name {
|
||||
continue
|
||||
}
|
||||
|
||||
cs.ServiceName = s.ServiceName
|
||||
cs.ServiceNodes = append(cs.ServiceNodes, &ConsulNode{
|
||||
Node: s.Node,
|
||||
NodeId: s.ServiceID,
|
||||
NodeAddress: s.Address,
|
||||
NodePort: s.ServicePort,
|
||||
})
|
||||
}
|
||||
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func (c *ConsulRegistry) NewService(name string, nodes ...Node) Service {
|
||||
var snodes []*ConsulNode
|
||||
|
||||
for _, node := range nodes {
|
||||
if n, ok := node.(*ConsulNode); ok {
|
||||
snodes = append(snodes, n)
|
||||
}
|
||||
}
|
||||
|
||||
return &ConsulService{
|
||||
ServiceName: name,
|
||||
ServiceNodes: snodes,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ConsulRegistry) NewNode(id, address string, port int) Node {
|
||||
return &ConsulNode{
|
||||
Node: id,
|
||||
NodeId: id,
|
||||
NodeAddress: address,
|
||||
NodePort: port,
|
||||
}
|
||||
}
|
||||
|
||||
func NewConsulRegistry() Registry {
|
||||
client, _ := consul.NewClient(&consul.Config{})
|
||||
|
||||
return &ConsulRegistry{
|
||||
Client: client,
|
||||
}
|
||||
}
|
20
registry/consul_service.go
Normal file
20
registry/consul_service.go
Normal file
@ -0,0 +1,20 @@
|
||||
package registry
|
||||
|
||||
type ConsulService struct {
|
||||
ServiceName string
|
||||
ServiceNodes []*ConsulNode
|
||||
}
|
||||
|
||||
func (c *ConsulService) Name() string {
|
||||
return c.ServiceName
|
||||
}
|
||||
|
||||
func (c *ConsulService) Nodes() []Node {
|
||||
var nodes []Node
|
||||
|
||||
for _, node := range c.ServiceNodes {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
19
registry/kubernetes_node.go
Normal file
19
registry/kubernetes_node.go
Normal file
@ -0,0 +1,19 @@
|
||||
package registry
|
||||
|
||||
type KubernetesNode struct {
|
||||
NodeId string
|
||||
NodeAddress string
|
||||
NodePort int
|
||||
}
|
||||
|
||||
func (c *KubernetesNode) Id() string {
|
||||
return c.NodeId
|
||||
}
|
||||
|
||||
func (c *KubernetesNode) Address() string {
|
||||
return c.NodeAddress
|
||||
}
|
||||
|
||||
func (c *KubernetesNode) Port() int {
|
||||
return c.NodePort
|
||||
}
|
77
registry/kubernetes_registry.go
Normal file
77
registry/kubernetes_registry.go
Normal file
@ -0,0 +1,77 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
k8s "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
)
|
||||
|
||||
type KubernetesRegistry struct {
|
||||
Client *k8s.Client
|
||||
Namespace string
|
||||
}
|
||||
|
||||
func (c *KubernetesRegistry) Deregister(s Service) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *KubernetesRegistry) Register(s Service) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *KubernetesRegistry) GetService(name string) (Service, error) {
|
||||
services, err := c.Client.Services(c.Namespace).List(labels.OneTermEqualSelector("name", name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(services.Items) == 0 {
|
||||
return nil, fmt.Errorf("Service not found")
|
||||
}
|
||||
|
||||
ks := &KubernetesService{ServiceName: name}
|
||||
for _, item := range services.Items {
|
||||
ks.ServiceNodes = append(ks.ServiceNodes, &KubernetesNode{
|
||||
NodeAddress: item.Spec.PortalIP,
|
||||
NodePort: item.Spec.Port,
|
||||
})
|
||||
}
|
||||
|
||||
return ks, nil
|
||||
}
|
||||
|
||||
func (c *KubernetesRegistry) NewService(name string, nodes ...Node) Service {
|
||||
var snodes []*KubernetesNode
|
||||
|
||||
for _, node := range nodes {
|
||||
if n, ok := node.(*KubernetesNode); ok {
|
||||
snodes = append(snodes, n)
|
||||
}
|
||||
}
|
||||
|
||||
return &KubernetesService{
|
||||
ServiceName: name,
|
||||
ServiceNodes: snodes,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *KubernetesRegistry) NewNode(id, address string, port int) Node {
|
||||
return &KubernetesNode{
|
||||
NodeId: id,
|
||||
NodeAddress: address,
|
||||
NodePort: port,
|
||||
}
|
||||
}
|
||||
|
||||
func NewKubernetesRegistry() Registry {
|
||||
client, _ := k8s.New(&k8s.Config{
|
||||
Host: "http://" + os.Getenv("KUBERNETES_RO_SERVICE_HOST") + ":" + os.Getenv("KUBERNETES_RO_SERVICE_PORT"),
|
||||
})
|
||||
|
||||
return &KubernetesRegistry{
|
||||
Client: client,
|
||||
Namespace: "default",
|
||||
}
|
||||
}
|
20
registry/kubernetes_service.go
Normal file
20
registry/kubernetes_service.go
Normal file
@ -0,0 +1,20 @@
|
||||
package registry
|
||||
|
||||
type KubernetesService struct {
|
||||
ServiceName string
|
||||
ServiceNodes []*KubernetesNode
|
||||
}
|
||||
|
||||
func (c *KubernetesService) Name() string {
|
||||
return c.ServiceName
|
||||
}
|
||||
|
||||
func (c *KubernetesService) Nodes() []Node {
|
||||
var nodes []Node
|
||||
|
||||
for _, node := range c.ServiceNodes {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
11
registry/node.go
Normal file
11
registry/node.go
Normal file
@ -0,0 +1,11 @@
|
||||
package registry
|
||||
|
||||
type Node interface {
|
||||
Id() string
|
||||
Address() string
|
||||
Port() int
|
||||
}
|
||||
|
||||
func NewNode(id, address string, port int) Node {
|
||||
return DefaultRegistry.NewNode(id, address, port)
|
||||
}
|
25
registry/registry.go
Normal file
25
registry/registry.go
Normal file
@ -0,0 +1,25 @@
|
||||
package registry
|
||||
|
||||
type Registry interface {
|
||||
Register(Service) error
|
||||
Deregister(Service) error
|
||||
GetService(string) (Service, error)
|
||||
NewService(string, ...Node) Service
|
||||
NewNode(string, string, int) Node
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultRegistry = NewConsulRegistry()
|
||||
)
|
||||
|
||||
func Register(s Service) error {
|
||||
return DefaultRegistry.Register(s)
|
||||
}
|
||||
|
||||
func Deregister(s Service) error {
|
||||
return DefaultRegistry.Deregister(s)
|
||||
}
|
||||
|
||||
func GetService(name string) (Service, error) {
|
||||
return DefaultRegistry.GetService(name)
|
||||
}
|
10
registry/service.go
Normal file
10
registry/service.go
Normal file
@ -0,0 +1,10 @@
|
||||
package registry
|
||||
|
||||
type Service interface {
|
||||
Name() string
|
||||
Nodes() []Node
|
||||
}
|
||||
|
||||
func NewService(name string, nodes ...Node) Service {
|
||||
return DefaultRegistry.NewService(name, nodes...)
|
||||
}
|
14
server/buffer.go
Normal file
14
server/buffer.go
Normal file
@ -0,0 +1,14 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type buffer struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (b *buffer) Close() error {
|
||||
return nil
|
||||
}
|
35
server/context.go
Normal file
35
server/context.go
Normal file
@ -0,0 +1,35 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"code.google.com/p/go.net/context"
|
||||
)
|
||||
|
||||
type ctx struct{}
|
||||
|
||||
func (ctx *ctx) Deadline() (deadline time.Time, ok bool) {
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
func (ctx *ctx) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *ctx) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *ctx) Value(key interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newContext(parent context.Context, s *serverContext) context.Context {
|
||||
return context.WithValue(parent, "serverContext", s)
|
||||
}
|
||||
|
||||
// return server.Context
|
||||
func NewContext(ctx context.Context) (Context, bool) {
|
||||
c, ok := ctx.Value("serverContext").(*serverContext)
|
||||
return c, ok
|
||||
}
|
8
server/headers.go
Normal file
8
server/headers.go
Normal file
@ -0,0 +1,8 @@
|
||||
package server
|
||||
|
||||
type Headers interface {
|
||||
Add(string, string)
|
||||
Del(string)
|
||||
Get(string) string
|
||||
Set(string, string)
|
||||
}
|
21
server/health_checker.go
Normal file
21
server/health_checker.go
Normal file
@ -0,0 +1,21 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func registerHealthChecker(mux *http.ServeMux) {
|
||||
req := &http.Request{
|
||||
Method: "GET",
|
||||
URL: &url.URL{
|
||||
Path: HealthPath,
|
||||
},
|
||||
}
|
||||
if _, path := mux.Handler(req); path != HealthPath {
|
||||
mux.HandleFunc(HealthPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, "ok")
|
||||
})
|
||||
}
|
||||
}
|
6
server/receiver.go
Normal file
6
server/receiver.go
Normal file
@ -0,0 +1,6 @@
|
||||
package server
|
||||
|
||||
type Receiver interface {
|
||||
Name() string
|
||||
Handler() interface{}
|
||||
}
|
6
server/request.go
Normal file
6
server/request.go
Normal file
@ -0,0 +1,6 @@
|
||||
package server
|
||||
|
||||
type Request interface {
|
||||
Headers() Headers
|
||||
Session(string) string
|
||||
}
|
29
server/rpc_receiver.go
Normal file
29
server/rpc_receiver.go
Normal file
@ -0,0 +1,29 @@
|
||||
package server
|
||||
|
||||
type RpcReceiver struct {
|
||||
name string
|
||||
handler interface{}
|
||||
}
|
||||
|
||||
func newRpcReceiver(name string, handler interface{}) *RpcReceiver {
|
||||
return &RpcReceiver{
|
||||
name: name,
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RpcReceiver) Name() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (r *RpcReceiver) Handler() interface{} {
|
||||
return r.handler
|
||||
}
|
||||
|
||||
func NewRpcReceiver(handler interface{}) *RpcReceiver {
|
||||
return newRpcReceiver("", handler)
|
||||
}
|
||||
|
||||
func NewNamedRpcReceiver(name string, handler interface{}) *RpcReceiver {
|
||||
return newRpcReceiver(name, handler)
|
||||
}
|
215
server/rpc_server.go
Normal file
215
server/rpc_server.go
Normal file
@ -0,0 +1,215 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/asim/go-micro/errors"
|
||||
log "github.com/cihub/seelog"
|
||||
rpc "github.com/youtube/vitess/go/rpcplus"
|
||||
js "github.com/youtube/vitess/go/rpcplus/jsonrpc"
|
||||
pb "github.com/youtube/vitess/go/rpcplus/pbrpc"
|
||||
)
|
||||
|
||||
type RpcServer struct {
|
||||
mtx sync.RWMutex
|
||||
rpc *rpc.Server
|
||||
address string
|
||||
exit chan chan error
|
||||
}
|
||||
|
||||
var (
|
||||
HealthPath = "/_status/health"
|
||||
RpcPath = "/_rpc"
|
||||
)
|
||||
|
||||
func executeRequestSafely(c *serverContext, r *http.Request) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
log.Criticalf("Panicked on request: %v", r)
|
||||
log.Criticalf("%v: %v", x, string(debug.Stack()))
|
||||
err := errors.InternalServerError("go.micro.server", "Unexpected error")
|
||||
c.WriteHeader(500)
|
||||
c.Write([]byte(err.Error()))
|
||||
}
|
||||
}()
|
||||
|
||||
http.DefaultServeMux.ServeHTTP(c, r)
|
||||
}
|
||||
|
||||
func (s *RpcServer) handler(w http.ResponseWriter, r *http.Request) {
|
||||
c := &serverContext{
|
||||
req: &serverRequest{r},
|
||||
outHeader: w.Header(),
|
||||
}
|
||||
|
||||
ctxs.Lock()
|
||||
ctxs.m[r] = c
|
||||
ctxs.Unlock()
|
||||
defer func() {
|
||||
ctxs.Lock()
|
||||
delete(ctxs.m, r)
|
||||
ctxs.Unlock()
|
||||
}()
|
||||
|
||||
// Patch up RemoteAddr so it looks reasonable.
|
||||
if addr := r.Header.Get("X-Forwarded-For"); len(addr) > 0 {
|
||||
r.RemoteAddr = addr
|
||||
} else {
|
||||
// Should not normally reach here, but pick a sensible default anyway.
|
||||
r.RemoteAddr = "127.0.0.1"
|
||||
}
|
||||
// The address in the headers will most likely be of these forms:
|
||||
// 123.123.123.123
|
||||
// 2001:db8::1
|
||||
// net/http.Request.RemoteAddr is specified to be in "IP:port" form.
|
||||
if _, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
|
||||
// Assume the remote address is only a host; add a default port.
|
||||
r.RemoteAddr = net.JoinHostPort(r.RemoteAddr, "80")
|
||||
}
|
||||
|
||||
executeRequestSafely(c, r)
|
||||
c.outHeader = nil // make sure header changes aren't respected any more
|
||||
|
||||
// Avoid nil Write call if c.Write is never called.
|
||||
if c.outCode != 0 {
|
||||
w.WriteHeader(c.outCode)
|
||||
}
|
||||
if c.outBody != nil {
|
||||
w.Write(c.outBody)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RpcServer) Address() string {
|
||||
s.mtx.RLock()
|
||||
defer s.mtx.RUnlock()
|
||||
return s.address
|
||||
}
|
||||
|
||||
func (s *RpcServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
serveCtx := getServerContext(req)
|
||||
|
||||
// TODO: get user scope from context
|
||||
// check access
|
||||
|
||||
if req.Method != "POST" {
|
||||
err := errors.BadRequest("go.micro.server", "Method not allowed")
|
||||
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
defer req.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
errr := errors.InternalServerError("go.micro.server", fmt.Sprintf("Error reading request body: %v", err))
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(errr.Error()))
|
||||
log.Errorf("Erroring reading request body: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
rbq := bytes.NewBuffer(b)
|
||||
rsp := bytes.NewBuffer(nil)
|
||||
defer rsp.Reset()
|
||||
defer rbq.Reset()
|
||||
|
||||
buf := &buffer{
|
||||
rbq,
|
||||
rsp,
|
||||
}
|
||||
|
||||
var cc rpc.ServerCodec
|
||||
switch req.Header.Get("Content-Type") {
|
||||
case "application/octet-stream":
|
||||
cc = pb.NewServerCodec(buf)
|
||||
case "application/json":
|
||||
cc = js.NewServerCodec(buf)
|
||||
default:
|
||||
err = errors.InternalServerError("go.micro.server", fmt.Sprintf("Unsupported content-type: %v", req.Header.Get("Content-Type")))
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := newContext(&ctx{}, serveCtx)
|
||||
err = s.rpc.ServeRequestWithContext(ctx, cc)
|
||||
if err != nil {
|
||||
// This should not be possible.
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
log.Errorf("Erroring serving request: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", req.Header.Get("Content-Type"))
|
||||
w.Header().Set("Content-Length", strconv.Itoa(rsp.Len()))
|
||||
w.Write(rsp.Bytes())
|
||||
}
|
||||
|
||||
func (s *RpcServer) Init() error {
|
||||
log.Debugf("Rpc handler %s", RpcPath)
|
||||
http.Handle(RpcPath, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RpcServer) NewReceiver(handler interface{}) Receiver {
|
||||
return newRpcReceiver("", handler)
|
||||
}
|
||||
|
||||
func (s *RpcServer) NewNamedReceiver(name string, handler interface{}) Receiver {
|
||||
return newRpcReceiver(name, handler)
|
||||
}
|
||||
|
||||
func (s *RpcServer) Register(r Receiver) error {
|
||||
if len(r.Name()) > 0 {
|
||||
s.rpc.RegisterName(r.Name(), r.Handler())
|
||||
return nil
|
||||
}
|
||||
|
||||
s.rpc.Register(r.Handler())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RpcServer) Start() error {
|
||||
registerHealthChecker(http.DefaultServeMux)
|
||||
|
||||
l, err := net.Listen("tcp", s.address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Listening on %s", l.Addr().String())
|
||||
|
||||
s.mtx.Lock()
|
||||
s.address = l.Addr().String()
|
||||
s.mtx.Unlock()
|
||||
|
||||
go http.Serve(l, http.HandlerFunc(s.handler))
|
||||
|
||||
go func() {
|
||||
ch := <-s.exit
|
||||
ch <- l.Close()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RpcServer) Stop() error {
|
||||
ch := make(chan error)
|
||||
s.exit <- ch
|
||||
return <-ch
|
||||
}
|
||||
|
||||
func NewRpcServer(address string) *RpcServer {
|
||||
return &RpcServer{
|
||||
rpc: rpc.NewServer(),
|
||||
address: address,
|
||||
exit: make(chan chan error),
|
||||
}
|
||||
}
|
112
server/server.go
Normal file
112
server/server.go
Normal file
@ -0,0 +1,112 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"code.google.com/p/go-uuid/uuid"
|
||||
"github.com/asim/go-micro/registry"
|
||||
"github.com/asim/go-micro/store"
|
||||
log "github.com/cihub/seelog"
|
||||
)
|
||||
|
||||
type Server interface {
|
||||
Address() string
|
||||
Init() error
|
||||
NewReceiver(interface{}) Receiver
|
||||
NewNamedReceiver(string, interface{}) Receiver
|
||||
Register(Receiver) error
|
||||
Start() error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
var (
|
||||
Name string
|
||||
Id string
|
||||
DefaultServer Server
|
||||
|
||||
flagRegistry string
|
||||
flagBindAddress string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&flagRegistry, "registry", "consul", "Registry for discovery. kubernetes, consul, etc")
|
||||
flag.StringVar(&flagBindAddress, "bind_address", ":0", "Bind address for the server. 127.0.0.1:8080")
|
||||
}
|
||||
|
||||
func Init() error {
|
||||
flag.Parse()
|
||||
|
||||
switch flagRegistry {
|
||||
case "kubernetes":
|
||||
registry.DefaultRegistry = registry.NewKubernetesRegistry()
|
||||
store.DefaultStore = store.NewMemcacheStore()
|
||||
}
|
||||
|
||||
if len(Name) == 0 {
|
||||
Name = "go-server"
|
||||
}
|
||||
|
||||
if len(Id) == 0 {
|
||||
Id = Name + "-" + uuid.NewUUID().String()
|
||||
}
|
||||
|
||||
if DefaultServer == nil {
|
||||
DefaultServer = NewRpcServer(flagBindAddress)
|
||||
}
|
||||
|
||||
return DefaultServer.Init()
|
||||
}
|
||||
|
||||
func NewReceiver(handler interface{}) Receiver {
|
||||
return DefaultServer.NewReceiver(handler)
|
||||
}
|
||||
|
||||
func NewNamedReceiver(path string, handler interface{}) Receiver {
|
||||
return DefaultServer.NewNamedReceiver(path, handler)
|
||||
}
|
||||
|
||||
func Register(r Receiver) error {
|
||||
return DefaultServer.Register(r)
|
||||
}
|
||||
|
||||
func Run() error {
|
||||
if err := Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse address for host, port
|
||||
parts := strings.Split(DefaultServer.Address(), ":")
|
||||
host := strings.Join(parts[:len(parts)-1], ":")
|
||||
port, _ := strconv.Atoi(parts[len(parts)-1])
|
||||
|
||||
// register service
|
||||
node := registry.NewNode(Id, host, port)
|
||||
service := registry.NewService(Name, node)
|
||||
|
||||
log.Debugf("Registering %s", node.Id())
|
||||
registry.Register(service)
|
||||
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
|
||||
log.Debugf("Received signal %s", <-ch)
|
||||
|
||||
log.Debugf("Deregistering %s", node.Id())
|
||||
registry.Deregister(service)
|
||||
|
||||
return Stop()
|
||||
}
|
||||
|
||||
func Start() error {
|
||||
log.Debugf("Starting server %s id %s", Name, Id)
|
||||
return DefaultServer.Start()
|
||||
}
|
||||
|
||||
func Stop() error {
|
||||
log.Debugf("Stopping server")
|
||||
return DefaultServer.Stop()
|
||||
}
|
120
server/server_context.go
Normal file
120
server/server_context.go
Normal file
@ -0,0 +1,120 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/asim/go-micro/client"
|
||||
log "github.com/cihub/seelog"
|
||||
)
|
||||
|
||||
var ctxs = struct {
|
||||
sync.Mutex
|
||||
m map[*http.Request]*serverContext
|
||||
}{
|
||||
m: make(map[*http.Request]*serverContext),
|
||||
}
|
||||
|
||||
// A server context interface
|
||||
type Context interface {
|
||||
Request() Request // the request made to the server
|
||||
Headers() Headers // the response headers
|
||||
NewRequest(string, string, interface{}) client.Request // a new scoped client request
|
||||
NewProtoRequest(string, string, interface{}) client.Request // a new scoped client request
|
||||
NewJsonRequest(string, string, interface{}) client.Request // a new scoped client request
|
||||
}
|
||||
|
||||
// context represents the context of an in-flight HTTP request.
|
||||
// It implements the appengine.Context and http.ResponseWriter interfaces.
|
||||
type serverContext struct {
|
||||
req *serverRequest
|
||||
outCode int
|
||||
outHeader http.Header
|
||||
outBody []byte
|
||||
}
|
||||
|
||||
// Copied from $GOROOT/src/pkg/net/http/transfer.go. Some response status
|
||||
// codes do not permit a response body (nor response entity headers such as
|
||||
// Content-Length, Content-Type, etc).
|
||||
func bodyAllowedForStatus(status int) bool {
|
||||
switch {
|
||||
case status >= 100 && status <= 199:
|
||||
return false
|
||||
case status == 204:
|
||||
return false
|
||||
case status == 304:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getServerContext(req *http.Request) *serverContext {
|
||||
ctxs.Lock()
|
||||
c := ctxs.m[req]
|
||||
ctxs.Unlock()
|
||||
|
||||
if c == nil {
|
||||
// Someone passed in an http.Request that is not in-flight.
|
||||
panic("NewContext passed an unknown http.Request")
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *serverContext) NewRequest(service, method string, request interface{}) client.Request {
|
||||
req := client.NewRequest(service, method, request)
|
||||
// TODO: set headers and scope
|
||||
req.Headers().Set("X-User-Session", c.Request().Session("X-User-Session"))
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *serverContext) NewProtoRequest(service, method string, request interface{}) client.Request {
|
||||
req := client.NewProtoRequest(service, method, request)
|
||||
// TODO: set headers and scope
|
||||
req.Headers().Set("X-User-Session", c.Request().Session("X-User-Session"))
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *serverContext) NewJsonRequest(service, method string, request interface{}) client.Request {
|
||||
req := client.NewJsonRequest(service, method, request)
|
||||
// TODO: set headers and scope
|
||||
req.Headers().Set("X-User-Session", c.Request().Session("X-User-Session"))
|
||||
return req
|
||||
}
|
||||
|
||||
// The response headers
|
||||
func (c *serverContext) Headers() Headers {
|
||||
return c.outHeader
|
||||
}
|
||||
|
||||
// The response headers
|
||||
func (c *serverContext) Header() http.Header {
|
||||
return c.outHeader
|
||||
}
|
||||
|
||||
// The request made to the server
|
||||
func (c *serverContext) Request() Request {
|
||||
return c.req
|
||||
}
|
||||
|
||||
func (c *serverContext) Write(b []byte) (int, error) {
|
||||
if c.outCode == 0 {
|
||||
c.WriteHeader(http.StatusOK)
|
||||
}
|
||||
if len(b) > 0 && !bodyAllowedForStatus(c.outCode) {
|
||||
return 0, http.ErrBodyNotAllowed
|
||||
}
|
||||
c.outBody = append(c.outBody, b...)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (c *serverContext) WriteHeader(code int) {
|
||||
if c.outCode != 0 {
|
||||
log.Errorf("WriteHeader called multiple times on request.")
|
||||
return
|
||||
}
|
||||
c.outCode = code
|
||||
}
|
||||
|
||||
func GetContext(r *http.Request) *serverContext {
|
||||
return getServerContext(r)
|
||||
}
|
25
server/server_request.go
Normal file
25
server/server_request.go
Normal file
@ -0,0 +1,25 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type serverRequest struct {
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
func (s *serverRequest) Headers() Headers {
|
||||
return s.req.Header
|
||||
}
|
||||
|
||||
func (s *serverRequest) Session(name string) string {
|
||||
if sess := s.Headers().Get(name); len(sess) > 0 {
|
||||
return sess
|
||||
}
|
||||
|
||||
c, err := s.req.Cookie(name)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return c.Value
|
||||
}
|
14
store/consul_item.go
Normal file
14
store/consul_item.go
Normal file
@ -0,0 +1,14 @@
|
||||
package store
|
||||
|
||||
type ConsulItem struct {
|
||||
key string
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (c *ConsulItem) Key() string {
|
||||
return c.key
|
||||
}
|
||||
|
||||
func (c *ConsulItem) Value() []byte {
|
||||
return c.value
|
||||
}
|
55
store/consul_store.go
Normal file
55
store/consul_store.go
Normal file
@ -0,0 +1,55 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
consul "github.com/armon/consul-api"
|
||||
)
|
||||
|
||||
type ConsulStore struct {
|
||||
Client *consul.Client
|
||||
}
|
||||
|
||||
func (c *ConsulStore) Get(key string) (Item, error) {
|
||||
kv, _, err := c.Client.KV().Get(key, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if kv == nil {
|
||||
return nil, errors.New("key not found")
|
||||
}
|
||||
|
||||
return &ConsulItem{
|
||||
key: kv.Key,
|
||||
value: kv.Value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *ConsulStore) Del(key string) error {
|
||||
_, err := c.Client.KV().Delete(key, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ConsulStore) Put(item Item) error {
|
||||
_, err := c.Client.KV().Put(&consul.KVPair{
|
||||
Key: item.Key(),
|
||||
Value: item.Value(),
|
||||
}, nil)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ConsulStore) NewItem(key string, value []byte) Item {
|
||||
return &ConsulItem{
|
||||
key: key,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func NewConsulStore() Store {
|
||||
client, _ := consul.NewClient(&consul.Config{})
|
||||
|
||||
return &ConsulStore{
|
||||
Client: client,
|
||||
}
|
||||
}
|
6
store/item.go
Normal file
6
store/item.go
Normal file
@ -0,0 +1,6 @@
|
||||
package store
|
||||
|
||||
type Item interface {
|
||||
Key() string
|
||||
Value() []byte
|
||||
}
|
14
store/memcached_item.go
Normal file
14
store/memcached_item.go
Normal file
@ -0,0 +1,14 @@
|
||||
package store
|
||||
|
||||
type MemcacheItem struct {
|
||||
key string
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (m *MemcacheItem) Key() string {
|
||||
return m.key
|
||||
}
|
||||
|
||||
func (m *MemcacheItem) Value() []byte {
|
||||
return m.value
|
||||
}
|
65
store/memcached_store.go
Normal file
65
store/memcached_store.go
Normal file
@ -0,0 +1,65 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
mc "github.com/bradfitz/gomemcache/memcache"
|
||||
)
|
||||
|
||||
type MemcacheStore struct {
|
||||
Client *mc.Client
|
||||
}
|
||||
|
||||
func (m *MemcacheStore) Get(key string) (Item, error) {
|
||||
kv, err := m.Client.Get(key)
|
||||
if err != nil && err == mc.ErrCacheMiss {
|
||||
return nil, errors.New("key not found")
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if kv == nil {
|
||||
return nil, errors.New("key not found")
|
||||
}
|
||||
|
||||
return &MemcacheItem{
|
||||
key: kv.Key,
|
||||
value: kv.Value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MemcacheStore) Del(key string) error {
|
||||
return m.Client.Delete(key)
|
||||
}
|
||||
|
||||
func (m *MemcacheStore) Put(item Item) error {
|
||||
return m.Client.Set(&mc.Item{
|
||||
Key: item.Key(),
|
||||
Value: item.Value(),
|
||||
})
|
||||
}
|
||||
|
||||
func (m *MemcacheStore) NewItem(key string, value []byte) Item {
|
||||
return &MemcacheItem{
|
||||
key: key,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMemcacheStore() Store {
|
||||
server := os.Getenv("MEMCACHED_SERVICE_HOST")
|
||||
port := os.Getenv("MEMCACHED_SERVICE_PORT")
|
||||
|
||||
if len(server) == 0 {
|
||||
server = "127.0.0.1"
|
||||
}
|
||||
|
||||
if len(port) == 0 {
|
||||
port = "11211"
|
||||
}
|
||||
|
||||
return &MemcacheStore{
|
||||
Client: mc.New(server + ":" + port),
|
||||
}
|
||||
}
|
28
store/store.go
Normal file
28
store/store.go
Normal file
@ -0,0 +1,28 @@
|
||||
package store
|
||||
|
||||
type Store interface {
|
||||
Get(string) (Item, error)
|
||||
Del(string) error
|
||||
Put(Item) error
|
||||
NewItem(string, []byte) Item
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultStore = NewConsulStore()
|
||||
)
|
||||
|
||||
func Get(key string) (Item, error) {
|
||||
return DefaultStore.Get(key)
|
||||
}
|
||||
|
||||
func Del(key string) error {
|
||||
return DefaultStore.Del(key)
|
||||
}
|
||||
|
||||
func Put(item Item) error {
|
||||
return DefaultStore.Put(item)
|
||||
}
|
||||
|
||||
func NewItem(key string, value []byte) Item {
|
||||
return DefaultStore.NewItem(key, value)
|
||||
}
|
3
template/Dockerfile
Normal file
3
template/Dockerfile
Normal file
@ -0,0 +1,3 @@
|
||||
FROM scratch
|
||||
ADD template /
|
||||
ENTRYPOINT [ "/template" ]
|
30
template/README.md
Normal file
30
template/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Template Service
|
||||
|
||||
An example Go service running with go-micro
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Install Consul
|
||||
[https://www.consul.io/intro/getting-started/install.html](https://www.consul.io/intro/getting-started/install.html)
|
||||
|
||||
Run Consul
|
||||
```
|
||||
$ consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul
|
||||
```
|
||||
|
||||
Run Service
|
||||
```
|
||||
$ go run main.go
|
||||
|
||||
1416690099281057746 [Debug] Rpc handler /_rpc
|
||||
1416690099281092588 [Debug] Starting server go.micro.service.go-template id go.micro.service.go-template-c0bfcb44-728a-11e4-b099-68a86d0d36b6
|
||||
1416690099281192941 [Debug] Listening on [::]:58264
|
||||
1416690099281215346 [Debug] Registering go.micro.service.go-template-c0bfcb44-728a-11e4-b099-68a86d0d36b6
|
||||
```
|
||||
|
||||
Test Service
|
||||
```
|
||||
$ go run go-micro/examples/service_client.go
|
||||
|
||||
go.micro.service.go-template-c0bfcb44-728a-11e4-b099-68a86d0d36b6: Hello John
|
||||
```
|
20
template/handler/example.go
Normal file
20
template/handler/example.go
Normal file
@ -0,0 +1,20 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.net/context"
|
||||
"code.google.com/p/goprotobuf/proto"
|
||||
|
||||
"github.com/asim/go-micro/server"
|
||||
example "github.com/asim/go-micro/template/proto/example"
|
||||
log "github.com/cihub/seelog"
|
||||
)
|
||||
|
||||
type Example struct{}
|
||||
|
||||
func (e *Example) Call(ctx context.Context, req *example.Request, rsp *example.Response) error {
|
||||
log.Debug("Received Example.Call request")
|
||||
|
||||
rsp.Msg = proto.String(server.Id + ": Hello " + req.GetName())
|
||||
|
||||
return nil
|
||||
}
|
28
template/main.go
Normal file
28
template/main.go
Normal file
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/asim/go-micro/server"
|
||||
"github.com/asim/go-micro/template/handler"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server.Name = "go.micro.service.template"
|
||||
|
||||
// Initialise Server
|
||||
server.Init()
|
||||
|
||||
// Register Handlers
|
||||
server.Register(
|
||||
server.NewReceiver(
|
||||
new(handler.Example),
|
||||
),
|
||||
)
|
||||
|
||||
// Run server
|
||||
if err := server.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
21
template/pod.json
Normal file
21
template/pod.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"kind": "Pod",
|
||||
"apiVersion": "v1beta1",
|
||||
"id": "template-service",
|
||||
"desiredState": {
|
||||
"manifest": {
|
||||
"version": "v1beta1",
|
||||
"id": "template-service",
|
||||
"containers": [{
|
||||
"name": "template-service",
|
||||
"image": "chuhnk/go-template",
|
||||
"ports": [{"name": "template-service", "containerPort": 8080}],
|
||||
"command": ["--registry=kubernetes", "--bind_address=:8080"]
|
||||
}],
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"name": "go.micro.service.template",
|
||||
}
|
||||
}
|
||||
|
57
template/proto/example/example.pb.go
Normal file
57
template/proto/example/example.pb.go
Normal file
@ -0,0 +1,57 @@
|
||||
// Code generated by protoc-gen-gogo.
|
||||
// source: asim/go-micro/template/proto/example/example.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package go_micro_service_template_example is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
asim/go-micro/template/proto/example/example.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Request
|
||||
Response
|
||||
*/
|
||||
package go_micro_service_template_example
|
||||
|
||||
import proto "code.google.com/p/gogoprotobuf/proto"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = math.Inf
|
||||
|
||||
type Request struct {
|
||||
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
|
||||
func (m *Request) GetName() string {
|
||||
if m != nil && m.Name != nil {
|
||||
return *m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Msg *string `protobuf:"bytes,1,req,name=msg" json:"msg,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
|
||||
func (m *Response) GetMsg() string {
|
||||
if m != nil && m.Msg != nil {
|
||||
return *m.Msg
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
}
|
9
template/proto/example/example.proto
Normal file
9
template/proto/example/example.proto
Normal file
@ -0,0 +1,9 @@
|
||||
package go.micro.service.template.example;
|
||||
|
||||
message Request {
|
||||
required string name = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
required string msg = 1;
|
||||
}
|
9
template/service.json
Normal file
9
template/service.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "template-service",
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1beta1",
|
||||
"port": 9091,
|
||||
"containerPort": 8080,
|
||||
"selector": { "name": "go.micro.service.template" },
|
||||
"labels": { "name": "go.micro.service.template" }
|
||||
}
|
Loading…
Reference in New Issue
Block a user