class EventMachine::WebSocket::Handshake

Resposible for creating the server handshake response

Attributes

parser[R]
protocol_version[R]

Public Class Methods

new(secure) click to toggle source

Unfortunately drafts 75 & 76 require knowledge of whether the connection is being terminated as ws/wss in order to generate the correct handshake response

# File lib/em-websocket/handshake.rb, line 16
def initialize(secure)
  @parser = Http::Parser.new
  @secure = secure

  @parser.on_headers_complete = proc { |headers|
    @headers = Hash[headers.map { |k,v| [k.downcase, v] }]
  }
end

Public Instance Methods

headers() click to toggle source

Returns the WebSocket upgrade headers as a hash.

Keys are strings, unmodified from the request.

# File lib/em-websocket/handshake.rb, line 39
def headers
  @parser.headers
end
headers_downcased() click to toggle source

The same as headers, except that the hash keys are downcased

# File lib/em-websocket/handshake.rb, line 45
def headers_downcased
  @headers
end
origin() click to toggle source

Returns the WebSocket origin header if provided

# File lib/em-websocket/handshake.rb, line 66
def origin
  @headers["origin"] || @headers["sec-websocket-origin"] || nil
end
path() click to toggle source

Returns the request path (excluding any query params)

# File lib/em-websocket/handshake.rb, line 51
def path
  @path
end
query() click to toggle source
# File lib/em-websocket/handshake.rb, line 60
def query
  Hash[query_string.split('&').map { |c| c.split('=', 2) }]
end
query_string() click to toggle source

Returns the query params as a string foo=bar&baz=…

# File lib/em-websocket/handshake.rb, line 56
def query_string
  @query_string
end
receive_data(data) click to toggle source
# File lib/em-websocket/handshake.rb, line 25
def receive_data(data)
  @parser << data

  if defined? @headers
    process(@headers, @parser.upgrade_data)
  end
rescue HTTP::Parser::Error => e
  fail(HandshakeError.new("Invalid HTTP header: #{e.message}"))
end
secure?() click to toggle source
# File lib/em-websocket/handshake.rb, line 70
def secure?
  @secure
end

Private Instance Methods

process(headers, remains) click to toggle source
# File lib/em-websocket/handshake.rb, line 76
def process(headers, remains)
  unless @parser.http_method == "GET"
    raise HandshakeError, "Must be GET request"
  end

  # Validate request path
  #
  # According to http://tools.ietf.org/search/rfc2616#section-5.1.2, an
  # invalid Request-URI should result in a 400 status code, but
  # HandshakeError's currently result in a WebSocket abort. It's not
  # clear which should take precedence, but an abort will do just fine.
  begin
    uri = URI.parse(@parser.request_url)
    @path = uri.path
    @query_string = uri.query || ""
  rescue URI::InvalidURIError
    raise HandshakeError, "Invalid request URI: #{@parser.request_url}"
  end

  # Validate Upgrade
  unless @parser.upgrade?
    raise HandshakeError, "Not an upgrade request"
  end
  upgrade = @headers['upgrade']
  unless upgrade.kind_of?(String) && upgrade.downcase == 'websocket'
    raise HandshakeError, "Invalid upgrade header: #{upgrade.inspect}"
  end

  # Determine version heuristically
  version = if @headers['sec-websocket-version']
    # Used from drafts 04 onwards
    @headers['sec-websocket-version'].to_i
  elsif @headers['sec-websocket-draft']
    # Used in drafts 01 - 03
    @headers['sec-websocket-draft'].to_i
  elsif @headers['sec-websocket-key1']
    76
  else
    75
  end

  # Additional handling of bytes after the header if required
  case version
  when 75
    if !remains.empty?
      raise HandshakeError, "Extra bytes after header"
    end
  when 76, 1..3
    if remains.length < 8
      # The whole third-key has not been received yet.
      return nil
    elsif remains.length > 8
      raise HandshakeError, "Extra bytes after third key"
    end
    @headers['third-key'] = remains
  end

  handshake_klass = case version
  when 75
    Handshake75
  when 76, 1..3
    Handshake76
  when 5, 6, 7, 8, 13
    Handshake04
  else
    # According to spec should abort the connection
    raise HandshakeError, "Protocol version #{version} not supported"
  end

  upgrade_response = handshake_klass.handshake(@headers, @parser.request_url, @secure)

  handler_klass = Handler.klass_factory(version)

  @protocol_version = version
  succeed(upgrade_response, handler_klass)
rescue HandshakeError => e
  fail(e)
end