Repository: django/channels

Issue:
AsyncHttpConsumer doesn't have normal consumer attributes
I was trying to access `self.channel_name` and `self.channel_layer` within a consumer inherited from AsyncHttpConsumer and it raised exceptions saying that my consumer had no such attributes.

Here is the toy code to reproduce this bug:

**routing.py**
```python
application = ProtocolTypeRouter({
    "http": URLRouter([
        url(r"^test/me", MeConsumer),
        url(r"", AsgiHandler)
    ])
})
```
**consumers.py**
```python
class MeConsumer(AsyncHttpConsumer):

    async def handle(self, body):
        my_channel_name = self.channel_name
        self.channel_layer.group_add("my_group", self.channel_name)
        print("Here is the channel name:", my_channel_name)
        await asyncio.sleep(10)
        await self.send_response(200, b"Your response bytes", headers=[
            ("Content-Type", "text/plain"),
        ])
```

*I couldn't help but peek into the source code, and I noticed that `AsyncHttpConsumer` overrides `__call__` of it's base class. I am not an expert but :eyes: it may be useful to know.*


Discussion:
Good catch. That probably also means that the `chat_message` example I added to the docs doesn't work right now since there's no way to dispatch messages through the channel layer to the `AsyncHttpConsumer`. https://channels.readthedocs.io/en/latest/topics/consumers.html#asynchttpconsumer

Maybe the code has to be changed to assemble the request body inside a new `def http_request` method which itself would call `self.handle()` as soon as the request body is complete. Also, we'd probably need a `def http_disconnect` method raising `StopConsumer` or something, and maybe also calling `self.disconnect()`

I'm not working on an ASGI project right now unfortunately, so if someone else wants to pick this up -- feel free. I hope that the pointers above are helpful.
Ah yes, this looks like a bug in the implementation. I'm not going to get to this soon but others are welcome to give it a go.

Resolving patch:
diff --git a/channels/generic/http.py b/channels/generic/http.py
--- a/channels/generic/http.py
+++ b/channels/generic/http.py
@@ -1,5 +1,7 @@
 from channels.consumer import AsyncConsumer
 
+from ..exceptions import StopConsumer
+
 
 class AsyncHttpConsumer(AsyncConsumer):
     """
@@ -7,25 +9,9 @@ class AsyncHttpConsumer(AsyncConsumer):
     HTTP endpoints.
     """
 
-    async def __call__(self, receive, send):
-        """
-        Async entrypoint - concatenates body fragments and hands off control
-        to ``self.handle`` when the body has been completely received.
-        """
-        self.send = send
-        body = []
-        try:
-            while True:
-                message = await receive()
-                if message["type"] == "http.disconnect":
-                    return
-                else:
-                    if "body" in message:
-                        body.append(message["body"])
-                    if not message.get("more_body"):
-                        await self.handle(b"".join(body))
-        finally:
-            await self.disconnect()
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.body = []
 
     async def send_headers(self, *, status=200, headers=None):
         """
@@ -41,11 +27,9 @@ async def send_headers(self, *, status=200, headers=None):
         elif isinstance(headers, dict):
             headers = list(headers.items())
 
-        await self.send({
-            "type": "http.response.start",
-            "status": status,
-            "headers": headers,
-        })
+        await self.send(
+            {"type": "http.response.start", "status": status, "headers": headers}
+        )
 
     async def send_body(self, body, *, more_body=False):
         """
@@ -56,11 +40,9 @@ async def send_body(self, body, *, more_body=False):
         the channel will be ignored.
         """
         assert isinstance(body, bytes), "Body is not bytes"
-        await self.send({
-            "type": "http.response.body",
-            "body": body,
-            "more_body": more_body,
-        })
+        await self.send(
+            {"type": "http.response.body", "body": body, "more_body": more_body}
+        )
 
     async def send_response(self, status, body, **kwargs):
         """
@@ -87,3 +69,24 @@ async def disconnect(self):
         from here.
         """
         pass
+
+    async def http_request(self, message):
+        """
+        Async entrypoint - concatenates body fragments and hands off control
+        to ``self.handle`` when the body has been completely received.
+        """
+        if "body" in message:
+            self.body.append(message["body"])
+        if not message.get("more_body"):
+            try:
+                await self.handle(b"".join(self.body))
+            finally:
+                await self.disconnect()
+                raise StopConsumer()
+
+    async def http_disconnect(self, message):
+        """
+        Let the user do their cleanup and close the consumer.
+        """
+        await self.disconnect()
+        raise StopConsumer()


Test patch: