The following pertains to receiving data from the server. For sending data to the server, the client merely does an HTTP POST and holds the connection open without sending data.
My working theory is that the BOSH/http-bind module is registered with the ejabberd_http module in order to receive HTTP POST events from the user's chat client. A new process is created for each POST.
Consider the following code:
process([], #request{method = 'POST',
data = Data,
ip = IP}) ->
?DEBUG("Incoming data: ~s", [Data]),
ejabberd_http_bind:process_request(Data, IP);
So it would seem that the first thing that happens when a new HTTP POST is received is that ejabberd_http_bind:process_request is called. Consider the following comment from mod_http_fileserver:
% @spec (LocalPath, Request) -> {HTTPCode::integer(), [Header], Page::string()}
So it looks like, when the process function returns, it is expected to return the HTML for the page requested. In our case, it should return the XML for the BOSH response.
To help facilitate my understanding of what is going on, I broke out the code that I am dealing with from one large function into several smaller ones. Here is the revised version:
%% Entry point for data coming from client through ejabberd HTTP server:
process_request(Data, IP) ->
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
Opts = [{xml_socket, true} | Opts1],
MaxStanzaSize =
case lists:keysearch(max_stanza_size, 1, Opts) of
{value, {_, Size}} -> Size;
_ -> infinity
end,
PayloadSize = iolist_size(Data),
case catch parse_request(Data, PayloadSize, MaxStanzaSize) of
%% No existing session:
{ok, {"", Rid, Attrs, Payload}} ->
process_no_existing_session(Rid, Attrs, Payload, PayloadSize, IP);
%% Existing session
{ok, {Sid, Rid, Attrs, Payload}} ->
process_existing_session (Sid, Rid, Attrs, Payload, PayloadSize, IP);
%% error the packet is too large
{size_limit, Sid} -> process_stanza_too_large(Sid);
%% unrecognized request --- return an error but keep the session going
_ ->
?DEBUG("Received bad request: ~p", [Data]),
{400, ?HEADER, ""}
end.
Not sure what the business with the {xml_socket, true} is doing, because the Opts variable never seems to get passed to anyone. The stuff that gets MaxStanzaSize seems to be getting the max stanza size for later use in the call to parse_request. Next we have the four cases:
%% No existing session:
{ok, {"", Rid, Attrs, Payload}} ->
process_no_existing_session(Rid, Attrs, Payload, PayloadSize, IP);
In the first case, we have a valid request that does not contain a session ID. This results in one of the break-out functions that I created:
process_no_existing_session (Rid, Attrs, Payload, PayloadSize, IP) ->
case xml:get_attr_s("to",Attrs) of
"" ->
%%
%% no "to" attribute, we cannot process this one so return an error
%%
?DEBUG("Session not created (Improper addressing)", []),
{200, ?HEADER,
"<body type='terminate' condition='improper-addressing' "
"xmlns='" ++ ?NS_HTTP_BIND ++ "'/>"};
XmppDomain ->
%%
%% create new session
%%
Sid = sha:sha(term_to_binary({now(), make_ref()})),
case start(XmppDomain, Sid, "", IP) of
%%
%% error trying to create the new session, tell the user we
%% cannot allow them to connect
%%
{error, _} ->
{200, ?HEADER,
"<body type='terminate' condition='internal-server-error' "
"xmlns='" ++ ?NS_HTTP_BIND ++ "'>BOSH module not started</body>"};
%%
%% created new session, send them the usual stuff that one
%% expects when connecting to a BOSH server
%%
{ok, Pid} ->
handle_session_start(Pid, XmppDomain, Sid, Rid,
Attrs, Payload, PayloadSize, IP)
end
end.
A request that contains no session ID generally means that a client wants to start a new session. For this to be the case, the "to" attribute should be present in the XMPP stanza. If not, then we treat the request as an error, otherwise we try to create the new session.
%% Existing session
{ok, {Sid, Rid, Attrs, Payload}} ->
process_existing_session (Sid, Rid, Attrs, Payload, PayloadSize, IP);
The next case from the original process_request function deals with requests for existing sessions, or at least requests that have a SID attribute. This becomes the second break-out function:
rocess_existing_session (Sid, Rid, Attrs, Payload, PayloadSize, IP) ->
%%
%% check for a restart connection
%%
StreamStart =
case xml:get_attr_s("xmpp:restart",Attrs) of
"true" -> true;
_ -> false
end,
%%
%% if the request is to termiante the stream, then add /stream:stream
%% to whatever content the client sent us.
%%
Payload2 =
case xml:get_attr_s("type",Attrs) of
"terminate" ->
%% close stream
Payload ++ [{xmlstreamend, "stream:stream"}];
_ ->
Payload
end,
%%
%% process the request and return the results to the client
%%
handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
StreamStart, IP).
This function checks for a couple of special cases (restart and terminate) and then passes the request on to handle_http_put.
The next case from the process_request function has to do with requests that are too long:
%% error the packet is too large
{size_limit, Sid} -> process_stanza_too_large(Sid);
The breakout for this case simply sends an error message. If the request pertained to a recognized session, then the session is closed as well:
process_stanza_too_large (Sid) ->
case mnesia:dirty_read({http_bind, Sid}) of
%%
%% the supplied session is unrecognized, return a 404
%%
[] -> {404, ?HEADER, ""};
%%
%% the session exists. Terminate the session and tell the client why.
%%
[#http_bind{pid = FsmRef}] ->
gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}),
{200, ?HEADER, "<body type='terminate' condition='undefined-condition' "
"xmlns='" ++ ?NS_HTTP_BIND ++ "'>Request Too Large</body>"}
end.
The final case is one that catches everything else.
%% unrecognized request --- return an error but keep the session going
_ ->
?DEBUG("Received bad request: ~p", [Data]),
{400, ?HEADER, ""}
This was short enough that it did not make sense to break it out into another function.
That's enough for one post. Next time I am going to dig into handle_http_put.
No comments:
Post a Comment