lib/mongrel2/testing.rb in mongrel2-0.15.1 vs lib/mongrel2/testing.rb in mongrel2-0.16.0

- old
+ new

@@ -65,15 +65,31 @@ constants.each do |cname| const_get(cname).freeze end + ### Return the default testing headers hash for the receiving class. + def self::default_headers + return const_get( :DEFAULT_TESTING_HEADERS ) + end + + + ### Return the default configuration for the receiving factory class. + def self::default_factory_config + return const_get( :DEFAULT_FACTORY_CONFIG ) + end + + + ############################################################# + ### I N S T A N C E M E T H O D S + ############################################################# + ### Create a new RequestFactory with the given +config+, which will be merged with ### DEFAULT_FACTORY_CONFIG. def initialize( config={} ) - config[:headers] = DEFAULT_TESTING_HEADERS.merge( config[:headers] ) if config[:headers] - config = DEFAULT_FACTORY_CONFIG.merge( config ) + config[:headers] = self.class.default_headers.merge( config[:headers] ) if config[:headers] + config = self.class.default_factory_config.merge( config ) @sender_id = config[:sender_id] @host = config[:host] @port = config[:port] @route = config[:route] @@ -195,8 +211,191 @@ return headers end end # RequestFactory + + + ### A factory for generating WebSocket request objects for testing. + class WebSocketFrameFactory < Mongrel2::RequestFactory + include Mongrel2::Constants + + # The default host + DEFAULT_TESTING_HOST = 'localhost' + DEFAULT_TESTING_PORT = '8113' + DEFAULT_TESTING_ROUTE = '/ws' + + # The default WebSocket opcode + DEFAULT_OPCODE = :text + + # Default headers + DEFAULT_TESTING_HEADERS = { + 'METHOD' => 'WEBSOCKET', + 'PATTERN' => '/ws', + 'URI' => '/ws', + 'VERSION' => 'HTTP/1.1', + 'PATH' => '/ws', + 'upgrade' => 'websocket', + 'host' => DEFAULT_TESTING_HOST, + 'sec-websocket-key' => 'rBP9u8uxVvIYrH/8bNOPwQ==', + 'sec-websocket-version' => '13', + 'connection' => 'Upgrade', + 'origin' => "http://#{DEFAULT_TESTING_HOST}", + 'FLAGS' => '0x89', # FIN + PING + 'x-forwarded-for' => '127.0.0.1' + } + + # The defaults used by the websocket request factory + DEFAULT_FACTORY_CONFIG = { + :sender_id => DEFAULT_TEST_UUID, + :conn_id => DEFAULT_CONN_ID, + :host => DEFAULT_TESTING_HOST, + :port => DEFAULT_TESTING_PORT, + :route => DEFAULT_TESTING_ROUTE, + :headers => DEFAULT_TESTING_HEADERS, + } + + # Freeze all testing constants + constants.each do |cname| + const_get(cname).freeze + end + + + ### Create a new factory using the specified +config+. + def initialize( config={} ) + config[:headers] = DEFAULT_TESTING_HEADERS.merge( config[:headers] ) if config[:headers] + config = DEFAULT_FACTORY_CONFIG.merge( config ) + + @sender_id = config[:sender_id] + @host = config[:host] + @port = config[:port] + @route = config[:route] + @headers = Mongrel2::Table.new( config[:headers] ) + + @conn_id = 0 + end + + ###### + public + ###### + + ### Create a new request with the specified +uri+, +data+, and +flags+. + def create( uri, data, *flags ) + raise "Request doesn't route through %p" % [ self.route ] unless + uri.start_with?( self.route ) + + headers = if flags.last.is_a?( Hash ) then flags.pop else {} end + flagheader = make_flags_header( flags ) + headers = self.make_merged_headers( uri, flagheader, headers ) + rclass = Mongrel2::Request.subclass_for_method( :WEBSOCKET ) + + return rclass.new( self.sender_id, self.conn_id.to_s, self.route, headers, data ) + end + + + ### Create a continuation frame. + def continuation( uri, payload='', *flags ) + flags << :continuation + return self.create( uri, payload, flags ) + end + + + ### Create a text frame. + def text( uri, payload='', *flags ) + flags << :text + return self.create( uri, payload, flags ) + end + + + ### Create a binary frame. + def binary( uri, payload='', *flags ) + flags << :binary + return self.create( uri, payload, flags ) + end + + + ### Create a close frame. + def close( uri, payload='', *flags ) + flags << :close << :fin + return self.create( uri, payload, flags ) + end + + + ### Create a ping frame. + def ping( uri, payload='', *flags ) + flags << :ping << :fin + return self.create( uri, payload, flags ) + end + + + ### Create a pong frame. + def pong( uri, payload='', *flags ) + flags << :pong << :fin + return self.create( uri, payload, flags ) + end + + + + ######### + protected + ######### + + ### Merge the factory's headers with +userheaders+, and then merge in the + ### special headers that Mongrel2 adds that are based on the +uri+ and other + ### server attributes. + def make_merged_headers( uri, flags, userheaders ) + headers = self.headers.merge( userheaders ) + uri = URI( uri ) + + # Add mongrel headers + headers.uri = uri.to_s + headers.path = uri.path + headers.host = "%s:%d" % [ self.host, self.port ] + headers.query = uri.query if uri.query + headers.pattern = self.route + headers.origin = "http://#{headers.host}" + headers.flags = "0x%02x" % [ flags ] + + return headers + end + + + ####### + private + ####### + + ### Make a flags value out of flag Symbols that correspond to the flag + ### bits and opcodes: [ :fin, :rsv1, :rsv2, :rsv3, :continuation, + ### :text, :binary, :close, :ping, :pong ]. If the flags contain + ### Integers instead, they are ORed with the result. + def make_flags_header( *flag_symbols ) + flag_symbols.flatten! + flag_symbols.compact! + + Mongrel2.log.debug "Making a flags header for symbols: %p" % [ flag_symbols ] + + return flag_symbols.inject( 0x00 ) do |flags, flag| + case flag + when :fin + flags | WebSocket::FIN_FLAG + when :rsv1 + flags | WebSocket::RSV1_FLAG + when :rsv2 + flags | WebSocket::RSV2_FLAG + when :rsv3 + flags | WebSocket::RSV3_FLAG + when :continuation, :text, :binary, :close, :ping, :pong + # Opcodes clear any other opcodes present + flags ^= ( flags & WebSocket::OPCODE_BITMASK ) + flags | WebSocket::OPCODE[ flag ] + when Integer + flags | flag + else + raise ArgumentError, "Don't know what the %p flag is." % [ flag ] + end + end + end + + end # class WebSocketFrameFactory end # module Mongrel2