rpc: add method to test for subscription support (#25942)

This adds two ways to check for subscription support. First, one can now check
whether the transport method (HTTP/WS/etc.) is capable of subscriptions using
the new Client.SupportsSubscriptions method.

Second, the error returned by Subscribe can now reliably be tested using this
pattern:
    
    sub, err := client.Subscribe(...)
    if errors.Is(err, rpc.ErrNotificationsUnsupported) {
        // no subscription support
    }

---------

Co-authored-by: Felix Lange <fjl@twurst.com>
pull/27472/head
zhiqiangxu 1 year ago committed by GitHub
parent 8bbaf882a6
commit 6f08c2f3f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      rpc/client.go
  2. 41
      rpc/errors.go
  3. 5
      rpc/handler.go
  4. 13
      rpc/subscription.go

@ -538,6 +538,13 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf
return op.sub, nil return op.sub, nil
} }
// SupportsSubscriptions reports whether subscriptions are supported by the client
// transport. When this returns false, Subscribe and related methods will return
// ErrNotificationsUnsupported.
func (c *Client) SupportsSubscriptions() bool {
return !c.isHTTP
}
func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) { func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) {
msg := &jsonrpcMessage{Version: vsn, ID: c.nextID(), Method: method} msg := &jsonrpcMessage{Version: vsn, ID: c.nextID(), Method: method}
if paramsIn != nil { // prevent sending "params":null if paramsIn != nil { // prevent sending "params":null

@ -58,12 +58,13 @@ var (
) )
const ( const (
errcodeDefault = -32000 errcodeDefault = -32000
errcodeNotificationsUnsupported = -32001 errcodeTimeout = -32002
errcodeTimeout = -32002 errcodeResponseTooLarge = -32003
errcodeResponseTooLarge = -32003 errcodePanic = -32603
errcodePanic = -32603 errcodeMarshalError = -32603
errcodeMarshalError = -32603
legacyErrcodeNotificationsUnsupported = -32001
) )
const ( const (
@ -80,6 +81,34 @@ func (e *methodNotFoundError) Error() string {
return fmt.Sprintf("the method %s does not exist/is not available", e.method) return fmt.Sprintf("the method %s does not exist/is not available", e.method)
} }
type notificationsUnsupportedError struct{}
func (e notificationsUnsupportedError) Error() string {
return "notifications not supported"
}
func (e notificationsUnsupportedError) ErrorCode() int { return -32601 }
// Is checks for equivalence to another error. Here we define that all errors with code
// -32601 (method not found) are equivalent to notificationsUnsupportedError. This is
// done to enable the following pattern:
//
// sub, err := client.Subscribe(...)
// if errors.Is(err, rpc.ErrNotificationsUnsupported) {
// // server doesn't support subscriptions
// }
func (e notificationsUnsupportedError) Is(other error) bool {
if other == (notificationsUnsupportedError{}) {
return true
}
rpcErr, ok := other.(Error)
if ok {
code := rpcErr.ErrorCode()
return code == -32601 || code == legacyErrcodeNotificationsUnsupported
}
return false
}
type subscriptionNotFoundError struct{ namespace, subscription string } type subscriptionNotFoundError struct{ namespace, subscription string }
func (e *subscriptionNotFoundError) ErrorCode() int { return -32601 } func (e *subscriptionNotFoundError) ErrorCode() int { return -32601 }

@ -530,10 +530,7 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
// handleSubscribe processes *_subscribe method calls. // handleSubscribe processes *_subscribe method calls.
func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage { func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
if !h.allowSubscribe { if !h.allowSubscribe {
return msg.errorResponse(&internalServerError{ return msg.errorResponse(ErrNotificationsUnsupported)
code: errcodeNotificationsUnsupported,
message: ErrNotificationsUnsupported.Error(),
})
} }
// Subscription method name is first argument. // Subscription method name is first argument.

@ -32,8 +32,17 @@ import (
) )
var ( var (
// ErrNotificationsUnsupported is returned when the connection doesn't support notifications // ErrNotificationsUnsupported is returned by the client when the connection doesn't
ErrNotificationsUnsupported = errors.New("notifications not supported") // support notifications. You can use this error value to check for subscription
// support like this:
//
// sub, err := client.EthSubscribe(ctx, channel, "newHeads", true)
// if errors.Is(err, rpc.ErrNotificationsUnsupported) {
// // Server does not support subscriptions, fall back to polling.
// }
//
ErrNotificationsUnsupported = notificationsUnsupportedError{}
// ErrSubscriptionNotFound is returned when the notification for the given id is not found // ErrSubscriptionNotFound is returned when the notification for the given id is not found
ErrSubscriptionNotFound = errors.New("subscription not found") ErrSubscriptionNotFound = errors.New("subscription not found")
) )

Loading…
Cancel
Save