couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jch...@apache.org
Subject svn commit: r727136 - in /couchdb/trunk: etc/couchdb/default.ini.tpl.in src/couchdb/Makefile.am src/couchdb/couch_external_manager.erl src/couchdb/couch_external_server.erl src/couchdb/couch_httpd_external.erl
Date Tue, 16 Dec 2008 20:42:05 GMT
Author: jchris
Date: Tue Dec 16 12:42:05 2008
New Revision: 727136

URL: http://svn.apache.org/viewvc?rev=727136&view=rev
Log:
the external process handler

Added:
    couchdb/trunk/src/couchdb/couch_external_manager.erl
    couchdb/trunk/src/couchdb/couch_external_server.erl
    couchdb/trunk/src/couchdb/couch_httpd_external.erl
Modified:
    couchdb/trunk/etc/couchdb/default.ini.tpl.in
    couchdb/trunk/src/couchdb/Makefile.am

Modified: couchdb/trunk/etc/couchdb/default.ini.tpl.in
URL: http://svn.apache.org/viewvc/couchdb/trunk/etc/couchdb/default.ini.tpl.in?rev=727136&r1=727135&r2=727136&view=diff
==============================================================================
--- couchdb/trunk/etc/couchdb/default.ini.tpl.in (original)
+++ couchdb/trunk/etc/couchdb/default.ini.tpl.in Tue Dec 16 12:42:05 2008
@@ -21,13 +21,16 @@
 [query_servers]
 javascript = %bindir%/%couchjs_command_name% %localdatadir%/server/main.js
 
+[external]
+action = %bindir%/%couchjs_command_name% %localdatadir%/server/action.js
+
 [daemons]
 view_manager={couch_view, start_link, []}
+external_manager={couch_external_manager, start_link, []}
 db_update_notifier={couch_db_update_notifier_sup, start_link, []}
 query_servers={couch_query_servers, start_link, []}
 httpd={couch_httpd, start_link, []}
 
-
 [httpd_global_handlers]
 / = {couch_httpd_misc_handlers, handle_welcome_req, <<"Welcome">>}
 favicon.ico = {couch_httpd_misc_handlers, handle_favicon_req, "%localdatadir%/www"}
@@ -42,3 +45,4 @@
 [httpd_db_handlers]
 _view = {couch_httpd_view, handle_view_req}
 _temp_view = {couch_httpd_view, handle_temp_view_req}
+_external = {couch_httpd_external, handle_external_req}

Modified: couchdb/trunk/src/couchdb/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/Makefile.am?rev=727136&r1=727135&r2=727136&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/Makefile.am (original)
+++ couchdb/trunk/src/couchdb/Makefile.am Tue Dec 16 12:42:05 2008
@@ -48,9 +48,12 @@
     couch_db_update_notifier_sup.erl \
     couch_doc.erl \
     couch_event_sup.erl \
+    couch_external_manager.erl \
+    couch_external_server.erl \
     couch_file.erl \
     couch_httpd.erl \
     couch_httpd_db.erl \
+    couch_httpd_external.erl \
     couch_httpd_view.erl \
     couch_httpd_misc_handlers.erl \
     couch_key_tree.erl \
@@ -79,9 +82,12 @@
     couch_db_update_notifier_sup.beam \
     couch_doc.beam \
     couch_event_sup.beam \
+    couch_external_manager.beam \
+    couch_external_server.beam \
     couch_file.beam \
     couch_httpd.beam \
     couch_httpd_db.beam \
+    couch_httpd_external.beam \
     couch_httpd_view.beam \
     couch_httpd_misc_handlers.beam \
     couch_key_tree.beam \

