Repository: aio-libs/aiohttp

Issue:
request.post() should raise HTTPRequestEntityTooLarge same as request.read()
`request.read()`

https://github.com/aio-libs/aiohttp/blob/0bc7d52ccdcc8014293130bb5be0a7075cf1c8bc/aiohttp/web_request.py#L526-L528

`request.post()`

https://github.com/aio-libs/aiohttp/blob/0bc7d52ccdcc8014293130bb5be0a7075cf1c8bc/aiohttp/web_request.py#L583-L585

Seems inconsistent to me that the two methods behave differently. `Request.post()` should raise `HTTPRequestEntityTooLarge` too. 

Or am I missing something?


Discussion:
Agree.
Perhaps it is a migration artifact.
@samuelcolvin would you provide a fix?

Resolving patch:
diff --git a/aiohttp/web_exceptions.py b/aiohttp/web_exceptions.py
--- a/aiohttp/web_exceptions.py
+++ b/aiohttp/web_exceptions.py
@@ -253,6 +253,14 @@ class HTTPPreconditionFailed(HTTPClientError):
 class HTTPRequestEntityTooLarge(HTTPClientError):
     status_code = 413
 
+    def __init__(self, max_size, actual_size, **kwargs):
+        kwargs.setdefault(
+            'text',
+            'Maximum request body size {} exceeded, '
+            'actual body size {}'.format(max_size, actual_size)
+        )
+        super().__init__(**kwargs)
+
 
 class HTTPRequestURITooLong(HTTPClientError):
     status_code = 414
diff --git a/aiohttp/web_request.py b/aiohttp/web_request.py
--- a/aiohttp/web_request.py
+++ b/aiohttp/web_request.py
@@ -529,9 +529,13 @@ async def read(self) -> bytes:
             while True:
                 chunk = await self._payload.readany()
                 body.extend(chunk)
-                if self._client_max_size \
-                        and len(body) >= self._client_max_size:
-                    raise HTTPRequestEntityTooLarge
+                if self._client_max_size:
+                    body_size = len(body)
+                    if body_size >= self._client_max_size:
+                        raise HTTPRequestEntityTooLarge(
+                            max_size=self._client_max_size,
+                            actual_size=body_size
+                        )
                 if not chunk:
                     break
             self._read_bytes = bytes(body)
@@ -571,11 +575,11 @@ async def post(self) -> MultiDictProxy:
 
         if content_type == 'multipart/form-data':
             multipart = await self.multipart()
+            max_size = self._client_max_size
 
             field = await multipart.next()
             while field is not None:
                 size = 0
-                max_size = self._client_max_size
                 content_type = field.headers.get(hdrs.CONTENT_TYPE)
 
                 if field.filename:
@@ -587,8 +591,10 @@ async def post(self) -> MultiDictProxy:
                         tmp.write(chunk)
                         size += len(chunk)
                         if 0 < max_size < size:
-                            raise ValueError(
-                                'Maximum request body size exceeded')
+                            raise HTTPRequestEntityTooLarge(
+                                max_size=max_size,
+                                actual_size=size
+                            )
                         chunk = await field.read_chunk(size=2**16)
                     tmp.seek(0)
 
@@ -605,8 +611,10 @@ async def post(self) -> MultiDictProxy:
                     out.add(field.name, value)
                     size += len(value)
                     if 0 < max_size < size:
-                        raise ValueError(
-                            'Maximum request body size exceeded')
+                        raise HTTPRequestEntityTooLarge(
+                            max_size=max_size,
+                            actual_size=size
+                        )
 
                 field = await multipart.next()
         else:


Test patch:
diff --git a/tests/test_web_functional.py b/tests/test_web_functional.py
--- a/tests/test_web_functional.py
+++ b/tests/test_web_functional.py
@@ -1451,7 +1451,8 @@ async def handler(request):
         resp = await client.post('/', data=data)
     assert 413 == resp.status
     resp_text = await resp.text()
-    assert 'Request Entity Too Large' in resp_text
+    assert 'Maximum request body size 1048576 exceeded, ' \
+           'actual body size 1048591' in resp_text
 
 
 async def test_app_max_client_size_adjusted(aiohttp_client):
@@ -1476,7 +1477,8 @@ async def handler(request):
         resp = await client.post('/', data=too_large_data)
     assert 413 == resp.status
     resp_text = await resp.text()
-    assert 'Request Entity Too Large' in resp_text
+    assert 'Maximum request body size 2097152 exceeded, ' \
+           'actual body size 2097166' in resp_text
 
 
 async def test_app_max_client_size_none(aiohttp_client):
@@ -1507,30 +1509,27 @@ async def handler(request):
 async def test_post_max_client_size(aiohttp_client):
 
     async def handler(request):
-        try:
-            await request.post()
-        except ValueError:
-            return web.Response()
-        raise web.HTTPBadRequest()
+        await request.post()
+        return web.Response()
 
     app = web.Application(client_max_size=10)
     app.router.add_post('/', handler)
     client = await aiohttp_client(app)
 
-    data = {"long_string": 1024 * 'x', 'file': io.BytesIO(b'test')}
+    data = {'long_string': 1024 * 'x', 'file': io.BytesIO(b'test')}
     resp = await client.post('/', data=data)
 
-    assert 200 == resp.status
+    assert 413 == resp.status
+    resp_text = await resp.text()
+    assert 'Maximum request body size 10 exceeded, ' \
+           'actual body size 1024' in resp_text
 
 
 async def test_post_max_client_size_for_file(aiohttp_client):
 
     async def handler(request):
-        try:
-            await request.post()
-        except ValueError:
-            return web.Response()
-        raise web.HTTPBadRequest()
+        await request.post()
+        return web.Response()
 
     app = web.Application(client_max_size=2)
     app.router.add_post('/', handler)
@@ -1539,7 +1538,7 @@ async def handler(request):
     data = {'file': io.BytesIO(b'test')}
     resp = await client.post('/', data=data)
 
-    assert 200 == resp.status
+    assert 413 == resp.status
 
 
 async def test_response_with_bodypart(aiohttp_client):