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>
This commit is contained in:
zhiqiangxu 2023-06-14 20:04:41 +08:00 committed by GitHub
parent 8bbaf882a6
commit 6f08c2f3f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 12 deletions

@ -538,6 +538,13 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf
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) {
msg := &jsonrpcMessage{Version: vsn, ID: c.nextID(), Method: method}
if paramsIn != nil { // prevent sending "params":null

@ -59,11 +59,12 @@ var (
const (
errcodeDefault = -32000
errcodeNotificationsUnsupported = -32001
errcodeTimeout = -32002
errcodeResponseTooLarge = -32003
errcodePanic = -32603
errcodeMarshalError = -32603
legacyErrcodeNotificationsUnsupported = -32001
)
const (
@ -80,6 +81,34 @@ func (e *methodNotFoundError) Error() string {
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 }
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.
func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
if !h.allowSubscribe {
return msg.errorResponse(&internalServerError{
code: errcodeNotificationsUnsupported,
message: ErrNotificationsUnsupported.Error(),
})
return msg.errorResponse(ErrNotificationsUnsupported)
}
// Subscription method name is first argument.

@ -32,8 +32,17 @@ import (
)
var (
// ErrNotificationsUnsupported is returned when the connection doesn't support notifications
ErrNotificationsUnsupported = errors.New("notifications not supported")
// ErrNotificationsUnsupported is returned by the client when the connection doesn't
// 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 = errors.New("subscription not found")
)