Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Multiple listeners #44

Closed
wants to merge 12 commits into from

4 participants

@stangrze

Hi.
There is a complete (IMO) multiple_listeners patch.
It introduces a bit of incompatibility as it introduces new module parameter for controllers, but IMO this approach is easier to understand for users, easier to use and much cleaner in implementation.
'Instance' variable holds atom 'default' when multiple listeners configuration is not used.

@evanmiller
Owner

You still have not answered my question. Why can't we just use the Req object to distinguish SSL and non-SSL traffic? Using the server name defined in the configuration seems like bad design because it reduces application re-usability.

As for the mochiweb change: Did you send this patch upstream? I am generally hesitant to change non-Boss code unless I know exactly what is going on. I would want feedback from the mochiweb team because there might be a good reason for using a timeout in the accept loop.

Finally if we go with the "servers" block we may want to eliminate the port/host/ssl options from the root config, but this can wait. PS- I like the "engine" name!

@kotedo

Wouldn't these "port" and "server" settings be redundant? They would be specified in the server block; why are they here?

src/boss/boss_web_controller.erl
@@ -393,13 +407,17 @@ process_result(_, {ok, Payload, Headers}) ->
{200, [{"Content-Type", proplists:get_value("Content-Type", Headers, "text/html")}
|proplists:delete("Content-Type", Headers)], Payload}.
+<<<<<<< HEAD
@narma
narma added a note

trees not merged?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stangrze

@kotedo Regular port and server options are for 'backward compatibility', They are ignored when 'servers' list is defined.
I think it has been described in doc, if not clear enough i'll try to figure out something better.

@evanmiller:
I dont think that identifier of listener entity is really part of request, and the role of that identifier can be more than just distinguishing between ssl and not-ssl. This can be forntend, backend or customer1,customer2 etc.
This way of passing instance name allows adding in future listener specific routes.
About application reusability: You may look at it from other direction, listener have to be named a specific way to acheive some funcionality.
I can imagine that apps like cb_admin can have parameter like 'allowed_listeners' which will be checked in _before if is executed on right listener (admin or backend interface) and return some redirect or notfound if not.
Same thing for some shopping cart or payment app and requirement for ssl.
about mochiweb: i just openned issue on mochi's github.

Sorry for delay i had to focus on something else for a while.

@evanmiller
Owner

Hmm. Perhaps ALL configuration should be server-specific? So automatically we get "allowed listeners" because applications are listener-specific. Then we could provide an API like boss_env:get_server_env('server_name') if you really want to get the server name (or other user-defined config variables). Then you could also have two instances of the same application running with independent data stores as long as they were hosted on different listeners. But then perhaps servers could be associated with domain names and not IP addresses; the Nginx configuration model could be a good example to follow. I need to think about this some more.

@stangrze

i dont like idea of CB becoming platform for virtual servers hosting, it is a bit too far IMO.
In my usecase i need access to same datastore in all listeners, especially session data, and i need that some urls are reachable on specific listener only and not reachable on others.

@stangrze stangrze closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 24, 2011
  1. @stangrze
  2. @stangrze
Commits on Nov 29, 2011
  1. @stangrze
  2. @stangrze
  3. @stangrze
Commits on Dec 2, 2011
  1. @stangrze

    Pass listener (server) name as module parameter to controller (Instance)

    stangrze authored
    controller header should be:
    -module(myapp_mycontroller_controller, [Instance, Req]).
    -module(myapp_mycontroller_controller, [Instance, Req, SessionID]).
Commits on Dec 5, 2011
  1. @stangrze
  2. @stangrze
  3. @stangrze

    Merge branch 'master' of git://github.com/evanmiller/ChicagoBoss into…

    stangrze authored
    … multiple_listeners
    
    Conflicts:
    	src/boss/boss_web_controller.erl
  4. @stangrze

    update to related docs

    stangrze authored
Commits on Dec 15, 2011
  1. @stangrze
  2. @stangrze

    fixed bad merge

    stangrze authored
