capricorn 2.0.8 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. data/erlang/lib/capricorn/ebin/capricorn.app +2 -1
  2. data/erlang/lib/capricorn/include/capricorn.hrl +1 -0
  3. data/erlang/lib/capricorn/src/cap_cluster_gems.erl +6 -1
  4. data/erlang/lib/capricorn/src/cap_console_dispatcher.erl +214 -0
  5. data/erlang/lib/capricorn/src/cap_machine_apps.erl +25 -1
  6. data/erlang/lib/capricorn/src/cap_sup.erl +14 -0
  7. data/erlang/lib/ejson/Makefile +24 -0
  8. data/erlang/lib/ejson/ebin/ejson.app +9 -0
  9. data/erlang/lib/ejson/include/ejson.hrl +40 -0
  10. data/erlang/lib/ejson/rebar.config +3 -0
  11. data/erlang/lib/ejson/src/ejson.erl +22 -0
  12. data/erlang/lib/ejson/src/ejson_decode.erl +337 -0
  13. data/erlang/lib/ejson/src/ejson_encode.erl +124 -0
  14. data/erlang/lib/ejson/test/arrays.escript +47 -0
  15. data/erlang/lib/ejson/test/compound.escript +56 -0
  16. data/erlang/lib/ejson/test/literals.escript +30 -0
  17. data/erlang/lib/ejson/test/numbers.escript +70 -0
  18. data/erlang/lib/ejson/test/objects.escript +51 -0
  19. data/erlang/lib/ejson/test/strings.escript +49 -0
  20. data/erlang/lib/ejson/test/timing.escript +43 -0
  21. data/erlang/lib/ejson/test/timing.json +382 -0
  22. data/erlang/lib/ejson/vendor/mochijson2.erl +621 -0
  23. data/erlang/lib/ejson/vendor/rfc4627.erl +625 -0
  24. data/erlang/lib/misultin/LICENSE.txt +41 -0
  25. data/erlang/lib/misultin/Makefile +26 -0
  26. data/erlang/lib/misultin/README.txt +120 -0
  27. data/erlang/lib/misultin/ebin/misultin.app +9 -0
  28. data/erlang/lib/misultin/examples/misultin_compress.erl +43 -0
  29. data/erlang/lib/misultin/examples/misultin_echo.erl +58 -0
  30. data/erlang/lib/misultin/examples/misultin_file.erl +43 -0
  31. data/erlang/lib/misultin/examples/misultin_gen_server.erl +158 -0
  32. data/erlang/lib/misultin/examples/misultin_get_variable.erl +55 -0
  33. data/erlang/lib/misultin/examples/misultin_hello_world.erl +43 -0
  34. data/erlang/lib/misultin/examples/misultin_rest.erl +68 -0
  35. data/erlang/lib/misultin/examples/misultin_ssl.erl +51 -0
  36. data/erlang/lib/misultin/examples/misultin_stream.erl +55 -0
  37. data/erlang/lib/misultin/examples/misultin_websocket_event_example.erl +103 -0
  38. data/erlang/lib/misultin/examples/misultin_websocket_example.erl +95 -0
  39. data/erlang/lib/misultin/include/misultin.hrl +95 -0
  40. data/erlang/lib/misultin/make.bat +55 -0
  41. data/erlang/lib/misultin/priv/README.txt +12 -0
  42. data/erlang/lib/misultin/priv/test_certificate.pem +21 -0
  43. data/erlang/lib/misultin/priv/test_privkey.pem +18 -0
  44. data/erlang/lib/misultin/rebar.config +3 -0
  45. data/erlang/lib/misultin/src/misultin.app.src +9 -0
  46. data/erlang/lib/misultin/src/misultin.erl +338 -0
  47. data/erlang/lib/misultin/src/misultin_http.erl +488 -0
  48. data/erlang/lib/misultin/src/misultin_req.erl +280 -0
  49. data/erlang/lib/misultin/src/misultin_socket.erl +193 -0
  50. data/erlang/lib/misultin/src/misultin_utility.erl +357 -0
  51. data/erlang/lib/misultin/src/misultin_websocket.erl +252 -0
  52. data/erlang/lib/misultin/src/misultin_ws.erl +78 -0
  53. data/erlang/rebar.config +2 -0
  54. data/erlang/rel/overlay/etc/capricorn/app.config +4 -0
  55. data/erlang/rel/reltool.config +5 -0
  56. data/lib/capricorn/recipes/apache-debian.rb +1 -1
  57. data/lib/capricorn/recipes/centos-plesk.rb +1 -1
  58. data/lib/capricorn/recipes/debian-plesk95.rb +1 -2
  59. data/lib/capricorn/recipes/macports.rb +1 -1
  60. data/lib/capricorn/version.rb +1 -1
  61. metadata +51 -4
