Skip to content

Commit

Permalink
Add close_boundary option to write closing boundary on multipart writ…
Browse files Browse the repository at this point in the history
…er (#3106)
  • Loading branch information
james4388 authored and asvetlov committed Jun 27, 2018
1 parent 3a65252 commit 5359dbe
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 4 deletions.
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

0 comments on commit 5359dbe

Please sign in to comment.