Added: couchdb/trunk/src/couchdb/couch_external_manager.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_external_manager.erl?rev=727136&view=auto
==============================================================================
--- couchdb/trunk/src/couchdb/couch_external_manager.erl (added)
+++ couchdb/trunk/src/couchdb/couch_external_manager.erl Tue Dec 16 12:42:05 2008
@@ -0,0 +1,97 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License.  You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_external_manager).
+-behaviour(gen_server).
+
+-export([start_link/0, execute/8, config_change/2]).
+-export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]).
+
+-include("couch_db.hrl").
+
+start_link() ->
+    gen_server:start_link({local, couch_external_manager}, couch_external_manager, [], []).
+
+execute(UrlName, Db, Verb, Path, Query, Body, Post, Cookie) ->
+    Pid = gen_server:call(couch_external_manager, {get, UrlName}),
+    case Pid of
+    {error, Reason} ->
+        Reason;
+    _ ->
+        couch_external_server:execute(Pid, Db, Verb, Path, Query, Body, Post, Cookie)
+    end.
+
+config_change("external", UrlName) ->
+    gen_server:call(couch_external_manager, {config, UrlName}).
+
+% gen_server API
+
+init([]) ->
+    Handlers = ets:new(couch_external_manager_handlers, [set, private]),
+    lists:foreach(fun({UrlName, Command}) ->
+        {ok, Pid} = couch_external_server:start_link(UrlName, Command),
+        true = ets:insert(Handlers, {UrlName, Pid})
+    end, couch_config:get("external")),
+    couch_config:register(fun config_change/2),
+    {ok, Handlers}.
+
+terminate(_Reason, Handlers) ->
+    ets:foldl(fun({_UrlName, Pid}, nil) ->
+        couch_external_server:stop(Pid),
+        nil
+    end, nil, Handlers),
+    ok.
+
+handle_call({get, UrlName}, _From, Handlers) ->
+    Resp = case ets:lookup(Handlers, UrlName) of
+    [{UrlName, Pid}] ->
+        Pid;
+    [] ->
+        Mesg = lists:flatten(io_lib:format("No server configured for ~p.", [UrlName])),
+        {error, {unknown_external_server, Mesg}}
+    end,
+    {reply, Resp, Handlers};
+handle_call({config, UrlName}, _From, Handlers) ->
+    % A newly added handler and a handler that had it's command
+    % changed are treated exactly the same.
+
+    % Shutdown the old handler.
+    case ets:lookup(Handlers, UrlName) of
+    [{UrlName, Pid}] ->
+        couch_external_server:stop(Pid);
+    _ ->
+        ok
+    end,
+    case couch_config:get("external", UrlName, nil) of
+    % Handler no longer exists
+    nil ->
+        ok;
+    % New handler start up.
+    Command ->
+        {ok, NewPid} = couch_external_server:start_link(UrlName, Command),
+        true = ets:insert(Handlers, {Command, NewPid})
+    end,
+    {reply, ok, Handlers}.
+
+handle_cast(_Whatever, State) ->
+    {noreply, State}.
+
+handle_info({'EXIT', Reason, Pid}, Handlers) ->
+    ?LOG_DEBUG("External server ~p died. (reason: ~p)", [Pid, Reason]),
+    % Remove Pid from the handlers table so we don't try closing
+    % it a second time in terminate/2.
+    ets:match_delete(Handlers, {'_', Pid}),
+    {stop, Handlers}.
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+

Added: couchdb/trunk/src/couchdb/couch_external_server.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_external_server.erl?rev=727136&view=auto
==============================================================================
--- couchdb/trunk/src/couchdb/couch_external_server.erl (added)
+++ couchdb/trunk/src/couchdb/couch_external_server.erl Tue Dec 16 12:42:05 2008
@@ -0,0 +1,80 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License.  You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_external_server).
+-behaviour(gen_server).
+
+-export([start_link/2, stop/1, execute/8]).
+-export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2, code_change/3]).

