github.com/annwntech/go-micro/v2@v2.9.5/proxy/http/http.go (about)

     1  // Package http provides a micro rpc to http proxy
     2  package http
     3  
     4  import (
     5  	"bytes"
     6  	"context"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/url"
    11  	"path"
    12  
    13  	"github.com/annwntech/go-micro/v2/errors"
    14  	"github.com/annwntech/go-micro/v2/proxy"
    15  	"github.com/annwntech/go-micro/v2/server"
    16  )
    17  
    18  // Proxy will proxy rpc requests as http POST requests. It is a server.Proxy
    19  type Proxy struct {
    20  	options proxy.Options
    21  
    22  	// The http backend to call
    23  	Endpoint string
    24  
    25  	// first request
    26  	first bool
    27  }
    28  
    29  func getMethod(hdr map[string]string) string {
    30  	switch hdr["Micro-Method"] {
    31  	case "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH":
    32  		return hdr["Micro-Method"]
    33  	default:
    34  		return "POST"
    35  	}
    36  }
    37  
    38  func getEndpoint(hdr map[string]string) string {
    39  	ep := hdr["Micro-Endpoint"]
    40  	if len(ep) > 0 && ep[0] == '/' {
    41  		return ep
    42  	}
    43  	return ""
    44  }
    45  
    46  func getTopic(hdr map[string]string) string {
    47  	ep := hdr["Micro-Topic"]
    48  	if len(ep) > 0 && ep[0] == '/' {
    49  		return ep
    50  	}
    51  	return "/" + hdr["Micro-Topic"]
    52  }
    53  
    54  // ProcessMessage handles incoming asynchronous messages
    55  func (p *Proxy) ProcessMessage(ctx context.Context, msg server.Message) error {
    56  	if p.Endpoint == "" {
    57  		p.Endpoint = proxy.DefaultEndpoint
    58  	}
    59  
    60  	// get the header
    61  	hdr := msg.Header()
    62  
    63  	// get topic
    64  	// use /topic as endpoint
    65  	endpoint := getTopic(hdr)
    66  
    67  	// set the endpoint
    68  	if len(endpoint) == 0 {
    69  		endpoint = p.Endpoint
    70  	} else {
    71  		// add endpoint to backend
    72  		u, err := url.Parse(p.Endpoint)
    73  		if err != nil {
    74  			return errors.InternalServerError(msg.Topic(), err.Error())
    75  		}
    76  		u.Path = path.Join(u.Path, endpoint)
    77  		endpoint = u.String()
    78  	}
    79  
    80  	// send to backend
    81  	hreq, err := http.NewRequest("POST", endpoint, bytes.NewReader(msg.Body()))
    82  	if err != nil {
    83  		return errors.InternalServerError(msg.Topic(), err.Error())
    84  	}
    85  
    86  	// set the headers
    87  	for k, v := range hdr {
    88  		hreq.Header.Set(k, v)
    89  	}
    90  
    91  	// make the call
    92  	hrsp, err := http.DefaultClient.Do(hreq)
    93  	if err != nil {
    94  		return errors.InternalServerError(msg.Topic(), err.Error())
    95  	}
    96  
    97  	// read body
    98  	b, err := ioutil.ReadAll(hrsp.Body)
    99  	hrsp.Body.Close()
   100  	if err != nil {
   101  		return errors.InternalServerError(msg.Topic(), err.Error())
   102  	}
   103  
   104  	if hrsp.StatusCode != 200 {
   105  		return errors.New(msg.Topic(), string(b), int32(hrsp.StatusCode))
   106  	}
   107  
   108  	return nil
   109  }
   110  
   111  // ServeRequest honours the server.Router interface
   112  func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
   113  	if p.Endpoint == "" {
   114  		p.Endpoint = proxy.DefaultEndpoint
   115  	}
   116  
   117  	for {
   118  		// get data
   119  		body, err := req.Read()
   120  		if err == io.EOF {
   121  			return nil
   122  		}
   123  		if err != nil {
   124  			return err
   125  		}
   126  
   127  		// get the header
   128  		hdr := req.Header()
   129  
   130  		// get method
   131  		method := getMethod(hdr)
   132  
   133  		// get endpoint
   134  		endpoint := getEndpoint(hdr)
   135  
   136  		// set the endpoint
   137  		if len(endpoint) == 0 {
   138  			endpoint = p.Endpoint
   139  		} else {
   140  			// add endpoint to backend
   141  			u, err := url.Parse(p.Endpoint)
   142  			if err != nil {
   143  				return errors.InternalServerError(req.Service(), err.Error())
   144  			}
   145  			u.Path = path.Join(u.Path, endpoint)
   146  			endpoint = u.String()
   147  		}
   148  
   149  		// send to backend
   150  		hreq, err := http.NewRequest(method, endpoint, bytes.NewReader(body))
   151  		if err != nil {
   152  			return errors.InternalServerError(req.Service(), err.Error())
   153  		}
   154  
   155  		// set the headers
   156  		for k, v := range hdr {
   157  			hreq.Header.Set(k, v)
   158  		}
   159  
   160  		// make the call
   161  		hrsp, err := http.DefaultClient.Do(hreq)
   162  		if err != nil {
   163  			return errors.InternalServerError(req.Service(), err.Error())
   164  		}
   165  
   166  		// read body
   167  		b, err := ioutil.ReadAll(hrsp.Body)
   168  		hrsp.Body.Close()
   169  		if err != nil {
   170  			return errors.InternalServerError(req.Service(), err.Error())
   171  		}
   172  
   173  		// set response headers
   174  		hdr = map[string]string{}
   175  		for k := range hrsp.Header {
   176  			hdr[k] = hrsp.Header.Get(k)
   177  		}
   178  		// write the header
   179  		rsp.WriteHeader(hdr)
   180  		// write the body
   181  		err = rsp.Write(b)
   182  		if err == io.EOF {
   183  			return nil
   184  		}
   185  		if err != nil {
   186  			return errors.InternalServerError(req.Service(), err.Error())
   187  		}
   188  	}
   189  }
   190  
   191  func (p *Proxy) String() string {
   192  	return "http"
   193  }
   194  
   195  // NewSingleHostProxy returns a router which sends requests to a single http backend
   196  func NewSingleHostProxy(url string) proxy.Proxy {
   197  	return &Proxy{
   198  		Endpoint: url,
   199  	}
   200  }
   201  
   202  // NewProxy returns a new proxy which will route using a http client
   203  func NewProxy(opts ...proxy.Option) proxy.Proxy {
   204  	var options proxy.Options
   205  	for _, o := range opts {
   206  		o(&options)
   207  	}
   208  
   209  	p := new(Proxy)
   210  	p.Endpoint = options.Endpoint
   211  	p.options = options
   212  
   213  	return p
   214  }