├── .gitignore ├── README.markdown ├── lib └── resty │ └── wechat.lua ├── t └── sanity.t └── valgrind.suppress /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | go 5 | t/servroot/ 6 | reindex 7 | *.t_ 8 | tags 9 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-wechat 5 | 6 | Status 7 | ====== 8 | 9 | 项目还处在积极开发一期任务阶段,欢迎兴趣相投者一起参与开发 10 | 11 | Description 12 | =========== 13 | 14 | 微信公众平台lua开发包,基于web服务器[nginx](http://nginx.org/)以及相应的一些扩展和第三方库 15 | 16 | Requirements 17 | ======== 18 | 19 | [ngx_lua](https://github.com/chaoslawful/lua-nginx-module), 20 | [libxml2](http://www.xmlsoft.org/), 21 | [LuaJIT](http://luajit.org) 22 | 23 | Synopsis 24 | ======== 25 | 26 | ```lua 27 | lua_package_path "/path/to/lua-resty-wechat/lib/?.lua;;"; 28 | 29 | server { 30 | location /test { 31 | content_by_lua ' 32 | local wechat = require "resty.wechat" 33 | local ok, err, rcvmsg 34 | local token = "acayf" --填入开发者在微信官网填写的token 35 | local chat = wechat:new(token) 36 | 37 | ok, err = chat:valid() --验证消息可靠性 38 | if not ok then 39 | print("failed to valid message :" .. err) 40 | return 41 | end 42 | 43 | if chat.method == "GET" then --微信第一次验证开发者URL 44 | ngx.print(chat.echostr) 45 | return 46 | end 47 | 48 | rcvmsg, err = chat:parse() --解析出开发者收到的消息 49 | if err then 50 | print("failed to parse message :" .. err) 51 | return 52 | end 53 | 54 | --回复开发者自定义消息 55 | ok, err = chat:reply({msgtype = "text", content = "hello world!"}) 56 | if not ok then 57 | print("failed to reply message :" .. err) 58 | end 59 | '; 60 | } 61 | } 62 | ``` 63 | 备注: 64 | 65 | token在[微信平台上申请](http://mp.weixin.qq.com/cgi-bin/callbackprofile?type=info&t=wxm-developer-ahead&lang=zh_CN) 66 | 67 | 开发者接收到的消息格式和自定义的发送消息格式都在[微信公众平台开发者文档](http://mp.weixin.qq.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5)中有详细描述 68 | 69 | TODO 70 | ==== 71 | 72 | 一期: 73 | 74 | 实现微信官方API 75 | 76 | 二期: 77 | 78 | 实现微信非官方发送API 79 | 80 | 三期: 81 | 82 | 实现微信非官方登陆API 83 | 84 | Authors 85 | ======= 86 | 87 | "plc1989" 88 | 89 | Copyright and License 90 | ===================== 91 | 92 | This module is licensed under the BSD license. 93 | 94 | Copyright (C) 2013-2014, by aCayF (潘力策) . 95 | 96 | All rights reserved. 97 | 98 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 99 | 100 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 101 | 102 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 103 | 104 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 105 | 106 | -------------------------------------------------------------------------------- /lib/resty/wechat.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Lice Pan (aCayF) 2 | 3 | 4 | local sort = table.sort 5 | local concat = table.concat 6 | local lower = string.lower 7 | local format = string.format 8 | local sha1_bin = ngx.sha1_bin 9 | local get_uri_args = ngx.req.get_uri_args 10 | local get_method = ngx.req.get_method 11 | local read_body = ngx.req.read_body 12 | local get_body_data = ngx.req.get_body_data 13 | local gsub = ngx.re.gsub 14 | local setmetatable = setmetatable 15 | local ffi = require "ffi" 16 | local ffi_new = ffi.new 17 | local ffi_str = ffi.string 18 | local C = ffi.C 19 | local print = print 20 | local ngx_print = ngx.print 21 | local ngx_flush = ngx.flush 22 | 23 | 24 | local _M = { _VERSION = '0.1.0' } 25 | 26 | ffi.cdef[[ 27 | typedef unsigned char u_char; 28 | 29 | u_char * ngx_hex_dump(u_char *dst, const u_char *src, size_t len); 30 | 31 | typedef unsigned char xmlChar; 32 | 33 | typedef enum { 34 | XML_ELEMENT_NODE= 1, 35 | XML_TEXT_NODE= 3, 36 | XML_CDATA_SECTION_NODE= 4, 37 | } xmlElementType; 38 | 39 | struct _xmlNode { 40 | void *_private; 41 | xmlElementType type; 42 | const xmlChar *name; 43 | struct _xmlNode *children; 44 | struct _xmlNode *last; 45 | struct _xmlNode *parent; 46 | struct _xmlNode *next; 47 | struct _xmlNode *prev; 48 | struct _xmlDoc *doc; 49 | struct _xmlNs *ns; 50 | xmlChar *content; 51 | struct _xmlAttr *properties; 52 | struct _xmlNs *nsDef; 53 | void *psvi; 54 | unsigned short line; 55 | unsigned short extra; 56 | }; 57 | 58 | struct _xmlDoc { 59 | void *_private; 60 | xmlElementType type; 61 | char *name; 62 | struct _xmlNode *children; 63 | struct _xmlNode *last; 64 | struct _xmlNode *parent; 65 | struct _xmlNode *next; 66 | struct _xmlNode *prev; 67 | struct _xmlDoc *doc; 68 | int compression; 69 | int standalone; 70 | struct _xmlDtd *intSubset; 71 | struct _xmlDtd *extSubset; 72 | struct _xmlNs *oldNs; 73 | const xmlChar *version; 74 | const xmlChar *encoding; 75 | void *ids; 76 | void *refs; 77 | const xmlChar *URL; 78 | int charset; 79 | struct _xmlDict *dict; 80 | void *psvi; 81 | int parseFlags; 82 | int properties; 83 | }; 84 | 85 | struct _xmlDoc * xmlReadMemory(const char * buffe, int size, const char * URL, const char * encoding, int options); 86 | 87 | void xmlFreeDoc(struct _xmlDoc * cur); 88 | 89 | void xmlCleanupParser(void); 90 | 91 | ]] 92 | 93 | local rcvmsgfmt = { 94 | common = {"tousername", "fromusername", "createtime", "msgtype" }, 95 | msgtype = { 96 | text = {"content", "msgid"}, 97 | image = {"picurl", "msgid", "mediaid"}, 98 | voice = {"mediaid", "format", "msgid", {"recognition"}}, 99 | video = {"mediaid", "thumbmediaid", "msgid"}, 100 | location = {"location_x", "location_y", "scale", "label", "msgid"}, 101 | link = {"title", "description", "url", "msgid"}, 102 | event = {"event"} 103 | }, 104 | event = { 105 | subscribe = {{"eventkey"}, {"ticket"}}, 106 | scan = {"eventkey", "ticket"}, 107 | unsubscribe = {{"eventkey"}}, 108 | location = {"latitude", "longitude", "precision"}, 109 | click = {"eventkey"} 110 | } 111 | } 112 | 113 | local sndmsgfmt = { 114 | --{ "nodename", "childnodetype", { ... }, [o=true] } 115 | common = { 116 | {"ToUserName", "c"}, 117 | {"FromUserName", "c"}, 118 | {"CreateTime", "t"}, 119 | {"MsgType", "c"} 120 | }, 121 | text = {{"Content", "c"}}, 122 | image = { 123 | {"Image", "e", { 124 | {"MediaId", "c"} 125 | } 126 | } 127 | }, 128 | voice = { 129 | {"Voice", "e", { 130 | {"MediaId", "c"} 131 | } 132 | } 133 | }, 134 | video = { 135 | {"Video", "e", { 136 | {"MediaId", "c"}, 137 | {"Title", "c", o=true}, 138 | {"Description", "c", o=true} 139 | } 140 | } 141 | }, 142 | music = { 143 | {"Music", "e", { 144 | {"Title", "c"}, 145 | {"Description", "c"}, 146 | {"MusicUrl", "c"}, 147 | {"HQMusicUrl", "c"}, 148 | {"ThumbMediaId", "c", o=true} 149 | } 150 | } 151 | }, 152 | news = { 153 | {"ArticleCount", "t"}, 154 | {"Articles", "e", { 155 | {"item", "e", { 156 | {"Title", "c", o=true}, 157 | {"Description", "c", o=true}, 158 | {"PicUrl", "c", o=true}, 159 | {"Url", "c", o=true} 160 | } 161 | } 162 | } 163 | } 164 | }, 165 | } 166 | 167 | local mt = { __index = _M } 168 | 169 | local lib = ffi.load("xml2") 170 | 171 | local str_type = ffi.typeof("uint8_t[?]") 172 | 173 | 174 | local function _normalize_items(str) 175 | str = gsub(str, "Title[1-9]>", "Title>") 176 | str = gsub(str, "Description[1-9]>", "Description>") 177 | str = gsub(str, "PicUrl[1-9]>", "PicUrl>") 178 | str = gsub(str, "Url[1-9]>", "Url>") 179 | 180 | return str 181 | end 182 | 183 | 184 | local function _insert_items(n) 185 | local newsfmts = sndmsgfmt["news"] 186 | local node = newsfmts[2] 187 | local tb = node[3] 188 | 189 | for i = 1, n - 1 do 190 | local item = {"item", "e", { 191 | {"Title" .. i, "c", o=true}, 192 | {"Description" .. i, "c", o=true}, 193 | {"PicUrl" .. i, "c", o=true}, 194 | {"Url" .. i, "c", o=true} 195 | } 196 | } 197 | -- push 198 | tb[#tb + 1] = item 199 | end 200 | end 201 | 202 | 203 | local function _retrieve_content(sndmsg, fmt) 204 | local name = fmt[1] 205 | name = lower(name) 206 | -- TODO 207 | local content = sndmsg[name] and sndmsg[name] or "" 208 | local optional = fmt.o 209 | 210 | if not optional and content == "" then 211 | return nil, "missing required argment -- " .. name 212 | end 213 | 214 | return content 215 | end 216 | 217 | 218 | local function _format_xml(sndmsg, fmts, str) 219 | for i = 1, #fmts do 220 | local fmt = fmts[i] 221 | local name = fmt[1] 222 | local t = fmt[2] 223 | local subfmts = fmt[3] 224 | local content, err 225 | 226 | if t == "e" then 227 | content, err = _format_xml(sndmsg, subfmts, "") 228 | if err then 229 | return nil, err 230 | end 231 | 232 | if content ~= "" then 233 | content = format("<%s>\n", name) .. content .. format("\n", name) 234 | end 235 | end 236 | 237 | if t == "t" then 238 | content, err = _retrieve_content(sndmsg, fmt) 239 | if err then 240 | return nil, err 241 | end 242 | 243 | if content ~= "" then 244 | content = format("<%s>%s\n", name, content, name) 245 | end 246 | end 247 | 248 | if t == "c" then 249 | content, err = _retrieve_content(sndmsg, fmt) 250 | if err then 251 | return nil, err 252 | end 253 | 254 | if content ~= "" then 255 | content = format("<%s>\n", name, content, name) 256 | end 257 | end 258 | 259 | str = str .. content 260 | end 261 | 262 | return str 263 | end 264 | 265 | 266 | function _M.reply(self, sndmsg) 267 | local msgtype = sndmsg.msgtype 268 | local fmts = sndmsgfmt[msgtype] 269 | local n = sndmsg.articlecount and tonumber(sndmsg.articlecount) or 0 270 | local rcvmsg = self.rcvmsg 271 | local stream, err 272 | 273 | if n > 10 then 274 | return nil, "invalid articlecount" 275 | end 276 | 277 | if not fmts then 278 | return nil, "invalid msgtype" 279 | end 280 | 281 | if not rcvmsg.fromusername 282 | or not rcvmsg.tousername then 283 | return nil, "invalid recieve message" 284 | end 285 | sndmsg.tousername = rcvmsg.fromusername 286 | sndmsg.fromusername = rcvmsg.tousername 287 | -- TODO 288 | sndmsg.createtime = rcvmsg.createtime 289 | 290 | if n > 1 then 291 | _insert_items(n) 292 | end 293 | 294 | stream = "" 295 | stream, err = _format_xml(sndmsg, sndmsgfmt.common, stream) 296 | if err then 297 | return nil, err 298 | end 299 | stream, err = _format_xml(sndmsg, fmts, stream) 300 | if err then 301 | return nil, err 302 | end 303 | stream = stream .. "" 304 | 305 | if n > 1 then 306 | stream = _normalize_items(stream) 307 | end 308 | 309 | ngx_print(stream) 310 | ngx_flush() 311 | self.stream = stream 312 | return true 313 | end 314 | 315 | 316 | local function _parse_key(nodePtr, key, rcvmsg) 317 | local node = nodePtr.node 318 | local name = ffi_str(node[0].name) 319 | local optional = (type(key) == "table") and true or false 320 | local k = optional and key[1] or key 321 | 322 | if lower(name) ~= k then -- case insensitive 323 | if not optional then 324 | return nil, "invalid node name -- " .. name 325 | else 326 | return true 327 | end 328 | end 329 | 330 | if node[0].type ~= lib.XML_ELEMENT_NODE then 331 | return nil, "invalid node type" 332 | end 333 | 334 | node = node[0].children 335 | if node == nil then 336 | return nil, "invalid subnode" 337 | end 338 | 339 | if node[0].type ~= lib.XML_TEXT_NODE 340 | and node[0].type ~= lib.XML_CDATA_SECTION_NODE then 341 | return nil, "invalid subnode type" 342 | end 343 | 344 | rcvmsg[k] = ffi_str(node[0].content) 345 | 346 | node = node[0].parent 347 | if node[0].next ~= nil then 348 | node = node[0].next 349 | end 350 | nodePtr.node = node 351 | 352 | return rcvmsg[k] 353 | end 354 | 355 | 356 | local function _parse_keytable(nodePtr, keytable, rcvmsg) 357 | for i = 1, #keytable do 358 | local key = keytable[i] 359 | 360 | local value, err = _parse_key(nodePtr, key, rcvmsg) 361 | if err then 362 | return nil, err 363 | end 364 | end 365 | 366 | return true 367 | end 368 | 369 | 370 | local function _retrieve_keytable(nodePtr, key, rcvmsg) 371 | local root = rcvmsgfmt.msgtype 372 | 373 | while true do 374 | if not root[key] then 375 | return nil, "invalid key -- " .. key 376 | end 377 | 378 | -- indicates that no subkeys present 379 | if not rcvmsgfmt[key] then 380 | break 381 | end 382 | 383 | local value, err = _parse_key(nodePtr, key, rcvmsg) 384 | if err then 385 | return nil, err 386 | end 387 | 388 | root = rcvmsgfmt[key] 389 | key = value 390 | end 391 | 392 | return root[key] 393 | end 394 | 395 | 396 | local function _parse_xml(node, rcvmsg) 397 | local keytable, ok, err 398 | 399 | -- element node named xml is expected 400 | if node[0].type ~= lib.XML_ELEMENT_NODE 401 | or ffi_str(node[0].name) ~= "xml" then 402 | return nil, "invalid xml title when parsing xml" 403 | end 404 | 405 | if node[0].children == nil then 406 | return nil, "invalid xml content when parsing xml" 407 | end 408 | 409 | -- parse common components 410 | local nodePtr = { node = node[0].children } 411 | keytable = rcvmsgfmt.common 412 | 413 | ok, err = _parse_keytable(nodePtr, keytable, rcvmsg) 414 | if not ok then 415 | return nil, err .. "when parsing common part" 416 | end 417 | 418 | -- retrieve msgtype-specific keytable 419 | keytable, err = _retrieve_keytable(nodePtr, rcvmsg.msgtype, rcvmsg) 420 | if err then 421 | return nil, err .. "when retrieving keytable" 422 | end 423 | 424 | -- parse msgtype-specific components 425 | ok, err = _parse_keytable(nodePtr, keytable, rcvmsg) 426 | if not ok then 427 | return nil, err .. "when parsing msgtype-specific part" 428 | end 429 | 430 | return true 431 | end 432 | 433 | 434 | function _M.parse(self) 435 | local doc = ffi.new("struct _xmlDoc *") 436 | local node = ffi.new("struct _xmlNode *") 437 | local body = self.body 438 | local rcvmsg = self.rcvmsg 439 | 440 | if not body then 441 | return nil, "invalid request body" 442 | end 443 | 444 | doc = lib.xmlReadMemory(body, #body, nil, nil, 0) 445 | if doc == nil then 446 | return nil, "invalid xml data" 447 | end 448 | 449 | -- root node 450 | node = doc[0].children 451 | local ok, err = _parse_xml(node, rcvmsg) 452 | 453 | -- cleanup used memory anyway 454 | lib.xmlFreeDoc(doc) 455 | lib.xmlCleanupParser() 456 | 457 | if not ok then 458 | return nil, err 459 | end 460 | 461 | return rcvmsg 462 | end 463 | 464 | 465 | local function _to_hex(s) 466 | local len = #s * 2 467 | local buf = ffi_new(str_type, len) 468 | C.ngx_hex_dump(buf, s, #s) 469 | return ffi_str(buf, len) 470 | end 471 | 472 | 473 | local function _check_signature(self) 474 | local signature = self.signature 475 | local timestamp = self.timestamp 476 | local nonce = self.nonce 477 | local token = self.token 478 | local tmptab = {token, timestamp, nonce} 479 | sort(tmptab) 480 | 481 | local tmpstr = concat(tmptab) 482 | --print("sorted string: ", tmpstr) 483 | tmpstr = sha1_bin(tmpstr) 484 | tmpstr = _to_hex(tmpstr) 485 | --print("caculated digest: ", tmpstr) 486 | 487 | if tmpstr ~= signature then 488 | return nil, "fade signature" 489 | end 490 | 491 | print("check signature success") 492 | return true 493 | end 494 | 495 | 496 | function _M.valid(self) 497 | if self.method == "GET" and not self.echostr then 498 | return nil, "missing echostr" 499 | end 500 | 501 | --print("received echostr: ", self.echostr) 502 | return _check_signature(self) 503 | end 504 | 505 | 506 | function _M.new(self,token) 507 | local args = get_uri_args() 508 | local method = get_method() 509 | 510 | read_body() 511 | local body = get_body_data() 512 | if body then 513 | body = gsub(body, "[\r\n]*", "", "i") 514 | end 515 | 516 | return setmetatable ({ 517 | signature = args.signature, --case insensitive 518 | timestamp = args.timestamp, 519 | nonce = args.nonce, 520 | echostr = args.echostr, 521 | token = token, 522 | method = method, 523 | body = body, 524 | rcvmsg = {} 525 | }, mt) 526 | end 527 | 528 | 529 | return _M 530 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(1); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | }; 15 | 16 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 17 | 18 | no_long_string(); 19 | no_diff(); 20 | 21 | run_tests(); 22 | 23 | __DATA__ 24 | 25 | === TEST 1: valid 26 | --- http_config eval: $::HttpConfig 27 | --- config 28 | location /t { 29 | content_by_lua ' 30 | local wechat = require "resty.wechat" 31 | local token = "acayf" 32 | local chat = wechat:new(token) 33 | local ok, err 34 | 35 | ok, err = chat:valid() 36 | if not ok then 37 | print("failed to valid message :" .. err) 38 | return 39 | end 40 | 41 | if chat.method == "GET" then 42 | ngx.print(chat.echostr) 43 | end 44 | '; 45 | } 46 | --- raw_request eval 47 | ["GET /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 48 | User-Agent: Mozilla/4.0\r 49 | Accept: */*\r 50 | Host: 101.69.255.134\r 51 | Pragma: no-cache\r 52 | Connection: Keep-Alive\r\n\r\n"] 53 | --- response_body chop 54 | 5961398446273956311 55 | --- abort 56 | --- no_error_log 57 | [error] 58 | 59 | 60 | 61 | === TEST 2: parse text 62 | --- http_config eval: $::HttpConfig 63 | --- config 64 | location /t { 65 | content_by_lua ' 66 | local wechat = require "resty.wechat" 67 | local token = "acayf" 68 | local chat = wechat:new(token) 69 | local ok, err, rcvmsg 70 | 71 | ok, err = chat:valid() 72 | if not ok then 73 | print("failed to valid message :" .. err) 74 | return 75 | end 76 | 77 | if chat.method == "GET" then 78 | ngx.print(chat.echostr) 79 | end 80 | 81 | rcvmsg, err = chat:parse() 82 | if err then 83 | print("failed to parse message :" .. err) 84 | return 85 | end 86 | 87 | for k, v in pairs(rcvmsg) do 88 | ngx.say(k, "=", v) 89 | end 90 | '; 91 | } 92 | --- raw_request eval 93 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 94 | User-Agent: Mozilla/4.0\r 95 | Accept: */*\r 96 | Host: 54.238.220.166\r 97 | Content-Type: text/xml\r 98 | Content-Length: 277\r 99 | Pragma: no-cache\r 100 | Connection: Keep-Alive\r\n\r\n". 101 | ' 102 | 103 | 1389784145 104 | 105 | 106 | 5969077451284321926 107 | '] 108 | --- abort 109 | --- response_body 110 | fromusername=o7El5t8T1myjbxlghgzIw3zASQK4 111 | content=haha 112 | tousername=gh_7f1e8c152f69 113 | createtime=1389784145 114 | msgid=5969077451284321926 115 | msgtype=text 116 | --- no_error_log 117 | [error] 118 | 119 | 120 | 121 | === TEST 3: parse image 122 | --- http_config eval: $::HttpConfig 123 | --- config 124 | location /t { 125 | content_by_lua ' 126 | local wechat = require "resty.wechat" 127 | local token = "acayf" 128 | local chat = wechat:new(token) 129 | local ok, err, rcvmsg 130 | 131 | ok, err = chat:valid() 132 | if not ok then 133 | print("failed to valid message :" .. err) 134 | return 135 | end 136 | 137 | if chat.method == "GET" then 138 | ngx.print(chat.echostr) 139 | end 140 | 141 | rcvmsg, err = chat:parse() 142 | if err then 143 | print("failed to parse message :" .. err) 144 | return 145 | end 146 | 147 | for k, v in pairs(rcvmsg) do 148 | ngx.say(k, "=", v) 149 | end 150 | '; 151 | } 152 | --- raw_request eval 153 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 154 | User-Agent: Mozilla/4.0\r 155 | Accept: */*\r 156 | Host: 54.238.220.166\r 157 | Content-Type: text/xml\r 158 | Content-Length: 488\r 159 | Pragma: no-cache\r 160 | Connection: Keep-Alive\r\n\r\n". 161 | ' 162 | 163 | 1389946192 164 | 165 | 166 | 5969773437849736839 167 | 168 | '] 169 | --- abort 170 | --- response_body 171 | fromusername=o7El5t8T1myjbxlghgzIw3zASQK4 172 | mediaid=IblbErURxw5GlffjlmqJ-y8_-J-H1Nr82b1rQ_BT1mE8pzJm8ZuVLM3Qfcfvl3bD 173 | createtime=1389946192 174 | tousername=gh_7f1e8c152f69 175 | picurl=http://mmbiz.qpic.cn/mmbiz/D8JCEXQrwkeOGMs3ibzB4EgYTib1ZyNIUeN4VSxboQc68w7AFC76WibqQPA5rpR3OJsia9JibMJV9MoX6vfQVrbdmqw/0 176 | msgid=5969773437849736839 177 | msgtype=image 178 | --- no_error_log 179 | [error] 180 | 181 | 182 | 183 | === TEST 4: parse voice 184 | --- http_config eval: $::HttpConfig 185 | --- config 186 | location /t { 187 | content_by_lua ' 188 | local wechat = require "resty.wechat" 189 | local token = "acayf" 190 | local chat = wechat:new(token) 191 | local ok, err, rcvmsg 192 | 193 | ok, err = chat:valid() 194 | if not ok then 195 | print("failed to valid message :" .. err) 196 | return 197 | end 198 | 199 | if chat.method == "GET" then 200 | ngx.print(chat.echostr) 201 | end 202 | 203 | rcvmsg, err = chat:parse() 204 | if err then 205 | print("failed to parse message :" .. err) 206 | return 207 | end 208 | 209 | for k, v in pairs(rcvmsg) do 210 | ngx.say(k, "=", v) 211 | end 212 | '; 213 | } 214 | --- raw_request eval 215 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 216 | User-Agent: Mozilla/4.0\r 217 | Accept: */*\r 218 | Host: 54.238.220.166\r 219 | Content-Type: text/xml\r 220 | Content-Length: 411\r 221 | Pragma: no-cache\r 222 | Connection: Keep-Alive\r\n\r\n". 223 | ' 224 | 225 | 1389960556 226 | 227 | 228 | 229 | 5969835130759976584 230 | 231 | '] 232 | --- abort 233 | --- response_body 234 | fromusername=o7El5t8T1myjbxlghgzIw3zASQK4 235 | format=amr 236 | mediaid=FoAaRyjroJLoRE_tbQjb_5gSzInoWr-GI80WRpLL3ceH1TYs7e3w_C8TNiKEiEoZ 237 | createtime=1389960556 238 | tousername=gh_7f1e8c152f69 239 | recognition= 240 | msgid=5969835130759976584 241 | msgtype=voice 242 | --- no_error_log 243 | [error] 244 | 245 | 246 | 247 | === TEST 5: parse video 248 | --- http_config eval: $::HttpConfig 249 | --- config 250 | location /t { 251 | content_by_lua ' 252 | local wechat = require "resty.wechat" 253 | local token = "acayf" 254 | local chat = wechat:new(token) 255 | local ok, err, rcvmsg 256 | 257 | ok, err = chat:valid() 258 | if not ok then 259 | print("failed to valid message :" .. err) 260 | return 261 | end 262 | 263 | if chat.method == "GET" then 264 | ngx.print(chat.echostr) 265 | end 266 | 267 | rcvmsg, err = chat:parse() 268 | if err then 269 | print("failed to parse message :" .. err) 270 | return 271 | end 272 | 273 | for k, v in pairs(rcvmsg) do 274 | ngx.say(k, "=", v) 275 | end 276 | '; 277 | } 278 | --- raw_request eval 279 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 280 | User-Agent: Mozilla/4.0\r 281 | Accept: */*\r 282 | Host: 54.238.220.166\r 283 | Content-Type: text/xml\r 284 | Content-Length: 444\r 285 | Pragma: no-cache\r 286 | Connection: Keep-Alive\r\n\r\n". 287 | ' 288 | 289 | 1390570723 290 | 291 | 292 | 293 | 5972455778260087218 294 | '] 295 | --- abort 296 | --- response_body 297 | fromusername=o7El5t8T1myjbxlghgzIw3zASQK4 298 | thumbmediaid=184LcL_is-GIU-IZzmgZNE4N4RaKww4JrxMLj82ecqoBZvzK2Zh1L8Gu73r5b9kM 299 | createtime=1390570723 300 | tousername=gh_7f1e8c152f69 301 | mediaid=kVJr1Lxar_ueFiNzrekpbXv0SSuUBIofsS88vi_QR5MhIOvUlw2lCOVQOgEvkYeh 302 | msgid=5972455778260087218 303 | msgtype=video 304 | --- no_error_log 305 | [error] 306 | 307 | 308 | 309 | === TEST 6: parse location 310 | --- http_config eval: $::HttpConfig 311 | --- config 312 | location /t { 313 | content_by_lua ' 314 | local wechat = require "resty.wechat" 315 | local token = "acayf" 316 | local chat = wechat:new(token) 317 | local ok, err, rcvmsg 318 | 319 | ok, err = chat:valid() 320 | if not ok then 321 | print("failed to valid message :" .. err) 322 | return 323 | end 324 | 325 | if chat.method == "GET" then 326 | ngx.print(chat.echostr) 327 | end 328 | 329 | rcvmsg, err = chat:parse() 330 | if err then 331 | print("failed to parse message :" .. err) 332 | return 333 | end 334 | 335 | for k, v in pairs(rcvmsg) do 336 | ngx.say(k, "=", v) 337 | end 338 | '; 339 | } 340 | --- raw_request eval 341 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 342 | User-Agent: Mozilla/4.0\r 343 | Accept: */*\r 344 | Host: 54.238.220.166\r 345 | Content-Type: text/xml\r 346 | Content-Length: 398\r 347 | Pragma: no-cache\r 348 | Connection: Keep-Alive\r\n\r\n". 349 | ' 350 | 351 | 1390283506 352 | 353 | 28.000000 354 | 120.000000 355 | 17 356 | 357 | 5971222190448219785 358 | '] 359 | --- abort 360 | --- response_body 361 | fromusername=o7El5t8T1myjbxlghgzIw3zASQK4 362 | scale=17 363 | tousername=gh_7f1e8c152f69 364 | createtime=1390283506 365 | label=中国浙江省 邮政编码: 300000 366 | msgid=5971222190448219785 367 | msgtype=location 368 | location_x=28.000000 369 | location_y=120.000000 370 | --- no_error_log 371 | [error] 372 | 373 | 374 | 375 | === TEST 7: parse link 376 | --- http_config eval: $::HttpConfig 377 | --- config 378 | location /t { 379 | content_by_lua ' 380 | local wechat = require "resty.wechat" 381 | local token = "acayf" 382 | local chat = wechat:new(token) 383 | local ok, err, rcvmsg 384 | 385 | ok, err = chat:valid() 386 | if not ok then 387 | print("failed to valid message :" .. err) 388 | return 389 | end 390 | 391 | if chat.method == "GET" then 392 | ngx.print(chat.echostr) 393 | end 394 | 395 | rcvmsg, err = chat:parse() 396 | if err then 397 | print("failed to parse message :" .. err) 398 | return 399 | end 400 | 401 | for k, v in pairs(rcvmsg) do 402 | ngx.say(k, "=", v) 403 | end 404 | '; 405 | } 406 | --- raw_request eval 407 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 408 | User-Agent: Mozilla/4.0\r 409 | Accept: */*\r 410 | Host: 54.238.220.166\r 411 | Content-Type: text/xml\r 412 | Content-Length: 369\r 413 | Pragma: no-cache\r 414 | Connection: Keep-Alive\r\n\r\n". 415 | ' 416 | 417 | 1390285815 418 | 419 | <![CDATA[题目]]> 420 | 421 | 422 | 5971232107527706252 423 | '] 424 | --- abort 425 | --- response_body 426 | fromusername=o7El5t8T1myjbxlghgzIw3zASQK4 427 | createtime=1390285815 428 | title=题目 429 | url=http://mp.weixin.qq.com/ 430 | tousername=gh_7f1e8c152f69 431 | description=描述 432 | msgid=5971232107527706252 433 | msgtype=link 434 | --- no_error_log 435 | [error] 436 | 437 | 438 | 439 | === TEST 8: parse event of subscribe 440 | --- http_config eval: $::HttpConfig 441 | --- config 442 | location /t { 443 | content_by_lua ' 444 | local wechat = require "resty.wechat" 445 | local token = "acayf" 446 | local chat = wechat:new(token) 447 | local ok, err, rcvmsg 448 | 449 | ok, err = chat:valid() 450 | if not ok then 451 | print("failed to valid message :" .. err) 452 | return 453 | end 454 | 455 | if chat.method == "GET" then 456 | ngx.print(chat.echostr) 457 | end 458 | 459 | rcvmsg, err = chat:parse() 460 | if err then 461 | print("failed to parse message :" .. err) 462 | return 463 | end 464 | 465 | for k, v in pairs(rcvmsg) do 466 | ngx.say(k, "=", v) 467 | end 468 | '; 469 | } 470 | --- raw_request eval 471 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 472 | User-Agent: Mozilla/4.0\r 473 | Accept: */*\r 474 | Host: 54.238.220.166\r 475 | Content-Type: text/xml\r 476 | Content-Length: 278\r 477 | Pragma: no-cache\r 478 | Connection: Keep-Alive\r\n\r\n". 479 | ' 480 | 481 | 1390283649 482 | 483 | 484 | 485 | '] 486 | --- abort 487 | --- response_body 488 | fromusername=o7El5t8T1myjbxlghgzIw3zASQK4 489 | event=subscribe 490 | tousername=gh_7f1e8c152f69 491 | eventkey= 492 | createtime=1390283649 493 | msgtype=event 494 | --- no_error_log 495 | [error] 496 | 497 | 498 | 499 | === TEST 10: parse event of unsubscribe 500 | --- http_config eval: $::HttpConfig 501 | --- config 502 | location /t { 503 | content_by_lua ' 504 | local wechat = require "resty.wechat" 505 | local token = "acayf" 506 | local chat = wechat:new(token) 507 | local ok, err, rcvmsg 508 | 509 | ok, err = chat:valid() 510 | if not ok then 511 | print("failed to valid message :" .. err) 512 | return 513 | end 514 | 515 | if chat.method == "GET" then 516 | ngx.print(chat.echostr) 517 | end 518 | 519 | rcvmsg, err = chat:parse() 520 | if err then 521 | print("failed to parse message :" .. err) 522 | return 523 | end 524 | 525 | for k, v in pairs(rcvmsg) do 526 | ngx.say(k, "=", v) 527 | end 528 | '; 529 | } 530 | --- raw_request eval 531 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 532 | User-Agent: Mozilla/4.0\r 533 | Accept: */*\r 534 | Host: 54.238.220.166\r 535 | Content-Type: text/xml\r 536 | Content-Length: 280\r 537 | Pragma: no-cache\r 538 | Connection: Keep-Alive\r\n\r\n". 539 | ' 540 | 541 | 1390283618 542 | 543 | 544 | 545 | '] 546 | --- abort 547 | --- response_body 548 | fromusername=o7El5t8T1myjbxlghgzIw3zASQK4 549 | event=unsubscribe 550 | tousername=gh_7f1e8c152f69 551 | eventkey= 552 | createtime=1390283618 553 | msgtype=event 554 | --- no_error_log 555 | [error] 556 | 557 | 558 | 559 | === TEST 13: reply text 560 | --- http_config eval: $::HttpConfig 561 | --- config 562 | location /t { 563 | content_by_lua ' 564 | local wechat = require "resty.wechat" 565 | local token = "acayf" 566 | local chat = wechat:new(token) 567 | local ok, err, rcvmsg 568 | 569 | ok, err = chat:valid() 570 | if not ok then 571 | print("failed to valid message :" .. err) 572 | return 573 | end 574 | 575 | if chat.method == "GET" then 576 | ngx.print(chat.echostr) 577 | return 578 | end 579 | 580 | rcvmsg, err = chat:parse() 581 | if err then 582 | print("failed to parse message :" .. err) 583 | return 584 | end 585 | 586 | ok, err = chat:reply({msgtype = "text", content = rcvmsg.content}) 587 | if not ok then 588 | print("failed to reply message :" .. err) 589 | end 590 | '; 591 | } 592 | --- raw_request eval 593 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 594 | User-Agent: Mozilla/4.0\r 595 | Accept: */*\r 596 | Host: 54.238.220.166\r 597 | Content-Type: text/xml\r 598 | Content-Length: 277\r 599 | Pragma: no-cache\r 600 | Connection: Keep-Alive\r\n\r\n". 601 | ' 602 | 603 | 1389784145 604 | 605 | 606 | 5969077451284321926 607 | '] 608 | --- abort 609 | --- response_body chop 610 | 611 | 612 | 1389784145 613 | 614 | 615 | 616 | --- no_error_log 617 | [error] 618 | 619 | 620 | 621 | === TEST 14: reply image 622 | --- http_config eval: $::HttpConfig 623 | --- config 624 | location /t { 625 | content_by_lua ' 626 | local wechat = require "resty.wechat" 627 | local token = "acayf" 628 | local chat = wechat:new(token) 629 | local ok, err, rcvmsg 630 | 631 | ok, err = chat:valid() 632 | if not ok then 633 | print("failed to valid message :" .. err) 634 | return 635 | end 636 | 637 | if chat.method == "GET" then 638 | ngx.print(chat.echostr) 639 | return 640 | end 641 | 642 | rcvmsg, err = chat:parse() 643 | if err then 644 | print("failed to parse message :" .. err) 645 | return 646 | end 647 | 648 | ok, err = chat:reply({msgtype = "image", mediaid = rcvmsg.mediaid}) 649 | if not ok then 650 | print("failed to reply message :" .. err) 651 | end 652 | '; 653 | } 654 | --- raw_request eval 655 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 656 | User-Agent: Mozilla/4.0\r 657 | Accept: */*\r 658 | Host: 54.238.220.166\r 659 | Content-Type: text/xml\r 660 | Content-Length: 488\r 661 | Pragma: no-cache\r 662 | Connection: Keep-Alive\r\n\r\n". 663 | ' 664 | 665 | 1389946192 666 | 667 | 668 | 5969773437849736839 669 | 670 | '] 671 | --- abort 672 | --- response_body chop 673 | 674 | 675 | 1389946192 676 | 677 | 678 | 679 | 680 | 681 | --- no_error_log 682 | [error] 683 | 684 | 685 | 686 | === TEST 15: reply voice 687 | --- http_config eval: $::HttpConfig 688 | --- config 689 | location /t { 690 | content_by_lua ' 691 | local wechat = require "resty.wechat" 692 | local token = "acayf" 693 | local chat = wechat:new(token) 694 | local ok, err, rcvmsg 695 | 696 | ok, err = chat:valid() 697 | if not ok then 698 | print("failed to valid message :" .. err) 699 | return 700 | end 701 | 702 | if chat.method == "GET" then 703 | ngx.print(chat.echostr) 704 | return 705 | end 706 | 707 | rcvmsg, err = chat:parse() 708 | if err then 709 | print("failed to parse message :" .. err) 710 | return 711 | end 712 | 713 | ok, err = chat:reply({msgtype = "voice", mediaid = rcvmsg.mediaid}) 714 | if not ok then 715 | print("failed to reply message :" .. err) 716 | end 717 | '; 718 | } 719 | --- raw_request eval 720 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 721 | User-Agent: Mozilla/4.0\r 722 | Accept: */*\r 723 | Host: 54.238.220.166\r 724 | Content-Type: text/xml\r 725 | Content-Length: 411\r 726 | Pragma: no-cache\r 727 | Connection: Keep-Alive\r\n\r\n". 728 | ' 729 | 730 | 1389960556 731 | 732 | 733 | 734 | 5969835130759976584 735 | 736 | '] 737 | --- abort 738 | --- response_body chop 739 | 740 | 741 | 1389960556 742 | 743 | 744 | 745 | 746 | 747 | --- no_error_log 748 | [error] 749 | 750 | 751 | 752 | === TEST 16: reply video 753 | --- http_config eval: $::HttpConfig 754 | --- config 755 | location /t { 756 | content_by_lua ' 757 | local wechat = require "resty.wechat" 758 | local token = "acayf" 759 | local chat = wechat:new(token) 760 | local ok, err, rcvmsg 761 | 762 | ok, err = chat:valid() 763 | if not ok then 764 | print("failed to valid message :" .. err) 765 | return 766 | end 767 | 768 | if chat.method == "GET" then 769 | ngx.print(chat.echostr) 770 | return 771 | end 772 | 773 | rcvmsg, err = chat:parse() 774 | if err then 775 | print("failed to parse message :" .. err) 776 | return 777 | end 778 | 779 | ok, err = chat:reply({msgtype = "video", 780 | mediaid = "cLoswtrpaMwdEfYRChivdajclcZULGMJ0J8a9M8W5_3tltrV23qkBm2sNanZIbwU", 781 | title = "title", 782 | description = "description"}) 783 | if not ok then 784 | print("failed to reply message :" .. err) 785 | end 786 | '; 787 | } 788 | --- raw_request eval 789 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 790 | User-Agent: Mozilla/4.0\r 791 | Accept: */*\r 792 | Host: 54.238.220.166\r 793 | Content-Type: text/xml\r 794 | Content-Length: 444\r 795 | Pragma: no-cache\r 796 | Connection: Keep-Alive\r\n\r\n". 797 | ' 798 | 799 | 1390570723 800 | 801 | 802 | 803 | 5972455778260087218 804 | '] 805 | --- abort 806 | --- response_body chop 807 | 808 | 809 | 1390570723 810 | 811 | 816 | 817 | --- no_error_log 818 | 819 | 820 | 821 | [error] 822 | === TEST 17: reply music 823 | --- http_config eval: $::HttpConfig 824 | --- config 825 | location /t { 826 | content_by_lua ' 827 | local wechat = require "resty.wechat" 828 | local token = "acayf" 829 | local chat = wechat:new(token) 830 | local ok, err, rcvmsg 831 | 832 | ok, err = chat:valid() 833 | if not ok then 834 | print("failed to valid message :" .. err) 835 | return 836 | end 837 | 838 | if chat.method == "GET" then 839 | ngx.print(chat.echostr) 840 | return 841 | end 842 | 843 | rcvmsg, err = chat:parse() 844 | if err then 845 | print("failed to parse message :" .. err) 846 | return 847 | end 848 | 849 | ok, err = chat:reply({msgtype = "music", title = "title", 850 | description = "description", 851 | musicurl = "http://mp3.com/test.mp3", 852 | hqmusicurl = "http://mp3.com/test.mp3"}) 853 | if not ok then 854 | print("failed to reply message :" .. err) 855 | end 856 | '; 857 | } 858 | --- raw_request eval 859 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 860 | User-Agent: Mozilla/4.0\r 861 | Accept: */*\r 862 | Host: 54.238.220.166\r 863 | Content-Type: text/xml\r 864 | Content-Length: 411\r 865 | Pragma: no-cache\r 866 | Connection: Keep-Alive\r\n\r\n". 867 | ' 868 | 869 | 1389960556 870 | 871 | 872 | 873 | 5969835130759976584 874 | 875 | '] 876 | --- abort 877 | --- response_body chop 878 | 879 | 880 | 1389960556 881 | 882 | 883 | <![CDATA[title]]> 884 | 885 | 886 | 887 | 888 | 889 | --- no_error_log 890 | [error] 891 | 892 | 893 | 894 | === TEST 18: reply news 1 895 | --- http_config eval: $::HttpConfig 896 | --- config 897 | location /t { 898 | content_by_lua ' 899 | local wechat = require "resty.wechat" 900 | local token = "acayf" 901 | local chat = wechat:new(token) 902 | local ok, err, rcvmsg 903 | 904 | ok, err = chat:valid() 905 | if not ok then 906 | print("failed to valid message :" .. err) 907 | return 908 | end 909 | 910 | if chat.method == "GET" then 911 | ngx.print(chat.echostr) 912 | return 913 | end 914 | 915 | rcvmsg, err = chat:parse() 916 | if err then 917 | print("failed to parse message :" .. err) 918 | return 919 | end 920 | 921 | ok, err = chat:reply({msgtype = "news", articlecount = 1, 922 | title = "title", picurl = "picurl", url = "url"}) 923 | if not ok then 924 | print("failed to reply message :" .. err) 925 | end 926 | '; 927 | } 928 | --- raw_request eval 929 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 930 | User-Agent: Mozilla/4.0\r 931 | Accept: */*\r 932 | Host: 54.238.220.166\r 933 | Content-Type: text/xml\r 934 | Content-Length: 277\r 935 | Pragma: no-cache\r 936 | Connection: Keep-Alive\r\n\r\n". 937 | ' 938 | 939 | 1389784145 940 | 941 | 942 | 5969077451284321926 943 | '] 944 | --- abort 945 | --- response_body chop 946 | 947 | 948 | 1389784145 949 | 950 | 1 951 | 952 | 953 | <![CDATA[title]]> 954 | 955 | 956 | 957 | 958 | 959 | --- no_error_log 960 | [error] 961 | 962 | 963 | 964 | === TEST 19: reply news 2 965 | --- http_config eval: $::HttpConfig 966 | --- config 967 | location /t { 968 | content_by_lua ' 969 | local wechat = require "resty.wechat" 970 | local token = "acayf" 971 | local chat = wechat:new(token) 972 | local ok, err, rcvmsg 973 | 974 | ok, err = chat:valid() 975 | if not ok then 976 | print("failed to valid message :" .. err) 977 | return 978 | end 979 | 980 | if chat.method == "GET" then 981 | ngx.print(chat.echostr) 982 | return 983 | end 984 | 985 | rcvmsg, err = chat:parse() 986 | if err then 987 | print("failed to parse message :" .. err) 988 | return 989 | end 990 | 991 | ok, err = chat:reply({msgtype = "news", articlecount = 2, 992 | title = "title", picurl = "picurl", url = "url", 993 | title1 = "title1", picurl1 = "picurl1", url1 = "url1"}) 994 | if not ok then 995 | print("failed to reply message :" .. err) 996 | end 997 | '; 998 | } 999 | --- raw_request eval 1000 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 1001 | User-Agent: Mozilla/4.0\r 1002 | Accept: */*\r 1003 | Host: 54.238.220.166\r 1004 | Content-Type: text/xml\r 1005 | Content-Length: 277\r 1006 | Pragma: no-cache\r 1007 | Connection: Keep-Alive\r\n\r\n". 1008 | ' 1009 | 1010 | 1389784145 1011 | 1012 | 1013 | 5969077451284321926 1014 | '] 1015 | --- abort 1016 | --- response_body chop 1017 | 1018 | 1019 | 1389784145 1020 | 1021 | 2 1022 | 1023 | 1024 | <![CDATA[title]]> 1025 | 1026 | 1027 | 1028 | 1029 | <![CDATA[title1]]> 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | --- no_error_log 1036 | [error] 1037 | 1038 | 1039 | 1040 | === TEST 20: reply news 3 1041 | --- http_config eval: $::HttpConfig 1042 | --- config 1043 | location /t { 1044 | content_by_lua ' 1045 | local wechat = require "resty.wechat" 1046 | local token = "acayf" 1047 | local chat = wechat:new(token) 1048 | local ok, err, rcvmsg 1049 | 1050 | ok, err = chat:valid() 1051 | if not ok then 1052 | print("failed to valid message :" .. err) 1053 | return 1054 | end 1055 | 1056 | if chat.method == "GET" then 1057 | ngx.print(chat.echostr) 1058 | return 1059 | end 1060 | 1061 | rcvmsg, err = chat:parse() 1062 | if err then 1063 | print("failed to parse message :" .. err) 1064 | return 1065 | end 1066 | 1067 | ok, err = chat:reply({msgtype = "news", articlecount = 3, 1068 | title = "title", picurl = "picurl", url = "url", 1069 | title1 = "title1", picurl1 = "picurl1", url1 = "url1", 1070 | title2 = "title2", picurl2 = "picurl2", url2 = "url2"}) 1071 | if not ok then 1072 | print("failed to reply message :" .. err) 1073 | end 1074 | '; 1075 | } 1076 | --- raw_request eval 1077 | ["POST /t?signature=9c32c80661e38ff5f5bc88cad6f7195d7ad93824&echostr=5961398446273956311×tamp=1387891159&nonce=1387996234 HTTP/1.0\r 1078 | User-Agent: Mozilla/4.0\r 1079 | Accept: */*\r 1080 | Host: 54.238.220.166\r 1081 | Content-Type: text/xml\r 1082 | Content-Length: 277\r 1083 | Pragma: no-cache\r 1084 | Connection: Keep-Alive\r\n\r\n". 1085 | ' 1086 | 1087 | 1389784145 1088 | 1089 | 1090 | 5969077451284321926 1091 | '] 1092 | --- abort 1093 | --- response_body chop 1094 | 1095 | 1096 | 1389784145 1097 | 1098 | 3 1099 | 1100 | 1101 | <![CDATA[title]]> 1102 | 1103 | 1104 | 1105 | 1106 | <![CDATA[title1]]> 1107 | 1108 | 1109 | 1110 | 1111 | <![CDATA[title2]]> 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | --- no_error_log 1118 | [error] 1119 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | # Valgrind suppression file for LuaJIT 2.0. 2 | { 3 | Optimized string compare 4 | Memcheck:Addr4 5 | fun:lj_str_cmp 6 | } 7 | { 8 | Optimized string compare 9 | Memcheck:Addr1 10 | fun:lj_str_cmp 11 | } 12 | { 13 | Optimized string compare 14 | Memcheck:Addr4 15 | fun:lj_str_new 16 | } 17 | { 18 | Optimized string compare 19 | Memcheck:Addr1 20 | fun:lj_str_new 21 | } 22 | { 23 | Optimized string compare 24 | Memcheck:Cond 25 | fun:lj_str_new 26 | } 27 | { 28 | 29 | Memcheck:Param 30 | epoll_ctl(event) 31 | fun:epoll_ctl 32 | } 33 | --------------------------------------------------------------------------------