fanout / django-eventstream

Server-Sent Events for Django
MIT License
664 stars 88 forks source link

Update Readme for Django-restframework router #139

Closed enzofrnt closed 6 months ago

enzofrnt commented 6 months ago

Related to #138, I updated to specify how to use it with Django-restframework and also a little adjustment about using something as Redis when using different process…

enzofrnt commented 6 months ago

On this use case everything is working greate with postman or, Google Chrome but on Angular no… I don't know why

enzofrnt commented 6 months ago

I said in the readme that we need renderer when using Access in the header. But I'm not sure, maybe that better to use it every time to ensure that we are using the right parameter in the response header.

Can you verify @jkarneges ? Thx.

jkarneges commented 6 months ago

Thanks, I think it could be good to add such instructions to the readme.

However, I don't understand the part about the renderer. It's just to set response headers? By the way, Access-Control-* headers should ideally be provided by the django-cors-headers middleware, which hopefully works with DRF too.

enzofrnt commented 6 months ago

Hi @jkarneges, thank you for your answer.

As someone who is relatively new to Django, having recently started using it in my work-study program, I am still on the learning curve. Therefore, some aspects of its implementation are not immediately clear to me, although I understand they serve to resolve specific issues.

In my scenario, the client makes a request and explicitly specifies Accept: text/event-stream in the request header, indicating a preference

client request header

Given that this access parameter seems to be a standard practice for SSE, it's crucial for us to accommodate this requirement.

However, I've encountered a snag. Without adding a specific renderer to the view, the response defaults to Content-Type: application/json. This contrasts with our client's explicit requirement for Content-Type: text/event-stream. I'm keen to understand the necessity of adjusting the Content-Type to adhere to the SSE standard and ensure compatibility with the client's expectations.

Because if we don't adhere, we get an error like this one : image

If my understanding is correct, we need to add a renderer to modify the response header by including all necessary elements for a functional SSE response.

enzofrnt commented 6 months ago

How, and if in the same time we can carefully make the module compatible as possible with this module drf-spectacular that will be pretty nice for documented use cases.

Here is the last version of my code for those supports :

class TextEventStreamRenderer(BaseRenderer):
    """
    A renderer to stream events to the client in text/event-stream format and add the right CORS headers.
    """
    media_type = 'text/event-stream'
    charset = 'utf-8'
    format = 'event-stream'
    Access_Control_Allow_Origin = EVENTSTREAM_ALLOW_ORIGIN

    def render(self, data, accepted_media_type=None, renderer_context=None):
        # Ensure data is a byte string
        if isinstance(data, str):
            data = data.encode(self.charset)
        return data

class EventsViewSet(ViewSet):
    """
    A viewset to stream events to the client.
    """
    renderer_classes = [TextEventStreamRenderer]

    def list(self, request, *args, **kwargs):
        channels = ['parser', 'scanner']
        kwargs['channels'] = channels
        django_request = request._request if hasattr(request, '_request') else request

        return events(django_request, **kwargs)
enzofrnt commented 6 months ago

@jkarneges I'm working on something better 👀 image

enzofrnt commented 6 months ago

@jkarneges Here is what I have done.

Extend HTML to display the ongoing event.

{% extends "rest_framework/base.html" %}

{% block script %}

<script type="text/javascript">
    var currentPageUrl = window.location.href;

    // Exemple d'utilisation : affichage dans la console
    console.log("URL actuelle :", currentPageUrl);
    var eventsContainer = document.querySelectorAll('.meta');
    console.log("Conteneur d'événements :", eventsContainer);
    eventsContainer[0].innerHTML += "<div id=\"sse-events\">Événements SSE s'afficheront ici...</div>"

    var eventSource = new EventSource(currentPageUrl);
    eventSource.onmessage = function(event) {
        console.log("Nouvel événement SSE :", event.data);
        eventsContainer[0].innerHTML += "<p>" + event.data + "</p>";
    };

</script>
{{ block.super }}
{% endblock %}

Renderer for SSE (Django did not have one working for SSE) :

from rest_framework.renderers import BaseRenderer
from project.settings import EVENTSTREAM_ALLOW_ORIGIN

class SSEEventRenderer(BaseRenderer):
    """
    A renderer to stream events to the client in text/event-stream format and add the right CORS headers.
    """
    media_type = 'text/event-stream'
    Access_Control_Allow_Origin = EVENTSTREAM_ALLOW_ORIGIN

    def render(self, data, accepted_media_type=None, renderer_context=None):
        # Ensure data is a byte string
        if isinstance(data, str):
            data = data.encode(self.charset)
        return data

A view set that I have tried to do… If you have competence to work on it, please take a look at it.

class EventsViewSet(ViewSet):
    """
    A viewset to stream events to the client.
    """

    http_method_names = ['get']

    def list(self, request, *args, **kwargs):
        channels = ['parser', 'scanner']
        kwargs['channels'] = channels
        django_request = request._request if hasattr(request, '_request') else request

        # Vérifie si le type de contenu accepté est "text/event-stream"
        if django_request.META.get('HTTP_ACCEPT') :
            print(django_request.META.get('HTTP_ACCEPT'))
            if 'text/event-stream' in django_request.META.get('HTTP_ACCEPT') :
                return events(django_request, **kwargs)
            elif 'text/html' in django_request.META.get('HTTP_ACCEPT'):
                return render(django_request, 'events.html', context={'channels': channels})

        return Response({'error': 'Bad request'}, status=400)
enzofrnt commented 6 months ago

I just add the renderer because for me, it didn't need more. Please let me know what you think, and may be help me