github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/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 "strings" 14 "time" 15 16 "golang.org/x/net/netutil" 17 18 "github.com/line/ostracon/libs/log" 19 types "github.com/line/ostracon/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 // mirrors http.Server#IdleTimeout 31 IdleTimeout time.Duration 32 // MaxBodyBytes controls the maximum number of bytes the 33 // server will read parsing the request body. 34 MaxBodyBytes int64 35 // mirrors http.Server#MaxHeaderBytes 36 MaxHeaderBytes int 37 } 38 39 // DefaultConfig returns a default configuration. 40 func DefaultConfig() *Config { 41 return &Config{ 42 MaxOpenConnections: 0, // unlimited 43 ReadTimeout: 10 * time.Second, 44 WriteTimeout: 10 * time.Second, 45 IdleTimeout: 60 * time.Second, 46 MaxBodyBytes: int64(1000000), // 1MB 47 MaxHeaderBytes: 1 << 20, // same as the net/http default 48 } 49 } 50 51 // Serve creates a http.Server and calls Serve with the given listener. It 52 // wraps handler with RecoverAndLogHandler and a handler, which limits the max 53 // body size to config.MaxBodyBytes. 54 // 55 // NOTE: This function blocks - you may want to call it in a go-routine. 56 func Serve(listener net.Listener, handler http.Handler, logger log.Logger, config *Config) error { 57 logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s", listener.Addr())) 58 s := &http.Server{ 59 Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger), 60 ReadTimeout: config.ReadTimeout, 61 WriteTimeout: config.WriteTimeout, 62 IdleTimeout: config.IdleTimeout, 63 MaxHeaderBytes: config.MaxHeaderBytes, 64 } 65 err := s.Serve(listener) 66 logger.Info("RPC HTTP server stopped", "err", err) 67 return err 68 } 69 70 // Serve creates a http.Server and calls ServeTLS with the given listener, 71 // certFile and keyFile. It wraps handler with RecoverAndLogHandler and a 72 // handler, which limits the max body size to config.MaxBodyBytes. 73 // 74 // NOTE: This function blocks - you may want to call it in a go-routine. 75 func ServeTLS( 76 listener net.Listener, 77 handler http.Handler, 78 certFile, keyFile string, 79 logger log.Logger, 80 config *Config, 81 ) error { 82 logger.Info(fmt.Sprintf("Starting RPC HTTPS server on %s (cert: %q, key: %q)", 83 listener.Addr(), certFile, keyFile)) 84 s := &http.Server{ 85 Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger), 86 ReadTimeout: config.ReadTimeout, 87 WriteTimeout: config.WriteTimeout, 88 IdleTimeout: config.IdleTimeout, 89 MaxHeaderBytes: config.MaxHeaderBytes, 90 } 91 err := s.ServeTLS(listener, certFile, keyFile) 92 93 logger.Error("RPC HTTPS server stopped", "err", err) 94 return err 95 } 96 97 // WriteRPCResponseHTTPError marshals res as JSON (with indent) and writes it 98 // to w. 99 // 100 // source: https://www.jsonrpc.org/historical/json-rpc-over-http.html 101 func WriteRPCResponseHTTPError( 102 w http.ResponseWriter, 103 httpCode int, 104 res types.RPCResponse, 105 ) error { 106 if res.Error == nil { 107 panic("tried to write http error response without RPC error") 108 } 109 110 jsonBytes, err := json.MarshalIndent(res, "", " ") 111 if err != nil { 112 return fmt.Errorf("json marshal: %w", err) 113 } 114 115 w.Header().Set("Content-Type", "application/json") 116 w.WriteHeader(httpCode) 117 _, err = w.Write(jsonBytes) 118 return err 119 } 120 121 // WriteRPCResponseHTTP marshals res as JSON (with indent) and writes it to w. 122 func WriteRPCResponseHTTP(w http.ResponseWriter, res ...types.RPCResponse) error { 123 var v interface{} 124 if len(res) == 1 { 125 v = res[0] 126 } else { 127 v = res 128 } 129 130 jsonBytes, err := json.MarshalIndent(v, "", " ") 131 if err != nil { 132 return fmt.Errorf("json marshal: %w", err) 133 } 134 w.Header().Set("Content-Type", "application/json") 135 w.WriteHeader(200) 136 _, err = w.Write(jsonBytes) 137 return err 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 if wErr := WriteRPCResponseHTTP(rww, res); wErr != nil { 176 logger.Error("failed to write response", "res", res, "err", wErr) 177 } 178 } else { 179 // Panics can contain anything, attempt to normalize it as an error. 180 var err error 181 switch e := e.(type) { 182 case error: 183 err = e 184 case string: 185 err = errors.New(e) 186 case fmt.Stringer: 187 err = errors.New(e.String()) 188 default: 189 } 190 191 logger.Error("panic in RPC HTTP handler", "err", e, "stack", string(debug.Stack())) 192 193 res := types.RPCInternalError(types.JSONRPCIntID(-1), err) 194 if wErr := WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, res); wErr != nil { 195 logger.Error("failed to write response", "res", res, "err", wErr) 196 } 197 } 198 } 199 200 // Finally, log. 201 durationMS := time.Since(begin).Nanoseconds() / 1000000 202 if rww.Status == -1 { 203 rww.Status = 200 204 } 205 logger.Debug("served RPC HTTP response", 206 "method", r.Method, 207 "url", r.URL, 208 "status", rww.Status, 209 "duration", durationMS, 210 "remoteAddr", r.RemoteAddr, 211 ) 212 }() 213 214 handler.ServeHTTP(rww, r) 215 }) 216 } 217 218 // Remember the status for logging 219 type responseWriterWrapper struct { 220 Status int 221 http.ResponseWriter 222 } 223 224 func (w *responseWriterWrapper) WriteHeader(status int) { 225 w.Status = status 226 w.ResponseWriter.WriteHeader(status) 227 } 228 229 // implements http.Hijacker 230 func (w *responseWriterWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) { 231 return w.ResponseWriter.(http.Hijacker).Hijack() 232 } 233 234 type maxBytesHandler struct { 235 h http.Handler 236 n int64 237 } 238 239 func (h maxBytesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 240 r.Body = http.MaxBytesReader(w, r.Body, h.n) 241 h.h.ServeHTTP(w, r) 242 } 243 244 // Listen starts a new net.Listener on the given address. 245 // It returns an error if the address is invalid or the call to Listen() fails. 246 func Listen(addr string, config *Config) (listener net.Listener, err error) { 247 parts := strings.SplitN(addr, "://", 2) 248 if len(parts) != 2 { 249 return nil, fmt.Errorf( 250 "invalid listening address %s (use fully formed addresses, including the tcp:// or unix:// prefix)", 251 addr, 252 ) 253 } 254 proto, addr := parts[0], parts[1] 255 listener, err = net.Listen(proto, addr) 256 if err != nil { 257 return nil, fmt.Errorf("failed to listen on %v: %v", addr, err) 258 } 259 if config.MaxOpenConnections > 0 { 260 listener = netutil.LimitListener(listener, config.MaxOpenConnections) 261 } 262 263 return listener, nil 264 }