Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add close_boundary option to write closing boundary on multipart writer #3106

Merged
merged 6 commits into from
Jun 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/3104.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `close_boundary` option in `MultipartWriter.write` method. Support streaming
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ Thanos Lefteris
Thijs Vermeir
Thomas Grainger
Tolga Tezel
Trinh Hoang Nhu
Vadim Suharnikov
Vaibhav Sagar
Vamsi Krishna Avula
Expand Down
5 changes: 3 additions & 2 deletions aiohttp/multipart.py
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ def size(self):
total += 2 + len(self._boundary) + 4 # b'--'+self._boundary+b'--\r\n'
return total

async def write(self, writer):
async def write(self, writer, close_boundary=True):
"""Write body."""
if not self._parts:
return
Expand All @@ -818,7 +818,8 @@ async def write(self, writer):

await writer.write(b'\r\n')

await writer.write(b'--' + self._boundary + b'--\r\n')
if close_boundary:
await writer.write(b'--' + self._boundary + b'--\r\n')


class MultipartPayloadWriter:
Expand Down
21 changes: 21 additions & 0 deletions docs/multipart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,27 @@ Please note, that on :meth:`MultipartWriter.write` all the file objects
will be read until the end and there is no way to repeat a request without
rewinding their pointers to the start.

Example MJPEG Streaming ``multipart/x-mixed-replace``. By default
:meth:`MultipartWriter.write` appends closing ``--boundary--`` and breaks your
content. Providing `close_boundary = False` prevents this.::

my_boundary = 'some-boundary'
response = web.StreamResponse(
status=200,
reason='OK',
headers={
'Content-Type': 'multipart/x-mixed-replace;boundary=--%s' % my_boundary
}
)
while True:
frame = get_jpeg_frame()
with MultipartWriter('image/jpeg', boundary=my_boundary) as mpwriter:
mpwriter.append(frame, {
'Content-Type': 'image/jpeg'
})
await mpwriter.write(response, close_boundary=False)
await response.drain()

Hacking Multipart
-----------------

Expand Down
12 changes: 10 additions & 2 deletions docs/multipart_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ Multipart reference
Returns the next body part reader.


.. class:: MultipartWriter(subtype='mixed', boundary=None)
.. class:: MultipartWriter(subtype='mixed', boundary=None, close_boundary=True)

Multipart body writer.

Expand Down Expand Up @@ -191,6 +191,14 @@ Multipart reference

Size of the payload.

.. comethod:: write(writer)
.. comethod:: write(writer, close_boundary=True)

Write body.

:param bool close_boundary: The (:class:`bool`) that will emit
boundary closing. You may want to disable
when streaming (``multipart/x-mixed-replace``)

.. versionadded:: 3.4

Support ``close_boundary`` argument.
34 changes: 34 additions & 0 deletions tests/test_multipart.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,40 @@ async def test_writer_write(buf, stream, writer):
b'--:--\r\n') == bytes(buf))


async def test_writer_write_no_close_boundary(buf, stream):
writer = aiohttp.MultipartWriter(boundary=':')
writer.append('foo-bar-baz')
writer.append_json({'test': 'passed'})
writer.append_form({'test': 'passed'})
writer.append_form([('one', 1), ('two', 2)])
await writer.write(stream, close_boundary=False)

assert (
(b'--:\r\n'
b'Content-Type: text/plain; charset=utf-8\r\n'
b'Content-Length: 11\r\n\r\n'
b'foo-bar-baz'
b'\r\n'

b'--:\r\n'
b'Content-Type: application/json\r\n'
b'Content-Length: 18\r\n\r\n'
b'{"test": "passed"}'
b'\r\n'

b'--:\r\n'
b'Content-Type: application/x-www-form-urlencoded\r\n'
b'Content-Length: 11\r\n\r\n'
b'test=passed'
b'\r\n'

b'--:\r\n'
b'Content-Type: application/x-www-form-urlencoded\r\n'
b'Content-Length: 11\r\n\r\n'
b'one=1&two=2'
b'\r\n') == bytes(buf))


async def test_writer_serialize_with_content_encoding_gzip(buf, stream,
writer):
writer.append('Time to Relax!', {CONTENT_ENCODING: 'gzip'})
Expand Down