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 }