yaws 1.62

Gaspar Chilingarov nm@REDACTED
Thu Jun 8 07:10:38 CEST 2006

Great, we got new release of yaws! :)))

Let me explain a little bit about added -RPC handlers.

Changes involve 4 files --
o  json.erl    -- generic encoder from erlang strauctures to string
	          and visa versa
o  jsonrpc.erl -- module to make json rpc calls to remote handlers
o  yaws_jsonrpc.erl -- handlers to dispatch JSONRPC calls
o  yaws_xmlrpc.erl  -- handlers to dispatch XMLRPC calls

Now let go deeper in the code:

json:encode(RPCData) -> string() -- encode erlang structures to string

RPCData -> int() | string() | bool() | {array, [RPCData,...]} |
           {struct, [ {atom(), RPCData}, ... ]

Thus any deep nested structure can be described by such notation.
Structures are represented as objects in javascript and arrays - just
map to arrays with numeric indexes.

json:decode_string(String) ->  RPCData -- parse string and convert to
				          erlang structures

Both of function crash on encode/decode errors.

jsonrpc:call(URL, Options, Payload) ->
     {ok,{response, Result}} |
     {error, ErrDescr}

URL -> string()
Options -> [] | OptionList
Payload -> {call, MethodName, Arguments}
MethodName -> atom()
Arguments -> [ RPCData, ... ]
Result -> [ RPCData ]

jsonrpc:call makes synchronous call to remote JSONRPC server.
HTTP is used for calling remote servers, so you should specify URL where
server resides. All calls are made by http:request function, so Options
are passed there without changes.

Payload argument specifies which method should be called on remote side
and with which arguments. Note, that passing several items in Arguments
list means that you invoke remote procedure with multiple arguments.

Result is returned also as a single element list with RPCData data type
- i.e. structures/arrays/etc.

Such format of calling arguments and returning is made to have full
interface compatibility with xmlrpc module

jsonrpc:call returns errors for some conditions - i.e. timeouts and so
on and crashes in case if it cannot encode/decode payload structures.

Now the most interesting part - writing handlers for RPC calls.
Writing xmlrpc and jsonrpc handlers is quite the same - even same
handlers can handle requests from both protocols.

Dispatching rpc calls without keeping state between calls:

In out() function you should have following code:

yaws_jsonrpc:handler(A, {M, F}).
A -> args()
M, F -> atom()

A is an yaws #args record (we need post request payload from there),
M,F is a tuple pointing to handler module:function . passing funs is not
supported yet - but it should be very easy to add.

When yaws_jsonrpc:handler is invoked it checks that we got a POST
request, parses POST request text to Payload, extracts from there
method name and arguments and invokes
M:F(State, {call, MethodName, Arguments}) -> Result handler.

State -> term()
MethodName -> atom(),
Arguments -> RPCData()
Result -> {false, ResponsePayload}

MehodName and Arguments comes from remote side,
State comes from A#args.state fields - so if you with to pass some data
from yaws page to RPC handler - you should put it there before calling
yaws_jsonrpc:handler .

As handler execution is completed  yaws_jsonrpc:handler encodes
resulting payload to tuple which is ready to be returned to yaws.

Stateless handlers/2 are 100% compatible with xmlrpc module handlers,
which allows smooth transition.

Handlers which use sessions are more interesting, but they are not
compatible with original xmlrpc module interface - only yaws_jsonrpc and
yaws_xmlrpc will support them. The sessions are cookie based, they use
yaws build-in session server functionality and allow rpc handlers keep
data on server side between calls.

There is 2 functions to support them
yaws_jsonrpc:handler_session(Args, {M, F})
yaws_jsonrpc:handler_session(Args, {M, F}, CookieName)

CookieName -> atom() | string()

handler_session/2 uses ookie with the name "SID".

In case of sessions callback handler should take 3 parameters

M:F(State, {call, MethodName, Arguments}, Session) -> Result

State -> term()
MethodName -> atom(),
Arguments -> RPCData()
Session -> undefined | SessionData
SessionData -> term()

Result -> {false, ResponsePayload} |
          {true, _NewTimeout, NewSessionValue, ResponsePayload}

_NewTimeout -> any value (just for compatibility)
NewSessionValue -> undefined | term()
ResponsePayload -> {response, RPCData}

At the first call Session value is `undefined` and handler can decide
what to do with session. Returning {false, Response} does not change
session state at all. If handler returns some data in NewSessionValue
argument then session is started (in this case cookie returned to
client) or session data updates. If handler with to stay without session
or destroy old session - it sould return `undefined` atom, which will
cause destroying cookie session in yaws.

yaws_(xml|json)rpc:handler* functions will crash in case it handler
cannot be found, illegal return values are generated or if payloads
cannot be encoded/decoded.

That's it - it's really easy to implement RPC server using this modules
and it provides convenient way to fully use yaws strength without
inventing the bicycle. Also it solves problems with javascript security
model -- because javascript functions may post data only to URLs at same
protocol/host/port combination from where js file was loaded.

You are welcome to try this modules and submit your suggestions and ideas.

With best regards, Gaspar
Gaspar Chilingarov

System Administrator,
Network security consulting

t +37493 419763 (mob)
i 63174784

More information about the erlang-questions mailing list