• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tcalmant / jsonrpclib / 18856320250

27 Oct 2025 09:23PM UTC coverage: 83.212% (+0.2%) from 82.994%
18856320250

push

github

web-flow
Merge pull request #65 from tcalmant/issue64

Fixes for #64

6 of 20 new or added lines in 1 file covered. (30.0%)

1 existing line in 1 file now uncovered.

1031 of 1239 relevant lines covered (83.21%)

5.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.6
/jsonrpclib/SimpleJSONRPCServer.py
1
#!/usr/bin/python
2
# -- Content-Encoding: UTF-8 --
3
"""
2✔
4
Defines a request dispatcher, a HTTP request handler, a HTTP server and a
5
CGI request handler.
6

7
:authors: Josh Marshall, Thomas Calmant
8
:copyright: Copyright 2025, Thomas Calmant
9
:license: Apache License 2.0
10
:version: 0.4.3.4
11

12
..
13

14
    Copyright 2025 Thomas Calmant
15

16
    Licensed under the Apache License, Version 2.0 (the "License");
17
    you may not use this file except in compliance with the License.
18
    You may obtain a copy of the License at
19

20
        http://www.apache.org/licenses/LICENSE-2.0
21

22
    Unless required by applicable law or agreed to in writing, software
23
    distributed under the License is distributed on an "AS IS" BASIS,
24
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25
    See the License for the specific language governing permissions and
26
    limitations under the License.
27
"""
28

29
# We use print() in the CGI request handler
30
from __future__ import print_function
7✔
31

32
# Standard library
33
import logging
7✔
34
import socket
7✔
35
import sys
7✔
36
import traceback
7✔
37

38
try:
7✔
39
    # Python 3
40
    # pylint: disable=F0401,E0611
41
    import xmlrpc.server as xmlrpcserver
7✔
42

43
    # Make sure the module is complete.
44
    # The "future" package under python2.7 provides an incomplete
45
    # variant of this package.
46
    SimpleXMLRPCDispatcher = xmlrpcserver.SimpleXMLRPCDispatcher
7✔
47
    SimpleXMLRPCRequestHandler = xmlrpcserver.SimpleXMLRPCRequestHandler
7✔
48
    resolve_dotted_attribute = xmlrpcserver.resolve_dotted_attribute  # type: ignore  # noqa: E501  # pylint: disable=invalid-name,line-too-long
7✔
49
    import socketserver
7✔
50
except (ImportError, AttributeError):
×
51
    # Python 2 or IronPython
52
    # pylint: disable=F0401,E0611
53
    import SimpleXMLRPCServer as xmlrpcserver  # type: ignore
×
54

55
    SimpleXMLRPCDispatcher = xmlrpcserver.SimpleXMLRPCDispatcher  # type: ignore  # noqa: E501  # pylint: disable=invalid-name,line-too-long
×
56
    SimpleXMLRPCRequestHandler = xmlrpcserver.SimpleXMLRPCRequestHandler  # type: ignore  # noqa: E501  # pylint: disable=invalid-name,line-too-long
×
57
    resolve_dotted_attribute = xmlrpcserver.resolve_dotted_attribute  # type: ignore  # noqa: E501  # pylint: disable=invalid-name,line-too-long
×
58
    import SocketServer as socketserver  # type: ignore
×
59

60
try:
7✔
61
    # Windows
62
    import fcntl
7✔
63
except ImportError:
×
64
    # Other systems
65
    # pylint: disable=C0103
66
    fcntl = None  # type: ignore
×
67

68
try:
7✔
69
    # Python with support for Unix socket
70
    _AF_UNIX = socket.AF_UNIX
7✔
71
except AttributeError:
×
72
    # Unix sockets are not supported, use a dummy value
73
    _AF_UNIX = -1  # type: ignore
×
74

75
# Local modules
76
import jsonrpclib.config
7✔
77
import jsonrpclib.threadpool
7✔
78
import jsonrpclib.utils as utils
7✔
79
from jsonrpclib import Fault
7✔
80

81
# ------------------------------------------------------------------------------
82

83
# Module version
84
__version_info__ = (0, 4, 3, 4)
7✔
85
__version__ = ".".join(str(x) for x in __version_info__)
7✔
86

87
# Documentation strings format
88
__docformat__ = "restructuredtext en"
7✔
89

90
# Prepare the logger
91
_logger = logging.getLogger(__name__)
7✔
92

93
# ------------------------------------------------------------------------------
94

95