This page is out of date. Refresh to see the latest.
View
10 doc-src/api-config.html
@@ -39,6 +39,16 @@
<li><code>mochiweb</code> - The <a href="http://code.google.com/p/mochiweb/">Mochiweb</a> Web Server</li>
<li><code>misultin</code> - The <a href="http://code.google.com/p/misultin/">Misultin</a> Web Server</li>
</ul>
+ <li><code>servers</code> - A list of proplists with per-listener configuration. This override the defaults and above options, and have to contain folowing parameters:
+ <ul>
+ <li><code>engine</code>The HTTP server to use. Valid values same as for parameter 'server' above. You can have many mochiwebs but only one misultin instance. </li>
+ <li><code>name</code>Name of server process, arbitrary atom can be set.</li>
+ <li><code>ip</code>IP address for HTTP server to bind to, use 0.0.0.0 to bind to all</li>
+ <li><code>port</code>The port to run the HTTP server on.</li>
+ <li><code>ssl_enable</code> - Enable HTTP over SSL for given server</li>
+ <li><code>ssl_options</code> - SSL options for this server; see <a href="http://www.erlang.org/doc/man/ssl.html">ssl(3erl)</a></li>
+ </ul>There are no defaults for those parameters.</li>
+
<li><code>base_url</code>Sets the base_url passed to the views (for deployments on suburl's)</li>
<li><code>session_adapter</code> Selects the session driver to use. Valid values:</li>
<ul>
View
6 doc-src/api-controller.html
@@ -10,13 +10,13 @@
The URL <nobr>/foo/bar</nobr> will call the function <code>foo_controller:bar</code>.
Each controller module should go into your project's src/controller/ directory and the file name should start with the application name and end with "_controller.erl", e.g. "appname_my_controller.erl".
Helper functions should go into your project's src/lib/ directory.
-Controllers can take one parameter or two parameters: the <a href="#simplebridge">SimpleBridge request object</a>, and an optional session ID (if <a href="api-session.html">sessions</a> are enabled). Declare it like:</p>
+Controllers can take two or three parameters: listener instance name, the <a href="#simplebridge">SimpleBridge request object</a>, and an optional session ID (if <a href="api-session.html">sessions</a> are enabled). Declare it like:</p>
<div class="code">
- <span class="attr">-module</span>(appname_my_controller, [Req]).
+ <span class="attr">-module</span>(appname_my_controller, [Instance, Req]).
</div>
<p>Or:</p>
<div class="code">
- <span class="attr">-module</span>(appname_my_controller, [Req, SessionID]).
+ <span class="attr">-module</span>(appname_my_controller, [Instance, Req, SessionID]).
</div>
<p>Each exported controller function takes two or three arguments:</p>
<ul>
View
2  doc-src/guide-example.html
@@ -17,7 +17,7 @@
<p>controller/blog_post_controller.erl:</p>
<div class="code">
- <span class="attr">-module</span>(blog_post_controller, [Req]).<br />
+ <span class="attr">-module</span>(blog_post_controller, [Instance, Req]).<br />
<span class="attr">-compile</span>(export_all).<br />
<br />
create(<span class="atom">'GET'</span>, []) -&gt;<br />
View
4 skel/boss.config
@@ -6,6 +6,10 @@
{log_dir, "log"},
{server, mochiweb},
{port, 8001},
+% {servers,[
+% [{engine, mochiweb}, {name,base_server}, {ip,"0.0.0.0"}, {port, 8001}, {ssl_enable, false}, {ssl_options, []}],
+% [{engine, mochiweb}, {name,ssl_server}, {ip,"0.0.0.0"}, {port, 8002}, {ssl_enable, true}, {ssl_options, [{certfile, "server.pem"}, {keyfile, "privkey.pem"}]}]
+% ]},
{session_adapter, mock},
{session_key, "_boss_session"},
{session_exp_time, 525600}
View
100 src/boss/boss_web_controller.erl
@@ -1,6 +1,6 @@
-module(boss_web_controller).
-behaviour(gen_server).
--export([start_link/0, start_link/1, handle_request/3, process_request/5]).
+-export([start_link/0, start_link/1, handle_request/4, process_request/6]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(DEBUGPRINT(A), error_logger:info_report("~~o)> " ++ A)).
@@ -86,28 +86,42 @@ init(Config) ->
error_logger:info_msg("Pinging master node ~p from ~p~n", [MasterNode, ThisNode]),
pong = net_adm:ping(MasterNode)
end,
-
+
MailDriver = boss_env:get_env(mail_driver, boss_mail_driver_smtp),
boss_mail:start([{driver, MailDriver}]),
- {ServerMod, RequestMod, ResponseMod} = case boss_env:get_env(server, misultin) of
- mochiweb -> {mochiweb_http, mochiweb_request_bridge, mochiweb_response_bridge};
- misultin -> {misultin, misultin_request_bridge, misultin_response_bridge}
- end,
- SSLEnable = boss_env:get_env(ssl_enable, false),
- SSLOptions = boss_env:get_env(ssl_options, []),
- ServerConfig = [{loop, fun(Req) ->
- ?MODULE:handle_request(Req, RequestMod, ResponseMod)
- end} | Config],
- Pid = case ServerMod of
- mochiweb_http -> mochiweb_http:start([{ssl, SSLEnable}, {ssl_opts, SSLOptions} | ServerConfig]);
- misultin ->
- case SSLEnable of
- true -> misultin:start_link([{ssl, SSLOptions} | ServerConfig]);
- false -> misultin:start_link(ServerConfig)
- end
+ ServList=boss_env:get_env(servers, [
+ [{engine, boss_env:get_env(server, misultin)}, {ip, proplists:get_value(ip, Config)}, {port, proplists:get_value(port, Config)},
+ {name, default},
+ {ssl_enable,boss_env:get_env(ssl_enable, false)}, {ssl_options,boss_env:get_env(ssl_options, [])}]]),
+ error_logger:info_msg("Configured Listener Instances: ~p~n", [ServList]),
+ StartServFun =fun (ServerParams) ->
+ {ServerMod, RequestMod, ResponseMod} = case proplists:get_value(engine, ServerParams) of
+ mochiweb -> {mochiweb_http, mochiweb_request_bridge, mochiweb_response_bridge};
+ misultin -> {misultin, misultin_request_bridge, misultin_response_bridge}
+ end,
+ Ip=proplists:get_value(ip, ServerParams),
+ Port=proplists:get_value(port, ServerParams),
+ Name=proplists:get_value(name, ServerParams),
+ SSLEnable = proplists:get_value(ssl_enable, ServerParams),
+ SSLOptions = proplists:get_value(ssl_options, ServerParams),
+ ServerConfig = [{loop, fun(Req) ->
+ ?MODULE:handle_request(Name, Req, RequestMod, ResponseMod)
+ end} | [{ip,Ip},{port,Port},{name,Name}]],
+ error_logger:info_msg("Starting Instance: ~p ~p on ~p:~p ssl:~p ssl_opts:~p~n", [Name,ServerMod,Ip,Port,SSLEnable,SSLOptions]),
+ Pid = case ServerMod of
+ mochiweb_http -> mochiweb_http:start([{ssl, SSLEnable}, {ssl_opts, SSLOptions} | ServerConfig]);
+ misultin ->
+ case SSLEnable of
+ true -> misultin:start_link([{ssl, SSLOptions} | ServerConfig]);
+ false -> misultin:start_link(ServerConfig)
+ end
+ end,
+ error_logger:info_msg("Started Instance: ~p on ~p:~p ssl:~p ssl_opts:~p pid:~p~n", [ServerMod,Ip,Port,SSLEnable,SSLOptions,Pid]),
+ Pid
end,
- {ok, #state{ http_pid = Pid, is_master_node = (ThisNode =:= MasterNode) }, 0}.
+ Pids=lists:map(StartServFun,ServList),
+ {ok, #state{ http_pid = Pids, is_master_node = (ThisNode =:= MasterNode) }, 0}.
handle_info(timeout, State) ->
Applications = boss_env:get_env(applications, []),
@@ -262,7 +276,7 @@ run_init_scripts(AppName) ->
end
end, [], boss_files:init_file_list(AppName)).
-handle_request(Req, RequestMod, ResponseMod) ->
+handle_request(Instance, Req, RequestMod, ResponseMod) ->
LoadedApplications = boss_web:get_all_applications(),
Request = simple_bridge:make_request(RequestMod, Req),
case Request:path() of
@@ -295,7 +309,7 @@ handle_request(Req, RequestMod, ResponseMod) ->
RouterPid = boss_web:router_pid(App),
{Time, {StatusCode, Headers, Payload}} = timer:tc(?MODULE, process_request, [
AppInfo#boss_app_info{ translator_pid = TranslatorPid, router_pid = RouterPid },
- Request, Mode, Url, SessionID]),
+ Request, Mode, Instance, Url, SessionID]),
ErrorFormat = "~s ~s [~p] ~p ~pms~n",
ErrorArgs = [Request:request_method(), Request:path(), App, StatusCode, Time div 1000],
case StatusCode of
@@ -317,18 +331,18 @@ handle_request(Req, RequestMod, ResponseMod) ->
end
end.
-process_request(AppInfo, Req, development, "/doc", SessionID) ->
- Result = case catch load_and_execute(development, {"doc", [], []}, AppInfo, Req, SessionID) of
+process_request(AppInfo, Req, development, Instance, "/doc", SessionID) ->
+ Result = case catch load_and_execute(development, {"doc", [], []}, AppInfo, Instance, Req, SessionID) of
{'EXIT', Reason} ->
{error, Reason};
Ok ->
Ok
end,
process_result(AppInfo, Result);
-process_request(AppInfo, Req, development, "/doc/"++ModelName, SessionID) ->
+process_request(AppInfo, Req, development, Instance, "/doc/"++ModelName, SessionID) ->
Result = case string:chr(ModelName, $.) of
0 ->
- case catch load_and_execute(development, {"doc", ModelName, []}, AppInfo, Req, SessionID) of
+ case catch load_and_execute(development, {"doc", ModelName, []}, AppInfo, Instance, Req, SessionID) of
{'EXIT', Reason} ->
{error, Reason};
Ok ->
@@ -338,7 +352,7 @@ process_request(AppInfo, Req, development, "/doc/"++ModelName, SessionID) ->
{not_found, "File not found"}
end,
process_result(AppInfo, Result);
-process_request(AppInfo, Req, Mode, Url, SessionID) ->
+process_request(AppInfo, Req, Mode, Instance, Url, SessionID) ->
RouterPid = AppInfo#boss_app_info.router_pid,
if
Mode =:= development ->
@@ -362,7 +376,7 @@ process_request(AppInfo, Req, Mode, Url, SessionID) ->
undefined ->
{not_found, "The requested page was not found. Additionally, no handler was found for processing 404 errors."};
_ ->
- case catch load_and_execute(Mode, Location, AppInfo, Req, SessionID) of
+ case catch load_and_execute(Mode, Location, AppInfo, Instance, Req, SessionID) of
{'EXIT', Reason} ->
{error, Reason};
Ok ->
@@ -395,13 +409,13 @@ process_result(_, {ok, Payload, Headers}) ->
{200, [{"Content-Type", proplists:get_value("Content-Type", Headers, "text/html")}
|proplists:delete("Content-Type", Headers)], Payload}.
-load_and_execute(Mode, {Controller, _, _} = Location, AppInfo, Req, SessionID) when Mode =:= production; Mode =:= testing->
+load_and_execute(Mode, {Controller, _, _} = Location, AppInfo, Instance, Req, SessionID) when Mode =:= production; Mode =:= testing->
case lists:member(boss_files:web_controller(AppInfo#boss_app_info.application, Controller),
AppInfo#boss_app_info.controller_modules) of
- true -> execute_action(Location, AppInfo, Req, SessionID);
+ true -> execute_action(Location, AppInfo, Instance, Req, SessionID);
false -> render_view(Location, AppInfo, Req, SessionID)
end;
-load_and_execute(development, {"doc", ModelName, _}, AppInfo, Req, _SessionID) ->
+load_and_execute(development, {"doc", ModelName, _}, AppInfo, _Instance, Req, _SessionID) ->
case boss_load:load_models() of
{ok, ModelModules} ->
case lists:member(ModelName, lists:map(fun atom_to_list/1, ModelModules)) of
@@ -424,7 +438,7 @@ load_and_execute(development, {"doc", ModelName, _}, AppInfo, Req, _SessionID) -
{error, ErrorList} ->
render_errors(ErrorList, AppInfo, Req)
end;
-load_and_execute(development, {Controller, _, _} = Location, AppInfo, Req, SessionID) ->
+load_and_execute(development, {Controller, _, _} = Location, AppInfo, Instance, Req, SessionID) ->
case boss_load:load_mail_controllers() of
{ok, _} ->
case boss_load:load_libraries() of
@@ -436,7 +450,7 @@ load_and_execute(development, {Controller, _, _} = Location, AppInfo, Req, Sessi
true ->
case boss_load:load_models() of
{ok, _} ->
- execute_action(Location, AppInfo, Req, SessionID);
+ execute_action(Location, AppInfo, Instance, Req, SessionID);
{error, ErrorList} ->
render_errors(ErrorList, AppInfo, Req)
end;
@@ -462,14 +476,14 @@ render_errors(ErrorList, AppInfo, Req) ->
Err
end.
-execute_action(Location, AppInfo, Req, SessionID) ->
- execute_action(Location, AppInfo, Req, SessionID, []).
+execute_action(Location, AppInfo, Instance, Req, SessionID) ->
+ execute_action(Location, AppInfo, Instance, Req, SessionID, []).
-execute_action({Controller, Action}, AppInfo, Req, SessionID, LocationTrail) ->
- execute_action({Controller, Action, []}, AppInfo, Req, SessionID, LocationTrail);
-execute_action({Controller, Action, Tokens}, AppInfo, Req, SessionID, LocationTrail) when is_atom(Action) ->
- execute_action({Controller, atom_to_list(Action), Tokens}, AppInfo, Req, SessionID, LocationTrail);
-execute_action({Controller, Action, Tokens} = Location, AppInfo, Req, SessionID, LocationTrail) ->
+execute_action({Controller, Action}, AppInfo, Instance, Req, SessionID, LocationTrail) ->
+ execute_action({Controller, Action, []}, AppInfo, Instance, Req, SessionID, LocationTrail);
+execute_action({Controller, Action, Tokens}, AppInfo, Instance, Req, SessionID, LocationTrail) when is_atom(Action) ->
+ execute_action({Controller, atom_to_list(Action), Tokens}, AppInfo, Instance, Req, SessionID, LocationTrail);
+execute_action({Controller, Action, Tokens} = Location, AppInfo, Instance, Req, SessionID, LocationTrail) ->
case lists:member(Location, LocationTrail) of
true ->
{error, "Circular redirect!"};
@@ -481,10 +495,10 @@ execute_action({Controller, Action, Tokens} = Location, AppInfo, Req, SessionID,
fun({Function, Arity}) -> {atom_to_list(Function), Arity} end,
Module:module_info(exports)),
ControllerInstance = case proplists:get_value("new", ExportStrings) of
- 1 ->
- Module:new(Req);
- 2 ->
- Module:new(Req, SessionID)
+ 2 ->
+ Module:new(Instance, Req);
+ 3 ->
+ Module:new(Instance, Req, SessionID)
end,
AuthInfo = case lists:member({"before_", 2}, ExportStrings) of
true ->
View
2  src/mochiweb/mochiweb_socket.erl
@@ -40,7 +40,7 @@ accept({ssl, ListenSocket}) ->
{error, Reason}
end;
accept(ListenSocket) ->
- gen_tcp:accept(ListenSocket, ?ACCEPT_TIMEOUT).
+ gen_tcp:accept(ListenSocket).
recv({ssl, Socket}, Length, Timeout) ->
ssl:recv(Socket, Length, Timeout);
Something went wrong with that request. Please try again.