603 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			603 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package http
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"crypto/tls"
 | |
| 	"errors"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/micro/go-micro/v3/network/transport"
 | |
| 	maddr "github.com/micro/go-micro/v3/util/addr"
 | |
| 	"github.com/micro/go-micro/v3/util/buf"
 | |
| 	mnet "github.com/micro/go-micro/v3/util/net"
 | |
| 	mls "github.com/micro/go-micro/v3/util/tls"
 | |
| 	"golang.org/x/net/http2"
 | |
| 	"golang.org/x/net/http2/h2c"
 | |
| )
 | |
| 
 | |
| type httpTransport struct {
 | |
| 	opts transport.Options
 | |
| }
 | |
| 
 | |
| type httpTransportClient struct {
 | |
| 	ht       *httpTransport
 | |
| 	addr     string
 | |
| 	conn     net.Conn
 | |
| 	dialOpts transport.DialOptions
 | |
| 	once     sync.Once
 | |
| 
 | |
| 	sync.RWMutex
 | |
| 
 | |
| 	// request must be stored for response processing
 | |
| 	r    chan *http.Request
 | |
| 	bl   []*http.Request
 | |
| 	buff *bufio.Reader
 | |
| 
 | |
| 	// local/remote ip
 | |
| 	local  string
 | |
| 	remote string
 | |
| }
 | |
| 
 | |
| type httpTransportSocket struct {
 | |
| 	ht *httpTransport
 | |
| 	w  http.ResponseWriter
 | |
| 	r  *http.Request
 | |
| 	rw *bufio.ReadWriter
 | |
| 
 | |
| 	mtx sync.RWMutex
 | |
| 
 | |
| 	// the hijacked when using http 1
 | |
| 	conn net.Conn
 | |
| 	// for the first request
 | |
| 	ch chan *http.Request
 | |
| 
 | |
| 	// h2 things
 | |
| 	buf *bufio.Reader
 | |
| 	// indicate if socket is closed
 | |
| 	closed chan bool
 | |
| 
 | |
| 	// local/remote ip
 | |
| 	local  string
 | |
| 	remote string
 | |
| }
 | |
| 
 | |
| type httpTransportListener struct {
 | |
| 	ht       *httpTransport
 | |
| 	listener net.Listener
 | |
| }
 | |
| 
 | |
| func (h *httpTransportClient) Local() string {
 | |
| 	return h.local
 | |
| }
 | |
| 
 | |
| func (h *httpTransportClient) Remote() string {
 | |
| 	return h.remote
 | |
| }
 | |
| 
 | |
| func (h *httpTransportClient) Send(m *transport.Message) error {
 | |
| 	header := make(http.Header)
 | |
| 
 | |
| 	for k, v := range m.Header {
 | |
| 		header.Set(k, v)
 | |
| 	}
 | |
| 
 | |
| 	b := buf.New(bytes.NewBuffer(m.Body))
 | |
| 	defer b.Close()
 | |
| 
 | |
| 	req := &http.Request{
 | |
| 		Method: "POST",
 | |
| 		URL: &url.URL{
 | |
| 			Scheme: "http",
 | |
| 			Host:   h.addr,
 | |
| 		},
 | |
| 		Header:        header,
 | |
| 		Body:          b,
 | |
| 		ContentLength: int64(b.Len()),
 | |
| 		Host:          h.addr,
 | |
| 	}
 | |
| 
 | |
| 	h.Lock()
 | |
| 	h.bl = append(h.bl, req)
 | |
| 	select {
 | |
| 	case h.r <- h.bl[0]:
 | |
| 		h.bl = h.bl[1:]
 | |
| 	default:
 | |
| 	}
 | |
| 	h.Unlock()
 | |
| 
 | |
| 	// set timeout if its greater than 0
 | |
| 	if h.ht.opts.Timeout > time.Duration(0) {
 | |
| 		h.conn.SetDeadline(time.Now().Add(h.ht.opts.Timeout))
 | |
| 	}
 | |
| 
 | |
| 	return req.Write(h.conn)
 | |
| }
 | |
