4

Question

How can one redirect to another page when server-sent-event is finished in Flask and on server side?

Problem description

An EventSource was implemented on client side code (JavaScript) and a Response was returned on server side code (Flask, Python). The EventSource will be closed if the last item from server was sent. I can't see a clear solution to redirect to another site via server side code. Maybe the answer is simple, but I'm not getting it.

Similar problem

A similar question is How to stop Server-Sent Events and one possible answer in the comment How do server-sent events actually work?. I'm not sure, if it's the same for php and python, so I've started a new question for that. I also didn't get it.

EDIT How do I close a Server-Send Events connection in Flask? is strongly related to this question. The question there is mainly to see how you can stop SSE from Flask.

Working solution on client side

So I came up with a solution that worked (a progress bar), but only on the client side. How do I have to change the code to get a working example of reconnecting via Flask functions?

Relevant code snippets

HTML/Jinja

{% block additional_stylesheets %}
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="{{ url_for('static', filename='data_import.css') }}" />
{%  endblock %}
{% block additional_javascripts %}
    <script src="{{ url_for('static', filename='js/data_import.js') }}"></script>
{% endblock %}
{% block content %}
    <div class="progress">
        <div class="progress-bar progress-bar-striped active"
             role="progressbar" aria-valuenow="0" aria-valuemin="0"
             aria-valuemax="100">
        </div>
    </div>
{% endblock %}

JavaScript

$(document).ready(function()
    {
        var source = new EventSource("/progress");

        source.addEventListener('import-progress', function(event)
            {
                $('.progress-bar')
                    .css('width', event.data + '%')
                    .attr('aria-valuenow', event.data);
            }, false
        );

        source.addEventListener('last-item', function()
            {
                source.close();
                redirect();
            }, false
        );
    }
);

# This works! But how to do the same thing without calling redirect()
# on client site?
function redirect()
{
    window.document.location.href = window.location.protocol + "//" +
            window.location.host + "/products.html";
}

Flask

from foo import app
from flask import render_template, Response

@app.route('/data_import.html')
def data_import():
    return render_template(
        'data_import.html')


@app.route('/progress')
def progress():
    return Response(import_progress(), mimetype='text/event-stream')

def import_progress():
    """
    Just a small example
    """
    for number in range(1, 101):
        sse_id = str(number)
        sse_progress = str(number)
        sse_event = 'import-progress'

        if number == 100:
            sse_event = 'last-item'

        yield "id:{_id}\nevent:{event}\ndata:{progress}\n\n".format(
            _id=sse_id, event=sse_event, progress=sse_progress)

I tried a lot to get a redirect working. But I don't know exactly how to do it. Every attempt so far has failed.

Community
  • 1
  • 1
colidyre
  • 4,170
  • 12
  • 37
  • 53

1 Answers1

4

What you want to do is send the redirect URL as the last event - you'll still need to redirect using JavaScript, but you won't have to hard-code the path any more:

def import_progress():
    """
    Just a small example
    """
    for number in range(1, 101):
        sse_id = str(number)
        sse_data = str(number)
        sse_event = 'import-progress'

        if number == 100:
            sse_event = 'last-item'
            sse_data = url_for('product_list')

        yield "id:{_id}\nevent:{event}\ndata:{data}\n\n".format(
            _id=sse_id, event=sse_event, data=sse_data)

Then your last-item handler becomes:

source.addEventListener('last-item', function(event) {
     source.close();
     redirect(event.data);
   }, false
);

And redirect becomes a simple:

function redirect(url) {
  document.location = url;
}
Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
  • Oh, that's a nice solution without hardcoding the url in the JavaScript file. Thx to that! I still would prefer a solution without calling `redirecting()` in JavaScript file and do that with Flask's `redirect(url_for('foo'))` method. Or is this simply not possible? I can't upvote your answer yet, unfortunately - I will do it soon as I can. – colidyre Aug 28 '15 at 17:08
  • 1
    Redirecting from the server side is simply not possible because you've already made the connection once you're consuming a SSE (just like you can't redirect a page once you've started sending it to the user without doing something on the client side). – Sean Vieira Aug 28 '15 at 18:53
  • As per the upvote - if it works for you, just hit the green check mark. That will help others know that you found the answer helpful. And welcome to StackOverflow! – Sean Vieira Aug 28 '15 at 18:55
  • 1
    _"Redirecting from the server side is simply not possible"_ is an important information. Thx! @SeanVieira – colidyre Aug 29 '15 at 00:45