cramp 0.11 → 0.12
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/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
|