Wednesday, 1 May 2019

Flask post/redirect/get pattern cannot recover from an error

I have an error recovery problem in a Flask view function. Its simplified version is here:

@app.route('/log', methods=['POST', 'GET'])
def elog():
    form = LogForm()
    if form.validate_on_submit():
        flask.session['logformdata'] = form.data
        return flask.redirect(flask.url_for('elog'))

    try:
        formdata = flask.session.pop('logformdata')
    except KeyError:
        return flask.render_template('log.html', form=form)

    log = ... # result of a query
    form.process(data=formdata)
    return flask.render_template('log.html', form=form, log=log)

The important part is that it implements the post/redirect/get pattern and stores the form data between POST and GET in the flask.session storage which is implemented with HTTP cookies.

Now let's assume there is a bug somewhere and the function crashes with certain input. Of course, it should not happen. But when it does, the user's experience is terrible.

In detail:

  • the user posts a form (POST)
  • the form data is stored in the flask.session, i.e. as a cookie
  • after a redirect, the function is called again (GET), but now it crashes unexpectedly. The user sees some error message. That is not good, but bugs happen.
  • the user reloads the page intending to start over, but gets the same error repeated again and again!

The key point is that the statement flask.session.pop removes the form data from the session storage, but when the function crashes, the corresponding cookie remains in the user's browser. Each reload triggers the bug again. Restarting the browser may help (depending on session.permanent flag). The only guaranteed remedy is to manually delete the cookie from the browser. This effectively makes the webpage unusable.

I think I can mitigate the problem with setting a very short cookie lifetime (15 seconds or so) or by generating a new secret key after each restart. I did not try it and it is definitely not a good solution if session cookie contains other data.

How can I make functions like the one above more robust?



from Flask post/redirect/get pattern cannot recover from an error

GAE/P: Transaction safety with API calls

Suppose you use a transaction to process a Stripe payment and update a user entity:

@ndb.transactional
def process_payment(user_key, amount):
    user = user_key.get()
    user.stripe_payment(amount) # API call to Stripe
    user.balance += amount
    user.put()

It is possible that the Stripe API call succeeds but that the put fails because of contention. The user would then be charged, but his account wouldn't reflect the payment.

You could pull the Stripe API call out of the transaction and do the transaction afterwards, but it seems like you still have the same problem. The charge succeeds but the transaction fails and the user's account isn't credited.

This seems like a really common scenario. How does one properly handle this?



from GAE/P: Transaction safety with API calls

Executing Unix/arbitrary commands on iOS

This is not for a production application - I'm just tinkering for the fun of it (on a device, not the simulator).

Is there any mechanism for executing arbitrary binaries on iOS? Say, ls.

I've tried posix_spawn(), but it always returns a status of 1.

system() doesn't even compile, it fails with: 'system' is unavailable: not available on iOS

fork() is similarly not allowed

I realize it's a long-shot, but I have a couple (local/personal) projects that are written as pre-compiled ARM binaries and I'd love to have them available on my iPad. (Plus it's just a fun experiment.)

Sidenote: I haven't jailbroken an iOS device in years and I'm hoping to avoid it - if the above isn't possible with stock iOS, I'll just sigh and move on.



from Executing Unix/arbitrary commands on iOS