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.
@@ -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
@@ -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
@@ -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
@@ -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,19 @@
1
+ module Funnel
2
+ module Routing
3
+
4
+ class Route
5
+
6
+
7
+ def initialize path, opts
8
+ @opts = opts || {}
9
+ @opts[:path] = path
10
+ end
11
+
12
+ def handler
13
+ @opts[:handler]
14
+ end
15
+
16
+
17
+ end
18
+ end
19
+ 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))
@@ -0,0 +1,16 @@
1
+ class Configuration
2
+
3
+ @@map = {
4
+ :host => "0.0.0.0",
5
+ :port => 4000
6
+ }
7
+
8
+ def self.get key
9
+ @@map[key.to_sym]
10
+ end
11
+
12
+ def self.put key, val
13
+ @@map[key.to_sym] = val
14
+ end
15
+
16
+ end
@@ -0,0 +1,3 @@
1
+ Funnel::Routing::Routes.draw do |map|
2
+ map.default :handler => Funnel::Servers::DummyServer
3
+ end
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path('../../config/boot', __FILE__)
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
+