libwebsocket 0.0.4

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.
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