1%% -*- coding: latin-1 -*- 2%% Copyright (C) 2003 Joakim Greben� <jocke@gleipnir.com>. 3%% All rights reserved. 4%% 5%% Copyright (C) 2006 Gaspar Chilingarov <nm@web.am> 6%% Gurgen Tumanyan <barbarian@armkb.com> 7%% All rights reserved. 8%% 9%% 10%% Redistribution and use in source and binary forms, with or without 11%% modification, are permitted provided that the following conditions 12%% are met: 13%% 14%% 1. Redistributions of source code must retain the above copyright 15%% notice, this list of conditions and the following disclaimer. 16%% 2. Redistributions in binary form must reproduce the above 17%% copyright notice, this list of conditions and the following 18%% disclaimer in the documentation and/or other materials provided 19%% with the distribution. 20%% 21%% THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 22%% OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23%% WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24%% ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25%% DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26%% DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 27%% GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28%% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 29%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 33%% NOTE: This module was originally called yaws_jsonrpc. 34%% It was hacked to transparently supports haXe remoting as well, 35%% hence its name was changed to the more generic 'yaws_rpc'. 36%% 37%% modified by Yariv Sadan (yarivvv@gmail.com) 38 39-module(yaws_rpc). 40-author("Gaspar Chilingarov <nm@web.am>, Gurgen Tumanyan <barbarian@armkb.com>"). 41-modified_by("Yariv Sadan <yarivvv@gmail.com>"). 42-modified_by("Steve Vinoski <vinoski@ieee.org>"). 43 44-export([handler/2]). 45-export([handler_session/2, handler_session/3]). 46 47%%-define(debug, 1). 48-include("yaws_debug.hrl"). 49-include("../include/yaws_api.hrl"). 50 51%%% ###################################################################### 52%%% public interface 53%%% 54 55%%% 56%%% use rpc handler which can automagically start sessions if we need 57%%% 58handler_session(Args, Handler) -> 59 handler_session(Args, Handler, 'SID'). 60 61%%% 62%%% allow overriding session Cookie name 63%%% 64handler_session(Args, Handler, SID_NAME) when is_atom(SID_NAME) -> 65 handler_session(Args, Handler, atom_to_list(SID_NAME)); 66 67handler_session(Args, Handler, SID_NAME) -> 68 handler(Args, Handler, {session, SID_NAME}). % go to generic handler 69 70%%% 71%%% xmlrpc:handler compatible call 72%%% no session support will be available 73handler(Args, Handler) -> 74 handler(Args, Handler, simple). 75 76 77%%% ###################################################################### 78%%% private functions 79%%% 80 81%%% we should be called from yaws page or module 82handler(Args, Handler, Type) when is_record(Args, arg) -> 83 case parse_request(Args) of 84 ok -> 85 handle_payload(Args, Handler, Type); 86 {status, StatusCode} -> % cannot parse request 87 send(Args, StatusCode) 88 end. 89 90-define(ERROR_LOG(Reason), 91 error_logger:error_report({?MODULE, ?LINE, Reason})). 92 93-define(LOG(Reason), ?ERROR_LOG(Reason)). 94 95%%% 96%%% check that request come in reasonable protocol version and reasonable method 97%%% 98parse_request(Args) -> 99 Req = Args#arg.req, 100 case {Req#http_request.method, Req#http_request.version} of 101 {'POST', {1,0}} -> 102 ?Debug("HTTP Version 1.0~n", []), 103 ok; 104 {'POST', {1,1}} -> 105 ?Debug("HTTP Version 1.1~n", []), 106 ok; 107 {'POST', _HTTPVersion} -> 108 {status, 505}; 109 {_Method, {1,1}} -> 110 {status, 501}; 111 _ -> 112 {status, 400} 113 end. 114 115handle_payload(Args, Handler, Type) -> 116 RpcType = recognize_rpc_type(Args), 117 %% haXe parameters are URL encoded 118 PL = unicode:characters_to_list(Args#arg.clidata), 119 {Payload,DecodedStr} = 120 case RpcType of 121 T when T==haxe; T==json -> 122 ?Debug("rpc ~p call ~p~n", [T, PL]), 123 {PL, yaws_api:url_decode(PL)}; 124 soap_dime -> 125 [{_,_,_,Req}|As] = yaws_dime:decode(Args#arg.clidata), 126 {Args#arg.clidata, {binary_to_list(Req), As}}; 127 _ -> 128 ?Debug("rpc plaintext call ~p~n", [PL]), 129 {PL, PL} 130 end, 131 case decode_handler_payload(RpcType, DecodedStr) of 132 Batch when RpcType == json, is_list(Batch) -> 133 BatchRes = 134 lists:foldl( 135 fun(Req, Acc) -> 136 Result = check_decoded_payload(Args, Handler, 137 Req, Payload, 138 Type, json), 139 case Result of 140 empty -> 141 Acc; 142 {result, _Code, Send} -> 143 [Send|Acc]; 144 {send, S} -> 145 %% TODO: it would be better if 146 %% Result was never of the 147 %% {send, ...} variety because 148 %% it requires us to take the 149 %% content out via searching. 150 case lists:keysearch(content,1,S) of 151 {value, {content, _, Send}} -> 152 [Send|Acc]; 153 _ -> 154 Acc 155 end 156 end 157 end, [], Batch), 158 case BatchRes of 159 [] -> 160 %% all notifications, no replies 161 send(Args, 200, json); 162 _ -> 163 send(Args, 200, 164 "["++yaws:join_sep(lists:reverse(BatchRes),",")++"]", 165 [], json) 166 end; 167 NonBatch -> 168 Result = check_decoded_payload(Args, Handler, NonBatch, 169 Payload, Type, RpcType), 170 case Result of 171 {send, Send} -> 172 Send; 173 empty -> 174 send(Args, 200, RpcType); 175 {result, Code, Send} -> 176 send(Args, Code, Send, [], RpcType) 177 end 178 end. 179 180check_decoded_payload(Args, Handler, DecodedResult, Payload, Type, RpcType) -> 181 case DecodedResult of 182 {ok, DecodedPayload, ID} -> 183 ?Debug("client2erl decoded call ~p~n", [DecodedPayload]), 184 eval_payload(Args, Handler, DecodedPayload, Type, ID, RpcType); 185 {error, Reason} -> 186 ?ERROR_LOG({html, client2erl, Payload, Reason}), 187 case RpcType of 188 json -> 189 case Reason of 190 {ErrCode, _ErrString} -> 191 {result, 200, json_error(ErrCode)}; 192 ErrCode -> 193 {result, 200, json_error(ErrCode)} 194 end; 195 _ -> 196 {send, send(Args, 400, RpcType)} 197 end 198 end. 199 200%%% Identify the RPC type. We first try to recognize haXe by the 201%%% "X-Haxe-Remoting" HTTP header, then the "SOAPAction" header, 202%%% and if those are absent we assume the request is JSON. 203recognize_rpc_type(Args) -> 204 case (Args#arg.headers)#headers.content_type of 205 "application/dime" -> soap_dime; 206 _ -> 207 OtherHeaders = ((Args#arg.headers)#headers.other), 208 recognize_rpc_hdr([{X,Y,yaws:to_lower(Z),Q,W} || 209 {X,Y,Z,Q,W} <- OtherHeaders]) 210 end. 211 212recognize_rpc_hdr([{_,_,"x-haxe-remoting",_,_}|_]) -> haxe; 213recognize_rpc_hdr([{_,_,"soapaction",_,_}|_]) -> soap; 214recognize_rpc_hdr([_|T]) -> recognize_rpc_hdr(T); 215recognize_rpc_hdr([]) -> json. 216 217%%% 218%%% call handler/3 and provide session support 219eval_payload(Args, {M, F}, Payload, {session, CookieName}, ID, RpcType) -> 220 {SessionValue, Cookie} = 221 case yaws_api:find_cookie_val(CookieName, 222 (Args#arg.headers)#headers.cookie) of 223 [] -> %% have no session started, just call handler 224 {undefined, undefined}; 225 Cookie2 -> %% get old session data 226 case yaws_api:cookieval_to_opaque(Cookie2) of 227 {ok, OP} -> 228 {OP, Cookie2}; 229 {error, _ErrMsg} -> % cannot get corresponding session 230 {undefined, undefined} 231 end 232 end, 233 CbackFun = callback_fun(M, F, Args, Payload, SessionValue, RpcType), 234 case catch CbackFun() of 235 {'EXIT', {function_clause, _}} when RpcType == json -> 236 case ID of 237 undefined -> 238 %% empty HTTP reply for notification 239 empty; 240 _ -> 241 {result, 200, json_error(-32601, ID)} 242 end; 243 {'EXIT', Reason} -> 244 ?ERROR_LOG({M, F, {'EXIT', Reason}}), 245 {send, send(Args, 500, RpcType)}; 246 {error, Reason} -> 247 ?ERROR_LOG({M, F, Reason}), 248 {send, send(Args, 500, RpcType)}; 249 {error, Reason, Rc} -> 250 ?ERROR_LOG({M, F, Reason}), 251 {send, send(Args, Rc, Reason, [], RpcType)}; 252 {false, ResponsePayload} -> 253 %% do not have updates in session data 254 {send, encode_send(Args, 200, ResponsePayload, [], ID, RpcType)}; 255 {false, ResponsePayload, RespCode} -> 256 %% do not have updates in session data 257 {send, encode_send(Args,RespCode,ResponsePayload,[],ID,RpcType)}; 258 false -> % soap or json-rpc notify 259 empty; 260 {true, _NewTimeout, NewSessionValue, ResponsePayload} -> 261 %% be compatible with xmlrpc module 262 CO = handle_cookie(Cookie, CookieName, SessionValue, 263 NewSessionValue, M, F), 264 {send, encode_send(Args, 200, ResponsePayload, CO, ID, RpcType)}; 265 {true, _NewTimeout, NewSessionValue, ResponsePayload, RespCode} -> 266 %% be compatible with xmlrpc module 267 CO = handle_cookie(Cookie, CookieName, SessionValue, 268 NewSessionValue, M, F), 269 {send, encode_send(Args, RespCode, 270 ResponsePayload, CO, ID, RpcType)} 271 end; 272 273%%% 274%%% call handler/2 without session support 275%%% 276eval_payload(Args, {M, F}, Payload, simple, ID, RpcType) -> 277 case catch M:F(Args#arg.state, Payload) of 278 {'EXIT', Reason} -> 279 ?ERROR_LOG({M, F, {'EXIT', Reason}}), 280 {send, send(Args, 500)}; 281 {error, Reason} -> 282 ?ERROR_LOG({M, F, Reason}), 283 {send, send(Args, 500)}; 284 {false, ResponsePayload} -> 285 {send, encode_send(Args, 200, ResponsePayload, [], ID, RpcType)}; 286 false -> % Soap notify 287 {send, send(Args, 200, RpcType)}; 288 {true, _NewTimeout, _NewState, ResponsePayload} -> 289 {send, encode_send(Args, 200, ResponsePayload, [], ID, RpcType)} 290 end. 291 292handle_cookie(Cookie, CookieName, SessionValue, NewSessionValue, M, F) -> 293 case NewSessionValue of 294 undefined when Cookie == undefined -> []; % nothing to do 295 undefined -> % rpc handler requested session delete 296 yaws_api:delete_cookie_session(Cookie), []; 297 %% XXX: may be return set-cookie with empty val? 298 _ -> 299 %% any other value will stored in session 300 case SessionValue of 301 undefined -> 302 %% got session data and should start new session now 303 Cookie1 = yaws_api:new_cookie_session(NewSessionValue), 304 case get_expire(M, F) of 305 false -> 306 yaws_api:setcookie(CookieName, Cookie1, "/"); 307 %% return set_cookie header 308 Expire -> 309 yaws_api:setcookie(CookieName, Cookie1, "/",Expire) 310 %% return set_cookie header 311 end; 312 _ -> 313 yaws_api:replace_cookie_session(Cookie, NewSessionValue), 314 [] % nothing to add to yaws data 315 end 316 end. 317 318%%% Make it possible for callback module to set Cookie Expire string! 319get_expire(M, F) -> 320 case catch M:F(cookie_expire) of 321 Expire when is_list(Expire) -> Expire; 322 _ -> false 323 end. 324 325callback_fun(M, F, Args, Payload, SessionValue, RpcType) 326 when RpcType =:= soap; RpcType =:= soap_dime -> 327 fun() -> yaws_soap_srv:handler(Args, {M,F}, Payload, SessionValue) end; 328callback_fun(M, F, Args, Payload, SessionValue, _RpcType) -> 329 fun() -> M:F(Args#arg.state, Payload, SessionValue) end. 330 331%%% XXX compatibility with XMLRPC handlers 332%%% XXX - potential bug here? 333encode_send(Args, StatusCode, [Payload], AddOn, ID, RpcType) -> 334 encode_send(Args, StatusCode, Payload, AddOn, ID, RpcType); 335 336encode_send(Args, StatusCode, Payload, AddOn, ID, RpcType) -> 337 ?Debug("rpc response ~p ~n", [Payload]), 338 case encode_handler_payload(Payload, ID, RpcType) of 339 {ok, EncodedPayload, NewRpcType} -> 340 ?Debug("rpc encoded response ~p ~n", [EncodedPayload]), 341 send(Args, StatusCode, EncodedPayload, AddOn, NewRpcType); 342 {ok, EncodedPayload} -> 343 ?Debug("rpc encoded response ~p ~n", [EncodedPayload]), 344 send(Args, StatusCode, EncodedPayload, AddOn, RpcType) 345 end. 346 347send(Args, StatusCode) -> 348 send(Args, StatusCode, json). 349 350send(Args, StatusCode, RpcType) -> 351 send(Args, StatusCode, "", [], RpcType). 352 353send(Args, StatusCode, Payload, AddOn, RpcType) when not is_list(AddOn) -> 354 send(Args, StatusCode, Payload, [AddOn], RpcType); 355send(Args, StatusCode, Payload, AddOnData, RpcType) -> 356 [{status, StatusCode}, 357 content_hdr(RpcType, Args, Payload), 358 {header, {content_length, lists:flatlength(Payload)}}] ++ AddOnData. 359 360content_hdr(json, _Args, Payload) -> {content, "application/json", Payload}; 361content_hdr(soap, Args, Payload) -> 362 CallerContentType = (Args#arg.headers)#headers.content_type, 363 %% drop caller charset info if present, may not 364 %% be appropriate for the response 365 ContentType = hd(string:tokens(CallerContentType, ";")), 366 {content, ContentType, Payload}; 367content_hdr(_, _Args, Payload) -> {content, "text/xml", Payload}. 368 369encode_handler_payload({Xml,[]}, _ID, soap_dime) -> 370 {ok, Xml, soap}; 371encode_handler_payload({Xml,As}, _ID, soap_dime) -> 372 EncodedPayload = yaws_dime:encode(Xml, As), 373 {ok, EncodedPayload}; 374encode_handler_payload(Xml, _ID, soap_dime) -> 375 {ok, Xml, soap}; 376encode_handler_payload({Xml,[]}, _ID, soap) -> 377 {ok, Xml}; 378encode_handler_payload({Xml,As}, _ID, soap) -> 379 EncodedPayload = yaws_dime:encode(Xml, As), 380 {ok, EncodedPayload, soap_dime}; 381encode_handler_payload(Xml, _ID, soap) -> 382 {ok, Xml}; 383encode_handler_payload({error, [ErlStruct]}, ID, RpcType) -> 384 encode_handler_payload({error, ErlStruct}, ID, RpcType); 385encode_handler_payload({error, ErlStruct}, ID, RpcType) -> 386 StructStr = 387 case RpcType of 388 json -> json2:encode({struct, [{id, ID}, {error, ErlStruct}, 389 {"jsonrpc", "2.0"}]}); 390 haxe -> [$h, $x, $r | haxe:encode({exception, ErlStruct})] 391 end, 392 {ok, StructStr}; 393encode_handler_payload({response, [ErlStruct]}, ID, RpcType) -> 394 encode_handler_payload({response, ErlStruct}, ID, RpcType); 395encode_handler_payload({response, ErlStruct}, ID, RpcType) -> 396 StructStr = 397 case RpcType of 398 json -> json2:encode({struct, [{result, ErlStruct}, {id, ID}, 399 {"jsonrpc", "2.0"}]}); 400 haxe -> [$h, $x, $r | haxe:encode(ErlStruct)] 401 end, 402 {ok, StructStr}. 403 404decode_handler_payload(json, JSonStr) -> 405 try 406 {ok, Obj} = json2:decode_string(JSonStr), 407 decode_handler_payload_json(Obj) 408 catch 409 error:Err -> 410 ?ERROR_LOG({json_decode, JSonStr, Err}), 411 {error, {-32700, Err}} 412 end; 413decode_handler_payload(haxe, [$_, $_, $x, $= | HaxeStr]) -> 414 try 415 {done, {ok, {array, [MethodName | _]}}, Cont} = haxe:decode(HaxeStr), 416 {done, {ok, Args}, _Cont2} = haxe:decode_next(Cont), 417 418 %% ID is undefined because haXe remoting doesn't automagically handle 419 %% sessions. 420 {ok, {call, list_to_atom(MethodName), Args}, undefined} 421 catch 422 error:Err -> {error, Err} 423 end; 424decode_handler_payload(haxe, _HaxeStr) -> 425 {error, missing_haxe_prefix}; 426 427decode_handler_payload(soap_dime, Payload) -> 428 {ok, Payload, undefined}; 429decode_handler_payload(soap, Payload) -> 430 {ok, Payload, undefined}. 431 432decode_handler_payload_json({struct, _}=Obj) -> 433 case jsonrpc:s(Obj, method) of 434 undefined -> 435 {error, -32600}; 436 Method0 when is_list(Method0) -> 437 Method = case jsonrpc:s(Obj, jsonrpc) of 438 "2.0" -> 439 try 440 list_to_existing_atom(Method0) 441 catch 442 error:badarg -> 443 Method0 444 end; 445 undefined -> 446 list_to_atom(Method0) 447 end, 448 Args = jsonrpc:s(Obj, params), 449 ArgsOk = case Args of 450 {struct, _} -> true; 451 {array, _} -> true; 452 undefined -> true; 453 _ -> false 454 end, 455 case ArgsOk of 456 true -> 457 ID = jsonrpc:s(Obj, id), 458 CallOrNotify = case ID of 459 undefined -> 460 notification; 461 _ -> 462 call 463 end, 464 {ok, {CallOrNotify, Method, Args}, ID}; 465 false -> 466 {error, -32602} 467 end; 468 _ -> 469 {error, -32600} 470 end; 471decode_handler_payload_json({array, []}) -> 472 {error, -32600}; 473decode_handler_payload_json({array, Batch}) -> 474 [decode_handler_payload_json(Obj) || Obj <- Batch]; 475decode_handler_payload_json(_) -> 476 {error, -32600}. 477 478json_error(ErrCode) -> 479 json_error(ErrCode, null). 480json_error(ErrCode, Id) -> 481 Err = {struct, [{"jsonrpc", "2.0"}, 482 {"id", Id}, 483 {"error", {struct, 484 [{"code", ErrCode}, 485 {"message", json_error_message(ErrCode)}]}}]}, 486 json2:encode(Err). 487 488json_error_message(-32700) -> "parse error"; 489json_error_message(-32600) -> "invalid request"; 490json_error_message(-32601) -> "method not found"; 491json_error_message(-32602) -> "invalid params"; 492json_error_message(-32603) -> "internal error"; 493json_error_message(Code) when Code >= -32099, Code =< -32000 -> "server error"; 494json_error_message(_) -> "json error". 495