websocket-driver 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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