github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/proxy/http/http.go (about)

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