Repository: tornadoweb/tornado

Issue:
Uninformative error doing GET with body, or POST without.
If you GET a URL with a request body:

```
yield http_client.fetch(url, body='data')
```

Tornado just raises AssertionError. Same for a POST with no body. This is a likely mistake for someone coming from a framework that automatically POSTs whenever a body is supplied. The traceback is no help:

```
  File "tornado/gen.py", line 496, in run
    next = self.yield_point.get_result()
  File "tornado/gen.py", line 395, in get_result
    return self.runner.pop_result(self.key).result()
  File "concurrent/futures/_base.py", line 397, in result
    return self.__get_result()
  File "concurrent/futures/_base.py", line 356, in __get_result
    raise self._exception
AssertionError
```

I think the asserts in SimpleHTTPClient should include a message about what the user did wrong. CurlHTTPClient should assert the same conditions, with the same messages.



Discussion:
Pull request incoming....


Resolving patch:
diff --git a/tornado/curl_httpclient.py b/tornado/curl_httpclient.py
--- a/tornado/curl_httpclient.py
+++ b/tornado/curl_httpclient.py
@@ -407,6 +407,11 @@ def _curl_setup_request(curl, request, buffer, headers):
 
     # Handle curl's cryptic options for every individual HTTP method
     if request.method in ("POST", "PUT"):
+        if request.body is None:
+            raise AssertionError(
+                'Body must not be empty for "%s" request'
+                % request.method)
+
         request_buffer = BytesIO(utf8(request.body))
         curl.setopt(pycurl.READFUNCTION, request_buffer.read)
         if request.method == "POST":
@@ -417,6 +422,9 @@ def ioctl(cmd):
             curl.setopt(pycurl.POSTFIELDSIZE, len(request.body))
         else:
             curl.setopt(pycurl.INFILESIZE, len(request.body))
+    elif request.method == "GET":
+        if request.body is not None:
+            raise AssertionError('Body must be empty for GET request')
 
     if request.auth_username is not None:
         userpwd = "%s:%s" % (request.auth_username, request.auth_password or '')
diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py
--- a/tornado/simple_httpclient.py
+++ b/tornado/simple_httpclient.py
@@ -297,9 +297,15 @@ def _on_connect(self):
             self.request.headers["User-Agent"] = self.request.user_agent
         if not self.request.allow_nonstandard_methods:
             if self.request.method in ("POST", "PATCH", "PUT"):
-                assert self.request.body is not None
+                if self.request.body is None:
+                    raise AssertionError(
+                        'Body must not be empty for "%s" request'
+                        % self.request.method)
             else:
-                assert self.request.body is None
+                if self.request.body is not None:
+                    raise AssertionError(
+                        'Body must be empty for "%s" request'
+                        % self.request.method)
         if self.request.body is not None:
             self.request.headers["Content-Length"] = str(len(
                 self.request.body))


Test patch:
diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py
--- a/tornado/test/httpclient_test.py
+++ b/tornado/test/httpclient_test.py
@@ -387,6 +387,19 @@ def test_all_methods(self):
                               allow_nonstandard_methods=True)
         self.assertEqual(response.body, b'OTHER')
 
+    @gen_test
+    def test_body(self):
+        hello_url = self.get_url('/hello')
+        with self.assertRaises(AssertionError) as context:
+            yield self.http_client.fetch(hello_url, body='data')
+
+        self.assertTrue('must be empty' in str(context.exception))
+
+        with self.assertRaises(AssertionError) as context:
+            yield self.http_client.fetch(hello_url, method='POST')
+
+        self.assertTrue('must not be empty' in str(context.exception))
+
 
 class RequestProxyTest(unittest.TestCase):
     def test_request_set(self):