mongrel2 0.52.2 → 0.53.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 ]
|