mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-15 04:30:25 +00:00
Update the multipart chapter
This commit is contained in:
parent
67c5b057f9
commit
2474ce9d73
1 changed files with 95 additions and 95 deletions
|
@ -2,36 +2,21 @@
|
||||||
== Multipart requests
|
== Multipart requests
|
||||||
|
|
||||||
Multipart originates from MIME, an Internet standard that
|
Multipart originates from MIME, an Internet standard that
|
||||||
extends the format of emails. Multipart messages are a
|
extends the format of emails.
|
||||||
container for parts of any content-type.
|
|
||||||
|
|
||||||
For example, a multipart message may have a part
|
A multipart message is a list of parts. A part contains
|
||||||
containing text and a second part containing an
|
headers and a body. The body of the parts may be
|
||||||
image. This is what allows you to attach files
|
of any media type, and contain text or binary data.
|
||||||
to emails.
|
It is possible for parts to contain a multipart media
|
||||||
|
type.
|
||||||
|
|
||||||
In the context of HTTP, multipart is most often used
|
In the context of HTTP, multipart is most often used
|
||||||
with the `multipart/form-data` content-type. This is
|
with the `multipart/form-data` media type. It is what
|
||||||
the content-type you have to use when you want browsers
|
browsers use to upload files through HTML forms.
|
||||||
to be allowed to upload files through HTML forms.
|
|
||||||
|
|
||||||
Multipart is of course not required for uploading
|
The `multipart/byteranges` is also common. It is the
|
||||||
files, it is only required when you want to do so
|
media type used to send arbitrary bytes from a resource,
|
||||||
through HTML forms.
|
enabling clients to resume downloads.
|
||||||
|
|
||||||
You can read and parse multipart messages using the
|
|
||||||
Req object directly.
|
|
||||||
|
|
||||||
Cowboy defines two functions that allows you to get
|
|
||||||
information about each part and read their contents.
|
|
||||||
|
|
||||||
=== Structure
|
|
||||||
|
|
||||||
A multipart message is a list of parts. Parts may
|
|
||||||
contain either a multipart message or a non-multipart
|
|
||||||
content-type. This allows parts to be arranged in a
|
|
||||||
tree structure, although this is a rare case as far
|
|
||||||
as the Web is concerned.
|
|
||||||
|
|
||||||
=== Form-data
|
=== Form-data
|
||||||
|
|
||||||
|
@ -42,29 +27,24 @@ values and is therefore not fit for uploading files.
|
||||||
|
|
||||||
That's where the `multipart/form-data` content-type
|
That's where the `multipart/form-data` content-type
|
||||||
comes in. When the form is configured to use this
|
comes in. When the form is configured to use this
|
||||||
content-type, the browser will use one part of the
|
content-type, the browser will create a multipart
|
||||||
message for each form field. This means that a file
|
message where each part corresponds to a field on
|
||||||
input field will be sent in its own part, but the
|
the form. For files, it also adds some metadata in
|
||||||
same applies to all other kinds of fields.
|
the part headers, like the file name.
|
||||||
|
|
||||||
A form with a text input, a file input and a select
|
A form with a text input, a file input and a select
|
||||||
choice box will result in a multipart message with
|
choice box will result in a multipart message with
|
||||||
three parts, one for each field.
|
three parts, one for each field.
|
||||||
|
|
||||||
The browser does its best to determine the content-type
|
The browser does its best to determine the media type
|
||||||
of the files it sends this way, but you should not
|
of the files it sends this way, but you should not
|
||||||
rely on it for determining the contents of the file.
|
rely on it for determining the contents of the file.
|
||||||
Proper investigation of the contents is recommended.
|
Proper investigation of the contents is recommended.
|
||||||
|
|
||||||
=== Checking the content-type
|
=== Checking for multipart messages
|
||||||
|
|
||||||
While there is a variety of multipart messages, the
|
The content-type header indicates the presence of
|
||||||
most common on the Web is `multipart/form-data`. It's
|
a multipart message:
|
||||||
the type of message being sent when an HTML form
|
|
||||||
allows uploading files.
|
|
||||||
|
|
||||||
You can quickly figure out if a multipart message
|
|
||||||
has been sent by parsing the `content-type` header.
|
|
||||||
|
|
||||||
[source,erlang]
|
[source,erlang]
|
||||||
----
|
----
|
||||||
|
@ -74,96 +54,116 @@ has been sent by parsing the `content-type` header.
|
||||||
|
|
||||||
=== Reading a multipart message
|
=== Reading a multipart message
|
||||||
|
|
||||||
To read a message you have to iterate over all its
|
Cowboy provides two sets of functions for reading
|
||||||
parts. Then, for each part, you can inspect its headers
|
request bodies as multipart messages.
|
||||||
and read its body.
|
|
||||||
|
The `cowboy_req:read_part/1,2` functions return the
|
||||||
|
next part's headers, if any.
|
||||||
|
|
||||||
|
The `cowboy_req:read_part_body/1,2` functions return
|
||||||
|
the current part's body. For large bodies you may
|
||||||
|
need to call the function multiple times.
|
||||||
|
|
||||||
|
To read a multipart message you need to iterate over
|
||||||
|
all its parts:
|
||||||
|
|
||||||
[source,erlang]
|
[source,erlang]
|
||||||
----
|
----
|
||||||
multipart(Req) ->
|
multipart(Req0) ->
|
||||||
case cowboy_req:part(Req) of
|
case cowboy_req:read_part(Req0) of
|
||||||
{ok, _Headers, Req2} ->
|
{ok, _Headers, Req1} ->
|
||||||
{ok, _Body, Req3} = cowboy_req:part_body(Req2),
|
{ok, _Body, Req} = cowboy_req:read_part_body(Req1),
|
||||||
multipart(Req3);
|
multipart(Req);
|
||||||
{done, Req2} ->
|
{done, Req} ->
|
||||||
Req2
|
Req
|
||||||
end.
|
end.
|
||||||
----
|
----
|
||||||
|
|
||||||
Parts do not have a size limit. When a part body is
|
When part bodies are too large, Cowboy will return
|
||||||
too big, Cowboy will return what it read so far and
|
a `more` tuple, and allow you to loop until the part
|
||||||
allow you to continue if you wish to do so.
|
body has been fully read.
|
||||||
|
|
||||||
The function `cow_multipart:form_data/1` can be used
|
The function `cow_multipart:form_data/1` can be used
|
||||||
to quickly obtain information about a part from a
|
to quickly obtain information about a part from a
|
||||||
`multipart/form-data` message. This function will
|
`multipart/form-data` message. The function returns
|
||||||
tell you if the part is for a normal field or if it
|
a `data` or a `file` tuple depending on whether this
|
||||||
is a file being uploaded.
|
is a normal field or a file being uploaded.
|
||||||
|
|
||||||
This can be used for example to allow large part bodies
|
The following snippet will use this function and
|
||||||
for files but crash when a normal field is too large.
|
use different strategies depending on whether the
|
||||||
|
part is a file:
|
||||||
|
|
||||||
[source,erlang]
|
[source,erlang]
|
||||||
----
|
----
|
||||||
multipart(Req) ->
|
multipart(Req0) ->
|
||||||
case cowboy_req:part(Req) of
|
case cowboy_req:read_part(Req0) of
|
||||||
{ok, Headers, Req2} ->
|
{ok, Headers, Req1} ->
|
||||||
Req4 = case cow_multipart:form_data(Headers) of
|
Req = case cow_multipart:form_data(Headers) of
|
||||||
{data, _FieldName} ->
|
{data, _FieldName} ->
|
||||||
{ok, _Body, Req3} = cowboy_req:part_body(Req2),
|
{ok, _Body, Req2} = cowboy_req:read_part_body(Req1),
|
||||||
Req3;
|
Req2;
|
||||||
{file, _FieldName, _Filename, _CType, _CTransferEncoding} ->
|
{file, _FieldName, _Filename, _CType, _CTransferEncoding} ->
|
||||||
stream_file(Req2)
|
stream_file(Req1)
|
||||||
end,
|
end,
|
||||||
multipart(Req4);
|
multipart(Req);
|
||||||
{done, Req2} ->
|
{done, Req} ->
|
||||||
Req2
|
Req
|
||||||
end.
|
end.
|
||||||
|
|
||||||
stream_file(Req) ->
|
stream_file(Req0) ->
|
||||||
case cowboy_req:part_body(Req) of
|
case cowboy_req:read_part_body(Req0) of
|
||||||
{ok, _Body, Req2} ->
|
{ok, _Body, Req} ->
|
||||||
Req2;
|
Req;
|
||||||
{more, _Body, Req2} ->
|
{more, _Body, Req} ->
|
||||||
stream_file(Req2)
|
stream_file(Req)
|
||||||
end.
|
end.
|
||||||
----
|
----
|
||||||
|
|
||||||
By default the body chunk Cowboy will return is limited
|
Both the part header and body reading functions can take
|
||||||
to 8MB. This can of course be overriden. Both functions
|
options that will be given to the request body reading
|
||||||
can take a second argument, the same list of options that
|
functions. By default, `cowboy_req:read_part/1` reads
|
||||||
will be passed to `cowboy_req:body/2` function.
|
up to 64KB for up to 5 seconds. `cowboy_req:read_part_body/1`
|
||||||
|
has the same defaults as `cowboy_req:read_body/1`.
|
||||||
|
|
||||||
|
To change the defaults for part headers:
|
||||||
|
|
||||||
|
[source,erlang]
|
||||||
|
cowboy_req:read_part(Req, #{length => 128000}).
|
||||||
|
|
||||||
|
And for part bodies:
|
||||||
|
|
||||||
|
[source,erlang]
|
||||||
|
cowboy_req:read_part_body(Req, #{length => 1000000, period => 7000}).
|
||||||
|
|
||||||
=== Skipping unwanted parts
|
=== Skipping unwanted parts
|
||||||
|
|
||||||
If you do not want to read a part's body, you can skip it.
|
Part bodies do not have to be read. Cowboy will automatically
|
||||||
Skipping is easy. If you do not call the function to read
|
skip it when you request the next part's body.
|
||||||
the part's body, Cowboy will automatically skip it when
|
|
||||||
you request the next part.
|
|
||||||
|
|
||||||
The following snippet reads all part headers and skips
|
The following snippet reads all part headers and skips
|
||||||
all bodies:
|
all bodies:
|
||||||
|
|
||||||
[source,erlang]
|
[source,erlang]
|
||||||
----
|
----
|
||||||
multipart(Req) ->
|
multipart(Req0) ->
|
||||||
case cowboy_req:part(Req) of
|
case cowboy_req:part(Req0) of
|
||||||
{ok, _Headers, Req2} ->
|
{ok, _Headers, Req} ->
|
||||||
multipart(Req2);
|
multipart(Req);
|
||||||
{done, Req2} ->
|
{done, Req} ->
|
||||||
Req2
|
Req
|
||||||
end.
|
end.
|
||||||
----
|
----
|
||||||
|
|
||||||
Similarly, if you start reading the body and it ends up
|
Similarly, if you start reading the body and it ends up
|
||||||
being too big, you can simply continue with the next part,
|
being too big, you can simply continue with the next part.
|
||||||
Cowboy will automatically skip what remains.
|
Cowboy will automatically skip what remains.
|
||||||
|
|
||||||
Note that the skipping rate may not be adequate for your
|
While Cowboy can skip part bodies automatically, the read
|
||||||
application. If you observe poor performance when skipping,
|
rate is not configurable. Depending on your application
|
||||||
you might want to consider manually skipping by calling
|
you may want to skip manually, in particular if you observe
|
||||||
the `cowboy_req:part_body/1` function directly.
|
poor performance while skipping.
|
||||||
|
|
||||||
And if you started reading the message but decide that you
|
You do not have to read all parts either. You can stop
|
||||||
do not need the remaining parts, you can simply stop reading
|
reading as soon as you find the data you need.
|
||||||
entirely and Cowboy will automatically figure out what to do.
|
|
||||||
|
// @todo Cover the building of multipart messages.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue