1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
|
# frozen_string_literal: true
module WebSocket
module Handshake
# Construct or parse a server WebSocket handshake.
#
# @example
# handshake = WebSocket::Handshake::Server.new
#
# # Parse client request
# @handshake << <<EOF
# GET /demo HTTP/1.1\r
# Upgrade: websocket\r
# Connection: Upgrade\r
# Host: example.com\r
# Origin: http://example.com\r
# Sec-WebSocket-Version: 13\r
# Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r
# \r
# EOF
#
# # All data received?
# @handshake.finished?
#
# # No parsing errors?
# @handshake.valid?
#
# # Create response
# @handshake.to_s # HTTP/1.1 101 Switching Protocols
# # Upgrade: websocket
# # Connection: Upgrade
# # Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
#
class Server < Base
# Initialize new WebSocket Server
#
# @param [Hash] args Arguments for server
#
# @option args [Boolean] :secure If true then server will use wss:// protocol
# @option args [Array<String>] :protocols an array of supported sub-protocols
#
# @example
# Websocket::Handshake::Server.new(secure: true)
def initialize(args = {})
super
@secure ||= false
end
# Add text of request from Client. This method will parse content immediately and update version, state and error(if neccessary)
#
# @param [String] data Data to add
#
# @example
# @handshake << <<EOF
# GET /demo HTTP/1.1
# Upgrade: websocket
# Connection: Upgrade
# Host: example.com
# Origin: http://example.com
# Sec-WebSocket-Version: 13
# Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
#
# EOF
def <<(data)
super
set_version if parse_data
end
rescue_method :<<
# Parse the request from a rack environment
# @param env Rack Environment
#
# @example
# @handshake.from_rack(env)
def from_rack(env)
@headers = env.select { |key, _value| key.to_s.start_with? 'HTTP_' }.each_with_object({}) do |tuple, memo|
key, value = tuple
memo[key.gsub(/\AHTTP_/, '').tr('_', '-').downcase] = value
end
@path = env['REQUEST_PATH']
@query = env['QUERY_STRING']
set_version
# Passenger is blocking on read
# Unicorn doesn't support readpartial
# Maybe someone is providing even plain string?
# Better safe than sorry...
if @version == 76
input = env['rack.input']
@leftovers = if input.respond_to?(:readpartial)
input.readpartial
elsif input.respond_to?(:read)
input.read
else
input.to_s
end
end
@state = :finished
end
# Parse the request from hash
# @param hash Hash to import data
# @option hash [Hash] :headers HTTP headers of request, downcased
# @option hash [String] :path Path for request(without host and query string)
# @option hash [String] :query Query string for request
# @option hash [String] :body Body of request(if exists)
#
# @example
# @handshake.from_hash(hash)
def from_hash(hash)
@headers = hash[:headers] || {}
@path = hash[:path] || '/'
@query = hash[:query] || ''
@leftovers = hash[:body]
set_version
@state = :finished
end
# Should send content to client after finished parsing?
# @return [Boolean] true
def should_respond?
true
end
# Host of server according to client header
# @return [String] host
def host
@headers['host'].to_s.split(':')[0].to_s
end
# Port of server according to client header
# @return [String] port
def port
@headers['host'].to_s.split(':')[1]
end
private
# Set version of protocol basing on client requets. AFter cotting method calls include_version.
def set_version
@version = @headers['sec-websocket-version'].to_i if @headers['sec-websocket-version']
@version ||= @headers['sec-websocket-draft'].to_i if @headers['sec-websocket-draft']
@version ||= 76 if @headers['sec-websocket-key1']
@version ||= 75
include_version
end
# Include set of methods for selected protocol version
# @return [Boolean] false if protocol number is unknown, otherwise true
def include_version
@handler = case @version
when 75 then Handler::Server75.new(self)
when 76, 0..3 then Handler::Server76.new(self)
when 4..17 then Handler::Server04.new(self)
else raise WebSocket::Error::Handshake::UnknownVersion
end
end
PATH = %r{^(\w+) (\/[^\s]*) HTTP\/1\.1$}
# Parse first line of Client response.
# @param [String] line Line to parse
# @return [Boolean] True if parsed correctly. False otherwise
def parse_first_line(line)
line_parts = line.match(PATH)
raise WebSocket::Error::Handshake::InvalidHeader unless line_parts
method = line_parts[1].strip
raise WebSocket::Error::Handshake::GetRequestRequired unless method == 'GET'
resource_name = line_parts[2].strip
@path, @query = resource_name.split('?', 2)
end
end
end
end
|