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 }