websocket-driver 0.7.1

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.
@@ -0,0 +1,20 @@
1
+ module WebSocket
2
+ class Driver
3
+ class Hybi
4
+
5
+ class Frame
6
+ attr_accessor :final,
7
+ :rsv1,
8
+ :rsv2,
9
+ :rsv3,
10
+ :opcode,
11
+ :masked,
12
+ :masking_key,
13
+ :length_bytes,
14
+ :length,
15
+ :payload
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ module WebSocket
2
+ class Driver
3
+ class Hybi
4
+
5
+ class Message
6
+ attr_accessor :rsv1,
7
+ :rsv2,
8
+ :rsv3,
9
+ :opcode,
10
+ :data
11
+
12
+ def initialize
13
+ @rsv1 = false
14
+ @rsv2 = false
15
+ @rsv3 = false
16
+ @opcode = nil
17
+ @data = String.new('').force_encoding(BINARY)
18
+ end
19
+
20
+ def <<(frame)
21
+ @rsv1 ||= frame.rsv1
22
+ @rsv2 ||= frame.rsv2
23
+ @rsv3 ||= frame.rsv3
24
+ @opcode ||= frame.opcode
25
+ @data << frame.payload
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,68 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Proxy
5
+ include EventEmitter
6
+
7
+ PORTS = {'ws' => 80, 'wss' => 443}
8
+
9
+ attr_reader :status, :headers
10
+
11
+ def initialize(client, origin, options)
12
+ super()
13
+
14
+ @client = client
15
+ @http = HTTP::Response.new
16
+ @socket = client.instance_variable_get(:@socket)
17
+ @origin = URI.parse(@socket.url)
18
+ @url = URI.parse(origin)
19
+ @options = options
20
+ @state = 0
21
+
22
+ @headers = Headers.new
23
+ @headers['Host'] = @origin.host + (@origin.port ? ":#{@origin.port}" : '')
24
+ @headers['Connection'] = 'keep-alive'
25
+ @headers['Proxy-Connection'] = 'keep-alive'
26
+
27
+ if @url.user
28
+ auth = Base64.strict_encode64([@url.user, @url.password] * ':')
29
+ @headers['Proxy-Authorization'] = 'Basic ' + auth
30
+ end
31
+ end
32
+
33
+ def set_header(name, value)
34
+ return false unless @state == 0
35
+ @headers[name] = value
36
+ true
37
+ end
38
+
39
+ def start
40
+ return false unless @state == 0
41
+ @state = 1
42
+
43
+ port = @origin.port || PORTS[@origin.scheme]
44
+ start = "CONNECT #{@origin.host}:#{port} HTTP/1.1"
45
+ headers = [start, @headers.to_s, '']
46
+
47
+ @socket.write(headers.join("\r\n"))
48
+ true
49
+ end
50
+
51
+ def parse(chunk)
52
+ @http.parse(chunk)
53
+ return unless @http.complete?
54
+
55
+ @status = @http.code
56
+ @headers = Headers.new(@http.headers)
57
+
58
+ if @status == 200
59
+ emit(:connect, ConnectEvent.new)
60
+ else
61
+ message = "Can't establish a connection to the server at #{@socket.url}"
62
+ emit(:error, ProtocolError.new(message))
63
+ end
64
+ end
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,80 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Server < Driver
5
+ EVENTS = %w[open message error close]
6
+
7
+ def initialize(socket, options = {})
8
+ super
9
+ @http = HTTP::Request.new
10
+ @delegate = nil
11
+ end
12
+
13
+ def env
14
+ @http.complete? ? @http.env : nil
15
+ end
16
+
17
+ def url
18
+ return nil unless e = env
19
+
20
+ url = "ws://#{e['HTTP_HOST']}"
21
+ url << e['PATH_INFO']
22
+ url << "?#{e['QUERY_STRING']}" unless e['QUERY_STRING'] == ''
23
+ url
24
+ end
25
+
26
+ %w[add_extension set_header start frame text binary ping close].each do |method|
27
+ define_method(method) do |*args, &block|
28
+ if @delegate
29
+ @delegate.__send__(method, *args, &block)
30
+ else
31
+ @queue << [method, args, block]
32
+ true
33
+ end
34
+ end
35
+ end
36
+
37
+ %w[protocol version].each do |method|
38
+ define_method(method) do
39
+ @delegate && @delegate.__send__(method)
40
+ end
41
+ end
42
+
43
+ def parse(chunk)
44
+ return @delegate.parse(chunk) if @delegate
45
+
46
+ @http.parse(chunk)
47
+ return fail_request('Invalid HTTP request') if @http.error?
48
+ return unless @http.complete?
49
+
50
+ @delegate = Driver.rack(self, @options)
51
+ open
52
+
53
+ EVENTS.each do |event|
54
+ @delegate.on(event) { |e| emit(event, e) }
55
+ end
56
+
57
+ emit(:connect, ConnectEvent.new)
58
+ end
59
+
60
+ def write(buffer)
61
+ @socket.write(buffer)
62
+ end
63
+
64
+ private
65
+
66
+ def fail_request(message)
67
+ emit(:error, ProtocolError.new(message))
68
+ emit(:close, CloseEvent.new(Hybi::ERRORS[:protocol_error], message))
69
+ end
70
+
71
+ def open
72
+ @queue.each do |method, args, block|
73
+ @delegate.__send__(method, *args, &block)
74
+ end
75
+ @queue = []
76
+ end
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,55 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class StreamReader
5
+ # Try to minimise the number of reallocations done:
6
+ MINIMUM_AUTOMATIC_PRUNE_OFFSET = 128
7
+
8
+ def initialize
9
+ @buffer = String.new('').force_encoding(BINARY)
10
+ @offset = 0
11
+ end
12
+
13
+ def put(chunk)
14
+ return unless chunk and chunk.bytesize > 0
15
+ @buffer << chunk.force_encoding(BINARY)
16
+ end
17
+
18
+ # Read bytes from the data:
19
+ def read(length)
20
+ return nil if (@offset + length) > @buffer.bytesize
21
+
22
+ chunk = @buffer.byteslice(@offset, length)
23
+ @offset += chunk.bytesize
24
+
25
+ prune if @offset > MINIMUM_AUTOMATIC_PRUNE_OFFSET
26
+
27
+ return chunk
28
+ end
29
+
30
+ def each_byte
31
+ prune
32
+
33
+ @buffer.each_byte do |octet|
34
+ @offset += 1
35
+ yield octet
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def prune
42
+ buffer_size = @buffer.bytesize
43
+
44
+ if @offset > buffer_size
45
+ @buffer = String.new('').force_encoding(BINARY)
46
+ else
47
+ @buffer = @buffer.byteslice(@offset, buffer_size - @offset)
48
+ end
49
+
50
+ @offset = 0
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,15 @@
1
+ module WebSocket
2
+ module HTTP
3
+
4
+ root = File.expand_path('../http', __FILE__)
5
+
6
+ autoload :Headers, root + '/headers'
7
+ autoload :Request, root + '/request'
8
+ autoload :Response, root + '/response'
9
+
10
+ def self.normalize_header(name)
11
+ name.to_s.strip.downcase.gsub(/^http_/, '').gsub(/_/, '-')
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,112 @@
1
+ module WebSocket
2
+ module HTTP
3
+
4
+ module Headers
5
+ MAX_LINE_LENGTH = 4096
6
+ CR = 0x0D
7
+ LF = 0x0A
8
+
9
+ # RFC 2616 grammar rules:
10
+ #
11
+ # CHAR = <any US-ASCII character (octets 0 - 127)>
12
+ #
13
+ # CTL = <any US-ASCII control character
14
+ # (octets 0 - 31) and DEL (127)>
15
+ #
16
+ # SP = <US-ASCII SP, space (32)>
17
+ #
18
+ # HT = <US-ASCII HT, horizontal-tab (9)>
19
+ #
20
+ # token = 1*<any CHAR except CTLs or separators>
21
+ #
22
+ # separators = "(" | ")" | "<" | ">" | "@"
23
+ # | "," | ";" | ":" | "\" | <">
24
+ # | "/" | "[" | "]" | "?" | "="
25
+ # | "{" | "}" | SP | HT
26
+ #
27
+ # Or, as redefined in RFC 7230:
28
+ #
29
+ # token = 1*tchar
30
+ #
31
+ # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
32
+ # / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
33
+ # / DIGIT / ALPHA
34
+ # ; any VCHAR, except delimiters
35
+
36
+ HEADER_LINE = /^([!#\$%&'\*\+\-\.\^_`\|~0-9a-z]+):\s*([\x20-\x7e]*?)\s*$/i
37
+
38
+ attr_reader :headers
39
+
40
+ def initialize
41
+ @buffer = []
42
+ @env = {}
43
+ @headers = {}
44
+ @stage = 0
45
+ end
46
+
47
+ def complete?
48
+ @stage == 2
49
+ end
50
+
51
+ def error?
52
+ @stage == -1
53
+ end
54
+
55
+ def parse(chunk)
56
+ chunk.each_byte do |octet|
57
+ if octet == LF and @stage < 2
58
+ @buffer.pop if @buffer.last == CR
59
+ if @buffer.empty?
60
+ complete if @stage == 1
61
+ else
62
+ result = case @stage
63
+ when 0 then start_line(string_buffer)
64
+ when 1 then header_line(string_buffer)
65
+ end
66
+
67
+ if result
68
+ @stage = 1
69
+ else
70
+ error
71
+ end
72
+ end
73
+ @buffer = []
74
+ else
75
+ @buffer << octet if @stage >= 0
76
+ error if @stage < 2 and @buffer.size > MAX_LINE_LENGTH
77
+ end
78
+ end
79
+ @env['rack.input'] = StringIO.new(string_buffer)
80
+ end
81
+
82
+ private
83
+
84
+ def complete
85
+ @stage = 2
86
+ end
87
+
88
+ def error
89
+ @stage = -1
90
+ end
91
+
92
+ def header_line(line)
93
+ return false unless parsed = line.scan(HEADER_LINE).first
94
+
95
+ key = HTTP.normalize_header(parsed[0])
96
+ value = parsed[1].strip
97
+
98
+ if @headers.has_key?(key)
99
+ @headers[key] << ', ' << value
100
+ else
101
+ @headers[key] = value
102
+ end
103
+ true
104
+ end
105
+
106
+ def string_buffer
107
+ @buffer.pack('C*')
108
+ end
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,45 @@
1
+ module WebSocket
2
+ module HTTP
3
+
4
+ class Request
5
+ include Headers
6
+
7
+ REQUEST_LINE = /^(OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT) ([\x21-\x7e]+) (HTTP\/[0-9]+\.[0-9]+)$/
8
+ REQUEST_TARGET = /^(.*?)(\?(.*))?$/
9
+ RESERVED_HEADERS = %w[content-length content-type]
10
+
11
+ attr_reader :env
12
+
13
+ private
14
+
15
+ def start_line(line)
16
+ return false unless parsed = line.scan(REQUEST_LINE).first
17
+
18
+ target = parsed[1].scan(REQUEST_TARGET).first
19
+
20
+ @env = {
21
+ 'REQUEST_METHOD' => parsed[0],
22
+ 'SCRIPT_NAME' => '',
23
+ 'PATH_INFO' => target[0],
24
+ 'QUERY_STRING' => target[2] || ''
25
+ }
26
+ true
27
+ end
28
+
29
+ def complete
30
+ super
31
+ @headers.each do |name, value|
32
+ rack_name = name.upcase.gsub(/-/, '_')
33
+ rack_name = "HTTP_#{rack_name}" unless RESERVED_HEADERS.include?(name)
34
+ @env[rack_name] = value
35
+ end
36
+ if host = @env['HTTP_HOST']
37
+ uri = URI.parse("http://#{host}")
38
+ @env['SERVER_NAME'] = uri.host
39
+ @env['SERVER_PORT'] = uri.port.to_s
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ module WebSocket
2
+ module HTTP
3
+
4
+ class Response
5
+ include Headers
6
+
7
+ STATUS_LINE = /^(HTTP\/[0-9]+\.[0-9]+) ([0-9]{3}) ([\x20-\x7e]+)$/
8
+
9
+ attr_reader :code
10
+
11
+ def [](name)
12
+ @headers[HTTP.normalize_header(name)]
13
+ end
14
+
15
+ def body
16
+ @buffer.pack('C*')
17
+ end
18
+
19
+ private
20
+
21
+ def start_line(line)
22
+ return false unless parsed = line.scan(STATUS_LINE).first
23
+ @code = parsed[1].to_i
24
+ true
25
+ end
26
+ end
27
+
28
+ end
29
+ end