github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/rpc/lib/server/http_server.go (about)

     1  // Commons for HTTP handling
     2  package rpcserver
     3  
     4  import (
     5  	"bufio"
     6  	"encoding/json"
     7  	"fmt"
     8  	"log/slog"
     9  	"net"
    10  	"net/http"
    11  	"runtime/debug"
    12  	"strings"
    13  	"time"
    14  
    15  	"golang.org/x/net/netutil"
    16  
    17  	types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types"
    18  	"github.com/gnolang/gno/tm2/pkg/errors"
    19  )
    20  
    21  // Config is a RPC server configuration.
    22  type Config struct {
    23  	// see netutil.LimitListener
    24  	MaxOpenConnections int
    25  	// mirrors http.Server#ReadTimeout
    26  	ReadTimeout time.Duration
    27  	// mirrors http.Server#WriteTimeout
    28  	WriteTimeout time.Duration
    29  	// MaxBodyBytes controls the maximum number of bytes the
    30  	// server will read parsing the request body.
    31  	MaxBodyBytes int64
    32  	// mirrors http.Server#MaxHeaderBytes
    33  	MaxHeaderBytes int
    34  }
    35  
    36  // DefaultConfig returns a default configuration.
    37  func DefaultConfig() *Config {
    38  	return &Config{
    39  		MaxOpenConnections: 0, // unlimited
    40  		ReadTimeout:        10 * time.Second,
    41  		WriteTimeout:       10 * time.Second,
    42  		MaxBodyBytes:       int64(5000000), // 5MB
    43  		MaxHeaderBytes:     1 << 20,        // same as the net/http default
    44  	}
    45  }
    46  
    47  // StartHTTPServer takes a listener and starts an HTTP server with the given handler.
    48  // It wraps handler with RecoverAndLogHandler.
    49  // NOTE: This function blocks - you may want to call it in a go-routine.
    50  func StartHTTPServer(listener net.Listener, handler http.Handler, logger *slog.Logger, config *Config) error {
    51  	logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s", listener.Addr()))
    52  	s := &http.Server{
    53  		Handler:           RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger),
    54  		ReadTimeout:       config.ReadTimeout,
    55  		ReadHeaderTimeout: 60 * time.Second,
    56  		WriteTimeout:      config.WriteTimeout,
    57  		MaxHeaderBytes:    config.MaxHeaderBytes,
    58  	}
    59  	err := s.Serve(listener)
    60  	logger.Info("RPC HTTP server stopped", "err", err)
    61  	return err
    62  }
    63  
    64  // StartHTTPAndTLSServer takes a listener and starts an HTTPS server with the given handler.
    65  // It wraps handler with RecoverAndLogHandler.
    66  // NOTE: This function blocks - you may want to call it in a go-routine.
    67  func StartHTTPAndTLSServer(
    68  	listener net.Listener,
    69  	handler http.Handler,
    70  	certFile, keyFile string,
    71  	logger *slog.Logger,
    72  	config *Config,
    73  ) error {
    74  	logger.Info(fmt.Sprintf("Starting RPC HTTPS server on %s (cert: %q, key: %q)",
    75  		listener.Addr(), certFile, keyFile))
    76  	s := &http.Server{
    77  		Handler:           RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger),
    78  		ReadTimeout:       config.ReadTimeout,
    79  		ReadHeaderTimeout: 60 * time.Second,
    80  		WriteTimeout:      config.WriteTimeout,
    81  		MaxHeaderBytes:    config.MaxHeaderBytes,
    82  	}
    83  	err := s.ServeTLS(listener, certFile, keyFile)
    84  
    85  	logger.Error("RPC HTTPS server stopped", "err", err)
    86  	return err
    87  }
    88  
    89  func WriteRPCResponseHTTPError(
    90  	w http.ResponseWriter,
    91  	httpCode int,
    92  	res types.RPCResponse,
    93  ) {
    94  	jsonBytes, err := json.MarshalIndent(res, "", "  ")
    95  	if err != nil {
    96  		panic(err)
    97  	}
    98  
    99  	w.Header().Set("Content-Type", "application/json")
   100  	w.WriteHeader(httpCode)
   101  	if _, err := w.Write(jsonBytes); err != nil {
   102  		panic(err)
   103  	}
   104  }
   105  
   106  func WriteRPCResponseHTTP(w http.ResponseWriter, res types.RPCResponse) {
   107  	jsonBytes, err := json.MarshalIndent(res, "", "  ")
   108  	if err != nil {
   109  		panic(err)
   110  	}
   111  	w.Header().Set("Content-Type", "application/json")
   112  	w.WriteHeader(200)
   113  	if _, err := w.Write(jsonBytes); err != nil {
   114  		panic(err)
   115  	}
   116  }
   117  
   118  // WriteRPCResponseArrayHTTP will do the same as WriteRPCResponseHTTP, except it
   119  // can write arrays of responses for batched request/response interactions via
   120  // the JSON RPC.
   121  func WriteRPCResponseArrayHTTP(w http.ResponseWriter, res types.RPCResponses) {
   122  	if len(res) == 1 {
   123  		WriteRPCResponseHTTP(w, res[0])
   124  	} else {
   125  		jsonBytes, err := json.MarshalIndent(res, "", "  ")
   126  		if err != nil {
   127  			panic(err)
   128  		}
   129  		w.Header().Set("Content-Type", "application/json")
   130  		w.WriteHeader(200)
   131  		if _, err := w.Write(jsonBytes); err != nil {
   132  			panic(err)
   133  		}
   134  	}
   135  }
   136  
   137  // -----------------------------------------------------------------------------
   138  
   139  // RecoverAndLogHandler wraps an HTTP handler, adding error logging.
   140  // If the inner function panics, the outer function recovers, logs, sends an
   141  // HTTP 500 error response.
   142  func RecoverAndLogHandler(handler http.Handler, logger *slog.Logger) http.Handler {
   143  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   144  		// Wrap the ResponseWriter to remember the status
   145  		rww := &ResponseWriterWrapper{-1, w}
   146  		begin := time.Now()
   147  
   148  		rww.Header().Set("X-Server-Time", fmt.Sprintf("%v", begin.Unix()))
   149  
   150  		defer func() {
   151  			// Send a 500 error if a panic happens during a handler.
   152  			// Without this, Chrome & Firefox were retrying aborted ajax requests,
   153  			// at least to my localhost.
   154  			if e := recover(); e != nil {
   155  				switch e := e.(type) {
   156  				case types.RPCResponse:
   157  					WriteRPCResponseHTTP(rww, e)
   158  
   159  				case error:
   160  					logger.Error(
   161  						"Panic in RPC HTTP handler", "err", e, "stack",
   162  						string(debug.Stack()),
   163  					)
   164  					WriteRPCResponseHTTPError(rww, http.StatusInternalServerError,
   165  						types.RPCInternalError(types.JSONRPCStringID(""), e))
   166  
   167  				default: // handle string type and any other types
   168  					logger.Error(
   169  						"Panic in RPC HTTP handler", "err", e, "stack",
   170  						string(debug.Stack()),
   171  					)
   172  					WriteRPCResponseHTTPError(rww, http.StatusInternalServerError,
   173  						types.RPCInternalError(types.JSONRPCStringID(""), fmt.Errorf("%v", e)))
   174  				}
   175  			}
   176  
   177  			// Finally, log.
   178  			durationMS := time.Since(begin).Nanoseconds() / 1000000
   179  			if rww.Status == -1 {
   180  				rww.Status = 200
   181  			}
   182  			logger.Info("Served RPC HTTP response",
   183  				"method", r.Method, "url", r.URL,
   184  				"status", rww.Status, "duration", durationMS,
   185  				"remoteAddr", r.RemoteAddr,
   186  			)
   187  		}()
   188  
   189  		handler.ServeHTTP(rww, r)
   190  	})
   191  }
   192  
   193  // Remember the status for logging
   194  type ResponseWriterWrapper struct {
   195  	Status int
   196  	http.ResponseWriter
   197  }
   198  
   199  func (w *ResponseWriterWrapper) WriteHeader(status int) {
   200  	w.Status = status
   201  	w.ResponseWriter.WriteHeader(status)
   202  }
   203  
   204  // implements http.Hijacker
   205  func (w *ResponseWriterWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) {
   206  	return w.ResponseWriter.(http.Hijacker).Hijack()
   207  }
   208  
   209  type maxBytesHandler struct {
   210  	h http.Handler
   211  	n int64
   212  }
   213  
   214  func (h maxBytesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   215  	r.Body = http.MaxBytesReader(w, r.Body, h.n)
   216  	h.h.ServeHTTP(w, r)
   217  }
   218  
   219  // Listen starts a new net.Listener on the given address.
   220  // It returns an error if the address is invalid or the call to Listen() fails.
   221  func Listen(addr string, config *Config) (listener net.Listener, err error) {
   222  	parts := strings.SplitN(addr, "://", 2)
   223  	if len(parts) != 2 {
   224  		return nil, errors.New(
   225  			"invalid listening address %s (use fully formed addresses, including the tcp:// or unix:// prefix)",
   226  			addr,
   227  		)
   228  	}
   229  	proto, addr := parts[0], parts[1]
   230  	listener, err = net.Listen(proto, addr)
   231  	if err != nil {
   232  		return nil, errors.New("failed to listen on %v: %v", addr, err)
   233  	}
   234  	if config.MaxOpenConnections > 0 {
   235  		listener = netutil.LimitListener(listener, config.MaxOpenConnections)
   236  	}
   237  
   238  	return listener, nil
   239  }