mirror of
https://github.com/go-micro/go-micro.git
synced 2025-01-05 10:20:53 +02:00
5d5aee1f08
set the go version to 1.16 in pr.yml and tests.yml, so as to be consistent with the version in go.mod.
271 lines
6.0 KiB
Go
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)
|
|
}
|