Skip to content

pyjamaswithcherrypyjsonrpc2

castilla edited this page Nov 12, 2012 · 39 revisions

JSON-RPC with CherryPy

Tested with:

  • CherryPy Version 3.2.2
  • Pyjamas Version 0.8 (2012-2-12)
  • Arch Linux

Notes

Pyjamas server-side stuff used for JSON-RPC is located here:

  • [path to pyjamas]pyjs/jsonrpc

The CherryPy related implementation is done with two files:

  • [path to pyjamas]pyjs/jsonrpc/__init__.py (base class JSONRPCService)
  • [path to pyjamas]pyjs/jsonrpc/cherrypy/jsonrpc (cherrypy class JSONRPCService)

Writing your own JSON-RPC service for CherryPy

You have to impelement a service class which is derived from cherrypy.jsonrpc.JSONRPCService.
Doing like this your code may look as follows:

JsonRpcServer.py:

import os
from pyjs.jsonrpc.cherrypy.jsonrpc import JSONRPCService

class JsonApplicationService(JSONRPCService):

    def __init__(self, defaultPage, loglevel=0, cherrylog=False):
        JSONRPCService.__init__(self, defaultPage)
        self.loglevel = loglevel
        self.cherrylog = cherrylog
        self.add_method(self.remotecall.__name__, self.remotecall)

    def _log(self, s, level=-999):
        if self.loglevel >= level:
            nl = os.linesep if self.cherrylog else ""
            print nl + ">>>", s

    def remotecall(self, param):
        self._log("remotecall called with %s" % param, level=1)
        ret = param # echo
        return ret

Applying your service to the CherryPy webserver

Once your service is ready you can apply it to CherryPy.
Here's a possibility how you can do that.

JsonRpcServer.py:

import cherrypy
import os
from optparse import OptionParser

# Import your Service class (if it is in its own file)
# from serviceclassfile import JsonApplicationService

if __name__ == "__main__":

    host = "127.0.0.1"
    port = 8080

    parser = OptionParser()
    parser.add_option("--cherrylog", action="store_true", dest="cherrylog",
                      help="enable cherry server logging, default=disabled", default=False)
    parser.add_option("--verbose", action="store", type="int", dest="verbose", metavar="LEVEL",
                      help="verbose level (0=silent), default=0", default=0)

    (options, args) = parser.parse_args()
    
    for x in args[0:2]:
        try:
            try: port = int(x)
            except: host = x
        except:
            pass

    defaultPage = "index.html"
    service = JsonApplicationService(defaultPage,
                                     options.verbose,
                                     options.cherrylog)

    configs = {"server.socket_host":host,
               "server.socket_port":int(port),
               "log.screen":options.cherrylog,
               "tools.encode.on":True,
               "tools.encode.encoding":"utf-8",
               "tools.staticdir.on":True,
               "tools.staticdir.dir":os.path.abspath(os.curdir),
    }

    cherrypy.quickstart(service, "/", {"global":configs})

Please see the CherryPy website for the configs used above.

Starting the CherryPy webserver

You may put the codelines above in a file (maybe JsonRpcServer.py) and start your CherryPy server as follows:

python2 services/JsonRpcServer.py --verbose=3 --cherrylog 50111
[15/Feb/2012:07:39:52] ENGINE Listening for SIGHUP.
[15/Feb/2012:07:39:52] ENGINE Listening for SIGTERM.
[15/Feb/2012:07:39:52] ENGINE Listening for SIGUSR1.
[15/Feb/2012:07:39:52] ENGINE Bus STARTING
[15/Feb/2012:07:39:52] ENGINE Started monitor thread 'Autoreloader'.
[15/Feb/2012:07:39:52] ENGINE Started monitor thread '_TimeoutMonitor'.
[15/Feb/2012:07:39:52] ENGINE Serving on 127.0.0.1:50111
[15/Feb/2012:07:39:52] ENGINE Bus STARTED

You have the server running on localhost port 50111.

JSON-RPC client side

On the client side you have to do your pyjamas user interface as well as the JSON-RPC proxy service.

JsonRpcClient.py:

from pyjamas.JSONService import JSONProxy
from pyjamas.ui.Button import Button
from pyjamas.ui.HTML import HTML
from pyjamas.ui.HorizontalPanel import HorizontalPanel
from pyjamas.ui.Label import Label
from pyjamas.ui.RootPanel import RootPanel
from pyjamas.ui.TextBox import TextBox
from pyjamas.ui.VerticalPanel import VerticalPanel

