Anonymous avatar Anonymous committed 7970dd8

Added a Write callable response method.

Body producing functions can return {writer, fun my_callable/1} as the status to indicate that they would like a fun write/1 callable passed to that function. This callable can then be used to write data to the client.

An example:

-module(writefun_resource).
-export([init/1, to_html/2]).

-include_lib("webmachine/include/webmachine.hrl").

init([]) -> {ok, undefined}.

to_html(ReqData, State) ->
MyBody = fun(Write) ->
Write("<html><body><pre>"),
lists:foreach(fun(_) ->
Write("Hello, new world\n"),
timer:sleep(100)
end, lists:seq(1, 10)),
Write("</pre></body></html>")
end,
{{writer, MyBody}, ReqData, State}.

Using a write callable like this allows our body producing functions to avoid the resumable properties currently required by the {stream, Callable} methods.

Comments (0)

Files changed (3)

src/webmachine_decision_core.erl

     case Body of
         {stream, StreamBody} ->
             {stream, make_encoder_stream(Encoder, Charsetter, StreamBody)};
+        {writer, BodyFun} ->
+            {writer, make_writer_stream(Encoder, Charsetter, BodyFun)};
         _ ->
             Encoder(Charsetter(iolist_to_binary(Body)))
     end.
 make_encoder_stream(Encoder, Charsetter, {Body, Next}) ->
     {Encoder(Charsetter(Body)),
      fun() -> make_encoder_stream(Encoder, Charsetter, Next()) end}.
-            
+
+make_writer_stream(Encoder, Charsetter, BodyFun) ->
+    {Encoder, Charsetter, BodyFun}.
+
 choose_encoding(AccEncHdr) ->
     Encs = [Enc || {Enc,_Fun} <- resource_call(encodings_provided)],
     case webmachine_util:choose_encoding(Encs, AccEncHdr) of

src/webmachine_request.erl

     Size = send_chunk(Socket, Data),
     send_stream_body(Socket, Next(), Size + SoFar).
 
+send_writer_body(Socket, {Encoder, Charsetter, BodyFun}) ->
+    put(bytes_written, 0),
+    Writer = fun(Data) ->
+        Size = send_chunk(Socket, Encoder(Charsetter(Data))),
+        put(bytes_written, get(bytes_written) + Size),
+        Size
+    end,
+    try
+        BodyFun(Writer)
+    catch
+        Type:Error ->
+            io:format("Error writing body: {~p, ~p}~n", [Type, Error]),
+            io:format("Stack: ~p~n", [erlang:get_stacktrace()]),
+            exit(normal)
+    end,
+    send_chunk(Socket, <<>>),
+    get(bytes_written).
+
 send_chunk(Socket, Data) ->
     Size = iolist_size(Data),
     send(Socket, mochihex:to_hex(Size)),
 send_response(Code, PassedState=#reqstate{reqdata=RD}) ->
     Body0 = wrq:resp_body(RD),
     {Body,Length} = case Body0 of
-        {stream, StreamBody} -> {StreamBody, chunked};
+        {stream, StreamBody} -> {{stream, StreamBody}, chunked};
+        {writer, WriteBody} -> {{writer, WriteBody}, chunked};
         _ -> {Body0, iolist_size([Body0])}
     end,
     send(PassedState#reqstate.socket,
     FinalLength = case wrq:method(RD) of 
 	'HEAD' -> Length;
 	_ -> 
-            case Length of
-                chunked -> send_stream_body(PassedState#reqstate.socket, Body);
-                _ -> send(PassedState#reqstate.socket, Body), Length
+            case Body of
+                {stream, Body2} ->
+                    send_stream_body(PassedState#reqstate.socket, Body2);
+                {writer, Body2} ->
+                    send_writer_body(PassedState#reqstate.socket, Body2);
+                _ ->
+                    send(PassedState#reqstate.socket, Body),
+                    Length
             end
     end,
     InitLogData = PassedState#reqstate.log_data,
 
 resp_body(_RD = #wm_reqdata{resp_body=undefined}) -> undefined;
 resp_body(_RD = #wm_reqdata{resp_body={stream,X}}) -> {stream,X};
+resp_body(_RD = #wm_reqdata{resp_body={writer,X}}) -> {writer,X};
 resp_body(_RD = #wm_reqdata{resp_body=RespB}) when is_binary(RespB) -> RespB;
 resp_body(_RD = #wm_reqdata{resp_body=RespB}) -> iolist_to_binary(RespB).
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.