You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2014/02/05 00:44:16 UTC
[03/44] couchdb commit: updated
refs/heads/1843-feature-bigcouch-multi-repo to 2385a37
Remove src/chttpd
Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/e41cfa40
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/e41cfa40
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/e41cfa40
Branch: refs/heads/1843-feature-bigcouch-multi-repo
Commit: e41cfa40ea519312f677b7c4ca0905c4f106636b
Parents: cd36fd1
Author: Paul J. Davis <pa...@gmail.com>
Authored: Tue Feb 4 17:37:21 2014 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Tue Feb 4 17:39:02 2014 -0600
----------------------------------------------------------------------
src/chttpd/src/chttpd.app.src | 41 -
src/chttpd/src/chttpd.erl | 848 ------------
src/chttpd/src/chttpd_app.erl | 21 -
src/chttpd/src/chttpd_config_listener.erl | 80 --
src/chttpd/src/chttpd_db.erl | 1280 ------------------
src/chttpd/src/chttpd_external.erl | 177 ---
src/chttpd/src/chttpd_misc.erl | 312 -----
src/chttpd/src/chttpd_rewrite.erl | 456 -------
src/chttpd/src/chttpd_show.erl | 322 -----
src/chttpd/src/chttpd_sup.erl | 29 -
src/chttpd/src/chttpd_view.erl | 405 ------
.../test/chttpd_delayed_response_test.erl | 41 -
src/chttpd/test/mock_request.erl | 37 -
13 files changed, 4049 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb/blob/e41cfa40/src/chttpd/src/chttpd.app.src
----------------------------------------------------------------------
diff --git a/src/chttpd/src/chttpd.app.src b/src/chttpd/src/chttpd.app.src
deleted file mode 100644
index 9ab91c8..0000000
--- a/src/chttpd/src/chttpd.app.src
+++ /dev/null
@@ -1,41 +0,0 @@
-% 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.
-
- {application, chttpd, [
- {description, "HTTP interface for CouchDB cluster"},
- {vsn, git},
- {modules, [
- chttpd,
- chttpd_app,
- chttpd_config_listener,
- chttpd_db,
- chttpd_external,
- chttpd_misc,
- chttpd_rewrite,
- chttpd_show,
- chttpd_sup,
- chttpd_view
- ]},
- {registered, [
- chttpd_sup,
- chttpd
- ]},
- {applications, [
- kernel,
- stdlib,
- twig,
- config,
- couch,
- fabric
- ]},
- {mod, {chttpd_app,[]}}
-]}.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/e41cfa40/src/chttpd/src/chttpd.erl
----------------------------------------------------------------------
diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl
deleted file mode 100644
index b7f8013..0000000
--- a/src/chttpd/src/chttpd.erl
+++ /dev/null
@@ -1,848 +0,0 @@
-% 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(chttpd).
--include_lib("couch/include/couch_db.hrl").
-
--export([start_link/0, start_link/1, start_link/2,
- stop/0, handle_request/1,
- primary_header_value/2, header_value/2, header_value/3, qs_value/2,
- qs_value/3, qs/1, qs_json_value/3, path/1, absolute_uri/2, body_length/1,
- verify_is_server_admin/1, unquote/1, quote/1, recv/2, recv_chunked/4,
- error_info/1, parse_form/1, json_body/1, json_body_obj/1, body/1,
- doc_etag/1, make_etag/1, etag_respond/3, partition/1, serve_file/3,
- server_header/0, start_chunked_response/3,send_chunk/2,
- start_response_length/4, send/2, start_json_response/2,
- start_json_response/3, end_json_response/1, send_response/4,
- send_method_not_allowed/2, send_error/2, send_error/4, send_redirect/2,
- send_chunked_error/2, send_json/2,send_json/3,send_json/4]).
-
--export([start_delayed_json_response/2, start_delayed_json_response/3,
- start_delayed_json_response/4,
- start_delayed_chunked_response/3, start_delayed_chunked_response/4,
- send_delayed_chunk/2, send_delayed_last_chunk/1,
- send_delayed_error/2, end_delayed_json_response/1,
- get_delayed_req/1]).
-
--record(delayed_resp, {
- start_fun,
- req,
- code,
- headers,
- first_chunk,
- resp=nil
-}).
-
-start_link() ->
- start_link(http).
-start_link(http) ->
- Port = config:get("chttpd", "port", "5984"),
- start_link(?MODULE, [{port, Port}]);
-
-start_link(https) ->
- Port = config:get("chttps", "port", "6984"),
- CertFile = config:get("chttps", "cert_file", nil),
- KeyFile = config:get("chttps", "key_file", nil),
- Options = case CertFile /= nil andalso KeyFile /= nil of
- true ->
- SslOpts = [{certfile, CertFile}, {keyfile, KeyFile}],
-
- %% set password if one is needed for the cert
- SslOpts1 = case config:get("chttps", "password", nil) of
- nil -> SslOpts;
- Password ->
- SslOpts ++ [{password, Password}]
- end,
- % do we verify certificates ?
- FinalSslOpts = case config:get("chttps",
- "verify_ssl_certificates", "false") of
- "false" -> SslOpts1;
- "true" ->
- case config:get("chttps",
- "cacert_file", nil) of
- nil ->
- io:format("Verify SSL certificate "
- ++"enabled but file containing "
- ++"PEM encoded CA certificates is "
- ++"missing", []),
- throw({error, missing_cacerts});
- CaCertFile ->
- Depth = list_to_integer(config:get("chttps",
- "ssl_certificate_max_depth",
- "1")),
- FinalOpts = [
- {cacertfile, CaCertFile},
- {depth, Depth},
- {verify, verify_peer}],
- % allows custom verify fun.
- case config:get("chttps",
- "verify_fun", nil) of
- nil -> FinalOpts;
- SpecStr ->
- FinalOpts
- ++ [{verify_fun, couch_httpd:make_arity_3_fun(SpecStr)}]
- end
- end
- end,
-
- [{port, Port},
- {ssl, true},
- {ssl_opts, FinalSslOpts}];
- false ->
- io:format("SSL enabled but PEM certificates are missing.", []),
- throw({error, missing_certs})
- end,
- start_link(https, Options).
-
-start_link(Name, Options) ->
- Options1 = Options ++ [
- {loop, fun ?MODULE:handle_request/1},
- {name, Name},
- {ip, config:get("chttpd", "bind_address", any)}
- ],
- ServerOptsCfg = config:get("chttpd", "server_options", "[]"),
- {ok, ServerOpts} = couch_util:parse_term(ServerOptsCfg),
- Options2 = lists:keymerge(1, lists:sort(Options1), lists:sort(ServerOpts)),
- case mochiweb_http:start(Options2) of
- {ok, Pid} ->
- {ok, Pid};
- {error, Reason} ->
- io:format("Failure to start Mochiweb: ~s~n", [Reason]),
- {error, Reason}
- end.
-
-stop() ->
- catch mochiweb_http:stop(https),
- mochiweb_http:stop(?MODULE).
-
-handle_request(MochiReq) ->
- Begin = os:timestamp(),
-
- case config:get("chttpd", "socket_options") of
- undefined ->
- ok;
- SocketOptsCfg ->
- {ok, SocketOpts} = couch_util:parse_term(SocketOptsCfg),
- ok = mochiweb_socket:setopts(MochiReq:get(socket), SocketOpts)
- end,
-
- AuthenticationFuns = [
- fun couch_httpd_auth:cookie_authentication_handler/1,
- fun couch_httpd_auth:default_authentication_handler/1
- ],
-
- % for the path, use the raw path with the query string and fragment
- % removed, but URL quoting left intact
- RawUri = MochiReq:get(raw_path),
- {"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri),
- {HandlerKey, _, _} = mochiweb_util:partition(Path, "/"),
-
- Peer = MochiReq:get(peer),
- LogForClosedSocket = io_lib:format("mochiweb_recv_error for ~s - ~p ~s", [
- Peer,
- MochiReq:get(method),
- RawUri
- ]),
-
- Method1 =
- case MochiReq:get(method) of
- % already an atom
- Meth when is_atom(Meth) -> Meth;
-
- % Non standard HTTP verbs aren't atoms (COPY, MOVE etc) so convert when
- % possible (if any module references the atom, then it's existing).
- Meth -> couch_util:to_existing_atom(Meth)
- end,
- increment_method_stats(Method1),
-
- % allow broken HTTP clients to fake a full method vocabulary with an X-HTTP-METHOD-OVERRIDE header
- MethodOverride = MochiReq:get_primary_header_value("X-HTTP-Method-Override"),
- Method2 = case lists:member(MethodOverride, ["GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", "COPY"]) of
- true ->
- twig:log(notice, "MethodOverride: ~s (real method was ~s)", [MethodOverride, Method1]),
- case Method1 of
- 'POST' -> couch_util:to_existing_atom(MethodOverride);
- _ ->
- % Ignore X-HTTP-Method-Override when the original verb isn't POST.
- % I'd like to send a 406 error to the client, but that'd require a nasty refactor.
- % throw({not_acceptable, <<"X-HTTP-Method-Override may only be used with POST requests.">>})
- Method1
- end;
- _ -> Method1
- end,
-
- % alias HEAD to GET as mochiweb takes care of stripping the body
- Method = case Method2 of
- 'HEAD' -> 'GET';
- Other -> Other
- end,
-
- HttpReq = #httpd{
- mochi_req = MochiReq,
- method = Method,
- path_parts = [list_to_binary(chttpd:unquote(Part))
- || Part <- string:tokens(Path, "/")],
- db_url_handlers = db_url_handlers(),
- design_url_handlers = design_url_handlers()
- },
-
- % put small token on heap to keep requests synced to backend calls
- erlang:put(nonce, couch_util:to_hex(crypto:rand_bytes(4))),
-
- Result =
- try
- case authenticate_request(HttpReq, AuthenticationFuns) of
- #httpd{} = Req ->
- HandlerFun = url_handler(HandlerKey),
- HandlerFun(possibly_hack(Req));
- Response ->
- Response
- end
- catch
- throw:{http_head_abort, Resp0} ->
- {ok, Resp0};
- throw:{http_abort, Resp0, Reason0} ->
- {aborted, Resp0, Reason0};
- throw:{invalid_json, _} ->
- send_error(HttpReq, {bad_request, "invalid UTF-8 JSON"});
- exit:{mochiweb_recv_error, E} ->
- twig:log(notice, LogForClosedSocket ++ " - ~p", [E]),
- exit(normal);
- throw:Error ->
- send_error(HttpReq, Error);
- error:database_does_not_exist ->
- send_error(HttpReq, database_does_not_exist);
- Tag:Error ->
- Stack = erlang:get_stacktrace(),
- % TODO improve logging and metrics collection for client disconnects
- case {Tag, Error, Stack} of
- {exit, normal, [{mochiweb_request, send, _, _} | _]} ->
- exit(normal); % Client disconnect (R15+)
- {exit, normal, [{mochiweb_request, send, _} | _]} ->
- exit(normal); % Client disconnect (R14)
- _Else ->
- JsonStack = json_stack({Error, nil, Stack}),
- twig:log(error, "req_err ~p:~p ~p", [Tag, Error, JsonStack]),
- send_error(HttpReq, {Error, nil, Stack})
- end
- end,
-
- RequestTime = timer:now_diff(os:timestamp(), Begin)/1000,
- {Status, Code} = case Result of
- {ok, #delayed_resp{resp=Resp}} ->
- {ok, Resp:get(code)};
- {ok, Resp} ->
- {ok, Resp:get(code)};
- {aborted, Resp, _} ->
- {aborted, Resp:get(code)}
- end,
- Host = MochiReq:get_header_value("Host"),
- twig:log(notice, "~s ~s ~s ~s ~B ~p ~B", [Peer, Host,
- atom_to_list(Method1), RawUri, Code, Status, round(RequestTime)]),
- couch_stats_collector:record({couchdb, request_time}, RequestTime),
- case Result of
- {ok, _} ->
- couch_stats_collector:increment({httpd, requests}),
- {ok, Resp};
- {aborted, _, Reason} ->
- couch_stats_collector:increment({httpd, aborted_requests}),
- twig:log(error, "Response abnormally terminated: ~p", [Reason]),
- exit(normal)
- end.
-
-%% HACK: replication currently handles two forms of input, #db{} style
-%% and #http_db style. We need a third that makes use of fabric. #db{}
-%% works fine for replicating the dbs and nodes database because they
-%% aren't sharded. So for now when a local db is specified as the source or
-%% the target, it's hacked to make it a full url and treated as a remote.
-possibly_hack(#httpd{path_parts=[<<"_replicate">>]}=Req) ->
- {Props0} = chttpd:json_body_obj(Req),
- Props1 = fix_uri(Req, Props0, <<"source">>),
- Props2 = fix_uri(Req, Props1, <<"target">>),
- put(post_body, {Props2}),
- Req;
-possibly_hack(Req) ->
- Req.
-
-fix_uri(Req, Props, Type) ->
- case is_http(replication_uri(Type, Props)) of
- true ->
- Props;
- false ->
- Uri = make_uri(Req,replication_uri(Type, Props)),
- [{Type,Uri}|proplists:delete(Type,Props)]
- end.
-
-replication_uri(Type, PostProps) ->
- case couch_util:get_value(Type, PostProps) of
- {Props} ->
- couch_util:get_value(<<"url">>, Props);
- Else ->
- Else
- end.
-
-is_http(<<"http://", _/binary>>) ->
- true;
-is_http(<<"https://", _/binary>>) ->
- true;
-is_http(_) ->
- false.
-
-make_uri(Req, Raw) ->
- Url = list_to_binary(["http://", config:get("httpd", "bind_address"),
- ":", config:get("chttpd", "port"), "/", Raw]),
- Headers = [
- {<<"authorization">>, ?l2b(header_value(Req,"authorization",""))},
- {<<"cookie">>, ?l2b(header_value(Req,"cookie",""))}
- ],
- {[{<<"url">>,Url}, {<<"headers">>,{Headers}}]}.
-%%% end hack
-
-
-% Try authentication handlers in order until one returns a result
-authenticate_request(#httpd{user_ctx=#user_ctx{}} = Req, _AuthFuns) ->
- Req;
-authenticate_request(#httpd{} = Req, [AuthFun|Rest]) ->
- authenticate_request(AuthFun(Req), Rest);
-authenticate_request(#httpd{} = Req, []) ->
- case config:get("chttpd", "require_valid_user", "false") of
- "true" ->
- throw({unauthorized, <<"Authentication required.">>});
- "false" ->
- case config:get("admins") of
- [] ->
- Ctx = #user_ctx{roles=[<<"_reader">>, <<"_writer">>, <<"_admin">>]},
- Req#httpd{user_ctx = Ctx};
- _ ->
- Req#httpd{user_ctx=#user_ctx{}}
- end
- end;
-authenticate_request(Response, _AuthFuns) ->
- Response.
-
-increment_method_stats(Method) ->
- couch_stats_collector:increment({httpd_request_methods, Method}).
-
-url_handler("") -> fun chttpd_misc:handle_welcome_req/1;
-url_handler("favicon.ico") -> fun chttpd_misc:handle_favicon_req/1;
-url_handler("_utils") -> fun chttpd_misc:handle_utils_dir_req/1;
-url_handler("_all_dbs") -> fun chttpd_misc:handle_all_dbs_req/1;
-url_handler("_active_tasks") -> fun chttpd_misc:handle_task_status_req/1;
-url_handler("_config") -> fun chttpd_misc:handle_config_req/1;
-url_handler("_replicate") -> fun chttpd_misc:handle_replicate_req/1;
-url_handler("_uuids") -> fun chttpd_misc:handle_uuids_req/1;
-url_handler("_log") -> fun chttpd_misc:handle_log_req/1;
-url_handler("_sleep") -> fun chttpd_misc:handle_sleep_req/1;
-url_handler("_session") -> fun couch_httpd_auth:handle_session_req/1;
-url_handler("_oauth") -> fun couch_httpd_oauth:handle_oauth_req/1;
-url_handler("_up") -> fun chttpd_misc:handle_up_req/1;
-url_handler("_membership") -> fun mem3_httpd:handle_membership_req/1;
-url_handler(_) -> fun chttpd_db:handle_request/1.
-
-db_url_handlers() ->
- [
- {<<"_view_cleanup">>, fun chttpd_db:handle_view_cleanup_req/2},
- {<<"_compact">>, fun chttpd_db:handle_compact_req/2},
- {<<"_design">>, fun chttpd_db:handle_design_req/2},
- {<<"_temp_view">>, fun chttpd_view:handle_temp_view_req/2},
- {<<"_changes">>, fun chttpd_db:handle_changes_req/2}
- ].
-
-design_url_handlers() ->
- [
- {<<"_view">>, fun chttpd_view:handle_view_req/3},
- {<<"_show">>, fun chttpd_show:handle_doc_show_req/3},
- {<<"_list">>, fun chttpd_show:handle_view_list_req/3},
- {<<"_update">>, fun chttpd_show:handle_doc_update_req/3},
- {<<"_info">>, fun chttpd_db:handle_design_info_req/3},
- {<<"_rewrite">>, fun chttpd_rewrite:handle_rewrite_req/3}
- ].
-
-% Utilities
-
-partition(Path) ->
- mochiweb_util:partition(Path, "/").
-
-header_value(#httpd{mochi_req=MochiReq}, Key) ->
- MochiReq:get_header_value(Key).
-
-header_value(#httpd{mochi_req=MochiReq}, Key, Default) ->
- case MochiReq:get_header_value(Key) of
- undefined -> Default;
- Value -> Value
- end.
-
-primary_header_value(#httpd{mochi_req=MochiReq}, Key) ->
- MochiReq:get_primary_header_value(Key).
-
-serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot) ->
- {ok, MochiReq:serve_file(RelativePath, DocumentRoot,
- server_header() ++ couch_httpd_auth:cookie_auth_header(Req, []))}.
-
-qs_value(Req, Key) ->
- qs_value(Req, Key, undefined).
-
-qs_value(Req, Key, Default) ->
- couch_util:get_value(Key, qs(Req), Default).
-
-qs_json_value(Req, Key, Default) ->
- case qs_value(Req, Key, Default) of
- Default ->
- Default;
- Result ->
- ?JSON_DECODE(Result)
- end.
-
-qs(#httpd{mochi_req=MochiReq}) ->
- MochiReq:parse_qs().
-
-path(#httpd{mochi_req=MochiReq}) ->
- MochiReq:get(path).
-
-absolute_uri(#httpd{mochi_req=MochiReq}, Path) ->
- XHost = config:get("httpd", "x_forwarded_host", "X-Forwarded-Host"),
- Host = case MochiReq:get_header_value(XHost) of
- undefined ->
- case MochiReq:get_header_value("Host") of
- undefined ->
- {ok, {Address, Port}} = inet:sockname(MochiReq:get(socket)),
- inet_parse:ntoa(Address) ++ ":" ++ integer_to_list(Port);
- Value1 ->
- Value1
- end;
- Value -> Value
- end,
- XSsl = config:get("httpd", "x_forwarded_ssl", "X-Forwarded-Ssl"),
- Scheme = case MochiReq:get_header_value(XSsl) of
- "on" -> "https";
- _ ->
- XProto = config:get("httpd", "x_forwarded_proto",
- "X-Forwarded-Proto"),
- case MochiReq:get_header_value(XProto) of
- % Restrict to "https" and "http" schemes only
- "https" -> "https";
- _ ->
- case MochiReq:get(scheme) of
- https ->
- "https";
- http ->
- "http"
- end
- end
- end,
- Scheme ++ "://" ++ Host ++ Path.
-
-unquote(UrlEncodedString) ->
- mochiweb_util:unquote(UrlEncodedString).
-
-quote(UrlDecodedString) ->
- mochiweb_util:quote_plus(UrlDecodedString).
-
-parse_form(#httpd{mochi_req=MochiReq}) ->
- mochiweb_multipart:parse_form(MochiReq).
-
-recv(#httpd{mochi_req=MochiReq}, Len) ->
- MochiReq:recv(Len).
-
-recv_chunked(#httpd{mochi_req=MochiReq}, MaxChunkSize, ChunkFun, InitState) ->
- % Fun is called once with each chunk
- % Fun({Length, Binary}, State)
- % called with Length == 0 on the last time.
- MochiReq:stream_body(MaxChunkSize, ChunkFun, InitState).
-
-body_length(Req) ->
- case header_value(Req, "Transfer-Encoding") of
- undefined ->
- case header_value(Req, "Content-Length") of
- undefined -> undefined;
- Length -> list_to_integer(Length)
- end;
- "chunked" -> chunked;
- Unknown -> {unknown_transfer_encoding, Unknown}
- end.
-
-body(#httpd{mochi_req=MochiReq, req_body=ReqBody}) ->
- case ReqBody of
- undefined ->
- % Maximum size of document PUT request body (4GB)
- MaxSize = list_to_integer(
- config:get("couchdb", "max_document_size", "4294967296")),
- Begin = os:timestamp(),
- try
- MochiReq:recv_body(MaxSize)
- after
- T = timer:now_diff(os:timestamp(), Begin) div 1000,
- put(body_time, T)
- end;
- _Else ->
- ReqBody
- end.
-
-json_body(Httpd) ->
- case body(Httpd) of
- undefined ->
- throw({bad_request, "Missing request body"});
- Body ->
- ?JSON_DECODE(Body)
- end.
-
-json_body_obj(Httpd) ->
- case json_body(Httpd) of
- {Props} -> {Props};
- _Else ->
- throw({bad_request, "Request body must be a JSON object"})
- end.
-
-
-doc_etag(#doc{revs={Start, [DiskRev|_]}}) ->
- "\"" ++ ?b2l(couch_doc:rev_to_str({Start, DiskRev})) ++ "\"".
-
-make_etag(Term) ->
- <<SigInt:128/integer>> = erlang:md5(term_to_binary(Term)),
- list_to_binary(io_lib:format("\"~.36B\"",[SigInt])).
-
-etag_match(Req, CurrentEtag) when is_binary(CurrentEtag) ->
- etag_match(Req, binary_to_list(CurrentEtag));
-
-etag_match(Req, CurrentEtag) ->
- EtagsToMatch = string:tokens(
- chttpd:header_value(Req, "If-None-Match", ""), ", "),
- lists:member(CurrentEtag, EtagsToMatch).
-
-etag_respond(Req, CurrentEtag, RespFun) ->
- case etag_match(Req, CurrentEtag) of
- true ->
- % the client has this in their cache.
- chttpd:send_response(Req, 304, [{"Etag", CurrentEtag}], <<>>);
- false ->
- % Run the function.
- RespFun()
- end.
-
-verify_is_server_admin(#httpd{user_ctx=#user_ctx{roles=Roles}}) ->
- case lists:member(<<"_admin">>, Roles) of
- true -> ok;
- false -> throw({unauthorized, <<"You are not a server admin.">>})
- end.
-
-start_response_length(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Length) ->
- couch_stats_collector:increment({httpd_status_codes, Code}),
- Resp = MochiReq:start_response_length({Code, Headers ++ server_header() ++
- couch_httpd_auth:cookie_auth_header(Req, Headers), Length}),
- case MochiReq:get(method) of
- 'HEAD' -> throw({http_head_abort, Resp});
- _ -> ok
- end,
- {ok, Resp}.
-
-send(Resp, Data) ->
- Resp:send(Data),
- {ok, Resp}.
-
-start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
- couch_stats_collector:increment({httpd_status_codes, Code}),
- Resp = MochiReq:respond({Code, Headers ++ server_header() ++
- couch_httpd_auth:cookie_auth_header(Req, Headers), chunked}),
- case MochiReq:get(method) of
- 'HEAD' -> throw({http_head_abort, Resp});
- _ -> ok
- end,
- {ok, Resp}.
-
-send_chunk(Resp, Data) ->
- Resp:write_chunk(Data),
- {ok, Resp}.
-
-send_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Body) ->
- couch_stats_collector:increment({httpd_status_codes, Code}),
- {ok, MochiReq:respond({Code, Headers ++ server_header() ++
- couch_httpd_auth:cookie_auth_header(Req, Headers), Body})}.
-
-send_method_not_allowed(Req, Methods) ->
- send_error(Req, 405, [{"Allow", Methods}], <<"method_not_allowed">>,
- ?l2b("Only " ++ Methods ++ " allowed"), []).
-
-send_json(Req, Value) ->
- send_json(Req, 200, Value).
-
-send_json(Req, Code, Value) ->
- send_json(Req, Code, [], Value).
-
-send_json(Req, Code, Headers, Value) ->
- couch_httpd:send_json(Req, Code, [timing(), reqid() | Headers], Value).
-
-start_json_response(Req, Code) ->
- start_json_response(Req, Code, []).
-
-start_json_response(Req, Code, Headers) ->
- couch_httpd:start_json_response(Req, Code, [timing(), reqid() | Headers]).
-
-end_json_response(Resp) ->
- couch_httpd:end_json_response(Resp).
-
-start_delayed_json_response(Req, Code) ->
- start_delayed_json_response(Req, Code, []).
-
-start_delayed_json_response(Req, Code, Headers) ->
- start_delayed_json_response(Req, Code, Headers, "").
-
-start_delayed_json_response(Req, Code, Headers, FirstChunk) ->
- {ok, #delayed_resp{
- start_fun = fun start_json_response/3,
- req = Req,
- code = Code,
- headers = Headers,
- first_chunk = FirstChunk}}.
-
-start_delayed_chunked_response(Req, Code, Headers) ->
- start_delayed_chunked_response(Req, Code, Headers, "").
-
-start_delayed_chunked_response(Req, Code, Headers, FirstChunk) ->
- {ok, #delayed_resp{
- start_fun = fun start_chunked_response/3,
- req = Req,
- code = Code,
- headers = Headers,
- first_chunk = FirstChunk}}.
-
-send_delayed_chunk(#delayed_resp{}=DelayedResp, Chunk) ->
- {ok, #delayed_resp{resp=Resp}=DelayedResp1} =
- start_delayed_response(DelayedResp),
- {ok, Resp} = send_chunk(Resp, Chunk),
- {ok, DelayedResp1}.
-
-send_delayed_last_chunk(Req) ->
- send_delayed_chunk(Req, []).
-
-send_delayed_error(#delayed_resp{req=Req,resp=nil}, Reason) ->
- {Code, ErrorStr, ReasonStr} = error_info(Reason),
- send_error(Req, Code, ErrorStr, ReasonStr);
-send_delayed_error(#delayed_resp{resp=Resp}, Reason) ->
- throw({http_abort, Resp, Reason}).
-
-end_delayed_json_response(#delayed_resp{}=DelayedResp) ->
- {ok, #delayed_resp{resp=Resp}} =
- start_delayed_response(DelayedResp),
- end_json_response(Resp).
-
-get_delayed_req(#delayed_resp{req=#httpd{mochi_req=MochiReq}}) ->
- MochiReq;
-get_delayed_req(Resp) ->
- Resp:get(request).
-
-start_delayed_response(#delayed_resp{resp=nil}=DelayedResp) ->
- #delayed_resp{
- start_fun=StartFun,
- req=Req,
- code=Code,
- headers=Headers,
- first_chunk=FirstChunk
- }=DelayedResp,
- {ok, Resp} = StartFun(Req, Code, Headers),
- case FirstChunk of
- "" -> ok;
- _ -> {ok, Resp} = send_chunk(Resp, FirstChunk)
- end,
- {ok, DelayedResp#delayed_resp{resp=Resp}};
-start_delayed_response(#delayed_resp{}=DelayedResp) ->
- {ok, DelayedResp}.
-
-error_info({Error, Reason}) when is_list(Reason) ->
- error_info({Error, couch_util:to_binary(Reason)});
-error_info(bad_request) ->
- {400, <<"bad_request">>, <<>>};
-error_info({bad_request, Reason}) ->
- {400, <<"bad_request">>, Reason};
-error_info({bad_request, Error, Reason}) ->
- {400, couch_util:to_binary(Error), couch_util:to_binary(Reason)};
-error_info({query_parse_error, Reason}) ->
- {400, <<"query_parse_error">>, Reason};
-error_info(database_does_not_exist) ->
- {404, <<"not_found">>, <<"Database does not exist.">>};
-error_info(not_found) ->
- {404, <<"not_found">>, <<"missing">>};
-error_info({not_found, Reason}) ->
- {404, <<"not_found">>, Reason};
-error_info({not_acceptable, Reason}) ->
- {406, <<"not_acceptable">>, Reason};
-error_info(conflict) ->
- {409, <<"conflict">>, <<"Document update conflict.">>};
-error_info({conflict, _}) ->
- {409, <<"conflict">>, <<"Document update conflict.">>};
-error_info({forbidden, Error, Msg}) ->
- {403, Error, Msg};
-error_info({forbidden, Msg}) ->
- {403, <<"forbidden">>, Msg};
-error_info({unauthorized, Msg}) ->
- {401, <<"unauthorized">>, Msg};
-error_info(file_exists) ->
- {412, <<"file_exists">>, <<"The database could not be "
- "created, the file already exists.">>};
-error_info({r_quorum_not_met, Reason}) ->
- {412, <<"read_quorum_not_met">>, Reason};
-error_info({w_quorum_not_met, Reason}) ->
- {500, <<"write_quorum_not_met">>, Reason};
-error_info({bad_ctype, Reason}) ->
- {415, <<"bad_content_type">>, Reason};
-error_info(requested_range_not_satisfiable) ->
- {416, <<"requested_range_not_satisfiable">>, <<"Requested range not satisfiable">>};
-error_info({error, illegal_database_name}) ->
- {400, <<"illegal_database_name">>, <<"Only lowercase letters (a-z), "
- "digits (0-9), and any of the characters _, $, (, ), +, -, and / are "
- "allowed. Moreover, the database name must begin with a letter.">>};
-error_info({missing_stub, Reason}) ->
- {412, <<"missing_stub">>, Reason};
-error_info(not_implemented) ->
- {501, <<"not_implemented">>, <<"this feature is not yet implemented">>};
-error_info({Error, null}) ->
- {500, couch_util:to_binary(Error), null};
-error_info(timeout) ->
- {500, <<"timeout">>, <<"The request could not be processed in a reasonable"
- " amount of time.">>};
-error_info({Error, Reason}) ->
- {500, couch_util:to_binary(Error), couch_util:to_binary(Reason)};
-error_info({Error, nil, _Stack}) ->
- error_info(Error);
-error_info({Error, Reason, _Stack}) ->
- error_info({Error, Reason});
-error_info(Error) ->
- {500, couch_util:to_binary(Error), null}.
-
-error_headers(#httpd{mochi_req=MochiReq}=Req, 401=Code, ErrorStr, ReasonStr) ->
- % this is where the basic auth popup is triggered
- case MochiReq:get_header_value("X-CouchDB-WWW-Authenticate") of
- undefined ->
- case config:get("httpd", "WWW-Authenticate", nil) of
- nil ->
- % If the client is a browser and the basic auth popup isn't turned on
- % redirect to the session page.
- case ErrorStr of
- <<"unauthorized">> ->
- case config:get("couch_httpd_auth", "authentication_redirect", nil) of
- nil -> {Code, []};
- AuthRedirect ->
- case config:get("couch_httpd_auth", "require_valid_user", "false") of
- "true" ->
- % send the browser popup header no matter what if we are require_valid_user
- {Code, [{"WWW-Authenticate", "Basic realm=\"server\""}]};
- _False ->
- % if the accept header matches html, then do the redirect. else proceed as usual.
- Accepts = case MochiReq:get_header_value("Accept") of
- undefined ->
- % According to the HTTP 1.1 spec, if the Accept
- % header is missing, it means the client accepts
- % all media types.
- "html";
- Else ->
- Else
- end,
- case re:run(Accepts, "\\bhtml\\b",
- [{capture, none}, caseless]) of
- nomatch ->
- {Code, []};
- match ->
- AuthRedirectBin = ?l2b(AuthRedirect),
- % Redirect to the path the user requested, not
- % the one that is used internally.
- UrlReturnRaw = case MochiReq:get_header_value("x-couchdb-vhost-path") of
- undefined -> MochiReq:get(path);
- VHostPath -> VHostPath
- end,
- UrlReturn = ?l2b(couch_util:url_encode(UrlReturnRaw)),
- UrlReason = ?l2b(couch_util:url_encode(ReasonStr)),
- {302, [{"Location", couch_httpd:absolute_uri(Req, <<AuthRedirectBin/binary,"?return=",UrlReturn/binary,"&reason=",UrlReason/binary>>)}]}
- end
- end
- end;
- _Else ->
- {Code, []}
- end;
- Type ->
- {Code, [{"WWW-Authenticate", Type}]}
- end;
- Type ->
- {Code, [{"WWW-Authenticate", Type}]}
- end;
-error_headers(_, Code, _, _) ->
- {Code, []}.
-
-send_error(_Req, {already_sent, Resp, _Error}) ->
- {ok, Resp};
-
-send_error(Req, Error) ->
- {Code, ErrorStr, ReasonStr} = error_info(Error),
- {Code1, Headers} = error_headers(Req, Code, ErrorStr, ReasonStr),
- send_error(Req, Code1, Headers, ErrorStr, ReasonStr, json_stack(Error)).
-
-send_error(Req, Code, ErrorStr, ReasonStr) ->
- send_error(Req, Code, [], ErrorStr, ReasonStr, []).
-
-send_error(Req, Code, Headers, ErrorStr, ReasonStr, Stack) ->
- send_json(Req, Code, Headers,
- {[{<<"error">>, ErrorStr},
- {<<"reason">>, ReasonStr} |
- case Stack of [] -> []; _ -> [{stack, Stack}] end
- ]}).
-
-% give the option for list functions to output html or other raw errors
-send_chunked_error(Resp, {_Error, {[{<<"body">>, Reason}]}}) ->
- send_chunk(Resp, Reason),
- send_chunk(Resp, []);
-
-send_chunked_error(Resp, Error) ->
- {Code, ErrorStr, ReasonStr} = error_info(Error),
- JsonError = {[{<<"code">>, Code},
- {<<"error">>, ErrorStr},
- {<<"reason">>, ReasonStr} |
- case json_stack(Error) of [] -> []; Stack -> [{stack, Stack}] end
- ]},
- send_chunk(Resp, ?l2b([$\n,?JSON_ENCODE(JsonError),$\n])),
- send_chunk(Resp, []).
-
-send_redirect(Req, Path) ->
- Headers = [{"Location", chttpd:absolute_uri(Req, Path)}],
- send_response(Req, 301, Headers, <<>>).
-
-server_header() ->
- couch_httpd:server_header().
-
-timing() ->
- case get(body_time) of
- undefined ->
- {"X-CouchDB-Body-Time", "0"};
- Time ->
- {"X-CouchDB-Body-Time", integer_to_list(Time)}
- end.
-
-reqid() ->
- {"X-Couch-Request-ID", get(nonce)}.
-
-json_stack({bad_request, _, _}) ->
- [];
-json_stack({_Error, _Reason, Stack}) ->
- lists:map(fun json_stack_item/1, Stack);
-json_stack(_) ->
- [].
-
-json_stack_item({M,F,A}) ->
- list_to_binary(io_lib:format("~s:~s/~B", [M, F, json_stack_arity(A)]));
-json_stack_item({M,F,A,L}) ->
- case proplists:get_value(line, L) of
- undefined -> json_stack_item({M,F,A});
- Line -> list_to_binary(io_lib:format("~s:~s/~B L~B",
- [M, F, json_stack_arity(A), Line]))
- end;
-json_stack_item(_) ->
- <<"bad entry in stacktrace">>.
-
-json_stack_arity(A) ->
- if is_integer(A) -> A; is_list(A) -> length(A); true -> 0 end.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/e41cfa40/src/chttpd/src/chttpd_app.erl
----------------------------------------------------------------------
diff --git a/src/chttpd/src/chttpd_app.erl b/src/chttpd/src/chttpd_app.erl
deleted file mode 100644
index d7a5aef..0000000
--- a/src/chttpd/src/chttpd_app.erl
+++ /dev/null
@@ -1,21 +0,0 @@
-% 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(chttpd_app).
--behaviour(application).
--export([start/2, stop/1]).
-
-start(_Type, StartArgs) ->
- chttpd_sup:start_link(StartArgs).
-
-stop(_State) ->
- ok.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/e41cfa40/src/chttpd/src/chttpd_config_listener.erl
----------------------------------------------------------------------
diff --git a/src/chttpd/src/chttpd_config_listener.erl b/src/chttpd/src/chttpd_config_listener.erl
deleted file mode 100644
index 4d07b83..0000000
--- a/src/chttpd/src/chttpd_config_listener.erl
+++ /dev/null
@@ -1,80 +0,0 @@
-% 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(chttpd_config_listener).
--behaviour(gen_server).
--behaviour(config_listener).
-
-% public interface
--export([start_link/0]).
-
-% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- code_change/3, terminate/2]).
-
-% config_listener callback
--export([handle_config_change/5]).
-
-start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-
-init([]) ->
- Settings = [
- {bind_address, config:get("chttpd", "bind_address")},
- {port, config:get("chttpd", "port")},
- {backlog, config:get("chttpd", "backlog")},
- {server_options, config:get("chttpd", "server_options")}
- ],
- ok = config:listen_for_changes(?MODULE, Settings),
- {ok, Settings}.
-
-handle_config_change("chttpd", "bind_address", Value, _, Settings) ->
- maybe_replace(bind_address, Value, Settings);
-handle_config_change("chttpd", "port", Value, _, Settings) ->
- maybe_replace(port, Value, Settings);
-handle_config_change("chttpd", "backlog", Value, _, Settings) ->
- maybe_replace(backlog, Value, Settings);
-handle_config_change("chttpd", "server_options", Value, _, Settings) ->
- maybe_replace(server_options, Value, Settings);
-handle_config_change(_, _, _, _, Settings) ->
- {ok, Settings}.
-
-handle_call(_, _, State) ->
- {reply, ignored, State}.
-
-handle_cast(_, State) ->
- {noreply, State}.
-
-handle_info({gen_event_EXIT, {config_listener, ?MODULE}, _Reason}, State) ->
- erlang:send_after(5000, self(), restart_config_listener),
- {noreply, State};
-handle_info(restart_config_listener, State) ->
- ok = config:listen_for_changes(?MODULE, State),
- {noreply, State};
-handle_info(_Msg, State) ->
- {noreply, State}.
-
-terminate(_, _State) ->
- ok.
-
-code_change(_, State, _) ->
- {ok, State}.
-
-% private
-maybe_replace(Key, Value, Settings) ->
- case couch_util:get_value(Key, Settings) of
- Value ->
- {ok, Settings};
- _ ->
- chttpd:stop(),
- {ok, lists:keyreplace(Key, 1, {Key, Value}, Settings)}
- end.