Unable to create POST request via HTMX for WebSocket

When trying to send a form in real-time chat, a GET request (HTTP GET /?csrfmiddlewaretoken=some_csrf) appears in the console instead of POST, and the program does not reach the consumer. Django-v5.2.4, daphne-v4.2.1

chat.html

    {% extends 'layouts/blank.html' %}

    {% block content %} 

    <wrapper class="block max-w-2xl mx-auto my-10 px-6">
        {% if chat_group.groupchat_name %}
        <div class="flex justify-between">
        <h2>{{ chat_group.groupchat_name }}</h2>
        {% if user == chat_group.admin %}
        <a href="{% url 'edit-chatroom' chat_group.group_name %}">
            <div class="p-2 bg-gray-200 hover:bg-blue-600 rounded-lg group">
        <svg class="fill-gray-500 group-hover:fill-white" width="16" height="16"
        <path d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z"></path>
                </svg>
            </div>
        </a>
        {% endif %}
        </div>
        {% endif %}
        <div id="chat_window" class="h-[45rem] flex flex-col bg-gray-800 rounded-2xl shadow-2xl relative p-1">
            <div class="flex justify-center text-emerald-400 bg-gray-800 p-2 sticky top-0 z-10">
                {% if other_user %}
                <div id="online-icon" class="gray-dot absolute top-2 left-2"></div>
                <a href="{% url 'profile' other_user.username %}">
                    <div class="flex items-center gap-2 p-4 sticky top-0 z-10">
                        <img class="w-10 h-10 rounded-full object-cover" src="{{ other_user.profile.avatar }}" />
                        <div>
                            <span class="font-bold text-white">{{ other_user.profile.name }}</span> 
                            <span class="text-sm font-light text-gray-400">@{{ other_user.username }}</span>
                        </div>
                    </div>
                </a>
                {% elif chat_group.groupchat_name %}
                <ul id="groupchat-members" class="flex gap-4">
                    {% for member in chat_group.members.all %}
                    <li>
                        <a href="{% url 'profile' member.username %}" class="flex flex-col text-gray-400 items-center justify-center w-20 gap-2">
                            <img src="{{ member.profile.avatar }}" class="w-14 h-14 rounded-full object-cover" />
                            {{ member.profile.name|slice:":10" }}
                        </a>
                    </li>
                    {% endfor %}
                </ul>
                {% else %}
                <div id="online-icon"></div>
                <span id="online-count" class="pr-1"></span>online
                {% endif %}
            </div>
            <div id='chat_container' class="overflow-y-auto grow">
                <ul id='chat_messages' class="flex flex-col justify-end gap-2 p-4">
                    {% for message in chat_messages reversed %}
                    {% include 'a_rtchat/chat_message.html' %}
                    {% endfor %}
                </ul>
            </div>
            <div class="sticky bottom-0 z-10 p-2 bg-gray-800">
                <div class="flex flex-col gap-4 items-center rounded-xl px-2 py-2">
                    <form id="chat_message_form" class="w-full"
                        hx-ext="ws"
                        ws-connect="/ws/chatroom/public-chat"
                        ws-send
                        _="on htmx:wsAfterSend reset() me">
                        {% csrf_token %}
                        {{ form }}
                    </form>
                </div>
            </div>
        </div>
    </wrapper>

    {% endblock %}


    {% block javascript %}
    <script>
        function scrollToBottom() {
            const container = document.getElementById('chat_container');
            container.scrollTop = container.scrollHeight;
        }
        scrollToBottom()
    </script>
    {% endblock javascript %}

routing.py

    from django.urls import path
    from .consumers import *


    websocket_urlpatterns = [
        path("ws/chatroom/<chatroom_name>", ChatroomConsumer.as_asgi()),
    ]

consumers.py

    from channels.generic.websocket import WebsocketConsumer
    from django.shortcuts import get_object_or_404
    import json

    from django.template.loader import render_to_string
    from a_rtchat.models import ChatGroup, GroupMessage


    class ChatroomConsumer(WebsocketConsumer):
        def connect(self):
            print(self.scope)
            self.user = self.scope['user']
            self.chatroom_name = self.scope['url_route']['kwargs']['chatroom_name']
            self.chatroom = get_object_or_404(ChatGroup, group_name=self.chatroom_name)
            self.accept()

        def reveive(self, text_data=None, bytes_data=None):
            text_data_json = json.loads(text_data)
            print(text_data_json)
            body = text_data_json['message']

            message = GroupMessage.objects.create(
                body=body,
                author = self.user,
                group = self.chatroom
            )
            context = {
                'message':message,
                'user':self.user,
            }
            html = render_to_string("a_rtchat/partials/chat_message_p.html", context=context)
            self.send(text_data=html)

asgi.py

    import os

    from django.core.asgi import get_asgi_application
    from channels.auth import AuthMiddlewareStack
    from channels.routing import ProtocolTypeRouter, URLRouter
    from channels.security.websocket import AllowedHostsOriginValidator

    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_core.settings')

    django_asgi_app = get_asgi_application()

    from a_rtchat.routing import websocket_urlpatterns

    application = ProtocolTypeRouter(
        {
            'http':django_asgi_app,
            'websocket':AllowedHostsOriginValidator(
                AuthMiddlewareStack(URLRouter(
                    websocket_urlpatterns
                    )))
        }
    )

