github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/rpc/lib/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  	"runtime/debug"
    12  	"time"
    13  
    14  	"github.com/hyperledger/burrow/logging"
    15  	"github.com/hyperledger/burrow/logging/structure"
    16  	"github.com/hyperledger/burrow/rpc/lib/types"
    17  )
    18  
    19  func StartHTTPServer(listener net.Listener, handler http.Handler, logger *logging.Logger) (*http.Server, error) {
    20  	logger.InfoMsg("Starting RPC HTTP server", "listen_address", listener.Addr().String())
    21  
    22  	server := &http.Server{Handler: RecoverAndLogHandler(handler, logger)}
    23  
    24  	go func() {
    25  		err := server.Serve(listener)
    26  		logger.TraceMsg("RPC HTTP server stopped", structure.ErrorKey, err)
    27  	}()
    28  
    29  	return server, nil
    30  }
    31  
    32  func WriteRPCResponseHTTP(w http.ResponseWriter, res types.RPCResponse) {
    33  	jsonBytes, err := json.MarshalIndent(res, "", "  ")
    34  	if err != nil {
    35  		panic(err)
    36  	}
    37  	w.Header().Set("Content-Type", "application/json")
    38  	w.WriteHeader(res.Error.HTTPStatusCode())
    39  	w.Write(jsonBytes) // nolint: errcheck, gas
    40  }
    41  
    42  //-----------------------------------------------------------------------------
    43  
    44  // Wraps an HTTP handler, adding error logging.
    45  // If the inner function panics, the outer function recovers, logs, sends an
    46  // HTTP 500 error response.
    47  func RecoverAndLogHandler(handler http.Handler, logger *logging.Logger) http.Handler {
    48  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    49  		// Wrap the ResponseWriter to remember the status
    50  		rww := &ResponseWriterWrapper{-1, w}
    51  		begin := time.Now()
    52  
    53  		// Common headers
    54  		origin := r.Header.Get("Origin")
    55  		rww.Header().Set("Access-Control-Allow-Origin", origin)
    56  		rww.Header().Set("Access-Control-Allow-Credentials", "true")
    57  		rww.Header().Set("Access-Control-Expose-Headers", "X-Server-Time")
    58  		rww.Header().Set("X-Server-Time", fmt.Sprintf("%v", begin.Unix()))
    59  
    60  		defer func() {
    61  			// Send a 500 error if a panic happens during a handler.
    62  			// Without this, Chrome & Firefox were retrying aborted ajax requests,
    63  			// at least to my localhost.
    64  			if e := recover(); e != nil {
    65  				// If RPCResponse
    66  				if res, ok := e.(types.RPCResponse); ok {
    67  					WriteRPCResponseHTTP(rww, res)
    68  				} else {
    69  					// For the rest,
    70  					err := errors.New(fmt.Sprint(e))
    71  					logger.TraceMsg("Panic in RPC HTTP handler",
    72  						structure.ErrorKey, err,
    73  						"stack", string(debug.Stack()))
    74  					rww.WriteHeader(http.StatusInternalServerError)
    75  					WriteRPCResponseHTTP(rww, types.RPCInternalError("", err))
    76  				}
    77  			}
    78  
    79  			// Finally, log.
    80  			duration := time.Since(begin)
    81  			if rww.Status == -1 {
    82  				rww.Status = 200
    83  			}
    84  			logger.InfoMsg("Served RPC HTTP response",
    85  				"method", r.Method,
    86  				"url", r.URL,
    87  				"status", rww.Status,
    88  				"duration", duration,
    89  				"remote_address", r.RemoteAddr,
    90  			)
    91  		}()
    92  
    93  		handler.ServeHTTP(rww, r)
    94  	})
    95  }
    96  
    97  // Remember the status for logging
    98  type ResponseWriterWrapper struct {
    99  	Status int
   100  	http.ResponseWriter
   101  }
   102  
   103  func (w *ResponseWriterWrapper) WriteHeader(status int) {
   104  	w.Status = status
   105  	w.ResponseWriter.WriteHeader(status)
   106  }
   107  
   108  // implements http.Hijacker
   109  func (w *ResponseWriterWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) {
   110  	return w.ResponseWriter.(http.Hijacker).Hijack()
   111  }