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.
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