roadrunner_send (roadrunner v0.12.1)

View Source

Send the response from inside the handler, as it runs.

A handler normally returns its response and the connection sends it. A handler that returns {send, Headers, Fun} sends it itself instead: the connection calls Fun(Req) and the fun sends the response with this module's functions, then returns the request.

handle(Req) ->
    {{send, [], fun send/1}, Req}.

send(Req0) ->
    ok = roadrunner_send:inform(103, [{~"link", ~"</app.css>; rel=preload"}], Req0),
    {ok, Body, Req} = roadrunner_req:read_body(Req0),
    ok = roadrunner_send:reply(roadrunner_resp:text(200, Body), Req),
    Req.

Use it when the response can only be decided while the handler runs:

  • inform/3 sends an interim 1xx response (such as 103 Early Hints) before the final one, as many times as needed
  • reply/2 sends a complete response built with roadrunner_resp, and reply/4 one given as its status, headers and body
  • stream/3 sends the status and headers and returns the same Send fun a {stream, ...} response gets, to write the body as it goes
  • file/4 sends a file, as a {sendfile, ...} response does
  • websocket/4 upgrades the connection to a WebSocket session, as a {websocket, Module, State, Opts} response does

The fun sends one final response (reply/2,4, stream/3, file/4 or websocket/4). The calls take the request the fun holds now, so pass the one returned by roadrunner_req:read_body/1,2, and return the last one from the fun, as handle/1 does.

What a call returns and when it raises:

  • ok (or {ok, Send} from stream/3) once the response went out.
  • {error, Reason} when it could not: the transport failed, a header has CR/LF/NUL (unsafe_header), the headers are over the client's HTTP/2 or HTTP/3 limit, file/4's file is missing or shorter than its range, or an upgrade was refused. A final response that could not go out is answered 500 in its place.
  • It raises already_sent for a final response, or an inform/3, after the final response; and not_in_send_fun when called outside the fun's process, such as from a process the fun spawns. The Send fun stream/3 returns is the fun's too: call it from the fun's process.

A fun that exits with {shutdown, Reason} stops the request on purpose, the OTP way: the response ends as far as it went (an open stream is ended, no response at all is answered 500), nothing is logged, and the request reports a normal stop. On HTTP/1.1 the connection then closes, since how much of the body the fun read is unknown; on HTTP/2 and HTTP/3 only the stream ends.

The framework finishes what the fun leaves open. A stream the fun did not end with Send(Data, fin) is ended when the fun returns. A fun that returns without a final response gets a 500 sent for it. A fun that crashes before a final response is answered 500; one that crashes after it closes the connection (HTTP/1.1) or resets the stream (HTTP/2, HTTP/3) when the body was still open. A file that fails to read once its response has started cuts the response the same way.

Headers in the {send, Headers, Fun} tuple is where middlewares add their headers (roadrunner_cors, roadrunner_security_headers). They are added to the final response, unless the call already sets a header with the same name. vary is the exception: both values are kept. Interim responses get only the headers they are given. roadrunner_compress does not compress a response sent this way.

The rules of the other response shapes apply. Header names must be lowercase. A response with a CR, LF or NUL in a header, or (on HTTP/2 and HTTP/3) headers over the client's header size limit, is not sent: the final ones are answered 500 and the call returns an error. A HEAD request gets no body from reply/2,4 or file/4. An interim response to an HTTP/1.0 client is skipped, as RFC 9110 §15.2 asks.

Summary

Types

The fun of a {send, Headers, Fun} response. It gets the request, sends the response with this module's functions, and returns the request.

Functions

Send bytes [Offset, Offset + Length) of a file as the response body, as a {sendfile, ...} response does: the headers must carry its content-length and content-type. Returns ok once it is sent, or {error, Reason} when it could not be. A file that is not there, or is shorter than the range, is answered 500 before anything is sent, and the call returns {error, enoent}, {error, file_too_short} or the like.

Send an interim response: a 1xx status, 102 to 199, ahead of the final response. Returns ok once it is written, or {error, Reason} when it could not be sent.

Send a complete response built with roadrunner_resp, such as roadrunner_resp:json(200, Term). The same as reply/4 with its status, headers and body.

Send a complete response. Returns ok once it is handed to the connection, or {error, Reason} when it could not be sent.

Send the status and headers of a streamed response. Returns {ok, Send} with the roadrunner_handler:send_fun/0 that writes the body, the same fun a {stream, ...} response gets, or {error, Reason} when the head could not be sent.

Upgrade the connection to a WebSocket session run by Module (a roadrunner_ws_handler) from State, with the upgrade options Opts (roadrunner_ws_handler:upgrade_opts/0), as a {websocket, Module, State, Opts} response does. On HTTP/1.1 it sends the 101 and the session takes the connection; the request ends when the session does. Returns ok once the session runs, or {error, Reason} when the upgrade was refused: an invalid handshake (answered 400 or 426), or HTTP/2 and HTTP/3, which answer 501.

Types

sender()

-type sender() :: fun((roadrunner_req:request()) -> roadrunner_req:request()).

The fun of a {send, Headers, Fun} response. It gets the request, sends the response with this module's functions, and returns the request.

Functions

file/4

Send bytes [Offset, Offset + Length) of a file as the response body, as a {sendfile, ...} response does: the headers must carry its content-length and content-type. Returns ok once it is sent, or {error, Reason} when it could not be. A file that is not there, or is shorter than the range, is answered 500 before anything is sent, and the call returns {error, enoent}, {error, file_too_short} or the like.

inform(Status, Headers, Req)

Send an interim response: a 1xx status, 102 to 199, ahead of the final response. Returns ok once it is written, or {error, Reason} when it could not be sent.

reply/2

-spec reply(roadrunner_resp:buffered_response(), roadrunner_req:request()) -> ok | {error, term()}.

Send a complete response built with roadrunner_resp, such as roadrunner_resp:json(200, Term). The same as reply/4 with its status, headers and body.

reply(Status, Headers, Body, Req)

Send a complete response. Returns ok once it is handed to the connection, or {error, Reason} when it could not be sent.

stream(Status, Headers, Req)

Send the status and headers of a streamed response. Returns {ok, Send} with the roadrunner_handler:send_fun/0 that writes the body, the same fun a {stream, ...} response gets, or {error, Reason} when the head could not be sent.

websocket(Module, State, Opts, Req)

-spec websocket(module(), term(), roadrunner_ws_handler:upgrade_opts(), roadrunner_req:request()) ->
                   ok | {error, term()}.

Upgrade the connection to a WebSocket session run by Module (a roadrunner_ws_handler) from State, with the upgrade options Opts (roadrunner_ws_handler:upgrade_opts/0), as a {websocket, Module, State, Opts} response does. On HTTP/1.1 it sends the 101 and the session takes the connection; the request ends when the session does. Returns ok once the session runs, or {error, Reason} when the upgrade was refused: an invalid handshake (answered 400 or 426), or HTTP/2 and HTTP/3, which answer 501.