funnel 0.1.0
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.markdown +56 -0
- data/bin/funnel +33 -0
- data/funnel.gemspec +37 -0
- data/lib/funnel.rb +19 -0
- data/lib/funnel/logger.rb +24 -0
- data/lib/funnel/routing/route.rb +19 -0
- data/lib/funnel/routing/router.rb +54 -0
- data/lib/funnel/routing/routes.rb +40 -0
- data/lib/funnel/server.rb +25 -0
- data/lib/funnel/servers/dummy_server.rb +20 -0
- data/lib/funnel/web_socket/connection.rb +71 -0
- data/lib/funnel/web_socket/frame.rb +22 -0
- data/lib/funnel/web_socket/headers.rb +48 -0
- data/templates/application/config/boot.rb +23 -0
- data/templates/application/config/config.rb +16 -0
- data/templates/application/config/routes.rb +3 -0
- data/templates/application/script/run +2 -0
- metadata +79 -0
data/README.markdown
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#funnel - a realtime resource-centric application framework
|
2
|
+
|
3
|
+
##Installation
|
4
|
+
|
5
|
+
gem install funnel -s http://gemcutter.org
|
6
|
+
|
7
|
+
##Creating a project
|
8
|
+
|
9
|
+
funnel myapp
|
10
|
+
|
11
|
+
##Project Hierarchy
|
12
|
+
|
13
|
+
/config
|
14
|
+
boot.rb
|
15
|
+
routes.rb
|
16
|
+
config.rb
|
17
|
+
/handlers -> your real time resource handlers
|
18
|
+
time.rb
|
19
|
+
game.rb
|
20
|
+
xmpp.rb
|
21
|
+
amqp.rb
|
22
|
+
/script
|
23
|
+
server
|
24
|
+
|
25
|
+
|
26
|
+
## your first "handler"
|
27
|
+
|
28
|
+
echo_handler.rb
|
29
|
+
|
30
|
+
class EchoHandler < Funnel::WebSocket::Connection
|
31
|
+
|
32
|
+
#called when the connection is ready to send
|
33
|
+
#and receive data
|
34
|
+
def on_ready
|
35
|
+
end
|
36
|
+
|
37
|
+
#called on disconnect
|
38
|
+
def on_disconnect
|
39
|
+
end
|
40
|
+
|
41
|
+
#right back at you
|
42
|
+
def on_data msg
|
43
|
+
send_message msg
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
set up the route in config/routes.rb
|
49
|
+
|
50
|
+
map.connect "/echo" => EchoHandler
|
51
|
+
|
52
|
+
##Dependencies
|
53
|
+
- eventmachine http://github.com/eventmachine/eventmachine
|
54
|
+
|
55
|
+
|
56
|
+
# tired... more info coming soon
|
data/bin/funnel
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'rubygems'
|
6
|
+
require 'funnel'
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
|
12
|
+
opts.banner = "Usage: funnel [options] application_name"
|
13
|
+
|
14
|
+
options[:verbose] = false
|
15
|
+
|
16
|
+
opts.on( '-v', '--verbose', 'Output more information' ) do
|
17
|
+
options[:verbose] = true
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
21
|
+
puts opts
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
if ARGV.length < 1
|
26
|
+
puts opts
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
|
30
|
+
end.parse!
|
31
|
+
|
32
|
+
app = Funnel::Generators::Application.new
|
33
|
+
app.generate "#{Dir.getwd}/#{ARGV[0]}", options
|
data/funnel.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
spec = Gem::Specification.new do |s|
|
2
|
+
s.name = 'funnel'
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.date = '2009-12-30'
|
5
|
+
s.summary = 'A realtime resource-centric application framework'
|
6
|
+
s.email = 'dan.simpson@gmail.com'
|
7
|
+
s.homepage = 'http://github.com/dansimpson/funnel'
|
8
|
+
s.description = 'A realtime resource-centric application framework'
|
9
|
+
s.has_rdoc = false
|
10
|
+
|
11
|
+
s.bindir = 'bin'
|
12
|
+
s.executables = ['funnel']
|
13
|
+
|
14
|
+
s.authors = ['Dan Simpson']
|
15
|
+
s.add_dependency('eventmachine', '>= 0.12.4')
|
16
|
+
|
17
|
+
|
18
|
+
s.files = [
|
19
|
+
'README.markdown',
|
20
|
+
'funnel.gemspec',
|
21
|
+
'lib/funnel.rb',
|
22
|
+
'lib/funnel/logger.rb',
|
23
|
+
'lib/funnel/web_socket/frame.rb',
|
24
|
+
'lib/funnel/web_socket/headers.rb',
|
25
|
+
'lib/funnel/web_socket/connection.rb',
|
26
|
+
'lib/funnel/routing/route.rb',
|
27
|
+
'lib/funnel/routing/routes.rb',
|
28
|
+
'lib/funnel/routing/router.rb',
|
29
|
+
'lib/funnel/server.rb',
|
30
|
+
'lib/funnel/servers/dummy_server.rb',
|
31
|
+
'templates/application/config/boot.rb',
|
32
|
+
'templates/application/config/config.rb',
|
33
|
+
'templates/application/config/routes.rb',
|
34
|
+
'templates/application/script/run',
|
35
|
+
'templates/application/handlers'
|
36
|
+
]
|
37
|
+
end
|
data/lib/funnel.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
module Funnel
|
6
|
+
module WebSocket
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'funnel/logger.rb'
|
11
|
+
require 'funnel/generators/application.rb'
|
12
|
+
require 'funnel/web_socket/frame.rb'
|
13
|
+
require 'funnel/web_socket/headers.rb'
|
14
|
+
require 'funnel/web_socket/connection.rb'
|
15
|
+
require 'funnel/routing/route.rb'
|
16
|
+
require 'funnel/routing/routes.rb'
|
17
|
+
require 'funnel/routing/router.rb'
|
18
|
+
require 'funnel/server.rb'
|
19
|
+
require 'funnel/servers/dummy_server.rb'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Logger
|
2
|
+
|
3
|
+
@@threshhold = 3
|
4
|
+
|
5
|
+
def self.info msg
|
6
|
+
log msg if @@threshhold <= 3
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.debug msf
|
10
|
+
log msg if @@threshhold <= 1
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.warning msg
|
14
|
+
log msg if @@threshhold <= 2
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def self.log msg
|
20
|
+
puts "#{Time.now} - #{msg}"
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Funnel
|
2
|
+
module Routing
|
3
|
+
|
4
|
+
class Router < WebSocket::Connection
|
5
|
+
|
6
|
+
# parse headers, validate headers, accept
|
7
|
+
# connection, and change the default handler
|
8
|
+
def receive_data data
|
9
|
+
|
10
|
+
@headers = WebSocket::Headers.parse(data)
|
11
|
+
|
12
|
+
if valid_connection? && valid_route?
|
13
|
+
accept_connection
|
14
|
+
defer_connection
|
15
|
+
else
|
16
|
+
close_connection
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
# is this a websocket connection?
|
22
|
+
def valid_connection?
|
23
|
+
@headers[:connection] =~ /upgrade/i && @headers[:upgrade] =~ /websocket/i
|
24
|
+
end
|
25
|
+
|
26
|
+
# TODO: add WebSocket-Protocol
|
27
|
+
def accept_connection
|
28
|
+
response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
29
|
+
response << "Upgrade: WebSocket\r\n"
|
30
|
+
response << "Connection: Upgrade\r\n"
|
31
|
+
response << "WebSocket-Origin: #{origin}\r\n"
|
32
|
+
response << "WebSocket-Location: ws://#{host}#{path}\r\n\r\n"
|
33
|
+
|
34
|
+
send_data response
|
35
|
+
end
|
36
|
+
|
37
|
+
# change the handler from this instance, to a new instance
|
38
|
+
# of the type defined by the routing map
|
39
|
+
def defer_connection
|
40
|
+
puts "got this far"
|
41
|
+
EM.instance_variable_get("@conns")[@signature] = Routes.get_handler(path).send(:new, @signature)
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid_route?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def requested_protocol
|
49
|
+
@header[:websocket_protocol] || :default
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Funnel
|
2
|
+
module Routing
|
3
|
+
|
4
|
+
class Routes
|
5
|
+
|
6
|
+
@@instance = nil
|
7
|
+
|
8
|
+
def self.get_handler path
|
9
|
+
@@instance.get_handler(path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.draw
|
13
|
+
@@instance = Routes.new unless @@instance
|
14
|
+
yield @@instance
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@routes = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def connect path, opts
|
23
|
+
@routes[path] = Route.new(path, opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
def default opts
|
27
|
+
@routes[:default] = Route.new(:default, opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_default
|
31
|
+
@routes.key?(:default) ? @routes[:default].handler : Funnel::Servers::DummyServer
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_handler path
|
35
|
+
@routes.key?(path) ? @routes[path].handler : @routes[:default].handler
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Funnel
|
2
|
+
class Server
|
3
|
+
|
4
|
+
def self.start host, port
|
5
|
+
|
6
|
+
EM.epoll if EM.epoll?
|
7
|
+
EM.kqueue if EM.kqueue?
|
8
|
+
|
9
|
+
EM.run do
|
10
|
+
|
11
|
+
trap("TERM") { stop }
|
12
|
+
trap("INT") { stop }
|
13
|
+
|
14
|
+
EM.start_server(host, port, Routing::Router)
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.stop
|
21
|
+
EM.stop
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Funnel
|
2
|
+
module Servers
|
3
|
+
class DummyServer < Funnel::WebSocket::Connection
|
4
|
+
|
5
|
+
def on_ready
|
6
|
+
Logger.info("DummyServer - on_ready")
|
7
|
+
end
|
8
|
+
|
9
|
+
def on_data data
|
10
|
+
Logger.info("DummyServer - on_data")
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_disconnect
|
14
|
+
Logger.info("DummyServer - on_disconnect")
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Funnel
|
2
|
+
module WebSocket
|
3
|
+
|
4
|
+
class Connection < EM::Connection
|
5
|
+
|
6
|
+
attr_accessor :headers
|
7
|
+
|
8
|
+
###
|
9
|
+
# helper methods
|
10
|
+
###
|
11
|
+
|
12
|
+
def cookies
|
13
|
+
@headers[:cookies]
|
14
|
+
end
|
15
|
+
|
16
|
+
def host
|
17
|
+
@headers[:host]
|
18
|
+
end
|
19
|
+
|
20
|
+
def origin
|
21
|
+
@headers[:origin]
|
22
|
+
end
|
23
|
+
|
24
|
+
def path
|
25
|
+
@headers[:path]
|
26
|
+
end
|
27
|
+
|
28
|
+
def protocol
|
29
|
+
@headers[:websocket_protocol]
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
#override this
|
35
|
+
def on_ready
|
36
|
+
puts "Connect"
|
37
|
+
end
|
38
|
+
|
39
|
+
#override this
|
40
|
+
def on_disconnect
|
41
|
+
puts "Disconnect"
|
42
|
+
end
|
43
|
+
|
44
|
+
#override this
|
45
|
+
def on_data data
|
46
|
+
puts "Data"
|
47
|
+
end
|
48
|
+
|
49
|
+
#called from EM
|
50
|
+
def unbind
|
51
|
+
on_disconnect
|
52
|
+
end
|
53
|
+
|
54
|
+
#called from EM
|
55
|
+
def post_init
|
56
|
+
@headers = {}
|
57
|
+
on_ready
|
58
|
+
end
|
59
|
+
|
60
|
+
#called from EM
|
61
|
+
def receive_data data
|
62
|
+
on_data Frame.decode(data)
|
63
|
+
end
|
64
|
+
|
65
|
+
def send_message msg
|
66
|
+
send_data Frame.encode(msg)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Funnel
|
2
|
+
module WebSocket
|
3
|
+
|
4
|
+
class Frame
|
5
|
+
|
6
|
+
# Frames need to start with 0x00-0x7f byte and end with
|
7
|
+
# an 0xFF byte. Per spec, we can also set the first
|
8
|
+
# byte to a value betweent 0x80 and 0xFF, followed by
|
9
|
+
# a leading length indicator. No support yet.
|
10
|
+
def self.encode data
|
11
|
+
"\x00#{data}\xff"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Strip leading and trailing bytes
|
15
|
+
def self.decode data
|
16
|
+
data.gsub(/^(\x00)|(\xff)$/, "")
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Funnel
|
2
|
+
module WebSocket
|
3
|
+
|
4
|
+
class Headers
|
5
|
+
|
6
|
+
PATH_PATTERN = /^GET (\/[^\s]*) HTTP\/1\.1$/
|
7
|
+
HEADER_PATTERN = /^([^:]+):\s*([^$]+)/
|
8
|
+
|
9
|
+
# Parse http style headers into a ruby hash
|
10
|
+
def self.parse data
|
11
|
+
lines = data.split("\r\n")
|
12
|
+
line = lines.shift
|
13
|
+
|
14
|
+
headers = {}
|
15
|
+
|
16
|
+
if line =~ PATH_PATTERN
|
17
|
+
headers[:path] = PATH_PATTERN.match(line)[1]
|
18
|
+
else
|
19
|
+
throw "Unrecognized Header!"
|
20
|
+
end
|
21
|
+
|
22
|
+
lines.each do |line|
|
23
|
+
kvp = HEADER_PATTERN.match(line)
|
24
|
+
headers[kvp[1].strip.downcase.to_sym] = kvp[2].strip
|
25
|
+
end
|
26
|
+
|
27
|
+
headers
|
28
|
+
end
|
29
|
+
|
30
|
+
# encode ruby hash into HTTP style headers
|
31
|
+
def self.encode data
|
32
|
+
result = ""
|
33
|
+
|
34
|
+
data.each_pair do |k,v|
|
35
|
+
result << k.to_s
|
36
|
+
result << ": "
|
37
|
+
result << v.to_s
|
38
|
+
result << "\r\n"
|
39
|
+
end
|
40
|
+
|
41
|
+
result << "\r\n"
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
FUNNEL_ROOT = File.expand_path('../', __FILE__)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'funnel'
|
6
|
+
|
7
|
+
#local includes
|
8
|
+
require 'config'
|
9
|
+
require 'funnel'
|
10
|
+
|
11
|
+
#load servers defined by user
|
12
|
+
handlers = Dir.entries(FUNNEL_ROOT + "/handlers").select { |n| not n =~ /^\.+$/ }
|
13
|
+
if handlers
|
14
|
+
handlers.each do |h|
|
15
|
+
require FUNNEL_ROOT + "/handlers/#{h}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#require routes and map them
|
20
|
+
require 'routes'
|
21
|
+
|
22
|
+
#start the server
|
23
|
+
Funnel::Server.start(Configuration.get(:host), Configuration.get(:port))
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: funnel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Simpson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-30 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: eventmachine
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.12.4
|
24
|
+
version:
|
25
|
+
description: A realtime resource-centric application framework
|
26
|
+
email: dan.simpson@gmail.com
|
27
|
+
executables:
|
28
|
+
- funnel
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- README.markdown
|
35
|
+
- funnel.gemspec
|
36
|
+
- lib/funnel.rb
|
37
|
+
- lib/funnel/logger.rb
|
38
|
+
- lib/funnel/web_socket/frame.rb
|
39
|
+
- lib/funnel/web_socket/headers.rb
|
40
|
+
- lib/funnel/web_socket/connection.rb
|
41
|
+
- lib/funnel/routing/route.rb
|
42
|
+
- lib/funnel/routing/routes.rb
|
43
|
+
- lib/funnel/routing/router.rb
|
44
|
+
- lib/funnel/server.rb
|
45
|
+
- lib/funnel/servers/dummy_server.rb
|
46
|
+
- templates/application/config/boot.rb
|
47
|
+
- templates/application/config/config.rb
|
48
|
+
- templates/application/config/routes.rb
|
49
|
+
- templates/application/script/run
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: http://github.com/dansimpson/funnel
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.3.5
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: A realtime resource-centric application framework
|
78
|
+
test_files: []
|
79
|
+
|