From c2beff712695199f0189906615146d2770843dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 10 Apr 2017 16:37:01 +0200 Subject: Initial cowboy_stream manual --- doc/src/manual/cowboy_app.asciidoc | 3 +- doc/src/manual/cowboy_stream.asciidoc | 392 ++++++++++++++++++++++++++++++++++ 2 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 doc/src/manual/cowboy_stream.asciidoc diff --git a/doc/src/manual/cowboy_app.asciidoc b/doc/src/manual/cowboy_app.asciidoc index 29c4497..4c5ad69 100644 --- a/doc/src/manual/cowboy_app.asciidoc +++ b/doc/src/manual/cowboy_app.asciidoc @@ -34,6 +34,7 @@ Handlers: * link:man:cowboy_static(3)[cowboy_static(3)] - Static file handler // @todo What about cowboy_stream_h? +// @todo cowboy_compress_h Behaviors: @@ -41,7 +42,7 @@ Behaviors: * link:man:cowboy_loop(3)[cowboy_loop(3)] - Loop handlers * link:man:cowboy_middleware(3)[cowboy_middleware(3)] - Middlewares * link:man:cowboy_rest(3)[cowboy_rest(3)] - REST handlers -// @todo * link:man:cowboy_stream(3)[cowboy_stream(3)] - Stream handlers +* link:man:cowboy_stream(3)[cowboy_stream(3)] - Stream handlers // @todo * link:man:cowboy_sub_protocol(3)[cowboy_sub_protocol(3)] - Sub protocols * link:man:cowboy_websocket(3)[cowboy_websocket(3)] - Websocket handlers diff --git a/doc/src/manual/cowboy_stream.asciidoc b/doc/src/manual/cowboy_stream.asciidoc new file mode 100644 index 0000000..d332752 --- /dev/null +++ b/doc/src/manual/cowboy_stream.asciidoc @@ -0,0 +1,392 @@ += cowboy_stream(3) + +== Name + +cowboy_handler - Stream handlers + +== Description + +The module `cowboy_stream` defines a callback interface +and a protocol for handling HTTP streams. + +An HTTP request and its associated response is called +a stream. A connection may have many streams. In HTTP/1.1 +they are executed sequentially, while in HTTP/2 they are +executed concurrently. + +Cowboy calls the stream handler for nearly all events +related to a stream. Exceptions vary depending on the +protocol. + +== Callbacks + +Stream handlers must implement the following interface: + +[source,erlang] +---- +init(StreamID, Req, Opts) -> {Commands, State} +data(StreamID, IsFin, Data, State) -> {Commands, State} +info(StreamID, Info, State) -> {Commands, State} +terminate(StreamID, Reason, State) -> any() +early_error(StreamID, Reason, PartialReq, Resp, Opts) -> Resp + +StreamID :: cowboy_stream:streamid() +Req :: cowboy_req:req() +Opts :: cowboy:opts() +Commands :: cowboy_stream:commands() +State :: any() +IsFin :: cowboy_stream:fin() +Data :: binary() +Info :: any() +Reason :: cowboy_stream:reason() +PartialReq - cowboy_req:req(), except all fields are optional +Resp :: cowboy_stream:resp_command() +---- + +HTTP/1.1 will initialize a stream only when the request-line +and all headers have been received. When errors occur before +that point Cowboy will call the callback `early_error/5` +with a partial request, the error reason and the response +Cowboy intends to send. All other events go throuh the +stream handler using the normal callbacks. + +HTTP/2 will initialize the stream when the `HEADERS` block has +been fully received and decoded. Any protocol error occuring +before that will not result in a response being sent and +will therefore not go through the stream handler. In addition +Cowboy may terminate streams without sending an HTTP response +back. + +The stream is initialized by calling `init/3`. All streams +that are initialized will eventually be terminated by calling +`terminate/3`. + +When Cowboy receives data for the stream it will call `data/4`. +The data given is the request body after any transfer decoding +has been applied. + +When Cowboy receives a message addressed to a stream, or when +Cowboy needs to inform the stream handler that an internal +event has occurred, it will call `info/3`. + +[[commands]] +== Commands + +Stream handlers can return a list of commands to be executed +from the `init/3`, `data/4` and `info/3` callbacks. In addition, +the `early_error/5` callback must return a response command. + +// @todo We need a 'log' command that would call error_logger. +// It's better than doing in the handlers directly because +// then we can have other stream handlers manipulate those logs. + +// @todo We need a command to send a message so that other +// stream handlers can manipulate these messages if necessary. + +The following commands are defined: + +[[response_command]] +=== response + +Send a response to the client. + +[source,erlang] +---- +{response, cowboy:http_status(), cowboy:http_headers(), + cowboy_req:resp_body()} +---- + +No more data can be sent after this command. + +[[headers_command]] +=== headers + +Initiate a response to the client. + +[source,erlang] +---- +{headers, cowboy:http_status(), cowboy:http_headers()} +---- + +This initiates a response to the client. The stream +will end when a data command with the `fin` flag is +returned. + +[[data_command]] +=== data + +Send data to the client. + +[source,erlang] +---- +{data, fin(), iodata()} +---- + +[[push_command]] +=== push + +Push a resource to the client. + +[source,erlang] +---- +{push, Method, Scheme, Host, inet:port_number(), + Path, Qs, cowboy:http_headers()} + +Method = Scheme = Host = Path = Qs = binary() +---- + +The command will be ignored if the protocol does not provide +any server push mechanism. + +=== flow + +TODO + +=== spawn + +Inform Cowboy that a process was spawned and should be +supervised. + +[source,erlang] +---- +{spawn, pid(), timeout()} +---- + +=== error_response + +Send an error response if no response was sent previously. + +[source,erlang] +---- +{error_response, cowboy:http_status(), cowboy:http_headers(), iodata()} +---- + +[[switch_protocol_command]] +=== switch_protocol + +Switch to a different protocol. + +[source,erlang] +---- +{switch_protocol, cowboy:http_headers(), module(), state()} +---- + +Contains the headers that will be sent in the 101 response, +along with the module implementing the protocol we are +switching to and its initial state. + +=== stop + +Stop the stream. + +[source,erlang] +---- +stop +---- + +While no more data can be sent after the `fin` flag was set, +the stream is still tracked by Cowboy until it is stopped by +the handler. + +The behavior when stopping a stream for which no response +has been sent will vary depending on the protocol. The stream +will end successfully as far as the client is concerned. + +To indicate that an error occurred, either use `error_response` +before stopping, or use `internal_error`. + +=== internal_error + +Stop the stream with an error. + +[source,erlang] +---- +{internal_error, Reason, HumanReadable} + +Reason = any() +HumanReadable = atom() +---- + +This command should be used when the stream cannot continue +because of an internal error. An `error_response` command +may be sent before that to advertise to the client why the +stream is dropped. + +== Predefined events + +Cowboy will forward all messages sent to the stream to +the `info/3` callback. To send a message to a stream, +send a message to the connection process with the form +`{{Pid, StreamID}, Msg}`. The connection process will +then forward `Msg` to the stream handlers. + +Cowboy will also forward the exit signals for the +processes that the stream spawned. + +=== EXIT + +//info(_StreamID, {'EXIT', Pid, normal}, State=#state{pid=Pid}) -> +//info(_StreamID, {'EXIT', Pid, {_Reason, [_, {cow_http_hd, _, _, _}|_]}}, State=#state{pid=Pid}) -> +//info(StreamID, Exit = {'EXIT', Pid, {Reason, Stacktrace}}, State=#state{ref=Ref, pid=Pid}) -> + +A process spawned by this stream has exited. + +[source,erlang] +---- +{'EXIT', pid(), any()} +---- + +This is the raw exit message without any modification. + +// === read_body +// +// //info(_StreamID, {read_body, Ref, Length, _}, +// //info(StreamID, {read_body, Ref, Length, Period}, State) -> +// +// TODO yeah I am not actually sure this one should be public just yet +// TODO if it is, then we probably shouldn't send a message directly, +// TODO but rather return a command that will end up sending the message +// +// TODO The problem being that no stream handler has access to that +// TODO message if we send it directly. So we should have a command +// TODO send_message or something that can be seen from all handlers. +// +// TODO The thing is that stream handlers can have 0 to N processes +// TODO so we have to make it easy to say which process should +// TODO receive the message, and perhaps *identify* which process +// TODO gets it? + +=== response + +Same as the xref:response_command[response command]. + +Usually sent when the request process replies to the client. +May also be sent by Cowboy internally. + +=== headers + +Same as the xref:headers_command[headers command]. + +Sent when the request process starts replying to the client. + +=== data + +Same as the xref:data_command[data command]. + +Sent when the request process streams data to the client. + +=== push + +Same as the xref:push_command[push command]. + +Sent when the request process pushes a resource to the client. + +=== switch_protocol + +Same as the xref:switch_protocol_command[switch_protocol command]. + +// @todo Not done for HTTP/2 yet. +Sent when switching to the HTTP/2 or Websocket protocol. + +== Exports + +The following function should be called by modules implementing +stream handlers to execute the next stream handler in the list: + +* link:man:cowboy_stream:init(3)[cowboy_stream:init(3)] - Initialize a stream +* link:man:cowboy_stream:data(3)[cowboy_stream:data(3)] - Handle data for a stream +* link:man:cowboy_stream:info(3)[cowboy_stream:info(3)] - Handle a message for a stream +* link:man:cowboy_stream:terminate(3)[cowboy_stream:terminate(3)] - Terminate a stream +* link:man:cowboy_stream:early_error(3)[cowboy_stream:early_error(3)] - Handle an early error for a stream + +== Types + +=== commands() + +[source,erlang] +---- +commands() :: [Command] +---- + +See the xref:commands[list of commands] for details. + +=== fin() + +[source,erlang] +---- +fin() :: fin | nofin +---- + +Used in commands and events to indicate that this is +the end of the stream. + +=== partial_req() + +[source,erlang] +---- +req() :: #{ + method => binary(), %% case sensitive + version => cowboy:http_version() | atom(), + scheme => binary(), %% lowercase; case insensitive + host => binary(), %% lowercase; case insensitive + port => inet:port_number(), + path => binary(), %% case sensitive + qs => binary(), %% case sensitive + headers => cowboy:http_headers(), + peer => {inet:ip_address(), inet:port_number()} +} +---- + +Partial request information received when an early error is +detected. + +=== reason() + +[source,erlang] +---- +reason() :: normal + | {internal_error, timeout | {error | exit | throw, any()}, HumanReadable} + | {socket_error, closed | atom(), HumanReadable} + | {stream_error, Error, HumanReadable} + | {connection_error, Error, HumanReadable} + | {stop, cow_http2:frame(), HumanReadable} + +Error = atom() +HumanReadable = atom() +---- + +Reason for the stream termination. + +=== resp_command() + +[source,erlang] +---- +resp_command() :: {response, cowboy:http_status(), + cowboy:http_headers(), cowboy_req:resp_body()} +---- + +See the xref:response_command[response command] for details. + +=== streamid() + +[source,erlang] +---- +streamid() :: any() +---- + +The identifier for this stream. + +The identifier is unique over the connection process. +It is possible to form a unique identifier node-wide and +cluster-wide by wrapping it in a `{self(), StreamID}` +tuple. + +== Changelog + +* *2.0*: Module introduced. + +== See also + +link:man:cowboy(7)[cowboy(7)], +link:man:cowboy_http(3)[cowboy_http(3)], +link:man:cowboy_http2(3)[cowboy_http2(3)] -- cgit v1.2.3