@@ -0,0 +1,357 @@
1
+ % ==========================================================================================================
2
+ % MISULTIN - Various Utilities
3
+ %
4
+ % >-|-|-(°>
5
+ %
6
+ % Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>.
7
+ % All rights reserved.
8
+ %
9
+ % BSD License
10
+ %
11
+ % Redistribution and use in source and binary forms, with or without modification, are permitted provided
12
+ % that the following conditions are met:
13
+ %
14
+ % * Redistributions of source code must retain the above copyright notice, this list of conditions and the
15
+ % following disclaimer.
16
+ % * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
17
+ % the following disclaimer in the documentation and/or other materials provided with the distribution.
18
+ % * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
19
+ % products derived from this software without specific prior written permission.
20
+ %
21
+ % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
22
+ % WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23
+ % PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
24
+ % ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ % TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26
+ % HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27
+ % NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ % POSSIBILITY OF SUCH DAMAGE.
29
+ % ==========================================================================================================
30
+ -module(misultin_utility).
31
+ -vsn("0.6.1").
32
+
33
+ % API
34
+ -export([get_http_status_code/1, get_content_type/1, get_key_value/2, header_get_value/2]).
35
+
36
+
37
+ % ============================ \/ API ======================================================================
38
+
39
+ % Function: HttpStatus
40
+ % Description: Returns a complete HTTP header
41
+ % most common first
42
+ get_http_status_code(200) ->
43
+ "HTTP/1.1 200 OK\r\n";
44
+ get_http_status_code(100) ->
45
+ "HTTP/1.1 100 Continue\r\n";
46
+ get_http_status_code(101) ->
47
+ "HTTP/1.1 101 Switching Protocols\r\n";
48
+ get_http_status_code(301) ->
49
+ "HTTP/1.1 301 Moved Permanently\r\n";
50
+ get_http_status_code(400) ->
51
+ "HTTP/1.1 400 Bad Request\r\n";
52
+ get_http_status_code(401) ->
53
+ "HTTP/1.1 401 Unauthorized\r\n";
54
+ get_http_status_code(403) ->
55
+ "HTTP/1.1 403 Forbidden\r\n";
56
+ get_http_status_code(404) ->
57
+ "HTTP/1.1 404 Not Found\r\n";
58
+ get_http_status_code(408) ->
59
+ "HTTP/1.1 408 Request Timeout\r\n";
60
+ get_http_status_code(500) ->
61
+ "HTTP/1.1 500 Internal Server Error\r\n";
62
+ get_http_status_code(501) ->
63
+ "HTTP/1.1 501 Not Implemented\r\n";
64
+ % less common last
65
+ get_http_status_code(201) ->
66
+ "HTTP/1.1 201 Created\r\n";
67
+ get_http_status_code(202) ->
68
+ "HTTP/1.1 202 Accepted\r\n";
69
+ get_http_status_code(203) ->
70
+ "HTTP/1.1 203 Non-Authoritative Information\r\n";
71
+ get_http_status_code(204) ->
72
+ "HTTP/1.1 204 No Content\r\n";
73
+ get_http_status_code(205) ->
74
+ "HTTP/1.1 205 Reset Content\r\n";
75
+ get_http_status_code(206) ->
76
+ "HTTP/1.1 206 Partial Content\r\n";
77
+ get_http_status_code(300) ->
78
+ "HTTP/1.1 300 Multiple Choices\r\n";
79
+ get_http_status_code(302) ->
80
+ "HTTP/1.1 302 Found\r\n";
81
+ get_http_status_code(303) ->
82
+ "HTTP/1.1 303 See Other\r\n";
83
+ get_http_status_code(304) ->
84
+ "HTTP/1.1 304 Not Modified\r\n";
85
+ get_http_status_code(305) ->
86
+ "HTTP/1.1 305 Use Proxy\r\n";
87
+ get_http_status_code(307) ->
88
+ "HTTP/1.1 307 Temporary Redirect\r\n";
89
+ get_http_status_code(402) ->
90
+ "HTTP/1.1 402 Payment Required\r\n";
91
+ get_http_status_code(405) ->
92
+ "HTTP/1.1 405 Method Not Allowed\r\n";
93
+ get_http_status_code(406) ->
94
+ "HTTP/1.1 406 Not Acceptable\r\n";
95
+ get_http_status_code(407) ->
96
+ "HTTP/1.1 407 Proxy Authentication Required\r\n";
97
+ get_http_status_code(409) ->
98
+ "HTTP/1.1 409 Conflict\r\n";
99
+ get_http_status_code(410) ->
100
+ "HTTP/1.1 410 Gone\r\n";
101
+ get_http_status_code(411) ->
102
+ "HTTP/1.1 411 Length Required\r\n";
103
+ get_http_status_code(412) ->
104
+ "HTTP/1.1 412 Precondition Failed\r\n";
105
+ get_http_status_code(413) ->
106
+ "HTTP/1.1 413 Request Entity Too Large\r\n";
107
+ get_http_status_code(414) ->
108
+ "HTTP/1.1 414 Request-URI Too Long\r\n";
109
+ get_http_status_code(415) ->
110
+ "HTTP/1.1 415 Unsupported Media Type\r\n";
111
+ get_http_status_code(416) ->
112
+ "HTTP/1.1 416 Requested Range Not Satisfiable\r\n";
113
+ get_http_status_code(417) ->
114
+ "HTTP/1.1 417 Expectation Failed\r\n";
115
+ get_http_status_code(502) ->
116
+ "HTTP/1.1 502 Bad Gateway\r\n";
117
+ get_http_status_code(503) ->
118
+ "HTTP/1.1 503 Service Unavailable\r\n";
119
+ get_http_status_code(504) ->
120
+ "HTTP/1.1 504 Gateway Timeout\r\n";
121
+ get_http_status_code(505) ->
122
+ "HTTP/1.1 505 HTTP Version Not Supported\r\n";
123
+ get_http_status_code(Other) ->
124
+ lists:flatten(io_lib:format("HTTP/1.1 ~p \r\n", [Other])).
125
+
126
+ % get content type
127
+ get_content_type(FileName) ->
128
+ case filename:extension(FileName) of
129
+ % most common first
130
+ ".doc" -> "application/msword";
131
+ ".exe" -> "application/octet-stream";
132
+ ".pdf" -> "application/pdf";
133
+ ".rtf" -> "application/rtf";
134
+ ".ppt" -> "application/vnd.ms-powerpoint";
135
+ ".tgz" -> "application/x-compressed";
136
+ ".tar" -> "application/x-tar";
137
+ ".zip" -> "application/zip";
138
+ ".mp3" -> "audio/mpeg";
139
+ ".wav" -> "audio/x-wav";
140
+ ".bmp" -> "image/bmp";
141
+ ".ram" -> "audio/x-pn-realaudio";
142
+ ".gif" -> "image/gif";
143
+ ".jpe" -> "image/jpeg";
144
+ ".jpeg" -> "image/jpeg";
145
+ ".jpg" -> "image/jpeg";
146
+ ".tif" -> "image/tiff";
147
+ ".tiff" -> "image/tiff";
148
+ ".htm" -> "text/html";
149
+ ".html" -> "text/html";
150
+ ".txt" -> "text/plain";
151
+ ".mp2" -> "video/mpeg";
152
+ ".mpa" -> "video/mpeg";
153
+ ".mpe" -> "video/mpeg";
154
+ ".mpeg" -> "video/mpeg";
155
+ ".mpg" -> "video/mpeg";
156
+ ".mov" -> "video/quicktime";
157
+ ".avi" -> "video/x-msvideo";
158
+ % less common last
159
+ ".evy" -> "application/envoy";
160
+ ".fif" -> "application/fractals";
161
+ ".spl" -> "application/futuresplash";
162
+ ".hta" -> "application/hta";
163
+ ".acx" -> "application/internet-property-stream";
164
+ ".hqx" -> "application/mac-binhex40";
165
+ ".dot" -> "application/msword";
166
+ ".bin" -> "application/octet-stream";
167
+ ".class" -> "application/octet-stream";
168
+ ".dms" -> "application/octet-stream";
169
+ ".lha" -> "application/octet-stream";
170
+ ".lzh" -> "application/octet-stream";
171
+ ".oda" -> "application/oda";
172
+ ".axs" -> "application/olescript";
173
+ ".prf" -> "application/pics-rules";
174
+ ".p10" -> "application/pkcs10";
175
+ ".crl" -> "application/pkix-crl";
176
+ ".ai" -> "application/postscript";
177
+ ".eps" -> "application/postscript";
178
+ ".ps" -> "application/postscript";
179
+ ".setpay" -> "application/set-payment-initiation";
180
+ ".setreg" -> "application/set-registration-initiation";
181
+ ".xla" -> "application/vnd.ms-excel";
182
+ ".xlc" -> "application/vnd.ms-excel";
183
+ ".xlm" -> "application/vnd.ms-excel";
184
+ ".xls" -> "application/vnd.ms-excel";
185
+ ".xlt" -> "application/vnd.ms-excel";
186
+ ".xlw" -> "application/vnd.ms-excel";
187
+ ".msg" -> "application/vnd.ms-outlook";
188
+ ".sst" -> "application/vnd.ms-pkicertstore";
189
+ ".cat" -> "application/vnd.ms-pkiseccat";
190
+ ".stl" -> "application/vnd.ms-pkistl";
191
+ ".pot" -> "application/vnd.ms-powerpoint";
192
+ ".pps" -> "application/vnd.ms-powerpoint";
193
+ ".mpp" -> "application/vnd.ms-project";
194
+ ".wcm" -> "application/vnd.ms-works";
195
+ ".wdb" -> "application/vnd.ms-works";
196
+ ".wks" -> "application/vnd.ms-works";
197
+ ".wps" -> "application/vnd.ms-works";
198
+ ".hlp" -> "application/winhlp";
199
+ ".bcpio" -> "application/x-bcpio";
200
+ ".cdf" -> "application/x-cdf";
201
+ ".z" -> "application/x-compress";
202
+ ".cpio" -> "application/x-cpio";
203
+ ".csh" -> "application/x-csh";
204
+ ".dcr" -> "application/x-director";
205
+ ".dir" -> "application/x-director";
206
+ ".dxr" -> "application/x-director";
207
+ ".dvi" -> "application/x-dvi";
208
+ ".gtar" -> "application/x-gtar";
209
+ ".gz" -> "application/x-gzip";
210
+ ".hdf" -> "application/x-hdf";
211
+ ".ins" -> "application/x-internet-signup";
212
+ ".isp" -> "application/x-internet-signup";
213
+ ".iii" -> "application/x-iphone";
214
+ ".js" -> "application/x-javascript";
215
+ ".latex" -> "application/x-latex";
216
+ ".mdb" -> "application/x-msaccess";
217
+ ".crd" -> "application/x-mscardfile";
218
+ ".clp" -> "application/x-msclip";
219
+ ".dll" -> "application/x-msdownload";
220
+ ".m13" -> "application/x-msmediaview";
221
+ ".m14" -> "application/x-msmediaview";
222
+ ".mvb" -> "application/x-msmediaview";
223
+ ".wmf" -> "application/x-msmetafile";
224
+ ".mny" -> "application/x-msmoney";
225
+ ".pub" -> "application/x-mspublisher";
226
+ ".scd" -> "application/x-msschedule";
227
+ ".trm" -> "application/x-msterminal";
228
+ ".wri" -> "application/x-mswrite";
229
+ ".nc" -> "application/x-netcdf";
230
+ ".pma" -> "application/x-perfmon";
231
+ ".pmc" -> "application/x-perfmon";
232
+ ".pml" -> "application/x-perfmon";
233
+ ".pmr" -> "application/x-perfmon";
234
+ ".pmw" -> "application/x-perfmon";
235
+ ".p12" -> "application/x-pkcs12";
236
+ ".pfx" -> "application/x-pkcs12";
237
+ ".p7b" -> "application/x-pkcs7-certificates";
238
+ ".spc" -> "application/x-pkcs7-certificates";
239
+ ".p7r" -> "application/x-pkcs7-certreqresp";
240
+ ".p7c" -> "application/x-pkcs7-mime";
241
+ ".p7m" -> "application/x-pkcs7-mime";
242
+ ".p7s" -> "application/x-pkcs7-signature";
243
+ ".sh" -> "application/x-sh";
244
+ ".shar" -> "application/x-shar";
245
+ ".swf" -> "application/x-shockwave-flash";
246
+ ".sit" -> "application/x-stuffit";
247
+ ".sv4cpio" -> "application/x-sv4cpio";
248
+ ".sv4crc" -> "application/x-sv4crc";
249
+ ".tcl" -> "application/x-tcl";
250
+ ".tex" -> "application/x-tex";
251
+ ".texi" -> "application/x-texinfo";
252
+ ".texinfo" -> "application/x-texinfo";
253
+ ".roff" -> "application/x-troff";
254
+ ".t" -> "application/x-troff";
255
+ ".tr" -> "application/x-troff";
256
+ ".man" -> "application/x-troff-man";
257
+ ".me" -> "application/x-troff-me";
258
+ ".ms" -> "application/x-troff-ms";
259
+ ".ustar" -> "application/x-ustar";
260
+ ".src" -> "application/x-wais-source";
261
+ ".cer" -> "application/x-x509-ca-cert";
262
+ ".crt" -> "application/x-x509-ca-cert";
263
+ ".der" -> "application/x-x509-ca-cert";
264
+ ".pko" -> "application/ynd.ms-pkipko";
265
+ ".au" -> "audio/basic";
266
+ ".snd" -> "audio/basic";
267
+ ".mid" -> "audio/mid";
268
+ ".rmi" -> "audio/mid";
269
+ ".aif" -> "audio/x-aiff";
270
+ ".aifc" -> "audio/x-aiff";
271
+ ".aiff" -> "audio/x-aiff";
272
+ ".m3u" -> "audio/x-mpegurl";
273
+ ".ra" -> "audio/x-pn-realaudio";
274
+ ".cod" -> "image/cis-cod";
275
+ ".ief" -> "image/ief";
276
+ ".jfif" -> "image/pipeg";
277
+ ".svg" -> "image/svg+xml";
278
+ ".ras" -> "image/x-cmu-raster";
279
+ ".cmx" -> "image/x-cmx";
280
+ ".ico" -> "image/x-icon";
281
+ ".pnm" -> "image/x-portable-anymap";
282
+ ".pbm" -> "image/x-portable-bitmap";
283
+ ".pgm" -> "image/x-portable-graymap";
284
+ ".ppm" -> "image/x-portable-pixmap";
285
+ ".rgb" -> "image/x-rgb";
286
+ ".xbm" -> "image/x-xbitmap";
287
+ ".xpm" -> "image/x-xpixmap";
288
+ ".xwd" -> "image/x-xwindowdump";
289
+ ".mht" -> "message/rfc822";
290
+ ".mhtml" -> "message/rfc822";
291
+ ".nws" -> "message/rfc822";
292
+ ".css" -> "text/css";
293
+ ".323" -> "text/h323";
294
+ ".stm" -> "text/html";
295
+ ".uls" -> "text/iuls";
296
+ ".bas" -> "text/plain";
297
+ ".c" -> "text/plain";
298
+ ".h" -> "text/plain";
299
+ ".rtx" -> "text/richtext";
300
+ ".sct" -> "text/scriptlet";
301
+ ".tsv" -> "text/tab-separated-values";
302
+ ".htt" -> "text/webviewhtml";
303
+ ".htc" -> "text/x-component";
304
+ ".etx" -> "text/x-setext";
305
+ ".vcf" -> "text/x-vcard";
306
+ ".mpv2" -> "video/mpeg";
307
+ ".qt" -> "video/quicktime";
308
+ ".lsf" -> "video/x-la-asf";
309
+ ".lsx" -> "video/x-la-asf";
310
+ ".asf" -> "video/x-ms-asf";
311
+ ".asr" -> "video/x-ms-asf";
312
+ ".asx" -> "video/x-ms-asf";
313
+ ".movie" -> "video/x-sgi-movie";
314
+ ".flr" -> "x-world/x-vrml";
315
+ ".vrml" -> "x-world/x-vrml";
316
+ ".wrl" -> "x-world/x-vrml";
317
+ ".wrz" -> "x-world/x-vrml";
318
+ ".xaf" -> "x-world/x-vrml";
319
+ ".xof" -> "x-world/x-vrml";
320
+ _ -> "application/octet-stream"
321
+ end.
322
+
323
+ % faster than proplists:get_value
324
+ get_key_value(Key, List)->
325
+ case lists:keyfind(Key, 1, List) of
326
+ false-> undefined;
327
+ {_K, Value}-> Value
328
+ end.
329
+
330
+ % Function: Value | false
331
+ % Description: Find atom Tag in Headers, Headers being both atoms [for known headers] and strings. Comparison on string Header Tags is case insensitive.
332
+ header_get_value(Tag, Headers) when is_atom(Tag) ->
333
+ case lists:keyfind(Tag, 1, Headers) of
334
+ false ->
335
+ % header not found, test also conversion to string -> convert all string tags to lowercase (HTTP tags are case insensitive)
336
+ F = fun({HTag, HValue}) ->
337
+ case is_atom(HTag) of
338
+ true -> {HTag, HValue};
339
+ false -> {string:to_lower(HTag), HValue}
340
+ end
341
+ end,
342
+ HeadersStr = lists:map(F, Headers),
343
+ % test
344
+ case lists:keyfind(string:to_lower(atom_to_list(Tag)), 1, HeadersStr) of
345
+ false -> false;
346
+ {_, Value} -> Value
347
+ end;
348
+ {_, Value} -> Value
349
+ end.
350
+
351
+ % ============================ /\ API ======================================================================
352
+
353
+
354
+
355
+ % ============================ \/ INTERNAL FUNCTIONS =======================================================
356
+
357
+ % ============================ /\ INTERNAL FUNCTIONS =======================================================
@@ -0,0 +1,252 @@
1
+ % ==========================================================================================================
2
+ % MISULTIN - WebSocket
3
+ %
4
+ % >-|-|-(°>
5
+ %
6
+ % Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>, Joe Armstrong.
7
+ % All rights reserved.
8
+ %
9
+ % Code portions from Joe Armstrong have been originally taken under MIT license at the address:
10
+ % <http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html>
11
+ %
12
+ % BSD License
13
+ %
14
+ % Redistribution and use in source and binary forms, with or without modification, are permitted provided
15
+ % that the following conditions are met:
16
+ %
17
+ % * Redistributions of source code must retain the above copyright notice, this list of conditions and the
18
+ % following disclaimer.
19
+ % * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
20
+ % the following disclaimer in the documentation and/or other materials provided with the distribution.
21
+ % * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
22
+ % products derived from this software without specific prior written permission.
23
+ %
24
+ % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
25
+ % WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
26
+ % PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
27
+ % ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
28
+ % TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29
+ % HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30
+ % NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
+ % POSSIBILITY OF SUCH DAMAGE.
32
+ % ==========================================================================================================
33
+ -module(misultin_websocket).
34
+ -vsn("0.6.1").
35
+
36
+ % API
37
+ -export([check/2, connect/3]).
38
+
39
+
40
+ % includes
41
+ -include("../include/misultin.hrl").
42
+
43
+
44
+ % ============================ \/ API ======================================================================
45
+
46
+ % Function: {true, Origin, Host, Path} | false
47
+ % Description: Check if the incoming request is a websocket handshake.
48
+ check(_Path, Headers) ->
49
+ ?LOG_DEBUG("testing for a websocket request path: ~p headers: ~p", [_Path, Headers]),
50
+ % set supported websocket protocols, order does matter
51
+ VsnSupported = [{'draft-hixie', 76}, {'draft-hixie', 68}],
52
+ % checks
53
+ check_websockets(VsnSupported, Headers).
54
+
55
+ % Connect and handshake with Websocket.
56
+ connect(Req, #ws{vsn = Vsn, socket = Socket, socket_mode = SocketMode, path = Path, headers = Headers, ws_autoexit = WsAutoExit} = Ws, WsLoop) ->
57
+ ?LOG_DEBUG("building handshake response", []),
58
+ % get data
59
+ Origin = misultin_utility:header_get_value('Origin', Headers),
60
+ Host = misultin_utility:header_get_value('Host', Headers),
61
+ % build handshake
62
+ HandshakeServer = handshake(Vsn, Req, Headers, {Path, Origin, Host}),
63
+ % send handshake back
64
+ misultin_socket:send(Socket, HandshakeServer, SocketMode),
65
+ % add data to ws record and spawn controlling process
66
+ Ws0 = misultin_ws:new(Ws#ws{origin = Origin, host = Host}, self()),
67
+ WsHandleLoopPid = spawn(fun() -> WsLoop(Ws0) end),
68
+ erlang:monitor(process, WsHandleLoopPid),
69
+ % set opts
70
+ misultin_socket:setopts(Socket, [{packet, 0}, {active, true}], SocketMode),
71
+ % add main websocket pid to misultin server reference
72
+ misultin:persistent_socket_pid_add(self()),
73
+ % start listening for incoming data
74
+ ws_loop(Socket, none, WsHandleLoopPid, SocketMode, WsAutoExit).
75
+
76
+ % ============================ /\ API ======================================================================
77
+
78
+
79
+ % ============================ \/ INTERNAL FUNCTIONS =======================================================
80
+
81
+ % Description: Loop to check for all available supported websocket protocols.
82
+ check_websockets([], _Headers) -> false;
83
+ check_websockets([Vsn|T], Headers) ->
84
+ case check_websocket(Vsn, Headers) of
85
+ false -> check_websockets(T, Headers);
86
+ {true, Vsn} -> {true, Vsn}
87
+ end.
88
+
89
+ % Function: {true, Vsn} | false
90
+ % Description: Check if the incoming request is a websocket request.
91
+ check_websocket({'draft-hixie', 76} = Vsn, Headers) ->
92
+ ?LOG_DEBUG("testing for websocket protocol ~p", [Vsn]),
93
+ % set required headers
94
+ RequiredHeaders = [
95
+ {'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {'Origin', ignore},
96
+ {'Sec-WebSocket-Key1', ignore}, {'Sec-WebSocket-Key2', ignore}
97
+ ],
98
+ % check for headers existance
99
+ case check_headers(Headers, RequiredHeaders) of
100
+ true ->
101
+ % return
102
+ {true, Vsn};
103
+ _RemainingHeaders ->
104
+ ?LOG_DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
105
+ false
106
+ end;
107
+ check_websocket({'draft-hixie', 68} = Vsn, Headers) ->
108
+ ?LOG_DEBUG("testing for websocket protocol ~p", [Vsn]),
109
+ % set required headers
110
+ RequiredHeaders = [
111
+ {'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {'Origin', ignore}
112
+ ],
113
+ % check for headers existance
114
+ case check_headers(Headers, RequiredHeaders) of
115
+ true -> {true, Vsn};
116
+ _RemainingHeaders ->
117
+ ?LOG_DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
118
+ false
119
+ end;
120
+ check_websocket(_Vsn, _Headers) -> false. % not implemented
121
+
122
+ % Function: true | [{RequiredTag, RequiredVal}, ..]
123
+ % Description: Check if headers correspond to headers requirements.
124
+ check_headers(Headers, RequiredHeaders) ->
125
+ F = fun({Tag, Val}) ->
126
+ % see if the required Tag is in the Headers
127
+ case misultin_utility:header_get_value(Tag, Headers) of
128
+ false -> true; % header not found, keep in list
129
+ HVal ->
130
+ case Val of
131
+ ignore -> false; % ignore value -> ok, remove from list
132
+ HVal -> false; % expected val -> ok, remove from list
133
+ _ -> true % val is different, keep in list
134
+ end
135
+ end
136
+ end,
137
+ case lists:filter(F, RequiredHeaders) of
138
+ [] -> true;
139
+ MissingHeaders -> MissingHeaders
140
+ end.
141
+
142
+ % Function: List
143
+ % Description: Builds the server handshake response.
144
+ handshake({'draft-hixie', 76}, #req{socket = Sock, socket_mode = SocketMode}, Headers, {Path, Origin, Host}) ->
145
+ % build data
146
+ Key1 = misultin_utility:header_get_value('Sec-WebSocket-Key1', Headers),
147
+ Key2 = misultin_utility:header_get_value('Sec-WebSocket-Key2', Headers),
148
+ % handshake needs body of the request, still need to read it [TODO: default recv timeout hard set, will be exported when WS protocol is final]
149
+ misultin_socket:setopts(Sock, [{packet, raw}, {active, false}], SocketMode),
150
+ Body = case misultin_socket:recv(Sock, 8, 30*1000, SocketMode) of
151
+ {ok, Bin} -> Bin;
152
+ {error, timeout} ->
153
+ ?LOG_WARNING("timeout in reading websocket body", []),
154
+ <<>>;
155
+ _Other ->
156
+ ?LOG_ERROR("tcp error treating data: ~p", [_Other]),
157
+ <<>>
158
+ end,
159
+ ?LOG_DEBUG("got content in body of websocket request: ~p", [Body]),
160
+ % prepare handhsake response
161
+ ["HTTP/1.1 101 WebSocket Protocol Handshake\r\n",
162
+ "Upgrade: WebSocket\r\n",
163
+ "Connection: Upgrade\r\n",
164
+ "Sec-WebSocket-Origin: ", Origin, "\r\n",
165
+ "Sec-WebSocket-Location: ws://", lists:concat([Host, Path]), "\r\n\r\n",
166
+ build_challenge({'draft-hixie', 76}, {Key1, Key2, Body})
167
+ ];
168
+ handshake({'draft-hixie', 68}, _Req, _Headers, {Path, Origin, Host}) ->
169
+ % prepare handhsake response
170
+ ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
171
+ "Upgrade: WebSocket\r\n",
172
+ "Connection: Upgrade\r\n",
173
+ "WebSocket-Origin: ", Origin , "\r\n",
174
+ "WebSocket-Location: ws://", lists:concat([Host, Path]), "\r\n\r\n"
175
+ ].
176
+
177
+ % Function: List
178
+ % Description: Builds the challenge for a handshake response.
179
+ % Code portions from Sergio Veiga <http://sergioveiga.com/index.php/2010/06/17/websocket-handshake-76-in-erlang/>
180
+ build_challenge({'draft-hixie', 76}, {Key1, Key2, Key3}) ->
181
+ Ikey1 = [D || D <- Key1, $0 =< D, D =< $9],
182
+ Ikey2 = [D || D <- Key2, $0 =< D, D =< $9],
183
+ Blank1 = length([D || D <- Key1, D =:= 32]),
184
+ Blank2 = length([D || D <- Key2, D =:= 32]),
185
+ Part1 = list_to_integer(Ikey1) div Blank1,
186
+ Part2 = list_to_integer(Ikey2) div Blank2,
187
+ Ckey = <<Part1:4/big-unsigned-integer-unit:8, Part2:4/big-unsigned-integer-unit:8, Key3/binary>>,
188
+ erlang:md5(Ckey).
189
+
190
+ % Main Websocket loop
191
+ ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit) ->
192
+ ?LOG_DEBUG("websocket loop", []),
193
+ receive
194
+ {tcp, Socket, Data} ->
195
+ handle_data(Buffer, binary_to_list(Data), Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
196
+ {tcp_closed, Socket} ->
197
+ ?LOG_DEBUG("tcp connection was closed, exit", []),
198
+ % close websocket and custom controlling loop
199
+ websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
200
+ {'DOWN', Ref, process, WsHandleLoopPid, Reason} ->
201
+ case Reason of
202
+ normal ->
203
+ ?LOG_DEBUG("linked websocket controlling loop stopped.", []);
204
+ _ ->
205
+ ?LOG_ERROR("linked websocket controlling loop crashed with reason: ~p", [Reason])
206
+ end,
207
+ % demonitor
208
+ erlang:demonitor(Ref),
209
+ % close websocket and custom controlling loop
210
+ websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
211
+ {send, Data} ->
212
+ ?LOG_DEBUG("sending data to websocket: ~p", [Data]),
213
+ misultin_socket:send(Socket, [0, Data, 255], SocketMode),
214
+ ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit);
215
+ shutdown ->
216
+ ?LOG_DEBUG("shutdown request received, closing websocket with pid ~p", [self()]),
217
+ % close websocket and custom controlling loop
218
+ websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
219
+ _Ignored ->
220
+ ?LOG_WARNING("received unexpected message, ignoring: ~p", [_Ignored]),
221
+ ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit)
222
+ end.
223
+
224
+ % Buffering and data handling
225
+ handle_data(none, [0|T], Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
226
+ handle_data([], T, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
227
+ handle_data(none, [], Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
228
+ ws_loop(Socket, none, WsHandleLoopPid, SocketMode, WsAutoExit);
229
+ handle_data(L, [255|T], Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
230
+ WsHandleLoopPid ! {browser, lists:reverse(L)},
231
+ handle_data(none, T, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
232
+ handle_data(L, [H|T], Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
233
+ handle_data([H|L], T, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
234
+ handle_data([], L, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
235
+ ws_loop(Socket, L, WsHandleLoopPid, SocketMode, WsAutoExit).
236
+
237
+ % Close socket and custom handling loop dependency
238
+ websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
239
+ % remove main websocket pid from misultin server reference
240
+ misultin:persistent_socket_pid_remove(self()),
241
+ case WsAutoExit of
242
+ true ->
243
+ % kill custom handling loop process
244
+ exit(WsHandleLoopPid, kill);
245
+ false ->
246
+ % the killing of the custom handling loop process is handled in the loop itself -> send event
247
+ WsHandleLoopPid ! closed
248
+ end,
249
+ % close main socket
250
+ misultin_socket:close(Socket, SocketMode).
251
+
252
+ % ============================ /\ INTERNAL FUNCTIONS =======================================================