Ext.Direct router
-----------------

  >>> from zope.publisher.browser import BrowserView
  >>> from zope.interface.declarations import implements
  >>> from falkolab.ext3.direct.decorator import directMethod

Manual enable devmode for error handling

  >>> from zope.app.appsetup import appsetup
  >>> ti=appsetup.getConfigContext() 
  >>> ti.provideFeature('devmode') 
    
Let's register api and views:
  
  >>> from zope.configuration import xmlconfig
  >>> ignored = xmlconfig.string("""
  ... <configure
  ...     xmlns="http://namespaces.zope.org/zope"
  ...     xmlns:extdirect="http://namespaces.zope.org/extdirect"
  ...     >
  ...   <!-- We only need to do this include in this example,
  ...        Normally the include has already been done for us. -->
  ...   <include package="falkolab.ext3.direct" file="meta.zcml" />
  ...
  ...   <extdirect:api
  ...       for="zope.app.folder.interfaces.IFolder"
  ...       namespace = "my.app"       
  ...       />    
  ...
  ...   <extdirect:view
  ...       for="zope.app.folder.interfaces.IFolder"
  ...       class="falkolab.ext3.direct.testing.AlbumList"
  ...       permission="zope.ManageContent"
  ...       name="albumlist"
  ...       />
  ... 
  ...   <extdirect:view
  ...       for="zope.app.folder.interfaces.IFolder"
  ...       class="falkolab.ext3.direct.testing.Contact"
  ...       permission="zope.ManageContent"
  ...       />
  ... </configure>
  ... """)
  
  >>> print http(r"""
  ... POST /@@directrouter HTTP/1.1
  ... Authorization: Basic bWdyOm1ncnB3
  ... Content-Length: 71
  ... Content-Type: application/json; charset=UTF-8
  ... Referer: http://localhost/
  ... 
  ... {"action":"albumlist","method":"getAll","data":[],"type":"rpc","tid":1}""")  
  HTTP/1.1 200 Ok
  Content-Length: 89
  Content-Type: text/javascript
  <BLANKLINE>
  {"action": "albumlist", "tid": 1, "type": "rpc", "method": "getAll", "result": [1, 2, 3]}
  
  >>> print http(r"""
  ... POST /@@directrouter HTTP/1.1
  ... Authorization: Basic bWdyOm1ncnB3
  ... Content-Length: 68
  ... Content-Type: application/json; charset=UTF-8
  ... Referer: http://localhost/
  ... 
  ... {"action":"albumlist","method":"add","data":[],"type":"rpc","tid":2}""")  
  HTTP/1.1 200 Ok
  Content-Length: 81
  Content-Type: text/javascript
  <BLANKLINE>
  {"action": "albumlist", "tid": 2, "type": "rpc", "method": "add", "result": "ok"}
  

Handle error:

  >>> print http(r"""
  ... POST /@@directrouter HTTP/1.1
  ... Authorization: Basic bWdyOm1ncnB3
  ... Content-Length: 74
  ... Content-Type: application/json; charset=UTF-8
  ... Referer: http://localhost/
  ... 
  ... {"action":"albumlist","method":"notExists","data":[],"type":"rpc","tid":3}""")  
  HTTP/1.1 200 Ok
  Content-Length: 722
  Content-Type: text/javascript
  <BLANKLINE>
  ..."tid": 3, "result": null, "action": "albumlist", "message": "Unable to find method 'notExists' on action 'albumlist'", "type": "exception", "method": "notExists"}
    
And batch request

  >>> print http(r"""
  ... POST /@@directrouter HTTP/1.1
  ... Authorization: Basic bWdyOm1ncnB3
  ... Content-Length: 142
  ... Content-Type: application/json; charset=UTF-8
  ... Referer: http://localhost/
  ... 
  ... [{"action":"Contact","method":"getInfo","data":[1],"type":"rpc","tid":4},{"action":"albumlist","method":"add","data":[],"type":"rpc","tid":5}]""")
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/javascript
  <BLANKLINE>
  [{"action": "Contact", "tid": 4, "type": "rpc", "method": "getInfo", "result": {"age": 23, "name": "Albert"}}, {"action": "albumlist", "tid": 5, "type": "rpc", "method": "add", "result": "ok"}]

  