Fetching contributors…
Cannot retrieve contributors at this time
410 lines (348 sloc) 16.7 KB
box(Str) ->
{pre,[], yaws_api:htmlize(Str)}}.
tbox(T) ->
ssi(File) ->
{ssi, File,[],[]}}}.
ss(A, File) ->
{ok, B} = file:read_file(
filename:join([A#arg.docroot, File])),
out(A) ->
[{ssi, "", "%%",[{"soap_intro", "choosen"}]},
{'div', [{id, "entry"}],
[{h1, [], "SOAP with Yaws"},
{p, [],
["SOAP is an XML-based protocol for communication over a network "
"connection. The main focus of SOAP is remote procedure calls (RPCs) "
"transported via HTTP. "
"SOAP is similar to XML-RPC but makes use of XML Schema to define "
"the data types it uses. "
{h2, [], "Preparations"},
{p, [],
["Yaws uses the 'erlsom' XML Schema "
"parser and some SOAP specific library code. "
"Thus, to be able to use SOAP with Yaws you need to have "
"'erlsom' installed. Currently, the easiest way of installing "
"'erlsom' is to check out the library from and "
"install it from there (you can also download a released version "
"of erlsom and install it)."]},
{p,[],"To install 'erlsom' do:"},
box("git clone\n"
"cd erlsom; chmod a+x configure; ./configure; make\n"
"sudo make install # iff you want to install as root\n"
{p, [],
"<b>Important:</b> The SOAP-specific code that makes use of erlsom has some limitations "
"that it is important to be aware of. Only the Soap <i>'document'</i> binding "
"style is supported. There is no support for non-soap bindings, nor for the "
"RPC binding style. Also, only the <i>'literal'</i> encoding is supported "
"There is no support for <i>'soap-encoding'</i>. For an explanation of the differences "
"between these concepts, see this "
"<a href=\"\">description</a>."},
{h2, [], "The SOAP client side"},
{p, [],
["The SOAP interface is defined by a WSDL specification, which "
"simply is a (rather verbose) piece of XML document that specifies "
"the public interface for a web service. As a client, "
"we don't need to understand everything in the WSDL specification "
"The parts that are most interesting is the name of the operation "
"we want to perform (i.e the function we want to call) and what "
"input data it expects."
["As an example, lets have a look at a public SOAP service that "
"returns some weather data for the location we send to it. "
"The WSDL specification can be found here: ",
{a, [{href, ""}],
" "}
["We start by finding the operation named: ",
{i, [], "GetWeatherByPlaceName, "},
"which is the operation we want to invoke. As can be seen, we have "
"one input message and one output message defined. The input message is "
"the one we (as a client) will send to the server. "
box("<wsdl:operation name=\"GetWeatherByPlaceName\">\n"
" <documentation>\n"
" Get one week weather forecast for a place name(USA)\n"
" </documentation>\n"
" <wsdl:input message=\"tns:GetWeatherByPlaceNameSoapIn\"/>\n"
" <wsdl:output message=\"tns:GetWeatherByPlaceNameSoapOut\"/>\n"
["Now, follow the reference to the message: ",
{i, [], "tns:GetWeatherByPlaceNameSoapIn, "},
"to where it is defined: "
box("<wsdl:message name=\"GetWeatherByPlaceNameSoapIn\">\n"
"<wsdl:part name=\"parameters\" element=\"tns:GetWeatherByPlaceName\"/>\n"
["Continue by following the reference to: ",
{i, [], "tns:GetWeatherByPlaceName, "},
"and you will end up with an XML Schema type definition: "
box("<s:element name=\"GetWeatherByPlaceName\">\n"
"<s:element minOccurs=\"0\" maxOccurs=\"1\" name=\"PlaceName\" type=\"s:string\"/>\n"
"This tells us that the function we want to call takes one argument "
"of a string type (which apparently denotes a Name of a place in the US). "
"Left for us is just to call the function from an Erlang shell which has "
"got the Yaws ebin directory in the path:"
box("1> inets:start().\n"
"2> yaws_soap_lib:call(\n"
" \"\",\n"
" \"GetWeatherByPlaceName\",\n"
" [\"Boston\"]).\n"
" [{'p:GetWeatherByPlaceNameResponse',\n"
" [],\n"
" {'p:WeatherForecasts',[],\n"
" \"40.3044128\",\n"
" \"79.81284\",\n"
" \"0.000453\",\n"
" \"42\",\n"
" \"BOSTON\",\n"
" \"PA\",\n"
" undefined,\n"
" {'p:ArrayOfWeatherData',\n"
" [],\n"
" [{'p:WeatherData',\n"
" [],\n"
" \"Friday, December 08, 2006\"|...},\n"
" .....\n"),
"So what happened here? We specified the URL to the WSDL file. "
"The yaws_soap_lib:call/3 function then went to retrieve the file, parsed it, "
"created a proper message, sent off the message, waited for the "
"reply and finally returned a parsed reply as Erlang records. "
"Even though this is very convenient, we probably want do more than just one call "
"to the web service. So to avoid retrieving and parsing the WSDL file for every "
"call. We can do it in two steps: "
box("1> inets:start().\n"
"2> Wsdl = yaws_soap_lib:initModel(\n"
" \"\").\n"
"3> yaws_soap_lib:call(\n"
" Wsdl,\n"
" \"GetWeatherByPlaceName\"\n"
" [\"Boston\"]).\n"
"To be able to work with the records that we get in the response, "
"we can create a header file that we can include in our source code. In our example "
"the generated '.hrl' file will look like this: "
box("4> yaws_soap_lib:write_hrl(Wsdl, \"/tmp/wfc.hrl\").\n"
"5> {ok,Bin}=file:read_file(\"/tmp/wfc.hrl\"),io:fwrite(binary_to_list(Bin)).\n"
"-record('soap:detail', {anyAttribs, choice}).\n"
"-record('soap:Fault', {anyAttribs, 'faultcode', 'faultstring', 'faultactor', 'detail'}).\n"
"-record('soap:Body', {anyAttribs, choice}).\n"
"-record('soap:Header', {anyAttribs, choice}).\n"
"-record('soap:Envelope', {anyAttribs, 'Header', 'Body', choice}).\n"
"-record('p:GetWeatherByPlaceNameResponse', {anyAttribs, 'GetWeatherByPlaceNameResult'}).\n"
"-record('p:GetWeatherByPlaceName', {anyAttribs, 'PlaceName'}).\n"
"-record('p:WeatherData', {anyAttribs, 'Day', 'WeatherImage', 'MaxTemperatureF', \n"
" 'MinTemperatureF', 'MaxTemperatureC', 'MinTemperatureC'}).\n"
"-record('p:ArrayOfWeatherData', {anyAttribs, 'WeatherData'}).\n"
"-record('p:WeatherForecasts', {anyAttribs, 'Latitude', 'Longitude', 'AllocationFactor', \n"
" 'FipsCode', 'PlaceName', 'StateCode', 'Status', 'Details'}).\n"
"-record('p:GetWeatherByZipCodeResponse', {anyAttribs, 'GetWeatherByZipCodeResult'}).\n"
"-record('p:GetWeatherByZipCode', {anyAttribs, 'ZipCode'}).\n"
"As you can see, every record in our header has an XML namespace prefix prepended "
"in the name of the record. The prefix 'p' as shown above is the default prefix you'll "
"get if you don't specify a prefix yourself. This is probably good enough, but if you "
"want to set it to something else, you can do it as shown below:"
box("6> yaws_soap_lib:initModel(... , \"foo\"). % foo is my prefix\n"
"7> yaws_soap_lib:write_hrl(... , ... , \"foo\").\n"
["Some final notes:",
{ul, [],
[{li, [],
"The \"http://...\" URL given as the first argument to the "
"functions above may as well be a local file, and thus written as \"file://....\". "},
{li, [],
"When we retrieve a HTTP located file, we will use 'ibrowse' if it exist "
"in the code path. Otherwise we will use the OTP 'http' client."},
{li, [],
"The prefix ('foo' in the example above) is passed to erlsom - it is one of erlsom's "
"options. If you want to specify other options, you can also pass the regular erlsom "
"options to yaws_soap_lib:initModel/2 and yaws_soap_lib:write_hrl/3. For example, to "
"specify how files that the XSD inside the WSDL refers to via 'include' statements "
"can be retrieved, you can pass it a function GetIncludes/4 by specifying "
"[{include_fun, GetIncludes}]. See the erlsom documentation for other options that you "
"could specify."}
{h2, [], "The SOAP server side"},
"If we want to run our own weather service we need to take the WSDL "
"and add our own location to it. Either we can just study the WSDL file to "
"see which URL we need to change in the 'service' part of the document, or "
"we can make use of some nice access functions that work on the "
"#wsdl{} record that yaws_soap_lib:initModel/2 returned, as shown below: "
box("8> Ops = yaws_soap_lib:wsdl_operations(Wsdl).\n"
"9> {ok,Op} = yaws_soap_lib:get_operation(Ops, \"GetWeatherByPlaceName\").\n"
"10> yaws_soap_lib:wsdl_op_address(Op).\n"
"Now, edit the WSDL file and change the above URL to something like this:"
box("<wsdl:service name=\"WeatherForecast\">\n"
" <documentation xmlns=......\n"
" <wsdl:port name=\"WeatherForecastSoap\".....\n"
" <soap:address location=\"http://localhost:8181/WeatherForecast.yaws\" />\n"
" </wsdl:port>\n"
"Next, start an Erlang shell and start Yaws with SOAP enabled. We need to write "
"the code that returns the weather info. This is done in a callback module that "
"the Yaws SOAP code will call with the incoming message. The message will be an "
"Erlang record and what we return must also be an Erlang record. So we will need "
"to create a .hrl containing the record definitions that we can include: "
box("1> Docroot = \"/tmp\".\n"
"2> GL = [{enable_soap,true}, % <== THIS WILL ENABLE SOAP IN A YAWS SERVER!!\n"
" {trace, false},\n"
" {tmpdir,Docroot},{logdir,Docroot},\n"
" {flags,[{tty_trace, false},{copy_errlog, true}]}].\n"
"3> SL = [{port,8181},{servername,\"localhost\"},{dir_listings, true},\n"
" {listen,{127,0,0,1}},{flags,[{auth_log,false},{access_log,false}]}].\n"
"4> yaws_soap_lib:write_hrl(\"file:///tmp/MyWeatherService.wsdl\", \"/tmp/my_soap.hrl\").\n"
"5> code:add_path(Docroot).\n"
"We continue by writing our weather forecast callback module:"
box("# cat /tmp/my_soap.erl\n"
"-include(\"my_soap.hrl\"). % .hrl file generated by erlsom\n"
" [#'p:GetWeatherByPlaceName'{'PlaceName' = Place}],\n"
" _Action, \n"
" _SessionValue) ->\n"
" {ok, undefined, get_weather_info(Place)}.\n"
"get_weather_info(Place) ->\n"
" WeatherData =\n"
" #'p:WeatherData'{anyAttribs = [],\n"
" 'Day' = \"Sunday, December 10, 2006\",\n"
" 'WeatherImage' = \"\",\n"
" 'MaxTemperatureF' = \"51\",\n"
" 'MinTemperatureF' = \"28\",\n"
" 'MaxTemperatureC' = \"11\",\n"
" 'MinTemperatureC' = \"-2\"\n"
" },\n"
" ArrayOfWeatherData =\n"
" #'p:ArrayOfWeatherData'{anyAttribs = [],\n"
" 'WeatherData' = [WeatherData]\n"
" },\n"
" Forecast =\n"
" #'p:WeatherForecasts'{anyAttribs = [],\n"
" 'Latitude' = \"40.3044128\",\n"
" 'Longitude' = \"79.81284\",\n"
" 'AllocationFactor' = \"0.000453\",\n"
" 'FipsCode' = \"42\",\n"
" 'PlaceName' = Place,\n"
" 'StateCode' = \"PA\",\n"
" 'Status' = undefined,\n"
" 'Details' = ArrayOfWeatherData\n"
" },\n"
" Response =\n"
" #'p:GetWeatherByPlaceNameResponse'{anyAttribs = [],\n"
" 'GetWeatherByPlaceNameResult' = Forecast\n"
" },\n"
" [Response]. \n"
"The final piece on the server side is the '.yaws' file that invokes the "
"Yaws SOAP server (note that we are using the same way of hooking in our "
"callback module as for Json and HaXe):"
box("# cat /tmp/WeatherForecast.yaws\n"
"out(A) ->\n"
" yaws_rpc:handler_session(A, {my_soap, handler}).\n"
"Now, in your Yaws shell, setup the Soap server as shown below. (If required, for "
"example to specify a prefix or a function to retrieve included files, you can specify "
"options similar to what we saw above for yaws_soap_lib:initModel/2 and "
"yaws_soap_lib:write_hrl/3 , using yaws_soap_srv:setup/3.)"
box("6> yaws:start_embedded(Docroot,SL,GL).\n"
"=INFO REPORT==== 29-Nov-2008::20:03:50 ===\n"
"Yaws: Listening to for servers\n"
" - http://localhost:8181 under /tmp\n"
"7> yaws_soap_srv:setup({my_soap, handler}, \"file:///tmp/MyWeatherService.wsdl\").\n"
"We are now ready to try it out. Start another Erlang shell and call it: "
box("1> inets:start().\n"
"2> yaws_soap_lib:call(\"file:///tmp/MyWeatherService.wsdl\",\n"
" \"GetWeatherByPlaceName\",\n"
" [\"Stockholm\"]).\n"
" [{'p:GetWeatherByPlaceNameResponse', [],\n"
" {'p:WeatherForecasts',[],\n"
" \"40.3044128\",\n"
" \"79.81284\",\n"
" \"0.000453\",\n"
" \"42\",\n"
" \"Stockholm\", % <=== Yippie, it works !!\n"
" \"PA\",\n"
" undefined,\n"
" {'p:ArrayOfWeatherData', [],\n"
" [{'p:WeatherData', [],\n"
" \"Sunday, December 10, 2006\"|...}]}}}]}\n"
"There you have it! "
{ssi, "END2",[],[]}