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:
		@@ -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.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user