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.
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 ]