Ruby 用 Grape 搭建微信公众平台回复 API 的蛋痛经过

alucardpj · 2013年01月16日 · 最后由 outman 回复于 2016年03月17日 · 11962 次阅读

前两天闲着没事,申请了一个微信公众账号,想自己搭一个回复后台。本来一个很简单的 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)这些细节提都没提。折腾了我好几天!!

最后总结一下:

  1. 微信的公众平台接口很不完善:
    1. 只支持 80 端口,自定义端口还不支持,我实在是无法理解这个有什么技术难度。
    2. get 方法进行接口校验基本是个摆设,传给一堆参数,还假模假样的设置了一个 token 进行校验,其实我不判断校验直接返回 echostr 也一点事儿都没有。而在真正使用 post 方法传消息的时候却没有给 echostr 进行校验,只传了以下参数 signature=7f82a86b5f4de67df1d0242793a098a13a3fcffa&timestamp=1358324694&nonce=1358209407 这样在 post 的时候反而没法校验请求是否合法。难以理解为什么要这样设计。
    3. 文档很不完善,像 “5 秒超时” 和 “http status code = 200” 这些细节一点都没提,只有开发人员自己去摸索。真是写给经理看的开发文档。
  2. grape api 框架写的很完善,但是有的地方框的太死了,文档也说得不是很清楚,对 json 说的比较多,而 xml 部分基本没提。对于接口调试没有方便的界面来查错,最后只好抓包调试,代价有点高。
  3. 杀鸡还是焉用牛刀,对于这么小的需求,其实一个 controller/action 就能搞定的事情折腾出这么多事儿来。太相信框架/组件也不是什么好事...

@huacnlee ruby-china 编辑器里怎么做多级列表啊,试了试 markdown 标准语法 “*”,但是好像不起作用。最后一段实在不知道怎么编辑了。

其实直接用 sinatra 挺好, grap 限制太大

#1 楼 @alucardpj 每级列表第一个字要缩进四格。比如 1. 的话,后面再加 2 个空格

1.  test a
    1.  item a
    1.  item b
1.  test b
  1. test a
    1. item a
    2. item b
  2. test b

#4 楼 @doitian 已编辑,多谢

总结 就第三条 +1 嘿嘿 我也用 sinatra 写了一个 微信公众平台的关键字回复的 service 结果发现还不支持 全图消息

楼主辛苦了,给你顶下!

#6 楼 @jhjguxin 我用 Sinatra 尝试写过。。但是没成功。。直接找了一个 PHP 的代码改了改。。

你的代码能分享下吗,或者说如何实现微信的接口?照文档来看确实不难,不过我没试成功

突然发现我认识楼主和楼主大学同班同学啊,世界真小真蛋疼啊⋯⋯

#12 楼 @sillybirdustc 我 leng,你也搞 rails?

@alucardpj 刚进入一个新组,弄 ruby 和 html5,就顺便到处看看……发现我木有你的联系方式,给你留个邮箱,把你的 qq 或常用联系方式给我一个吧~~~sillybirdustc@gmail.com

@alucardpj 关于你终结的第 1 条第 2 点,signature timestamp nonce,每次用户 post 过来信息后,都需要和 token 做验证的,来判断是否是微信发过来的消息,echostr 是绑定修改接入网站信息时用到的,同样也需要 signature timestamp nonce 和 token 做验证

Cnode 那边有个微信机器人

这两天试了一下用 Rails 做个机器人,发现也很简单,什么额外的 Gem 都不用装,用 Grape 太折腾了

#17 楼 @edokeh 能分享下代码么?

嗨,大佬您好,我是一个没基础的大学生……也许打扰到您了,但我的确很想学习微信应用和平台的开发,不知道可不可以问您一些问题,或者能有幸与您合作?请加我的 QQ 吧~794079189

愣,在 lz 大学同学后面发现了 ustc..

用 header "HTTP/1.1 200 OK" 不起效,还是得用 status("200")

status 的源码没找到

def status(status = nil); end

有全局的方法 修改 post 的返回值么

客户端 只是判断 200 的状态 每个 post 方法 都要 status(200) 手都残了

@allenwei Grape 限制大,现在不会 SDL 内部调用,请问有方案吗? @alucardpj 既然是 RESTful, status code 应第一时间发现

好像不是 201,200 的问题,我们这里是好的。只是微信会不断通知,搞得不知道是不是返回给它的 xml 有问题。但问题是好像没有问题。

fahchen 依然是蛋疼的微信 API 中提及了此贴 04月03日 10:56
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册