# If your server uses pyjs.jsonrpc.cherrypy.jsonrpc
# your proxy url MUST be like that
PROXYURL = "services"

class JsonRpcClient:

    def onModuleLoad(self):
        remotemethods = ["remotecall"]
        self.remote = RemoteService(PROXYURL, remotemethods)
        self.status = Label(Text="Ready, waiting for input...")
        self.textbox = TextBox(Width=400)
        self.buttonClear = Button("Clear", self)
        self.buttonRemotecall = Button("Send to CherryPy JSON-RSP Server", self)
        panel = VerticalPanel(Spacing=20)
        panel.add(HTML("<h2>CherryPy JSON-RPC Client</h2>"))
        panel.add(self.status)
        panel.add(self.textbox)
        hp = HorizontalPanel()
        hp.add(self.buttonRemotecall)
        hp.add(self.buttonClear)
        panel.add(hp)
        RootPanel().add(panel)

    def onClick(self, sender):
        if sender == self.buttonClear:
            self.status.setText("Ready, waiting for input...")
            self.textbox.setText("")
        elif sender == self.buttonRemotecall:
            self.status.setText("Waiting remote response...")
            text = self.textbox.getText()
            id = self.remote.remotecall(text, self)

    def onRemoteResponse(self, response, request_info):
        self.status.setText("Remote response: %s" % response)

    def onRemoteError(self, code, errobj, request_info):
        # onRemoteError gets the HTTP error code or 0 and
        # errobj is an jsonrpc 2.0 error dict:
        #     {
        #       'code': jsonrpc-error-code (integer) ,
        #       'message': jsonrpc-error-message (string) ,
        #       'data' : extra-error-data
        #     }
        message = errobj['message']
        if code != 0:
            self.status.setText("HTTP error %d: %s" % (code, message))
        else:
            code = errobj['code']
            self.status.setText("JSONRPC Error %s: %s" % (code, message))


class RemoteService(JSONProxy):
    def __init__(self, url, methods):
        JSONProxy.__init__(self, url, methods)

JSON-RPC client side with Pyjamas desktop

The follwoing code was tested with the python-webkit-engine. Note, that the cherrypy server must be up and running.

Desktop.py:

import pyjd
from JsonRpcClient import JsonRpcClient

if __name__ == '__main__':
    
    HOST = "http://127.0.0.1"
    SERVERPORT = 50111
    pyjd.setup("%s:%d/Desktop.html" % (HOST, SERVERPORT))
    app = JsonRpcClient()
    app.onModuleLoad()
    pyjd.run()

Desktop.html:

<html>
  <head>
    <title>CherryPY JSON-RPC Client</title>
  </head>
  <body bgcolor="white">
    <script src="bootstrap.js" type="text/javascript"></script>
  </body>
</html>

JSON-RPC client side in a browser

Build the following code and start a browser at localhost:50111

Main.py:

import pyjd
from JsonRpcClient import JsonRpcClient

if __name__ == "__main__":
    
    app = JsonRpcClient()
    app.onModuleLoad()
    pyjd.run()

index.html:

<!DOCTYPE HTML>
<html>
  <head>
    <meta name="pygwt:module" content="Main">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>CherryPY JSON-RPC Client</title>
  </head>
  <body>
    <script src="bootstrap.js" type="text/javascript"></script>
      <iframe id='__pygwt_historyFrame'
        style='width:0;height:0;border:0;display:none;'></iframe>
  </body>
</html>

Putting all together

Files:

  YOURDIR/JsonRpcCLient.py
  YOURDIR/Desktop.py
  YOURDIR/Main.py
  YOURDIR/public/index.html
  YOURDIR/public/Desktop.html
  YOURDIR/public/services/JsonRpcServer.py

Build:

In YOURDIR do
PATH_TO_PYJAMAS/bin/pyjsbuild --cache-buster --no-compile-inplace Main

Start the server:

In YOURDIR do
cd output; python2 services/JsonRpcServer.py --verbose=3 50111

Start Pyjamas Desktop:

In YOURDIR do
PATH_TO_PYJAMAS/bin/pyjd Desktop.py

Start web application:

Open a browser at localhost:50111

Enjoy Pyjamas!

Something went wrong with that request. Please try again.