96
def get_version(request):
7✔
97
    """
98
    Computes the JSON-RPC version
99

100
    :param request: A request dictionary
101
    :return: The JSON-RPC version or None
102
    """
103
    if "jsonrpc" in request:
7✔
104
        return 2.0
7✔
105
    elif "id" in request:
7✔
106
        return 1.0
×
107

108
    return None
7✔
109

110

111
def validate_request(request, json_config):
7✔
112
    """
113
    Validates the format of a request dictionary
114

115
    :param request: A request dictionary
116
    :param json_config: A JSONRPClib Config instance
117
    :return: True if the dictionary is valid, else a Fault object
118
    """
119
    if not isinstance(request, utils.DictType):
7✔
120
        # Invalid request type
121
        fault = Fault(
7✔
122
            -32600,
123
            "Request must be a dict, not {0}".format(type(request).__name__),
124
            config=json_config,
125
        )
126
        _logger.warning("Invalid request content: %s", fault)
7✔
127
        return fault
7✔
128

129
    # Get the request ID
130
    rpcid = request.get("id", None)
7✔
131

132
    # Check request version
133
    version = get_version(request)
7✔
134
    if not version:
7✔
135
        fault = Fault(
7✔
136
            -32600,
137
            "Request {0} invalid.".format(request),
138
            rpcid=rpcid,
139
            config=json_config,
140
        )
141
        _logger.warning("No version in request: %s", fault)
7✔
142
        return fault
7✔
143

144
    # Default parameters: empty list
145
    request.setdefault("params", [])
7✔
146

147
    # Check parameters
148
    method = request.get("method", None)
7✔
149
    params = request.get("params")
7✔
150
    param_types = (utils.ListType, utils.DictType, utils.TupleType)
7✔
151

152
    if (
7✔
153
        not method
154
        or not isinstance(method, utils.STRING_TYPES)
155
        or not isinstance(params, param_types)
156
    ):
157
        # Invalid type of method name or parameters
158
        fault = Fault(
7✔
159
            -32600,
160
            "Invalid request parameters or method.",
161
            rpcid=rpcid,
162
            config=json_config,
163
        )
164
        _logger.warning("Invalid request content: %s", fault)
7✔
165
        return fault
7✔
166

167
    # Valid request
168
    return True
7✔
169

170

171
# ------------------------------------------------------------------------------
172

173

174
class NoMulticallResult(Exception):
7✔
175
    """
176
    No result in multicall
177
    """
178

179

180
class SimpleJSONRPCDispatcher(SimpleXMLRPCDispatcher, object):
7✔
181
    """
182
    Mix-in class that dispatches JSON-RPC requests.
183

184
    This class is used to register JSON-RPC method handlers
185
    and then to dispatch them. This class doesn't need to be
186
    instanced directly when used by SimpleJSONRPCServer.
187
    """
188

189
    def __init__(self, encoding=None, config=jsonrpclib.config.DEFAULT):
7✔
190
        """
191
        Sets up the dispatcher with the given encoding.
192
        None values are allowed.
193
        """
194
        SimpleXMLRPCDispatcher.__init__(
7✔
195
            self, allow_none=True, encoding=encoding or "UTF-8"
196
        )
197
        self.json_config = config
7✔
198

199
        # Notification thread pool
200
        self.__notification_pool = None
7✔
201

202
    def set_notification_pool(self, thread_pool):
7✔
203
        """
204
        Sets the thread pool to use to handle notifications
205
        """
206
        self.__notification_pool = thread_pool
×
207

208
    def _unmarshaled_dispatch(self, request, dispatch_method=None):
7✔
209
        """
210
        Loads the request dictionary (unmarshaled), calls the method(s)
211
        accordingly and returns a JSON-RPC dictionary (not marshaled)
212

213
        :param request: JSON-RPC request dictionary (or list of)
214
        :param dispatch_method: Custom dispatch method (for method resolution)
215
        :return: A JSON-RPC dictionary (or an array of) or None if the request
216
                 was a notification
217
        :raise NoMulticallResult: No result in batch
218
        """
219
        if not request:
7✔
220
            # Invalid request dictionary
221
            fault = Fault(
7✔
222
                -32600,
223
                "Request invalid -- no request data.",
224
                config=self.json_config,
225
            )
226
            _logger.warning("Invalid request: %s", fault)
7✔
227
            return fault.dump()
7✔
228

229
        if isinstance(request, utils.ListType):
7✔
230
            # This SHOULD be a batch, by spec
231
            responses = []
