github.com/okex/exchain@v1.8.0/libs/tendermint/rpc/jsonrpc/server/http_server.go (about)

     1  // Commons for HTTP handling
     2  package server
     3  
     4  import (
     5  	"bufio"
     6  	"encoding/json"
     7  	"fmt"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"runtime/debug"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/pkg/errors"
    16  	"golang.org/x/net/netutil"
    17  
    18  	"github.com/okex/exchain/libs/tendermint/libs/log"
    19  	types "github.com/okex/exchain/libs/tendermint/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(fmt.Sprintf("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  		WriteTimeout:   config.WriteTimeout,
    59  		MaxHeaderBytes: config.MaxHeaderBytes,
    60  	}
    61  	err := s.Serve(listener)
    62  	logger.Info("RPC HTTP server stopped", "err", err)
    63  	return err
    64  }
    65  
    66  // Serve creates a http.Server and calls ServeTLS with the given listener,
    67  // certFile and keyFile. It wraps handler with RecoverAndLogHandler and a
    68  // handler, which limits the max body size to config.MaxBodyBytes.
    69  //
    70  // NOTE: This function blocks - you may want to call it in a go-routine.
    71  func ServeTLS(
    72  	listener net.Listener,
    73  	handler http.Handler,
    74  	certFile, keyFile string,
    75  	logger log.Logger,
    76  	config *Config,
    77  ) error {
    78  	logger.Info(fmt.Sprintf("Starting RPC HTTPS server on %s (cert: %q, key: %q)",
    79  		listener.Addr(), certFile, keyFile))
    80  	s := &http.Server{
    81  		Handler:        RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger),
    82  		ReadTimeout:    config.ReadTimeout,
    83  		WriteTimeout:   config.WriteTimeout,
    84  		MaxHeaderBytes: config.MaxHeaderBytes,
    85  	}
    86  	err := s.ServeTLS(listener, certFile, keyFile)
    87  
    88  	logger.Error("RPC HTTPS server stopped", "err", err)
    89  	return err
    90  }
    91  
    92  func WriteRPCResponseHTTPError(
    93  	w http.ResponseWriter,
    94  	httpCode int,
    95  	res types.RPCResponse,
    96  ) {
    97  	jsonBytes, err := json.MarshalIndent(res, "", "  ")
    98  	if err != nil {
    99  		panic(err)
   100  	}
   101  
   102  	w.Header().Set("Content-Type", "application/json")
   103  	w.WriteHeader(httpCode)
   104  	if _, err := w.Write(jsonBytes); err != nil {
   105  		panic(err)
   106  	}
   107  }
   108  
   109  func WriteRPCResponseHTTP(w http.ResponseWriter, res types.RPCResponse) {
   110  	jsonBytes, err := json.MarshalIndent(res, "", "  ")
   111  	if err != nil {
   112  		panic(err)
   113  	}
   114  	w.Header().Set("Content-Type", "application/json")
   115  	w.WriteHeader(200)
   116  	if _, err := w.Write(jsonBytes); err != nil {
   117  		panic(err)
   118  	}
   119  }
   120  
   121  // WriteRPCResponseArrayHTTP will do the same as WriteRPCResponseHTTP, except it
   122  // can write arrays of responses for batched request/response interactions via
   123  // the JSON RPC.
   124  func WriteRPCResponseArrayHTTP(w http.ResponseWriter, res []types.RPCResponse) {
   125  	if len(res) == 1 {
   126  		WriteRPCResponseHTTP(w, res[0])
   127  	} else {
   128  		jsonBytes, err := json.MarshalIndent(res, "", "  ")
   129  		if err != nil {
   130  			panic(err)
   131  		}
   132  		w.Header().Set("Content-Type", "application/json")
   133  		w.WriteHeader(200)
   134  		if _, err := w.Write(jsonBytes); err != nil {
   135  			panic(err)
   136  		}
   137  	}
   138  }
   139  
   140  //-----------------------------------------------------------------------------
   141  
   142  // RecoverAndLogHandler wraps an HTTP handler, adding error logging.
   143  // If the inner function panics, the outer function recovers, logs, sends an
   144  // HTTP 500 error response.
   145  func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler {
   146  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   147  		// Wrap the ResponseWriter to remember the status
   148  		rww := &responseWriterWrapper{-1, w}
   149  		begin := time.Now()
   150  
   151  		rww.Header().Set("X-Server-Time", fmt.Sprintf("%v", begin.Unix()))
   152  
   153  		defer func() {
   154  			// Handle any panics in the panic handler below. Does not use the logger, since we want
   155  			// to avoid any further panics. However, we try to return a 500, since it otherwise
   156  			// defaults to 200 and there is no other way to terminate the connection. If that
   157  			// should panic for whatever reason then the Go HTTP server will handle it and
   158  			// terminate the connection - panicing is the de-facto and only way to get the Go HTTP
   159  			// server to terminate the request and close the connection/stream:
   160  			// https://github.com/golang/go/issues/17790#issuecomment-258481416
   161  			if e := recover(); e != nil {
   162  				fmt.Fprintf(os.Stderr, "Panic during RPC panic recovery: %v\n%v\n", e, string(debug.Stack()))
   163  				w.WriteHeader(500)
   164  			}
   165  		}()
   166  
   167  		defer func() {
   168  			// Send a 500 error if a panic happens during a handler.
   169  			// Without this, Chrome & Firefox were retrying aborted ajax requests,
   170  			// at least to my localhost.
   171  			if e := recover(); e != nil {
   172  
   173  				// If RPCResponse
   174  				if res, ok := e.(types.RPCResponse); ok {
   175  					WriteRPCResponseHTTP(rww, res)
   176  				} else {
   177  					// Panics can contain anything, attempt to normalize it as an error.
   178  					var err error
   179  					switch e := e.(type) {
   180  					case error:
   181  						err = e
   182  					case string:
   183  						err = errors.New(e)
   184  					case fmt.Stringer:
   185  						err = errors.New(e.String())
   186  					default:
   187  					}
   188  
   189  					logger.Error(
   190  						"Panic in RPC HTTP handler", "err", e, "stack",
   191  						string(debug.Stack()),
   192  					)
   193  					WriteRPCResponseHTTPError(
   194  						rww,
   195  						http.StatusInternalServerError,
   196  						types.RPCInternalError(types.JSONRPCIntID(-1), err),
   197  					)
   198  				}
   199  			}
   200  
   201  			// Finally, log.
   202  			durationMS := time.Since(begin).Nanoseconds() / 1000000
   203  			if rww.Status == -1 {
   204  				rww.Status = 200
   205  			}
   206  			logger.Info("Served RPC HTTP response",
   207  				"method", r.Method, "url", r.URL,
   208  				"status", rww.Status, "duration", durationMS,
   209  				"remoteAddr", r.RemoteAddr,
   210  			)
   211  		}()
   212  
   213  		handler.ServeHTTP(rww, r)
   214  	})
   215  }
   216  
   217  // Remember the status for logging
   218  type responseWriterWrapper struct {
   219  	Status int
   220  	http.ResponseWriter
   221  }
   222  
   223  func (w *responseWriterWrapper) WriteHeader(status int) {
   224  	w.Status = status
   225  	w.ResponseWriter.WriteHeader(status)
   226  }
   227  
   228  // implements http.Hijacker
   229  func (w *responseWriterWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) {
   230  	return w.ResponseWriter.(http.Hijacker).Hijack()
   231  }
   232  
   233  type maxBytesHandler struct {
   234  	h http.Handler
   235  	n int64
   236  }
   237  
   238  func (h maxBytesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   239  	r.Body = http.MaxBytesReader(w, r.Body, h.n)
   240  	h.h.ServeHTTP(w, r)
   241  }
   242  
   243  // Listen starts a new net.Listener on the given address.
   244  // It returns an error if the address is invalid or the call to Listen() fails.
   245  func Listen(addr string, config *Config) (listener net.Listener, err error) {
   246  	parts := strings.SplitN(addr, "://", 2)
   247  	if len(parts) != 2 {
   248  		return nil, errors.Errorf(
   249  			"invalid listening address %s (use fully formed addresses, including the tcp:// or unix:// prefix)",
   250  			addr,
   251  		)
   252  	}
   253  	proto, addr := parts[0], parts[1]
   254  	listener, err = net.Listen(proto, addr)
   255  	if err != nil {
   256  		return nil, errors.Errorf("failed to listen on %v: %v", addr, err)
   257  	}
   258  	if config.MaxOpenConnections > 0 {
   259  		listener = netutil.LimitListener(listener, config.MaxOpenConnections)
   260  	}
   261  
   262  	return listener, nil
   263  }