Previously...
- I decided to change the plan.
- I discovered that my new plan would not work
- I decided to use a new, new plan.
So back to the existing code. The function I left off with was handle_http_put:
handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
case http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) of
{error, not_exists} ->
?DEBUG("no session associated with sid: ~p", [Sid]),
{404, ?HEADER, ""};
{{error, Reason}, Sess} ->
?DEBUG("Error on HTTP put. Reason: ~p", [Reason]),
handle_http_put_error(Reason, Sess);
{{repeat, OutPacket}, Sess} ->
?DEBUG("http_put said 'repeat!' ...~nOutPacket: ~p", [OutPacket]),
send_outpacket(Sess, OutPacket);
{{wait, Pause}, _Sess} ->
?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]),
timer:sleep(Pause),
handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize,
StreamStart, IP);
{buffered, _Sess} ->
{200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"};
{ok, Sess} ->
prepare_response(Sess, Rid, [], StreamStart)
end.
This function seems to be more of an administrator rather than a worker: it handles various cases and delegates action to other functions. An interesting aspect is that functions like handle_http_put_error and send_outpacket would need to return immediately instead of waiting, since in those cases the data to be delivered is readily apparent.
Here is the code for http_put:
http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
?DEBUG("Looking for session: ~p", [Sid]),
case mnesia:dirty_read({http_bind, Sid}) of
[] -> {error, not_exists};
[#http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess] ->
NewStream =
case StreamStart of
true -> {To, StreamVersion};
_ -> ""
end,
{gen_fsm:sync_send_all_state_event(FsmRef, #http_put{rid = Rid, attrs = Attrs,
payload = Payload, payload_size = PayloadSize, hold = Hold,
stream = NewStream, ip = IP}, 30000), Sess}
end.
I have to remember that this function does not try to generate something that the HTTP server can understand --- it leaves that up to the caller. In the situation where a session exists for the request, the gen_fsm:sync_send_all_state_event call is used. This call blocks until a response is received.
The blocking call makes sense in the context of the synchronous nature of the BOSH protocol: the synchronous call does not return until either some data is ready for the client or a timeout occurs.
Backtracking to handle_http_put, here is the handle_http_put_error function:
handle_http_put_error(Reason, #http_bind{pid=FsmRef, version=Version})
when Version >= 0 ->
gen_fsm:sync_send_all_state_event(FsmRef, {stop, {put_error,Reason}}),
case Reason of
not_exists ->
{200, ?HEADER,
xml:element_to_binary(
{xmlelement, "body",
[{"xmlns", ?NS_HTTP_BIND},
{"type", "terminate"},
{"condition", "item-not-found"}], []})};
bad_key ->
{200, ?HEADER,
xml:element_to_binary(
{xmlelement, "body",
[{"xmlns", ?NS_HTTP_BIND},
{"type", "terminate"},
{"condition", "item-not-found"}], []})};
polling_too_frequently ->
{200, ?HEADER,
xml:element_to_binary(
{xmlelement, "body",
[{"xmlns", ?NS_HTTP_BIND},
{"type", "terminate"},
{"condition", "policy-violation"}], []})}
end;
handle_http_put_error(Reason, #http_bind{pid=FsmRef}) ->
gen_fsm:sync_send_all_state_event(FsmRef,{stop, {put_error_no_version, Reason}}),
case Reason of
not_exists -> %% bad rid
?DEBUG("Closing HTTP bind session (Bad rid).", []),
{404, ?HEADER, ""};
bad_key ->
?DEBUG("Closing HTTP bind session (Bad key).", []),
{404, ?HEADER, ""};
polling_too_frequently ->
?DEBUG("Closing HTTP bind session (User polling too frequently).", []),
{403, ?HEADER, ""}
end.
There are two versions of the method: one for when the version supplied is greater than or equal to zero, and then the other version when that is not the case. Not sure how erlang would handle the situation where the version is not defined, but I'm guessing it would just take the second pattern instead of the first.
In either case, the function tells the session FSM to shut down (the sync_send_all_state_event) before creating something that the HTTP server can understand and sending it back.
Backtracking again, I noticed that the code for send_outpacket is pretty involved, I'll save that one for a separate post. Instead, I will backtrack to prepare_response:
prepare_response(Sess, Rid, OutputEls, StreamStart) ->
receive after Sess#http_bind.process_delay -> ok end,
case catch http_get(Sess, Rid) of
{ok, cancel} ->
{200, ?HEADER, "<body type='error' xmlns='"++?NS_HTTP_BIND++"'/>"};
{ok, empty} ->
{200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"};
{ok, terminate} ->
{200, ?HEADER, "<body type='terminate' xmlns='"++?NS_HTTP_BIND++"'/>"};
{ok, ROutPacket} ->
OutPacket = lists:reverse(ROutPacket),
prepare_outpacket_response(Sess, Rid, OutputEls++OutPacket, StreamStart);
{'EXIT', {shutdown, _}} ->
{200, ?HEADER, "<body type='terminate' condition='system-shutdown' xmlns='"++?NS_HTTP_BIND++"'/>"};
{'EXIT', _Reason} ->
{200, ?HEADER, "<body type='terminate' xmlns='"++?NS_HTTP_BIND++"'/>"}
end.
OK, I don't understand why this bit of code is here:
receive after Sess#http_bind.process_delay -> ok end,
This basically causes the erlang process to pause for the amount of time in Session.process_delay. But I was assuming that the call the sync_send_all_state_event in http_put would wait for whatever period of time was needed, so why the second wait? More mysteries...
The code to format the different responses seems straight-forward enough, teh call to http_get, however, requires more exploration. Unfortunately, that function, like send_outpacket, is complex so I will save that one for the next post.
So next time: