rack-weixin 0.0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+ source 'http://ruby.taobao.org'
3
+
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-weixin (0.0.3.1)
5
+ multi_xml (>= 0.5.2)
6
+ rack
7
+ roxml
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ remote: http://ruby.taobao.org/
12
+ specs:
13
+ activesupport (3.2.12)
14
+ i18n (~> 0.6)
15
+ multi_json (~> 1.0)
16
+ diff-lcs (1.1.3)
17
+ i18n (0.6.2)
18
+ multi_json (1.6.1)
19
+ multi_xml (0.5.3)
20
+ nokogiri (1.5.6)
21
+ rack (1.5.2)
22
+ rack-test (0.6.2)
23
+ rack (>= 1.0)
24
+ rake (10.0.3)
25
+ roxml (3.3.1)
26
+ activesupport (>= 2.3.0)
27
+ nokogiri (>= 1.3.3)
28
+ rspec (2.12.0)
29
+ rspec-core (~> 2.12.0)
30
+ rspec-expectations (~> 2.12.0)
31
+ rspec-mocks (~> 2.12.0)
32
+ rspec-core (2.12.2)
33
+ rspec-expectations (2.12.1)
34
+ diff-lcs (~> 1.1.3)
35
+ rspec-mocks (2.12.2)
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ rack-test
42
+ rack-weixin!
43
+ rake
44
+ rspec (>= 2.0.0)
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ 微信公众平台 开放消息接口 Rack Middlware
2
+ ========================================
3
+
4
+ * 验证微信请求 with 'weixin/middleware'
5
+ * 解析推送消息 with 'weixin/model'
6
+ * 生成回复消息 with 'weixin/model'
7
+
8
+
9
+ Installation
10
+ ------------
11
+ ```
12
+ $ gem install rack-weixin
13
+ ```
14
+
15
+
16
+ Usage
17
+ -----
18
+
19
+ A sinatra demo
20
+
21
+ ```ruby
22
+ # -*- encoding : utf-8 -*-
23
+ require 'sinatra'
24
+ require 'weixin/middleware'
25
+ require 'weixin/model'
26
+
27
+ use Weixin::Middleware, 'your api token', '/your_app_root'
28
+
29
+ configure do
30
+ set :wx_id, 'your_weixin_account'
31
+ end
32
+
33
+ helpers do
34
+
35
+ def create_message(from, to, type, content, flag=0)
36
+ msg = Weixin::TextReplyMessage.new
37
+ msg.ToUserName = to
38
+ msg.FromUserName = from
39
+ msg.Content = content
40
+ msg.to_xml
41
+ end
42
+ end
43
+
44
+ get '/your_app_root' do
45
+ params[:echostr]
46
+ end
47
+
48
+ post '/your_app_root' do
49
+ content_type :xml, 'charset' => 'utf-8'
50
+
51
+ message = request.env[Weixin::Middleware::WEIXIN_MSG]
52
+ logger.info "message: #{request.env[Weixin::Middleware::WEIXIN_MSG_RAW]}"
53
+
54
+ from = message.FromUserName
55
+ if message.class == Weixin::TextMessage
56
+ content = message.Content
57
+ if content == 'Hello2BizUser'
58
+ reply_msg_content = "感谢关注!#{reply_msg_content}"
59
+ end
60
+ end
61
+
62
+ create_message(settings.wx_id, from, 'text', reply_msg_content)
63
+ end
64
+ ```
65
+
66
+ TODO
67
+ ----
68
+
69
+ Copyright
70
+ ---------
71
+
72
+ Permission is hereby granted, free of charge, to any person obtaining a copy
73
+ of this software and associated documentation files (the "Software"), to
74
+ deal in the Software without restriction, including without limitation the
75
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
76
+ sell copies of the Software, and to permit persons to whom the Software is
77
+ furnished to do so, subject to the following conditions:
78
+
79
+ The above copyright notice and this permission notice shall be included in
80
+ all copies or substantial portions of the Software.
81
+
82
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
83
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
84
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
85
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
86
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
87
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ desc 'Run all the tests'
5
+ task :default => [:test]
6
+
7
+ desc "Run all the tests"
8
+ task :test do
9
+ sh "rspec -Ilib/*:spec spec"
10
+ end
11
+
@@ -0,0 +1,62 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'digest/sha1'
3
+
4
+ module Weixin
5
+
6
+ class Middleware
7
+
8
+ POST_BODY = 'rack.input'.freeze
9
+ WEIXIN_MSG = 'weixin.msg'.freeze
10
+ WEIXIN_MSG_RAW = 'weixin.msg.raw'.freeze
11
+
12
+ def initialize(app, app_token, path)
13
+ @app = app
14
+ @app_token = app_token
15
+ @path = path
16
+ end
17
+
18
+ def call(env)
19
+ dup._call(env)
20
+ end
21
+
22
+ def _call(env)
23
+ if @path == env['PATH_INFO'].to_s && ['GET', 'POST'].include?(env['REQUEST_METHOD'])
24
+
25
+ @req = Rack::Request.new(env)
26
+ return invalid_request! unless request_is_valid?
27
+ return [
28
+ 200,
29
+ { 'Content-type' => 'text/plain', 'Content-length' => @req.params['echostr'].length.to_s },
30
+ [ @req.params['echostr'] ]
31
+ ] if @req.get?
32
+
33
+ raw_msg = env[POST_BODY].read
34
+ begin
35
+ env.update WEIXIN_MSG => Weixin::Message.factory(raw_msg), WEIXIN_MSG_RAW => env[POST_BODY]
36
+ @app.call(env)
37
+ rescue Exception => e
38
+ return [500, { 'Content-typ' => 'text/html' }, ["Message parsing error: #{e.to_s}"]]
39
+ end
40
+ else
41
+ @app.call(env)
42
+ end
43
+
44
+ end
45
+
46
+ def invalid_request!
47
+ [401, { 'Content-type' => 'text/html', 'Content-Length' => '0'}, []]
48
+ end
49
+
50
+ def request_is_valid?
51
+ begin
52
+ param_array = [@app_token, @req.params['timestamp'], @req.params['nonce']]
53
+ sign = Digest::SHA1.hexdigest( param_array.sort.join )
54
+ sign == @req.params['signature'] ? true : false
55
+ rescue
56
+ false
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+
@@ -0,0 +1,146 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'roxml'
3
+ require 'multi_xml'
4
+ require 'ostruct'
5
+
6
+ module Weixin
7
+
8
+ class Message
9
+
10
+ def initialize(hash)
11
+ @source = OpenStruct.new(hash)
12
+ end
13
+
14
+ def method_missing(method, *args, &block)
15
+ @source.send(method, *args, &block)
16
+ end
17
+
18
+ def CreateTime
19
+ @source.CreateTime.to_i
20
+ end
21
+
22
+ def MsgId
23
+ @source.MsgId.to_i
24
+ end
25
+
26
+ def Message.factory(xml)
27
+ hash = MultiXml.parse(xml)['xml']
28
+ case hash['MsgType']
29
+ when 'text'
30
+ TextMessage.new(hash)
31
+ when 'image'
32
+ ImageMessage.new(hash)
33
+ when 'location'
34
+ LocationMessage.new(hash)
35
+ when 'link'
36
+ LinkMessage.new(hash)
37
+ when 'event'
38
+ EventMessage.new(hash)
39
+ else
40
+ raise ArgumentError, 'Unknown Message'
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ TextMessage = Class.new(Message)
47
+ ImageMessage = Class.new(Message)
48
+ LinkMessage = Class.new(Message)
49
+
50
+ class LocationMessage < Message
51
+
52
+ def Location_X
53
+ @source.Location_X.to_f
54
+ end
55
+
56
+ def Location_Y
57
+ @source.Location_Y.to_f
58
+ end
59
+
60
+ def Scale
61
+ @source.Scale.to_i
62
+ end
63
+ end
64
+
65
+ class EventMessage < Message
66
+
67
+ def Latitude
68
+ @source.Latitude.to_f
69
+ end
70
+
71
+ def Longitude
72
+ @source.Longitude.to_f
73
+ end
74
+
75
+ def Precision
76
+ @source.Precision.to_f
77
+ end
78
+ end
79
+
80
+ class ReplyMessage
81
+ include ROXML
82
+ xml_name :xml
83
+ #xml_convention :camelcase
84
+
85
+ xml_accessor :ToUserName, :cdata => true
86
+ xml_accessor :FromUserName, :cdata => true
87
+ xml_reader :CreateTime, :as => Integer
88
+ xml_reader :MsgType, :cdata => true
89
+ xml_accessor :FuncFlag, :as => Integer
90
+
91
+ def initialize
92
+ @CreateTime = Time.now.to_i
93
+ @FuncFlag = 0
94
+ end
95
+
96
+ def to_xml
97
+ super.to_xml(:encoding => 'UTF-8', :indent => 0, :save_with => 0)
98
+ end
99
+ end
100
+
101
+ class TextReplyMessage < ReplyMessage
102
+ xml_accessor :Content, :cdata => true
103
+
104
+ def initialize
105
+ super
106
+ @MsgType = 'text'
107
+ end
108
+ end
109
+
110
+ class Music
111
+ include ROXML
112
+
113
+ xml_accessor :Title, :cdata => true
114
+ xml_accessor :Description, :cdata => true
115
+ xml_accessor :MusicUrl, :cdata => true
116
+ xml_accessor :HQMusicUrl, :cdata => true
117
+ end
118
+
119
+ class MusicReplyMessage < ReplyMessage
120
+ xml_accessor :Music, :as => Music
121
+
122
+ def initialize
123
+ super
124
+ @MsgType = 'music'
125
+ end
126
+ end
127
+
128
+ class Item
129
+ include ROXML
130
+
131
+ xml_accessor :Title, :cdata => true
132
+ xml_accessor :Description, :cdata => true
133
+ xml_accessor :PicUrl, :cdata => true
134
+ xml_accessor :Url, :cdata => true
135
+ end
136
+
137
+ class NewsReplyMessage < ReplyMessage
138
+ xml_accessor :ArticleCount, :as => Integer
139
+ xml_accessor :Articles, :as => [Item], :in => 'Articles', :from => 'Item'
140
+
141
+ def initialize
142
+ super
143
+ @MsgType = 'news'
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,3 @@
1
+ module Weixin
2
+ VERSION = "0.0.3.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "weixin/version"
3
+
4
+ Gem::Specification.new do |s|
5
+
6
+ s.name = 'rack-weixin'
7
+ s.version = Weixin::VERSION
8
+
9
+ s.description = 'Rack middleware for Weixin apps: message validation/parser/generator'
10
+ s.summary = 'Rack middleware for Weixin apps'
11
+
12
+ s.authors = ['wolfg1969']
13
+ s.email = 'wolfg1969@gmail.com'
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec\.rb/}
17
+
18
+ s.add_dependency 'rack'
19
+ s.add_dependency 'multi_xml', '>= 0.5.2'
20
+ s.add_dependency 'roxml'
21
+ s.add_development_dependency 'rake'
22
+ s.add_development_dependency 'rack-test'
23
+ s.add_development_dependency 'rspec', '>= 2.0.0'
24
+
25
+ s.homepage = 'https://github.com/wolfg1969/rack-weixin'
26
+ s.require_paths = %w[lib]
27
+ end
@@ -0,0 +1,104 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'rspec'
3
+ require 'weixin/model'
4
+
5
+ describe 'weixin/model' do
6
+
7
+ context 'Weixin:Message' do
8
+ it 'is a text message' do
9
+ msg = Weixin::Message.factory(%(
10
+ <xml>
11
+ <ToUserName><![CDATA[to]]></ToUserName>
12
+ <FromUserName><![CDATA[from]]></FromUserName>
13
+ <CreateTime>1360391199</CreateTime>
14
+ <MsgType><![CDATA[text]]></MsgType>
15
+ <Content><![CDATA[Hello2BizUser]]></Content>
16
+ <MsgId>5842835709471227904</MsgId>
17
+ </xml>
18
+ ))
19
+ msg.class.should == Weixin::TextMessage
20
+ msg.MsgType.should == 'text'
21
+ msg.ToUserName.should == 'to'
22
+ msg.FromUserName.should == 'from'
23
+ msg.CreateTime.should == 1360391199
24
+ msg.Content.should == 'Hello2BizUser'
25
+ msg.MsgId.should == 5842835709471227904
26
+ end
27
+
28
+ it 'is a image message' do
29
+ msg = Weixin::Message.factory(%(
30
+ <xml>
31
+ <ToUserName><![CDATA[to]]></ToUserName>
32
+ <FromUserName><![CDATA[from]]></FromUserName>
33
+ <CreateTime>1360391199</CreateTime>
34
+ <MsgType><![CDATA[image]]></MsgType>
35
+ <PicUrl><![CDATA[http://mmsns.qpic.cn/mmsns/Leiaa5NQF4FOTOSo3hXrEsGsodU2jHcWZiaInTxmTh6GaCIJ8hBHicIDA/0]]></PicUrl>
36
+ <MsgId>5842835709471227904</MsgId>
37
+ </xml>
38
+ ))
39
+ msg.class.should == Weixin::ImageMessage
40
+ msg.MsgType.should == 'image'
41
+ msg.ToUserName.should == 'to'
42
+ msg.FromUserName.should == 'from'
43
+ msg.CreateTime.should == 1360391199
44
+ msg.MsgId.should == 5842835709471227904
45
+ msg.PicUrl.should_not nil
46
+ end
47
+
48
+ it 'is a location message' do
49
+ msg = Weixin::Message.factory(%(
50
+ <xml>
51
+ <ToUserName><![CDATA[to]]></ToUserName>
52
+ <FromUserName><![CDATA[from]]></FromUserName>
53
+ <CreateTime>1360391199</CreateTime>
54
+ <MsgType><![CDATA[location]]></MsgType>
55
+ <Location_X>69.866013</Location_X>
56
+ <Location_Y>136.269449</Location_Y>
57
+ <Scale>15</Scale>
58
+ <Label><![CDATA[somewhere]]></Label>
59
+ <MsgId>5842835709471227904</MsgId>
60
+ </xml>
61
+ ))
62
+ msg.class.should == Weixin::LocationMessage
63
+ msg.MsgType.should == 'location'
64
+ msg.ToUserName.should == 'to'
65
+ msg.FromUserName.should == 'from'
66
+ msg.CreateTime.should == 1360391199
67
+ msg.MsgId.should == 5842835709471227904
68
+ msg.Label.should_not nil
69
+ msg.Scale.should == 15
70
+ msg.Location_X.should == 69.866013
71
+ msg.Location_Y.should == 136.269449
72
+ end
73
+
74
+ end
75
+
76
+ context 'Weixin::ReplyMessage' do
77
+
78
+ it 'is a text reply message' do
79
+ msg = Weixin::TextReplyMessage.new
80
+ msg.ToUserName = 'to'
81
+ msg.FromUserName = 'from'
82
+ msg.Content = 'blah'
83
+ msg.MsgType.should == 'text'
84
+ end
85
+
86
+ it 'is a news reply message' do
87
+ msg = Weixin::NewsReplyMessage.new
88
+ msg.ToUserName = 'to'
89
+ msg.FromUserName = 'from'
90
+ item1 = Weixin::Item.new
91
+ item1.Title = 'title1'
92
+ item1.Description = 'blah'
93
+ item2 = Weixin::Item.new
94
+ item2.Title = 'title2'
95
+ item2.Description = 'blah blah'
96
+ msg.Articles = [item1, item2]
97
+ msg.ArticleCount = 2
98
+ msg.MsgType.should == 'news'
99
+ #puts msg.to_xml
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,55 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'rspec'
3
+ require 'rack'
4
+ require 'rack/test'
5
+ require 'rack/mock'
6
+ require 'digest/sha1'
7
+ require 'weixin/middleware'
8
+
9
+ describe "Weixin::Middleware" do
10
+
11
+ include Rack::Test::Methods
12
+
13
+ before(:all) do
14
+ @app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['hello']] }
15
+ @app_token = 'mytoken'
16
+ @context_path = '/'
17
+ end
18
+
19
+ def middleware()
20
+ Weixin::Middleware.new @app, @app_token, @context_path
21
+ end
22
+
23
+ def mock_env(echostr, token = @app_token, path = '/')
24
+ timestamp = Time.now.to_i.to_s
25
+ nonce = '123456'
26
+ param_array = [token, timestamp, nonce]
27
+ sign = Digest::SHA1.hexdigest( param_array.sort.join )
28
+ url = "#{path}?echostr=#{echostr}&timestamp=#{timestamp}&nonce=#{nonce}&signature=#{sign}"
29
+ Rack::MockRequest.env_for(url)
30
+ end
31
+
32
+ it 'does not match the path info' do
33
+ app = middleware
34
+ echostr = '123'
35
+ status, headers, body = app.call mock_env(echostr, @app_token, '/not_weixin_app')
36
+ status.should eq(200)
37
+ body.should_not == [echostr]
38
+ end
39
+
40
+ it 'is valid weixin request' do
41
+ app = middleware
42
+ echostr = '123'
43
+ status, headers, body = app.call mock_env(echostr)
44
+ status.should eq(200)
45
+ body.should == [echostr]
46
+ end
47
+
48
+ it 'is invalid weixin request' do
49
+ app = middleware
50
+ status, headers, body = app.call mock_env('123', 'wrong_token')
51
+ status.should eq(401)
52
+ body.should == []
53
+ end
54
+
55
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-weixin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - wolfg1969
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: multi_xml
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 0.5.2
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 0.5.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: roxml
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rack-test
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: 2.0.0
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: 2.0.0
110
+ description: ! 'Rack middleware for Weixin apps: message validation/parser/generator'
111
+ email: wolfg1969@gmail.com
112
+ executables: []
113
+ extensions: []
114
+ extra_rdoc_files: []
115
+ files:
116
+ - .gitignore
117
+ - Gemfile
118
+ - Gemfile.lock
119
+ - README.md
120
+ - Rakefile
121
+ - lib/weixin/middleware.rb
122
+ - lib/weixin/model.rb
123
+ - lib/weixin/version.rb
124
+ - rack-weixin.gemspec
125
+ - spec/model_spec.rb
126
+ - spec/rack_middleware_spec.rb
127
+ homepage: https://github.com/wolfg1969/rack-weixin
128
+ licenses: []
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ segments:
140
+ - 0
141
+ hash: -95414285
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ! '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ segments:
149
+ - 0
150
+ hash: -95414285
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 1.8.24
154
+ signing_key:
155
+ specification_version: 3
156
+ summary: Rack middleware for Weixin apps
157
+ test_files:
158
+ - spec/model_spec.rb
159
+ - spec/rack_middleware_spec.rb