diaspora-vines 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +19 -0
  4. data/README.md +7 -0
  5. data/Rakefile +23 -0
  6. data/bin/vines +4 -0
  7. data/conf/certs/README +39 -0
  8. data/conf/certs/ca-bundle.crt +3895 -0
  9. data/conf/config.rb +42 -0
  10. data/lib/vines/cli.rb +132 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/cluster.rb +246 -0
  17. data/lib/vines/command/bcrypt.rb +12 -0
  18. data/lib/vines/command/cert.rb +50 -0
  19. data/lib/vines/command/init.rb +68 -0
  20. data/lib/vines/command/ldap.rb +38 -0
  21. data/lib/vines/command/restart.rb +12 -0
  22. data/lib/vines/command/schema.rb +24 -0
  23. data/lib/vines/command/start.rb +28 -0
  24. data/lib/vines/command/stop.rb +18 -0
  25. data/lib/vines/config/host.rb +125 -0
  26. data/lib/vines/config/port.rb +132 -0
  27. data/lib/vines/config/pubsub.rb +108 -0
  28. data/lib/vines/config.rb +223 -0
  29. data/lib/vines/contact.rb +111 -0
  30. data/lib/vines/daemon.rb +78 -0
  31. data/lib/vines/error.rb +150 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +23 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza/iq/auth.rb +18 -0
  37. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  38. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  39. data/lib/vines/stanza/iq/error.rb +16 -0
  40. data/lib/vines/stanza/iq/ping.rb +16 -0
  41. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  42. data/lib/vines/stanza/iq/query.rb +10 -0
  43. data/lib/vines/stanza/iq/result.rb +16 -0
  44. data/lib/vines/stanza/iq/roster.rb +140 -0
  45. data/lib/vines/stanza/iq/session.rb +17 -0
  46. data/lib/vines/stanza/iq/vcard.rb +56 -0
  47. data/lib/vines/stanza/iq/version.rb +25 -0
  48. data/lib/vines/stanza/iq.rb +48 -0
  49. data/lib/vines/stanza/message.rb +40 -0
  50. data/lib/vines/stanza/presence/error.rb +23 -0
  51. data/lib/vines/stanza/presence/probe.rb +37 -0
  52. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  53. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  54. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  55. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  56. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  57. data/lib/vines/stanza/presence.rb +141 -0
  58. data/lib/vines/stanza/pubsub/create.rb +39 -0
  59. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  60. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  61. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  62. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  63. data/lib/vines/stanza/pubsub.rb +22 -0
  64. data/lib/vines/stanza.rb +175 -0
  65. data/lib/vines/storage/ldap.rb +71 -0
  66. data/lib/vines/storage/local.rb +139 -0
  67. data/lib/vines/storage/null.rb +39 -0
  68. data/lib/vines/storage/sql.rb +138 -0
  69. data/lib/vines/storage.rb +239 -0
  70. data/lib/vines/store.rb +110 -0
  71. data/lib/vines/stream/client/auth.rb +74 -0
  72. data/lib/vines/stream/client/auth_restart.rb +29 -0
  73. data/lib/vines/stream/client/bind.rb +72 -0
  74. data/lib/vines/stream/client/bind_restart.rb +24 -0
  75. data/lib/vines/stream/client/closed.rb +13 -0
  76. data/lib/vines/stream/client/ready.rb +17 -0
  77. data/lib/vines/stream/client/session.rb +210 -0
  78. data/lib/vines/stream/client/start.rb +27 -0
  79. data/lib/vines/stream/client/tls.rb +38 -0
  80. data/lib/vines/stream/client.rb +84 -0
  81. data/lib/vines/stream/component/handshake.rb +26 -0
  82. data/lib/vines/stream/component/ready.rb +23 -0
  83. data/lib/vines/stream/component/start.rb +19 -0
  84. data/lib/vines/stream/component.rb +58 -0
  85. data/lib/vines/stream/http/auth.rb +22 -0
  86. data/lib/vines/stream/http/bind.rb +32 -0
  87. data/lib/vines/stream/http/bind_restart.rb +37 -0
  88. data/lib/vines/stream/http/ready.rb +29 -0
  89. data/lib/vines/stream/http/request.rb +172 -0
  90. data/lib/vines/stream/http/session.rb +120 -0
  91. data/lib/vines/stream/http/sessions.rb +65 -0
  92. data/lib/vines/stream/http/start.rb +23 -0
  93. data/lib/vines/stream/http.rb +157 -0
  94. data/lib/vines/stream/parser.rb +79 -0
  95. data/lib/vines/stream/sasl.rb +128 -0
  96. data/lib/vines/stream/server/auth.rb +13 -0
  97. data/lib/vines/stream/server/auth_restart.rb +13 -0
  98. data/lib/vines/stream/server/final_restart.rb +21 -0
  99. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  100. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  101. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  102. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  103. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  104. data/lib/vines/stream/server/outbound/start.rb +20 -0
  105. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  106. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  107. data/lib/vines/stream/server/ready.rb +24 -0
  108. data/lib/vines/stream/server/start.rb +13 -0
  109. data/lib/vines/stream/server/tls.rb +13 -0
  110. data/lib/vines/stream/server.rb +150 -0
  111. data/lib/vines/stream/state.rb +60 -0
  112. data/lib/vines/stream.rb +247 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +6 -0
  116. data/lib/vines/xmpp_server.rb +25 -0
  117. data/lib/vines.rb +203 -0
  118. data/test/cluster/publisher_test.rb +57 -0
  119. data/test/cluster/sessions_test.rb +47 -0
  120. data/test/cluster/subscriber_test.rb +109 -0
  121. data/test/config/host_test.rb +369 -0
  122. data/test/config/pubsub_test.rb +187 -0
  123. data/test/config_test.rb +732 -0
  124. data/test/contact_test.rb +102 -0
  125. data/test/error_test.rb +58 -0
  126. data/test/ext/nokogiri.rb +14 -0
  127. data/test/jid_test.rb +147 -0
  128. data/test/kit_test.rb +31 -0
  129. data/test/router_test.rb +243 -0
  130. data/test/stanza/iq/disco_info_test.rb +78 -0
  131. data/test/stanza/iq/disco_items_test.rb +49 -0
  132. data/test/stanza/iq/private_storage_test.rb +184 -0
  133. data/test/stanza/iq/roster_test.rb +229 -0
  134. data/test/stanza/iq/session_test.rb +25 -0
  135. data/test/stanza/iq/vcard_test.rb +146 -0
  136. data/test/stanza/iq/version_test.rb +64 -0
  137. data/test/stanza/iq_test.rb +70 -0
  138. data/test/stanza/message_test.rb +126 -0
  139. data/test/stanza/presence/probe_test.rb +50 -0
  140. data/test/stanza/presence/subscribe_test.rb +83 -0
  141. data/test/stanza/pubsub/create_test.rb +116 -0
  142. data/test/stanza/pubsub/delete_test.rb +169 -0
  143. data/test/stanza/pubsub/publish_test.rb +309 -0
  144. data/test/stanza/pubsub/subscribe_test.rb +205 -0
  145. data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
  146. data/test/stanza_test.rb +85 -0
  147. data/test/storage/ldap_test.rb +201 -0
  148. data/test/storage/local_test.rb +59 -0
  149. data/test/storage/mock_redis.rb +97 -0
  150. data/test/storage/null_test.rb +29 -0
  151. data/test/storage/storage_tests.rb +182 -0
  152. data/test/storage_test.rb +85 -0
  153. data/test/store_test.rb +130 -0
  154. data/test/stream/client/auth_test.rb +137 -0
  155. data/test/stream/client/ready_test.rb +47 -0
  156. data/test/stream/client/session_test.rb +27 -0
  157. data/test/stream/component/handshake_test.rb +52 -0
  158. data/test/stream/component/ready_test.rb +103 -0
  159. data/test/stream/component/start_test.rb +39 -0
  160. data/test/stream/http/auth_test.rb +70 -0
  161. data/test/stream/http/ready_test.rb +86 -0
  162. data/test/stream/http/request_test.rb +209 -0
  163. data/test/stream/http/sessions_test.rb +49 -0
  164. data/test/stream/http/start_test.rb +50 -0
  165. data/test/stream/parser_test.rb +122 -0
  166. data/test/stream/sasl_test.rb +195 -0
  167. data/test/stream/server/auth_test.rb +61 -0
  168. data/test/stream/server/outbound/auth_test.rb +75 -0
  169. data/test/stream/server/ready_test.rb +98 -0
  170. data/test/test_helper.rb +42 -0
  171. data/test/token_bucket_test.rb +44 -0
  172. data/test/user_test.rb +96 -0
  173. data/vines.gemspec +30 -0
  174. metadata +387 -0
