Add http server which implements go-micro.Server

This commit is contained in:
Asim 2016-06-30 20:21:57 +01:00 committed by Vasiliy Tolstov
commit a067b0b2e8
7 changed files with 514 additions and 0 deletions

65
README.md Normal file
View File

@ -0,0 +1,65 @@
# HTTP Server
The HTTP Server is a go-micro.Server. It's a partial implementation which strips out codecs, transports, etc but enables you
to create a HTTP Server that could potentially be used for REST based API services.
## Usage
```go
import (
"net/http"
"github.com/micro/go-micro/server"
httpServer "github.com/micro/go-plugins/server/http"
)
func main() {
srv := httpServer.NewServer(
server.Name("helloworld"),
)
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`hello world`))
})
hd := srv.NewHandler(mux)
srv.Handle(hd)
srv.Start()
srv.Register()
}
```
Or as part of a service
```go
import (
"net/http"
"github.com/micro/go-micro"
"github.com/micro/go-micro/server"
httpServer "github.com/micro/go-plugins/server/http"
)
func main() {
srv := httpServer.NewServer(
server.Name("helloworld"),
)
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`hello world`))
})
hd := srv.NewHandler(mux)
srv.Handle(hd)
service := micro.NewService(
micro.Server(srv),
)
service.Init()
service.Run()
}
```

114
extractor.go Normal file
View File

@ -0,0 +1,114 @@
package http
import (
"fmt"
"net"
"strconv"
"strings"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/server"
)
var (
privateBlocks []*net.IPNet
)
func init() {
for _, b := range []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"} {
if _, block, err := net.ParseCIDR(b); err == nil {
privateBlocks = append(privateBlocks, block)
}
}
}
func extractAddress(addr string) (string, error) {
if len(addr) > 0 && (addr != "0.0.0.0" && addr != "[::]") {
return addr, nil
}
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", fmt.Errorf("Failed to get interface addresses! Err: %v", err)
}
var ipAddr []byte
for _, rawAddr := range addrs {
var ip net.IP
switch addr := rawAddr.(type) {
case *net.IPAddr:
ip = addr.IP
case *net.IPNet:
ip = addr.IP
default:
continue
}
if ip.To4() == nil {
continue
}
if !isPrivateIP(ip.String()) {
continue
}
ipAddr = ip
break
}
if ipAddr == nil {
return "", fmt.Errorf("No private IP address found, and explicit IP not provided")
}
return net.IP(ipAddr).String(), nil
}
func isPrivateIP(ipAddr string) bool {
ip := net.ParseIP(ipAddr)
for _, priv := range privateBlocks {
if priv.Contains(ip) {
return true
}
}
return false
}
func serviceDef(opts server.Options) *registry.Service {
var advt, host string
var port int
if len(opts.Advertise) > 0 {
advt = opts.Advertise
} else {
advt = opts.Address
}
parts := strings.Split(advt, ":")
if len(parts) > 1 {
host = strings.Join(parts[:len(parts)-1], ":")
port, _ = strconv.Atoi(parts[len(parts)-1])
} else {
host = parts[0]
}
addr, err := extractAddress(host)
if err != nil {
addr = host
}
node := &registry.Node{
Id: opts.Name + "-" + opts.Id,
Address: addr,
Port: port,
Metadata: opts.Metadata,
}
node.Metadata["server"] = "http"
return &registry.Service{
Name: opts.Name,
Version: opts.Version,
Nodes: []*registry.Node{node},
}
}

27
handler.go Normal file
View File

@ -0,0 +1,27 @@
package http
import (
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/server"
)
type httpHandler struct {
opts server.HandlerOptions
hd interface{}
}
func (h *httpHandler) Name() string {
return "handler"
}
func (h *httpHandler) Handler() interface{} {
return h.hd
}
func (h *httpHandler) Endpoints() []*registry.Endpoint {
return []*registry.Endpoint{}
}
func (h *httpHandler) Options() server.HandlerOptions {
return h.opts
}

148
http.go Normal file
View File

