Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

274 lines (239 sloc) 11.134 kb
%%%-------------------------------------------------------------------
%%% Created : 29 Nov 2006 by Torbjorn Tornkvist <tobbe@tornkvist.org>
%%% Author : Willem de Jong (w.a.de.jong@gmail.com).
%%% Desc : A SOAP server.
%%%-------------------------------------------------------------------
-module(yaws_soap_srv).
-behaviour(gen_server).
%% API
-export([start_link/0, start_link/1,
setup/1, setup/2, setup/3,
handler/4
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("../include/yaws_api.hrl").
-include("../include/yaws.hrl").
-include("../include/soap.hrl").
-define(SERVER, ?MODULE).
%% State
-record(s, {
wsdl_list = [] % list of {Id, WsdlModel} tuples, where Id == {M,F}
}).
-define(OK_CODE, 200).
-define(BAD_MESSAGE_CODE, 400).
%% -define(METHOD_NOT_ALLOWED_CODE, 405).
-define(SERVER_ERROR_CODE, 500).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
start_link([]).
start_link(L) ->
%% We are dependent on erlsom
case code:ensure_loaded(erlsom) of
{error, _} ->
Emsg = "could not load erlsom",
error_logger:error_msg("~p: exiting, reason: ~s~n",
[?MODULE, Emsg]),
{error, Emsg};
{module, erlsom} ->
gen_server:start_link({local, ?SERVER}, ?MODULE, L, [])
end.
%%% To be called from yaws_rpc.erl
%%% Return according to yaws_rpc:eval_payload/6
handler(Args, Id, Payload, SessionValue) ->
Headers = Args#arg.headers,
SoapAction = yaws_soap_lib:findHeader("SOAPAction", Headers#headers.other),
case gen_server:call(?SERVER, {request, Id, Payload,
SessionValue, SoapAction}, infinity) of
{ok, XmlDoc, ResCode, undefined} ->
{false, XmlDoc, ResCode};
{ok, XmlDoc, ResCode, SessVal} ->
{true, 0, SessVal, XmlDoc, ResCode};
{error, _, _} = Error ->
Error;
false ->
false % soap notify
end.
%% Setup a SOAP interface according to the config file.
setup(_ConfigFile) ->
tbd.
setup(Id, WsdlFile) when is_tuple(Id),size(Id)==2 ->
Wsdl = yaws_soap_lib:initModel(WsdlFile),
gen_server:call(?SERVER, {add_wsdl, Id, Wsdl}, infinity).
%% PrefixOrOptions can be either a prefix (a String) or a property
%% list. It is used to construct the options that are passed to Erlsom
%% to compile the WSDL file. Passing a string ("Prefix") is equivalent
%% to [{prefix, "Prefix"}].
%% If a list of erlsom options is passed, and this does not contain
%% the {prefix, ...} option, the yaws_soap default ("p") will be used.
setup(Id, WsdlFile, PrefixOrOptions) when is_tuple(Id),size(Id)==2 ->
Wsdl = yaws_soap_lib:initModel(WsdlFile, PrefixOrOptions),
gen_server:call(?SERVER, {add_wsdl, Id, Wsdl}, infinity).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init(L) -> %% [ {{Mod,Handler}, WsdlFile} ]
WsdlList = lists:foldl( fun( SoapSrvMod, OldList) ->
setup_on_init( SoapSrvMod, OldList )
end,[],L),
{ok, #s{wsdl_list = WsdlList}}.
setup_on_init( {Id, WsdlFile}, OldList ) when is_tuple(Id),size(Id) == 2 ->
Wsdl = yaws_soap_lib:initModel(WsdlFile),
uinsert({Id, Wsdl}, OldList);
setup_on_init( {Id, WsdlFile, Prefix}, OldList ) when is_tuple(Id),
size(Id) == 2 ->
Wsdl = yaws_soap_lib:initModel(WsdlFile, Prefix),
uinsert({Id, Wsdl}, OldList).
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({add_wsdl, Id, WsdlModel}, _From, State) ->
NewWsdlList = uinsert({Id, WsdlModel}, State#s.wsdl_list),
{reply, ok, State#s{wsdl_list = NewWsdlList}};
%%
handle_call( {request, Id, Payload, SessionValue, SoapAction}, _From, State) ->
Reply = request(State, Id, Payload, SessionValue, SoapAction),
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
request(State, {M,F} = Id, {Req, Attachments}, SessionValue, Action) ->
{ok, Model} = get_model(State, Id),
%%error_logger:info_report([?MODULE, {payload, Req}]),
case catch yaws_soap_lib:parseMessage(Req, Model) of
{ok, Header, Body} ->
%% call function
result(Model, catch apply(M, F, [Header, Body,
Action, SessionValue,
Attachments]));
{error, Error} ->
cli_error(Error);
OtherError ->
srv_error(io_lib:format("Error parsing message: ~p", [OtherError]))
end;
request(State, {M,F} = Id, Req, SessionValue, Action) ->
%%error_logger:info_report([?MODULE, {payload, Req}]),
{ok, Model} = get_model(State, Id),
Umsg = (catch erlsom_lib:toUnicode(Req)),
case catch yaws_soap_lib:parseMessage(Umsg, Model) of
{ok, Header, Body} ->
%% call function
result(Model, catch apply(M, F, [Header, Body,
Action, SessionValue]));
{error, Error} ->
cli_error(Error);
OtherError ->
srv_error(io_lib:format("Error parsing message: ~p", [OtherError]))
end.
%%% Analyse the result and produce some output
result(Model, {ok, ResHeader, ResBody, ResCode, SessVal}) ->
return(Model, ResHeader, ResBody, ResCode, SessVal, undefined);
result(Model, {ok, ResHeader, ResBody}) ->
return(Model, ResHeader, ResBody, ?OK_CODE, undefined, undefined);
result(Model, {ok, ResHeader, ResBody, Files}) ->
return(Model, ResHeader, ResBody, ?OK_CODE, undefined, Files);
result(_Model, {error, client, ClientMssg}) ->
cli_error(ClientMssg);
result(_Model, false) -> % soap notify !
false;
result(_Model, Error) ->
srv_error(io_lib:format("Error processing message: ~p", [Error])).
return(#wsdl{model = Model}, ResHeader, ResBody, ResCode, SessVal, Files) ->
return(Model, ResHeader, ResBody, ResCode, SessVal, Files);
return(Model, ResHeader, ResBody, ResCode, SessVal, Files)
when not is_list(ResBody) ->
return(Model, ResHeader, [ResBody], ResCode, SessVal, Files);
return(Model, ResHeader, ResBody, ResCode, SessVal, Files) ->
%% add envelope
Header2 = case ResHeader of
undefined -> undefined;
_ -> #'soap:Header'{choice = ResHeader}
end,
Envelope = #'soap:Envelope'{'Body' = #'soap:Body'{choice = ResBody},
'Header' = Header2},
case catch erlsom:write(Envelope, Model) of
{ok, XmlDoc} ->
case Files of
undefined ->
{ok, XmlDoc, ResCode, SessVal};
_ ->
DIME = yaws_dime:encode(XmlDoc, Files),
{ok, DIME, ResCode, SessVal}
end;
{error, WriteError} ->
srv_error(f("Error writing XML: ~p", [WriteError]));
OtherWriteError ->
error_logger:error_msg("~p(~p): OtherWriteError=~p~n",
[?MODULE, ?LINE, OtherWriteError]),
srv_error(f("Error writing XML: ~p", [OtherWriteError]))
end.
f(S,A) -> lists:flatten(io_lib:format(S,A)).
cli_error(Error) ->
error_logger:error_msg("~p(~p): Cli Error: ~p~n",
[?MODULE, ?LINE, Error]),
Fault = yaws_soap_lib:makeFault("Client", "Client error"),
{error, Fault, ?BAD_MESSAGE_CODE}.
srv_error(Error) ->
error_logger:error_msg("~p(~p): Srv Error: ~p~n",
[?MODULE, ?LINE, Error]),
Fault = yaws_soap_lib:makeFault("Server", "Server error"),
{error, Fault, ?SERVER_ERROR_CODE}.
get_model(State, Id) ->
case lists:keysearch(Id, 1, State#s.wsdl_list) of
{value, {_, Model}} -> {ok, Model};
_ -> {error, "model not found"}
end.
uinsert({K,_} = E, [{K,_}|T]) -> [E|T];
uinsert(E, [H|T]) -> [H|uinsert(E,T)];
uinsert(E, []) -> [E].
Jump to Line
Something went wrong with that request. Please try again.