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