Browse files

don't use cowboy.

cowboy was introducing some issue and isn't enough reliable for our
purpose so for now only use ranch to improve the accepting loop and
reduce memory and fd leaks.
  • Loading branch information...
1 parent ccfd4a3 commit 91bedce9d01cf670f5f21e0ece95d6d99dddbe49 @benoitc benoitc committed Dec 5, 2012
View
5 apps/couch_httpd/include/couch_httpd.hrl
@@ -11,3 +11,8 @@
auth,
default_fun,
url_handlers}).
+
+-record(hstate, {
+ socket :: inet:socket(),
+ transport :: module(),
+ loop :: {module(), any(), any()}}).
View
4 apps/couch_httpd/rebar.config
@@ -12,8 +12,8 @@
{branch, "master"}}},
%% cowboy adapter to mochiweb
- {mochicow, ".*", {git,"git://github.com/refuge/mochicow.git",
- {branch, "refuge"}}},
+ {ranch, ".*", {git,"git://github.com/refuge/ranch.git",
+ "fb7ed3807620f7534c617789e7347192838a419a"}},
%% logging
{lager, ".*", {git, "git://github.com/basho/lager.git",
View
15 apps/couch_httpd/src/couch_httpd.erl
@@ -14,7 +14,7 @@
-include_lib("couch/include/couch_db.hrl").
-include("couch_httpd.hrl").
--export([handle_request/2, child_spec/1, init/3, get_protocol_options/0,
+-export([handle_request/2, child_spec/1, get_protocol_options/0,
set_auth_handlers/0]).
-export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,qs_json_value/3]).
@@ -152,7 +152,7 @@ child_spec(Name, Transport, Port, TransOpts0) ->
TransOpts = [{port, Port}|TransOpts0],
ranch:child_spec(Name, NbAcceptors, Transport, TransOpts,
- cowboy_protocol, ProtoOpts).
+ couch_httpd_protocol, ProtoOpts).
get_protocol_options() ->
DefaultSpec = "{couch_httpd_db, handle_request}",
@@ -185,14 +185,7 @@ get_protocol_options() ->
Args = [{DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers,
SocketOptions}],
- HttpdOptions = [{loop, {couch_httpd, handle_request, Args}}],
-
- Dispatch = [
- %% {Host, list({Path, Handler, Opts})}
- {'_', [{'_', couch_httpd, HttpdOptions}]}
- ],
-
- {ok, [{dispatch, Dispatch}]}.
+ {ok, [{loop, {couch_httpd, handle_request, Args}}]}.
display_uris() ->
display_uris(get_bindings()).
@@ -249,8 +242,6 @@ make_arity_3_fun(SpecStr) ->
make_fun_spec_strs(SpecStr) ->
re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}]).
-init(_Prot, _Req, _Opts) ->
- {upgrade, protocol, mochicow_upgrade}.
handle_request(Req, {DefaultFun, UrlHandlers, DbUrlHandlers,
DesignUrlHandlers, SocketOptions}) ->
View
1 apps/couch_httpd/src/couch_httpd_external.erl
@@ -81,6 +81,7 @@ json_req_obj(#httpd{mochi_req=Req,
Hlist = mochiweb_headers:to_list(Headers),
{ok, Info} = couch_db:get_db_info(Db),
+ lager:info("Headers ~p~n", [Hlist]),
% add headers...
{[{<<"info">>, {Info}},
{<<"id">>, DocId},
View
160 apps/couch_httpd/src/couch_httpd_protocol.erl
@@ -0,0 +1,160 @@
+%M% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%%% ex: ft=erlang ts=4 sw=4 et
+%%%
+%%% This file is part of rcouch released under the Apache 2 license.
+%%% See the NOTICE for more information.
+
+
+-module(couch_httpd_protocol).
+
+-export([start_link/4]).
+-export([init/4]).
+
+-export([loop/1]).
+-export([after_response/2, reentry/1]).
+-export([new_request/3, call_body/2]).
+
+-include("couch_httpd.hrl").
+
+-define(REQUEST_RECV_TIMEOUT, 300000). %% timeout waiting for request line
+-define(HEADERS_RECV_TIMEOUT, 30000). %% timeout waiting for headers
+
+-define(MAX_HEADERS, 1000).
+-define(DEFAULTS, [{name, ?MODULE},
+ {port, 8888}]).
+
+
+%% @doc Start a mochiweb process
+-spec start_link(pid(), inet:socket(), module(), any()) -> {ok, pid()}.
+start_link(ListenerPid, Socket, Transport, Opts) ->
+ Pid = spawn_link(?MODULE, init, [ListenerPid, Socket, Transport, Opts]),
+ {ok, Pid}.
+
+
+%% @private
+-spec init(pid(), inet:socket(), module(), any()) -> ok | none().
+init(ListenerPid, Socket, Transport, Opts) ->
+ {loop, HttpLoop} = proplists:lookup(loop, Opts),
+ ok = ranch:accept_ack(ListenerPid),
+ loop(#hstate{socket = Socket,
+ transport = Transport,
+ loop = HttpLoop}).
+
+loop(#hstate{transport=Transport, socket=Socket}=State) ->
+ ok = Transport:setopts(Socket, [{packet, http}]),
+ request(State).
+
+request(#hstate{transport=Transport, socket=Socket}=State) ->
+ ok = Transport:setopts(Socket, [{active, once}]),
+ receive
+ {Protocol, _, {http_request, Method, Path, Version}}
+ when Protocol == http orelse Protocol == ssl ->
+ ok = Transport:setopts(Socket, [{packet, httph}]),
+ headers(State, {Method, Path, Version}, [], 0);
+ {Protocol, _, {http_error, "\r\n"}}
+ when Protocol == http orelse Protocol == ssl ->
+ request(State);
+ {Protocol, _, {http_error, "\n"}}
+ when Protocol == http orelse Protocol == ssl ->
+ request(State);
+ {tcp_closed, _} ->
+ Transport:close(Socket),
+ exit(normal);
+ {ssl_closed, _} ->
+ Transport:close(Socket),
+ exit(normal);
+ _Other ->
+ handle_invalid_request(State)
+ after ?REQUEST_RECV_TIMEOUT ->
+ Transport:close(Socket),
+ exit(normal)
+ end.
+
+headers(#hstate{transport=Transport, socket=Socket}=State, Request,
+ Headers, ?MAX_HEADERS) ->
+ %% Too many headers sent, bad request.
+ ok = Transport:setopts(Socket, [{packet, raw}]),
+ handle_invalid_request(State, Request, Headers);
+headers(#hstate{transport=Transport, socket=Socket, loop=Loop}=State, Request,
+ Headers, HeaderCount) ->
+ ok = Transport:setopts(Socket, [{active, once}]),
+ receive
+ {Protocol, _, http_eoh}
+ when Protocol == http orelse Protocol == ssl ->
+ Req = new_request(State, Request, Headers),
+ call_body(Loop, Req),
+ ?MODULE:after_response(Loop, Req);
+ {Protocol, _, {http_header, _, Name, _, Value}}
+ when Protocol == http orelse Protocol == ssl ->
+ headers(State, Request, [{Name, Value} | Headers],
+ 1 + HeaderCount);
+ {tcp_closed, _} ->
+ Transport:close(Socket),
+ exit(normal);
+ _Other ->
+ handle_invalid_request(State, Request, Headers)
+ after ?HEADERS_RECV_TIMEOUT ->
+ Transport:close(Socket),
+ exit(normal)
+ end.
+
+call_body({M, F, A}, Req) ->
+ erlang:apply(M, F, [Req | A]);
+call_body({M, F}, Req) ->
+ M:F(Req);
+call_body(Body, Req) ->
+ Body(Req).
+
+-spec handle_invalid_request(term()) -> no_return().
+handle_invalid_request(State) ->
+ handle_invalid_request(State, {'GET', {abs_path, "/"}, {0,9}}, []),
+ exit(normal).
+
+-spec handle_invalid_request(term(), term(), term()) -> no_return().
+handle_invalid_request(#hstate{transport=Transport, socket=Socket}=State,
+ Request, RevHeaders) ->
+ Req = new_request(mochiweb_socket(State), Request, RevHeaders),
+ Req:respond({400, [], []}),
+ Transport:close(Socket),
+ exit(normal).
+
+new_request(#hstate{transport=Transport, socket=Socket}=State,
+ Request, RevHeaders) ->
+ Transport:setopts(Socket, [{packet, raw}]),
+ mochiweb:new_request({mochiweb_socket(State), Request,
+ lists:reverse(RevHeaders)}).
+
+
+reentry(Body) ->
+ fun (Req) ->
+ ?MODULE:after_response(Body, Req)
+ end.
+
+after_response(Body, Req) ->
+ {Transport, Socket} = case Req:get(socket) of
+ {ssl, S} ->
+ {ranch_ssl, S};
+ S ->
+ {ranch_tcp, S}
+ end,
+
+ case Req:should_close() of
+ true ->
+ mochiweb_socket:close(Socket),
+ exit(normal);
+ false ->
+ Req:cleanup(),
+ erlang:garbage_collect(),
+ ?MODULE:loop(#hstate{transport=Transport,
+ socket=Socket,
+ loop=Body})
+ end.
+
+
+mochiweb_socket(#hstate{transport=Transport, socket=Socket}) ->
+ case Transport of
+ ranch_ssl ->
+ {ssl, Socket};
+ _ ->
+ Socket
+ end.
View
5 apps/couch_httpd/src/couch_httpd_rewrite.erl
@@ -187,12 +187,11 @@ handle_rewrite_req(#httpd{
?LOG_DEBUG("rewrite to ~p ~n", [RawPath1]),
% build a new mochiweb request
- MochiReq1 = mochicow_request:new(MochiReq:get(socket),
+ MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
MochiReq:get(method),
RawPath1,
MochiReq:get(version),
- Headers,
- MochiReq:get(buffer)),
+ Headers),
% cleanup, It force mochiweb to reparse raw uri.
MochiReq1:cleanup(),
View
10 apps/couch_httpd/src/couch_httpd_vhost.erl
@@ -111,12 +111,11 @@ dispatch_host(MochiReq) ->
_Else ->
NewPath1 = mochiweb_util:urlunsplit_path({NewPath, Query,
Fragment}),
- MochiReq1 = mochicow_request:new(MochiReq:get(socket),
+ MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
MochiReq:get(method),
NewPath1,
MochiReq:get(version),
- MochiReq:get(headers),
- MochiReq:get(buffer)),
+ MochiReq:get(headers)),
Fun(MochiReq1, VhostTarget)
end
end,
@@ -138,12 +137,11 @@ redirect_to_vhost(MochiReq, VhostTarget) ->
MochiReq:get(headers)),
% build a new mochiweb request
- MochiReq1 = mochicow_request:new(MochiReq:get(socket),
+ MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
MochiReq:get(method),
Target,
MochiReq:get(version),
- Headers,
- MochiReq:get(buffer)),
+ Headers),
% cleanup, It force mochiweb to reparse raw uri.
MochiReq1:cleanup(),
MochiReq1.

0 comments on commit 91bedce

Please sign in to comment.