7✔
232
            for req_entry in request:
7✔
233
                # Validate the request
234
                result = validate_request(req_entry, self.json_config)
7✔
235
                if isinstance(result, Fault):
7✔
236
                    responses.append(result.dump())
7✔
237
                    continue
7✔
238

239
                # Call the method
240
                resp_entry = self._marshaled_single_dispatch(
7✔
241
                    req_entry, dispatch_method
242
                )
243

244
                # Store its result
245
                if isinstance(resp_entry, Fault):
7✔
246
                    # pylint: disable=E1103
247
                    responses.append(resp_entry.dump())
×
248
                elif resp_entry is not None:
7✔
249
                    responses.append(resp_entry)
7✔
250

251
            if not responses:
7✔
252
                # No non-None result
253
                _logger.error("No result in Multicall")
7✔
254
                raise NoMulticallResult("No result")
7✔
255

256
            return responses
7✔
257

258
        else:
259
            # Single call
260
            result = validate_request(request, self.json_config)
7✔
261
            if isinstance(result, Fault):
7✔
262
                return result.dump()
7✔
263

264
            # Call the method
265
            response = self._marshaled_single_dispatch(request, dispatch_method)
7✔
266
            if isinstance(response, Fault):
7✔
267
                # pylint: disable=E1103
268
                return response.dump()
×
269

270
            return response
7✔
271

272
    def _marshaled_dispatch(self, data, dispatch_method=None, path=None):
7✔
273
        """
274
        Parses the request data (marshaled), calls method(s) and returns a
275
        JSON string (marshaled)
276

277
        :param data: A JSON request string
278
        :param dispatch_method: Custom dispatch method (for method resolution)
279
        :param path: Unused parameter, to keep compatibility with xmlrpclib
280
        :return: A JSON-RPC response string (marshaled)
281
        """
282
        # Parse the request
283
        try:
7✔
284
            request = jsonrpclib.loads(data, self.json_config)
7✔
285
        except Exception as ex:
7✔
286
            # Parsing/loading error
287
            fault = Fault(
7✔
288
                -32700,
289
                "Request {0} invalid. ({1}:{2})".format(
290
                    data, type(ex).__name__, ex
291
                ),
292
                config=self.json_config,
293
            )
294
            _logger.warning("Error parsing request: %s", fault)
7✔
295
            return fault.response()
7✔
296

297
        # Get the response dictionary
298
        try:
7✔
299
            response = self._unmarshaled_dispatch(request, dispatch_method)
7✔
300
            if response is not None:
7✔
301
                # Compute the string representation of the dictionary/list
302
                return jsonrpclib.jdumps(response, self.encoding)
7✔
303
            else:
304
                # No result (notification)
305
                return ""
7✔
306
        except NoMulticallResult:
7✔
307
            # Return an empty string (jsonrpclib internal behaviour)
308
            return ""
7✔
309

310
    def _marshaled_single_dispatch(self, request, dispatch_method=None):
7✔
311
        """
312
        Dispatches a single method call
313

314
        :param request: A validated request dictionary
315
        :param dispatch_method: Custom dispatch method (for method resolution)
316
        :return: A JSON-RPC response dictionary, or None if it was a
317
                 notification request
318
        """
319
        method = request.get("method")
7✔
320
        params = request.get("params")
7✔
321

322
        # Prepare a request-specific configuration
323
        if "jsonrpc" not in request and self.json_config.version >= 2:
7✔
324
            # JSON-RPC 1.0 request on a JSON-RPC 2.0
325
            # => compatibility needed
326
            config = self.json_config.copy()
×
327
            config.version = 1.0
×
328
        else:
329
            # Keep server configuration as is
330
            config = self.json_config
7✔
331

332
        # Test if this is a notification request
333
        is_notification = "id" not in request or request["id"] in (None, "")
7✔
334
        if is_notification and self.__notification_pool is not None:
7✔
335
            # Use the thread pool for notifications
336
            if dispatch_method is not None:
×
337
                self.__notification_pool.enqueue(
×
338
                    dispatch_method, method, params
339
                )
340
            else:
341
                self.__notification_pool.enqueue(
×
342
                    self._dispatch, method, params, config
343
                )
344

345
            # Return immediately
346
            return None
×
347
        else:
348
            # Synchronous call
349
            try:
7✔
350
                # Call the method
351
                if dispatch_method is not None:
7✔
352
                    response = dispatch_method(method, params)
×
353
                else:
354
                    response = self._dispatch(method, params, config)
