mongrel2 0.52.2 → 0.53.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +128 -2
  5. data/History.rdoc +10 -0
  6. data/Manifest.txt +1 -0
  7. data/Rakefile +1 -1
  8. data/lib/mongrel2.rb +3 -3
  9. data/lib/mongrel2/config.rb +2 -1
  10. data/lib/mongrel2/config/directory.rb +2 -1
  11. data/lib/mongrel2/config/dsl.rb +2 -1
  12. data/lib/mongrel2/config/filter.rb +2 -1
  13. data/lib/mongrel2/config/handler.rb +2 -1
  14. data/lib/mongrel2/config/host.rb +2 -1
  15. data/lib/mongrel2/config/log.rb +2 -1
  16. data/lib/mongrel2/config/mimetype.rb +2 -1
  17. data/lib/mongrel2/config/proxy.rb +2 -1
  18. data/lib/mongrel2/config/route.rb +2 -1
  19. data/lib/mongrel2/config/server.rb +2 -1
  20. data/lib/mongrel2/config/setting.rb +2 -1
  21. data/lib/mongrel2/config/statistic.rb +2 -1
  22. data/lib/mongrel2/config/xrequest.rb +2 -1
  23. data/lib/mongrel2/connection.rb +1 -1
  24. data/lib/mongrel2/constants.rb +2 -1
  25. data/lib/mongrel2/control.rb +2 -1
  26. data/lib/mongrel2/exceptions.rb +2 -1
  27. data/lib/mongrel2/handler.rb +9 -8
  28. data/lib/mongrel2/httprequest.rb +2 -1
  29. data/lib/mongrel2/httpresponse.rb +2 -1
  30. data/lib/mongrel2/jsonrequest.rb +2 -1
  31. data/lib/mongrel2/request.rb +16 -9
  32. data/lib/mongrel2/response.rb +14 -6
  33. data/lib/mongrel2/table.rb +24 -3
  34. data/lib/mongrel2/testing.rb +12 -25
  35. data/lib/mongrel2/websocket.rb +230 -148
  36. data/lib/mongrel2/xmlrequest.rb +2 -1
  37. data/spec/constants.rb +3 -2
  38. data/spec/helpers.rb +2 -1
  39. data/spec/matchers.rb +2 -1
  40. data/spec/mongrel2/config/directory_spec.rb +2 -1
  41. data/spec/mongrel2/config/dsl_spec.rb +2 -1
  42. data/spec/mongrel2/config/filter_spec.rb +2 -1
  43. data/spec/mongrel2/config/handler_spec.rb +2 -1
  44. data/spec/mongrel2/config/host_spec.rb +2 -1
  45. data/spec/mongrel2/config/log_spec.rb +2 -1
  46. data/spec/mongrel2/config/proxy_spec.rb +2 -1
  47. data/spec/mongrel2/config/route_spec.rb +2 -1
  48. data/spec/mongrel2/config/server_spec.rb +2 -1
  49. data/spec/mongrel2/config/setting_spec.rb +2 -1
  50. data/spec/mongrel2/config/statistic_spec.rb +2 -1
  51. data/spec/mongrel2/config/xrequest_spec.rb +2 -1
  52. data/spec/mongrel2/config_spec.rb +2 -1
  53. data/spec/mongrel2/connection_spec.rb +2 -1
  54. data/spec/mongrel2/constants_spec.rb +2 -1
  55. data/spec/mongrel2/control_spec.rb +2 -1
  56. data/spec/mongrel2/handler_spec.rb +4 -3
  57. data/spec/mongrel2/httprequest_spec.rb +2 -1
  58. data/spec/mongrel2/httpresponse_spec.rb +2 -1
  59. data/spec/mongrel2/request_spec.rb +7 -1
  60. data/spec/mongrel2/response_spec.rb +9 -1
  61. data/spec/mongrel2/table_spec.rb +4 -3
  62. data/spec/mongrel2/testing_spec.rb +142 -0
  63. data/spec/mongrel2/websocket_spec.rb +300 -136
  64. data/spec/mongrel2/xmlrequest_spec.rb +2 -1
  65. data/spec/mongrel2_spec.rb +2 -1
  66. metadata +6 -5
  67. metadata.gz.sig +0 -0
@@ -1,4 +1,5 @@
1
- #!/usr/bin/ruby
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/%d)>" % [
146
+ return "#<%p:0x%016x %s (%s)>" % [
138
147
  self.class,
139
148
  self.object_id * 2,
140
149
  self.inspect_details,
141
- self.sender_id,
142
- self.conn_id
150
+ self.socket_id
143
151
  ]
144
152
  end
145
153
 
@@ -1,4 +1,5 @@
1
- #!/usr/bin/ruby
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 = [ :"[]", :"[]=", :delete, :fetch, :has_key?, :include?, :member?, :store ]
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
  #########
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
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
- @conn_id = 0
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 WebSocketFrameFactory < Mongrel2::RequestFactory
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
 
@@ -1,5 +1,7 @@
1
1
  # -*- ruby -*-
2
- #encoding: utf-8
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 common to WebSocket frame classes.
210
- module Methods
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
- ### Get a string identifying the websocket the frame belongs to.
213
- def socket_id
214
- return [ self.sender_id, self.conn_id ].join( ':' )
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 frame class; this is used for both requests and responses in
307
- # WebSocket services.
308
- class Frame < Mongrel2::Request
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. Since
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 self
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
- ### Create a response frame from the given request +frame+.
330
- def self::from_request( frame )
331
- self.log.debug "Creating a %p response to request %p" % [ self, frame ]
332
- response = new( frame.sender_id, frame.conn_id, frame.path )
333
- response.request_frame = frame
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
- ### Override the constructor to add Integer flags extracted from the FLAGS header.
360
- def initialize( sender_id, conn_id, path, headers={}, payload='', raw=nil )
361
- payload.force_encoding( Encoding::UTF_8 ) if
362
- payload.encoding == Encoding::ASCII_8BIT
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
- super
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 :body
379
- alias_method :payload, :body
380
- alias_method :payload=, :body=
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" % [ self.errors.join(', ') ]
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
- if block_given?
520
- block = Proc.new
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.remember_payload_settings do
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 bytes
545
- self.remember_payload_settings do
546
- self.payload.rewind
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
- ### Remember the payload IO's external encoding, position, etc. and restore them
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
- yield
571
- ensure
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
- ### Create a frame in response to the receiving Frame (i.e., with the same
578
- ### Mongrel2 connection ID and sender).
579
- def response( *flags )
580
- unless @response
581
- @response = super()
582
-
583
- # Set the opcode
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 = ''.force_encoding( Encoding::ASCII_8BIT )
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 ]