1
0
mirror of https://github.com/go-micro/go-micro.git synced 2025-01-05 10:20:53 +02:00
go-micro/plugins/proxy/http/http.go
Benjamin 5d5aee1f08
replace ioutil with io and os (#2327)
set the go version to 1.16 in pr.yml and tests.yml, so as to be consistent with the version in go.mod.
2021-10-30 19:24:40 +01:00

271 lines
6.0 KiB
Go

// Package http provides a micro to http proxy
package http
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"path/filepath"
"strings"
"go-micro.dev/v4"
"go-micro.dev/v4/errors"
"go-micro.dev/v4/server"
)
// Router will proxy rpc requests as http POST requests. It is a server.Router
type Router struct {
// Converts RPC Foo.Bar to /foo/bar
Resolver *Resolver
// The http backend to call
Backend string
// first request
first bool
// rpc ep / http ep mapping
eps map[string]string
}
// Resolver resolves rpc to http. It explicity maps Foo.Bar to /foo/bar
type Resolver struct{}
var (
// The default backend
DefaultBackend = "http://localhost:9090"
// The default router
DefaultRouter = &Router{}
)
// Foo.Bar becomes /foo/bar
func (r *Resolver) Resolve(ep string) string {
// replace . with /
ep = strings.Replace(ep, ".", "/", -1)
// lowercase the whole thing
ep = strings.ToLower(ep)
// prefix with "/"
return filepath.Join("/", ep)
}
// set the nil things
func (p *Router) setup() {
if p.Resolver == nil {
p.Resolver = new(Resolver)
}
if p.Backend == "" {
p.Backend = DefaultBackend
}
if p.eps == nil {
p.eps = map[string]string{}
}
}
// Endpoint returns the http endpoint for an rpc endpoint.
// Endpoint("Foo.Bar") returns http://localhost:9090/foo/bar
func (p *Router) Endpoint(rpcEp string) (string, error) {
p.setup()
// get http endpoint
ep, ok := p.eps[rpcEp]
if !ok {
// get default
ep = p.Resolver.Resolve(rpcEp)
}
// already full qualified URL
if strings.HasPrefix(ep, "http://") || strings.HasPrefix(ep, "https://") {
return ep, nil
}
// parse into url
// full path to call
u, err := url.Parse(p.Backend)
if err != nil {
return "", err
}
// set path
u.Path = filepath.Join(u.Path, ep)
// set scheme
if len(u.Scheme) == 0 {
u.Scheme = "http"
}
// set host
if len(u.Host) == 0 {
u.Host = "localhost"
}
// create ep
return u.String(), nil
}
// RegisterEndpoint registers a http endpoint against an RPC endpoint.
// It converts relative paths into backend:endpoint. Anything prefixed
// with http:// or https:// will be left as is.
// RegisterEndpoint("Foo.Bar", "/foo/bar")
// RegisterEndpoint("Greeter.Hello", "/helloworld")
// RegisterEndpoint("Greeter.Hello", "http://localhost:8080/")
func (p *Router) RegisterEndpoint(rpcEp, httpEp string) error {
p.setup()
// create ep
p.eps[rpcEp] = httpEp
return nil
}
func (p *Router) ProcessMessage(ctx context.Context, msg server.Message) error {
return nil
}
// ServeRequest honours the server.Router interface
func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
// rudimentary post based streaming
for {
// get data
body, err := req.Read()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
var rpcEp string
// get rpc endpoint
if p.first {
p.first = false
rpcEp = req.Endpoint()
} else {
hdr := req.Header()
rpcEp = hdr["X-Micro-Endpoint"]
}
// get http endpoint
ep, err := p.Endpoint(rpcEp)
if err != nil {
return errors.NotFound(req.Service(), err.Error())
}
// no stream support currently
// TODO: lookup host
hreq, err := http.NewRequest("POST", ep, bytes.NewReader(body))
if err != nil {
return errors.InternalServerError(req.Service(), err.Error())
}
// get the header
hdr := req.Header()
// 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 := io.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 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")
//
// // Add additional routes
// r.RegisterEndpoint("Hello.World", "/helloworld")
//
// // 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{
Resolver: new(Resolver),
Backend: url,
eps: map[string]string{},
}
}
// NewService returns a new http proxy. It acts as a micro service and proxies to a http backend.
// Routes are dynamically set e.g Foo.Bar routes to /foo/bar. The default backend is http://localhost:9090.
// Optionally specify the backend endpoint url or the router. Also choose to register specific endpoints.
//
// Usage:
//
// service := NewService(
// micro.Name("greeter"),
// // Sets the default http endpoint
// http.WithBackend("http://localhost:10001"),
// )
//
// Set fixed backend endpoints
//
// // register an endpoint
// http.RegisterEndpoint("Hello.World", "/helloworld")
//
// service := NewService(
// micro.Name("greeter"),
// // Set the 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...)
}
// RegisterEndpoint registers a http endpoint against an RPC endpoint
// RegisterEndpoint("Foo.Bar", "/foo/bar")
// RegisterEndpoint("Greeter.Hello", "/helloworld")
// RegisterEndpoint("Greeter.Hello", "http://localhost:8080/")
func RegisterEndpoint(rpcEp string, httpEp string) error {
return DefaultRouter.RegisterEndpoint(rpcEp, httpEp)
}