7✔
355
            except Exception as ex:
×
356
                # Return a fault
357
                fault = Fault(
×
358
                    -32603,
359
                    "{0}:{1}".format(type(ex).__name__, ex),
360
                    config=config,
361
                )
362
                _logger.error("Error calling method %s: %s", method, fault)
×
363
                return fault.dump()
×
364

365
            if is_notification:
7✔
366
                # It's a notification, no result needed
367
                # Do not use 'not id' as it might be the integer 0
368
                return None
7✔
369

370
        # Prepare a JSON-RPC dictionary
371
        try:
7✔
372
            return jsonrpclib.dump(
7✔
373
                response, rpcid=request["id"], is_response=True, config=config
374
            )
375
        except Exception as ex:
×
376
            # JSON conversion exception
377
            fault = Fault(
×
378
                -32603, "{0}:{1}".format(type(ex).__name__, ex), config=config
379
            )
380
            _logger.error("Error preparing JSON-RPC result: %s", fault)
×
381
            return fault.dump()
×
382

383
    def _dispatch(self, method, params, config=None):
7✔
384
        """
385
        Default method resolver and caller
386

387
        :param method: Name of the method to call
388
        :param params: List of arguments to give to the method
389
        :param config: Request-specific configuration
390
        :return: The result of the method
391
        """
392
        config = config or self.json_config
7✔
393

394
        func = None
7✔
395
        try:
7✔
396
            # Look into registered methods
397
            func = self.funcs[method]
7✔
398
        except KeyError:
7✔
399
            if self.instance is not None:
7✔
400
                # Try with the registered instance
401
                try:
×
402
                    # Instance has a custom dispatcher
403
                    return getattr(self.instance, "_dispatch")(method, params)
×
404
                except AttributeError:
×
405
                    # Resolve the method name in the instance
406
                    try:
×
407
                        func = resolve_dotted_attribute(
×
408
                            self.instance, method, True
409
                        )
410
                    except AttributeError:
×
411
                        # Unknown method
412
                        pass
×
413

414
        if func is not None:
7✔
415
            try:
7✔
416
                # Call the method
417
                if isinstance(params, utils.ListType):
7✔
418
                    return func(*params)
7✔
419
                else:
420
                    return func(**params)
7✔
421
            except TypeError as ex:
7✔
422
                # Maybe the parameters are wrong
423
                fault = Fault(
7✔
424
                    -32602, "Invalid parameters: {0}".format(ex), config=config
425
                )
426
                _logger.warning("Invalid call parameters: %s", fault)
7✔
427
                return fault
7✔
428
            except:
7✔
429
                # Method exception
430
                err_lines = traceback.format_exception(*sys.exc_info())
7✔
431
                trace_string = "{0} | {1}".format(
7✔
432
                    err_lines[-2].splitlines()[0].strip(), err_lines[-1]
433
                )
434
                fault = Fault(
7✔
435
                    -32603,
436
                    "Server error: {0}".format(trace_string),
437
                    config=config,
438
                )
439
                _logger.exception("Server-side exception: %s", fault)
7✔
440
                return fault
7✔
441
        else:
442
            # Unknown method
443
            fault = Fault(
7✔
444
                -32601,
445
                "Method {0} not supported.".format(method),
446
                config=config,
447
            )
448
            _logger.warning("Unknown method: %s", fault)
7✔
449
            return fault
7✔
450

451

452
# ------------------------------------------------------------------------------
453

454

455
class SimpleJSONRPCRequestHandler(SimpleXMLRPCRequestHandler):
7✔
456
    """
457
    HTTP request handler.
458

459
    The server that receives the requests must have a json_config member,
460
    containing a JSONRPClib Config instance
461
    """
462

463
    def do_POST(self):
7✔
464
        """
465
        Handles POST requests
466
        """
467
        if not self.is_rpc_path_valid():
7✔
468
            self.report_404()
7✔
469
            return
7✔
470

471
        # Retrieve the configuration
472
        config = getattr(self.server, "json_config", jsonrpclib.config.DEFAULT)
7✔
473

474
        try:
7✔
475
            # Read the request body
476
            max_chunk_size = 10 * 1024 * 1024
7✔
477
            size_remaining = int(self.headers["content-length"])
7✔
478
            chunks = []
7✔
479
            while size_remaining:
7✔
480
                chunk_size = min(size_remaining, max_chunk_size)
7✔
481
                raw_chunk = self.rfile.read(chunk_size)
7✔
482
                if not raw_chunk:
