@@ -140,74 +140,84 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger *slog.Logger) http.H
140
140
return
141
141
}
142
142
143
- // first try to unmarshal the incoming request as an array of RPC requests
144
- var (
145
- requests types. RPCRequests
146
- responses types.RPCResponses
147
- )
148
-
149
- // isRPCRequestArray is used to determine if the incoming payload is an array of requests.
150
- // This flag helps decide whether to return an array of responses (for batch requests) or a single response.
151
- isRPCRequestArray := true
143
+ // --- Branch 1: Attempt to Unmarshal as a Batch (Slice) of Requests ---
144
+ var requests types. RPCRequests
145
+ if err := json . Unmarshal ( b , & requests ); err == nil {
146
+ var responses types.RPCResponses
147
+ for _ , req := range requests {
148
+ if resp := processRequest ( r , req , funcMap , logger ); resp != nil {
149
+ responses = append ( responses , * resp )
150
+ }
151
+ }
152
152
153
- if err := json .Unmarshal (b , & requests ); err != nil {
154
- // next, try to unmarshal as a single request
155
- var request types.RPCRequest
156
- if err := json .Unmarshal (b , & request ); err != nil {
157
- WriteRPCResponseHTTP (w , types .RPCParseError (types .JSONRPCStringID ("" ), errors .Wrap (err , "error unmarshalling request" )))
153
+ if len (responses ) > 0 {
154
+ WriteRPCResponseArrayHTTP (w , responses )
158
155
return
159
156
}
160
- requests = []types.RPCRequest {request }
161
- isRPCRequestArray = false
162
157
}
163
158
164
- for _ , request := range requests {
165
- request := request
166
- // A Notification is a Request object without an "id" member.
167
- // The Server MUST NOT reply to a Notification, including those that are within a batch request.
168
- if request .ID == types .JSONRPCStringID ("" ) {
169
- logger .Debug ("HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)" )
170
- continue
171
- }
172
- if len (r .URL .Path ) > 1 {
173
- responses = append (responses , types .RPCInvalidRequestError (request .ID , errors .New ("path %s is invalid" , r .URL .Path )))
174
- continue
175
- }
176
- rpcFunc , ok := funcMap [request .Method ]
177
- if ! ok || rpcFunc .ws {
178
- responses = append (responses , types .RPCMethodNotFoundError (request .ID ))
179
- continue
180
- }
181
- ctx := & types.Context {JSONReq : & request , HTTPReq : r }
182
- args := []reflect.Value {reflect .ValueOf (ctx )}
183
- if len (request .Params ) > 0 {
184
- fnArgs , err := jsonParamsToArgs (rpcFunc , request .Params )
185
- if err != nil {
186
- responses = append (responses , types .RPCInvalidParamsError (request .ID , errors .Wrap (err , "error converting json params to arguments" )))
187
- continue
188
- }
189
- args = append (args , fnArgs ... )
190
- }
191
- returns := rpcFunc .f .Call (args )
192
- logger .Info ("HTTPJSONRPC" , "method" , request .Method , "args" , args , "returns" , returns )
193
- result , err := unreflectResult (returns )
194
- if err != nil {
195
- responses = append (responses , types .RPCInternalError (request .ID , err ))
196
- continue
159
+ // --- Branch 2: Attempt to Unmarshal as a Single Request ---
160
+ var request types.RPCRequest
161
+ if err := json .Unmarshal (b , & request ); err == nil {
162
+ if resp := processRequest (r , request , funcMap , logger ); resp != nil {
163
+ WriteRPCResponseHTTP (w , * resp )
164
+ return
197
165
}
198
- responses = append (responses , types .NewRPCSuccessResponse (request .ID , result ))
199
- }
200
- if len (responses ) == 0 {
166
+ } else {
167
+ WriteRPCResponseHTTP (w , types .RPCParseError (types .JSONRPCStringID ("" ), errors .Wrap (err , "error unmarshalling request" )))
201
168
return
202
169
}
170
+ }
171
+ }
203
172
204
- if isRPCRequestArray {
205
- WriteRPCResponseArrayHTTP (w , responses )
206
- return
173
+ // processRequest checks and processes a single JSON-RPC request.
174
+ // If the request should produce a response, it returns a pointer to that response.
175
+ // Otherwise (e.g. if the request is a notification or fails validation), it returns nil.
176
+ func processRequest (r * http.Request , req types.RPCRequest , funcMap map [string ]* RPCFunc , logger * slog.Logger ) * types.RPCResponse {
177
+ // Skip notifications (an empty ID indicates no response should be sent)
178
+ if req .ID == types .JSONRPCStringID ("" ) {
179
+ logger .Debug ("Skipping notification (empty ID)" )
180
+ return nil
181
+ }
182
+
183
+ // Check that the URL path is valid (assume only "/" is acceptable)
184
+ if len (r .URL .Path ) > 1 {
185
+ resp := types .RPCInvalidRequestError (req .ID , fmt .Errorf ("invalid path: %s" , r .URL .Path ))
186
+ return & resp
187
+ }
188
+
189
+ // Look up the requested method in the function map.
190
+ rpcFunc , ok := funcMap [req .Method ]
191
+ if ! ok || rpcFunc .ws {
192
+ resp := types .RPCMethodNotFoundError (req .ID )
193
+ return & resp
194
+ }
195
+
196
+ ctx := & types.Context {JSONReq : & req , HTTPReq : r }
197
+ args := []reflect.Value {reflect .ValueOf (ctx )}
198
+ if len (req .Params ) > 0 {
199
+ fnArgs , err := jsonParamsToArgs (rpcFunc , req .Params )
200
+ if err != nil {
201
+ resp := types .RPCInvalidParamsError (req .ID , errors .Wrap (err , "error converting json params to arguments" ))
202
+ return & resp
207
203
}
204
+ args = append (args , fnArgs ... )
205
+ }
208
206
209
- WriteRPCResponseHTTP (w , responses [0 ])
207
+ // Call the RPC function using reflection.
208
+ returns := rpcFunc .f .Call (args )
209
+ logger .Info ("HTTPJSONRPC" , "method" , req .Method , "args" , args , "returns" , returns )
210
+
211
+ // Convert the reflection return values into a result value for JSON serialization.
212
+ result , err := unreflectResult (returns )
213
+ if err != nil {
214
+ resp := types .RPCInternalError (req .ID , err )
215
+ return & resp
210
216
}
217
+
218
+ // Build and return a successful response.
219
+ resp := types .NewRPCSuccessResponse (req .ID , result )
220
+ return & resp
211
221
}
212
222
213
223
func handleInvalidJSONRPCPaths (next http.HandlerFunc ) http.HandlerFunc {
0 commit comments