PageRenderTime 28ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/misultin_req.erl

https://github.com/essiene/misultin
Erlang | 280 lines | 184 code | 25 blank | 71 comment | 0 complexity | d1a88305fe11085e55c7255670ed9eaa MD5 | raw file
  1. % ==========================================================================================================
  2. % MISULTIN - Request
  3. %
  4. % >-|-|-(°>
  5. %
  6. % Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>,
  7. % Bob Ippolito <bob@mochimedia.com> for Mochi Media, Inc.
  8. % All rights reserved.
  9. %
  10. % Code portions from Bob Ippolito have been originally taken under MIT license from MOCHIWEB:
  11. % <http://code.google.com/p/mochiweb/>
  12. %
  13. % BSD License
  14. %
  15. % Redistribution and use in source and binary forms, with or without modification, are permitted provided
  16. % that the following conditions are met:
  17. %
  18. % * Redistributions of source code must retain the above copyright notice, this list of conditions and the
  19. % following disclaimer.
  20. % * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
  21. % the following disclaimer in the documentation and/or other materials provided with the distribution.
  22. % * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
  23. % products derived from this software without specific prior written permission.
  24. %
  25. % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
  26. % WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  27. % PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  28. % ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  29. % TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  30. % HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  31. % NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  32. % POSSIBILITY OF SUCH DAMAGE.
  33. % ==========================================================================================================
  34. -module(misultin_req, [Req, SocketPid]).
  35. -vsn("0.6.1").
  36. % macros
  37. -define(PERCENT, 37). % $\%
  38. -define(FULLSTOP, 46). % $\.
  39. -define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse
  40. (C >= $a andalso C =< $f) orelse
  41. (C >= $A andalso C =< $F))).
  42. -define(FILE_READ_BUFFER, 64*1012).
  43. % API
  44. -export([raw/0]).
  45. -export([ok/1, ok/2, ok/3, respond/2, respond/3, respond/4, stream/1, stream/2, stream/3]).
  46. -export([get/1, parse_qs/0, parse_post/0, file/1, file/2, resource/1]).
  47. % includes
  48. -include("../include/misultin.hrl").
  49. -include_lib("kernel/include/file.hrl").
  50. % ============================ \/ API ======================================================================
  51. % Description: Returns raw request content.
  52. raw() ->
  53. Req.
  54. % Description: Get request info.
  55. get(socket) ->
  56. Req#req.socket;
  57. get(socket_mode) ->
  58. Req#req.socket_mode;
  59. get(peer_addr) ->
  60. Req#req.peer_addr;
  61. get(peer_port) ->
  62. Req#req.peer_port;
  63. get(peer_cert) ->
  64. Req#req.peer_cert;
  65. get(connection) ->
  66. Req#req.connection;
  67. get(content_length) ->
  68. Req#req.content_length;
  69. get(vsn) ->
  70. Req#req.vsn;
  71. get(method) ->
  72. Req#req.method;
  73. get(uri) ->
  74. Req#req.uri;
  75. get(args) ->
  76. Req#req.args;
  77. get(headers) ->
  78. Req#req.headers;
  79. get(body) ->
  80. Req#req.body.
  81. % Description: Formats a 200 response.
  82. ok(Template) ->
  83. ok([], Template).
  84. ok(Headers, Template) ->
  85. respond(200, Headers, Template).
  86. ok(Headers, Template, Vars) ->
  87. respond(200, Headers, Template, Vars).
  88. % Description: Formats a response.
  89. respond(HttpCode, Template) ->
  90. respond(HttpCode, [], Template).
  91. respond(HttpCode, Headers, Template) ->
  92. {HttpCode, Headers, Template}.
  93. respond(HttpCode, Headers, Template, Vars) when is_list(Template) =:= true ->
  94. {HttpCode, Headers, io_lib:format(Template, Vars)}.
  95. % Description: Start stream.
  96. stream(close) ->
  97. catch SocketPid ! stream_close;
  98. stream(head) ->
  99. stream(head, 200, []);
  100. stream(Template) ->
  101. catch SocketPid ! {stream_data, Template}.
  102. stream(head, Headers) ->
  103. stream(head, 200, Headers);
  104. stream(Template, Vars) when is_list(Template) =:= true ->
  105. catch SocketPid ! {stream_data, io_lib:format(Template, Vars)}.
  106. stream(head, HttpCode, Headers) ->
  107. catch SocketPid ! {stream_head, HttpCode, Headers}.
  108. % Description: Sends a file to the browser.
  109. file(FilePath) ->
  110. file_send(FilePath, []).
  111. % Description: Sends a file for download.
  112. file(attachment, FilePath) ->
  113. % get filename
  114. FileName = filename:basename(FilePath),
  115. file_send(FilePath, [{'Content-Disposition', lists:flatten(io_lib:format("attachment; filename=~s", [FileName]))}]).
  116. % Description: Parse QueryString
  117. parse_qs() ->
  118. parse_qs(Req#req.args).
  119. % Description: Parse Post
  120. parse_post() ->
  121. % get header confirmation
  122. case misultin_utility:get_key_value('Content-Type', Req#req.headers) of
  123. undefined ->
  124. [];
  125. ContentType ->
  126. [Type|_CharSet] = string:tokens(ContentType, ";"),
  127. case Type of
  128. "application/x-www-form-urlencoded" ->
  129. parse_qs(Req#req.body);
  130. _Other ->
  131. []
  132. end
  133. end.
  134. % Description: Sets resource elements for restful services.
  135. resource(Options) when is_list(Options) ->
  136. % clean uri
  137. {_UriType, RawUri} = Req#req.uri,
  138. Uri = lists:foldl(fun(Option, Acc) -> clean_uri(Option, Acc) end, RawUri, Options),
  139. % split
  140. string:tokens(Uri, "/").
  141. % ============================ /\ API ======================================================================
  142. % ============================ \/ INTERNAL FUNCTIONS =======================================================
  143. % parse querystring & post
  144. parse_qs(Binary) when is_binary(Binary) ->
  145. parse_qs(binary_to_list(Binary));
  146. parse_qs(String) ->
  147. parse_qs(String, []).
  148. parse_qs([], Acc) ->
  149. lists:reverse(Acc);
  150. parse_qs(String, Acc) ->
  151. {Key, Rest} = parse_qs_key(String),
  152. {Value, Rest1} = parse_qs_value(Rest),
  153. parse_qs(Rest1, [{Key, Value} | Acc]).
  154. parse_qs_key(String) ->
  155. parse_qs_key(String, []).
  156. parse_qs_key([], Acc) ->
  157. {qs_revdecode(Acc), ""};
  158. parse_qs_key([$= | Rest], Acc) ->
  159. {qs_revdecode(Acc), Rest};
  160. parse_qs_key(Rest=[$; | _], Acc) ->
  161. {qs_revdecode(Acc), Rest};
  162. parse_qs_key(Rest=[$& | _], Acc) ->
  163. {qs_revdecode(Acc), Rest};
  164. parse_qs_key([C | Rest], Acc) ->
  165. parse_qs_key(Rest, [C | Acc]).
  166. parse_qs_value(String) ->
  167. parse_qs_value(String, []).
  168. parse_qs_value([], Acc) ->
  169. {qs_revdecode(Acc), ""};
  170. parse_qs_value([$; | Rest], Acc) ->
  171. {qs_revdecode(Acc), Rest};
  172. parse_qs_value([$& | Rest], Acc) ->
  173. {qs_revdecode(Acc), Rest};
  174. parse_qs_value([C | Rest], Acc) ->
  175. parse_qs_value(Rest, [C | Acc]).
  176. % revdecode
  177. qs_revdecode(S) ->
  178. qs_revdecode(S, []).
  179. qs_revdecode([], Acc) ->
  180. Acc;
  181. qs_revdecode([$+ | Rest], Acc) ->
  182. qs_revdecode(Rest, [$\s | Acc]);
  183. qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) ->
  184. qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]);
  185. qs_revdecode([C | Rest], Acc) ->
  186. qs_revdecode(Rest, [C | Acc]).
  187. % unexdigit
  188. unhexdigit(C) when C >= $0, C =< $9 -> C - $0;
  189. unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10;
  190. unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10.
  191. % unquote
  192. unquote(Binary) when is_binary(Binary) ->
  193. unquote(binary_to_list(Binary));
  194. unquote(String) ->
  195. qs_revdecode(lists:reverse(String)).
  196. % Description: Clean URI.
  197. clean_uri(lowercase, Uri) ->
  198. string:to_lower(Uri);
  199. clean_uri(urldecode, Uri) ->
  200. unquote(Uri);
  201. % ignore unexisting option
  202. clean_uri(_Unavailable, Uri) ->
  203. Uri.
  204. % sending of a file
  205. file_send(FilePath, Headers) ->
  206. % get file size
  207. case file:read_file_info(FilePath) of
  208. {ok, FileInfo} ->
  209. % get filesize
  210. FileSize = FileInfo#file_info.size,
  211. % send headers
  212. HeadersFull = [{'Content-Type', misultin_utility:get_content_type(FilePath)}, {'Content-Size', FileSize} | Headers],
  213. stream(head, HeadersFull),
  214. % do the gradual sending
  215. case file_open_and_send(FilePath) of
  216. {error, _Reason} ->
  217. {raw, misultin_utility:get_http_status_code(500)};
  218. ok ->
  219. % sending successful
  220. ok
  221. end;
  222. {error, _Reason} ->
  223. {raw, misultin_utility:get_http_status_code(500)}
  224. end.
  225. file_open_and_send(FilePath) ->
  226. case file:open(FilePath, [read, binary]) of
  227. {error, Reason} ->
  228. {error, Reason};
  229. {ok, IoDevice} ->
  230. % read portions
  231. case file_read_and_send(IoDevice, 0) of
  232. {error, Reason} ->
  233. file:close(IoDevice),
  234. {error, Reason};
  235. ok ->
  236. file:close(IoDevice),
  237. ok
  238. end
  239. end.
  240. file_read_and_send(IoDevice, Position) ->
  241. % read buffer
  242. case file:pread(IoDevice, Position, ?FILE_READ_BUFFER) of
  243. {ok, Data} ->
  244. % file read, send
  245. stream(Data),
  246. % loop
  247. file_read_and_send(IoDevice, Position + ?FILE_READ_BUFFER);
  248. eof ->
  249. % finished, close
  250. stream(close),
  251. ok;
  252. {error, Reason} ->
  253. {error, Reason}
  254. end.
  255. % ============================ /\ INTERNAL FUNCTIONS =======================================================