mongrel2 0.52.2 → 0.53.0
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +128 -2
- data/History.rdoc +10 -0
- data/Manifest.txt +1 -0
- data/Rakefile +1 -1
- data/lib/mongrel2.rb +3 -3
- data/lib/mongrel2/config.rb +2 -1
- data/lib/mongrel2/config/directory.rb +2 -1
- data/lib/mongrel2/config/dsl.rb +2 -1
- data/lib/mongrel2/config/filter.rb +2 -1
- data/lib/mongrel2/config/handler.rb +2 -1
- data/lib/mongrel2/config/host.rb +2 -1
- data/lib/mongrel2/config/log.rb +2 -1
- data/lib/mongrel2/config/mimetype.rb +2 -1
- data/lib/mongrel2/config/proxy.rb +2 -1
- data/lib/mongrel2/config/route.rb +2 -1
- data/lib/mongrel2/config/server.rb +2 -1
- data/lib/mongrel2/config/setting.rb +2 -1
- data/lib/mongrel2/config/statistic.rb +2 -1
- data/lib/mongrel2/config/xrequest.rb +2 -1
- data/lib/mongrel2/connection.rb +1 -1
- data/lib/mongrel2/constants.rb +2 -1
- data/lib/mongrel2/control.rb +2 -1
- data/lib/mongrel2/exceptions.rb +2 -1
- data/lib/mongrel2/handler.rb +9 -8
- data/lib/mongrel2/httprequest.rb +2 -1
- data/lib/mongrel2/httpresponse.rb +2 -1
- data/lib/mongrel2/jsonrequest.rb +2 -1
- data/lib/mongrel2/request.rb +16 -9
- data/lib/mongrel2/response.rb +14 -6
- data/lib/mongrel2/table.rb +24 -3
- data/lib/mongrel2/testing.rb +12 -25
- data/lib/mongrel2/websocket.rb +230 -148
- data/lib/mongrel2/xmlrequest.rb +2 -1
- data/spec/constants.rb +3 -2
- data/spec/helpers.rb +2 -1
- data/spec/matchers.rb +2 -1
- data/spec/mongrel2/config/directory_spec.rb +2 -1
- data/spec/mongrel2/config/dsl_spec.rb +2 -1
- data/spec/mongrel2/config/filter_spec.rb +2 -1
- data/spec/mongrel2/config/handler_spec.rb +2 -1
- data/spec/mongrel2/config/host_spec.rb +2 -1
- data/spec/mongrel2/config/log_spec.rb +2 -1
- data/spec/mongrel2/config/proxy_spec.rb +2 -1
- data/spec/mongrel2/config/route_spec.rb +2 -1
- data/spec/mongrel2/config/server_spec.rb +2 -1
- data/spec/mongrel2/config/setting_spec.rb +2 -1
- data/spec/mongrel2/config/statistic_spec.rb +2 -1
- data/spec/mongrel2/config/xrequest_spec.rb +2 -1
- data/spec/mongrel2/config_spec.rb +2 -1
- data/spec/mongrel2/connection_spec.rb +2 -1
- data/spec/mongrel2/constants_spec.rb +2 -1
- data/spec/mongrel2/control_spec.rb +2 -1
- data/spec/mongrel2/handler_spec.rb +4 -3
- data/spec/mongrel2/httprequest_spec.rb +2 -1
- data/spec/mongrel2/httpresponse_spec.rb +2 -1
- data/spec/mongrel2/request_spec.rb +7 -1
- data/spec/mongrel2/response_spec.rb +9 -1
- data/spec/mongrel2/table_spec.rb +4 -3
- data/spec/mongrel2/testing_spec.rb +142 -0
- data/spec/mongrel2/websocket_spec.rb +300 -136
- data/spec/mongrel2/xmlrequest_spec.rb +2 -1
- data/spec/mongrel2_spec.rb +2 -1
- metadata +6 -5
- metadata.gz.sig +0 -0
data/lib/mongrel2/response.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'stringio'
|
4
5
|
require 'tnetstring'
|
@@ -32,7 +33,7 @@ class Mongrel2::Response
|
|
32
33
|
|
33
34
|
### Create a new Response object for the specified +sender_id+, +conn_id+, and +body+.
|
34
35
|
def initialize( sender_id, conn_id, body='' )
|
35
|
-
body = StringIO.new( body, 'a+' ) unless body.respond_to?( :read )
|
36
|
+
body = StringIO.new( body.dup, 'a+' ) unless body.respond_to?( :read )
|
36
37
|
|
37
38
|
@sender_id = sender_id
|
38
39
|
@conn_id = conn_id
|
@@ -76,7 +77,7 @@ class Mongrel2::Response
|
|
76
77
|
### Set the response's entity body to +newbody+. If +newbody+ is a String-ish object
|
77
78
|
### (i.e., it responds to #to_str), it will be wrapped in a StringIO in 'a+' mode).
|
78
79
|
def body=( newbody )
|
79
|
-
newbody = StringIO.new( newbody, 'a+' ) if newbody.respond_to?( :to_str )
|
80
|
+
newbody = StringIO.new( newbody.dup, 'a+' ) if newbody.respond_to?( :to_str )
|
80
81
|
@body = newbody
|
81
82
|
end
|
82
83
|
|
@@ -131,15 +132,22 @@ class Mongrel2::Response
|
|
131
132
|
end
|
132
133
|
|
133
134
|
|
135
|
+
### Returns a string containing the request's sender and connection IDs
|
136
|
+
### separated by a colon.
|
137
|
+
def socket_id
|
138
|
+
return "%s:%d" % [ self.sender_id, self.conn_id ]
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
|
134
143
|
### Returns a string containing a human-readable representation of the Response,
|
135
144
|
### suitable for debugging.
|
136
145
|
def inspect
|
137
|
-
return "#<%p:0x%016x %s (%s
|
146
|
+
return "#<%p:0x%016x %s (%s)>" % [
|
138
147
|
self.class,
|
139
148
|
self.object_id * 2,
|
140
149
|
self.inspect_details,
|
141
|
-
self.
|
142
|
-
self.conn_id
|
150
|
+
self.socket_id
|
143
151
|
]
|
144
152
|
end
|
145
153
|
|
data/lib/mongrel2/table.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'forwardable'
|
4
5
|
require 'loggability'
|
@@ -28,7 +29,10 @@ class Mongrel2::Table
|
|
28
29
|
log_to :mongrel2
|
29
30
|
|
30
31
|
# Methods that understand case-insensitive keys
|
31
|
-
KEYED_METHODS = [
|
32
|
+
KEYED_METHODS = %i[ [] []= delete fetch has_key? include? member? store ]
|
33
|
+
|
34
|
+
# Method to not delegate to the inner hash
|
35
|
+
NON_DELEGATED_METHODS = %i[ inspect freeze ]
|
32
36
|
|
33
37
|
|
34
38
|
### Auto-generate methods which call the given +delegate+ after normalizing
|
@@ -74,7 +78,7 @@ class Mongrel2::Table
|
|
74
78
|
# Delegate some methods to the underlying Hash
|
75
79
|
begin
|
76
80
|
unoverridden_methods = Hash.instance_methods(false).collect {|mname| mname.to_sym }
|
77
|
-
def_delegators :@hash, *( unoverridden_methods - KEYED_METHODS )
|
81
|
+
def_delegators :@hash, *( unoverridden_methods - KEYED_METHODS - NON_DELEGATED_METHODS )
|
78
82
|
end
|
79
83
|
|
80
84
|
|
@@ -152,6 +156,23 @@ class Mongrel2::Table
|
|
152
156
|
end
|
153
157
|
|
154
158
|
|
159
|
+
### Return a human-readable representation of the object suitable for debugging.
|
160
|
+
def inspect
|
161
|
+
return "#<%p:%#x %p>" % [
|
162
|
+
self.class,
|
163
|
+
self.object_id * 2,
|
164
|
+
@hash
|
165
|
+
]
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
### Overridden to freeze the inner hash as well.
|
170
|
+
def freeze
|
171
|
+
super
|
172
|
+
@hash.freeze
|
173
|
+
end
|
174
|
+
|
175
|
+
|
155
176
|
#########
|
156
177
|
protected
|
157
178
|
#########
|
data/lib/mongrel2/testing.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'uri'
|
4
5
|
require 'pathname'
|
@@ -108,7 +109,7 @@ module Mongrel2
|
|
108
109
|
DEFAULT_TESTING_ROUTE = DEFAULT_TESTING_URL.path
|
109
110
|
|
110
111
|
# The default set of headers used for HTTP requests
|
111
|
-
DEFAULT_TESTING_HEADERS =
|
112
|
+
DEFAULT_TESTING_HEADERS = Mongrel2::Table.new(
|
112
113
|
'x-forwarded-for' => '127.0.0.1',
|
113
114
|
'accept-language' => 'en-US,en;q=0.8',
|
114
115
|
'accept-encoding' => 'gzip,deflate,sdch',
|
@@ -120,7 +121,7 @@ module Mongrel2
|
|
120
121
|
'Safari/535.1',
|
121
122
|
'url-scheme' => 'http',
|
122
123
|
'VERSION' => 'HTTP/1.1',
|
123
|
-
|
124
|
+
)
|
124
125
|
|
125
126
|
# The defaults used by the HTTP request factory
|
126
127
|
DEFAULT_FACTORY_CONFIG = {
|
@@ -161,12 +162,12 @@ module Mongrel2
|
|
161
162
|
config = self.class.default_factory_config.merge( config )
|
162
163
|
|
163
164
|
@sender_id = config[:sender_id]
|
165
|
+
@conn_id = config[:conn_id]
|
164
166
|
@host = config[:host]
|
165
167
|
@port = config[:port]
|
166
168
|
@route = config[:route]
|
167
|
-
@headers = Mongrel2::Table.new( config[:headers] )
|
168
169
|
|
169
|
-
@
|
170
|
+
@headers = Mongrel2::Table.new( config[:headers] )
|
170
171
|
end
|
171
172
|
|
172
173
|
######
|
@@ -286,7 +287,7 @@ module Mongrel2
|
|
286
287
|
|
287
288
|
# A factory for generating WebSocket request objects for testing.
|
288
289
|
#
|
289
|
-
class
|
290
|
+
class WebSocketRequestFactory < Mongrel2::RequestFactory
|
290
291
|
include Mongrel2::Constants
|
291
292
|
|
292
293
|
# The default host
|
@@ -294,11 +295,8 @@ module Mongrel2
|
|
294
295
|
DEFAULT_TESTING_PORT = '8113'
|
295
296
|
DEFAULT_TESTING_ROUTE = '/ws'
|
296
297
|
|
297
|
-
# The default WebSocket opcode
|
298
|
-
DEFAULT_OPCODE = :text
|
299
|
-
|
300
298
|
# Default headers
|
301
|
-
DEFAULT_TESTING_HEADERS =
|
299
|
+
DEFAULT_TESTING_HEADERS = Mongrel2::Table.new(
|
302
300
|
'METHOD' => 'WEBSOCKET',
|
303
301
|
'PATTERN' => '/ws',
|
304
302
|
'URI' => '/ws',
|
@@ -312,7 +310,7 @@ module Mongrel2
|
|
312
310
|
'origin' => "http://#{DEFAULT_TESTING_HOST}",
|
313
311
|
'FLAGS' => '0x89', # FIN + PING
|
314
312
|
'x-forwarded-for' => '127.0.0.1'
|
315
|
-
|
313
|
+
)
|
316
314
|
|
317
315
|
# The defaults used by the websocket request factory
|
318
316
|
DEFAULT_FACTORY_CONFIG = {
|
@@ -332,20 +330,6 @@ module Mongrel2
|
|
332
330
|
end
|
333
331
|
|
334
332
|
|
335
|
-
### Create a new factory using the specified +config+.
|
336
|
-
def initialize( config={} )
|
337
|
-
config[:headers] = DEFAULT_TESTING_HEADERS.merge( config[:headers] ) if config[:headers]
|
338
|
-
config = DEFAULT_FACTORY_CONFIG.merge( config )
|
339
|
-
|
340
|
-
@sender_id = config[:sender_id]
|
341
|
-
@host = config[:host]
|
342
|
-
@port = config[:port]
|
343
|
-
@route = config[:route]
|
344
|
-
@headers = Mongrel2::Table.new( config[:headers] )
|
345
|
-
|
346
|
-
@conn_id = 0
|
347
|
-
end
|
348
|
-
|
349
333
|
######
|
350
334
|
public
|
351
335
|
######
|
@@ -447,6 +431,8 @@ module Mongrel2
|
|
447
431
|
headers.origin = "http://#{headers.host}"
|
448
432
|
headers.flags = "0x%02x" % [ flags ]
|
449
433
|
|
434
|
+
self.log.debug "Headers are: %p" % [ headers ]
|
435
|
+
|
450
436
|
return headers
|
451
437
|
end
|
452
438
|
|
@@ -489,5 +475,6 @@ module Mongrel2
|
|
489
475
|
|
490
476
|
end # class WebSocketFrameFactory
|
491
477
|
|
478
|
+
|
492
479
|
end # module Mongrel2
|
493
480
|
|
data/lib/mongrel2/websocket.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# -*- ruby -*-
|
2
|
-
#
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'forwardable'
|
3
5
|
|
4
6
|
require 'mongrel2/request' unless defined?( Mongrel2::Request )
|
5
7
|
require 'mongrel2/constants'
|
@@ -196,22 +198,46 @@ module Mongrel2::WebSocket
|
|
196
198
|
end # module WebSocket
|
197
199
|
include Constants
|
198
200
|
|
201
|
+
|
199
202
|
# Base exception class for WebSocket-related errors
|
200
203
|
class Error < ::RuntimeError; end
|
201
204
|
|
205
|
+
|
202
206
|
# Exception raised when a frame is malformed, doesn't parse, or is otherwise invalid.
|
203
207
|
class FrameError < Mongrel2::WebSocket::Error; end
|
204
208
|
|
209
|
+
|
205
210
|
# Exception raised when a handshake is created with an unrequested sub-protocol.
|
206
211
|
class HandshakeError < Mongrel2::WebSocket::Error; end
|
207
212
|
|
208
213
|
|
209
|
-
# A mixin containing methods
|
210
|
-
module
|
214
|
+
# A mixin containing methods for request/response classes that wrap a Frame.
|
215
|
+
module FrameMethods
|
216
|
+
extend Forwardable
|
217
|
+
|
218
|
+
|
219
|
+
##
|
220
|
+
# The Websocket data as a Mongrel2::WebSocket::Frame
|
221
|
+
attr_reader :frame
|
222
|
+
|
223
|
+
##
|
224
|
+
# Delegate some methods to the contained frame
|
225
|
+
def_instance_delegators :frame,
|
226
|
+
:opcode, :opcode=, :numeric_opcode, :payload, :each_chunk, :flags, :set_flags,
|
227
|
+
:fin?, :fin=, :rsv1?, :rsv1=, :rsv2?, :rsv2=, :rsv3?, :rsv3=, :control?,
|
228
|
+
:has_rsv_flags?
|
229
|
+
|
230
|
+
### Append operator -- append +object+ to the contained frame's payload and
|
231
|
+
### return the receiver.
|
232
|
+
def <<( object )
|
233
|
+
self.frame << object
|
234
|
+
return self
|
235
|
+
end
|
236
|
+
|
211
237
|
|
212
|
-
###
|
213
|
-
def
|
214
|
-
return [ self.
|
238
|
+
### Return the details to include in the contents of the #inspected object.
|
239
|
+
def inspect_details
|
240
|
+
return "frame: %p" % [ self.frame ]
|
215
241
|
end
|
216
242
|
|
217
243
|
end # module Methods
|
@@ -219,8 +245,7 @@ module Mongrel2::WebSocket
|
|
219
245
|
|
220
246
|
# The client (request) handshake for a WebSocket opening handshake.
|
221
247
|
class ClientHandshake < Mongrel2::HTTPRequest
|
222
|
-
include Mongrel2::WebSocket::Constants
|
223
|
-
Mongrel2::WebSocket::Methods
|
248
|
+
include Mongrel2::WebSocket::Constants
|
224
249
|
|
225
250
|
# Set this class as the one that will handle WEBSOCKET_HANDSHAKE requests
|
226
251
|
register_request_type( self, :WEBSOCKET_HANDSHAKE )
|
@@ -265,8 +290,7 @@ module Mongrel2::WebSocket
|
|
265
290
|
|
266
291
|
# The server (response) handshake for a WebSocket opening handshake.
|
267
292
|
class ServerHandshake < Mongrel2::HTTPResponse
|
268
|
-
include Mongrel2::WebSocket::Constants
|
269
|
-
Mongrel2::WebSocket::Methods
|
293
|
+
include Mongrel2::WebSocket::Constants
|
270
294
|
|
271
295
|
### Create a server handshake frame from the given client +handshake+.
|
272
296
|
def self::from_request( handshake )
|
@@ -303,39 +327,151 @@ module Mongrel2::WebSocket
|
|
303
327
|
end # class ServerHandshake
|
304
328
|
|
305
329
|
|
306
|
-
# WebSocket
|
307
|
-
|
308
|
-
|
309
|
-
include Mongrel2::WebSocket::Constants,
|
310
|
-
Mongrel2::WebSocket::Methods
|
311
|
-
|
312
|
-
# The default frame header flags: FIN + CLOSE
|
313
|
-
DEFAULT_FLAGS = FIN_FLAG | OPCODE[:close]
|
330
|
+
# WebSocket request -- this is the container for Frames from a client.
|
331
|
+
class Request < Mongrel2::Request
|
332
|
+
include Mongrel2::WebSocket::FrameMethods
|
314
333
|
|
315
334
|
|
316
335
|
# Set this class as the one that will handle WEBSOCKET requests
|
317
336
|
register_request_type( self, :WEBSOCKET )
|
318
337
|
|
319
338
|
|
320
|
-
### Override the type of response returned by this request type.
|
321
|
-
### WebSocket connections are symmetrical, responses are just new
|
322
|
-
### Mongrel2::WebSocket::Frames with the same Mongrel2 sender and
|
323
|
-
### connection IDs.
|
339
|
+
### Override the type of response returned by this request type.
|
324
340
|
def self::response_class
|
325
|
-
return
|
341
|
+
return Mongrel2::WebSocket::Response
|
342
|
+
end
|
343
|
+
|
344
|
+
|
345
|
+
### Init a few instance variables unique to websocket requests/responses.
|
346
|
+
def initialize( * )
|
347
|
+
super
|
348
|
+
|
349
|
+
payload = self.body.read
|
350
|
+
self.body.rewind
|
351
|
+
|
352
|
+
@frame = Mongrel2::WebSocket::Frame.new( payload, self.headers.flags )
|
353
|
+
end
|
354
|
+
|
355
|
+
|
356
|
+
### Create a frame in response to the receiving Frame (i.e., with the same
|
357
|
+
### Mongrel2 connection ID and sender).
|
358
|
+
def response( payload=nil, *flags )
|
359
|
+
res = super()
|
360
|
+
|
361
|
+
if payload.is_a?( Symbol ) || payload.is_a?( Integer )
|
362
|
+
flags.unshift( payload )
|
363
|
+
payload = nil
|
364
|
+
end
|
365
|
+
|
366
|
+
if payload
|
367
|
+
res.payload.truncate( 0 )
|
368
|
+
res.payload << payload
|
369
|
+
end
|
370
|
+
res.set_flags( *flags ) unless flags.empty?
|
371
|
+
|
372
|
+
return res
|
326
373
|
end
|
327
374
|
|
375
|
+
end # class Request
|
376
|
+
|
377
|
+
|
378
|
+
# WebSocket response -- this is the container for Frames sent to a client.
|
379
|
+
class Response < Mongrel2::Response
|
380
|
+
extend Forwardable
|
381
|
+
include Mongrel2::WebSocket::FrameMethods
|
382
|
+
|
328
383
|
|
329
|
-
###
|
330
|
-
|
331
|
-
|
332
|
-
response =
|
333
|
-
|
384
|
+
### Return a response to the specified +request+, inferring appropriate flags
|
385
|
+
### if appropriate.
|
386
|
+
def self::from_request( request )
|
387
|
+
response = super
|
388
|
+
|
389
|
+
if request.opcode == :ping
|
390
|
+
response.opcode = :pong
|
391
|
+
IO.copy_stream( request.payload, response.payload, 4096 )
|
392
|
+
else
|
393
|
+
# Numeric in case it's a custom (reserved) value
|
394
|
+
response.opcode = request.numeric_opcode
|
395
|
+
end
|
334
396
|
|
335
397
|
return response
|
336
398
|
end
|
337
399
|
|
338
400
|
|
401
|
+
### Init a few instance variables unique to websocket requests/responses.
|
402
|
+
def initialize( sender_id, conn_id, body='' )
|
403
|
+
@frame = Mongrel2::WebSocket::Frame.new( body )
|
404
|
+
super( sender_id, conn_id, @frame.payload )
|
405
|
+
end
|
406
|
+
|
407
|
+
|
408
|
+
##
|
409
|
+
# Delegate some methods to the contained frame
|
410
|
+
def_instance_delegators :frame,
|
411
|
+
:puts, :to_s, :each_chunk, :<<, :make_close_frame, :set_status
|
412
|
+
|
413
|
+
end # class Response
|
414
|
+
|
415
|
+
|
416
|
+
# WebSocket frame class; this is used for both requests and responses in
|
417
|
+
# WebSocket services.
|
418
|
+
class Frame
|
419
|
+
extend Loggability
|
420
|
+
include Mongrel2::WebSocket::Constants
|
421
|
+
|
422
|
+
|
423
|
+
# The default frame header flags: FIN + CLOSE
|
424
|
+
DEFAULT_FLAGS = FIN_FLAG | OPCODE[:close]
|
425
|
+
|
426
|
+
# The default size of the payload of fragment frames
|
427
|
+
DEFAULT_FRAGMENT_SIZE = 4096
|
428
|
+
|
429
|
+
|
430
|
+
# Loggability API -- log to the mongrel2 logger
|
431
|
+
log_to :mongrel2
|
432
|
+
|
433
|
+
|
434
|
+
### Create one or more fragmented frames for the data read from +io+ and yield
|
435
|
+
### each to the specified block. If no block is given, return a iterator that
|
436
|
+
### will yield the frames instead. The +io+ can be any object that responds to
|
437
|
+
### #readpartial, and the blocking semantics follow those of that method when
|
438
|
+
### iterating.
|
439
|
+
def self::each_fragment( io, opcode, size: DEFAULT_FRAGMENT_SIZE, &block )
|
440
|
+
raise ArgumentError, "Invalid opcode %p" % [opcode] unless OPCODE.key?( opcode )
|
441
|
+
|
442
|
+
iter = Enumerator.new do |yielder|
|
443
|
+
count = 0
|
444
|
+
|
445
|
+
until io.eof?
|
446
|
+
self.log.debug "Reading frame %d" % [ count ]
|
447
|
+
data = io.readpartial( size )
|
448
|
+
frame = if count.zero?
|
449
|
+
new( data, opcode )
|
450
|
+
else
|
451
|
+
new( data, :continuation )
|
452
|
+
end
|
453
|
+
frame.fin = io.eof?
|
454
|
+
|
455
|
+
yielder.yield( frame )
|
456
|
+
|
457
|
+
count += 1
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
return iter.each( &block ) if block
|
462
|
+
return iter
|
463
|
+
end
|
464
|
+
|
465
|
+
|
466
|
+
# Make convenience constructors for each opcode
|
467
|
+
OPCODE.keys.each do |opcode_name|
|
468
|
+
define_singleton_method( opcode_name ) do |payload='', *flags|
|
469
|
+
flags << opcode_name
|
470
|
+
return new( payload, *flags )
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
|
339
475
|
### Define accessors for the flag of the specified +name+ and +bit+.
|
340
476
|
def self::attr_flag( name, bitmask )
|
341
477
|
define_method( "#{name}?" ) do
|
@@ -351,22 +487,18 @@ module Mongrel2::WebSocket
|
|
351
487
|
end
|
352
488
|
|
353
489
|
|
354
|
-
|
355
490
|
#################################################################
|
356
491
|
### I N S T A N C E M E T H O D S
|
357
492
|
#################################################################
|
358
493
|
|
359
|
-
###
|
360
|
-
def initialize(
|
361
|
-
payload.
|
362
|
-
|
494
|
+
### Create a new websocket frame that will be the body of a request or response.
|
495
|
+
def initialize( payload='', *flags )
|
496
|
+
@payload = StringIO.new( payload.dup )
|
497
|
+
@flags = DEFAULT_FLAGS
|
498
|
+
@errors = []
|
499
|
+
@chunksize = DEFAULT_CHUNKSIZE
|
363
500
|
|
364
|
-
|
365
|
-
|
366
|
-
@flags = Integer( self.headers.flags || DEFAULT_FLAGS )
|
367
|
-
@request_frame = nil
|
368
|
-
@errors = []
|
369
|
-
@chunksize = DEFAULT_CHUNKSIZE
|
501
|
+
self.set_flags( *flags ) unless flags.empty?
|
370
502
|
end
|
371
503
|
|
372
504
|
|
@@ -375,17 +507,14 @@ module Mongrel2::WebSocket
|
|
375
507
|
######
|
376
508
|
|
377
509
|
# The payload data
|
378
|
-
attr_accessor :
|
379
|
-
alias_method :
|
380
|
-
alias_method :
|
510
|
+
attr_accessor :payload
|
511
|
+
alias_method :body, :payload
|
512
|
+
alias_method :body=, :payload=
|
381
513
|
|
382
514
|
|
383
515
|
# The frame's header flags as an Integer
|
384
516
|
attr_accessor :flags
|
385
517
|
|
386
|
-
# The frame that this one is a response to
|
387
|
-
attr_accessor :request_frame
|
388
|
-
|
389
518
|
# The Array of validation errors
|
390
519
|
attr_reader :errors
|
391
520
|
|
@@ -407,6 +536,38 @@ module Mongrel2::WebSocket
|
|
407
536
|
attr_flag :rsv3, RSV3_FLAG
|
408
537
|
|
409
538
|
|
539
|
+
### Apply flag bits and opcodes: (:fin, :rsv1, :rsv2, :rsv3, :continuation,
|
540
|
+
### :text, :binary, :close, :ping, :pong) to the frame.
|
541
|
+
###
|
542
|
+
### # Transform the frame into a CLOSE frame and set its FIN flag
|
543
|
+
### frame.set_flags( :fin, :close )
|
544
|
+
###
|
545
|
+
def set_flags( *flag_symbols )
|
546
|
+
flag_symbols.flatten!
|
547
|
+
flag_symbols.compact!
|
548
|
+
|
549
|
+
self.log.debug "Setting flags for symbols: %p" % [ flag_symbols ]
|
550
|
+
|
551
|
+
flag_symbols.each do |flag|
|
552
|
+
case flag
|
553
|
+
when :fin, :rsv1, :rsv2, :rsv3
|
554
|
+
self.__send__( "#{flag}=", true )
|
555
|
+
when :continuation, :text, :binary, :close, :ping, :pong
|
556
|
+
self.opcode = flag
|
557
|
+
when Integer
|
558
|
+
self.log.debug " setting Integer flags directly: %#08b" % [ flag ]
|
559
|
+
self.flags |= flag
|
560
|
+
when /\A0x\h{2}\z/
|
561
|
+
val = Integer( flag )
|
562
|
+
self.log.debug " setting (stringified) Integer flags directly: %#08b" % [ val ]
|
563
|
+
self.flags = val
|
564
|
+
else
|
565
|
+
raise ArgumentError, "Don't know what the %p flag is." % [ flag ]
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
|
410
571
|
### Returns true if one or more of the RSV1-3 bits is set.
|
411
572
|
def has_rsv_flags?
|
412
573
|
return ( self.flags & RSV_FLAG_MASK ).nonzero?
|
@@ -486,7 +647,8 @@ module Mongrel2::WebSocket
|
|
486
647
|
def validate
|
487
648
|
unless self.valid?
|
488
649
|
self.log.error "Validation failed."
|
489
|
-
raise Mongrel2::WebSocket::FrameError, "invalid frame: %s" %
|
650
|
+
raise Mongrel2::WebSocket::FrameError, "invalid frame: %s" %
|
651
|
+
[ self.errors.join(', ') ]
|
490
652
|
end
|
491
653
|
end
|
492
654
|
|
@@ -507,7 +669,7 @@ module Mongrel2::WebSocket
|
|
507
669
|
|
508
670
|
### Mongrel2::Connection API -- Yield the response in chunks if called with a block, else
|
509
671
|
### return an Enumerator that will do the same.
|
510
|
-
def each_chunk
|
672
|
+
def each_chunk( &block )
|
511
673
|
self.validate
|
512
674
|
|
513
675
|
iter = Enumerator.new do |yielder|
|
@@ -516,122 +678,42 @@ module Mongrel2::WebSocket
|
|
516
678
|
end
|
517
679
|
end
|
518
680
|
|
519
|
-
|
520
|
-
|
521
|
-
iter.each( &block )
|
522
|
-
else
|
523
|
-
return iter
|
524
|
-
end
|
681
|
+
return iter unless block
|
682
|
+
return iter.each( &block )
|
525
683
|
end
|
526
684
|
|
527
685
|
|
528
686
|
### Stringify into a response suitable for sending to the client.
|
529
687
|
def to_s
|
530
|
-
self.
|
531
|
-
self.payload.rewind
|
532
|
-
self.payload.set_encoding( 'binary' )
|
533
|
-
|
534
|
-
header = self.make_header
|
535
|
-
data = self.payload.read
|
536
|
-
|
537
|
-
return header + data
|
538
|
-
end
|
688
|
+
return self.each_byte.to_a.pack( 'C*' )
|
539
689
|
end
|
540
690
|
|
541
691
|
|
542
692
|
### Return an Enumerator for the bytes of the raw frame as it appears
|
543
693
|
### on the wire.
|
544
|
-
def
|
545
|
-
self.
|
546
|
-
self.payload.
|
547
|
-
self.log.debug "Making a bytes iterator for a %s payload" %
|
548
|
-
[ self.payload.external_encoding.name ]
|
549
|
-
|
550
|
-
return Enumerator.new do |yielder|
|
551
|
-
self.payload.set_encoding( 'binary' )
|
552
|
-
self.payload.rewind
|
553
|
-
|
554
|
-
header_i = self.make_header.each_byte
|
555
|
-
body_i = self.payload.each_byte
|
556
|
-
|
557
|
-
header_i.each_with_index {|byte, i| yielder.yield(byte) }
|
558
|
-
body_i.each_with_index {|byte, i| yielder.yield(byte) }
|
559
|
-
end
|
560
|
-
end
|
561
|
-
end
|
694
|
+
def each_byte( &block )
|
695
|
+
self.log.debug "Making a bytes iterator for a %s payload" %
|
696
|
+
[ self.payload.external_encoding.name ]
|
562
697
|
|
698
|
+
payload_copy = self.payload.clone
|
699
|
+
payload_copy.set_encoding( 'binary' )
|
700
|
+
payload_copy.rewind
|
563
701
|
|
564
|
-
|
565
|
-
### when the block returns.
|
566
|
-
def remember_payload_settings
|
567
|
-
original_enc = self.payload.external_encoding
|
568
|
-
original_pos = self.payload.pos
|
702
|
+
iter = self.make_header.each_byte + payload_copy.each_byte
|
569
703
|
|
570
|
-
|
571
|
-
|
572
|
-
self.payload.set_encoding( original_enc ) if original_enc
|
573
|
-
self.payload.pos = original_pos if original_pos
|
704
|
+
return iter unless block
|
705
|
+
return iter.each( &block )
|
574
706
|
end
|
707
|
+
alias_method :bytes, :each_byte
|
575
708
|
|
576
709
|
|
577
|
-
###
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
self.log.debug "Setting up response %p with symmetrical flags" % [ @response ]
|
585
|
-
if self.opcode == :ping
|
586
|
-
@response.opcode = :pong
|
587
|
-
IO.copy_stream( self.payload, @response.payload, 4096 )
|
588
|
-
else
|
589
|
-
@response.opcode = self.numeric_opcode
|
590
|
-
end
|
591
|
-
|
592
|
-
# Set flags in the response
|
593
|
-
unless flags.empty?
|
594
|
-
self.log.debug " applying custom flags: %p" % [ flags ]
|
595
|
-
@response.set_flags( *flags )
|
596
|
-
end
|
597
|
-
|
598
|
-
end
|
599
|
-
|
600
|
-
return @response
|
601
|
-
end
|
602
|
-
|
603
|
-
|
604
|
-
### Apply flag bits and opcodes: (:fin, :rsv1, :rsv2, :rsv3, :continuation,
|
605
|
-
### :text, :binary, :close, :ping, :pong) to the frame.
|
606
|
-
###
|
607
|
-
### # Transform the frame into a CLOSE frame and set its FIN flag
|
608
|
-
### frame.set_flags( :fin, :close )
|
609
|
-
###
|
610
|
-
def set_flags( *flag_symbols )
|
611
|
-
flag_symbols.flatten!
|
612
|
-
flag_symbols.compact!
|
613
|
-
|
614
|
-
self.log.debug "Setting flags for symbols: %p" % [ flag_symbols ]
|
615
|
-
|
616
|
-
flag_symbols.each do |flag|
|
617
|
-
case flag
|
618
|
-
when :fin, :rsv1, :rsv2, :rsv3
|
619
|
-
self.__send__( "#{flag}=", true )
|
620
|
-
when :continuation, :text, :binary, :close, :ping, :pong
|
621
|
-
self.opcode = flag
|
622
|
-
when Integer
|
623
|
-
self.log.debug " setting Integer flags directly: 0b%08b" % [ flag ]
|
624
|
-
self.flags |= flag
|
625
|
-
else
|
626
|
-
raise ArgumentError, "Don't know what the %p flag is." % [ flag ]
|
627
|
-
end
|
628
|
-
end
|
629
|
-
end
|
630
|
-
|
631
|
-
|
632
|
-
### Compatibility with Mongrel2::Response.
|
633
|
-
def extended_reply? # :nodoc:
|
634
|
-
return false
|
710
|
+
### Return the frame as a human-readable string suitable for debugging.
|
711
|
+
def inspect
|
712
|
+
return "#<%p:%#0x %s>" % [
|
713
|
+
self.class,
|
714
|
+
self.object_id * 2,
|
715
|
+
self.inspect_details,
|
716
|
+
]
|
635
717
|
end
|
636
718
|
|
637
719
|
|
@@ -655,7 +737,7 @@ module Mongrel2::WebSocket
|
|
655
737
|
|
656
738
|
### Make a WebSocket header for the frame and return it.
|
657
739
|
def make_header
|
658
|
-
header =
|
740
|
+
header = nil
|
659
741
|
length = self.payload.size
|
660
742
|
|
661
743
|
self.log.debug "Making wire protocol header for payload of %d bytes" % [ length ]
|