| 
 | |
| func (h *httpTransportClient) Recv(m *transport.Message) error {
 | |
| 	if m == nil {
 | |
| 		return errors.New("message passed in is nil")
 | |
| 	}
 | |
| 
 | |
| 	var r *http.Request
 | |
| 	if !h.dialOpts.Stream {
 | |
| 		rc, ok := <-h.r
 | |
| 		if !ok {
 | |
| 			return io.EOF
 | |
| 		}
 | |
| 		r = rc
 | |
| 	}
 | |
| 
 | |
| 	// set timeout if its greater than 0
 | |
| 	if h.ht.opts.Timeout > time.Duration(0) {
 | |
| 		h.conn.SetDeadline(time.Now().Add(h.ht.opts.Timeout))
 | |
| 	}
 | |
| 
 | |
| 	rsp, err := http.ReadResponse(h.buff, r)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer rsp.Body.Close()
 | |
| 
 | |
| 	b, err := ioutil.ReadAll(rsp.Body)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if rsp.StatusCode != 200 {
 | |
| 		return errors.New(rsp.Status + ": " + string(b))
 | |
| 	}
 | |
| 
 | |
| 	m.Body = b
 | |
| 
 | |
| 	if m.Header == nil {
 | |
| 		m.Header = make(map[string]string, len(rsp.Header))
 | |
| 	}
 | |
| 
 | |
| 	for k, v := range rsp.Header {
 | |
| 		if len(v) > 0 {
 | |
| 			m.Header[k] = v[0]
 | |
| 		} else {
 | |
| 			m.Header[k] = ""
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (h *httpTransportClient) Close() error {
 | |
| 	h.once.Do(func() {
 | |
| 		h.Lock()
 | |
| 		h.buff.Reset(nil)
 | |
| 		h.Unlock()
 | |
| 		close(h.r)
 | |
| 	})
 | |
| 	return h.conn.Close()
 | |
| }
 | |
| 
 | |
| func (h *httpTransportSocket) Local() string {
 | |
| 	return h.local
 | |
| }
 | |
| 
 | |
| func (h *httpTransportSocket) Remote() string {
 | |
| 	return h.remote
 | |
| }
 | |
| 
 | |
| func (h *httpTransportSocket) Recv(m *transport.Message) error {
 | |
| 	if m == nil {
 | |
| 		return errors.New("message passed in is nil")
 | |
| 	}
 | |
| 	if m.Header == nil {
 | |
| 		m.Header = make(map[string]string, len(h.r.Header))
 | |
| 	}
 | |
| 
 | |
| 	// process http 1
 | |
| 	if h.r.ProtoMajor == 1 {
 | |
| 		// set timeout if its greater than 0
 | |
| 		if h.ht.opts.Timeout > time.Duration(0) {
 | |
| 			h.conn.SetDeadline(time.Now().Add(h.ht.opts.Timeout))
 | |
| 		}
 | |
| 
 | |
| 		var r *http.Request
 | |
| 
 | |
| 		select {
 | |
| 		// get first request
 | |
| 		case r = <-h.ch:
 | |
| 		// read next request
 | |
| 		default:
 | |
| 			rr, err := http.ReadRequest(h.rw.Reader)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			r = rr
 | |
| 		}
 | |
| 
 | |
| 		// read body
 | |
| 		b, err := ioutil.ReadAll(r.Body)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		// set body
 | |
| 		r.Body.Close()
 | |
| 		m.Body = b
 | |
| 
 | |
| 		// set headers
 | |
| 		for k, v := range r.Header {
 | |
| 			if len(v) > 0 {
 | |
| 				m.Header[k] = v[0]
 | |
| 			} else {
 | |
| 				m.Header[k] = ""
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// return early early
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// only process if the socket is open
 | |
| 	select {
 | |
| 	case <-h.closed:
 | |
| 		return io.EOF
 | |
| 	default:
 | |
| 		// no op
 | |
| 	}
 | |
| 
 | |
| 	// processing http2 request
 | |
| 	// read streaming body
 | |
| 
 | |
| 	// set max buffer size
 | |
| 	// TODO: adjustable buffer size
 | |
| 	buf := make([]byte, 4*1024*1024)
 | |
| 
 | |
| 	// read the request body
 | |
| 	n, err := h.buf.Read(buf)
 | |
| 	// not an eof error
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// check if we have data
 | |
| 	if n > 0 {
 | |
| 		m.Body = buf[:n]
 | |
| 	}
 | |
| 
 | |
| 	// set headers
 | |
| 	for k, v := range h.r.Header {
 | |
| 		if len(v) > 0 {
 | |
| 			m.Header[k] = v[0]
 | |
| 		} else {
 | |
| 			m.Header[k] = ""
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// set path
 | |
| 	m.Header[":path"] = h.r.URL.Path
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (h *httpTransportSocket) Send(m *transport.Message) error {
 | |
| 	if h.r.ProtoMajor == 1 {
 | |
| 		// make copy of header
 | |
| 		hdr := make(http.Header)
 | |
| 		for k, v := range h.r.Header {
 | |
| 			hdr[k] = v
 | |
| 		}
 | |
| 
 | |
| 		rsp := &http.Response{
 | |
| 			Header:        hdr,
 | |
| 			Body:          ioutil.NopCloser(bytes.NewReader(m.Body)),
 | |
| 			Status:        "200 OK",
 | |
| 			StatusCode:    200,
 | |
| 			Proto:         "HTTP/1.1",
 | |
| 			ProtoMajor:    1,
 | |
| 			ProtoMinor:    1,
 | |
| 			ContentLength: int64(len(m.Body)),
 | |
| 		}
 | |
| 
 | |
| 		for k, v := range m.Header {
 | |
| 			rsp.Header.Set(k, v)
 | |
| 		}
 | |
| 
 | |
| 		// set timeout if its greater than 0
 | |
| 		if h.ht.opts.Timeout > time.Duration(0) {
 | |
| 			h.conn.SetDeadline(time.Now().Add(h.ht.opts.Timeout))
 | |
| 		}
 | |
| 
 | |
| 		return rsp.Write(h.conn)
 | |
| 	}
 | |
| 
 | |
| 	// only process if the socket is open
 | |
| 	select {
 | |
| 	case <-h.closed:
 | |
| 		return io.EOF
 | |
| 	default:
 | |
| 		// no op
 | |
| 	}
 | |
| 
 | |
| 	// we need to lock to protect the write
 | |
| 	h.mtx.RLock()
 | |
| 	defer h.mtx.RUnlock()
 | |
| 
 | |
| 	// set headers
 | |
| 	for k, v := range m.Header {
 | |
| 		h.w.Header().Set(k, v)
 | |
| 	}
 | |
| 
 | |
| 	// write request
 | |
| 	_, err := h.w.Write(m.Body)
 | |
| 
 | |
| 	// flush the trailers
 | |
| 	h.w.(http.Flusher).Flush()
 | |
| 
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (h *httpTransportSocket) error(m *transport.Message) error {
 | |
| 	if h.r.ProtoMajor == 1 {
 | |
| 		rsp := &http.Response{
 | |
| 			Header:        make(http.Header),
 | |
| 			Body:          ioutil.NopCloser(bytes.NewReader(m.Body)),
 | |
| 			Status:        "500 Internal Server Error",
 | |
| 			StatusCode:    http.StatusInternalServerError,
 | |
| 			Proto:         "HTTP/1.1",
 | |
| 			ProtoMajor:    1,
 | |
| 			ProtoMinor:    1,
 | |
| 			ContentLength: int64(len(m.Body)),
 | |
| 		}
 | |
| 
 | |
| 		for k, v := range m.Header {
 | |
| 			rsp.Header.Set(k, v)
 | |
| 		}
 | |
| 
 | |
| 		return rsp.Write(h.conn)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (h *httpTransportSocket) Close() error {
 | |
| 	h.mtx.Lock()
 | |
| 	defer h.mtx.Unlock()
 | |
| 	select {
 | |
| 	case <-h.closed:
 | |
| 		return nil
 | |
| 	default:
 | |
| 		// close the channel
 | |
| 		close(h.closed)
 | |
| 
 | |
| 		// close the buffer
 | |
| 		h.r.Body.Close()
 | |
| 
 | |
| 		// close the connection
 | |
| 		if h.r.ProtoMajor == 1 {
 | |
| 			return h.conn.Close()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (h *httpTransportListener) Addr() string {
 | |
| 	return h.listener.Addr().String()
 | |
| }
 | |
| 
 | |
| func (h *httpTransportListener) Close() error {
 | |
| 	return h.listener.Close()
 | |
| }
 | |
| 
 | |
| func (h *httpTransportListener) Accept(fn func(transport.Socket)) error {
 | |
| 	// create handler mux
 | |
| 	mux := http.NewServeMux()
 | |
| 
 | |
| 	// register our transport handler
 | |
| 	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 | |
| 		var buf *bufio.ReadWriter
 | |
| 		var con net.Conn
 | |
| 
 | |
| 		// read a regular request
 | |
| 		if r.ProtoMajor == 1 {
 | |
| 			b, err := ioutil.ReadAll(r.Body)
 | |
| 			if err != nil {
 | |
| 				http.Error(w, err.Error(), http.StatusInternalServerError)
 | |
| 				return
 | |
| 			}
 | |
| 			r.Body = ioutil.NopCloser(bytes.NewReader(b))
 | |
| 			// hijack the conn
 | |
| 			hj, ok := w.(http.Hijacker)
 | |
| 			if !ok {
 | |
| 				// we're screwed
 | |
| 				http.Error(w, "cannot serve conn", http.StatusInternalServerError)
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			conn, bufrw, err := hj.Hijack()
 | |
| 			if err != nil {
 | |
| 				http.Error(w, err.Error(), http.StatusInternalServerError)
 | |
| 				return
 | |
| 			}
 | |
| 			defer conn.Close()
 | |
| 			buf = bufrw
 | |
| 			con = conn
 | |
| 		}
 | |
| 
 | |
| 		// buffered reader
 | |
| 		bufr := bufio.NewReader(r.Body)
 | |
| 
 | |
| 		// save the request
 | |
| 		ch := make(chan *http.Request, 1)
 | |
| 		ch <- r
 | |
| 
 | |
| 		// create a new transport socket
 | |
| 		sock := &httpTransportSocket{
 | |
| 			ht:     h.ht,
 | |
| 			w:      w,
 | |
| 			r:      r,
 | |
| 			rw:     buf,
 | |
| 			buf:    bufr,
 | |
| 			ch:     ch,
 | |
| 			conn:   con,
 | |
| 			local:  h.Addr(),
 | |
| 			remote: r.RemoteAddr,
 | |
| 			closed: make(chan bool),
 | |
| 		}
 | |
| 
 | |
| 		// execute the socket
 | |
| 		fn(sock)
 | |
| 	})
 | |
| 
 | |
| 	// get optional handlers
 | |
| 	if h.ht.opts.Context != nil {
 | |
| 		handlers, ok := h.ht.opts.Context.Value("http_handlers").(map[string]http.Handler)
 | |
| 		if ok {
 | |
| 			for pattern, handler := range handlers {
 | |
| 				mux.Handle(pattern, handler)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// default http2 server
 | |
| 	srv := &http.Server{
 | |
| 		Handler: mux,
 | |
| 	}
 | |
| 
 | |
| 	// insecure connection use h2c
 | |
| 	if !(h.ht.opts.Secure || h.ht.opts.TLSConfig != nil) {
 | |
| 		srv.Handler = h2c.NewHandler(mux, &http2.Server{})
 | |
| 	}
 | |
| 
 | |
| 	// begin serving
 | |
| 	return srv.Serve(h.listener)
 | |
| }
 | |
| 
 | |
| func (h *httpTransport) Dial(addr string, opts ...transport.DialOption) (transport.Client, error) {
 | |
| 	dopts := transport.DialOptions{
 | |
| 		Timeout: transport.DefaultDialTimeout,
 | |
| 	}
 | |
| 
 | |
| 	for _, opt := range opts {
 | |
| 		opt(&dopts)
 | |
| 	}
 | |
| 
 | |
| 	var conn net.Conn
 | |
| 	var err error
 | |
| 
 | |
| 	// TODO: support dial option here rather than using internal config
 | |
| 	if h.opts.Secure || h.opts.TLSConfig != nil {
 | |
| 		config := h.opts.TLSConfig
 | |
| 		if config == nil {
 | |
| 			config = &tls.Config{
 | |
| 				InsecureSkipVerify: true,
 | |
| 			}
 | |
| 		}
 | |
| 		config.NextProtos = []string{"http/1.1"}
 | |
| 		conn, err = newConn(func(addr string) (net.Conn, error) {
 | |
| 			return tls.DialWithDialer(&net.Dialer{Timeout: dopts.Timeout}, "tcp", addr, config)
 | |
| 		})(addr)
 | |
| 	} else {
 | |
| 		conn, err = newConn(func(addr string) (net.Conn, error) {
 | |
| 			return net.DialTimeout("tcp", addr, dopts.Timeout)
 | |
| 		})(addr)
 | |
| 	}
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &httpTransportClient{
 | |
| 		ht:       h,
 | |
| 		addr:     addr,
 | |
| 		conn:     conn,
 | |
| 		buff:     bufio.NewReader(conn),
 | |
| 		dialOpts: dopts,
 | |
| 		r:        make(chan *http.Request, 1),
 | |
| 		local:    conn.LocalAddr().String(),
 | |
| 		remote:   conn.RemoteAddr().String(),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (h *httpTransport) Listen(addr string, opts ...transport.ListenOption) (transport.Listener, error) {
 | |
| 	var options transport.ListenOptions
 | |
| 	for _, o := range opts {
 | |
| 		o(&options)
 | |
| 	}
 | |
| 
 | |
| 	var l net.Listener
 | |
| 	var err error
 | |
| 
 | |
| 	// TODO: support use of listen options
 | |
| 	if h.opts.Secure || h.opts.TLSConfig != nil {
 | |
| 		config := h.opts.TLSConfig
 | |
| 
 | |
| 		fn := func(addr string) (net.Listener, error) {
 | |
| 			if config == nil {
 | |
| 				hosts := []string{addr}
 | |
| 
 | |
| 				// check if its a valid host:port
 | |
| 				if host, _, err := net.SplitHostPort(addr); err == nil {
 | |
| 					if len(host) == 0 {
 | |
| 						hosts = maddr.IPs()
 | |
| 					} else {
 | |
| 						hosts = []string{host}
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				// generate a certificate
 | |
| 				cert, err := mls.Certificate(hosts...)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				config = &tls.Config{Certificates: []tls.Certificate{cert}}
 | |
| 			}
 | |
| 			return tls.Listen("tcp", addr, config)
 | |
| 		}
 | |
| 
 | |
| 		l, err = mnet.Listen(addr, fn)
 | |
| 	} else {
 | |
| 		fn := func(addr string) (net.Listener, error) {
 | |
| 			return net.Listen("tcp", addr)
 | |
| 		}
 | |
| 
 | |
| 		l, err = mnet.Listen(addr, fn)
 | |
| 	}
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &httpTransportListener{
 | |
| 		ht:       h,
 | |
| 		listener: l,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (h *httpTransport) Init(opts ...transport.Option) error {
 | |
| 	for _, o := range opts {
 | |
| 		o(&h.opts)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (h *httpTransport) Options() transport.Options {
 | |
| 	return h.opts
 | |
| }
 | |
| 
 | |
| func (h *httpTransport) String() string {
 | |
| 	return "http"
 | |
| }
 | |
| 
 | |
| func NewTransport(opts ...transport.Option) transport.Transport {
 | |
| 	var options transport.Options
 | |
| 	for _, o := range opts {
 | |
| 		o(&options)
 | |
| 	}
 | |
| 	return &httpTransport{opts: options}
 | |
| }
 |