Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 360 lines (296 sloc) 12.489 kB
c332fc2 Adding SOAP processing capabilities to Yaws.
Tobbe Tornquist authored
1
2 <erl>
3
4
5 box(Str) ->
6 {'div',[{class,"box"}],
7 {pre,[], yaws_api:htmlize(Str)}}.
8
9 tbox(T) ->
10 box(lists:flatten(io_lib:format("~p",[T]))).
11
12
13 ssi(File) ->
14 {'div',[{class,"box"}],
15 {pre,[],
16 {ssi, File,[],[]}}}.
17
18 ss(A, File) ->
19 {ok, B} = file:read_file(
20 filename:join([A#arg.docroot, File])),
21 box(binary_to_list(B)).
22
23
24
25 out(A) ->
26 [{ssi, "TAB.inc", "%%",[{"soap_intro", "choosen"}]},
27 {ehtml,
28 {'div', [{id, "entry"}],
29
30 [{h1, [], "SOAP with Yaws"},
31
32 {p, [],
33 ["SOAP is an XML-based protocol for communication over a network "
34 "connection. The main focus of SOAP is remote procedure calls (RPCs) "
35 "transported via HTTP. "
36 "SOAP is similar to XML-RPC but makes use of XML Schema to define "
37 "the data types it is making use of. "
38 ]},
39
40 {h2, [], "Preparations"},
41
42 {p, [],
43 ["Yaws is making use of the 'erlsom' XML Schema "
44 "parser and some SOAP specific library code. "
45 "Thus, to be able to use SOAP with Yaws you need to have "
46 "'erlsom' installed. Currently, the easiest way of installing "
47 "'erlsom' is to check out the library from Sourceforge and "
48 "install it from there. "]},
49
50 {p,[],"To install 'erlsom' do:"},
51
52 box("cvs -d:pserver:anonymous@erlsom.cvs.sourceforge.net:/cvsroot/erlsom login\n"
53 " cvs -z3 -d:pserver:anonymous@erlsom.cvs.sourceforge.net:/cvsroot/erlsom co -P erlsom\n"
54 "cd erlsom; make\n"
55 "sudo make install # iff you want to install as root\n"
56 ),
57
58 {h2, [], "The SOAP client side"},
59
60 {p, [],
61 ["The SOAP interface is defined by a WSDL specification, which "
62 "simply is a (rather verbose) piece of XML document that specifies "
63 "the public interface for a web service. As a client, "
64 "we don't need to understand everything in the WSDL specification "
65 "The parts that are most interesting is the name of the operation "
66 "we want to perform (i.e the function we want to call) and what "
67 "input data it expects."
68 ]},
69
70 {p,[],
71 ["As an example, lets have a look at a public SOAP service that "
72 "returns some weather data for the location we send to it. "
73 "The WSDL specification can be found here: ",
74 {a, [{href, "http://www.webservicex.net/WeatherForecast.asmx?WSDL"}],
75 "http://www.webservicex.net/WeatherForecast.asmx?WSDL "}
76 ]},
77
78 {p,[],
79 ["We start by finding the operation named: ",
80 {i, [], "GetWeatherByPlaceName, "},
81 "which is the operation we want to invoke. As can be seen, we have "
82 "one input message and one output message defined. The input message is "
83 "the one we (as a client) will send to the server. "
84 ]},
85
86 box("<wsdl:operation name=\"GetWeatherByPlaceName\">\n"
87 " <documentation>\n"
88 " Get one week weather forecast for a place name(USA)\n"
89 " </documentation>\n"
90 " <wsdl:input message=\"tns:GetWeatherByPlaceNameSoapIn\"/>\n"
91 " <wsdl:output message=\"tns:GetWeatherByPlaceNameSoapOut\"/>\n"
92 "</wsdl:operation>\n"),
93
94 {p,[],
95 ["Now, follow the reference to the message: ",
96 {i, [], "tns:GetWeatherByPlaceNameSoapIn, "},
97 "to where it is defined: "
98 ]},
99
100 box("<wsdl:message name=\"GetWeatherByPlaceNameSoapIn\">\n"
101 "<wsdl:part name=\"parameters\" element=\"tns:GetWeatherByPlaceName\"/>\n"
102 "</wsdl:message>\n"),
103
104 {p,[],
105 ["Continue by following the reference to: ",
106 {i, [], "tns:GetWeatherByPlaceName, "},
107 "and you will end up with an XML Schema type definition: "
108 ]},
109
110 box("<s:element name=\"GetWeatherByPlaceName\">\n"
111 "<s:complexType>\n"
112 "<s:sequence>\n"
113 "<s:element minOccurs=\"0\" maxOccurs=\"1\" name=\"PlaceName\" type=\"s:string\"/>\n"
114 "</s:complexType>\n"
115 "</s:sequence>\n"
116 "</s:element>\n"),
117
118 {p,[],
119 "This tells us that the function we want to call takes one argument "
120 "of a string type (which apparently denotes a Name of a place in the US). "
121 "Left for us is just to call the function from an Erlang shell which has "
122 "got the Yaws ebin directory in the path:"
123 },
124
125 box("1> yaws_soap_lib:call(\n"
126 " \"http://www.webservicex.net/WeatherForecast.asmx?WSDL\"\n"
127 " \"GetWeatherByPlaceName\"\n"
128 " [\"Boston\"]).\n"
129 "{ok,undefined,\n"
130 " [{'p:GetWeatherByPlaceNameResponse',\n"
131 " [],\n"
132 " {'p:WeatherForecasts',[],\n"
133 " \"40.3044128\",\n"
134 " \"79.81284\",\n"
135 " \"0.000453\",\n"
136 " \"42\",\n"
137 " \"BOSTON\",\n"
138 " \"PA\",\n"
139 " undefined,\n"
140 " {'p:ArrayOfWeatherData',\n"
141 " [],\n"
142 " [{'p:WeatherData',\n"
143 " [],\n"
144 " \"Friday, December 08, 2006\"|...},\n"
145 " .....\n"),
146
147 {p,[],
148 "So what happend here? We specified the URL to the WSDL file. "
149 "The yaws_soap_lib:call/3 function then went to retrieve the file, parsed it, "
150 "created a proper message, sent off the message, waited for the "
151 "reply and finally returned a parsed reply as Erlang records. "
152 },
153
154 {p,[],
155 "Eventhough this was very convenient, we probably want do more thnn just one call "
156 "to the web service. So to avoid retrieving and parsing the WSDL file for every "
157 "call. We can do it in two steps: "
158 },
159
160 box("1> Wsdl = yaws_soap_lib:initModel(\n"
161 " \"http://www.webservicex.net/WeatherForecast.asmx?WSDL\").\n"
162 "...\n"
163 "2> yaws_soap_lib:call(\n"
164 " Wsdl,\n"
165 " \"GetWeatherByPlaceName\"\n"
166 " [\"Boston\"]).\n"
167 ),
168
169 {p,[],
170 "To be able to work with the records that we get in the response, "
171 "we can create a header file that we can include in our source code. In our example "
172 "the generated '.hrl' file will look like this: "
173 },
174
175 box("3> yaws_soap_lib:write_hrl(Wsdl, \"/tmp/wfc.hrl\").\n"
176 "...\n"
177 "4> {ok,Bin}=file:read_file(\"/tmp/wfc.hrl\"),io:fwrite(binary_to_list(Bin)).\n"
178 "-record('soap:detail', {anyAttribs, choice}).\n"
179 "-record('soap:Fault', {anyAttribs, 'faultcode', 'faultstring', 'faultactor', 'detail'}).\n"
180 "-record('soap:Body', {anyAttribs, choice}).\n"
181 "-record('soap:Header', {anyAttribs, choice}).\n"
182 "-record('soap:Envelope', {anyAttribs, 'Header', 'Body', choice}).\n"
183 "-record('p:GetWeatherByPlaceNameResponse', {anyAttribs, 'GetWeatherByPlaceNameResult'}).\n"
184 "-record('p:GetWeatherByPlaceName', {anyAttribs, 'PlaceName'}).\n"
185 "-record('p:WeatherData', {anyAttribs, 'Day', 'WeatherImage', 'MaxTemperatureF', \n"
186 " 'MinTemperatureF', 'MaxTemperatureC', 'MinTemperatureC'}).\n"
187 "-record('p:ArrayOfWeatherData', {anyAttribs, 'WeatherData'}).\n"
188 "-record('p:WeatherForecasts', {anyAttribs, 'Latitude', 'Longitude', 'AllocationFactor', \n"
189 " 'FipsCode', 'PlaceName', 'StateCode', 'Status', 'Details'}).\n"
190 "-record('p:GetWeatherByZipCodeResponse', {anyAttribs, 'GetWeatherByZipCodeResult'}).\n"
191 "-record('p:GetWeatherByZipCode', {anyAttribs, 'ZipCode'}).\n"
192 ),
193
194 {p,[],
195 "As you can see, every record in our header has an XML namespace prefix prepended "
196 "in the name of the record. The prefix 'p' as shown above is the default prefix you'll "
197 "get if you don't specify a prefix yourself. This is probably good enough, but if you "
198 "want to set it to something else, you can do it as shown below:"
199 },
200
201 box("5> yaws_soap_lib:initModel(... , \"foo\"). % foo is my prefix\n"
202 "6> yaws_soap_lib:write_hrl(... , ... , \"foo\").\n"
203 ),
204
205 {p,[],
206 ["Some final notes:",
207 {ul, [],
208 [{li, [],
209 "The \"http://...\" URL given as the first argument to the "
210 "functions above may as well be a local file, and thus written as \"file://....\". "},
211 {li, [],
212 "When we retrieve a HTTP located file, we will use 'ibrowse' if it exist "
213 "in the code path. Otherwise we will use the OTP 'http' client."}
214 ]}]},
215
216 {h2, [], "The SOAP server side"},
217
218 {p,[],
219 "If we want to run our own weather service we need to take the WSDL "
220 "and add our own location to it. Either we can just study the WSDL file to "
221 "see which URL we need to change in the 'service' part of the document, or "
222 "we can make use of some nice access functions, that work on the "
223 "#wsdl{} record that yaws_soap_lib:initModel/2 returned, as shown below: "
224 },
225
226 box("7> Ops = yaws_soap_lib:wsdl_operations(Wsdl).\n"
227 "8> {ok,Op} = yaws_soap_lib:get_operation(Ops, \"GetWeatherByPlaceName\").\n"
228 "9> yaws_soap_lib:wsdl_op_address(Op).\n"
229 "\"http://www.webservicex.net/WeatherForecast.asmx\"\n"
230 ),
231
232 {p,[],
233 "Now, edit the WSDL file and change the above URL to something like this:"
234 },
235
236 box("<wsdl:service name=\"WeatherForecast\">\n"
237 " <documentation xmlns=......\n"
238 " <wsdl:port name=\"WeatherForecastSoap\".....\n"
239 " <soap:address location=\"http://localhost:8181/WeatherForecast.yaws\" />\n"
240 " </wsdl:port>\n"
241 ".....\n"
242 ),
243
244
245 {p,[],
246 "Next, start an Erlang shell and start Yaws with SOAP enabled. We need to write "
247 "the code that returns the weather info. This is done in a callback module that "
248 "the Yaws SOAP code will call with the incoming message. The message will be an "
249 "Erlang record and what we return must also be an Erlang record. So we will need "
250 "to create a .hrl containing the record definitions that we can include: "
251 },
252
253 box("1> Docroot = \"/tmp\".\n"
254 "\n"
255 "2> GL = [{enable_soap,true}, % <== THIS WILL ENABLE SOAP IN A YAWS SERVER!!\n"
256 " {trace, false},\n"
257 " {tmpdir,\"/tmp\"},{logdir,\"/tmp\"},\n"
258 " {flags,[{auth_log,false},{tty_trace, false},{copy_errlog, true}]}].\n"
259 "\n"
260 "3> SL = [{port,8181},{servername,\"localhost\"},{dir_listings, true},\n"
261 " {listen,{127,0,0,1}},{flags,[{access_log,false}]}].\n"
262 "\n"
263 "% BELOW, WE CREATE THE .hrl FILE!!\n"
264 "4> yaws_soap_lib:write_hrl(\"file:///tmp/MyWeatherService.wsdl\", \"/tmp/my_soap.hrl\").\n"
265 "\n"
266 "% WE MUST ADD A PATH TO OUR CALLBACK CODE!!\n"
267 "5> code:add_path(\"/tmp\").\n"
268 ),
269
270 {p,[],
271 "We continue by writing our weather forecast callback module:"
272 },
273
274 box("# cat /tmp/my_soap.erl\n"
275 "-module(my_soap).\n"
276 "-export([handler/4]).\n"
277 "-include(\"my_soap.hrl\"). % .hrl file generated by erlsom\n"
278 "\n"
279 "handler(_Header,\n"
280 " [#'p:GetWeatherByPlaceName'{'PlaceName' = Place}],\n"
281 " _Action, \n"
282 " _SessionValue) ->\n"
283 " {ok, undefined, get_weather_info(Place)}.\n"
284 "\n"
285 "get_weather_info(Place) -> \n"
286 " [{'p:GetWeatherByPlaceNameResponse', [],\n"
287 " {'p:WeatherForecasts',[],\n"
288 " \"40.3044128\",\n"
289 " \"79.81284\",\n"
290 " \"0.000453\",\n"
291 " \"42\",\n"
292 " Place,\n"
293 " \"PA\",\n"
294 " undefined,\n"
295 " {'p:ArrayOfWeatherData', [],\n"
296 " [{'p:WeatherData', [],\n"
297 " \"Sunday, December 10, 2006\",\n"
298 " \"http://www.nws.noaa.gov/weather/images/fcicons/nfew.jpg\",\n"
299 " \"51\",\n"
300 " \"28\",\n"
301 " \"11\",\n"
302 " \"-2\"}]}}}].\n"
303 ),
304
305 {p,[],
306 "The final piece on the server side is the '.yaws' file that invokes the "
307 "Yaws SOAP server (note that we are using the same way of hooking in our "
308 "callback module as for Json and HaXe):"
309 },
310
311 box("# cat /tmp/WeatherForecast.yaws\n"
312 "<erl>\n"
313 "out(A) ->\n"
314 " yaws_rpc:handler_session(A, {my_soap, handler}).\n"
315 "</erl>\n"
316 ),
317
318 {p,[],
319 "We are now ready to try it out. Start another Erlang shell and call it: "
320 },
321
322 box("1> yaws_soap_lib:call(\"file:///tmp/MyWeatherService.wsdl\",\n"
323 " \"GetWeatherByPlaceName\",\n"
324 " [\"Stockholm\"]).\n"
325 "{ok,undefined,\n"
326 " [{'p:GetWeatherByPlaceNameResponse', [],\n"
327 " {'p:WeatherForecasts',[],\n"
328 " \"40.3044128\",\n"
329 " \"79.81284\",\n"
330 " \"0.000453\",\n"
331 " \"42\",\n"
332 " \"Stockholm\", % <=== Yippie, it works !!\n"
333 " \"PA\",\n"
334 " undefined,\n"
335 " {'p:ArrayOfWeatherData', [],\n"
336 " [{'p:WeatherData', [],\n"
337 " \"Sunday, December 10, 2006\"|...}]}}}]}\n"
338 "\n"
339 ),
340
341 {p,[],
342 "There you have it! "
343 },
344
345
346 {ssi, "END2",[],[]}
347 ]}}].
348
349
350
351
352 </erl>
353
354
355
356
357
358
359
Something went wrong with that request. Please try again.