django / channels

Developer-friendly asynchrony for Django
https://channels.readthedocs.io
BSD 3-Clause "New" or "Revised" License
6.04k stars 798 forks source link

Http Consumer Error (Http response already has been started) #1041

Closed DiegoGallegos4 closed 6 years ago

DiegoGallegos4 commented 6 years ago

I have an http endpoint, therefore I am using AsyncHttpConsumer(example on documentation). But when accesing the url I receiver the following error:

ValueError: HTTP response has already been started

The consumers looks as follows:

# Http consumer
class HttpConsumer(AsyncHttpConsumer):
    async def handle(self, body):
        await self.send_response(200, {"msg": "any message"})
        await self.channel_layer.send("task-queue", {"type": "task.queue"})

# Background worker consumer
class TaskQueue(SyncConsumer):
     def task_queue(self, message):
            logger.info("Receive message")routes is the following:

Routing looks as follows:

application = ProtocoTypeRouter({
    "http": AuthMiddlewareStack(
             URLRouter([
                   url(r"^stream/test/", consumers.HttpConsumer),
                   url(r"(.*?)", AsgiHandler)
             ])
     ),
     "channel": ChannelNameRouter({
              "task-queue": consumers.TaskQueue
      })
})

I have tried simplifying the example just to use the HttpConsumer but still getting the same error. The channel layer is configured to use the redis config that appears in the documentation.

Posting this here as I am only trying the documentation BasicHttpConsumer and the basic channel layer consumer.

matthiask commented 6 years ago

You did probably simplify too much. The send_response line has a syntax error. The second argument to send_response should be a response body as a bytestring. If you want to send more than one response chunk (as maybe you're trying to do) you should use send_body and send_headers. Maybe try posting the full HttpConsumer you're using?

DiegoGallegos4 commented 6 years ago

Fix the typo:

I migrated from channels 1.1.3 where I used function based consumers. This is the result:

class KYCReportDownloadConsumer(AsyncHttpConsumer):

    async def handle(self, body):
        user = self.scope["user"]
        if not user.has_perm(models.CAN_ACCESS_KYC_REPORT):
            await self.send_response(403, b"Forbidden")
            return

        qs = urllib.parse.parse_qs(self.scope["query_string"].decode("utf-8"))
        params = {
            "type":        "task.queue",
            "task":        "compliance.kyc_report.download",
            "result_code": qs["result_code"][0],
            "email":       user.email,
        }
        if qs.get("compliance_inprocess"):
            params["compliance_inprocess"] = qs["compliance_inprocess"][0]
        if qs.get("compliance_location"):
            params["compliance_location"] = qs["compliance_location"][0]

        msg = "Report being sent to email: {email}"(email=user.email)

        await self.send_response(200, {"msg": msg})
        await self.channel_layer.send(
            "task-queue",
            params
        )

I try simplifying the consumer to just use send_response(200, b"test") but still the same error.

andrewgodwin commented 6 years ago

If you paste the traceback of where the error happens that would help - it should trace back into your application.

DiegoGallegos4 commented 6 years ago

Traceback is really long.

Found the error, error is related to what @matthiask noticed about converting the message to bytes. Posting part of the traceback and as result it threw the error I posted first:

ERROR 2018-05-02 17:50:45,235 Exception inside application: Body is not bytes File "/Users/diegogallegos/anaconda2/envs/merlin2/lib/python3.6/site-packages/channels/generic/http.py", line 25, in call await self.handle(b"".join(body)) File "/Users/diegogallegos/Documents/Apps/xapo/merlin/merlin/compliance/consumers.py", line 60, in handle ("Content-Type", "application/json"), File "/Users/diegogallegos/anaconda2/envs/merlin2/lib/python3.6/site-packages/channels/generic/http.py", line 70, in send_response await self.send_body(body) File "/Users/diegogallegos/anaconda2/envs/merlin2/lib/python3.6/site-packages/channels/generic/http.py", line 56, in send_body assert isinstance(body, bytes), "Body is not bytes" Body is not bytes

Converted the response to as follows:

json.dumps({"msg": msg}).encode("utf-8")
andrewgodwin commented 6 years ago

And that fixed it?

DiegoGallegos4 commented 6 years ago

Yes, thanks for the help! Maybe a small example with object in the documentation in the future will be a good reminder. Can close now. When finish migrating I can submit a small example with some stuff the docs is not covering for completion. Awesome work!

andrewgodwin commented 6 years ago

OK. Documentation improvements are always welcome!

matthiask commented 6 years ago

@DiegoGallegos4 The LongPollConsumer example on https://channels.readthedocs.io/en/latest/topics/consumers.html even sends JSON using the json.dumps().encode() dance. Still, I'll happily help you submit a documentation improvement in case you have a good idea what to improve 😊