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