github.com/vipernet-xyz/tm@v0.34.24/rpc/jsonrpc/server/http_server.go (about)

     1  // Commons for HTTP handling
     2  package server
     3  
     4  import (
     5  	"bufio"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  	"net/http"
    11  	"os"
    12  	"runtime/debug"
    13  	"strings"
    14  	"time"
    15  
    16  	"golang.org/x/net/netutil"
    17  
    18  	"github.com/vipernet-xyz/tm/libs/log"
    19  	types "github.com/vipernet-xyz/tm/rpc/jsonrpc/types"
    20  )
    21  
    22  // Config is a RPC server configuration.
    23  type Config struct {
    24  	// see netutil.LimitListener
    25  	MaxOpenConnections int
    26  	// mirrors http.Server#ReadTimeout
    27  	ReadTimeout time.Duration
    28  	// mirrors http.Server#WriteTimeout
    29  	WriteTimeout time.Duration
    30  	// MaxBodyBytes controls the maximum number of bytes the
    31  	// server will read parsing the request body.
    32  	MaxBodyBytes int64
    33  	// mirrors http.Server#MaxHeaderBytes
    34  	MaxHeaderBytes int
    35  }
    36  
    37  // DefaultConfig returns a default configuration.
    38  func DefaultConfig() *Config {
    39  	return &Config{
    40  		MaxOpenConnections: 0, // unlimited
    41  		ReadTimeout:        10 * time.Second,
    42  		WriteTimeout:       10 * time.Second,
    43  		MaxBodyBytes:       int64(1000000), // 1MB
    44  		MaxHeaderBytes:     1 << 20,        // same as the net/http default
    45  	}
    46  }
    47  
    48  // Serve creates a http.Server and calls Serve with the given listener. It
    49  // wraps handler with RecoverAndLogHandler and a handler, which limits the max
    50  // body size to config.MaxBodyBytes.
    51  //
    52  // NOTE: This function blocks - you may want to call it in a go-routine.
    53  func Serve(listener net.Listener, handler http.Handler, logger log.Logger, config *Config) error {
    54  	logger.Info("serve", "msg", log.NewLazySprintf("Starting RPC HTTP server on %s", listener.Addr()))
    55  	s := &http.Server{
    56  		Handler:           RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger),
    57  		ReadTimeout:       config.ReadTimeout,
    58  		ReadHeaderTimeout: config.ReadTimeout,
    59  		WriteTimeout:      config.WriteTimeout,
    60  		MaxHeaderBytes:    config.MaxHeaderBytes,
    61  	}
    62  	err := s.Serve(listener)
    63  	logger.Info("RPC HTTP server stopped", "err", err)
    64  	return err
    65  }
    66  
    67  // Serve creates a http.Server and calls ServeTLS with the given listener,
    68  // certFile and keyFile. It wraps handler with RecoverAndLogHandler and a
    69  // handler, which limits the max body size to config.MaxBodyBytes.
    70  //
    71  // NOTE: This function blocks - you may want to call it in a go-routine.
    72  func ServeTLS(
    73  	listener net.Listener,
    74  	handler http.Handler,
    75  	certFile, keyFile string,
    76  	logger log.Logger,
    77  	config *Config,
    78  ) error {
    79  	logger.Info("serve tls", "msg", log.NewLazySprintf("Starting RPC HTTPS server on %s (cert: %q, key: %q)",
    80  		listener.Addr(), certFile, keyFile))
    81  	s := &http.Server{
    82  		Handler:           RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger),
    83  		ReadTimeout:       config.ReadTimeout,
    84  		ReadHeaderTimeout: config.ReadTimeout,
    85  		WriteTimeout:      config.WriteTimeout,
    86  		MaxHeaderBytes:    config.MaxHeaderBytes,
    87  	}
    88  	err := s.ServeTLS(listener, certFile, keyFile)
    89  
    90  	logger.Error("RPC HTTPS server stopped", "err", err)
    91  	return err
    92  }
    93  
    94  // WriteRPCResponseHTTPError marshals res as JSON (with indent) and writes it
    95  // to w.
    96  //
    97  // source: https://www.jsonrpc.org/historical/json-rpc-over-http.html
    98  func WriteRPCResponseHTTPError(
    99  	w http.ResponseWriter,
   100  	httpCode int,
   101  	res types.RPCResponse,
   102  ) error {
   103  	if res.Error == nil {
   104  		panic("tried to write http error response without RPC error")
   105  	}
   106  
   107  	jsonBytes, err := json.Marshal(res)
   108  	if err != nil {
   109  		return fmt.Errorf("json marshal: %w", err)
   110  	}
   111  
   112  	w.Header().Set("Content-Type", "application/json")
   113  	w.WriteHeader(httpCode)
   114  	_, err = w.Write(jsonBytes)
   115  	return err
   116  }
   117  
   118  // WriteRPCResponseHTTP marshals res as JSON (with indent) and writes it to w.
   119  func WriteRPCResponseHTTP(w http.ResponseWriter, res ...types.RPCResponse) error {
   120  	return writeRPCResponseHTTP(w, []httpHeader{}, res...)
   121  }
   122  
   123  // WriteCacheableRPCResponseHTTP marshals res as JSON (with indent) and writes
   124  // it to w. Adds cache-control to the response header and sets the expiry to
   125  // one day.
   126  func WriteCacheableRPCResponseHTTP(w http.ResponseWriter, res ...types.RPCResponse) error {
   127  	return writeRPCResponseHTTP(w, []httpHeader{{"Cache-Control", "public, max-age=86400"}}, res...)
   128  }
   129  
   130  type httpHeader struct {
   131  	name  string
   132  	value string
   133  }
   134  
   135  func writeRPCResponseHTTP(w http.ResponseWriter, headers []httpHeader, res ...types.RPCResponse) error {
   136  	var v interface{}
   137  	if len(res) == 1 {
   138  		v = res[0]
   139  	} else {
   140  		v = res
   141  	}
   142  
   143  	jsonBytes, err := json.Marshal(v)
   144  	if err != nil {
   145  		return fmt.Errorf("json marshal: %w", err)
   146  	}
   147  	w.Header().Set("Content-Type", "application/json")
   148  	for _, header := range headers {
   149  		w.Header().Set(header.name, header.value)
   150  	}
   151  	w.WriteHeader(200)
   152  	_, err = w.Write(jsonBytes)
   153  	return err
   154  }
   155  
   156  //-----------------------------------------------------------------------------
   157  
   158  // RecoverAndLogHandler wraps an HTTP handler, adding error logging.
   159  // If the inner function panics, the outer function recovers, logs, sends an
   160  // HTTP 500 error response.
   161  func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler {
   162  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   163  		// Wrap the ResponseWriter to remember the status
   164  		rww := &responseWriterWrapper{-1, w}
   165  		begin := time.Now()
   166  
   167  		rww.Header().Set("X-Server-Time", fmt.Sprintf("%v", begin.Unix()))
   168  
   169  		defer func() {
   170  			// Handle any panics in the panic handler below. Does not use the logger, since we want
   171  			// to avoid any further panics. However, we try to return a 500, since it otherwise
   172  			// defaults to 200 and there is no other way to terminate the connection. If that
   173  			// should panic for whatever reason then the Go HTTP server will handle it and
   174  			// terminate the connection - panicing is the de-facto and only way to get the Go HTTP
   175  			// server to terminate the request and close the connection/stream:
   176  			// https://github.com/golang/go/issues/17790#issuecomment-258481416
   177  			if e := recover(); e != nil {
   178  				fmt.Fprintf(os.Stderr, "Panic during RPC panic recovery: %v\n%v\n", e, string(debug.Stack()))
   179  				w.WriteHeader(500)
   180  			}
   181  		}()
   182  
   183  		defer func() {
   184  			// Send a 500 error if a panic happens during a handler.
   185  			// Without this, Chrome & Firefox were retrying aborted ajax requests,
   186  			// at least to my localhost.
   187  			if e := recover(); e != nil {
   188  				// If RPCResponse
   189  				if res, ok := e.(types.RPCResponse); ok {
   190  					if wErr := WriteRPCResponseHTTP(rww, res); wErr != nil {
   191  						logger.Error("failed to write response", "res", res, "err", wErr)
   192  					}
   193  				} else {
   194  					// Panics can contain anything, attempt to normalize it as an error.
   195  					var err error
   196  					switch e := e.(type) {
   197  					case error:
   198  						err = e
   199  					case string:
   200  						err = errors.New(e)
   201  					case fmt.Stringer:
   202  						err = errors.New(e.String())
   203  					default:
   204  					}
   205  
   206  					logger.Error("panic in RPC HTTP handler", "err", e, "stack", string(debug.Stack()))
   207  
   208  					res := types.RPCInternalError(types.JSONRPCIntID(-1), err)
   209  					if wErr := WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, res); wErr != nil {
   210  						logger.Error("failed to write response", "res", res, "err", wErr)
   211  					}
   212  				}
   213  			}
   214  
   215  			// Finally, log.
   216  			durationMS := time.Since(begin).Nanoseconds() / 1000000
   217  			if rww.Status == -1 {
   218  				rww.Status = 200
   219  			}
   220  			logger.Debug("served RPC HTTP response",
   221  				"method", r.Method,
   222  				"url", r.URL,
   223  				"status", rww.Status,
   224  				"duration", durationMS,
   225  				"remoteAddr", r.RemoteAddr,
   226  			)
   227  		}()
   228  
   229  		handler.ServeHTTP(rww, r)
   230  	})
   231  }
   232  
   233  // Remember the status for logging
   234  type responseWriterWrapper struct {
   235  	Status int
   236  	http.ResponseWriter
   237  }
   238  
   239  func (w *responseWriterWrapper) WriteHeader(status int) {
   240  	w.Status = status
   241  	w.ResponseWriter.WriteHeader(status)
   242  }
   243  
   244  // implements http.Hijacker
   245  func (w *responseWriterWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) {
   246  	return w.ResponseWriter.(http.Hijacker).Hijack()
   247  }
   248  
   249  type maxBytesHandler struct {
   250  	h http.Handler
   251  	n int64
   252  }
   253  
   254  func (h maxBytesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   255  	r.Body = http.MaxBytesReader(w, r.Body, h.n)
   256  	h.h.ServeHTTP(w, r)
   257  }
   258  
   259  // Listen starts a new net.Listener on the given address.
   260  // It returns an error if the address is invalid or the call to Listen() fails.
   261  func Listen(addr string, config *Config) (listener net.Listener, err error) {
   262  	parts := strings.SplitN(addr, "://", 2)
   263  	if len(parts) != 2 {
   264  		return nil, fmt.Errorf(
   265  			"invalid listening address %s (use fully formed addresses, including the tcp:// or unix:// prefix)",
   266  			addr,
   267  		)
   268  	}
   269  	proto, addr := parts[0], parts[1]
   270  	listener, err = net.Listen(proto, addr)
   271  	if err != nil {
   272  		return nil, fmt.Errorf("failed to listen on %v: %v", addr, err)
   273  	}
   274  	if config.MaxOpenConnections > 0 {
   275  		listener = netutil.LimitListener(listener, config.MaxOpenConnections)
   276  	}
   277  
   278  	return listener, nil
   279  }