add proxy interface and move router packages
This commit is contained in:
177
proxy/http/http.go
Normal file
177
proxy/http/http.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Package http provides a micro rpc to http proxy
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/micro/go-micro"
|
||||
"github.com/micro/go-micro/errors"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// Router will proxy rpc requests as http POST requests. It is a server.Router
|
||||
type Router struct {
|
||||
// The http backend to call
|
||||
Backend string
|
||||
|
||||
// first request
|
||||
first bool
|
||||
}
|
||||
|
||||
var (
|
||||
// The default backend
|
||||
DefaultBackend = "http://localhost:9090"
|
||||
// The default router
|
||||
DefaultRouter = &Router{}
|
||||
)
|
||||
|
||||
func getMethod(hdr map[string]string) string {
|
||||
switch hdr["Micro-Method"] {
|
||||
case "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH":
|
||||
return hdr["Micro-Method"]
|
||||
default:
|
||||
return "POST"
|
||||
}
|
||||
}
|
||||
|
||||
func getEndpoint(hdr map[string]string) string {
|
||||
ep := hdr["Micro-Endpoint"]
|
||||
if len(ep) > 0 && ep[0] == '/' {
|
||||
return ep
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ServeRequest honours the server.Router interface
|
||||
func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
|
||||
if p.Backend == "" {
|
||||
p.Backend = DefaultBackend
|
||||
}
|
||||
|
||||
for {
|
||||
// get data
|
||||
body, err := req.Read()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the header
|
||||
hdr := req.Header()
|
||||
|
||||
// get method
|
||||
method := getMethod(hdr)
|
||||
|
||||
// get endpoint
|
||||
endpoint := getEndpoint(hdr)
|
||||
|
||||
// set the endpoint
|
||||
if len(endpoint) == 0 {
|
||||
endpoint = p.Backend
|
||||
} else {
|
||||
// add endpoint to backend
|
||||
u, err := url.Parse(p.Backend)
|
||||
if err != nil {
|
||||
return errors.InternalServerError(req.Service(), err.Error())
|
||||
}
|
||||
u.Path = path.Join(u.Path, endpoint)
|
||||
endpoint = u.String()
|
||||
}
|
||||
|
||||
// send to backend
|
||||
hreq, err := http.NewRequest(method, endpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return errors.InternalServerError(req.Service(), err.Error())
|
||||
}
|
||||
|
||||
// set the headers
|
||||
for k, v := range hdr {
|
||||
hreq.Header.Set(k, v)
|
||||
}
|
||||
|
||||
// make the call
|
||||
hrsp, err := http.DefaultClient.Do(hreq)
|
||||
if err != nil {
|
||||
return errors.InternalServerError(req.Service(), err.Error())
|
||||
}
|
||||
|
||||
// read body
|
||||
b, err := ioutil.ReadAll(hrsp.Body)
|
||||
hrsp.Body.Close()
|
||||
if err != nil {
|
||||
return errors.InternalServerError(req.Service(), err.Error())
|
||||
}
|
||||
|
||||
// set response headers
|
||||
hdr = map[string]string{}
|
||||
for k, _ := range hrsp.Header {
|
||||
hdr[k] = hrsp.Header.Get(k)
|
||||
}
|
||||
// write the header
|
||||
rsp.WriteHeader(hdr)
|
||||
// write the body
|
||||
err = rsp.Write(b)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return errors.InternalServerError(req.Service(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSingleHostRouter returns a router which sends requests to a single http backend
|
||||
//
|
||||
// It is used by setting it in a new micro service to act as a proxy for a http backend.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// Create a new router to the http backend
|
||||
//
|
||||
// r := NewSingleHostRouter("http://localhost:10001")
|
||||
//
|
||||
// // Create your new service
|
||||
// service := micro.NewService(
|
||||
// micro.Name("greeter"),
|
||||
// // Set the router
|
||||
// http.WithRouter(r),
|
||||
// )
|
||||
//
|
||||
// // Run the service
|
||||
// service.Run()
|
||||
func NewSingleHostRouter(url string) *Router {
|
||||
return &Router{
|
||||
Backend: url,
|
||||
}
|
||||
}
|
||||
|
||||
// NewService returns a new http proxy. It acts as a micro service proxy.
|
||||
// Any request on the transport is routed to a fixed http backend.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// service := NewService(
|
||||
// micro.Name("greeter"),
|
||||
// // Sets the default http endpoint
|
||||
// http.WithBackend("http://localhost:10001"),
|
||||
// )
|
||||
//
|
||||
func NewService(opts ...micro.Option) micro.Service {
|
||||
// prepend router to opts
|
||||
opts = append([]micro.Option{
|
||||
WithRouter(DefaultRouter),
|
||||
}, opts...)
|
||||
|
||||
// create the new service
|
||||
return micro.NewService(opts...)
|
||||
}
|
122
proxy/http/http_test.go
Normal file
122
proxy/http/http_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro"
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/registry/memory"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
type testHandler struct{}
|
||||
|
||||
func (t *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"hello": "world"}`))
|
||||
}
|
||||
|
||||
func TestHTTPRouter(t *testing.T) {
|
||||
c, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
addr := c.Addr().String()
|
||||
|
||||
url := fmt.Sprintf("http://%s", addr)
|
||||
|
||||
testCases := []struct {
|
||||
// http endpoint to call e.g /foo/bar
|
||||
httpEp string
|
||||
// rpc endpoint called e.g Foo.Bar
|
||||
rpcEp string
|
||||
// should be an error
|
||||
err bool
|
||||
}{
|
||||
{"/", "Foo.Bar", false},
|
||||
{"/", "Foo.Baz", false},
|
||||
{"/helloworld", "Hello.World", true},
|
||||
}
|
||||
|
||||
// handler
|
||||
http.Handle("/", new(testHandler))
|
||||
|
||||
// new proxy
|
||||
p := NewSingleHostRouter(url)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
// new micro service
|
||||
service := micro.NewService(
|
||||
micro.Context(ctx),
|
||||
micro.Name("foobar"),
|
||||
micro.Registry(memory.NewRegistry()),
|
||||
micro.AfterStart(func() error {
|
||||
wg.Done()
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
// set router
|
||||
service.Server().Init(
|
||||
server.WithRouter(p),
|
||||
)
|
||||
|
||||
// run service
|
||||
// server
|
||||
go http.Serve(c, nil)
|
||||
go service.Run()
|
||||
|
||||
// wait till service is started
|
||||
wg.Wait()
|
||||
|
||||
for _, test := range testCases {
|
||||
req := service.Client().NewRequest("foobar", test.rpcEp, map[string]string{"foo": "bar"}, client.WithContentType("application/json"))
|
||||
var rsp map[string]string
|
||||
err := service.Client().Call(ctx, req, &rsp)
|
||||
if err != nil && test.err == false {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v := rsp["hello"]; v != "world" {
|
||||
t.Fatalf("Expected hello world got %s from %s", v, test.rpcEp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPRouterOptions(t *testing.T) {
|
||||
// test endpoint
|
||||
service := NewService(
|
||||
WithBackend("http://foo.bar"),
|
||||
)
|
||||
|
||||
r := service.Server().Options().Router
|
||||
httpRouter, ok := r.(*Router)
|
||||
if !ok {
|
||||
t.Fatal("Expected http router to be installed")
|
||||
}
|
||||
if httpRouter.Backend != "http://foo.bar" {
|
||||
t.Fatalf("Expected endpoint http://foo.bar got %v", httpRouter.Backend)
|
||||
}
|
||||
|
||||
// test router
|
||||
service = NewService(
|
||||
WithRouter(&Router{Backend: "http://foo2.bar"}),
|
||||
)
|
||||
r = service.Server().Options().Router
|
||||
httpRouter, ok = r.(*Router)
|
||||
if !ok {
|
||||
t.Fatal("Expected http router to be installed")
|
||||
}
|
||||
if httpRouter.Backend != "http://foo2.bar" {
|
||||
t.Fatalf("Expected endpoint http://foo2.bar got %v", httpRouter.Backend)
|
||||
}
|
||||
}
|
32
proxy/http/options.go
Normal file
32
proxy/http/options.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro"
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// WithBackend provides an option to set the http backend url
|
||||
func WithBackend(url string) micro.Option {
|
||||
return func(o *micro.Options) {
|
||||
// get the router
|
||||
r := o.Server.Options().Router
|
||||
|
||||
// not set
|
||||
if r == nil {
|
||||
r = DefaultRouter
|
||||
o.Server.Init(server.WithRouter(r))
|
||||
}
|
||||
|
||||
// check its a http router
|
||||
if httpRouter, ok := r.(*Router); ok {
|
||||
httpRouter.Backend = url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithRouter provides an option to set the http router
|
||||
func WithRouter(r server.Router) micro.Option {
|
||||
return func(o *micro.Options) {
|
||||
o.Server.Init(server.WithRouter(r))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user