@@ -0,0 +1,58 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+
6
+ # Implements the XMPP protocol for trusted, external component (XEP-0114)
7
+ # streams. This serves connected streams using the jabber:component:accept
8
+ # namespace.
9
+ class Component < Stream
10
+ attr_reader :remote_domain
11
+
12
+ def initialize(config)
13
+ super
14
+ @remote_domain = nil
15
+ @stream_id = Kit.uuid
16
+ advance(Start.new(self))
17
+ end
18
+
19
+ def max_stanza_size
20
+ config[:component].max_stanza_size
21
+ end
22
+
23
+ def ready?
24
+ state.class == Component::Ready
25
+ end
26
+
27
+ def stream_type
28
+ :component
29
+ end
30
+
31
+ def start(node)
32
+ @remote_domain = node['to']
33
+ send_stream_header
34
+ raise StreamErrors::ImproperAddressing unless valid_address?(@remote_domain)
35
+ raise StreamErrors::HostUnknown unless config.component?(@remote_domain)
36
+ raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:component]
37
+ raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
38
+ end
39
+
40
+ def secret
41
+ password = config.component_password(@remote_domain)
42
+ Digest::SHA1.hexdigest(@stream_id + password)
43
+ end
44
+
45
+ private
46
+
47
+ def send_stream_header
48
+ attrs = {
49
+ 'xmlns' => NAMESPACES[:component],
50
+ 'xmlns:stream' => NAMESPACES[:stream],
51
+ 'id' => @stream_id,
52
+ 'from' => @remote_domain
53
+ }
54
+ write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Auth < Client::Auth
7
+ def initialize(stream, success=BindRestart)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ unless stream.valid_session?(node['sid']) && body?(node) && node['rid']
13
+ raise StreamErrors::NotAuthorized
14
+ end
15
+ nodes = stream.parse_body(node)
16
+ raise StreamErrors::NotAuthorized unless nodes.size == 1
17
+ super(nodes.first)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Bind < Client::Bind
7
+ FEATURES = %Q{<stream:features xmlns:stream="#{NAMESPACES[:stream]}"/>}.freeze
8
+
9
+ def initialize(stream, success=Ready)
10
+ super
11
+ end
12
+
13
+ def node(node)
14
+ unless stream.valid_session?(node['sid']) && body?(node) && node['rid']
15
+ raise StreamErrors::NotAuthorized
16
+ end
17
+ nodes = stream.parse_body(node)
18
+ raise StreamErrors::NotAuthorized unless nodes.size == 1
19
+ super(nodes.first)
20
+ end
21
+
22
+ private
23
+
24
+ # Override Client::Bind#send_empty_features to properly namespace the
25
+ # empty features element.
26
+ def send_empty_features
27
+ stream.write(FEATURES)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class BindRestart < State
7
+ def initialize(stream, success=Bind)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless restart?(node)
13
+
14
+ doc = Document.new
15
+ body = doc.create_element('body') do |el|
16
+ el.add_namespace(nil, NAMESPACES[:http_bind])
17
+ el.add_namespace('stream', NAMESPACES[:stream])
18
+ el << doc.create_element('stream:features') do |features|
19
+ features << doc.create_element('bind', 'xmlns' => NAMESPACES[:bind])
20
+ end
21
+ end
22
+ stream.reply(body)
23
+ advance
24
+ end
25
+
26
+ private
27
+
28
+ def restart?(node)
29
+ session = stream.valid_session?(node['sid'])
30
+ restart = node.attribute_with_ns('restart', NAMESPACES[:bosh]).value rescue nil
31
+ domain = node['to'] == stream.domain
32
+ session && body?(node) && domain && restart == 'true' && node['rid']
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Ready < Client::Ready
7
+ RID, SID, TYPE, TERMINATE = %w[rid sid type terminate].map {|s| s.freeze }
8
+
9
+ def node(node)
10
+ unless stream.valid_session?(node[SID]) && body?(node) && node[RID]
11
+ raise StreamErrors::NotAuthorized
12
+ end
13
+ stream.parse_body(node).each do |child|
14
+ begin
15
+ super(child)
16
+ rescue StanzaError => e
17
+ stream.error(e)
18
+ end
19
+ end
20
+ stream.terminate if terminate?(node)
21
+ end
22
+
23
+ def terminate?(node)
24
+ node[TYPE] == TERMINATE
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,172 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Request
7
+ BUF_SIZE = 1024
8
+ MODIFIED = '%a, %d %b %Y %H:%M:%S GMT'.freeze
9
+ MOVED = 'Moved Permanently'.freeze
10
+ NOT_FOUND = 'Not Found'.freeze
11
+ NOT_MODIFIED = 'Not Modified'.freeze
12
+ IF_MODIFIED = 'If-Modified-Since'.freeze
13
+ TEXT_PLAIN = 'text/plain'.freeze
14
+ OPTIONS = 'OPTIONS'.freeze
15
+ CONTENT_TYPES = {
16
+ 'html' => 'text/html; charset="utf-8"',
17
+ 'js' => 'application/javascript; charset="utf-8"',
18
+ 'css' => 'text/css',
19
+ 'png' => 'image/png',
20
+ 'jpg' => 'image/jpeg',
21
+ 'jpeg' => 'image/jpeg',
22
+ 'gif' => 'image/gif',
23
+ 'manifest' => 'text/cache-manifest'
24
+ }.freeze
25
+
26
+ attr_reader :stream, :body, :headers, :method, :path, :url, :query
27
+
28
+ def initialize(stream, parser, body)
29
+ @stream, @body = stream, body
30
+ @headers = parser.headers
31
+ @method = parser.http_method
32
+ @path = parser.request_path
33
+ @url = parser.request_url
34
+ @query = parser.query_string
35
+ @received = Time.now
36
+ end
37
+
38
+ # Return the number of seconds since this request was received.
39
+ def age
40
+ Time.now - @received
41
+ end
42
+
43
+ # Write the requested file to the client out of the given document root
44
+ # directory. Take care to prevent directory traversal attacks with paths
45
+ # like ../../../etc/passwd. Use the If-Modified-Since request header
46
+ # to implement caching.
47
+ def reply_with_file(dir)
48
+ path = File.expand_path(File.join(dir, @path))
49
+
50
+ # redirect requests missing a slash so relative links work
51
+ if File.directory?(path) && !@path.end_with?('/')
52
+ send_status(301, MOVED, "Location: #{redirect_uri}")
53
+ return
54
+ end
55
+
56
+ path = File.join(path, 'index.html') if File.directory?(path)
57
+
58
+ if path.start_with?(dir) && File.exist?(path)
59
+ modified?(path) ? send_file(path) : send_status(304, NOT_MODIFIED)
60
+ else
61
+ missing = File.join(dir, '404.html')
62
+ if File.exist?(missing)
63
+ send_file(missing, 404, NOT_FOUND)
64
+ else
65
+ send_status(404, NOT_FOUND)
66
+ end
67
+ end
68
+ end
69
+
70
+ # Send an HTTP 200 OK response wrapping the XMPP node content back
71
+ # to the client.
72
+ def reply(node, content_type)
73
+ body = node.to_s
74
+ header = [
75
+ "HTTP/1.1 200 OK",
76
+ "Access-Control-Allow-Origin: *",
77
+ "Content-Type: #{content_type}",
78
+ "Content-Length: #{body.bytesize}",
79
+ vroute_cookie
80
+ ].compact.join("\r\n")
81
+ @stream.stream_write([header, body].join("\r\n\r\n"))
82
+ end
83
+
84
+ # Return true if the request method is OPTIONS, signaling a
85
+ # CORS preflight check.
86
+ def options?
87
+ @method == OPTIONS
88
+ end
89
+
90
+ # Send a 200 OK response, allowing any origin domain to connect to the
91
+ # server, in response to CORS preflight OPTIONS requests. This allows
92
+ # any web application using strophe.js to connect to our BOSH port.
93
+ def reply_to_options
94
+ allow = @headers['Access-Control-Request-Headers']
95
+ headers = [
96
+ "Access-Control-Allow-Origin: *",
97
+ "Access-Control-Allow-Methods: POST, GET, OPTIONS",
98
+ "Access-Control-Allow-Headers: #{allow}",
99
+ "Access-Control-Max-Age: #{60 * 60 * 24 * 30}"
100
+ ]
101
+ send_status(200, 'OK', headers)
102
+ end
103
+
104
+ private
105
+
106
+ # Attempt to rebuild the full request URI from the Host header. If it
107
+ # wasn't sent by the client, just return the relative path that
108
+ # was requested. The Location response header must contain the fully
109
+ # qualified URI, but most browsers will accept relative paths as well.
110
+ def redirect_uri
111
+ host = headers['Host']
112
+ uri = "#{path}/"
113
+ uri = "#{uri}?#{query}" unless (query || '').empty?
114
+ uri = "http://#{host}#{uri}" if host
115
+ uri
116
+ end
117
+
118
+ # Return true if the file has been modified since the client last
119
+ # requested it with the If-Modified-Since header.
120
+ def modified?(path)
121
+ @headers[IF_MODIFIED] != mtime(path)
122
+ end
123
+
124
+ def mtime(path)
125
+ File.mtime(path).utc.strftime(MODIFIED)
126
+ end
127
+
128
+ def send_status(status, message, *headers)
129
+ header = [
130
+ "HTTP/1.1 #{status} #{message}",
131
+ "Content-Length: 0",
132
+ *headers
133
+ ].join("\r\n")
134
+ @stream.stream_write("#{header}\r\n\r\n")
135
+ end
136
+
137
+ # Stream the contents of the file to the client in a 200 OK response.
138
+ # Send a Last-Modified response header so clients can send us an
139
+ # If-Modified-Since request header for caching.
140
+ def send_file(path, status=200, message='OK')
141
+ header = [
142
+ "HTTP/1.1 #{status} #{message}",
143
+ "Content-Type: #{content_type(path)}",
144
+ "Content-Length: #{File.size(path)}",
145
+ "Last-Modified: #{mtime(path)}"
146
+ ].join("\r\n")
147
+ @stream.stream_write("#{header}\r\n\r\n")
148
+
149
+ File.open(path) do |file|
150
+ while (buf = file.read(BUF_SIZE)) != nil
151
+ @stream.stream_write(buf)
152
+ end
153
+ end
154
+ end
155
+
156
+ def content_type(path)
157
+ ext = File.extname(path).sub('.', '')
158
+ CONTENT_TYPES[ext] || TEXT_PLAIN
159
+ end
160
+
161
+ # Provide a vroute cookie in each response that uniquely identifies this
162
+ # HTTP server. Reverse proxy servers (nginx/apache) can use this cookie
163
+ # to implement sticky sessions. Return nil if vroute was not set in
164
+ # config.rb and no cookie should be sent.
165
+ def vroute_cookie
166
+ route = @stream.config[:http].vroute
167
+ route ? "Set-Cookie: vroute=#{route}; path=/; HttpOnly" : nil
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Session < Client::Session
7
+ include Nokogiri::XML
8
+
9
+ attr_accessor :content_type, :hold, :inactivity, :wait
10
+
11
+ CONTENT_TYPE = 'text/xml; charset=utf-8'.freeze
12
+
13
+ def initialize(stream)
14
+ super
15
+ @state = Http::Start.new(stream)
16
+ @inactivity, @wait, @hold = 20, 60, 1
17
+ @replied = Time.now
18
+ @requests, @responses = [], []
19
+ @content_type = CONTENT_TYPE
20
+ end
21
+
22
+ def close
23
+ Sessions.delete(@id)
24
+ router.delete(self)
25
+ delete_from_cluster
26
+ unsubscribe_pubsub
27
+ @requests.each {|req| req.stream.close_connection }
28
+ @requests.clear
29
+ @responses.clear
30
+ @state = Client::Closed.new(nil)
31
+ @unbound = true
32
+ @available = false
33
+ broadcast_unavailable
34
+ end
35
+
36
+ def ready?
37
+ @state.class == Http::Ready
38
+ end
39
+
40
+ def requests
41
+ @requests.clone
42
+ end
43
+
44
+ def expired?
45
+ respond_to_expired_requests
46
+ @requests.empty? && (Time.now - @replied > @inactivity)
47
+ end
48
+
49
+ # Resume this session from its most recent state with a new client
50
+ # stream and incoming node.
51
+ def resume(stream, node)
52
+ stream.session.requests.each do |req|
53
+ request(req)
54
+ end
55
+ stream.session = self
56
+ @state.stream = stream
57
+ @state.node(node)
58
+ end
59
+
60
+ def request(request)
61
+ if @responses.any?
62
+ request.reply(wrap_body(@responses.join), @content_type)
63
+ @replied = Time.now
64
+ @responses.clear
65
+ else
66
+ while @requests.size >= @hold
67
+ @requests.shift.reply(wrap_body(''), @content_type)
68
+ @replied = Time.now
69
+ end
70
+ @requests << request
71
+ end
72
+ end
73
+
74
+ # Send an HTTP 200 OK response wrapping the XMPP node content back
75
+ # to the client.
76
+ def reply(node)
77
+ if request = @requests.shift
78
+ request.reply(node, @content_type)
79
+ @replied = Time.now
80
+ end
81
+ end
82
+
83
+ # Write the XMPP node to the client stream after wrapping it in a BOSH
84
+ # body tag. If there's a waiting request, the node is written
85
+ # immediately. If not, it's queued until the next request arrives.
86
+ def write(node)
87
+ if request = @requests.shift
88
+ request.reply(wrap_body(node), @content_type)
89
+ @replied = Time.now
90
+ else
91
+ @responses << node.to_s
92
+ end
93
+ end
94
+
95
+ def unbind!(stream)
96
+ @requests.reject! {|req| req.stream == stream }
97
+ end
98
+
99
+ private
100
+
101
+ def respond_to_expired_requests
102
+ expired = @requests.select {|req| req.age > @wait }
103
+ expired.each do |request|
104
+ request.reply(wrap_body(''), @content_type)
105
+ @requests.delete(request)
106
+ @replied = Time.now
107
+ end
108
+ end
109
+
110
+ def wrap_body(data)
111
+ doc = Document.new
112
+ doc.create_element('body') do |node|
113
+ node.add_namespace(nil, NAMESPACES[:http_bind])
114
+ node.inner_html = data.to_s
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ # Sessions is a cache of Http::Session objects for transient HTTP
7
+ # connections. The cache is monitored for expired client connections.
8
+ class Sessions
9
+ include Vines::Log
10
+
11
+ @@instance = nil
12
+ def self.instance
13
+ @@instance ||= self.new
14
+ end
15
+
16
+ def self.[](sid)
17
+ instance[sid]
18
+ end
19
+
20
+ def self.[]=(sid, session)
21
+ instance[sid] = session
22
+ end
23
+
24
+ def self.delete(sid)
25
+ instance.delete(sid)
26
+ end
27
+
28
+ def initialize
29
+ @sessions = {}
30
+ start_timer
31
+ end
32
+
33
+ def []=(sid, session)
34
+ @sessions[sid] = session
35
+ end
36
+
37
+ def [](sid)
38
+ @sessions[sid]
39
+ end
40
+
41
+ def delete(sid)
42
+ @sessions.delete(sid)
43
+ end
44
+
45
+ private
46
+
47
+ # Check for expired clients to cleanup every second.
48
+ def start_timer
49
+ @timer ||= EventMachine::PeriodicTimer.new(1) { cleanup }
50
+ end
51
+
52
+ # Remove cached information for all expired connections. An expired
53
+ # HTTP client is one that has no queued requests and has had no activity
54
+ # for over 20 seconds.
55
+ def cleanup
56
+ @sessions.each_value do |session|
57
+ session.close if session.expired?
58
+ end
59
+ rescue => e
60
+ log.error("Expired session cleanup failed: #{e}")
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Start < State
7
+ def initialize(stream, success=Auth)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless body?(node)
13
+ if session = Sessions[node['sid']]
14
+ session.resume(stream, node)
15
+ else
16
+ stream.start(node)
17
+ advance
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end