@ -0,0 +1,148 @@
// Package http implements a go-micro.Server
package http
import (
"errors"
"net"
"net/http"
"sync"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/server"
)
type httpServer struct {
sync.Mutex
opts server.Options
hd server.Handler
exit chan chan error
}
func (h *httpServer) Options() server.Options {
h.Lock()
opts := h.opts
h.Unlock()
return opts
}
func (h *httpServer) Init(opts ...server.Option) error {
h.Lock()
for _, o := range opts {
o(&h.opts)
}
h.Unlock()
return nil
}
func (h *httpServer) Handle(handler server.Handler) error {
if _, ok := handler.Handler().(http.Handler); !ok {
return errors.New("Handle requires http.Handler")
}
h.Lock()
h.hd = handler
h.Unlock()
return nil
}
func (h *httpServer) NewHandler(handler interface{}, opts ...server.HandlerOption) server.Handler {
var options server.HandlerOptions
for _, o := range opts {
o(&options)
}
return &httpHandler{
opts: options,
hd: handler,
}
}
func (h *httpServer) NewSubscriber(topic string, handler interface{}, opts ...server.SubscriberOption) server.Subscriber {
var options server.SubscriberOptions
for _, o := range opts {
o(&options)
}
return &httpSubscriber{
opts: options,
topic: topic,
hd: handler,
}
}
func (h *httpServer) Subscribe(s server.Subscriber) error {
return errors.New("subscribe is not supported")
}
func (h *httpServer) Register() error {
h.Lock()
opts := h.opts
h.Unlock()
service := serviceDef(opts)
rOpts := []registry.RegisterOption{
registry.RegisterTTL(opts.RegisterTTL),
}
return opts.Registry.Register(service, rOpts...)
}
func (h *httpServer) Deregister() error {
h.Lock()
opts := h.opts
h.Unlock()
service := serviceDef(opts)
return opts.Registry.Deregister(service)
}
func (h *httpServer) Start() error {
h.Lock()
opts := h.opts
hd := h.hd
h.Unlock()
ln, err := net.Listen("tcp", opts.Address)
if err != nil {
return err
}
h.Lock()
h.opts.Address = ln.Addr().String()
h.Unlock()
handler, ok := hd.Handler().(http.Handler)
if !ok {
return errors.New("Server required http.Handler")
}
go http.Serve(ln, handler)
go func() {
ch := <-h.exit
ch <- ln.Close()
}()
return nil
}
func (h *httpServer) Stop() error {
ch := make(chan error)
h.exit <- ch
return <-ch
}
func (h *httpServer) String() string {
return "http"
}
func newServer(opts ...server.Option) server.Server {
return &httpServer{
opts: newOptions(opts...),
exit: make(chan chan error),
}
}
func NewServer(opts ...server.Option) server.Server {
return newServer(opts...)
}

89
http_test.go Normal file
View File

@ -0,0 +1,89 @@
package http
import (
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/registry/mock"
"github.com/micro/go-micro/server"
)
func TestHTTPServer(t *testing.T) {
reg := mock.NewRegistry()
// create server
srv := NewServer(server.Registry(reg))
// create server mux
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`hello world`))
})
// create handler
hd := srv.NewHandler(mux)
// register handler
if err := srv.Handle(hd); err != nil {
t.Fatal(err)
}
// start server
if err := srv.Start(); err != nil {
t.Fatal(err)
}
// register server
if err := srv.Register(); err != nil {
t.Fatal(err)
}
// lookup server
service, err := reg.GetService(server.DefaultName)
if err != nil {
t.Fatal(err)
}
if len(service) != 1 {
t.Fatalf("Expected 1 service got %d: %+v", len(service), service)
}
if len(service[0].Nodes) != 1 {
t.Fatalf("Expected 1 node got %d: %+v", len(service[0].Nodes), service[0].Nodes)
}
// make request
rsp, err := http.Get(fmt.Sprintf("http://%s:%d", service[0].Nodes[0].Address, service[0].Nodes[0].Port))
if err != nil {
t.Fatal(err)
}
defer rsp.Body.Close()
b, err := ioutil.ReadAll(rsp.Body)
if err != nil {
t.Fatal(err)
}
if s := string(b); s != "hello world" {
t.Fatalf("Expected response %s, got %s", "hello world", s)
}
// deregister server
if err := srv.Deregister(); err != nil {
t.Fatal(err)
}
// try get service
service, err = reg.GetService(server.DefaultName)
if err == nil {
t.Fatal("Expected %v got %+v", registry.ErrNotFound, service)
}
// stop server
if err := srv.Stop(); err != nil {
t.Fatal(err)
}
}

43
options.go Normal file
View File

@ -0,0 +1,43 @@
package http
import (
"github.com/micro/go-micro/codec"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/server"
"golang.org/x/net/context"
)
func newOptions(opt ...server.Option) server.Options {
opts := server.Options{
Codecs: make(map[string]codec.NewCodec),
Metadata: map[string]string{},
Context: context.Background(),
}
for _, o := range opt {
o(&opts)
}
if opts.Registry == nil {
opts.Registry = registry.DefaultRegistry
}
if len(opts.Address) == 0 {
opts.Address = server.DefaultAddress
}
if len(opts.Name) == 0 {
opts.Name = server.DefaultName
}
if len(opts.Id) == 0 {
opts.Id = server.DefaultId
}
if len(opts.Version) == 0 {
opts.Version = server.DefaultVersion
}
return opts
}

28
subscriber.go Normal file
View File

@ -0,0 +1,28 @@
package http
import (
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/server"
)
type httpSubscriber struct {
opts server.SubscriberOptions
topic string
hd interface{}
}
func (h *httpSubscriber) Topic() string {
return h.topic
}
func (h *httpSubscriber) Subscriber() interface{} {
return h.hd
}
func (h *httpSubscriber) Endpoints() []*registry.Endpoint {
return []*registry.Endpoint{}
}
func (h *httpSubscriber) Options() server.SubscriberOptions {
return h.opts
}