This commit is contained in:
Asim 2015-01-13 23:31:27 +00:00
commit 8e55cde513
43 changed files with 1639 additions and 0 deletions

3
README.me Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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),
}
}

View 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
View 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
View 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,
}
}

View 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
}

View 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
}

View 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",
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
package server
type Receiver interface {
Name() string
Handler() interface{}
}

6
server/request.go Normal file
View File

@ -0,0 +1,6 @@
package server
type Request interface {
Headers() Headers
Session(string) string
}

29
server/rpc_receiver.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
package store
type Item interface {
Key() string
Value() []byte
}

14
store/memcached_item.go Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
FROM scratch
ADD template /
ENTRYPOINT [ "/template" ]

30
template/README.md Normal file
View 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
```

View 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
View 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
View 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",
}
}

View 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() {
}

View 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
View 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" }
}