Browse files

added SSL support

  • Loading branch information...
1 parent b802fc9 commit 38d38fa10a5f51a2b00b6fbc00afe88149e066d7 @ostinelli committed Apr 21, 2010
View
3 README.txt
@@ -63,6 +63,9 @@ API Documentation is available online on the Misultin's wiki: http://code.google
CHANGELOG
==========================================================================================================
+0.5: - added SSL support
+ - refactoring of the acceptor loop
+
0.4: - added preliminary websocket support
0.3.4: - added Req support to return the socket handling the request
View
51 examples/misultin_ssl.erl
@@ -0,0 +1,51 @@
+% ==========================================================================================================
+% MISULTIN - Example: Echoes inputted GET variables into an XML.
+%
+% >-|-|-(°>
+%
+% 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_ssl).
+-export([start/1, stop/0]).
+
+% start misultin http server
+start(Port) ->
+ misultin:start_link([{port, Port}, {loop, fun(Req) -> handle_http(Req) end},
+ {ssl, [
+ {certfile, "../priv/test_certificate.pem"},
+ {keyfile, "../priv/test_privkey.pem"},
+ {password, "misultin"}
+ ]}
+ ]).
+
+% stop misultin
+stop() ->
+ misultin:stop().
+
+% callback on request received
+handle_http(Req) ->
+ % output
+ Req:ok("ok").
+
View
6 include/misultin.hrl
@@ -28,7 +28,7 @@
% define debug
-ifdef(log_debug).
--define(LOG_DEBUG(Str, Args), erlang:apply(error_logger, info_msg, [lists:concat(["[DEBUG] module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args])).
+-define(LOG_DEBUG(Str, Args), erlang:apply(error_logger, info_msg, [lists:concat(["[DEBUG] pid: ", pid_to_list(self()), "~n module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args])).
-define(LOG_INFO(Str, Args), erlang:apply(error_logger, info_msg, [lists:concat([" module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args])).
-define(LOG_WARNING(Str, Args), erlang:apply(error_logger, warning_msg, [lists:concat([" module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args])).
-define(LOG_ERROR(Str, Args), erlang:apply(error_logger, error_msg, [lists:concat([" module: ", ?MODULE, "~n line: ", ?LINE, "~n", Str, "~n"]), Args])).
@@ -56,8 +56,10 @@
% Request
-record(req, {
socket, % the socket handling the request
+ socket_mode, % http | ssl
peer_addr, % peer IP | undefined
peer_port, % peer port | undefined
+ peer_cert, % undefined | the DER encoded peer certificate that can be decoded with public_key:pkix_decode_cert/2
connection = keep_alive, % keep_alive | close
content_length, % Integer
vsn, % {Maj,Min}
@@ -71,8 +73,10 @@
% Websocket Request
-record(ws, {
socket, % the socket handling the request
+ socket_mode, % http | ssl
peer_addr, % peer IP | undefined
peer_port, % peer port | undefined
+ peer_cert, % undefined | the DER encoded peer certificate that can be decoded with public_key:pkix_decode_cert/2
origin, % the originator
host, % the host
path % the websocket GET request path
View
12 priv/README.txt
@@ -0,0 +1,12 @@
+==========================================================================================================
+DISCLAIMER
+==========================================================================================================
+
+Please note that the included certificate 'test_certificate.pem' and private key 'test_privkey.pem' are
+publicly available via the Misultin repositories, and should NOT be used for any secure application. These
+have been provided here for your testing comfort only.
+
+You may consider getting your copy of OpenSSL <http://www.openssl.org> and generate your own certificate
+and private key by issuing a command similar to:
+
+openssl req -new -x509 -newkey rsa:1024 -days 365 -keyout test_privkey.pem -out test_certificate.pem
View
21 priv/test_certificate.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAvCgAwIBAgIJAPFFl046vv7vMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
+VQQGEwJJVDENMAsGA1UECBMEQ29tbzENMAsGA1UEBxMEQ29tbzERMA8GA1UEChMI
+TWlzdWx0aW4xETAPBgNVBAsTCE1pc3VsdGluMREwDwYDVQQDEwhNaXN1bHRpbjEk
+MCIGCSqGSIb3DQEJARYVcm9iZXJ0b0Bvc3RpbmVsbGkubmV0MB4XDTEwMDQyMDE3
+NDczOFoXDTIwMDQxNzE3NDczOFowgYoxCzAJBgNVBAYTAklUMQ0wCwYDVQQIEwRD
+b21vMQ0wCwYDVQQHEwRDb21vMREwDwYDVQQKEwhNaXN1bHRpbjERMA8GA1UECxMI
+TWlzdWx0aW4xETAPBgNVBAMTCE1pc3VsdGluMSQwIgYJKoZIhvcNAQkBFhVyb2Jl
+cnRvQG9zdGluZWxsaS5uZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM8A
+BT4OzNCcgrfVnzmEp8VdyR+O0ubsSBunX8J/BTKbWgZVrrGrY9fO8AkVmD1VFm8n
+w/yLlz/Ow24j40UCY82Y9gMDgADa3BqcDPn1lPdGpOHhaMXMRFKnrVOfwMPE0wfx
+kpr9/I5rAPAnkX1WtvOWMK0V5yGsuIMBd2S4VzmrAgMBAAGjgfIwge8wHQYDVR0O
+BBYEFItpRD/8fT21N/pLeSWKexZHWP/3MIG/BgNVHSMEgbcwgbSAFItpRD/8fT21
+N/pLeSWKexZHWP/3oYGQpIGNMIGKMQswCQYDVQQGEwJJVDENMAsGA1UECBMEQ29t
+bzENMAsGA1UEBxMEQ29tbzERMA8GA1UEChMITWlzdWx0aW4xETAPBgNVBAsTCE1p
+c3VsdGluMREwDwYDVQQDEwhNaXN1bHRpbjEkMCIGCSqGSIb3DQEJARYVcm9iZXJ0
+b0Bvc3RpbmVsbGkubmV0ggkA8UWXTjq+/u8wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOBgQA5ePVuJo6LcHegSKfD1GlTu2Ffkom547e9PYmKBaDyuNhCfP4F
+YB4GMi1SZeCsaYzJpEOCY9JEJD8hJO7xPnyKnwc3FhT7KfYHWO7FNZdmdMxE99mi
+lolCiDfglJkQCPihtxK5TKBDK+lzMub+1Xmc3VOvIuonzjh+VkSz1rCciQ==
+-----END CERTIFICATE-----
View
18 priv/test_privkey.pem
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,E6F891BC96EC9B16
+
+kBpeVQGiACcY96k5ix5H+/Htt6XIGB2cyUhBMZlgBPQSAttU9B94YV+nXZmIVi8f
+r0cBDqGZ7uv+YjQom1HWw/NilWJr5x67VOhBGvg3kb2wYe3aMeGrmqRpPqNi6K2U
+t9QuragXj7tyFu8+sYnW0SeI7GN9aghF2S9bVKasOFDBHGDg1igb7PzBsctrQh1Q
+Hhkl2/Ql3+j18yUNZ2vCZgvGE2UfX5TJ79irpUCFiSgbf31EU7WCePZfSuNZfJB7
+hjQM5q/vfEmgqVCdVR1W8wFxdalPJOA819gKwpKBBgpfWPn2Gvyw/yAhm3FPLasG
+OgrY9PgsAcfozXgCOJP6NEP8IHzvb7kWoTussgBCd8P2Vv+YTp8WkBQDtpAlQExf
+03sKUE9+pDXnzLnq4Pore6xBlzcZ4hu35WUo854UApRT/OQB5+Kmth9pqax43bbp
+9Lfg6Zg9NXDuGHbRdK3U48uXLa7lDi5TMJ3LHuJ+DxZq4WNCTbMA3YZTFnjGiD/N
+NvTy54oQThjn67N7BQe3PeWI2ryGEWJAXShnc0ZTaxQaQn+18zVAe2tQOFUPhbbA
+Bq7zx49gea1tlJC1DHLktmw72v0g5W3HZ2fP1m+9socH9n4iORGEpicwuMgf9Tlb
+T0mFP3hL0y1wEdBoohF6Euk1Y33P44tbXsYn6bP2/mVmWphVA3wWOocMYw/UgSxM
+pzpC+z6y19dhqYNJyywgsMv6GQdrovW1DF2udmjx1Mv7qbwdJlH0GyLdyBL8aZFb
+WBXI2PjWWtZS/F1U7QsELzV3mM1U8n+K5hZuBPtvLzohpq2W59tPkg==
+-----END RSA PRIVATE KEY-----
View
4 src/misultin.app
@@ -1,7 +1,7 @@
{application, misultin,
[
- {description, "Lightweight HTTP and Websockets Server Library"},
- {vsn, "0.4.0"},
+ {description, "Lightweight HTTP(s) and Websockets Server Library"},
+ {vsn, "0.5.0"},
{modules, [misultin, misultin_req, misultin_socket, misultin_utility, misultin_websocket, misultin_ws]},
{registered, [misultin]},
{env, []},
View
143 src/misultin.erl
@@ -32,21 +32,23 @@
% ==========================================================================================================
-module(misultin).
-behaviour(gen_server).
--vsn("0.4.0").
+-vsn("0.5.0").
% 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, websocket_pid_add/1, websocket_pid_remove/1]).
+-export([start_link/1, stop/0, websocket_pid_add/1, websocket_pid_remove/1]).
% macros
-define(SERVER, ?MODULE).
% records
-record(state, {
listen_socket,
+ socket_mode,
port,
+ options,
loop,
acceptor,
recv_timeout,
@@ -71,11 +73,6 @@ start_link(Options) when is_list(Options) ->
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).
-
% Function -> ok
% Description: Adds a new websocket pid reference to status
websocket_pid_add(WsPid) ->
@@ -106,41 +103,72 @@ init([Options]) ->
{backlog, 128, fun is_integer/1, backlog_not_integer},
{recv_timeout, 30*1000, fun is_integer/1, recv_timeout_not_integer},
{stream_support, true, fun is_boolean/1, invalid_stream_support_option},
- {ws_loop, none, fun is_function/1, ws_loop_not_function}
+ {ws_loop, none, fun is_function/1, ws_loop_not_function},
+ {ssl, false, fun check_ssl_options/1, invalid_ssl_options}
],
OptionsVerified = lists:foldl(fun(OptionName, Acc) -> [get_option(OptionName, Options)|Acc] end, [], OptionProps),
case proplists:get_value(error, OptionsVerified) of
undefined ->
- % get options
+ % ok, no error found in options ->
Ip = proplists:get_value(ip, OptionsVerified),
Port = proplists:get_value(port, OptionsVerified),
Loop = proplists:get_value(loop, OptionsVerified),
Backlog = proplists:get_value(backlog, OptionsVerified),
RecvTimeout = proplists:get_value(recv_timeout, OptionsVerified),
StreamSupport = proplists:get_value(stream_support, OptionsVerified),
WsLoop = proplists:get_value(ws_loop, OptionsVerified),
+ SslOptions0 = proplists:get_value(ssl, OptionsVerified),
% ipv6 support
?LOG_DEBUG("ip address is: ~p", [Ip]),
- InetOpt = case Ip of
- {_, _, _, _} ->
- % IPv4
- inet;
- {_, _, _, _, _, _, _, _} ->
- % IPv6
- inet6
+ % set additional options according to socket mode if necessary
+ Continue = case SslOptions0 of
+ false ->
+ % without SSL
+ SocketMode = http,
+ InetOpt = case Ip of
+ {_, _, _, _} ->
+ % IPv4
+ inet;
+ {_, _, _, _, _, _, _, _} ->
+ % IPv6
+ inet6
+ end,
+ AdditionalOptions = [InetOpt],
+ true;
+ _ ->
+ % with SSL
+ SocketMode = ssl,
+ % the only current way to use {active, once} in Ssl is to start the crypto module
+ % and set {ssl_imp, new} as SSL option, see
+ % <http://www.erlang.org/cgi-bin/ezmlm-cgi?4:mss:50633:201004:fpopocbfkpppecdembbe>
+ AdditionalOptions = [{ssl_imp, new}|SslOptions0],
+ % start Ssl and crypto applications if necessary, and get outcomes
+ AppStartResults = lists:keyfind(error, 1, [start_application(ssl), start_application(crypto)]),
+ case AppStartResults of
+ false ->
+ % all applications started succesfully
+ true;
+ _ ->
+ % error starting application
+ {error, AppStartResults}
+ end
end,
- % ok, no error found in options -> create listening socket.
- case gen_tcp:listen(Port, [binary, {packet, http}, InetOpt, {ip, Ip}, {reuseaddr, true}, {active, false}, {backlog, Backlog}]) of
- {ok, ListenSocket} ->
- % start listening
- ?LOG_DEBUG("starting listener loop", []),
- % create acceptor
- AcceptorPid = misultin_socket:start_link(ListenSocket, Port, Loop, RecvTimeout, StreamSupport, WsLoop),
- {ok, #state{listen_socket = ListenSocket, port = Port, loop = Loop, acceptor = AcceptorPid, recv_timeout = RecvTimeout, stream_support = StreamSupport, ws_loop = WsLoop}};
- {error, Reason} ->
- ?LOG_ERROR("error starting: ~p", [Reason]),
- % error
- {stop, Reason}
+ % proceed?
+ case Continue of
+ true ->
+ % set options
+ OptionsTcp = [binary, {packet, http}, {ip, Ip}, {reuseaddr, true}, {active, false}, {backlog, Backlog}|AdditionalOptions],
+ % create listening socket and acceptor
+ case create_listener_and_acceptor(Port, OptionsTcp, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode) of
+ {ok, ListenSocket, AcceptorPid} ->
+ ?LOG_DEBUG("listening socket and acceptor succesfully started",[]),
+ {ok, #state{listen_socket = ListenSocket, socket_mode = SocketMode, port = Port, options = OptionsTcp, loop = Loop, acceptor = AcceptorPid, recv_timeout = RecvTimeout, stream_support = StreamSupport, ws_loop = WsLoop}};
+ {error, Reason} ->
+ ?LOG_ERROR("error starting listener socket: ~p", [Reason]),
+ {stop, Reason}
+ end;
+ Error ->
+ {stop, Error}
end;
Reason ->
% error found in options
@@ -168,12 +196,6 @@ handle_cast(stop, State) ->
?LOG_INFO("manual shutdown..", []),
{stop, normal, State};
-% create
-handle_cast(create_acceptor, #state{listen_socket = ListenSocket, port = Port, loop = Loop, recv_timeout = RecvTimeout, stream_support = StreamSupport, ws_loop = WsLoop} = State) ->
- ?LOG_DEBUG("creating new acceptor process", []),
- AcceptorPid = misultin_socket:start_link(ListenSocket, Port, Loop, RecvTimeout, StreamSupport, WsLoop),
- {noreply, State#state{acceptor = AcceptorPid}};
-
% add websocket reference to server
handle_cast({add_ws_pid, WsPid}, #state{ws_references = WsReferences} = State) ->
{noreply, State#state{ws_references = [WsPid|WsReferences]}};
@@ -192,10 +214,13 @@ handle_cast(_Msg, State) ->
% Description: Handling all non call/cast messages.
% ----------------------------------------------------------------------------------------------------------
-% The current acceptor has died, respawn
-handle_info({'EXIT', Pid, _Reason}, #state{listen_socket = ListenSocket, port = Port, loop = Loop, acceptor = Pid, recv_timeout = RecvTimeout, stream_support = StreamSupport, ws_loop = WsLoop} = State) ->
+% Acceptor died
+% -> shutdown in progress, ignore
+handle_info({'EXIT', Pid, {error, {{accept_failed, {shutdown, _}}}}}, #state{acceptor = Pid} = State) -> {noreply, State};
+% -> respawn listening socket and acceptor
+handle_info({'EXIT', Pid, _Reason}, #state{listen_socket = ListenSocket, socket_mode = SocketMode, port = Port, loop = Loop, acceptor = Pid, recv_timeout = RecvTimeout, stream_support = StreamSupport, ws_loop = WsLoop} = State) ->
?LOG_WARNING("acceptor has died with reason: ~p, respawning", [_Reason]),
- AcceptorPid = misultin_socket:start_link(ListenSocket, Port, Loop, RecvTimeout, StreamSupport, WsLoop),
+ AcceptorPid = misultin_socket:start_link(ListenSocket, Port, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode),
{noreply, State#state{acceptor = AcceptorPid}};
% handle_info generic fallback (ignore)
@@ -208,15 +233,15 @@ handle_info(_Info, State) ->
% 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, ws_references = WsReferences}) ->
+terminate(_Reason, #state{listen_socket = ListenSocket, socket_mode = SocketMode, acceptor = AcceptorPid, ws_references = WsReferences}) ->
?LOG_INFO("shutting down server with Pid ~p", [self()]),
% kill acceptor
exit(AcceptorPid, kill),
% send a shutdown message to all websockets, if any
?LOG_DEBUG("sending shutdown message to websockets, if any", []),
lists:foreach(fun(WsPid) -> catch WsPid ! shutdown end, WsReferences),
- % stop gen_tcp
- gen_tcp:close(ListenSocket),
+ % stop tcp socket
+ misultin_socket:close(ListenSocket, SocketMode),
terminated.
% ----------------------------------------------------------------------------------------------------------
@@ -240,6 +265,19 @@ check_and_convert_string_to_ip(Ip) ->
{ok, IpTuple} ->
IpTuple
end.
+
+% Function: -> true | false
+% Description: Checks if all necessary Ssl Options have been specified
+check_ssl_options(SslOptions) ->
+ Opts = [verify, fail_if_no_peer_cert, verify_fun, depth, certfile, keyfile, password, cacertfile, ciphers, reuse_sessions, reuse_session],
+ F = fun({Name, _Value}) ->
+ ?LOG_DEBUG("testing ~p", [Name]),
+ case lists:member(Name, Opts) of
+ false -> ?LOG_DEBUG("NOT found ~p", [Name]),false;
+ _ -> ?LOG_DEBUG("found ~p", [Name]),true
+ end
+ end,
+ lists:all(F, SslOptions).
% Description: Validate and get misultin options.
get_option({OptionName, DefaultValue, CheckAndConvertFun, FailTypeError}, Options) ->
@@ -262,4 +300,29 @@ get_option({OptionName, DefaultValue, CheckAndConvertFun, FailTypeError}, Option
end
end.
+% Function: -> ok | {error, Reason}
+% Description: Start an application.
+start_application(Application) ->
+ case lists:keyfind(Application, 1, application:which_applications()) of
+ false ->
+ application:start(Application);
+ _ ->
+ ok
+ end.
+
+% Function: -> {ok, ListenSocket, AcceptorPid} | {error, Error}
+% Description: Create listening socket
+create_listener_and_acceptor(Port, Options, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode) ->
+ ?LOG_DEBUG("starting listening ~p socket with options ~p on port ~p", [SocketMode, Options, Port]),
+ case misultin_socket:listen(Port, Options, SocketMode) of
+ {ok, ListenSocket} ->
+ ?LOG_DEBUG("starting acceptor",[]),
+ % create acceptor
+ AcceptorPid = misultin_socket:start_link(ListenSocket, Port, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode),
+ {ok, ListenSocket, AcceptorPid};
+ {error, Reason} ->
+ % error
+ {error, Reason}
+ end.
+
% ============================ /\ INTERNAL FUNCTIONS =======================================================
View
6 src/misultin_req.erl
@@ -32,7 +32,7 @@
% POSSIBILITY OF SUCH DAMAGE.
% ==========================================================================================================
-module(misultin_req, [Req, SocketPid]).
--vsn("0.4.0").
+-vsn("0.5.0").
% macros
-define(PERCENT, 37). % $\%
@@ -61,10 +61,14 @@ raw() ->
% Description: Get request info.
get(socket) ->
Req#req.socket;
+get(socket_mode) ->
+ Req#req.socket_mode;
get(peer_addr) ->
Req#req.peer_addr;
get(peer_port) ->
Req#req.peer_port;
+get(peer_cert) ->
+ Req#req.peer_cert;
get(connection) ->
Req#req.connection;
get(content_length) ->
View
290 src/misultin_socket.erl
@@ -31,23 +31,25 @@
% POSSIBILITY OF SUCH DAMAGE.
% ==========================================================================================================
-module(misultin_socket).
--vsn("0.4.0").
+-vsn("0.5.0").
% API
--export([start_link/6]).
+-export([start_link/7]).
% callbacks
--export([listener/6]).
+-export([listener/7]).
% internal
--export([socket_loop/2, send/2, close/1]).
+-export([socket_loop/2]).
+-export([listen/3, setopts/3, send/3, close/2]).
% macros
-define(MAX_HEADERS_COUNT, 100).
% records
-record(c, {
sock,
+ socket_mode,
port,
loop,
recv_timeout,
@@ -63,87 +65,134 @@
% Function: {ok,Pid} | ignore | {error, Error}
% Description: Starts the socket.
-start_link(ListenSocket, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop) ->
- proc_lib:spawn_link(?MODULE, listener, [ListenSocket, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop]).
+start_link(ListenSocket, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode) ->
+ proc_lib:spawn_link(?MODULE, listener, [ListenSocket, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode]).
% Function: {ok,Pid} | ignore | {error, Error}
% Description: Starts the socket.
-listener(ListenSocket, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop) ->
- case catch gen_tcp:accept(ListenSocket) of
- {ok, Sock} ->
- ?LOG_DEBUG("accepted an incoming TCP connection, spawning controlling process", []),
- Pid = spawn(fun () ->
- receive
- set ->
- ?LOG_DEBUG("activated controlling process", []),
- ok
- after 60000 ->
- exit({error, controlling_failed})
- end,
- % build connection record
- {ok, {Addr, Port}} = inet:peername(Sock),
- C = #c{sock = Sock, port = ListenPort, loop = Loop, recv_timeout = RecvTimeout, stream_support = StreamSupport, ws_loop = WsLoop},
- % jump to state 'request'
- ?LOG_DEBUG("jump to state request", []),
- request(C, #req{socket = Sock, peer_addr = Addr, peer_port = Port})
+listener(ListenSocket, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode) ->
+ case catch accept(ListenSocket, SocketMode) of
+ {ok, {sslsocket, _, _} = Sock} ->
+ % received a SSL socket -> spawn a ssl_accept process to avoid locking the main listener
+ spawn(fun() ->
+ case ssl:ssl_accept(Sock, 60000) of
+ ok ->
+ create_socket_pid(Sock, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode);
+ {error, Reason} ->
+ % could not negotiate a SSL transaction, leave process
+ ?LOG_WARNING("could not negotiate a SSL transaction: ~p", [Reason]),
+ catch close(Sock, SocketMode)
+ end
end),
- % set controlling process
- gen_tcp:controlling_process(Sock, Pid),
- Pid ! set,
% get back to accept loop
- listener(ListenSocket, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop);
- _Else ->
- ?LOG_ERROR("accept failed error: ~p", [_Else]),
- exit({error, accept_failed})
+ listener(ListenSocket, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode);
+ {ok, Sock} ->
+ % received a HTTP socket
+ create_socket_pid(Sock, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode),
+ % get back to accept loop
+ listener(ListenSocket, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode);
+ {error, _Error} ->
+ ?LOG_WARNING("accept failed with error: ~p", [_Error]),
+ % get back to accept loop
+ listener(ListenSocket, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode);
+ {'EXIT', Error} ->
+ ?LOG_ERROR("accept exited with error: ~p, quitting process", [Error]),
+ exit({error, {accept_failed, Error}})
end.
% ============================ /\ API ======================================================================
% ============================ \/ INTERNAL FUNCTIONS =======================================================
+% start socket Pid
+create_socket_pid(Sock, ListenPort, Loop, RecvTimeout, StreamSupport, WsLoop, SocketMode) ->
+ ?LOG_DEBUG("accepted an incoming TCP connection in ~p mode on socket ~p, spawning controlling process", [SocketMode, Sock]),
+ Pid = spawn(fun() ->
+ receive
+ set ->
+ ?LOG_DEBUG("activated controlling process ~p", [self()]),
+ % get peer address and port
+ {PeerAddr, PeerPort} = peername(Sock, SocketMode),
+ % get peer certificate, if any
+ PeerCert = peercert(Sock, SocketMode),
+ % build connection record
+ C = #c{sock = Sock, socket_mode = SocketMode, port = ListenPort, loop = Loop, recv_timeout = RecvTimeout, stream_support = StreamSupport, ws_loop = WsLoop},
+ % jump to state 'request'
+ ?LOG_DEBUG("jump to state request", []),
+ request(C, #req{socket = Sock, socket_mode = SocketMode, peer_addr = PeerAddr, peer_port = PeerPort, peer_cert = PeerCert})
+ after 60000 ->
+ ?LOG_ERROR("timeout waiting for set in controlling process, closing socket", []),
+ catch close(Sock, SocketMode)
+ end
+ end),
+ % set controlling process
+ case controlling_process(Sock, Pid, SocketMode) of
+ ok ->
+ Pid ! set;
+ {error, _Reason} ->
+ ?LOG_ERROR("could not set controlling process: ~p, closing socket", [_Reason]),
+ catch close(Sock, SocketMode)
+ end.
+
% REQUEST: wait for a HTTP Request line. Transition to state headers if one is received.
-request(#c{sock = Sock, recv_timeout = RecvTimeout} = C, Req) ->
- inet:setopts(Sock, [{active, once}]),
+request(#c{sock = Sock, socket_mode = SocketMode, recv_timeout = RecvTimeout} = C, Req) ->
+ setopts(Sock, [{active, once}, {packet, http}], SocketMode),
receive
- {http, Sock, {http_request, Method, Path, Version}} ->
+ {SocketMode, Sock, {http_request, Method, Path, Version}} ->
?LOG_DEBUG("received full headers of a new HTTP packet", []),
+ % change packet type if in ssl mode
+ case SocketMode of
+ ssl -> setopts(Sock, [{packet, httph}], SocketMode);
+ _ -> ok
+ end,
+ % go to headers
headers(C, Req#req{vsn = Version, method = Method, uri = Path, connection = default_connection(Version)}, []);
- {http, Sock, {http_error, "\r\n"}} ->
+ {SocketMode, Sock, {http_error, "\r\n"}} ->
request(C, Req);
- {http, Sock, {http_error, "\n"}} ->
+ {SocketMode, Sock, {http_error, "\n"}} ->
request(C, Req);
- {http, Sock, _Other} ->
- ?LOG_DEBUG("tcp error on incoming request: ~p, send bad request error back", [_Other]),
- send(Sock, misultin_utility:get_http_status_code(400));
+ {http, Sock, {http_error, _Other}} ->
+ ?LOG_WARNING("received a http error, might be a ssl request while socket in http mode: ~p, sending forbidden response and closing socket", [_Other]),
+ send(Sock, misultin_utility:get_http_status_code(403), SocketMode),
+ close(Sock, SocketMode),
+ exit(normal);
_Other ->
- request(C, Req)
+ ?LOG_WARNING("tcp error on incoming request: ~p, send bad request error back, closing socket", [_Other]),
+ close(Sock, SocketMode),
+ exit(normal)
after RecvTimeout ->
?LOG_DEBUG("normal receive timeout, exit", []),
- close(Sock),
+ close(Sock, SocketMode),
exit(normal)
end.
% HEADERS: collect HTTP headers. After the end of header marker transition to body state.
headers(C, Req, H) ->
headers(C, Req, H, 0).
-headers(#c{sock = Sock}, _Req, _H, ?MAX_HEADERS_COUNT) ->
+headers(#c{sock = Sock, socket_mode = SocketMode}, _Req, _H, ?MAX_HEADERS_COUNT) ->
?LOG_DEBUG("too many headers sent, bad request",[]),
- send(Sock, misultin_utility:get_http_status_code(400));
-headers(#c{sock = Sock, recv_timeout = RecvTimeout, ws_loop = WsLoop} = C, Req, H, HeaderCount) ->
- inet:setopts(Sock, [{active, once}]),
+ send(Sock, misultin_utility:get_http_status_code(400), SocketMode);
+headers(#c{sock = Sock, socket_mode = SocketMode, recv_timeout = RecvTimeout, ws_loop = WsLoop} = C, Req, H, HeaderCount) ->
+ setopts(Sock, [{active, once}], SocketMode),
receive
- {http, Sock, {http_header, _, 'Content-Length', _, Val}} ->
+ {SocketMode, Sock, {http_header, _, 'Content-Length', _, Val} = Head} ->
+ ?LOG_DEBUG("received header: ~p", [Head]),
headers(C, Req#req{content_length = Val}, [{'Content-Length', Val}|H], HeaderCount + 1);
- {http, Sock, {http_header, _, 'Connection', _, Val}} ->
+ {SocketMode, Sock, {http_header, _, 'Connection', _, Val} = Head} ->
+ ?LOG_DEBUG("received header: ~p", [Head]),
headers(C, Req#req{connection = keep_alive(Req#req.vsn, Val)}, [{'Connection', Val}|H], HeaderCount + 1);
- {http, Sock, {http_header, _, Header, _, Val}} ->
+ {SocketMode, Sock, {http_header, _, Header, _, Val} = Head} ->
+ ?LOG_DEBUG("received header: ~p", [Head]),
headers(C, Req, [{Header, Val}|H], HeaderCount + 1);
- {http, Sock, {http_error, "\r\n"}} ->
+ {SocketMode, Sock, {http_error, "\r\n"} = Head} ->
+ ?LOG_DEBUG("received header: ~p", [Head]),
headers(C, Req, H, HeaderCount);
- {http, Sock, {http_error, "\n"}} ->
+ {SocketMode, Sock, {http_error, "\n"} = Head} ->
+ ?LOG_DEBUG("received header: ~p", [Head]),
headers(C, Req, H, HeaderCount);
- {http, Sock, http_eoh} ->
+ {SocketMode, Sock, http_eoh} ->
+ ?LOG_DEBUG("received EOH header", []),
Headers = lists:reverse(H),
{_PathType, Path} = Req#req.uri,
% check if it's a websocket request
@@ -157,16 +206,17 @@ headers(#c{sock = Sock, recv_timeout = RecvTimeout, ws_loop = WsLoop} = C, Req,
body(C, Req#req{headers = Headers});
{true, Origin, Host, Path} ->
?LOG_DEBUG("websocket request received", []),
- misultin_websocket:connect(#ws{socket = Sock, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port, origin = Origin, host = Host, path = Path}, WsLoop)
+ misultin_websocket:connect(#ws{socket = Sock, socket_mode = SocketMode, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port, origin = Origin, host = Host, path = Path}, WsLoop)
end;
- {http, Sock, _Other} ->
+ {SocketMode, Sock, _Other} ->
?LOG_DEBUG("tcp error treating headers: ~p, send bad request error back", [_Other]),
- send(Sock, misultin_utility:get_http_status_code(400));
+ send(Sock, misultin_utility:get_http_status_code(400), SocketMode);
_Other ->
- headers(C, Req, H, HeaderCount)
+ ?LOG_DEBUG("received unknown message: ~p, ignoring", [_Other]),
+ ignored
after RecvTimeout ->
?LOG_DEBUG("headers timeout, sending request timeout error", []),
- send(Sock, misultin_utility:get_http_status_code(408))
+ send(Sock, misultin_utility:get_http_status_code(408), SocketMode)
end.
% default connection
@@ -193,67 +243,67 @@ 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, recv_timeout = RecvTimeout} = C, Req) ->
+body(#c{sock = Sock, socket_mode = SocketMode, recv_timeout = RecvTimeout} = C, Req) ->
case Req#req.method of
'GET' ->
?LOG_DEBUG("GET request received",[]),
Close = handle_get(C, Req),
case Close of
close ->
% close socket
- close(Sock);
+ close(Sock, SocketMode);
keep_alive ->
- request(C, #req{socket = Sock, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port})
+ request(C, #req{socket = Sock, socket_mode = SocketMode, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port, peer_cert = Req#req.peer_cert})
end;
'POST' ->
?LOG_DEBUG("POST request received", []),
case catch list_to_integer(Req#req.content_length) of
{'EXIT', _} ->
% TODO: provide a fallback when content length is not or wrongly specified
?LOG_DEBUG("specified content length is not a valid integer number: ~p", [Req#req.content_length]),
- send(Sock, misultin_utility:get_http_status_code(411)),
+ send(Sock, misultin_utility:get_http_status_code(411), SocketMode),
exit(normal);
0 ->
?LOG_DEBUG("zero content-lenght specified, skipping parsing body of request", []),
Close = handle_post(C, Req),
case Close of
close ->
% close socket
- close(Sock);
+ close(Sock, SocketMode);
keep_alive ->
- inet:setopts(Sock, [{packet, http}]),
- request(C, #req{socket = Sock, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port})
+ setopts(Sock, [{packet, http}], SocketMode),
+ request(C, #req{socket = Sock, socket_mode = SocketMode, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port, peer_cert = Req#req.peer_cert})
end;
Len ->
?LOG_DEBUG("parsing POST content in body of request", []),
- inet:setopts(Sock, [{packet, raw}, {active, false}]),
- case gen_tcp:recv(Sock, Len, RecvTimeout) of
+ setopts(Sock, [{packet, raw}, {active, false}], SocketMode),
+ case recv(Sock, Len, RecvTimeout, SocketMode) of
{ok, Bin} ->
Close = handle_post(C, Req#req{body = Bin}),
case Close of
close ->
% close socket
- close(Sock);
+ close(Sock, SocketMode);
keep_alive ->
- inet:setopts(Sock, [{packet, http}]),
- request(C, #req{socket = Sock, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port})
+ setopts(Sock, [{packet, http}], SocketMode),
+ request(C, #req{socket = Sock, socket_mode = SocketMode, peer_addr = Req#req.peer_addr, peer_port = Req#req.peer_port, peer_cert = Req#req.peer_cert})
end;
{error, timeout} ->
?LOG_DEBUG("request timeout, sending error", []),
- send(Sock, misultin_utility:get_http_status_code(408));
+ send(Sock, misultin_utility:get_http_status_code(408), SocketMode);
_Other ->
?LOG_DEBUG("tcp error treating post data: ~p, send bad request error back", [_Other]),
- send(Sock, misultin_utility:get_http_status_code(400))
+ send(Sock, misultin_utility:get_http_status_code(400), SocketMode)
end
end;
_Other ->
?LOG_DEBUG("method not implemented: ~p", [_Other]),
- send(Sock, misultin_utility:get_http_status_code(501)),
+ send(Sock, misultin_utility:get_http_status_code(501), SocketMode),
exit(normal)
end.
% handle a get request
-handle_get(C, #req{connection = Conn} = Req) ->
+handle_get(C, #req{socket_mode = SocketMode, connection = Conn} = Req) ->
case Req#req.uri of
{abs_path, Path} ->
{F, Args} = split_at_q_mark(Path, []),
@@ -264,18 +314,18 @@ handle_get(C, #req{connection = Conn} = Req) ->
call_mfa(C, Req#req{args = Args, uri = {absoluteURI, F}}),
Conn;
{absoluteURI, _Other_method, _Host, _, _Path} ->
- send(C#c.sock, misultin_utility:get_http_status_code(501)),
+ send(C#c.sock, misultin_utility:get_http_status_code(501), SocketMode),
close;
{scheme, _Scheme, _RequestString} ->
- send(C#c.sock, misultin_utility:get_http_status_code(510)),
+ send(C#c.sock, misultin_utility:get_http_status_code(510), SocketMode),
close;
_ ->
- send(C#c.sock, misultin_utility:get_http_status_code(403)),
+ send(C#c.sock, misultin_utility:get_http_status_code(403), SocketMode),
close
end.
% handle a post request
-handle_post(C, #req{connection = Conn} = Req) ->
+handle_post(C, #req{socket_mode = SocketMode, connection = Conn} = Req) ->
case Req#req.uri of
{abs_path, _Path} ->
call_mfa(C, Req),
@@ -284,18 +334,18 @@ handle_post(C, #req{connection = Conn} = Req) ->
call_mfa(C, Req),
Conn;
{absoluteURI, _Other_method, _Host, _, _Path} ->
- send(C#c.sock, misultin_utility:get_http_status_code(501)),
+ send(C#c.sock, misultin_utility:get_http_status_code(501), SocketMode),
close;
{scheme, _Scheme, _RequestString} ->
- send(C#c.sock, misultin_utility:get_http_status_code(501)),
+ send(C#c.sock, misultin_utility:get_http_status_code(501), SocketMode),
close;
_ ->
- send(C#c.sock, misultin_utility:get_http_status_code(403)),
+ send(C#c.sock, misultin_utility:get_http_status_code(403), SocketMode),
close
end.
% Description: Main dispatcher
-call_mfa(#c{sock = Sock, loop = Loop, stream_support = StreamSupport} = C, Request) ->
+call_mfa(#c{sock = Sock, socket_mode = SocketMode, loop = Loop, stream_support = StreamSupport} = C, Request) ->
% spawn listening process for Request messages [only used to support stream requests]
case StreamSupport of
true ->
@@ -312,7 +362,7 @@ call_mfa(#c{sock = Sock, loop = Loop, stream_support = StreamSupport} = C, Reque
% kill listening socket
catch SocketPid ! shutdown,
% send response
- send(Sock, misultin_utility:get_http_status_code(500)),
+ send(Sock, misultin_utility:get_http_status_code(500), SocketMode),
% force exit
exit(normal);
{HttpCode, Headers0, Body} ->
@@ -327,9 +377,9 @@ call_mfa(#c{sock = Sock, loop = Loop, stream_support = StreamSupport} = C, Reque
Headers = add_output_header('Connection', {Headers1, Request}),
Enc_headers = enc_headers(Headers),
Resp = [misultin_utility:get_http_status_code(HttpCode), Enc_headers, <<"\r\n">>, BodyBinary],
- send(Sock, Resp);
+ send(Sock, Resp, SocketMode);
{raw, Body} ->
- send(Sock, Body);
+ send(Sock, Body, SocketMode);
_ ->
% loop exited normally, kill listening socket
catch SocketPid ! shutdown
@@ -344,22 +394,22 @@ convert_to_binary(Body) when is_atom(Body) ->
list_to_binary(atom_to_list(Body)).
% Description: Socket loop for stream responses
-socket_loop(#c{sock = Sock} = C, Request) ->
+socket_loop(#c{sock = Sock, socket_mode = SocketMode} = C, Request) ->
receive
{stream_head, HttpCode, Headers0} ->
?LOG_DEBUG("sending stream head", []),
Headers = add_output_header('Connection', {Headers0, Request}),
Enc_headers = enc_headers(Headers),
Resp = [misultin_utility:get_http_status_code(HttpCode), Enc_headers, <<"\r\n">>],
- send(Sock, Resp),
+ send(Sock, Resp, SocketMode),
socket_loop(C, Request);
{stream_data, Body} ->
?LOG_DEBUG("sending stream data", []),
- send(Sock, Body),
+ send(Sock, Body, SocketMode),
socket_loop(C, Request);
stream_close ->
?LOG_DEBUG("closing stream", []),
- close(Sock);
+ close(Sock, SocketMode);
shutdown ->
?LOG_DEBUG("shutting down socket loop", []),
shutdown
@@ -416,10 +466,58 @@ split_at_q_mark([H|T], Acc) ->
split_at_q_mark([], Acc) ->
{lists:reverse(Acc), []}.
-% TCP send
-send(Sock, Data) ->
+% socket listen
+listen(Port, Options, http) -> gen_tcp:listen(Port, Options);
+listen(Port, Options, ssl) -> ssl:listen(Port, Options).
+
+% socket accept
+accept(ListenSocket, http) -> gen_tcp:accept(ListenSocket);
+accept(ListenSocket, ssl) ->
+ try ssl:transport_accept(ListenSocket)
+ catch
+ error:{badmatch, {error, Reason}} ->
+ {error, Reason}
+ end.
+
+% socket controlling process
+controlling_process(Sock, Pid, http) -> gen_tcp:controlling_process(Sock, Pid);
+controlling_process(Sock, Pid, ssl) -> ssl:controlling_process(Sock, Pid).
+
+% Function: -> {PeerAddr, PeerPort} | PeerAddr = list() | undefined | PeerPort = integer() | undefined
+% Description: Get socket peername
+peername(Sock, http) -> peername(Sock, fun inet:peername/1);
+peername(Sock, ssl) -> peername(Sock, fun ssl:peername/1);
+peername(Sock, F) ->
+ case F(Sock) of
+ {ok, {Addr, Port}} ->
+ {Addr, Port};
+ {error, _Reason} ->
+ {undefined, undefined}
+ end.
+
+% Function: -> Certificate | undefined
+% Description: Get socket certificate
+peercert(_Sock, http) -> undefined;
+peercert(Sock, ssl) ->
+ case ssl:peercert(Sock) of
+ {ok, Cert} -> Cert;
+ {error, _Reason} -> undefined
+ end.
+
+% socket set options
+setopts(Sock, Options, http) -> inet:setopts(Sock, Options);
+setopts(Sock, Options, ssl) -> ssl:setopts(Sock, Options).
+
+% socket receive
+recv(Sock, Len, RecvTimeout, http) -> gen_tcp:recv(Sock, Len, RecvTimeout);
+recv(Sock, Len, RecvTimeout, ssl) -> ssl:recv(Sock, Len, RecvTimeout).
+
+% socket send
+send(Sock, Data, http) -> send(Sock, Data, fun gen_tcp:send/2);
+send(Sock, Data, ssl) -> send(Sock, Data, fun ssl:send/2);
+send(Sock, Data, F) ->
?LOG_DEBUG("sending data: ~p", [Data]),
- case gen_tcp:send(Sock, Data) of
+ case F(Sock, Data) of
ok ->
ok;
{error, _Reason} ->
@@ -428,14 +526,16 @@ send(Sock, Data) ->
end.
% TCP close
-close(Sock) ->
+close(Sock, http) -> close(Sock, fun gen_tcp:close/1);
+close(Sock, ssl) -> close(Sock, fun ssl:close/1);
+close(Sock, F) ->
?LOG_DEBUG("closing socket", []),
- case gen_tcp:close(Sock) of
+ case catch F(Sock) of
ok ->
ok;
- {error, _Reason} ->
- ?LOG_WARNING("could not close socket: ~p", [_Reason]),
+ _Else ->
+ ?LOG_WARNING("could not close socket: ~p", [_Else]),
exit(normal)
end.
-
+
% ============================ /\ INTERNAL FUNCTIONS =======================================================
View
2 src/misultin_utility.erl
@@ -28,7 +28,7 @@
% POSSIBILITY OF SUCH DAMAGE.
% ==========================================================================================================
-module(misultin_utility).
--vsn("0.4.0").
+-vsn("0.5.0").
% API
-export([get_http_status_code/1, get_content_type/1]).
View
50 src/misultin_websocket.erl
@@ -31,7 +31,7 @@
% POSSIBILITY OF SUCH DAMAGE.
% ==========================================================================================================
-module(misultin_websocket).
--vsn("0.4.0").
+-vsn("0.5.0").
% API
-export([check/2, connect/2]).
@@ -57,7 +57,7 @@ check(Path, Headers) ->
end.
% Connect and handshake with Websocket.
-connect(#ws{socket = Socket, origin = Origin, host = Host, path = Path} = Ws, WsLoop) ->
+connect(#ws{socket = Socket, socket_mode = SocketMode, origin = Origin, host = Host, path = Path} = Ws, WsLoop) ->
?LOG_DEBUG("received websocket handshake request", []),
HandshakeServer = ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
"Upgrade: WebSocket\r\n",
@@ -66,32 +66,32 @@ connect(#ws{socket = Socket, origin = Origin, host = Host, path = Path} = Ws, Ws
"WebSocket-Location: ws://", lists:concat([Host, Path]), "\r\n\r\n"
],
% send handshake back
- misultin_socket:send(Socket, HandshakeServer),
+ misultin_socket:send(Socket, HandshakeServer, SocketMode),
% spawn controlling process
Ws0 = misultin_ws:new(Ws, self()),
WsHandleLoopPid = spawn(fun() -> WsLoop(Ws0) end),
erlang:monitor(process, WsHandleLoopPid),
% set opts
- inet:setopts(Socket, [{packet, 0}, {active, true}]),
+ misultin_socket:setopts(Socket, [{packet, 0}, {active, true}], SocketMode),
% add main websocket pid to misultin server reference
misultin:websocket_pid_add(self()),
% start listening for incoming data
- ws_loop(Socket, none, WsHandleLoopPid).
+ ws_loop(Socket, none, WsHandleLoopPid, SocketMode).
% ============================ /\ API ======================================================================
% ============================ \/ INTERNAL FUNCTIONS =======================================================
% Main Websocket loop
-ws_loop(Socket, Buffer, WsHandleLoopPid) ->
+ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode) ->
receive
{tcp, Socket, Data} ->
- handle_data(Buffer, binary_to_list(Data), Socket, WsHandleLoopPid);
+ handle_data(Buffer, binary_to_list(Data), Socket, WsHandleLoopPid, SocketMode);
{tcp_closed, Socket} ->
?LOG_DEBUG("tcp connection was closed, exit", []),
% close websocket and custom controlling loop
- websocket_close(Socket, WsHandleLoopPid);
+ websocket_close(Socket, WsHandleLoopPid, SocketMode);
{'DOWN', Ref, process, WsHandleLoopPid, Reason} ->
case Reason of
normal ->
@@ -102,40 +102,40 @@ ws_loop(Socket, Buffer, WsHandleLoopPid) ->
% demonitor
erlang:demonitor(Ref),
% close websocket and custom controlling loop
- websocket_close(Socket, WsHandleLoopPid);
+ websocket_close(Socket, WsHandleLoopPid, SocketMode);
{send, Data} ->
?LOG_DEBUG("sending data to websocket: ~p", [Data]),
- misultin_socket:send(Socket, [0, Data, 255]),
- ws_loop(Socket, Buffer, WsHandleLoopPid);
+ misultin_socket:send(Socket, [0, Data, 255], SocketMode),
+ ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode);
shutdown ->
?LOG_DEBUG("shutdown request received, closing websocket with pid ~p", [self()]),
% close websocket and custom controlling loop
- websocket_close(Socket, WsHandleLoopPid);
+ websocket_close(Socket, WsHandleLoopPid, SocketMode);
_Ignored ->
?LOG_WARNING("received unexpected message, ignoring: ~p", [_Ignored]),
- ws_loop(Socket, Buffer, WsHandleLoopPid)
+ ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode)
end.
% Buffering and data handling
-handle_data(none, [0|T], Socket, WsHandleLoopPid) ->
- handle_data([], T, Socket, WsHandleLoopPid);
-handle_data(none, [], Socket, WsHandleLoopPid) ->
- ws_loop(Socket, none, WsHandleLoopPid);
-handle_data(L, [255|T], Socket, WsHandleLoopPid) ->
+handle_data(none, [0|T], Socket, WsHandleLoopPid, SocketMode) ->
+ handle_data([], T, Socket, WsHandleLoopPid, SocketMode);
+handle_data(none, [], Socket, WsHandleLoopPid, SocketMode) ->
+ ws_loop(Socket, none, WsHandleLoopPid, SocketMode);
+handle_data(L, [255|T], Socket, WsHandleLoopPid, SocketMode) ->
WsHandleLoopPid ! {browser, lists:reverse(L)},
- handle_data(none, T, Socket, WsHandleLoopPid);
-handle_data(L, [H|T], Socket, WsHandleLoopPid) ->
- handle_data([H|L], T, Socket, WsHandleLoopPid);
-handle_data([], L, Socket, WsHandleLoopPid) ->
- ws_loop(Socket, L, WsHandleLoopPid).
+ handle_data(none, T, Socket, WsHandleLoopPid, SocketMode);
+handle_data(L, [H|T], Socket, WsHandleLoopPid, SocketMode) ->
+ handle_data([H|L], T, Socket, WsHandleLoopPid, SocketMode);
+handle_data([], L, Socket, WsHandleLoopPid, SocketMode) ->
+ ws_loop(Socket, L, WsHandleLoopPid, SocketMode).
% Close socket and custom handling loop dependency
-websocket_close(Socket, WsHandleLoopPid) ->
+websocket_close(Socket, WsHandleLoopPid, SocketMode) ->
% remove main websocket pid from misultin server reference
misultin:websocket_pid_remove(self()),
% kill custom handling loop process
exit(WsHandleLoopPid, kill),
% close main socket
- misultin_socket:close(Socket).
+ misultin_socket:close(Socket, SocketMode).
% ============================ /\ INTERNAL FUNCTIONS =======================================================
View
6 src/misultin_ws.erl
@@ -28,7 +28,7 @@
% POSSIBILITY OF SUCH DAMAGE.
% ==========================================================================================================
-module(misultin_ws, [Ws, SocketPid]).
--vsn("0.4.0").
+-vsn("0.5.0").
% API
-export([raw/0, get/1, send/1]).
@@ -46,10 +46,14 @@ raw() ->
% Description: Get websocket info.
get(socket) ->
Ws#ws.socket;
+get(socket_mode) ->
+ Ws#req.socket_mode;
get(peer_addr) ->
Ws#ws.peer_addr;
get(peer_port) ->
Ws#ws.peer_port;
+get(peer_cert) ->
+ Ws#ws.peer_cert;
get(origin) ->
Ws#ws.origin;
get(host) ->

0 comments on commit 38d38fa

Please sign in to comment.