cramp 0.11 → 0.12
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cramp.rb +12 -1
- data/lib/cramp/abstract.rb +72 -0
- data/lib/cramp/action.rb +12 -0
- data/lib/cramp/body.rb +48 -0
- data/lib/cramp/callbacks.rb +48 -0
- data/lib/cramp/keep_connection_alive.rb +19 -0
- data/lib/cramp/periodic_timer.rb +49 -0
- data/lib/cramp/rendering.rb +11 -0
- data/lib/cramp/test_case.rb +54 -0
- data/lib/cramp/websocket.rb +84 -0
- data/lib/cramp/{controller/websocket → websocket}/rainbows_backend.rb +1 -1
- data/lib/cramp/{controller/websocket → websocket}/thin_backend.rb +1 -1
- metadata +37 -86
- data/lib/cramp/controller.rb +0 -15
- data/lib/cramp/controller/abstract.rb +0 -71
- data/lib/cramp/controller/action.rb +0 -14
- data/lib/cramp/controller/body.rb +0 -50
- data/lib/cramp/controller/callbacks.rb +0 -50
- data/lib/cramp/controller/keep_connection_alive.rb +0 -21
- data/lib/cramp/controller/periodic_timer.rb +0 -51
- data/lib/cramp/controller/rendering.rb +0 -13
- data/lib/cramp/controller/test_case.rb +0 -57
- data/lib/cramp/controller/websocket.rb +0 -63
- data/lib/cramp/model.rb +0 -40
- data/lib/cramp/model/arel_monkey_patches.rb +0 -66
- data/lib/cramp/model/attribute.rb +0 -104
- data/lib/cramp/model/attribute_methods.rb +0 -83
- data/lib/cramp/model/base.rb +0 -119
- data/lib/cramp/model/callbacks.rb +0 -41
- data/lib/cramp/model/column.rb +0 -72
- data/lib/cramp/model/emysql_ext.rb +0 -21
- data/lib/cramp/model/engine.rb +0 -75
- data/lib/cramp/model/engine/connection.rb +0 -32
- data/lib/cramp/model/evented_mysql.rb +0 -298
- data/lib/cramp/model/finders.rb +0 -27
- data/lib/cramp/model/quoting.rb +0 -104
- data/lib/cramp/model/relation.rb +0 -62
- data/lib/cramp/model/status.rb +0 -18
data/lib/cramp.rb
CHANGED
@@ -11,8 +11,19 @@ require 'active_support/concern'
|
|
11
11
|
require 'active_support/core_ext/hash/indifferent_access'
|
12
12
|
require 'active_support/buffered_logger'
|
13
13
|
|
14
|
+
require 'rack'
|
15
|
+
|
14
16
|
module Cramp
|
15
|
-
VERSION = '0.
|
17
|
+
VERSION = '0.12'
|
16
18
|
|
17
19
|
mattr_accessor :logger
|
20
|
+
|
21
|
+
autoload :Action, "cramp/action"
|
22
|
+
autoload :Websocket, "cramp/websocket"
|
23
|
+
autoload :Body, "cramp/body"
|
24
|
+
autoload :PeriodicTimer, "cramp/periodic_timer"
|
25
|
+
autoload :KeepConnectionAlive, "cramp/keep_connection_alive"
|
26
|
+
autoload :Abstract, "cramp/abstract"
|
27
|
+
autoload :Callbacks, "cramp/callbacks"
|
28
|
+
autoload :TestCase, "cramp/test_case"
|
18
29
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
2
|
+
|
3
|
+
module Cramp
|
4
|
+
class Abstract
|
5
|
+
|
6
|
+
include Callbacks
|
7
|
+
|
8
|
+
ASYNC_RESPONSE = [-1, {}, []].freeze
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def call(env)
|
12
|
+
controller = new(env).process
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(env)
|
17
|
+
@env = env
|
18
|
+
end
|
19
|
+
|
20
|
+
def process
|
21
|
+
EM.next_tick { before_start }
|
22
|
+
ASYNC_RESPONSE
|
23
|
+
end
|
24
|
+
|
25
|
+
def continue
|
26
|
+
init_async_body
|
27
|
+
|
28
|
+
status, headers = respond_with
|
29
|
+
send_initial_response(status, headers, @body)
|
30
|
+
|
31
|
+
EM.next_tick { start } if respond_to?(:start)
|
32
|
+
EM.next_tick { on_start }
|
33
|
+
end
|
34
|
+
|
35
|
+
def respond_with
|
36
|
+
[200, {'Content-Type' => 'text/html'}]
|
37
|
+
end
|
38
|
+
|
39
|
+
def init_async_body
|
40
|
+
@body = Body.new
|
41
|
+
|
42
|
+
if self.class.on_finish_callbacks.any?
|
43
|
+
@body.callback { on_finish }
|
44
|
+
@body.errback { on_finish }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def finish
|
49
|
+
@body.succeed
|
50
|
+
end
|
51
|
+
|
52
|
+
def send_initial_response(response_status, response_headers, response_body)
|
53
|
+
EM.next_tick { @env['async.callback'].call [response_status, response_headers, response_body] }
|
54
|
+
end
|
55
|
+
|
56
|
+
def halt(status, headers = {}, halt_body = '')
|
57
|
+
send_initial_response(status, headers, halt_body)
|
58
|
+
end
|
59
|
+
|
60
|
+
def request
|
61
|
+
@request ||= Rack::Request.new(@env)
|
62
|
+
end
|
63
|
+
|
64
|
+
def params
|
65
|
+
@params ||= request.params.update(route_params).symbolize_keys
|
66
|
+
end
|
67
|
+
|
68
|
+
def route_params
|
69
|
+
@env['router.params']||@env['usher.params']
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/cramp/action.rb
ADDED
data/lib/cramp/body.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Copyright 2008 James Tucker <raggi@rubyforge.org>.
|
2
|
+
|
3
|
+
module Cramp
|
4
|
+
class Body
|
5
|
+
include EventMachine::Deferrable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@queue = []
|
9
|
+
|
10
|
+
# Make sure to flush out the queue before closing the connection
|
11
|
+
callback { flush }
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(body)
|
15
|
+
@queue << body
|
16
|
+
schedule_dequeue
|
17
|
+
end
|
18
|
+
|
19
|
+
def each &blk
|
20
|
+
@body_callback = blk
|
21
|
+
schedule_dequeue
|
22
|
+
end
|
23
|
+
|
24
|
+
def closed?
|
25
|
+
@deferred_status != :unknown
|
26
|
+
end
|
27
|
+
|
28
|
+
def flush
|
29
|
+
return unless @body_callback
|
30
|
+
|
31
|
+
until @queue.empty?
|
32
|
+
Array(@queue.shift).each {|chunk| @body_callback.call(chunk) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def schedule_dequeue
|
37
|
+
return unless @body_callback
|
38
|
+
|
39
|
+
EventMachine.next_tick do
|
40
|
+
next unless body = @queue.shift
|
41
|
+
|
42
|
+
Array(body).each {|chunk| @body_callback.call(chunk) }
|
43
|
+
schedule_dequeue unless @queue.empty?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Cramp
|
2
|
+
module Callbacks
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_inheritable_accessor :before_start_callbacks, :on_finish_callbacks, :on_start_callback, :instance_reader => false
|
8
|
+
self.before_start_callbacks = []
|
9
|
+
self.on_finish_callbacks = []
|
10
|
+
self.on_start_callback = []
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def before_start(*methods)
|
15
|
+
self.before_start_callbacks += methods
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_finish(*methods)
|
19
|
+
self.on_finish_callbacks += methods
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_start(*methods)
|
23
|
+
self.on_start_callback += methods
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def before_start(n = 0)
|
28
|
+
if callback = self.class.before_start_callbacks[n]
|
29
|
+
EM.next_tick { send(callback) { before_start(n+1) } }
|
30
|
+
else
|
31
|
+
continue
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_start
|
36
|
+
self.class.on_start_callback.each do |callback|
|
37
|
+
EM.next_tick { send(callback) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def on_finish
|
42
|
+
self.class.on_finish_callbacks.each do |callback|
|
43
|
+
EM.next_tick { send(callback) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Cramp
|
2
|
+
module KeepConnectionAlive
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include PeriodicTimer
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def keep_connection_alive(options = {})
|
9
|
+
options = { :every => 15 }.merge(options)
|
10
|
+
periodic_timer :keep_connection_alive, options
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def keep_connection_alive
|
15
|
+
render " "
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Cramp
|
2
|
+
module PeriodicTimer
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_inheritable_accessor :periodic_timers, :instance_reader => false
|
8
|
+
self.periodic_timers ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def periodic_timer(method, options = {})
|
13
|
+
self.periodic_timers << [method, options]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(*)
|
18
|
+
super
|
19
|
+
@timers = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def continue
|
23
|
+
super
|
24
|
+
EM.next_tick { start_periodic_timers }
|
25
|
+
end
|
26
|
+
|
27
|
+
def init_async_body
|
28
|
+
super
|
29
|
+
|
30
|
+
if self.class.periodic_timers.any?
|
31
|
+
@body.callback { stop_periodic_timers }
|
32
|
+
@body.errback { stop_periodic_timers }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def start_periodic_timers
|
39
|
+
self.class.periodic_timers.each do |method, options|
|
40
|
+
@timers << EventMachine::PeriodicTimer.new(options[:every] || 1) { send(method) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop_periodic_timers
|
45
|
+
@timers.each {|t| t.cancel }
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/test_case'
|
3
|
+
|
4
|
+
module Cramp
|
5
|
+
class TestCase < ::ActiveSupport::TestCase
|
6
|
+
|
7
|
+
setup :create_request
|
8
|
+
|
9
|
+
def create_request
|
10
|
+
@request = Rack::MockRequest.new(app)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(path, options = {}, headers = {}, &block)
|
14
|
+
callback = options.delete(:callback) || block
|
15
|
+
headers = headers.merge('async.callback' => callback)
|
16
|
+
|
17
|
+
EM.run { @request.get(path, headers) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_body(path, options = {}, headers = {}, &block)
|
21
|
+
callback = options.delete(:callback) || block
|
22
|
+
response_callback = proc {|response| response[-1].each {|chunk| callback.call(chunk) } }
|
23
|
+
headers = headers.merge('async.callback' => response_callback)
|
24
|
+
|
25
|
+
EM.run { @request.get(path, headers) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_body_chunks(path, options = {}, headers = {}, &block)
|
29
|
+
callback = options.delete(:callback) || block
|
30
|
+
count = options.delete(:count) || 1
|
31
|
+
|
32
|
+
stopping = false
|
33
|
+
chunks = []
|
34
|
+
|
35
|
+
get_body(path, options, headers) do |body_chunk|
|
36
|
+
chunks << body_chunk unless stopping
|
37
|
+
|
38
|
+
if chunks.count >= count
|
39
|
+
stopping = true
|
40
|
+
callback.call(chunks) if callback
|
41
|
+
EM.next_tick { EM.stop }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def app
|
47
|
+
raise "Please define a method called 'app' returning an async Rack Application"
|
48
|
+
end
|
49
|
+
|
50
|
+
def stop
|
51
|
+
EM.stop
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Cramp
|
2
|
+
module WebsocketExtension
|
3
|
+
WEBSOCKET_RECEIVE_CALLBACK = 'websocket.receive_callback'.freeze
|
4
|
+
|
5
|
+
def websocket?
|
6
|
+
@env['HTTP_CONNECTION'] == 'Upgrade' && @env['HTTP_UPGRADE'] == 'WebSocket'
|
7
|
+
end
|
8
|
+
|
9
|
+
def websocket_upgrade_data
|
10
|
+
location = "ws://#{@env['HTTP_HOST']}#{@env['REQUEST_PATH']}"
|
11
|
+
challenge = solve_challange(
|
12
|
+
@env['HTTP_SEC_WEBSOCKET_KEY1'],
|
13
|
+
@env['HTTP_SEC_WEBSOCKET_KEY2'],
|
14
|
+
@env['rack.input'].read
|
15
|
+
)
|
16
|
+
|
17
|
+
upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
18
|
+
upgrade << "Upgrade: WebSocket\r\n"
|
19
|
+
upgrade << "Connection: Upgrade\r\n"
|
20
|
+
upgrade << "Sec-WebSocket-Origin: #{@env['HTTP_ORIGIN']}\r\n"
|
21
|
+
upgrade << "Sec-WebSocket-Location: #{location}\r\n\r\n"
|
22
|
+
upgrade << challenge
|
23
|
+
|
24
|
+
upgrade
|
25
|
+
end
|
26
|
+
|
27
|
+
def solve_challange(first, second, third)
|
28
|
+
# Refer to 5.2 4-9 of the draft 76
|
29
|
+
sum =
|
30
|
+
[extract_nums(first) / count_spaces(first)].pack("N*") +
|
31
|
+
[extract_nums(second) / count_spaces(second)].pack("N*") +
|
32
|
+
third
|
33
|
+
Digest::MD5.digest(sum)
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_nums(string)
|
37
|
+
string.scan(/[0-9]/).join.to_i
|
38
|
+
end
|
39
|
+
|
40
|
+
def count_spaces(string)
|
41
|
+
string.scan(/ /).size
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Websocket < Abstract
|
46
|
+
include PeriodicTimer
|
47
|
+
|
48
|
+
# TODO : Websockets shouldn't need this in an ideal world
|
49
|
+
include KeepConnectionAlive
|
50
|
+
|
51
|
+
class_inheritable_accessor :on_data_callbacks, :instance_reader => false
|
52
|
+
self.on_data_callbacks = []
|
53
|
+
|
54
|
+
class << self
|
55
|
+
def backend=(backend)
|
56
|
+
raise "Websocket backend #{backend} is unknown" unless [:thin, :rainbows].include?(backend.to_sym)
|
57
|
+
require "cramp/websocket/#{backend}_backend.rb"
|
58
|
+
end
|
59
|
+
|
60
|
+
def on_data(*methods)
|
61
|
+
self.on_data_callbacks += methods
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def process
|
66
|
+
@env['websocket.receive_callback'] = method(:_on_data_receive)
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
def render(body)
|
71
|
+
@body.call("\x00#{body}\xff")
|
72
|
+
end
|
73
|
+
|
74
|
+
def _on_data_receive(data)
|
75
|
+
data = data.split(/\000([^\377]*)\377/).select{|d| !d.empty? }.collect{|d| d.gsub(/^\x00|\xff$/, '') }
|
76
|
+
self.class.on_data_callbacks.each do |callback|
|
77
|
+
data.each do |message|
|
78
|
+
EM.next_tick { send(callback, message) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|