2019-06-03 20:44:43 +03:00
|
|
|
// Package mucp transparently forwards the incoming request using a go-micro client.
|
|
|
|
package mucp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"io"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/micro/go-micro/client"
|
2019-06-26 18:12:57 +03:00
|
|
|
rselect "github.com/micro/go-micro/client/selector/router"
|
2019-06-03 20:44:43 +03:00
|
|
|
"github.com/micro/go-micro/codec"
|
|
|
|
"github.com/micro/go-micro/codec/bytes"
|
2019-06-12 14:45:42 +03:00
|
|
|
"github.com/micro/go-micro/config/options"
|
2019-06-21 19:20:31 +03:00
|
|
|
"github.com/micro/go-micro/network/proxy"
|
2019-06-26 18:12:57 +03:00
|
|
|
"github.com/micro/go-micro/network/router"
|
2019-06-03 20:44:43 +03:00
|
|
|
"github.com/micro/go-micro/server"
|
|
|
|
)
|
|
|
|
|
2019-06-07 15:42:39 +03:00
|
|
|
// Proxy will transparently proxy requests to an endpoint.
|
|
|
|
// If no endpoint is specified it will call a service using the client.
|
|
|
|
type Proxy struct {
|
|
|
|
// embed options
|
|
|
|
options.Options
|
|
|
|
|
|
|
|
// Endpoint specified the fixed service endpoint to call.
|
2019-06-03 20:44:43 +03:00
|
|
|
Endpoint string
|
|
|
|
|
|
|
|
// The client to use for outbound requests
|
|
|
|
Client client.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
// read client request and write to server
|
|
|
|
func readLoop(r server.Request, s client.Stream) error {
|
|
|
|
// request to backend server
|
|
|
|
req := s.Request()
|
|
|
|
|
|
|
|
for {
|
|
|
|
// get data from client
|
|
|
|
// no need to decode it
|
|
|
|
body, err := r.Read()
|
|
|
|
if err == io.EOF {
|
|
|
|
return nil
|
|
|
|
}
|
2019-06-18 20:51:52 +03:00
|
|
|
|
2019-06-03 20:44:43 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the header from client
|
|
|
|
hdr := r.Header()
|
|
|
|
msg := &codec.Message{
|
|
|
|
Type: codec.Request,
|
|
|
|
Header: hdr,
|
|
|
|
Body: body,
|
|
|
|
}
|
2019-06-18 20:51:52 +03:00
|
|
|
|
2019-06-03 20:44:43 +03:00
|
|
|
// write the raw request
|
|
|
|
err = req.Codec().Write(msg, nil)
|
|
|
|
if err == io.EOF {
|
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServeRequest honours the server.Router interface
|
2019-06-07 15:42:39 +03:00
|
|
|
func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
|
2019-06-03 20:44:43 +03:00
|
|
|
// set default client
|
|
|
|
if p.Client == nil {
|
|
|
|
p.Client = client.DefaultClient
|
|
|
|
}
|
|
|
|
|
|
|
|
opts := []client.CallOption{}
|
|
|
|
|
|
|
|
// service name
|
|
|
|
service := req.Service()
|
|
|
|
endpoint := req.Endpoint()
|
|
|
|
|
|
|
|
// call a specific backend
|
2019-06-07 15:42:39 +03:00
|
|
|
if len(p.Endpoint) > 0 {
|
2019-06-03 20:44:43 +03:00
|
|
|
// address:port
|
2019-06-07 15:42:39 +03:00
|
|
|
if parts := strings.Split(p.Endpoint, ":"); len(parts) > 1 {
|
|
|
|
opts = append(opts, client.WithAddress(p.Endpoint))
|
2019-06-03 20:44:43 +03:00
|
|
|
// use as service name
|
|
|
|
} else {
|
2019-06-07 15:42:39 +03:00
|
|
|
service = p.Endpoint
|
2019-06-03 20:44:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// read initial request
|
|
|
|
body, err := req.Read()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// create new request with raw bytes body
|
|
|
|
creq := p.Client.NewRequest(service, endpoint, &bytes.Frame{body}, client.WithContentType(req.ContentType()))
|
|
|
|
|
|
|
|
// create new stream
|
|
|
|
stream, err := p.Client.Stream(ctx, creq, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer stream.Close()
|
|
|
|
|
|
|
|
// create client request read loop
|
|
|
|
go readLoop(req, stream)
|
|
|
|
|
|
|
|
// get raw response
|
|
|
|
resp := stream.Response()
|
|
|
|
|
|
|
|
// create server response write loop
|
|
|
|
for {
|
|
|
|
// read backend response body
|
|
|
|
body, err := resp.Read()
|
|
|
|
if err == io.EOF {
|
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// read backend response header
|
|
|
|
hdr := resp.Header()
|
|
|
|
|
|
|
|
// write raw response header to client
|
|
|
|
rsp.WriteHeader(hdr)
|
|
|
|
|
|
|
|
// write raw response body to client
|
|
|
|
err = rsp.Write(body)
|
|
|
|
if err == io.EOF {
|
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-07 15:42:39 +03:00
|
|
|
// NewSingleHostProxy returns a proxy which sends requests to a single backend
|
|
|
|
func NewSingleHostProxy(endpoint string) *Proxy {
|
|
|
|
return &Proxy{
|
|
|
|
Options: options.NewOptions(),
|
|
|
|
Endpoint: endpoint,
|
2019-06-03 20:44:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-07 15:42:39 +03:00
|
|
|
// NewProxy returns a new proxy which will route based on mucp headers
|
|
|
|
func NewProxy(opts ...options.Option) proxy.Proxy {
|
|
|
|
p := new(Proxy)
|
|
|
|
p.Options = options.NewOptions(opts...)
|
|
|
|
p.Options.Init(options.WithString("mucp"))
|
|
|
|
|
|
|
|
// get endpoint
|
|
|
|
ep, ok := p.Options.Values().Get("proxy.endpoint")
|
|
|
|
if ok {
|
|
|
|
p.Endpoint = ep.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
// get client
|
|
|
|
c, ok := p.Options.Values().Get("proxy.client")
|
|
|
|
if ok {
|
|
|
|
p.Client = c.(client.Client)
|
|
|
|
}
|
|
|
|
|
2019-06-26 18:12:57 +03:00
|
|
|
// get router
|
|
|
|
r, ok := p.Options.Values().Get("proxy.router")
|
|
|
|
if ok {
|
|
|
|
// set the router in the client
|
|
|
|
p.Client.Init(
|
|
|
|
// pass new selector as an option to the client
|
|
|
|
client.Selector(rselect.NewSelector(
|
|
|
|
// set the router in the selector
|
|
|
|
rselect.WithRouter(r.(router.Router)),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-06-07 15:42:39 +03:00
|
|
|
return p
|
2019-06-03 20:44:43 +03:00
|
|
|
}
|