Class: Mongrel2::WebSocket::Frame
- Inherits:
-
Object
- Object
- Mongrel2::WebSocket::Frame
- Extended by:
- Loggability
- Includes:
- Constants
- Defined in:
- lib/mongrel2/websocket.rb
Overview
WebSocket frame class; this is used for both requests and responses in WebSocket services.
Constant Summary collapse
- DEFAULT_FLAGS =
The default frame header flags: FIN + CLOSE
- DEFAULT_FRAGMENT_SIZE =
The default size of the payload of fragment frames
4096
Constants included from Constants
Constants::CLOSE_ABNORMAL_STATUS, Constants::CLOSE_BAD_DATA, Constants::CLOSE_BAD_DATA_TYPE, Constants::CLOSE_EXCEPTION, Constants::CLOSE_GOING_AWAY, Constants::CLOSE_MESSAGE_TOO_LARGE, Constants::CLOSE_MISSING_EXTENSION, Constants::CLOSE_MISSING_STATUS, Constants::CLOSE_NORMAL, Constants::CLOSE_POLICY_VIOLATION, Constants::CLOSE_PROTOCOL_ERROR, Constants::CLOSE_RESERVED, Constants::CLOSE_TLS_ERROR, Constants::CLOSING_STATUS_DESC, Constants::DEFAULT_CHUNKSIZE, Constants::FIN_FLAG, Constants::OPCODE, Constants::OPCODE_BITMASK, Constants::OPCODE_CONTROL_MASK, Constants::OPCODE_NAME, Constants::RSV1_FLAG, Constants::RSV2_FLAG, Constants::RSV3_FLAG, Constants::RSV_FLAG_MASK
Instance Attribute Summary collapse
-
#chunksize ⇒ Object
The number of bytes to write to Mongrel in a single “chunk”.
-
#errors ⇒ Object
readonly
The Array of validation errors.
-
#flags ⇒ Object
The frame’s header flags as an Integer.
-
#payload ⇒ Object
(also: #body)
The payload data.
Class Method Summary collapse
-
.attr_flag(name, bitmask) ⇒ Object
Define accessors for the flag of the specified
name
andbit
. -
.each_fragment(io, opcode, size: DEFAULT_FRAGMENT_SIZE, &block) ⇒ Object
Create one or more fragmented frames for the data read from
io
and yield each to the specified block.
Instance Method Summary collapse
-
#<<(object) ⇒ Object
Append the given
object
to the payload. -
#control? ⇒ Boolean
Returns
true
if the request is a WebSocket control frame. -
#each_byte(&block) ⇒ Object
(also: #bytes)
Return an Enumerator for the bytes of the raw frame as it appears on the wire.
-
#each_chunk(&block) ⇒ Object
Mongrel2::Connection API – Yield the response in chunks if called with a block, else return an Enumerator that will do the same.
-
#has_rsv_flags? ⇒ Boolean
Returns true if one or more of the RSV1-3 bits is set.
-
#initialize(payload = '', *flags) ⇒ Frame
constructor
Create a new websocket frame that will be the body of a request or response.
-
#inspect ⇒ Object
Return the frame as a human-readable string suitable for debugging.
-
#make_close_frame(statuscode = Mongrel2::WebSocket::CLOSE_NORMAL) ⇒ Object
Set the :close opcode on this frame and set its status to
statuscode
. -
#numeric_opcode ⇒ Object
Return the numeric opcode of the frame.
-
#opcode ⇒ Object
Returns the name of the frame’s opcode as a Symbol.
-
#opcode=(code) ⇒ Object
Set the frame’s opcode to
code
, which should be either a numeric opcode or its equivalent name (i.e., :continuation, :text, :binary, :close, :ping, :pong). -
#puts(*objects) ⇒ Object
Write the given
objects
to the payload, calling #to_s on each one. -
#set_flags(*flag_symbols) ⇒ Object
Apply flag bits and opcodes: (:fin, :rsv1, :rsv2, :rsv3, :continuation, :text, :binary, :close, :ping, :pong) to the frame.
-
#set_status(statuscode) ⇒ Object
Overwrite the frame’s payload with a status message based on
statuscode
. -
#to_s ⇒ Object
Stringify into a response suitable for sending to the client.
-
#valid? ⇒ Boolean
Sanity-checks the frame and returns
false
if any problems are found. -
#validate ⇒ Object
Validate the frame, raising a Mongrel2::WebSocket::FrameError if there are validation problems.
Constructor Details
#initialize(payload = '', *flags) ⇒ Frame
Create a new websocket frame that will be the body of a request or response.
495 496 497 498 499 500 501 502 |
# File 'lib/mongrel2/websocket.rb', line 495 def initialize( payload='', *flags ) @payload = StringIO.new( payload.dup ) @flags = DEFAULT_FLAGS @errors = [] @chunksize = DEFAULT_CHUNKSIZE self.set_flags( *flags ) unless flags.empty? end |
Instance Attribute Details
#chunksize ⇒ Object
The number of bytes to write to Mongrel in a single “chunk”
522 523 524 |
# File 'lib/mongrel2/websocket.rb', line 522 def chunksize @chunksize end |
#errors ⇒ Object (readonly)
The Array of validation errors
519 520 521 |
# File 'lib/mongrel2/websocket.rb', line 519 def errors @errors end |
#flags ⇒ Object
The frame’s header flags as an Integer
516 517 518 |
# File 'lib/mongrel2/websocket.rb', line 516 def flags @flags end |
#payload ⇒ Object Also known as: body
The payload data
510 511 512 |
# File 'lib/mongrel2/websocket.rb', line 510 def payload @payload end |
Class Method Details
.attr_flag(name, bitmask) ⇒ Object
Define accessors for the flag of the specified name
and bit
.
476 477 478 479 480 481 482 483 484 485 486 487 |
# File 'lib/mongrel2/websocket.rb', line 476 def self::attr_flag( name, bitmask ) define_method( "#{name}?" ) do (self.flags & bitmask).nonzero? end define_method( "#{name}=" ) do |newvalue| if newvalue self.flags |= bitmask else self.flags ^= ( self.flags & bitmask ) end end end |
.each_fragment(io, opcode, size: DEFAULT_FRAGMENT_SIZE, &block) ⇒ Object
Create one or more fragmented frames for the data read from io
and yield each to the specified block. If no block is given, return a iterator that will yield the frames instead. The io
can be any object that responds to #readpartial, and the blocking semantics follow those of that method when iterating.
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 |
# File 'lib/mongrel2/websocket.rb', line 439 def self::each_fragment( io, opcode, size: DEFAULT_FRAGMENT_SIZE, &block ) raise ArgumentError, "Invalid opcode %p" % [opcode] unless OPCODE.key?( opcode ) iter = Enumerator.new do |yielder| count = 0 until io.eof? self.log.debug "Reading frame %d" % [ count ] data = io.readpartial( size ) frame = if count.zero? new( data, opcode ) else new( data, :continuation ) end frame.fin = io.eof? yielder.yield( frame ) count += 1 end end return iter.each( &block ) if block return iter end |
Instance Method Details
#<<(object) ⇒ Object
Append the given object
to the payload. Returns the Frame for chaining.
615 616 617 618 |
# File 'lib/mongrel2/websocket.rb', line 615 def <<( object ) self.payload << object return self end |
#control? ⇒ Boolean
Returns true
if the request is a WebSocket control frame.
608 609 610 |
# File 'lib/mongrel2/websocket.rb', line 608 def control? return ( self.flags & OPCODE_CONTROL_MASK ).nonzero? end |
#each_byte(&block) ⇒ Object Also known as: bytes
Return an Enumerator for the bytes of the raw frame as it appears on the wire.
694 695 696 697 698 699 700 701 702 703 704 705 706 |
# File 'lib/mongrel2/websocket.rb', line 694 def each_byte( &block ) self.log.debug "Making a bytes iterator for a %s payload" % [ self.payload.external_encoding.name ] payload_copy = self.payload.clone payload_copy.set_encoding( 'binary' ) payload_copy.rewind iter = self.make_header.each_byte + payload_copy.each_byte return iter unless block return iter.each( &block ) end |
#each_chunk(&block) ⇒ Object
Mongrel2::Connection API – Yield the response in chunks if called with a block, else return an Enumerator that will do the same.
672 673 674 675 676 677 678 679 680 681 682 683 |
# File 'lib/mongrel2/websocket.rb', line 672 def each_chunk( &block ) self.validate iter = Enumerator.new do |yielder| self.bytes.each_slice( self.chunksize ) do |bytes| yielder.yield( bytes.pack('C*') ) end end return iter unless block return iter.each( &block ) end |
#has_rsv_flags? ⇒ Boolean
Returns true if one or more of the RSV1-3 bits is set.
572 573 574 |
# File 'lib/mongrel2/websocket.rb', line 572 def has_rsv_flags? return ( self.flags & RSV_FLAG_MASK ).nonzero? end |
#inspect ⇒ Object
Return the frame as a human-readable string suitable for debugging.
711 712 713 714 715 716 717 |
# File 'lib/mongrel2/websocket.rb', line 711 def inspect return "#<%p:%#0x %s>" % [ self.class, self.object_id * 2, self.inspect_details, ] end |
#make_close_frame(statuscode = Mongrel2::WebSocket::CLOSE_NORMAL) ⇒ Object
Set the :close opcode on this frame and set its status to statuscode
.
628 629 630 631 |
# File 'lib/mongrel2/websocket.rb', line 628 def make_close_frame( statuscode=Mongrel2::WebSocket::CLOSE_NORMAL ) self.opcode = :close self.set_status( statuscode ) end |
#numeric_opcode ⇒ Object
Return the numeric opcode of the frame.
585 586 587 |
# File 'lib/mongrel2/websocket.rb', line 585 def numeric_opcode return self.flags & OPCODE_BITMASK end |
#opcode ⇒ Object
Returns the name of the frame’s opcode as a Symbol. The #numeric_opcode method returns the numeric one.
579 580 581 |
# File 'lib/mongrel2/websocket.rb', line 579 def opcode return OPCODE_NAME[ self.numeric_opcode ] end |
#opcode=(code) ⇒ Object
Set the frame’s opcode to code
, which should be either a numeric opcode or its equivalent name (i.e., :continuation, :text, :binary, :close, :ping, :pong)
592 593 594 595 596 597 598 599 600 601 602 603 604 |
# File 'lib/mongrel2/websocket.rb', line 592 def opcode=( code ) opcode = nil if code.is_a?( Numeric ) opcode = Integer( code ) else opcode = OPCODE[ code.to_sym ] or raise ArgumentError, "unknown opcode %p" % [ code ] end self.flags ^= ( self.flags & OPCODE_BITMASK ) self.flags |= opcode end |
#puts(*objects) ⇒ Object
Write the given objects
to the payload, calling #to_s on each one.
622 623 624 |
# File 'lib/mongrel2/websocket.rb', line 622 def puts( *objects ) self.payload.puts( *objects ) end |
#set_flags(*flag_symbols) ⇒ Object
Apply flag bits and opcodes: (:fin, :rsv1, :rsv2, :rsv3, :continuation, :text, :binary, :close, :ping, :pong) to the frame.
# Transform the frame into a CLOSE frame and set its FIN flag
frame.set_flags( :fin, :close )
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 |
# File 'lib/mongrel2/websocket.rb', line 545 def set_flags( *flag_symbols ) flag_symbols.flatten! flag_symbols.compact! self.log.debug "Setting flags for symbols: %p" % [ flag_symbols ] flag_symbols.each do |flag| case flag when :fin, :rsv1, :rsv2, :rsv3 self.__send__( "#{flag}=", true ) when :continuation, :text, :binary, :close, :ping, :pong self.opcode = flag when Integer self.log.debug " setting Integer flags directly: %#08b" % [ flag ] self.flags |= flag when /\A0x\h{2}\z/ val = Integer( flag ) self.log.debug " setting (stringified) Integer flags directly: %#08b" % [ val ] self.flags = val else raise ArgumentError, "Don't know what the %p flag is." % [ flag ] end end end |
#set_status(statuscode) ⇒ Object
Overwrite the frame’s payload with a status message based on statuscode
.
636 637 638 639 640 641 642 |
# File 'lib/mongrel2/websocket.rb', line 636 def set_status( statuscode ) self.log.warn "Unknown status code %d" unless CLOSING_STATUS_DESC.key?( statuscode ) status_msg = "%d %s" % [ statuscode, CLOSING_STATUS_DESC[statuscode] ] self.payload.truncate( 0 ) self.payload.puts( status_msg ) end |
#to_s ⇒ Object
Stringify into a response suitable for sending to the client.
687 688 689 |
# File 'lib/mongrel2/websocket.rb', line 687 def to_s return self.each_byte.to_a.pack( 'C*' ) end |
#valid? ⇒ Boolean
Sanity-checks the frame and returns false
if any problems are found. Error messages will be in #errors.
658 659 660 661 662 663 664 665 666 667 |
# File 'lib/mongrel2/websocket.rb', line 658 def valid? self.errors.clear self.validate_payload_encoding self.validate_control_frame self.validate_opcode self.validate_reserved_flags return self.errors.empty? end |
#validate ⇒ Object
Validate the frame, raising a Mongrel2::WebSocket::FrameError if there are validation problems.
647 648 649 650 651 652 653 |
# File 'lib/mongrel2/websocket.rb', line 647 def validate unless self.valid? self.log.error "Validation failed." raise Mongrel2::WebSocket::FrameError, "invalid frame: %s" % [ self.errors.join(', ') ] end end |