7✔
483
                    break
×
484
                chunks.append(utils.from_bytes(raw_chunk))
7✔
485
                size_remaining -= len(raw_chunk)
7✔
486
            data = "".join(chunks)
7✔
487

488
            try:
7✔
489
                # Decode content
490
                data = self.decode_request_content(data)
7✔
491
                if data is None:
7✔
492
                    # Unknown encoding, response has been sent
493
                    return
×
494
            except AttributeError:
×
495
                # Available since Python 2.7
496
                pass
×
497

498
            # Execute the method
499
            response = self.server._marshaled_dispatch(
7✔
500
                data, getattr(self, "_dispatch", None), self.path
501
            )
502

503
            # No exception: send a 200 OK
504
            self.send_response(200)
7✔
505
        except:
×
506
            # Exception: send 500 Server Error
507
            self.send_response(500)
×
508
            err_lines = traceback.format_exception(*sys.exc_info())
×
509
            trace_string = "{0} | {1}".format(
×
510
                err_lines[-2].splitlines()[0].strip(), err_lines[-1]
511
            )
512
            fault = jsonrpclib.Fault(
×
513
                -32603, "Server error: {0}".format(trace_string), config=config
514
            )
515
            _logger.exception("Server-side error: %s", fault)
×
516
            response = fault.response()
×
517

518
        if response is None:
7✔
519
            # Avoid to send None
520
            response = ""
×
521

522
        # Convert the response to the valid string format
523
        response = utils.to_bytes(response)
7✔
524

525
        # Send it
526
        self.send_header("Content-type", config.content_type)
7✔
527
        self.send_header("Content-length", str(len(response)))
7✔
528
        self.end_headers()
7✔
529
        if response:
7✔
530
            self.wfile.write(response)
7✔
531

532

533
# ------------------------------------------------------------------------------
534

535

536
class SimpleJSONRPCServer(socketserver.TCPServer, SimpleJSONRPCDispatcher):
7✔
537
    """
538
    JSON-RPC server (and dispatcher)
539
    """
540

541
    # This simplifies server restart after error
542
    allow_reuse_address = True
7✔
543

544
    # pylint: disable=C0103
545
    def __init__(
7✔
546
        self,
547
        addr,
548
        requestHandler=SimpleJSONRPCRequestHandler,
549
        logRequests=True,
550
        encoding=None,
551
        bind_and_activate=True,
552
        address_family=socket.AF_INET,
553
        config=jsonrpclib.config.DEFAULT,
554
    ):
555
        """
556
        Sets up the server and the dispatcher
557

558
        :param addr: The server listening address
559
        :param requestHandler: Custom request handler
560
        :param logRequests: Flag to(de)activate requests logging
561
        :param encoding: The dispatcher request encoding
562
        :param bind_and_activate: If True, starts the server immediately
563
        :param address_family: The server listening address family
564
        :param config: A JSONRPClib Config instance
565
        """
566
        # Set up the dispatcher fields
567
        SimpleJSONRPCDispatcher.__init__(self, encoding, config)
7✔
568

569
        # Flag to ease handling of Unix socket mode
570
        unix_socket = address_family == _AF_UNIX
7✔
571

572
        # Disable the reuse address flag when in Unix socket mode, or an
573
        # exception will raise when binding the socket
574
        self.allow_reuse_address = self.allow_reuse_address and not unix_socket
7✔
575

576
        # Prepare the server configuration
577
        self.address_family = address_family
7✔
578
        self.json_config = config
7✔
579

580
        # logRequests is used by SimpleXMLRPCRequestHandler
581
        # This must be disabled in Unix socket mode (or an exception will raise
582
        # at each connection)
583
        self.logRequests = logRequests and not unix_socket
7✔
584

585
        # Work on the request handler
586
        class RequestHandlerWrapper(requestHandler, object):
7✔
587
            """
588
            Wraps the request handle to have access to the configuration
589
            """
590

591
            def __init__(self, *args, **kwargs):
7✔
592
                """
593
                Constructs the wrapper after having stored the configuration
594
                """
595
                self.config = config
7✔
596

597
                if unix_socket:
7✔
598
                    # Disable TCP features over Unix socket, or an
599
                    # "invalid argument" error will raise
600
                    self.disable_nagle_algorithm = False
7✔
601

602
                super(RequestHandlerWrapper, self).__init__(*args, **kwargs)
7✔
603

604
        # Set up the server
