cramp 0.11 → 0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/lib/cramp.rb +12 -1
  2. data/lib/cramp/abstract.rb +72 -0
  3. data/lib/cramp/action.rb +12 -0
  4. data/lib/cramp/body.rb +48 -0
  5. data/lib/cramp/callbacks.rb +48 -0
  6. data/lib/cramp/keep_connection_alive.rb +19 -0
  7. data/lib/cramp/periodic_timer.rb +49 -0
  8. data/lib/cramp/rendering.rb +11 -0
  9. data/lib/cramp/test_case.rb +54 -0
  10. data/lib/cramp/websocket.rb +84 -0
  11. data/lib/cramp/{controller/websocket → websocket}/rainbows_backend.rb +1 -1
  12. data/lib/cramp/{controller/websocket → websocket}/thin_backend.rb +1 -1
  13. metadata +37 -86
  14. data/lib/cramp/controller.rb +0 -15
  15. data/lib/cramp/controller/abstract.rb +0 -71
  16. data/lib/cramp/controller/action.rb +0 -14
  17. data/lib/cramp/controller/body.rb +0 -50
  18. data/lib/cramp/controller/callbacks.rb +0 -50
  19. data/lib/cramp/controller/keep_connection_alive.rb +0 -21
  20. data/lib/cramp/controller/periodic_timer.rb +0 -51
  21. data/lib/cramp/controller/rendering.rb +0 -13
  22. data/lib/cramp/controller/test_case.rb +0 -57
  23. data/lib/cramp/controller/websocket.rb +0 -63
  24. data/lib/cramp/model.rb +0 -40
  25. data/lib/cramp/model/arel_monkey_patches.rb +0 -66
  26. data/lib/cramp/model/attribute.rb +0 -104
  27. data/lib/cramp/model/attribute_methods.rb +0 -83
  28. data/lib/cramp/model/base.rb +0 -119
  29. data/lib/cramp/model/callbacks.rb +0 -41
  30. data/lib/cramp/model/column.rb +0 -72
  31. data/lib/cramp/model/emysql_ext.rb +0 -21
  32. data/lib/cramp/model/engine.rb +0 -75
  33. data/lib/cramp/model/engine/connection.rb +0 -32
  34. data/lib/cramp/model/evented_mysql.rb +0 -298
  35. data/lib/cramp/model/finders.rb +0 -27
  36. data/lib/cramp/model/quoting.rb +0 -104
  37. data/lib/cramp/model/relation.rb +0 -62
  38. 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.11'
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
@@ -0,0 +1,12 @@
1
+ module Cramp
2
+ class Action < Abstract
3
+
4
+ include PeriodicTimer
5
+ include KeepConnectionAlive
6
+
7
+ def render(body)
8
+ @body.call(body)
9
+ end
10
+
11
+ end
12
+ end
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,11 @@
1
+ module Cramp
2
+ module Rendering
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ def render(body)
7
+ @body.call(body)
8
+ end
9
+
10
+ end
11
+ 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
@@ -1,7 +1,7 @@
1
1
  require 'rainbows'
2
2
 
3
3
  class Rainbows::EventMachine::Client
4
- include Cramp::Controller::WebsocketExtension
4
+ include Cramp::WebsocketExtension
5
5
 
6
6
  def websocket_handshake!
7
7
  @state = :websocket
@@ -30,7 +30,7 @@ class Thin::Connection
30
30
  end
31
31
 
32
32
  class Thin::Request
33
- include Cramp::Controller::WebsocketExtension
33
+ include Cramp::WebsocketExtension
34
34
  end
35
35
 
36
36
  class Thin::Response