github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/rpc/jsonrpc/server/http_server.go (about)

     1  // Commons for HTTP handling
     2  package server
     3  
     4  import (
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  	"net/http"
    11  	"runtime/debug"
    12  	"strings"
    13  	"time"
    14  
    15  	"golang.org/x/net/netutil"
    16  
    17  	"github.com/ari-anchor/sei-tendermint/libs/log"
    18  	rpctypes "github.com/ari-anchor/sei-tendermint/rpc/jsonrpc/types"
    19  )
    20  
    21  // Config is a RPC server configuration.
    22  type Config struct {
    23  	// The maximum number of connections that will be accepted by the listener.
    24  	// See https://godoc.org/golang.org/x/net/netutil#LimitListener
    25  	MaxOpenConnections int
    26  
    27  	// Used to set the HTTP server's per-request read timeout.
    28  	// See https://godoc.org/net/http#Server.ReadTimeout
    29  	ReadTimeout time.Duration
    30  
    31  	// Used to set the HTTP server's per-request write timeout.  Note that this
    32  	// affects ALL methods on the server, so it should not be set too low. This
    33  	// should be used as a safety valve, not a resource-control timeout.
    34  	//
    35  	// See https://godoc.org/net/http#Server.WriteTimeout
    36  	WriteTimeout time.Duration
    37  
    38  	// Controls the maximum number of bytes the server will read parsing the
    39  	// request body.
    40  	MaxBodyBytes int64
    41  
    42  	// Controls the maximum size of a request header.
    43  	// See https://godoc.org/net/http#Server.MaxHeaderBytes
    44  	MaxHeaderBytes int
    45  }
    46  
    47  // DefaultConfig returns a default configuration.
    48  func DefaultConfig() *Config {
    49  	return &Config{
    50  		MaxOpenConnections: 0, // unlimited
    51  		ReadTimeout:        10 * time.Second,
    52  		WriteTimeout:       0,       // no default timeout
    53  		MaxBodyBytes:       1000000, // 1MB
    54  		MaxHeaderBytes:     1 << 20, // same as the net/http default
    55  	}
    56  }
    57  
    58  // Serve creates a http.Server and calls Serve with the given listener. It
    59  // wraps handler to recover panics and limit the request body size.
    60  func Serve(ctx context.Context, listener net.Listener, handler http.Handler, logger log.Logger, config *Config) error {
    61  	logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s", listener.Addr()))
    62  	h := recoverAndLogHandler(MaxBytesHandler(handler, config.MaxBodyBytes), logger)
    63  	s := &http.Server{
    64  		Handler:        h,
    65  		ReadTimeout:    config.ReadTimeout,
    66  		WriteTimeout:   config.WriteTimeout,
    67  		MaxHeaderBytes: config.MaxHeaderBytes,
    68  	}
    69  	sig := make(chan struct{})
    70  	go func() {
    71  		select {
    72  		case <-ctx.Done():
    73  			sctx, cancel := context.WithTimeout(context.Background(), time.Second)
    74  			defer cancel()
    75  			_ = s.Shutdown(sctx)
    76  		case <-sig:
    77  		}
    78  	}()
    79  
    80  	if err := s.Serve(listener); err != nil {
    81  		logger.Info("RPC HTTP server stopped", "err", err)
    82  		close(sig)
    83  		return err
    84  	}
    85  	return nil
    86  }
    87  
    88  // Serve creates a http.Server and calls ServeTLS with the given listener,
    89  // certFile and keyFile. It wraps handler to recover panics and limit the
    90  // request body size.
    91  func ServeTLS(ctx context.Context, listener net.Listener, handler http.Handler, certFile, keyFile string, logger log.Logger, config *Config) error {
    92  	logger.Info("Starting RPC HTTPS server",
    93  		"listenterAddr", listener.Addr(),
    94  		"certFile", certFile,
    95  		"keyFile", keyFile)
    96  
    97  	s := &http.Server{
    98  		Handler:        recoverAndLogHandler(MaxBytesHandler(handler, config.MaxBodyBytes), logger),
    99  		ReadTimeout:    config.ReadTimeout,
   100  		WriteTimeout:   config.WriteTimeout,
   101  		MaxHeaderBytes: config.MaxHeaderBytes,
   102  	}
   103  	sig := make(chan struct{})
   104  	go func() {
   105  		select {
   106  		case <-ctx.Done():
   107  			sctx, cancel := context.WithTimeout(context.Background(), time.Second)
   108  			defer cancel()
   109  			_ = s.Shutdown(sctx)
   110  		case <-sig:
   111  		}
   112  	}()
   113  
   114  	if err := s.ServeTLS(listener, certFile, keyFile); err != nil {
   115  		logger.Error("RPC HTTPS server stopped", "err", err)
   116  		close(sig)
   117  		return err
   118  	}
   119  	return nil
   120  }
   121  
   122  // writeInternalError writes an internal server error (500) to w with the text
   123  // of err in the body. This is a fallback used when a handler is unable to
   124  // write the expected response.
   125  func writeInternalError(w http.ResponseWriter, err error) {
   126  	w.Header().Set("Content-Type", "text/plain")
   127  	w.WriteHeader(http.StatusInternalServerError)
   128  	fmt.Fprintln(w, err.Error())
   129  }
   130  
   131  // writeHTTPResponse writes a JSON-RPC response to w. If rsp encodes an error,
   132  // the response body is its error object; otherwise its responses is the result.
   133  //
   134  // Unless there is an error encoding the response, the status is 200 OK.
   135  func writeHTTPResponse(w http.ResponseWriter, log log.Logger, rsp rpctypes.RPCResponse) {
   136  	var body []byte
   137  	var err error
   138  	if rsp.Error != nil {
   139  		body, err = json.Marshal(rsp.Error)
   140  	} else {
   141  		body = rsp.Result
   142  	}
   143  	if err != nil {
   144  		log.Error("Error encoding RPC response: %w", err)
   145  		writeInternalError(w, err)
   146  		return
   147  	}
   148  	w.Header().Set("Content-Type", "application/json")
   149  	w.WriteHeader(http.StatusOK)
   150  	_, _ = w.Write(body)
   151  }
   152  
   153  // writeRPCResponse writes one or more JSON-RPC responses to w. A single
   154  // response is encoded as an object, otherwise the response is sent as a batch
   155  // (array) of response objects.
   156  //
   157  // Unless there is an error encoding the responses, the status is 200 OK.
   158  func writeRPCResponse(w http.ResponseWriter, log log.Logger, rsps ...rpctypes.RPCResponse) {
   159  	var body []byte
   160  	var err error
   161  	if len(rsps) == 1 {
   162  		body, err = json.Marshal(rsps[0])
   163  	} else {
   164  		body, err = json.Marshal(rsps)
   165  	}
   166  	if err != nil {
   167  		log.Error("Error encoding RPC response: %w", err)
   168  		writeInternalError(w, err)
   169  		return
   170  	}
   171  	w.Header().Set("Content-Type", "application/json")
   172  	w.WriteHeader(http.StatusOK)
   173  	_, _ = w.Write(body)
   174  }
   175  
   176  //-----------------------------------------------------------------------------
   177  
   178  // recoverAndLogHandler wraps an HTTP handler, adding error logging.  If the
   179  // inner handler panics, the wrapper recovers, logs, sends an HTTP 500 error
   180  // response to the client.
   181  func recoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler {
   182  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   183  		// Capture the HTTP status written by the handler.
   184  		var httpStatus int
   185  		rww := newStatusWriter(w, &httpStatus)
   186  
   187  		// Recover panics from inside handler and try to send the client
   188  		// 500 Internal server error. If the handler panicked after already
   189  		// sending a (partial) response, this is a no-op.
   190  		defer func() {
   191  			if v := recover(); v != nil {
   192  				var err error
   193  				switch e := v.(type) {
   194  				case error:
   195  					err = e
   196  				case string:
   197  					err = errors.New(e)
   198  				case fmt.Stringer:
   199  					err = errors.New(e.String())
   200  				default:
   201  					err = fmt.Errorf("panic with value %v", v)
   202  				}
   203  
   204  				logger.Error("Panic in RPC HTTP handler",
   205  					"err", err, "stack", string(debug.Stack()))
   206  				writeInternalError(rww, err)
   207  			}
   208  		}()
   209  
   210  		// Log timing and response information from the handler.
   211  		begin := time.Now()
   212  		defer func() {
   213  			elapsed := time.Since(begin)
   214  			logger.Debug("served RPC HTTP response",
   215  				"method", r.Method,
   216  				"url", r.URL,
   217  				"status", httpStatus,
   218  				"duration-sec", elapsed.Seconds(),
   219  				"remoteAddr", r.RemoteAddr,
   220  			)
   221  		}()
   222  
   223  		rww.Header().Set("X-Server-Time", fmt.Sprintf("%v", begin.Unix()))
   224  		handler.ServeHTTP(rww, r)
   225  	})
   226  }
   227  
   228  // MaxBytesHandler wraps h in a handler that limits the size of the request
   229  // body to at most maxBytes. If maxBytes <= 0, the request body is not limited.
   230  func MaxBytesHandler(h http.Handler, maxBytes int64) http.Handler {
   231  	if maxBytes <= 0 {
   232  		return h
   233  	}
   234  	return maxBytesHandler{handler: h, maxBytes: maxBytes}
   235  }
   236  
   237  type maxBytesHandler struct {
   238  	handler  http.Handler
   239  	maxBytes int64
   240  }
   241  
   242  func (h maxBytesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   243  	req.Body = http.MaxBytesReader(w, req.Body, h.maxBytes)
   244  	h.handler.ServeHTTP(w, req)
   245  }
   246  
   247  // newStatusWriter wraps an http.ResponseWriter to capture the HTTP status code
   248  // in *code.
   249  func newStatusWriter(w http.ResponseWriter, code *int) statusWriter {
   250  	return statusWriter{
   251  		ResponseWriter: w,
   252  		Hijacker:       w.(http.Hijacker),
   253  		code:           code,
   254  	}
   255  }
   256  
   257  type statusWriter struct {
   258  	http.ResponseWriter
   259  	http.Hijacker // to support websocket upgrade
   260  
   261  	code *int
   262  }
   263  
   264  // WriteHeader implements part of http.ResponseWriter. It delegates to the
   265  // wrapped writer, and as a side effect captures the written code.
   266  //
   267  // Note that if a request does not explicitly call WriteHeader, the code will
   268  // not be updated.
   269  func (w statusWriter) WriteHeader(code int) {
   270  	*w.code = code
   271  	w.ResponseWriter.WriteHeader(code)
   272  }
   273  
   274  // Listen starts a new net.Listener on the given address.
   275  // It returns an error if the address is invalid or the call to Listen() fails.
   276  func Listen(addr string, maxOpenConnections int) (listener net.Listener, err error) {
   277  	parts := strings.SplitN(addr, "://", 2)
   278  	if len(parts) != 2 {
   279  		return nil, fmt.Errorf(
   280  			"invalid listening address %s (use fully formed addresses, including the tcp:// or unix:// prefix)",
   281  			addr,
   282  		)
   283  	}
   284  	proto, addr := parts[0], parts[1]
   285  	listener, err = net.Listen(proto, addr)
   286  	if err != nil {
   287  		return nil, fmt.Errorf("failed to listen on %v: %v", addr, err)
   288  	}
   289  	if maxOpenConnections > 0 {
   290  		listener = netutil.LimitListener(listener, maxOpenConnections)
   291  	}
   292  
   293  	return listener, nil
   294  }