Permalink
Browse files

No commit message

  • Loading branch information...
0 parents commit 72a97f441d6680166457a9c639d984519fe5ad9f @ostinelli committed Jul 25, 2009
37 LICENSE.txt
@@ -0,0 +1,37 @@
+==========================================================================================================
+MISULTIN - An Erlang library for building fast lightweight HTTP servers.
+<http://code.google.com/p/misultin/>
+
+>-|-|-<°>
+
+Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>, Sean Hinde,
+ Bob Ippolito <bob@mochimedia.com> for Mochi Media, Inc.
+All rights reserved.
+
+Code portions from Sean Hinde have been originally taken under BSD license from Trapexit at the address:
+<http://www.trapexit.org/A_fast_web_server_demonstrating_some_undocumented_Erlang_features>
+
+Code portions from Bob Ippolito have been originally taken under MIT license from MOCHIWEB:
+<http://code.google.com/p/mochiweb/>
+==========================================================================================================
+
+BSD License
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided
+that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+ following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+ the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
57 README.txt
@@ -0,0 +1,57 @@
+==========================================================================================================
+MISULTIN - An Erlang library for building fast lightweight HTTP servers.
+<http://code.google.com/p/misultin/>
+
+>-|-|-<°>
+
+
+INSTALL INSTRUCTIONS
+==========================================================================================================
+
+1. Compile
+
+Run the appropriate script:
+
+ * OSX | Linux users: ./compile
+ * Windows users: compile.bat. Note that Erlang bin directory (by default, C:\Program Files\erl5.7.2\bin\) must be in your path for the script to work.
+
+Optionally, you may consider setting a logging level with the compile options. Run the script with the -h option for more information.
+
+You may also prefer to compile the files manually. If so, compile the files in the src directory and drop the resulting .beam files into the ebin directory.
+2. (Optional) Copy Misultin files
+
+This optional step is recommended and will allow Misultin to be called from modules running from any directory on your file system.
+
+Locate the directory where Erlang is installed on your system. Under OSX and Linux, it should be something similar to /usr/local/lib/erlang/ and under Windows C:\Program Files\erl5.7.2\.
+
+Browse into the lib directory under this Erlang root and create a misultin directory, then copy all Misultin files and directories into the newly created directory. You should now have Misultin under something similar to:
+
+ * OSX | Linux users: /usr/local/lib/erlang/lib/misultin/
+ * Windows users: C:\Program Files\erl5.7.2\lib\misultin\
+
+If you do not want to proceed with this optional step, then all you need to do is copy the relevant Misultin files in the same directory of the code calling it.
+3. Test
+
+Open an erlang shell, compile and run misultin_hello_world from the Misultin examples directory by issuing:
+
+(one@rob.loc)1>c(misultin_hello_world).
+{ok,example_simple}
+(one@rob.loc)2>misultin_hello_world:start().
+{ok,<0.50.0>}
+
+Open your favourite browser and point it to http://localhost:8080/, you should read "Hello World." printed on your page.
+
+You may now stop the misultin_hello_world HTTP server:
+
+(one@rob.loc)3>misultin_hello_world:stop().
+
+4. Congratulations!
+
+You're ready to go.
+
+>-|-|-<°>
+
+DOCUMENTATION
+==========================================================================================================
+
+API Documentation is available online on the Misultin's wiki: http://code.google.com/p/misultin/wiki/
61 compile
@@ -0,0 +1,61 @@
+#!/bin/bash
+# ==========================================================================================================
+# MISULTIN - compile
+#
+# >-|-|-<°>
+#
+# Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>
+# All rights reserved.
+#
+# BSD License
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided
+# that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+# following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+# the following disclaimer in the documentation and/or other materials provided with the distribution.
+# * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+# products derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+# ==========================================================================================================
+# init
+misultin_home=$(dirname $0)
+
+# get options ":c:d: v"
+while getopts ":d: h" o ; do
+ case $o in
+ d) # debug support
+ case $OPTARG in
+ error) debug="-D debug=$OPTARG";;
+ warning) debug="-D debug=$OPTARG";;
+ info) debug="-D debug=$OPTARG";;
+ debug) debug="-D debug=$OPTARG";;
+ *) echo "[ERROR]: unknown debug level $OPTARG"
+ exit 1;;
+ esac
+ ;;
+
+ h) echo "Usage: compile [options]
+Options:
+-d [level] compile with debug mode: error|warning|info|debug
+-h print this help"
+ exit 0
+ ;;
+
+ esac
+done
+
+# compile in main src/ to ebin/ dir
+echo -n "compiling... "
+erlc ${debug} -o ${misultin_home}/ebin/ ${misultin_home}/src/*
+echo "ok."
61 compile.bat
@@ -0,0 +1,61 @@
+@echo off
+REM ==========================================================================================================
+REM MISULTIN - compile
+REM
+REM >-|-|-<°>
+REM
+REM Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>
+REM All rights reserved.
+REM
+REM BSD License
+REM
+REM Redistribution and use in source and binary forms, with or without modification, are permitted provided
+REM that the following conditions are met:
+REM
+REM * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+REM following disclaimer.
+REM * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+REM the following disclaimer in the documentation and/or other materials provided with the distribution.
+REM * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+REM products derived from this software without specific prior written permission.
+REM
+REM THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+REM WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+REM PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+REM ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+REM TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+REM HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+REM NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+REM POSSIBILITY OF SUCH DAMAGE.
+REM ==========================================================================================================
+
+:BEGIN
+IF "%1"=="-d" GOTO SETDEBUG
+IF "%1"=="-h" GOTO SHOWHELP
+GOTO COMPILE
+
+:SETDEBUG
+SET command=
+IF "%2"=="error" GOTO SETCOMMAND
+IF "%2"=="warning" GOTO SETCOMMAND
+IF "%2"=="info" GOTO SETCOMMAND
+IF "%2"=="debug" GOTO SETCOMMAND
+echo [ERROR]: unknown debug level %2%
+GOTO END
+
+:SETCOMMAND
+SET command=-D debug=%2
+
+:COMPILE
+echo compiling...
+FOR %%f in (src/*.erl) DO erlc %command% -o ebin src/%%f
+echo ok.
+GOTO END
+
+:SHOWHELP
+echo Usage: compile.bat [options]
+echo Options:
+echo -d [level] compile with debug mode: error warning info debug
+echo -h print this help
+
+:END
57 examples/misultin_echo.erl
@@ -0,0 +1,57 @@
+% ==========================================================================================================
+% MISULTIN - Example: simple
+%
+% Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>
+% All rights reserved.
+%
+% BSD License
+%
+% Redistribution and use in source and binary forms, with or without modification, are permitted provided
+% that the following conditions are met:
+%
+% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+% following disclaimer.
+% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+% the following disclaimer in the documentation and/or other materials provided with the distribution.
+% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+% products derived from this software without specific prior written permission.
+%
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+% POSSIBILITY OF SUCH DAMAGE.
+% ==========================================================================================================
+-module(misultin_echo).
+-vsn('0.1').
+-export([start/0, stop/0, handle_http/1]).
+
+% start misultin http server on port 8080
+start() ->
+ misultin:start_link([{port, 8080}, {loop, fun(Req) -> handle_http(Req) end}]).
+
+% stop misultin
+stop() ->
+ misultin:stop().
+
+% callback on request received
+handle_http(Req) ->
+ % get params depending on method
+ Method = Req:get(method),
+ case Method of
+ 'GET' ->
+ Args = Req:parse_qs();
+ 'POST' ->
+ Args = Req:parse_post()
+ end,
+ % build an XML with all parameters and values
+ BuildXml = fun({Param, Value}, Acc) ->
+ [lists:flatten(io_lib:format("<param><name>~s</name><value>~s</value></param>", [Param, Value]))|Acc]
+ end,
+ Xml = lists:flatten(lists:reverse(lists:foldl(BuildXml, [], Args))),
+ % output
+ Req:ok([{"Content-Type", "text/xml"}], "<misultin_test><method>~s</method>~s</misultin_test>", [Method, Xml]).
+
155 examples/misultin_gen_server.erl
@@ -0,0 +1,155 @@
+% ==========================================================================================================
+% MISULTIN - Example: simple
+%
+% Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>
+% All rights reserved.
+%
+% BSD License
+%
+% Redistribution and use in source and binary forms, with or without modification, are permitted provided
+% that the following conditions are met:
+%
+% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+% following disclaimer.
+% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+% the following disclaimer in the documentation and/or other materials provided with the distribution.
+% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+% products derived from this software without specific prior written permission.
+%
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+% POSSIBILITY OF SUCH DAMAGE.
+% ==========================================================================================================
+-module(misultin_gen_server).
+-vsn('0.1').
+-behaviour(gen_server).
+
+% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+% API
+-export([start_link/0, stop/0]).
+
+% internal functions
+-export([handle_http/1]).
+
+% macros
+-define(SERVER, ?MODULE).
+
+
+% ============================ \/ API ======================================================================
+
+% Function: {ok,Pid} | ignore | {error, Error}
+% Description: Starts the server.
+start_link() ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
+
+% Function: -> ok
+% Description: Manually stops the server.
+stop() ->
+ gen_server:cast(?SERVER, stop).
+
+% ============================ /\ API ======================================================================
+
+
+% ============================ \/ GEN_SERVER CALLBACKS =====================================================
+
+% ----------------------------------------------------------------------------------------------------------
+% Function: -> {ok, State} | {ok, State, Timeout} | ignore | {stop, Reason}
+% Description: Initiates the server.
+% ----------------------------------------------------------------------------------------------------------
+init([]) ->
+ % trap_exit -> this gen_server needs to be supervised
+ process_flag(trap_exit, true),
+ % start misultin & set monitor
+ misultin:start_link([{port, 8080}, {loop, fun(Req) -> handle_http(Req) end}]),
+ erlang:monitor(process, misultin),
+ {ok, []}.
+
+% ----------------------------------------------------------------------------------------------------------
+% 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 generic fallback
+handle_call(_Request, _From, State) ->
+ {reply, undefined, State}.
+
+% ----------------------------------------------------------------------------------------------------------
+% Function: handle_cast(Msg, State) -> {noreply, State} | {noreply, State, Timeout} | {stop, Reason, State}
+% Description: Handling cast messages.
+% ----------------------------------------------------------------------------------------------------------
+
+% manual shutdown
+handle_cast(stop, State) ->
+ {stop, normal, State};
+
+% handle_cast generic fallback (ignore)
+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 when misultin server goes down -> take down example_gen_server too [the supervisor will take everything up again]
+handle_info({'DOWN', _, _, {misultin, _}, _}, State) ->
+ {stop, normal, State};
+
+% handle_info generic fallback (ignore)
+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. When it returns,
+% the gen_server terminates with Reason. The return value is ignored.
+% ----------------------------------------------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ % stop misultin
+ misultin:stop(),
+ terminated.
+
+% ----------------------------------------------------------------------------------------------------------
+% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+% Description: Convert process state when code is changed.
+% ----------------------------------------------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+% ============================ /\ GEN_SERVER CALLBACKS =====================================================
+
+
+% ============================ \/ INTERNAL FUNCTIONS =======================================================
+
+% ---------------------------- \/ misultin requests --------------------------------------------------------
+
+handle_http(Req) ->
+ % get params depending on method
+ Method = Req:get(method),
+ case Method of
+ 'GET' ->
+ Args = Req:parse_qs();
+ 'POST' ->
+ Args = Req:parse_post()
+ end,
+ % build an XML with all parameters and values
+ BuildXml = fun({Param, Value}, Acc) ->
+ [lists:flatten(io_lib:format("<param><name>~s</name><value>~s</value></param>", [Param, Value]))|Acc]
+ end,
+ Xml = lists:flatten(lists:reverse(lists:foldl(BuildXml, [], Args))),
+ % output
+ Req:ok([{"Content-Type", "text/xml"}], "<misultin_test><method>~s</method>~s</misultin_test>", [Method, Xml]).
+
+% ---------------------------- /\ misultin requests --------------------------------------------------------
+
+% ============================ /\ INTERNAL FUNCTIONS =======================================================
+
55 examples/misultin_get_variable.erl
@@ -0,0 +1,55 @@
+% ==========================================================================================================
+% MISULTIN - Example: simple
+%
+% Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>
+% All rights reserved.
+%
+% BSD License
+%
+% Redistribution and use in source and binary forms, with or without modification, are permitted provided
+% that the following conditions are met:
+%
+% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+% following disclaimer.
+% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+% the following disclaimer in the documentation and/or other materials provided with the distribution.
+% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+% products derived from this software without specific prior written permission.
+%
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+% POSSIBILITY OF SUCH DAMAGE.
+% ==========================================================================================================
+-module(misultin_get_variable).
+-vsn('0.1').
+-export([start/0, stop/0, handle_http/1]).
+
+% start misultin http server on port 8080
+start() ->
+ misultin:start_link([{port, 8080}, {loop, fun(Req) -> handle_http(Req) end}]).
+
+% stop misultin
+stop() ->
+ misultin:stop().
+
+% callback on request received
+handle_http(Req) ->
+ % get params
+ Args = Req:parse_qs(),
+ Value = proplists:get_value("value", Args),
+ case Value of
+ undefined ->
+ Req:ok([{"Content-Type", "text/xml"}], "<http_test><error>no value specified</error></http_test>");
+ _ ->
+ Req:ok([{"Content-Type", "text/xml"}], "<http_test><value>~s</value></http_test>", [Value])
+ end.
+
+
+
+
+
42 examples/misultin_hello_world.erl
@@ -0,0 +1,42 @@
+% ==========================================================================================================
+% MISULTIN - Example: simple
+%
+% Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>
+% All rights reserved.
+%
+% BSD License
+%
+% Redistribution and use in source and binary forms, with or without modification, are permitted provided
+% that the following conditions are met:
+%
+% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+% following disclaimer.
+% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+% the following disclaimer in the documentation and/or other materials provided with the distribution.
+% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+% products derived from this software without specific prior written permission.
+%
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+% POSSIBILITY OF SUCH DAMAGE.
+% ==========================================================================================================
+-module(misultin_hello_world).
+-vsn('0.1').
+-export([start/0, stop/0, handle_http/1]).
+
+% start misultin http server on port 8080
+start() ->
+ misultin:start_link([{port, 8080}, {loop, fun(Req) -> handle_http(Req) end}]).
+
+% stop misultin
+stop() ->
+ misultin:stop().
+
+% callback on request received
+handle_http(Req) ->
+ Req:ok("Hello World.").
67 examples/misultin_rest.erl
@@ -0,0 +1,67 @@
+% ==========================================================================================================
+% MISULTIN - Example: simple
+%
+% Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>
+% All rights reserved.
+%
+% BSD License
+%
+% Redistribution and use in source and binary forms, with or without modification, are permitted provided
+% that the following conditions are met:
+%
+% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+% following disclaimer.
+% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+% the following disclaimer in the documentation and/or other materials provided with the distribution.
+% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+% products derived from this software without specific prior written permission.
+%
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+% POSSIBILITY OF SUCH DAMAGE.
+% ==========================================================================================================
+-module(misultin_rest).
+-vsn('0.1').
+-export([start/0, stop/0, handle_http/1]).
+
+% start misultin http server on port 8080
+start() ->
+ misultin:start_link([{port, 8080}, {loop, fun(Req) -> handle_http(Req) end}]).
+
+% stop misultin
+stop() ->
+ misultin:stop().
+
+% callback function called on incoming http request
+handle_http(Req) ->
+ % dispatch to rest
+ handle(Req:get(method), Req:resource([lowercase, urldecode]), Req).
+
+% ---------------------------- \/ handle rest --------------------------------------------------------------
+
+% handle a GET on /
+handle('GET', [], Req) ->
+ Req:ok([{"Content-Type", "text/plain"}], "Main home page.");
+
+% handle a GET on /users
+handle('GET', ["users"], Req) ->
+ Req:ok([{"Content-Type", "text/plain"}], "Main users root.");
+
+% handle a GET on /users/{username}
+handle('GET', ["users", UserName], Req) ->
+ Req:ok([{"Content-Type", "text/plain"}], "This is ~s's page.", [UserName]);
+
+% handle a GET on /users/{username}/messages
+handle('GET', ["users", UserName, "messages"], Req) ->
+ Req:ok([{"Content-Type", "text/plain"}], "This is ~s's messages page.", [UserName]);
+
+% handle the 404 page not found
+handle(_, _, Req) ->
+ Req:ok([{"Content-Type", "text/plain"}], "Page not found.").
+
+% ---------------------------- /\ handle rest --------------------------------------------------------------
90 include/misultin.hrl
@@ -0,0 +1,90 @@
+% ==========================================================================================================
+% MISULTIN - Include file
+%
+% Copyright (C) 2009, Sean Hinde, Roberto Ostinelli <roberto@ostinelli.net>
+% All rights reserved.
+%
+% BSD License
+%
+% Redistribution and use in source and binary forms, with or without modification, are permitted provided
+% that the following conditions are met:
+%
+% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+% following disclaimer.
+% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+% the following disclaimer in the documentation and/or other materials provided with the distribution.
+% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+% products derived from this software without specific prior written permission.
+%
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+% POSSIBILITY OF SUCH DAMAGE.
+% ==========================================================================================================
+% macros
+-define(internal_server_error_500, "HTTP/1.1 500 Internal Server Error\r\n\r\n").
+-define(not_implemented_501, "HTTP/1.1 501 Not Implemented\r\n\r\n").
+-define(forbidden_403, "HTTP/1.1 403 Forbidden\r\n\r\n").
+-define(not_found_404, "HTTP/1.1 404 Not Found\r\n\r\n").
+-define(server_idle_timeout, 30*1000).
+
+% define debug
+-ifdef(debug).
+-define(DEBUG(Level, Str, Args),
+ % Level = error | warning | info | debug
+ case Level of
+ error ->
+ erlang:apply(error_logger, error_msg, [lists:concat([" module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args]);
+ warning ->
+ case ?debug of
+ debug ->
+ erlang:apply(error_logger, info_msg, [lists:concat([" module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args]);
+ info ->
+ erlang:apply(error_logger, info_msg, [lists:concat([" module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args]);
+ warning ->
+ erlang:apply(error_logger, warning_msg, [lists:concat([" module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args]);
+ _ ->
+ ok
+ end;
+ info ->
+ case ?debug of
+ debug ->
+ erlang:apply(error_logger, info_msg, [lists:concat([" module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args]);
+ info ->
+ erlang:apply(error_logger, info_msg, [lists:concat([" module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args]);
+ _ ->
+ ok
+ end;
+ debug ->
+ case ?debug of
+ debug ->
+ erlang:apply(error_logger, info_msg, [lists:concat(["[DEBUG] module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args]);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end
+).
+-else.
+-define(DEBUG(Level, Str, Args), true).
+-endif.
+
+
+% Request
+-record(req, {
+ peer_addr, % peer IP | undefined
+ peer_port, % peer port | undefined
+ connection = keep_alive, % keep_alive | close
+ content_length, % Integer
+ vsn, % {Maj,Min}
+ method, % 'GET'|'POST'
+ uri, % Truncated URI /index.html
+ args = "", % Part of URI after ?
+ headers, % [{Tag, Val}]
+ body = <<>> % Content Body
+}).
233 src/misultin.erl
@@ -0,0 +1,233 @@
+% ==========================================================================================================
+% MISULTIN - Main
+%
+% >-|-|-<°>
+%
+% Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>, Sean Hinde.
+% All rights reserved.
+%
+% Code portions from Sean Hinde have been originally taken under BSD license from Trapexit at the address:
+% <http://www.trapexit.org/A_fast_web_server_demonstrating_some_undocumented_Erlang_features>
+%
+% BSD License
+%
+% Redistribution and use in source and binary forms, with or without modification, are permitted provided
+% that the following conditions are met:
+%
+% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+% following disclaimer.
+% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+% the following disclaimer in the documentation and/or other materials provided with the distribution.
+% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+% products derived from this software without specific prior written permission.
+%
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+% POSSIBILITY OF SUCH DAMAGE.
+% ==========================================================================================================
+-module(misultin).
+-behaviour(gen_server).
+-vsn('0.1').
+
+% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+% API
+-export([start_link/1, stop/0, create_acceptor/0]).
+
+% macros
+-define(SERVER, ?MODULE).
+
+% records
+-record(state, {
+ listen_socket,
+ port,
+ acceptor,
+ loop
+}).
+
+% includes
+-include("../include/misultin.hrl").
+
+
+% ============================ \/ API ======================================================================
+
+% Function: {ok,Pid} | ignore | {error, Error}
+% Description: Starts the server.
+start_link(Options) when is_list(Options) ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, [Options], []).
+
+% Function: -> ok
+% Description: Manually stops the server.
+stop() ->
+ gen_server:cast(?SERVER, stop).
+
+% Function: -> ok
+% Description: Send message to cause a new acceptor to be created
+create_acceptor() ->
+ gen_server:cast(?SERVER, create_acceptor).
+
+% ============================ /\ API ======================================================================
+
+
+% ============================ \/ GEN_SERVER CALLBACKS =====================================================
+
+% ----------------------------------------------------------------------------------------------------------
+% Function: -> {ok, State} | {ok, State, Timeout} | ignore | {stop, Reason}
+% Description: Initiates the server.
+% ----------------------------------------------------------------------------------------------------------
+init([Options]) ->
+ %%%%% process_flag(trap_exit, true),
+ ?DEBUG(info, "starting with Pid: ~p", [self()]),
+ % test and get options
+ OptionNames = [port, loop],
+ OptionsVerified = lists:foldl(fun(OptionName, Acc) -> [get_option(OptionName, Options)|Acc] end, [], OptionNames),
+ case proplists:get_value(error, OptionsVerified) of
+ undefined ->
+ % get options
+ Port = proplists:get_value(port, OptionsVerified),
+ Loop = proplists:get_value(loop, OptionsVerified),
+ % ok, no error found in options -> create listening socket.
+ % {backlog, 30} specifies the length of the OS accept queue
+ % {packet, http} puts the socket into http mode. This makes the socket wait for a HTTP Request line,
+ % and if this is received to immediately switch to receiving HTTP header lines. The socket stays in header
+ % mode until the end of header marker is received (CR,NL,CR,NL), at which time it goes back to wait for a
+ % following HTTP Request line.
+ case gen_tcp:listen(Port, [binary, {packet, http}, {reuseaddr, true}, {active, false}, {backlog, 30}]) of
+ {ok, ListenSocket} ->
+ % create first acceptor process
+ ?DEBUG(debug, "creating first acceptor process", []),
+ AcceptorPid = misultin_socket:start_link(ListenSocket, Port, Loop),
+ {ok, #state{listen_socket = ListenSocket, port = Port, loop = Loop, acceptor = AcceptorPid}};
+ {error, Reason} ->
+ ?DEBUG(error, "error starting: ~p", [Reason]),
+ % error
+ {stop, Reason}
+ end;
+ Reason ->
+ % error found in options
+ {stop, Reason}
+ end.
+
+% ----------------------------------------------------------------------------------------------------------
+% 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 generic fallback
+handle_call(_Request, _From, State) ->
+ {reply, undefined, State}.
+
+% ----------------------------------------------------------------------------------------------------------
+% Function: handle_cast(Msg, State) -> {noreply, State} | {noreply, State, Timeout} | {stop, Reason, State}
+% Description: Handling cast messages.
+% ----------------------------------------------------------------------------------------------------------
+
+% manual shutdown
+handle_cast(stop, State) ->
+ ?DEBUG(info, "manual shutdown..", []),
+ {stop, normal, State};
+
+% create
+handle_cast(create_acceptor, #state{listen_socket = ListenSocket, port = Port, loop = Loop} = State) ->
+ ?DEBUG(debug, "creating new acceptor process", []),
+ AcceptorPid = misultin_socket:start_link(ListenSocket, Port, Loop),
+ {noreply, State#state{acceptor = AcceptorPid}};
+
+% handle_cast generic fallback (ignore)
+handle_cast(_Msg, State) ->
+ ?DEBUG(warning, "received unknown cast message: ~p", [_Msg]),
+ {noreply, State}.
+
+% ----------------------------------------------------------------------------------------------------------
+% Function: handle_info(Info, State) -> {noreply, State} | {noreply, State, Timeout} | {stop, Reason, State}
+% Description: Handling all non call/cast messages.
+% ----------------------------------------------------------------------------------------------------------
+
+% The current acceptor has died normally, ignore
+handle_info({'EXIT', Pid, normal}, #state{acceptor = Pid} = State) ->
+ ?DEBUG(debug, "current acceptor ~p has died normally", [Pid]),
+ {noreply, State};
+
+% The current acceptor has died abnormally, wait a little and try again
+handle_info({'EXIT', Pid, _Abnormal}, #state{listen_socket = ListenSocket, port = Port, loop = Loop, acceptor = Pid} = State) ->
+ ?DEBUG(warning, "current acceptor ~p has died with reason: ~p, waiting a few seconds then respawning", [_Abnormal]),
+ timer:sleep(2000),
+ AcceptorPid = misultin_socket:start_link(ListenSocket, Port, Loop),
+ {noreply, State#state{acceptor = AcceptorPid}};
+
+% An acceptor has died, ignore
+handle_info({'EXIT', _Pid, _Reason}, State) ->
+ ?DEBUG(debug, "the acceptor ~p has died with reason: ~p", [_Reason]),
+ {noreply, State};
+
+% handle_info generic fallback (ignore)
+handle_info(_Info, State) ->
+ ?DEBUG(warning, "received unknown info message: ~p", [_Info]),
+ {noreply, State}.
+
+% ----------------------------------------------------------------------------------------------------------
+% Function: terminate(Reason, State) -> void()
+% Description: This function is called by a gen_server when it is about to terminate. When it returns,
+% the gen_server terminates with Reason. The return value is ignored.
+% ----------------------------------------------------------------------------------------------------------
+terminate(_Reason, #state{listen_socket = ListenSocket, acceptor = AcceptorPid}) ->
+ ?DEBUG(info, "shutting down server with Pid ~p", [self()]),
+ % kill acceptor - TODO: find a more gentle way to do so
+ exit(AcceptorPid, kill),
+ % stop gen_tcp
+ gen_tcp:close(ListenSocket),
+ terminated.
+
+% ----------------------------------------------------------------------------------------------------------
+% Function: code_change(OldVsn, State, Extra) -> {ok, NewState}
+% Description: Convert process state when code is changed.
+% ----------------------------------------------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+% ============================ /\ GEN_SERVER CALLBACKS =====================================================
+
+
+% ============================ \/ INTERNAL FUNCTIONS =======================================================
+
+% Description: Validate and get misultin options.
+get_option(port, Options) ->
+ case proplists:get_value(port, Options) of
+ undefined ->
+ % default to 80
+ {port, 80};
+ Port ->
+ case is_integer(Port) of
+ false ->
+ % error
+ {error, {port_not_integer, Port}};
+ true ->
+ % ok
+ {port, Port}
+ end
+ end;
+get_option(loop, Options) ->
+ case proplists:get_value(loop, Options) of
+ undefined ->
+ % error
+ {error, undefined_loop};
+ Loop ->
+ case is_function(Loop) of
+ false ->
+ % error
+ {error, {loop_not_function, Loop}};
+ true ->
+ % ok
+ {loop, Loop}
+ end
+ end.
+
+% ============================ /\ INTERNAL FUNCTIONS =======================================================
410 src/misultin_req.erl
@@ -0,0 +1,410 @@
+% ==========================================================================================================
+% MISULTIN - Request
+%
+% >-|-|-<°>
+%
+% Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>,
+% Bob Ippolito <bob@mochimedia.com> for Mochi Media, Inc.
+% All rights reserved.
+%
+% Code portions from Bob Ippolito have been originally taken under MIT license from MOCHIWEB:
+% <http://code.google.com/p/mochiweb/>
+%
+% BSD License
+%
+% Redistribution and use in source and binary forms, with or without modification, are permitted provided
+% that the following conditions are met:
+%
+% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+% following disclaimer.
+% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+% the following disclaimer in the documentation and/or other materials provided with the distribution.
+% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+% products derived from this software without specific prior written permission.
+%
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+% POSSIBILITY OF SUCH DAMAGE.
+% ==========================================================================================================
+-module(misultin_req, [Req]).
+-vsn('0.1').
+
+% macros
+-define(PERCENT, 37). % $\%
+-define(FULLSTOP, 46). % $\.
+-define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse
+ (C >= $a andalso C =< $f) orelse
+ (C >= $A andalso C =< $F))).
+
+% API
+-export([raw/0]).
+-export([ok/1, ok/2, ok/3, respond/1, respond/2, respond/3, respond/4]).
+-export([get/1, parse_qs/0, parse_post/0, file/1, resource/1]).
+
+% includes
+-include("../include/misultin.hrl").
+
+
+% ============================ \/ API ======================================================================
+
+% Description: Returns raw request content.
+raw() ->
+ Req.
+
+% Description: Formats a 200 response.
+ok(Template) ->
+ ok([], Template, []).
+ok(Headers, Template) ->
+ ok(Headers, Template, []).
+ok(Headers, Template, Vars) ->
+ respond(200, Headers, Template, Vars).
+
+% Description: Formats a response.
+respond(HttpCode) when is_integer(HttpCode) =:= true ->
+ {raw, lists:flatten(io_lib:format("HTTP/1.1 ~p Not Found\r\n\r\n", [HttpCode]))}.
+respond(HttpCode, Template) ->
+ respond(HttpCode, [], Template, []).
+respond(HttpCode, Headers, Template) ->
+ respond(HttpCode, Headers, Template, []).
+respond(HttpCode, Headers, Template, Vars) when is_list(Template) =:= true ->
+ {HttpCode, Headers, list_to_binary(lists:flatten(io_lib:format(Template, Vars)))};
+respond(HttpCode, Headers, Template, _Vars) when is_binary(Template) =:= true ->
+ {HttpCode, Headers, Template}.
+
+% Description: Sends a file.
+file(FilePath) ->
+ % get filename
+ FileName = filename:basename(FilePath),
+ ?DEBUG(debug, "sending filename ~p", [FileName]),
+ % read file
+ case file:read_file(FilePath) of
+ {error, _Reason} ->
+ {raw, ?internal_server_error_500};
+ {ok, Binary} ->
+ Headers = [
+ {'Content-Type', get_content_type(FileName)},
+ {'Content-Disposition', lists:flatten(io_lib:format("attachment; filename=~s", [FileName]))},
+ {'Content-Size', size(Binary)}
+ ],
+ respond(200, Headers, Binary)
+ end.
+
+% Description: Get request info.
+get(peer_addr) ->
+ Req#req.peer_addr;
+get(peer_port) ->
+ Req#req.peer_port;
+get(connection) ->
+ Req#req.connection;
+get(content_length) ->
+ Req#req.content_length;
+get(vsn) ->
+ Req#req.vsn;
+get(method) ->
+ Req#req.method;
+get(uri) ->
+ Req#req.uri;
+get(args) ->
+ Req#req.args;
+get(headers) ->
+ Req#req.headers;
+get(body) ->
+ Req#req.body.
+
+% Description: Parse QueryString
+parse_qs() ->
+ parse_qs(Req#req.args).
+
+% Description: Parse Post
+parse_post() ->
+ % get header confirmation
+ case proplists:get_value('Content-Type', Req#req.headers) of
+ undefined ->
+ [];
+ "application/x-www-form-urlencoded" ->
+ parse_qs(Req#req.body)
+ end.
+
+% Description: Sets resource elements for restful services.
+resource(Options) when is_list(Options) ->
+ % clean uri
+ {_UriType, RawUri} = Req#req.uri,
+ Uri = lists:foldl(fun(Option, Acc) -> clean_uri(Option, Acc) end, RawUri, Options),
+ % split
+ string:tokens(Uri, "/").
+
+% ============================ /\ API ======================================================================
+
+
+
+% ============================ \/ INTERNAL FUNCTIONS =======================================================
+
+% parse querystring & post
+parse_qs(Binary) when is_binary(Binary) ->
+ parse_qs(binary_to_list(Binary));
+parse_qs(String) ->
+ parse_qs(String, []).
+parse_qs([], Acc) ->
+ lists:reverse(Acc);
+parse_qs(String, Acc) ->
+ {Key, Rest} = parse_qs_key(String),
+ {Value, Rest1} = parse_qs_value(Rest),
+ parse_qs(Rest1, [{Key, Value} | Acc]).
+parse_qs_key(String) ->
+ parse_qs_key(String, []).
+parse_qs_key([], Acc) ->
+ {qs_revdecode(Acc), ""};
+parse_qs_key([$= | Rest], Acc) ->
+ {qs_revdecode(Acc), Rest};
+parse_qs_key(Rest=[$; | _], Acc) ->
+ {qs_revdecode(Acc), Rest};
+parse_qs_key(Rest=[$& | _], Acc) ->
+ {qs_revdecode(Acc), Rest};
+parse_qs_key([C | Rest], Acc) ->
+ parse_qs_key(Rest, [C | Acc]).
+parse_qs_value(String) ->
+ parse_qs_value(String, []).
+parse_qs_value([], Acc) ->
+ {qs_revdecode(Acc), ""};
+parse_qs_value([$; | Rest], Acc) ->
+ {qs_revdecode(Acc), Rest};
+parse_qs_value([$& | Rest], Acc) ->
+ {qs_revdecode(Acc), Rest};
+parse_qs_value([C | Rest], Acc) ->
+ parse_qs_value(Rest, [C | Acc]).
+
+% revdecode
+qs_revdecode(S) ->
+ qs_revdecode(S, []).
+qs_revdecode([], Acc) ->
+ Acc;
+qs_revdecode([$+ | Rest], Acc) ->
+ qs_revdecode(Rest, [$\s | Acc]);
+qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) ->
+ qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]);
+qs_revdecode([C | Rest], Acc) ->
+ qs_revdecode(Rest, [C | Acc]).
+
+% unexdigit
+unhexdigit(C) when C >= $0, C =< $9 -> C - $0;
+unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10;
+unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10.
+
+% unquote
+unquote(Binary) when is_binary(Binary) ->
+ unquote(binary_to_list(Binary));
+unquote(String) ->
+ qs_revdecode(lists:reverse(String)).
+
+% get content type
+get_content_type(FileName) ->
+ case filename:extension(FileName) of
+ % most common first
+ ".doc" -> "application/msword";
+ ".exe" -> "application/octet-stream";
+ ".pdf" -> "application/pdf";
+ ".rtf" -> "application/rtf";
+ ".ppt" -> "application/vnd.ms-powerpoint";
+ ".tgz" -> "application/x-compressed";
+ ".tar" -> "application/x-tar";
+ ".zip" -> "application/zip";
+ ".mp3" -> "audio/mpeg";
+ ".wav" -> "audio/x-wav";
+ ".bmp" -> "image/bmp";
+ ".ram" -> "audio/x-pn-realaudio";
+ ".gif" -> "image/gif";
+ ".jpe" -> "image/jpeg";
+ ".jpeg" -> "image/jpeg";
+ ".jpg" -> "image/jpeg";
+ ".tif" -> "image/tiff";
+ ".tiff" -> "image/tiff";
+ ".htm" -> "text/html";
+ ".html" -> "text/html";
+ ".txt" -> "text/plain";
+ ".mp2" -> "video/mpeg";
+ ".mpa" -> "video/mpeg";
+ ".mpe" -> "video/mpeg";
+ ".mpeg" -> "video/mpeg";
+ ".mpg" -> "video/mpeg";
+ ".mov" -> "video/quicktime";
+ ".avi" -> "video/x-msvideo";
+ % less common last
+ ".evy" -> "application/envoy";
+ ".fif" -> "application/fractals";
+ ".spl" -> "application/futuresplash";
+ ".hta" -> "application/hta";
+ ".acx" -> "application/internet-property-stream";
+ ".hqx" -> "application/mac-binhex40";
+ ".dot" -> "application/msword";
+ ".bin" -> "application/octet-stream";
+ ".class" -> "application/octet-stream";
+ ".dms" -> "application/octet-stream";
+ ".lha" -> "application/octet-stream";
+ ".lzh" -> "application/octet-stream";
+ ".oda" -> "application/oda";
+ ".axs" -> "application/olescript";
+ ".prf" -> "application/pics-rules";
+ ".p10" -> "application/pkcs10";
+ ".crl" -> "application/pkix-crl";
+ ".ai" -> "application/postscript";
+ ".eps" -> "application/postscript";
+ ".ps" -> "application/postscript";
+ ".setpay" -> "application/set-payment-initiation";
+ ".setreg" -> "application/set-registration-initiation";
+ ".xla" -> "application/vnd.ms-excel";
+ ".xlc" -> "application/vnd.ms-excel";
+ ".xlm" -> "application/vnd.ms-excel";
+ ".xls" -> "application/vnd.ms-excel";
+ ".xlt" -> "application/vnd.ms-excel";
+ ".xlw" -> "application/vnd.ms-excel";
+ ".msg" -> "application/vnd.ms-outlook";
+ ".sst" -> "application/vnd.ms-pkicertstore";
+ ".cat" -> "application/vnd.ms-pkiseccat";
+ ".stl" -> "application/vnd.ms-pkistl";
+ ".pot" -> "application/vnd.ms-powerpoint";
+ ".pps" -> "application/vnd.ms-powerpoint";
+ ".mpp" -> "application/vnd.ms-project";
+ ".wcm" -> "application/vnd.ms-works";
+ ".wdb" -> "application/vnd.ms-works";
+ ".wks" -> "application/vnd.ms-works";
+ ".wps" -> "application/vnd.ms-works";
+ ".hlp" -> "application/winhlp";
+ ".bcpio" -> "application/x-bcpio";
+ ".cdf" -> "application/x-cdf";
+ ".z" -> "application/x-compress";
+ ".cpio" -> "application/x-cpio";
+ ".csh" -> "application/x-csh";
+ ".dcr" -> "application/x-director";
+ ".dir" -> "application/x-director";
+ ".dxr" -> "application/x-director";
+ ".dvi" -> "application/x-dvi";
+ ".gtar" -> "application/x-gtar";
+ ".gz" -> "application/x-gzip";
+ ".hdf" -> "application/x-hdf";
+ ".ins" -> "application/x-internet-signup";
+ ".isp" -> "application/x-internet-signup";
+ ".iii" -> "application/x-iphone";
+ ".js" -> "application/x-javascript";
+ ".latex" -> "application/x-latex";
+ ".mdb" -> "application/x-msaccess";
+ ".crd" -> "application/x-mscardfile";
+ ".clp" -> "application/x-msclip";
+ ".dll" -> "application/x-msdownload";
+ ".m13" -> "application/x-msmediaview";
+ ".m14" -> "application/x-msmediaview";
+ ".mvb" -> "application/x-msmediaview";
+ ".wmf" -> "application/x-msmetafile";
+ ".mny" -> "application/x-msmoney";
+ ".pub" -> "application/x-mspublisher";
+ ".scd" -> "application/x-msschedule";
+ ".trm" -> "application/x-msterminal";
+ ".wri" -> "application/x-mswrite";
+ ".nc" -> "application/x-netcdf";
+ ".pma" -> "application/x-perfmon";
+ ".pmc" -> "application/x-perfmon";
+ ".pml" -> "application/x-perfmon";
+ ".pmr" -> "application/x-perfmon";
+ ".pmw" -> "application/x-perfmon";
+ ".p12" -> "application/x-pkcs12";
+ ".pfx" -> "application/x-pkcs12";
+ ".p7b" -> "application/x-pkcs7-certificates";
+ ".spc" -> "application/x-pkcs7-certificates";
+ ".p7r" -> "application/x-pkcs7-certreqresp";
+ ".p7c" -> "application/x-pkcs7-mime";
+ ".p7m" -> "application/x-pkcs7-mime";
+ ".p7s" -> "application/x-pkcs7-signature";
+ ".sh" -> "application/x-sh";
+ ".shar" -> "application/x-shar";
+ ".swf" -> "application/x-shockwave-flash";
+ ".sit" -> "application/x-stuffit";
+ ".sv4cpio" -> "application/x-sv4cpio";
+ ".sv4crc" -> "application/x-sv4crc";
+ ".tcl" -> "application/x-tcl";
+ ".tex" -> "application/x-tex";
+ ".texi" -> "application/x-texinfo";
+ ".texinfo" -> "application/x-texinfo";
+ ".roff" -> "application/x-troff";
+ ".t" -> "application/x-troff";
+ ".tr" -> "application/x-troff";
+ ".man" -> "application/x-troff-man";
+ ".me" -> "application/x-troff-me";
+ ".ms" -> "application/x-troff-ms";
+ ".ustar" -> "application/x-ustar";
+ ".src" -> "application/x-wais-source";
+ ".cer" -> "application/x-x509-ca-cert";
+ ".crt" -> "application/x-x509-ca-cert";
+ ".der" -> "application/x-x509-ca-cert";
+ ".pko" -> "application/ynd.ms-pkipko";
+ ".au" -> "audio/basic";
+ ".snd" -> "audio/basic";
+ ".mid" -> "audio/mid";
+ ".rmi" -> "audio/mid";
+ ".aif" -> "audio/x-aiff";
+ ".aifc" -> "audio/x-aiff";
+ ".aiff" -> "audio/x-aiff";
+ ".m3u" -> "audio/x-mpegurl";
+ ".ra" -> "audio/x-pn-realaudio";
+ ".cod" -> "image/cis-cod";
+ ".ief" -> "image/ief";
+ ".jfif" -> "image/pipeg";
+ ".svg" -> "image/svg+xml";
+ ".ras" -> "image/x-cmu-raster";
+ ".cmx" -> "image/x-cmx";
+ ".ico" -> "image/x-icon";
+ ".pnm" -> "image/x-portable-anymap";
+ ".pbm" -> "image/x-portable-bitmap";
+ ".pgm" -> "image/x-portable-graymap";
+ ".ppm" -> "image/x-portable-pixmap";
+ ".rgb" -> "image/x-rgb";
+ ".xbm" -> "image/x-xbitmap";
+ ".xpm" -> "image/x-xpixmap";
+ ".xwd" -> "image/x-xwindowdump";
+ ".mht" -> "message/rfc822";
+ ".mhtml" -> "message/rfc822";
+ ".nws" -> "message/rfc822";
+ ".css" -> "text/css";
+ ".323" -> "text/h323";
+ ".stm" -> "text/html";
+ ".uls" -> "text/iuls";
+ ".bas" -> "text/plain";
+ ".c" -> "text/plain";
+ ".h" -> "text/plain";
+ ".rtx" -> "text/richtext";
+ ".sct" -> "text/scriptlet";
+ ".tsv" -> "text/tab-separated-values";
+ ".htt" -> "text/webviewhtml";
+ ".htc" -> "text/x-component";
+ ".etx" -> "text/x-setext";
+ ".vcf" -> "text/x-vcard";
+ ".mpv2" -> "video/mpeg";
+ ".qt" -> "video/quicktime";
+ ".lsf" -> "video/x-la-asf";
+ ".lsx" -> "video/x-la-asf";
+ ".asf" -> "video/x-ms-asf";
+ ".asr" -> "video/x-ms-asf";
+ ".asx" -> "video/x-ms-asf";
+ ".movie" -> "video/x-sgi-movie";
+ ".flr" -> "x-world/x-vrml";
+ ".vrml" -> "x-world/x-vrml";
+ ".wrl" -> "x-world/x-vrml";
+ ".wrz" -> "x-world/x-vrml";
+ ".xaf" -> "x-world/x-vrml";
+ ".xof" -> "x-world/x-vrml";
+ _ -> "application/octet-stream"
+ end.
+
+% Description: Clean URI.
+clean_uri(lowercase, Uri) ->
+ string:to_lower(Uri);
+clean_uri(urldecode, Uri) ->
+ unquote(Uri);
+% ignore unexisting option
+clean_uri(_Unavailable, Uri) ->
+ Uri.
+
+% ============================ /\ INTERNAL FUNCTIONS =======================================================
273 src/misultin_socket.erl
@@ -0,0 +1,273 @@
+% ==========================================================================================================
+% MISULTIN - Socket
+%
+% >-|-|-<°>
+%
+% Copyright (C) 2009, Roberto Ostinelli <roberto@ostinelli.net>, Sean Hinde.
+% All rights reserved.
+%
+% Code portions from Sean Hinde have been originally taken under BSD license from Trapexit at the address:
+% <http://www.trapexit.org/A_fast_web_server_demonstrating_some_undocumented_Erlang_features>
+%
+% BSD License
+%
+% Redistribution and use in source and binary forms, with or without modification, are permitted provided
+% that the following conditions are met:
+%
+% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+% following disclaimer.
+% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+% the following disclaimer in the documentation and/or other materials provided with the distribution.
+% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+% products derived from this software without specific prior written permission.
+%
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+% POSSIBILITY OF SUCH DAMAGE.
+% ==========================================================================================================
+-module(misultin_socket).
+-vsn('0.1').
+
+% API
+-export([start_link/3]).
+
+% callbacks
+-export([init/3]).
+
+% records
+-record(c, {
+ sock,
+ port,
+ loop
+}).
+
+% includes
+-include("../include/misultin.hrl").
+
+
+% ============================ \/ API ======================================================================
+
+% Function: {ok,Pid} | ignore | {error, Error}
+% Description: Starts the socket.
+start_link(ListenSocket, ListenPort, Loop) ->
+ proc_lib:spawn_link(?MODULE, init, [ListenSocket, ListenPort, Loop]).
+
+% Description: Initiates the socket.
+init(ListenSocket, ListenPort, Loop) ->
+ case catch gen_tcp:accept(ListenSocket) of
+ {ok, Socket} ->
+ ?DEBUG(debug, "accepted an incoming TCP connection", []),
+ % Send the cast message to the listener process to create a new acceptor
+ misultin:create_acceptor(),
+ {ok, {Addr, Port}} = inet:peername(Socket),
+ C = #c{sock = Socket, port = ListenPort, loop = Loop},
+ % jump to state 'request'
+ ?DEBUG(debug, "jump to state request", []),
+ request(C, #req{peer_addr = Addr, peer_port = Port});
+ _Else ->
+ ?DEBUG(error, "accept failed error: ~p", [_Else]),
+ exit({error, accept_failed})
+ end.
+
+% ============================ /\ API ======================================================================
+
+
+% ============================ \/ INTERNAL FUNCTIONS =======================================================
+
+% REQUEST: wait for a HTTP Request line. Transition to state headers if one is received.
+request(C, Req) ->
+ case gen_tcp:recv(C#c.sock, 0, 30000) of
+ {ok, {http_request, Method, Path, Version}} ->
+ headers(C, Req#req{vsn = Version, method = Method, uri = Path}, []);
+ {error, {http_error, "\r\n"}} ->
+ request(C, Req);
+ {error, {http_error, "\n"}} ->
+ request(C, Req);
+ _Other ->
+ exit(normal)
+ end.
+
+% HEADERS: collect HTTP headers. After the end of header marker transition to body state.
+headers(C, Req, H) ->
+ case gen_tcp:recv(C#c.sock, 0, ?server_idle_timeout) of
+ {ok, {http_header, _, 'Content-Length', _, Val}} ->
+ Len = list_to_integer(Val),
+ headers(C, Req#req{content_length = Len}, [{'Content-Length', Len}|H]);
+ {ok, {http_header, _, 'Connection', _, Val}} ->
+ KeepAlive = keep_alive(Req#req.vsn, Val),
+ headers(C, Req#req{connection = KeepAlive}, [{'Connection', Val}|H]);
+ {ok, {http_header, _, Header, _, Val}} ->
+ headers(C, Req, [{Header, Val}|H]);
+ {error, {http_error, "\r\n"}} ->
+ headers(C, Req, H);
+ {error, {http_error, "\n"}} ->
+ headers(C, Req, H);
+ {ok, http_eoh} ->
+ body(C, Req#req{headers = lists:reverse(H)});
+ _Other ->
+ exit(normal)
+ end.
+
+% Shall we keep the connection alive? Default case for HTTP/1.1 is yes, default for HTTP/1.0 is no.
+% string:to_upper is used only as last resort.
+keep_alive({1,1}, "close") -> close;
+keep_alive({1,1}, "Close") -> close;
+keep_alive({1,1}, Head) ->
+ case string:to_upper(Head) of
+ "CLOSE" -> close;
+ _ -> keep_alive
+ end;
+keep_alive({1,0}, "Keep-Alive") -> keep_alive;
+keep_alive({1,0}, Head) ->
+ case string:to_upper(Head) of
+ "KEEP-ALIVE" -> keep_alive;
+ _ -> close
+ end;
+keep_alive({0,9}, _) -> close;
+keep_alive(_Vsn, _KA) -> close.
+
+% BODY: collect the body of the HTTP request if there is one, and lookup and call the implementation callback.
+% Depending on whether the request is persistent transition back to state request to await the next request or exit.
+body(#c{sock = Sock} = C, Req) ->
+ case Req#req.method of
+ 'GET' ->
+ Close = handle_get(C, Req),
+ case Close of
+ close ->
+ gen_tcp:close(Sock);
+ keep_alive ->
+ inet:setopts(Sock, [{packet, http}]),
+ request(C, #req{})
+ end;
+ 'POST' when is_integer(Req#req.content_length) ->
+ inet:setopts(Sock, [{packet, raw}]),
+ case gen_tcp:recv(Sock, Req#req.content_length, 60000) of
+ {ok, Bin} ->
+ Close = handle_post(C, Req#req{body = Bin}),
+ case Close of
+ close ->
+ gen_tcp:close(Sock);
+ keep_alive ->
+ inet:setopts(Sock, [{packet, http}]),
+ request(C, #req{})
+ end;
+ _Other ->
+ exit(normal)
+ end;
+ _Other ->
+ send(C, ?not_implemented_501),
+ exit(normal)
+ end.
+
+% handle a get request
+handle_get(C, #req{connection = Conn} = Req) ->
+ case Req#req.uri of
+ {abs_path, Path} ->
+ {F, Args} = split_at_q_mark(Path, []),
+ call_mfa(C, Req#req{args = Args, uri = {abs_path, F}}),
+ Conn;
+ {absoluteURI, http, _Host, _, Path} ->
+ {F, Args} = split_at_q_mark(Path, []),
+ call_mfa(C, Req#req{args = Args, uri = {absoluteURI, F}}),
+ Conn;
+ {absoluteURI, _Other_method, _Host, _, _Path} ->
+ send(C, ?not_implemented_501),
+ close;
+ {scheme, _Scheme, _RequestString} ->
+ send(C, ?not_implemented_501),
+ close;
+ _ ->
+ send(C, ?forbidden_403),
+ close
+ end.
+
+% handle a post request
+handle_post(C, #req{connection = Conn} = Req) ->
+ case Req#req.uri of
+ {abs_path, _Path} ->
+ call_mfa(C, Req),
+ Conn;
+ {absoluteURI, http, _Host, _, _Path} ->
+ call_mfa(C, Req),
+ Conn;
+ {absoluteURI, _Other_method, _Host, _, _Path} ->
+ send(C, ?not_implemented_501),
+ close;
+ {scheme, _Scheme, _RequestString} ->
+ send(C, ?not_implemented_501),
+ close;
+ _ ->
+ send(C, ?forbidden_403),
+ close
+ end.
+
+% Description: Main dispatcher
+call_mfa(#c{loop = Loop} = C, Request) ->
+ % create request
+ Req = misultin_req:new(Request),
+ % call loop
+ case catch Loop(Req) of
+ {'EXIT', _Reason} ->
+ ?DEBUG(error, "worker crash: ~p", [_Reason]),
+ send(C, ?internal_server_error_500),
+ exit(normal);
+ {HttpCode, Headers0, Body} ->
+ ?DEBUG(debug, "sending response", []),
+ Headers = add_content_length(Headers0, Body),
+ Enc_headers = enc_headers(Headers),
+ Resp = [list_to_binary(lists:flatten(io_lib:format("HTTP/1.1 ~p OK\r\n", [HttpCode]))), Enc_headers, <<"\r\n">>, Body],
+ send(C, Resp);
+ {raw, Head} ->
+ ?DEBUG(debug, "sending httpcode response: ~p", [Head]),
+ send(C, Head);
+ _ ->
+ send(C, ?not_found_404)
+ end.
+
+% Description: Add content length
+add_content_length(Headers, Body) ->
+ case lists:keysearch('Content-Length', 1, Headers) of
+ {value, _} ->
+ Headers;
+ false ->
+ [{'Content-Length', size(Body)}|Headers]
+ end.
+
+% Description: Encode headers
+enc_headers([{Tag, Val}|T]) when is_atom(Tag) ->
+ [atom_to_list(Tag), ": ", enc_header_val(Val), "\r\n"|enc_headers(T)];
+enc_headers([{Tag, Val}|T]) when is_list(Tag) ->
+ [Tag, ": ", enc_header_val(Val), "\r\n"|enc_headers(T)];
+enc_headers([]) ->
+ [].
+enc_header_val(Val) when is_atom(Val) ->
+ atom_to_list(Val);
+enc_header_val(Val) when is_integer(Val) ->
+ integer_to_list(Val);
+enc_header_val(Val) ->
+ Val.
+
+% Split the path at the ?
+split_at_q_mark([$?|T], Acc) ->
+ {lists:reverse(Acc), T};
+split_at_q_mark([H|T], Acc) ->
+ split_at_q_mark(T, [H|Acc]);
+split_at_q_mark([], Acc) ->
+ {lists:reverse(Acc), []}.
+
+% TCP send
+send(#c{sock = Sock}, Data) ->
+ case gen_tcp:send(Sock, Data) of
+ ok ->
+ ok;
+ {error, _Reason} ->
+ ?DEBUG(debug, "worker crash: ~p", [_Reason]),
+ exit(normal)
+ end.
+
+% ============================ /\ INTERNAL FUNCTIONS =======================================================

0 comments on commit 72a97f4

Please sign in to comment.