libwebsocket 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # Protocol::WebSocket
2
+
3
+ A WebSocket message parser/constructor. It is not a server and is not meant to
4
+ be one. It can be used in any server, event loop etc.
5
+
6
+ ## Server handshake
7
+
8
+ h = LibWebSocket::Handshake::Server.new
9
+
10
+ # Parse client request
11
+ h.parse \<<EOF
12
+ GET /demo HTTP/1.1
13
+ Upgrade: WebSocket
14
+ Connection: Upgrade
15
+ Host: example.com
16
+ Origin: http://example.com
17
+ Sec-WebSocket-Key1: 18x 6]8vM;54 *(5: { U1]8 z [ 8
18
+ Sec-WebSocket-Key2: 1_ tx7X d < nw 334J702) 7]o}` 0
19
+
20
+ Tm[K T2u
21
+ EOF
22
+
23
+ h.error # Check if there were any errors
24
+ h.done? # Returns true
25
+
26
+ # Create response
27
+ h.to_s # HTTP/1.1 101 WebSocket Protocol Handshake
28
+ # Upgrade: WebSocket
29
+ # Connection: Upgrade
30
+ # Sec-WebSocket-Origin: http://example.com
31
+ # Sec-WebSocket-Location: ws://example.com/demo
32
+ #
33
+ # fQJ,fN/4F4!~K~MH
34
+
35
+ ## Client handshake
36
+
37
+ h = LibWebSocket::Handshake::Client.new(url => 'ws://example.com')
38
+
39
+ # Create request
40
+ h.to_s # GET /demo HTTP/1.1
41
+ # Upgrade: WebSocket
42
+ # Connection: Upgrade
43
+ # Host: example.com
44
+ # Origin: http://example.com
45
+ # Sec-WebSocket-Key1: 18x 6]8vM;54 *(5: { U1]8 z [ 8
46
+ # Sec-WebSocket-Key2: 1_ tx7X d < nw 334J702) 7]o}` 0
47
+ #
48
+ # Tm[K T2u
49
+
50
+ # Parse server response
51
+ h.parse \<<EOF
52
+ HTTP/1.1 101 WebSocket Protocol Handshake
53
+ Upgrade: WebSocket
54
+ Connection: Upgrade
55
+ Sec-WebSocket-Origin: http://example.com
56
+ Sec-WebSocket-Location: ws://example.com/demo
57
+
58
+ fQJ,fN/4F4!~K~MH
59
+ EOF
60
+
61
+ h.error # Check if there were any errors
62
+ h.done? # Returns true
63
+
64
+ ## Parsing and constructing frames
65
+
66
+ # Create frame
67
+ frame = LibWebSocket::Frame.new('123')
68
+ frame.to_s # \x00123\xff
69
+
70
+ # Parse frames
71
+ frame = LibWebSocket::Frame.new
72
+ frame.append("123\x00foo\xff56\x00bar\xff789")
73
+ frame.next # foo
74
+ frame.next # bar
75
+
76
+ ## Examples
77
+
78
+ For examples on how to use LibWebSocket with various event loops see
79
+ examples directory in the repository.
80
+
81
+ ## Copyright
82
+
83
+ Copyright (C) 2010, Bernard Potocki.
84
+
85
+ Based on protocol-websocket perl distribution by Viacheslav Tykhanovskyi.
86
+
87
+ This program is free software, you can redistribute it and/or modify it under
88
+ the MIT License.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ require 'rake/testtask'
3
+ require 'lib/libwebsocket'
4
+
5
+ task :default => :test
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << "test"
9
+ t.test_files = FileList['test/**/test_*.rb']
10
+ end
11
+
12
+ begin
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gemspec|
15
+ gemspec.name = "libwebsocket"
16
+ gemspec.version = LibWebSocket::VERSION
17
+ gemspec.summary = "Universal Ruby library to handle WebSocket protocol"
18
+ gemspec.description = "Universal Ruby library to handle WebSocket protocol"
19
+ gemspec.email = "bernard.potocki@imanel.org"
20
+ gemspec.homepage = "http://github.com/imanel/libwebsocket"
21
+ gemspec.authors = ["Bernard Potocki"]
22
+ gemspec.files.exclude ".gitignore"
23
+ end
24
+ rescue LoadError
25
+ puts "Jeweler not available. Install it with: gem install jeweler"
26
+ end
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'eventmachine'
5
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/libwebsocket')
6
+
7
+ module EchoServer
8
+ def receive_data(data)
9
+ @hs ||= LibWebSocket::Handshake::Server.new
10
+ @frame ||= LibWebSocket::Frame.new
11
+
12
+ if !@hs.done?
13
+ @hs.parse(data)
14
+
15
+ if @hs.done?
16
+ send_data(@hs.to_s)
17
+ end
18
+
19
+ return
20
+ end
21
+
22
+ @frame.append(data)
23
+
24
+ while message = @frame.next
25
+ send_data @frame.new(message).to_s
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ EventMachine::run do
32
+ host = '0.0.0.0'
33
+ port = 8080
34
+ EventMachine::start_server host, port, EchoServer
35
+ puts "Started EchoServer on #{host}:#{port}..."
36
+ end
@@ -0,0 +1,59 @@
1
+ require "socket"
2
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/libwebsocket')
3
+
4
+ class WebSocket
5
+
6
+ def initialize(url, params = {})
7
+ @hs ||= LibWebSocket::Handshake::Client.new(:url => url, :version => params[:version])
8
+ @frame ||= LibWebSocket::Frame.new
9
+
10
+ @socket = TCPSocket.new(@hs.url.host, @hs.url.port || 80)
11
+
12
+ @socket.write(@hs.to_s)
13
+ @socket.flush
14
+
15
+ loop do
16
+ data = @socket.getc
17
+ next if data.nil?
18
+
19
+ result = @hs.parse(data.chr)
20
+
21
+ raise @hs.error unless result
22
+
23
+ if @hs.done?
24
+ @handshaked = true
25
+ break
26
+ end
27
+ end
28
+ end
29
+
30
+ def send(data)
31
+ raise "no handshake!" unless @handshaked
32
+
33
+ data = @frame.new(data).to_s
34
+ @socket.write data
35
+ @socket.flush
36
+ end
37
+
38
+ def receive
39
+ raise "no handshake!" unless @handshaked
40
+
41
+ data = @socket.gets("\xff")
42
+ @frame.append(data)
43
+
44
+ messages = []
45
+ while message = @frame.next
46
+ messages << message
47
+ end
48
+ messages
49
+ end
50
+
51
+ def socket
52
+ @socket
53
+ end
54
+
55
+ def close
56
+ @socket.close
57
+ end
58
+
59
+ end
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'thin'
5
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/libwebsocket')
6
+
7
+ # This is required due to thin incompatibility with streamming of data
8
+ module ThinExtension
9
+ def self.included(thin_conn)
10
+ thin_conn.class_eval do
11
+ alias :pre_process_without_websocket :pre_process
12
+ alias :pre_process :pre_process_with_websocket
13
+
14
+ alias :receive_data_without_websocket :receive_data
15
+ alias :receive_data :receive_data_with_websocket
16
+ end
17
+ end
18
+
19
+ attr_accessor :websocket_client
20
+
21
+ def pre_process_with_websocket
22
+ @request.env['async.connection'] = self
23
+ pre_process_without_websocket
24
+ end
25
+ def receive_data_with_websocket(data)
26
+ if self.websocket_client
27
+ self.websocket_client.receive_data(data)
28
+ else
29
+ receive_data_without_websocket(data)
30
+ end
31
+ end
32
+ end
33
+
34
+ ::Thin::Connection.send(:include, ThinExtension)
35
+
36
+ class EchoServer
37
+ def call(env)
38
+ @hs ||= LibWebSocket::Handshake::Server.new
39
+ @connection = env['async.connection']
40
+
41
+ if !@hs.done?
42
+ @hs.parse(env)
43
+
44
+ if @hs.done?
45
+ @connection.websocket_client = self
46
+ resp = @hs.to_rack
47
+ return resp
48
+ end
49
+
50
+ return
51
+ end
52
+ end
53
+
54
+ def receive_data(data)
55
+ @frame ||= LibWebSocket::Frame.new
56
+
57
+ @frame.append(data)
58
+
59
+ while message = @frame.next
60
+ @connection.send_data @frame.new(message).to_s
61
+ end
62
+ end
63
+ end
64
+
65
+ Thin::Server.start('127.0.0.1', 8080) do
66
+ map '/' do
67
+ run proc{ |env| EchoServer.new.call(env) }
68
+ end
69
+ end
@@ -0,0 +1,17 @@
1
+ # Client/server WebSocket message and frame parser/constructor. This module does
2
+ # not provide a WebSocket server or client, but is made for using in http servers
3
+ # or clients to provide WebSocket support.
4
+ module LibWebSocket
5
+
6
+ VERSION = '0.0.4' # Version of LibWebSocket
7
+
8
+ autoload :Cookie, "#{File.dirname(__FILE__)}/libwebsocket/cookie"
9
+ autoload :Frame, "#{File.dirname(__FILE__)}/libwebsocket/frame"
10
+ autoload :Handshake, "#{File.dirname(__FILE__)}/libwebsocket/handshake"
11
+ autoload :Message, "#{File.dirname(__FILE__)}/libwebsocket/message"
12
+ autoload :Request, "#{File.dirname(__FILE__)}/libwebsocket/request"
13
+ autoload :Response, "#{File.dirname(__FILE__)}/libwebsocket/response"
14
+ autoload :Stateful, "#{File.dirname(__FILE__)}/libwebsocket/stateful"
15
+ autoload :URL, "#{File.dirname(__FILE__)}/libwebsocket/url"
16
+
17
+ end
@@ -0,0 +1,60 @@
1
+ module LibWebSocket
2
+ #A base class for LibWebSocket::Cookie::Request and LibWebSocket::Cookie::Response.
3
+ class Cookie
4
+
5
+ autoload :Request, "#{File.dirname(__FILE__)}/cookie/request"
6
+ autoload :Response, "#{File.dirname(__FILE__)}/cookie/response"
7
+
8
+ attr_accessor :pairs
9
+
10
+ TOKEN = /[^;,\s"]+/ # Cookie token
11
+ NAME = /[^;,\s"=]+/ # Cookie name
12
+ QUOTED_STRING = /"(?:\\"|[^"])+"/ # Cookie quoted value
13
+ VALUE = /(?:#{TOKEN}|#{QUOTED_STRING})/ # Cookie unquoted value
14
+
15
+ def initialize(hash = {})
16
+ hash.each do |k,v|
17
+ instance_variable_set("@#{k}",v)
18
+ end
19
+ end
20
+
21
+ # Parse cookie string to array
22
+ def parse(string)
23
+ self.pairs = []
24
+
25
+ return if string.nil? || string == ''
26
+
27
+ while string.slice!(/\s*(#{NAME})\s*(?:=\s*(#{VALUE}))?;?/)
28
+ attr, value = $1, $2
29
+ if !value.nil?
30
+ value.gsub!(/^"/, '')
31
+ value.gsub!(/"$/, '')
32
+ value.gsub!(/\\"/, '"')
33
+ end
34
+ self.pairs.push([attr, value])
35
+ end
36
+
37
+ return self
38
+ end
39
+
40
+ # Convert cookie array to string
41
+ def to_s
42
+ pairs = []
43
+
44
+ self.pairs.each do |pair|
45
+ string = ''
46
+ string += pair[0]
47
+
48
+ unless pair[1].nil?
49
+ string += '='
50
+ string += (!pair[1].match(/^#{VALUE}$/) ? "\"#{pair[1]}\"" : pair[1])
51
+ end
52
+
53
+ pairs.push(string)
54
+ end
55
+
56
+ return pairs.join("; ")
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,48 @@
1
+ module LibWebSocket
2
+ class Cookie
3
+ # Construct or parse a WebSocket request cookie.
4
+ class Request < Cookie
5
+
6
+ attr_accessor :name, :value, :version, :path, :domain
7
+
8
+ # Parse a WebSocket request cookie.
9
+ # @example
10
+ # cookie = LibWebSocket::Cookie::Request.new
11
+ # cookies = cookie.parse('$Version=1; foo="bar"; $Path=/; bar=baz; $Domain=.example.com')
12
+ def parse(string)
13
+ result = super
14
+ return unless result
15
+
16
+ cookies = []
17
+
18
+ pair = self.pairs.shift
19
+ version = pair[1]
20
+
21
+ cookie = nil
22
+ self.pairs.each do |pair|
23
+ next if pair[0].nil?
24
+
25
+ if pair[0].match(/^[^\$]/)
26
+ cookies.push(cookie) unless cookie.nil?
27
+
28
+ cookie = self.build_cookie( :name => pair[0], :value => pair[1], :version => version)
29
+ elsif pair[0] == '$Path'
30
+ cookie.path = pair[1]
31
+ elsif pair[0] == '$Domain'
32
+ cookie.domain = pair[1]
33
+ end
34
+ end
35
+
36
+ cookies.push(cookie) unless cookie.nil?
37
+
38
+ return cookies
39
+ end
40
+
41
+ protected
42
+
43
+ def build_cookie(hash)
44
+ self.class.new(hash)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,44 @@
1
+ module LibWebSocket
2
+ class Cookie
3
+ # Construct or parse a WebSocket response cookie.
4
+ class Response < Cookie
5
+
6
+ attr_accessor :name, :value, :comment, :comment_url, :discard, :max_age, :path, :portlist, :secure
7
+
8
+ # Construct a WebSocket response cookie.
9
+ # @example
10
+ # cookie = LibWebSocket::Cookie::Response.new(
11
+ # :name => 'foo',
12
+ # :value => 'bar',
13
+ # :discard => 1,
14
+ # :max_age => 0
15
+ # )
16
+ # cookie.to_s # foo=bar; Discard; Max-Age=0; Version=1
17
+ def to_s
18
+ pairs = []
19
+
20
+ pairs.push([self.name, self.value])
21
+
22
+ pairs.push ['Comment', self.comment] if self.comment
23
+ pairs.push ['CommentURL', self.comment_url] if self.comment_url
24
+ pairs.push ['Discard'] if self.discard
25
+ pairs.push ['Max-Age', self.max_age] if self.max_age
26
+ pairs.push ['Path', self.path] if self.path
27
+
28
+ if self.portlist
29
+ self.portlist = Array(self.portlist)
30
+ list = self.portlist.join(' ')
31
+ pairs.push ['Port', "\"#{list}\""]
32
+ end
33
+
34
+ pairs.push ['Secure'] if self.secure
35
+ pairs.push ['Version', '1']
36
+
37
+ self.pairs = pairs
38
+
39
+ super
40
+ end
41
+
42
+ end
43
+ end
44
+ end