base.html

    {% load static %}
    {% load django_htmx %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta name="description" content="Django Template">
        <title>Real-time Chat</title>
        <link rel="icon" type="image/x-icon" href="{% static 'favicon.ico' %}">
        <script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
        <script src="https://unpkg.com/htmx.org@2.0.2"></script>
        <script src="https://cdn.jsdelivr.net/npm/htmx.org/dist/htmx.min.js" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2" integrity="sha384-vuKxTKv5TX/b3lLzDKP2U363sOAoRo5wSvzzc3LJsbaQRSBSS+3rKKHcOx5J8doU" crossorigin="anonymous"></script>
        {% django_htmx_script %}
        <script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
        <script src="https://unpkg.com/hyperscript.org@0.9.14"></script>
        <script src="https://cdn.tailwindcss.com"></script>
        <style type="text/tailwindcss">
            [x-cloak] { 
                display: none !important; 
            }
            h1 {
                @apply text-4xl font-bold mb-4
            }
            h2 {
                @apply text-xl font-bold mb-2
            }
            h3 {
                @apply text-lg font-bold
            }
            p {
                @apply mb-4
            }
            .button, button, [type='submit'], [type='button'] {
                @apply bg-indigo-600 text-white font-bold rounded-lg shadow-lg transition-all cursor-pointer
            }
            .button, button a, [type='submit'], [type='button'] {
                @apply px-6 py-4 inline-block 
            }
            .button:hover, button:hover, [type='submit']:hover, [type='button']:hover {
                @apply bg-indigo-700
            }
            .button:active, button:active, [type='submit']:active, [type='button']:active {
                @apply scale-95
            }
            .button.alert, button.alert {
                @apply bg-red-700
            }
            .button.alert:hover, button.alert:hover {
                @apply bg-red-600
            }
            .button-red {
                @apply !bg-red-500 hover:!bg-red-600
            }
            .button-gray {
                @apply !bg-gray-300 hover:!bg-[#c3c9d0]
            }
            .navitems>li>a {
                @apply flex items-center gap-2 h-12 px-4 hover:bg-[rgba(31,41,55,0.3)] rounded-lg;
            }
            .hoverlist>* {
                @apply hover:bg-gray-100 rounded-md transition duration-150;
            }
            .hoverlist>*>a {
                @apply flex items-center p-2;
            }
            .highlight {
                @apply !bg-indigo-100;
            }
            .allauth content a {
                @apply underline underline-offset-2
            }
            .allauth content a:hover {
                @apply text-indigo-500
            }
            .allauth form[action="/accounts/signup/"] ul {
                @apply hidden
            }
            .allauth form[action="/accounts/signup/"] ul.errorlist {
                @apply block
            }
            .allauth .helptext {
                @apply block mt-4
            }
            label {
                @apply hidden
            }
            input[type=file] {
                @apply bg-white pl-0
            }
            .textarea, textarea, input {
                @apply w-full rounded-lg py-4 px-5 bg-gray-100
            }
            .errorlist li {
                @apply p-1 pl-4 border-l-red-500 border-l-4 border-solid mb-2 text-red-500
            }
            label[for="id_remember"] {
                @apply inline-block w-auto mr-2
            }
            input[name="remember"] {
                @apply w-auto
            }
            .alert-info { @apply bg-sky-500 }
            .alert-success { @apply bg-green-500 }
            .alert-warning { @apply bg-red-500 }
            .alert-danger { @apply bg-red-500 }
        </style>
    </head>
    <body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' class="{% block class %}{% endblock %}">
        
        {% include 'includes/messages.html' %}

        {% include 'includes/header.html' %}

        {% block layout %}
        {% endblock %}

        {% block javascript %}
        {% endblock %}

    </body>
    </html>

I don't really understand what needs to be done, in the documentation and tutorials everything seems to be fine for everyone

You're facing a common confusion with htmx-ext-ws and how it interacts with Django WebSockets. Let's walk through why your POST isn't working, and how to fix it.

```<form id="chat_message_form"
      hx-ext="ws"
      ws-connect="/ws/chatroom/public-chat"
      ws-send
      _="on htmx:wsAfterSend reset() me">```
  1. Fix the receive method typo:
```def receive(self, text_data=None, bytes_data=None):
    ...

1. **No need to worry about POST vs GET**: When using `htmx-ext-ws`, the form is serialized and sent as a JSON message — not as a POST request. That's expected behavior.

tl;dr Make sure the htmx-ext-ws is properly loaded.

Looking at your previous post, you've attached js like this

<script src="https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2" integrity="sha384-vuKxTKv5TX/b3lLzDKP2U363sOAoRo5wSvzzc3LJsbaQRSBSS+3rKKHcOx5J8doU" crossorigin="anonymous"></script>

When I try do the same, I get the following error in console:

Failed to find a valid digest in the 'integrity' attribute for resource 'https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2' with computed SHA-384 integrity '0RHpPqZ4QLJXG/ALtieEL/G+NuAI98LYSkT9s6FgciveUhAdo/6wab5NU1tm2Bxs'. The resource has been blocked.

This probably prevents the extention js file from being loaded and that's why you're getting the GET request as this is the default behaviour that the browser falls back to.

Вернуться на верх