+
+-define(TIMEOUT, 5000).
+
+-include("couch_db.hrl").
+
+% External API
+
+start_link(Name, Command) ->
+    gen_server:start_link(couch_external_server, [Name, Command], []).
+
+stop(Pid) ->
+    gen_server:cast(Pid, stop).
+
+execute(Pid, Db, Verb, Path, Query, Body, Post, Cookie) ->
+    gen_server:call(Pid, {execute, Db, Verb, Path, Query, Body, Post, Cookie}).
+
+% Gen Server Handlers
+
+init([Name, Command]) ->
+    ?LOG_INFO("Starting process for: ~s", [Name]),
+    {ok, Pid} = couch_os_process:start_link(Command),
+    {ok, {Name, Command, Pid}}.
+
+terminate(_Reason, {Name, _Command, Pid}) ->
+    ?LOG_INFO("External Process Terminating: ~p: ~p", [Name, Pid]),
+    couch_os_process:stop(Pid),
+    ok.
+
+handle_call({execute, Db, Verb, Path, Query, Body, Post, Cookie}, _From, {Name, Command,
Pid}) ->
+    ?LOG_DEBUG("Query Params ~p",[Query]),
+    {ok, Info} = couch_db:get_db_info(Db),
+    Json = {[
+        {<<"info">>, {Info}},
+        {<<"verb">>, Verb},
+        {<<"path">>, Path},
+        {<<"query">>, to_json_terms(Query)},
+        {<<"body">>, Body},
+        {<<"form">>, to_json_terms(Post)},
+        {<<"cookie">>, to_json_terms(Cookie)}]},
+    {reply, couch_os_process:prompt(Pid, Json), {Name, Command, Pid}}.
+
+handle_info({'EXIT', Pid, Reason}, {Name, Command, Pid}) ->
+    ?LOG_INFO("EXTERNAL: Restarting process for ~s (reason: ~w)", [Name, Reason]),
+    {ok, Pid} = couch_os_process:start_link(Command),
+    {noreply, {Name, Command, Pid}}.
+
+handle_cast(stop, State) ->
+    {stop, normal, State};
+handle_cast(_Whatever, State) ->
+    {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+% Internal API
+
+to_json_terms(Data) ->
+    to_json_terms(Data, []).
+to_json_terms([], Acc) ->
+    {lists:reverse(Acc)};
+to_json_terms([{Key, Value} | Rest], Acc) ->
+    to_json_terms(Rest, [{list_to_binary(Key), list_to_binary(Value)} | Acc]).
+

Added: couchdb/trunk/src/couchdb/couch_httpd_external.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_external.erl?rev=727136&view=auto
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_external.erl (added)
+++ couchdb/trunk/src/couchdb/couch_httpd_external.erl Tue Dec 16 12:42:05 2008
@@ -0,0 +1,107 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License.  You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_httpd_external).
+
+-export([handle_external_req/2]).
+
+-import(couch_httpd,[send_error/4]).
+
+-include("couch_db.hrl").
+
+-record(extern_resp_args, {
+    code = 200,
+    data = <<>>,
+    ctype = "application/json",
+    headers = []
+}).
+
+handle_external_req(#httpd{mochi_req=Req, 
+                        method=Verb,
+                        path_parts=[_DbName, _External, UrlName | Path]
+                    }=HttpReq, Db) ->
+    ReqBody = Req:recv_body(),
+    ParsedForm = case Req:get_primary_header_value("content-type") of
+        "application/x-www-form-urlencoded" ++ _ ->
+            mochiweb_util:parse_qs(ReqBody);
+        _ ->
+            []
+    end,
+    Response = couch_external_manager:execute(binary_to_list(UrlName), 
+        Db, Verb, Path, Req:parse_qs(), ReqBody, ParsedForm,
+        Req:parse_cookie()),
+        
+    case Response of
+    {unknown_external_server, Msg} ->
+        send_error(HttpReq, 404, <<"external_server_error">>, Msg);
+    _ ->
+        send_external_response(Req, Response)
+    end;
+handle_external_req(#httpd{path_parts=[_, _]}=Req, _Db) ->
+    send_error(Req, 404, <<"external_server_error">>, <<"No server name
specified.">>);
+handle_external_req(Req, _) ->
+    send_error(Req, 404, <<"external_server_error">>, <<"Broken assumption">>).
+
+send_external_response(Req, Response) ->
+    #extern_resp_args{
+        code = Code,
+        data = Data,
+        ctype = CType,
+        headers = Headers
+    } = parse_external_response(Response),
+    ?LOG_DEBUG("External Response ~p",[Response]),
+    Resp = Req:respond({Code, 
+        default_or_content_type(CType, Headers), chunked}),
+    Resp:write_chunk(Data),
+    Resp:write_chunk(""),
+    {ok, Resp}.
+
+parse_external_response({Response}) ->
+    lists:foldl(fun({Key,Value}, Args) ->
+        case {Key, Value} of
+            {"", _} ->
+                Args;
+            {<<"code">>, Value} ->
+                Args#extern_resp_args{code=Value};
+            {<<"json">>, Value} ->
+                Args#extern_resp_args{
+                    data=?JSON_ENCODE(Value),
+                    ctype="application/json"};
+            {<<"body">>, Value} ->
+                Args#extern_resp_args{data=Value, ctype="text/html"};
+            {<<"headers">>, {Headers}} ->
+                NewHeaders = lists:map(fun({Header, HVal}) ->
+                    {binary_to_list(Header), binary_to_list(HVal)}
+                end, Headers),
+                Args#extern_resp_args{headers=NewHeaders};
+            _ -> % unknown key
+                Msg = lists:flatten(io_lib:format("Invalid data from external server: ~s
= ~p", [Key, Value])),
+                throw({external_response_error, Msg})
+            end
+        end, #extern_resp_args{}, Response).
+
+default_or_content_type(DefaultContentType, Headers) ->
+    {ContentType, OtherHeaders} = lists:partition(
+        fun({HeaderName, _}) ->
+            HeaderName == "Content-Type"
+        end, Headers),
+
+    % XXX: What happens if we were passed multiple content types? We add another?
+    case ContentType of
+        [{"Content-Type", SetContentType}] ->
+            TrueContentType = SetContentType;
+        _Else ->
+            TrueContentType = DefaultContentType
+    end,
+
+    HeadersWithContentType = lists:append(OtherHeaders, [{"Content-Type", TrueContentType}]),
+    HeadersWithContentType.



Mime
View raw message