0

I am completely confused by the following behaviour in Flask. I am sure there is something basic going on but I don't know if this is a script or a server issue, so I posted the shortest example I could come up with.

The page has two ways to post data to the server. One is triggered by submitting a <form> and the other by a script that listens for Ctrl+V. I believe both should lead to the last render_template call. But only the first one does.

paste.html

{%if msg %}
    {{ msg }}
{% else %}
Waiting
{% endif %}    
<form action="/pastetest" method="post">
      Dummy<br>
  <input type="text" name="foo" value="bar">
  <br>
  <input type="submit" value="Send info" />
</form>

Script Listens for a 'paste' event (Ctrl V) and posts some dummy string in a form.

document.addEventListener('paste', function (e) {sendform(e);},false);
sendform = function (e) {
        var fd = new FormData();
        fd.append('data','testing paste');
        var request = new XMLHttpRequest();
        request.open("POST", "/pastetest");
        request.send(fd);
        console.log("Sent")
    };

Flask python:

@app.route('/')
def index():
    return render_template('paste.html',msg="Waiting")

@app.route('/pastetest',methods=['POST']) 
def image_upload():
    if request.method == 'POST':
        print("On the server")
        print(request.form)
        alldone="All done time=%d" % time.time()
        print(alldone)
        return redirect(url_for('confirm',msg=alldone))

@app.route('/confirm/<msg>')
def confirm(msg):
    print("At confirm",msg)
    return render_template("paste.html",msg=msg) #<---should end up here

From the console I see that the data is being posted and confirm is being called. 127.0.0.1 - - [19/Nov/2017 00:51:42] "GET /confirm/All%20done%20time%3D1511049102 HTTP/1.1" 200 -

but the page only renders after the submit button. Otherwise it is stuck on Waiting.

Edit: To clarify: all I need is to get the posted data (just a dummy string above but will be a blob from the clipboard), do something with it and then render a template with the results. What is the easiest way to do this?

(I'm totally ignorant about js/client-server etc. Just trying to make a simple web interface for a image-analysis program that I already wrote. Simple explanations appreciated :)

A. Roy
  • 366
  • 3
  • 12

2 Answers2

1

It does not render your page since you are just sending an XmlHttpRequest, that alone will not have your browser rendering the response.

Your JavaScript does send a POST request to the URL specified as you can see from your logs, but since you are not doing anything with the response to that request, nothing happens.

This will work...

<form action="/pastetest" method="post">

...because with submitting your HTML form, your browser is not just sending a POST request to the URL in action, it will also change location to that URL and render the response it is getting.

You'll notice that when submitting the HTML form, the URL in your browser location bar does change, which it doesn't when sending your XmlHttpRequest.

Edit, summarizing from comments:

In both cases, the server will execute the functions image_upload and confirm, in other words from the server perspective, they are equivalent, the main difference here is that with the XmlHttpRequest method, the response is not automatically rendered by the browser.

As for the goal described in the question, having the browser render the result page in both cases, we have to think about what happened in the XmlHttpRequest case once we have our response:

  • the server already executed functions image_upload and confirm
  • our rendered template from the confirm function is in our response

In case we do not mind the confirm function being executed twice, we could simply point window.location of our browser to the responseURL of the request.

The function will definitely be executed twice when we do this though, it has already been executed once in order to generate the response we got, and having our browser go to the responseURL (in our case, that will be the URL corresponding to the confirm function), it will execute again.

If we wanted to prevent this, we would need another solution though.

One option would be to inform the server that we do not want a redirect to the rendered response page when doing our initial upload, but instead just want a status information (we could use HTTP status code here, e.g. we interpret status=200 as success) on our upload, and the server response should be where we need to point window.location of our browser.

This way, the server would execute confirm only once, as it would not automatically redirect us anymore in case of our XmlHttpRequest, instead it would just tell us where we need to go and leave the responsibility to actually go there to the client-side JavaScript.

This would mean a change of our server-side code, the server would need to discern what type of response we want. We could implement this with two different endpoints for our upload, e.g. /pastetest for form submit, and /pastetestxhr for XmlHttpRequest.

Another option would be to extract parts of the rendered template from the response using JavaScript, and replace parts of our current page, as in remove our HTML form from the page, and replace it with our success notification from the response.

In order to do this in a convenient way, we will likely want to use some client side JavaScript libraries such as jQuery.

This would also only execute confirm once, as we would refrain from changing browser location to that endpoint after we got our response, and instead use the response we already have.

This approach does not require any server-side code to be changed, but introduce more complexity in our client-side code.

A third option could be a hybrid approach, we inform the server that we do not want an automatic redirect, and expect as a response an HTML snippet we can insert in our page as is, without any complex parsing of the response to extract the bits and pieces we want to insert into our page.

This one would require a litte more complex client-side JavaScript (less complex than our option 2 lined out above though), and server-side code change.

bgse
  • 8,237
  • 2
  • 37
  • 39
  • Let me see if I get this: the final `render_template` is coming back as a response to the `XmlHttpRequest`? So how do I "close" the request (don't know the right terms) so the server can get on with rendering templates. – A. Roy Nov 19 '17 at 02:29
  • @A.Roy Yes exactly, the response body will have your rendered HTML (you can see this in developer console of your browser). – bgse Nov 19 '17 at 02:31
  • @A.Roy As far as the server is concerned, the request/response is already finished at that point, it doesn't care what the JavaScript in your browser does or does not do with the response. You'd have to handle showing the result in your JavaScript, as in get the response body, and for example display the contents in an `alert()`, see the [link in my answer](https://stackoverflow.com/questions/3038901/how-to-get-the-response-of-xmlhttprequest). – bgse Nov 19 '17 at 02:35
  • I saw the link but was struggling to understand it. I added a little edit explaining what I'm trying to do. So should I return the `msg` from the server and in place of the alert in your link, redirect the url by `window.location.href = ...` ? – A. Roy Nov 19 '17 at 02:48
  • @A.Roy If you are not comfortable with client side JavaScript stunts yet, I'd recommend considering [file uploads with flask](http://flask.pocoo.org/docs/0.12/patterns/fileuploads/), that approach tends to have a lot less "moving parts" to worry about, and should be pretty straight-forward from your already working HTML form submit. – bgse Nov 19 '17 at 02:57
  • Yes I already have the file uploads set up, and want to add the option of pasting images from the clipboard. Everything works with the capturing the clipboard data, posting the blob to the server, saving the image etc. I though the last part would be easy - all I want is for the server to get on with its thing, just as if the data came from a form submit. – A. Roy Nov 19 '17 at 03:05
  • @A.Roy The server does "get on with its thing", you just don't see it on the browser side. If you do not care if the `confirm` function is called multiple times (it will, once for your XHR and once when setting a new location) and dont need to support IE11, you could indeed simply point the browser to [the response url](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseURL) as you already considered. – bgse Nov 19 '17 at 03:30
  • Okay, makes sense. Could you add that to your answer so I can select it as accepted? – A. Roy Nov 19 '17 at 20:12
  • @A.Roy No problem, it got a bit lengthy but I wanted to line out the problem and possible options in case any future readers find that helpful. – bgse Nov 19 '17 at 21:15
0

The browser will not render the response from XmlHttpRequest unless you tell the browser via JS to do so. I suggest you take a look at the 'Development Tools' of your browser and check under the Network tab. You will see your XHR's response in there but it will not replace the content or your browser.