前两天闲着没事,申请了一个微信公众账号,想自己搭一个回复后台。本来一个很简单的 controller 就可以搞定了,但脑子一热用了 grape(https://github.com/intridea/grape),蛋痛的历程就开始了。
微信公众平台接口用的是 xml,因此仿照 grape 给的示例直接改动 format 为 xml
class MyAPI < Grape::API
version 'v1', :using => :path
format :xml
但回复报错,后来仔细查看文档,需要增加 content_type 说明
Your API can declare which types to support by using content_type. Response format is determined by the request's extension, an explicit format parameter in the query string, or Accept header.
加上 content_type 说明后,现在可以接受 xml post 数据了
class MyAPI < Grape::API
version 'v1', :using => :path
format :xml
content_type :xml, "text/xml"
一开始的时候可以使用 params["xml"]["xxx"] 可以直接获取解析后的 xml post 数据,但是后来不知道是哪个 gem update 了还是怎么回事,传上来的 xml post 数据不能直接映射到 params hash 里面,解析出现了问题。这一点很奇怪,只好抛弃 params 手动解析
body = Hash.from_xml(request.body.read)
以上两条都处理完以后以为可以正常回复了,但是手机上发消息死活还是没有回应。我猜测是不是返回的 xml 格式问题,我采用的是 nokogiri 拼装 xml,会自动带上 xml declaration<? xml version='1.0'?>,原来在做其他平台的 xml api 解析的时候碰到过 xml declaration 影响解析的情况。所以就想方设法在输出中去掉了 xml declaration,但是没效果,仍然发消息没回复。
google 搜索的结果有说道接口回复不能超过 5 秒,否则有可能视为会话超时( http://www.html-js.com/?p=1527 ),我是采用 nginx+passenger 的 server,在 nginx 初次启动 passenger 的时候确实会比较慢。为了调查是否超时,只能抓包看了。
由于微信接口测试只能在线上环境进行,没法使用 wireshark,只能采用 tcpdump 抓包分析
sudo tcpdump -A -s0 host xxx and port 80 -w ~/dump.cap
抓包结果是回复还是很及时的,基本不超过 1 秒钟(只是简单的将用户发来的消息回传回去,未作额外处理)。因此应该不是超时的问题。
似乎走到了绝境了,万般无奈之下只能反复的看抓包结果,无意中发现 grape post 回复的 http status code 不是一般的 200 OK,而是 201 created。会不会是微信平台那边不接受非 200 status code 而拒绝服务?抱着试试看的想法试着加了一行 status 设置
desc "reply"
post do
body = Hash.from_xml(request.body.read)
status("200")
builder = Nokogiri::XML::Builder.new do |x|
...
end
end
终于,手机端看到回复了,当场泪奔啊...
事后分析 grape 源码,发现 post 的时候是默认回复 201 的
def status(status = nil)
if status
@status = status
else
return @status if @status
case request.request_method.to_s.upcase
when 'POST'
201
else
200
end
end
end
再查看 W3C 对 http post 的说明(http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5)
The action performed by the POST method might not result in a resource that can be identified by a URI. In this case, either 200 (OK) or 204 (No Content) is the appropriate response status, depending on whether or not the response includes an entity that describes the result.
If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header
在不创建可以被 URI 定位的资源的时候,应该回复 200 或者 204,在创建了 URI 资源的时候,应该回复 201。
仔细想想,微信发消息虽然是 post,但是也确实也没有创建资源,因此要求 status code 为 200 也没错。
可是微信的文档应该说一下啊!!微信公众平台的狗屁文档(http://mp.weixin.qq.com/cgi-bin/readtemplate?t=wxm-callbackapi-doc&lang=zh_CN)这些细节提都没提。折腾了我好几天!!
最后总结一下:
signature=7f82a86b5f4de67df1d0242793a098a13a3fcffa×tamp=1358324694&nonce=1358209407
这样在 post 的时候反而没法校验请求是否合法。难以理解为什么要这样设计。