cmd/geth, node: allow configuring JSON-RPC on custom path prefix (#22184)

This change allows users to set a custom path prefix on which to mount the http-rpc
or ws-rpc handlers via the new flags --http.rpcprefix and --ws.rpcprefix.

Fixes #21826

Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
rene
2021-02-02 10:05:46 +01:00
committed by GitHub
parent e3430ac7df
commit 4eae0c6b6f
11 changed files with 322 additions and 67 deletions

View File

@ -39,12 +39,14 @@ type httpConfig struct {
Modules []string
CorsAllowedOrigins []string
Vhosts []string
prefix string // path prefix on which to mount http handler
}
// wsConfig is the JSON-RPC/Websocket configuration
type wsConfig struct {
Origins []string
Modules []string
prefix string // path prefix on which to mount ws handler
}
type rpcHandler struct {
@ -62,6 +64,7 @@ type httpServer struct {
listener net.Listener // non-nil when server is running
// HTTP RPC handler things.
httpConfig httpConfig
httpHandler atomic.Value // *rpcHandler
@ -79,6 +82,7 @@ type httpServer struct {
func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer {
h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)}
h.httpHandler.Store((*rpcHandler)(nil))
h.wsHandler.Store((*rpcHandler)(nil))
return h
@ -142,12 +146,17 @@ func (h *httpServer) start() error {
// if server is websocket only, return after logging
if h.wsAllowed() && !h.rpcAllowed() {
h.log.Info("WebSocket enabled", "url", fmt.Sprintf("ws://%v", listener.Addr()))
url := fmt.Sprintf("ws://%v", listener.Addr())
if h.wsConfig.prefix != "" {
url += h.wsConfig.prefix
}
h.log.Info("WebSocket enabled", "url", url)
return nil
}
// Log http endpoint.
h.log.Info("HTTP server started",
"endpoint", listener.Addr(),
"prefix", h.httpConfig.prefix,
"cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","),
"vhosts", strings.Join(h.httpConfig.Vhosts, ","),
)
@ -170,26 +179,60 @@ func (h *httpServer) start() error {
}
func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rpc := h.httpHandler.Load().(*rpcHandler)
if r.RequestURI == "/" {
// Serve JSON-RPC on the root path.
ws := h.wsHandler.Load().(*rpcHandler)
if ws != nil && isWebsocket(r) {
// check if ws request and serve if ws enabled
ws := h.wsHandler.Load().(*rpcHandler)
if ws != nil && isWebsocket(r) {
if checkPath(r, h.wsConfig.prefix) {
ws.ServeHTTP(w, r)
return
}
if rpc != nil {
rpc.ServeHTTP(w, r)
return
}
} else if rpc != nil {
return
}
// if http-rpc is enabled, try to serve request
rpc := h.httpHandler.Load().(*rpcHandler)
if rpc != nil {
// First try to route in the mux.
// Requests to a path below root are handled by the mux,
// which has all the handlers registered via Node.RegisterHandler.
// These are made available when RPC is enabled.
h.mux.ServeHTTP(w, r)
return
muxHandler, pattern := h.mux.Handler(r)
if pattern != "" {
muxHandler.ServeHTTP(w, r)
return
}
if checkPath(r, h.httpConfig.prefix) {
rpc.ServeHTTP(w, r)
return
}
}
w.WriteHeader(404)
w.WriteHeader(http.StatusNotFound)
}
// checkPath checks whether a given request URL matches a given path prefix.
func checkPath(r *http.Request, path string) bool {
// if no prefix has been specified, request URL must be on root
if path == "" {
return r.URL.Path == "/"
}
// otherwise, check to make sure prefix matches
return len(r.URL.Path) >= len(path) && r.URL.Path[:len(path)] == path
}
// validatePrefix checks if 'path' is a valid configuration value for the RPC prefix option.
func validatePrefix(what, path string) error {
if path == "" {
return nil
}
if path[0] != '/' {
return fmt.Errorf(`%s RPC path prefix %q does not contain leading "/"`, what, path)
}
if strings.ContainsAny(path, "?#") {
// This is just to avoid confusion. While these would match correctly (i.e. they'd
// match if URL-escaped into path), it's not easy to understand for users when
// setting that on the command line.
return fmt.Errorf("%s RPC path prefix %q contains URL meta-characters", what, path)
}
return nil
}
// stop shuts down the HTTP server.