605
        socketserver.TCPServer.__init__(
7✔
606
            self, addr, RequestHandlerWrapper, bind_and_activate
607
        )
608

609
        # Windows-specific
610
        if fcntl is not None and hasattr(fcntl, "FD_CLOEXEC"):
7✔
611
            flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
7✔
612
            flags |= fcntl.FD_CLOEXEC
7✔
613
            fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
7✔
614

615

616
# ------------------------------------------------------------------------------
617

618

619
class PooledJSONRPCServer(socketserver.ThreadingMixIn, SimpleJSONRPCServer):
7✔
620
    """
621
    JSON-RPC server based on a thread pool
622
    """
623

624
    def __init__(
7✔
625
        self,
626
        addr,
627
        requestHandler=SimpleJSONRPCRequestHandler,
628
        logRequests=True,
629
        encoding=None,
630
        bind_and_activate=True,
631
        address_family=socket.AF_INET,
632
        config=jsonrpclib.config.DEFAULT,
633
        thread_pool=None,
634
    ):
635
        """
636
        Sets up the server and the dispatcher
637

638
        :param addr: The server listening address
639
        :param requestHandler: Custom request handler
640
        :param logRequests: Flag to(de)activate requests logging
641
        :param encoding: The dispatcher request encoding
642
        :param bind_and_activate: If True, starts the server immediately
643
        :param address_family: The server listening address family
644
        :param config: A JSONRPClib Config instance
645
        :param thread_pool: A ThreadPool object. The pool must be started.
646
        """
647
        # Normalize the thread pool
648
        if thread_pool is None:
7✔
649
            # Start a thread pool with  30 threads max, 0 thread min
650
            thread_pool = jsonrpclib.threadpool.ThreadPool(
7✔
651
                30, 0, logname="PooledJSONRPCServer"
652
            )
653
            thread_pool.start()
7✔
654

655
        # Store the thread pool
656
        self.__request_pool = thread_pool
7✔
657

658
        # Prepare the server
659
        SimpleJSONRPCServer.__init__(
7✔
660
            self,
661
            addr,
662
            requestHandler,
663
            logRequests,
664
            encoding,
665
            bind_and_activate,
666
            address_family,
667
            config,
668
        )
669

670
    def process_request(self, request, client_address):
7✔
671
        """
672
        Handle a client request: queue it in the thread pool
673
        """
674
        self.__request_pool.enqueue(
7✔
675
            self.process_request_thread, request, client_address
676
        )
677

678
    def server_close(self):
7✔
679
        """
680
        Clean up the server
681
        """
682
        SimpleJSONRPCServer.shutdown(self)
7✔
683
        SimpleJSONRPCServer.server_close(self)
7✔
684
        self.__request_pool.stop()
7✔
685

686

687
# ------------------------------------------------------------------------------
688

689
if sys.version_info < (3, 15):
7✔
690
    CGIXMLRPCRequestHandler = xmlrpcserver.CGIXMLRPCRequestHandler
7✔
691

692
    class CGIJSONRPCRequestHandler(
7✔
693
        SimpleJSONRPCDispatcher, CGIXMLRPCRequestHandler
694
    ):
695
        """
696
        JSON-RPC CGI handler (and dispatcher)
697
        """
698

699
        def __init__(self, encoding="UTF-8", config=jsonrpclib.config.DEFAULT):
7✔
700
            """
701
            Sets up the dispatcher
702

703
            :param encoding: Dispatcher encoding
704
            :param config: A JSONRPClib Config instance
705
            """
NEW
706
            SimpleJSONRPCDispatcher.__init__(self, encoding, config)
×
NEW
707
            CGIXMLRPCRequestHandler.__init__(self, encoding=encoding)
×
708

709
        def handle_jsonrpc(self, request_text):
7✔
710
            """
711
            Handle a JSON-RPC request
712
            """
NEW
713
            try:
×
NEW
714
                writer = sys.stdout.buffer
×
NEW
715
            except AttributeError:
×
NEW
716
                writer = sys.stdout
×
717

NEW
718
            response = self._marshaled_dispatch(request_text)
×
NEW
719
            response = response.encode(self.encoding)
×
NEW
720
            print("Content-Type:", self.json_config.content_type)
×
NEW
721
            print("Content-Length:", len(response))
×
NEW
722
            print()
×
NEW
723
            sys.stdout.flush()
×
NEW
724
            writer.write(response)
×
NEW
725
            writer.flush()
×
726

727
        # XML-RPC alias
728
        handle_xmlrpc = handle_jsonrpc
7✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc