pythonflaskwebargs

Flask problems handling a request with URLencoded parameters


I'm having a weird problem; I'm running Flask and I have an API function to cancel memberships. The URL also contains a parameter to set a reason for cancelation. this is a short text, and it can contain extended characters, so the string is URLencoded by the calling party.

In certain cases however, Flask returns a 400 error, before it even reaches my own processing code. For example, the following URL:

curl -X "DELETE" "http://localhost:5000/contracts/C9ABA4AA-834E-4711-91A8-F21057DF693B?date=2015-8-1&canceldate=2015-6-27&booktoday=true&overrideEnddate=true&cancelreason=traslado+a+m%C3%A1s+de+15+km&correctionreason="

Gives me a 400 error: The browser (or proxy) sent a request that this server could not understand. although it seems to be a perfectly valid URL. When I trap the error the underlying error data is:

'ascii' codec can't encode character u'\\xe1' in position 12: ordinal not in range(128)

I found out that all is well when I remove the encoded extended character á (%C3%A1) from the URL string.

I can also solve it by adding this workaround I found elsewhere to my app.init()

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

I understood that this statement forces the Python interpreter to use UTF-8 by default to decode bytestrings, instead of ASCII.
This gives me a solution to my problem, but it feels overly complicated and unnessecary.

So, the real question is, what am I missing here? Is there a setting within Flask that I don't know about that solves this, or can it really be the case that Flask can't handle specific URL encoded strings? I would expect Flask to be able to handle URL encoded parameters by itself, without a workaround in non-library code...

I have no traceback, because Flask will trap it and handle it as an HTTP error and return a 400 HTTP status code.

EDIT: The origin of the exception is in this part of the Flask code (site-packages\flask\app.py):

def full_dispatch_request(self):
    """Dispatches the request and on top of that performs request
    pre and postprocessing as well as HTTP exception catching and
    error handling.

    .. versionadded:: 0.7
"""
self.try_trigger_before_first_request_functions()
try:
    request_started.send(self)
    rv = self.preprocess_request()
    if rv is None:
        rv = self.dispatch_request()
except Exception as e:
    rv = self.handle_user_exception(e)
response = self.make_response(rv)
response = self.process_response(response)
request_finished.send(self, response=response)
return response

The code skips to the except: and returns a 400 error.

Thanks for any pointers you can give me.


Solution

  • Found out the solution; And it seems I left out some essential information in my question that was needed to resolve the issue.

    We tried to duplicate the problem in a very simple single-page Flask app, so I could put it up here on StackOverflow as an example. However, the simple Flask testpage worked fine, so we went to look for the differences.

    What I totally forgot, and we noticed just then, was that we use the webargs library to parse http parameters.

    The parameters were parsed as strings, and there also is a type unicode available in webargs. Changing the type to unicode solved the problem.

    @api.route('/contracts/<ppl_mshp_id>', methods=['DELETE'])
    @use_args({'date': Arg(type_=str, validate=is_date, required=False),
               'canceldate': Arg(type_=str, validate=is_date, required=False),
               'cancelreason': Arg(type_=unicode, required=False, default=u""),
               'correction': Arg(type_=float, required=False),
               'correctionreason': Arg(type_=unicode, required=False, default=u""),
               'restitution': Arg(type_=bool, required=False, default=False),
               'booktoday': Arg(type_=bool, required=False, default=False),
               'overrideEnddate': Arg(type_=bool, required=False, default=False),
               })
    def cancel_contract(args, ppl_mshp_id):
    ...
    

    Thank you @ketouem for your time and trouble, and sorry for being incomplete in my question.