支付宝接口编码不规范问题

大厂靠不住系列之二

因为上次写过阿里大鱼的接口问题,所以这就是之二了。

虽然这两篇都是吐槽阿里的,但是国内的大厂都差不多,只是相对来说阿里槽点更多,所以叫大厂靠不住系列,其实国内厂除了阿里我也只用过腾讯。

本来这个问题是几个月前碰到的,但是因为要写那个Docker系列,就没空来吐槽这个,现在Docker系列完工了,就把这个事说说吧。

问题说明

当时是在做一个支付宝服务窗的项目,估计搞过这东西的人会少一些,但实际就是一个山寨微信公众号的东西,然而系统设计要烂得多(虽然微信也没有好到哪里去,但至少还没发现这里说的低级错误),只能算是凑和能用罢了。

支付宝当然也有号称的开放平台AOP,对应淘宝开放平台的TOP,二者的接口设计属于看上去差不多(各种JAVA风格的命名加各种XML),一看就是亲手捡的那种。

本来嘛,只要能用也就忍了,但是被我碰到了一个没法用的问题。

事情是这样的,支付宝的接口在很多地方都是使用GB编码,当然这在大多数时候都不是问题,毕竟编码问题是个有十几二十年历史的问题,早就有一堆的解决方案,对于我这里用python3的环境来说,无非是GB转unicode一下而已。

但是在处理某些支付宝平台推送过来的请求时,还是会有编码问题,我确定我的代码里已经全部处理了编码问题,但仍然在运行时报错。

通过查看日志才发现,问题不在我这。

因为我是用flask框架实现的,flask的底层又是用werkzeug(好吧,我早年说过我不喜欢这货,但是现在是工作需要用到,没办法)处理HTTP请求,现在的问题就在于werkzeug对于application/x-www-form-urlencoded和application/x-url-encoded两种类型的POST请求不处理content-type的所有参数,且默认UTF-8编码……

而支付宝恰恰是通过在application/x-www-form-urlencoded这个content-type中加入charset参数,指定使用GB编码来发送请求,导致了werkzeug无法处理。

解决方案

为了解决这个问题,我需要修改werkzeug,让它能够取得content-type里的charset参数,并使用这个参数对请求内容进行编码转换。

具体位置在werkzeug的formparser.py中的_parse_urlencoded方法里:

@exhaust_stream
def _parse_urlencoded(self, stream, mimetype, content_length, options):
    if self.max_form_memory_size is not None and \
       content_length is not None and \
       content_length > self.max_form_memory_size:
        raise exceptions.RequestEntityTooLarge()
    form = url_decode_stream(stream, self.charset,
                             errors=self.errors, cls=self.cls)
    return stream, form, self.cls()

可以看到,它是用self.charset进行转换,而这个默认就是utf-8,并且我也不可能把这个默认初始化改成GB编码,毕竟utf-8更通用,所以需要自动切换编码方式。

还好content-type的参数已经被解析好了,就在options参数里,于是,只要在form = url_deocde_stream(...)这句之前加入编码判断即可。

if options:
        self.charset = options.get('charset', self.charset)

最后,我就是在那句前面加了上面两行,解决了这个问题。

后记

后来我把这个修改发了pullrequest给werkzeug,然而被拒绝了,因为作者说这种修改是不符合规范的,应该让支付宝去修复它们的屎(原话)。

所以我去查了W3C的HTML推荐规范(2014-10-28版),事实证明的确如此,规范早就不再支持在POST的application/x-www-form-urlencoded这种content-type里指定参数的做法,支付宝在这里用charset指定编码是不规范的做法。

所以这的确是支付宝的屎。

推送到[go4pro.org]