webmate 0.1.4 → 0.1.5
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/.gitignore +2 -1
- data/README.md +14 -1
- data/lib/webmate.rb +15 -10
- data/lib/webmate/application.rb +15 -100
- data/lib/webmate/config.rb +7 -4
- data/lib/webmate/presenters/base_presenter.rb +0 -58
- data/lib/webmate/responders/abstract.rb +2 -2
- data/lib/webmate/responders/base.rb +0 -9
- data/lib/webmate/responders/response.rb +1 -19
- data/lib/webmate/routes/base.rb +99 -0
- data/lib/webmate/{route_helpers/routes_collection.rb → routes/collection.rb} +44 -28
- data/lib/webmate/routes/handler.rb +90 -0
- data/lib/webmate/socket.io/packets/base.rb +8 -20
- data/lib/webmate/support/json.rb +15 -0
- data/lib/webmate/version.rb +1 -1
- data/lib/webmate/views/scope.rb +0 -1
- data/lib/webmate/websockets.rb +6 -4
- data/spec/lib/application_spec.rb +20 -0
- data/spec/lib/routes/base_spec.rb +46 -0
- data/spec/lib/routes/collection_spec.rb +75 -0
- data/spec/spec_helper.rb +1 -1
- metadata +11 -8
- data/lib/webmate/route_helpers/route.rb +0 -91
- data/lib/webmate/support/em_mongoid.rb +0 -53
- data/spec/lib/route_helpers/route_spec.rb +0 -41
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
# Webmate
|
2
2
|
|
3
|
-
|
3
|
+
Webmate is a fully asynchronous real-time web application framework in Ruby. It is built using EventMachine and WebSockets. Webmate primarily designed for providing full-duplex bi-directional communication.
|
4
|
+
|
5
|
+
## Why Webmate?
|
6
|
+
|
7
|
+
Webmate provides high-level api to create applications based on Websocket. Instead of separating code to http/websocket, you write one code, which may work using http or websocket (or both).
|
8
|
+
|
9
|
+
Sample:
|
10
|
+
|
11
|
+
Webmate::Application.define_routes do
|
12
|
+
resources :tasks, transport: [:http, :WS]
|
13
|
+
end
|
14
|
+
|
15
|
+
This simple route will allow you create, update, delete tasks using ONE websocket connection.
|
16
|
+
|
4
17
|
|
5
18
|
## Quick start
|
6
19
|
|
data/lib/webmate.rb
CHANGED
@@ -15,6 +15,7 @@ require 'webmate/application'
|
|
15
15
|
require 'webmate/config'
|
16
16
|
require 'webmate/websockets'
|
17
17
|
require 'webmate/logger'
|
18
|
+
require 'webmate/support/json'
|
18
19
|
|
19
20
|
require 'bundler'
|
20
21
|
Bundler.setup
|
@@ -30,8 +31,9 @@ require 'webmate/responders/response'
|
|
30
31
|
require 'webmate/responders/templates'
|
31
32
|
require 'webmate/observers/base'
|
32
33
|
require 'webmate/decorators/base'
|
33
|
-
require 'webmate/
|
34
|
-
require 'webmate/
|
34
|
+
require 'webmate/routes/collection'
|
35
|
+
require 'webmate/routes/base'
|
36
|
+
require 'webmate/routes/handler'
|
35
37
|
|
36
38
|
Bundler.require(:default, Webmate.env.to_sym)
|
37
39
|
|
@@ -52,10 +54,13 @@ require 'webmate/presenters/base'
|
|
52
54
|
require 'webmate/presenters/scoped'
|
53
55
|
require 'webmate/presenters/base_presenter'
|
54
56
|
|
55
|
-
#
|
56
|
-
|
57
|
-
|
57
|
+
# require priority initialization files
|
58
|
+
configatron.app.priotity_initialize_files.each do |path|
|
59
|
+
file = "#{Webmate.root}/#{path}"
|
60
|
+
require file if FileTest.exists?(file)
|
61
|
+
end
|
58
62
|
|
63
|
+
# auto-load files
|
59
64
|
configatron.app.load_paths.each do |path|
|
60
65
|
Dir[ File.join( Webmate.root, path, '**', '*.rb') ].each do |file|
|
61
66
|
class_name = File.basename(file, '.rb')
|
@@ -65,17 +70,17 @@ configatron.app.load_paths.each do |path|
|
|
65
70
|
end
|
66
71
|
end
|
67
72
|
|
68
|
-
#
|
69
|
-
|
70
|
-
|
73
|
+
# require initialization files
|
74
|
+
configatron.app.initialize_paths.each do |path|
|
75
|
+
Dir[ File.join( Webmate.root, path, '**', '*.rb')].each do |file|
|
76
|
+
require file
|
77
|
+
end
|
71
78
|
end
|
72
79
|
|
73
80
|
class Webmate::Application
|
74
|
-
#register Webmate::RouteHelpers::Channels
|
75
81
|
register Sinatra::Reloader
|
76
82
|
register SinatraMore::MarkupPlugin
|
77
83
|
|
78
|
-
#helpers Webmate::Views::Helpers
|
79
84
|
helpers Sinatra::Cookies
|
80
85
|
helpers Webmate::Sprockets::Helpers
|
81
86
|
|
data/lib/webmate/application.rb
CHANGED
@@ -1,102 +1,25 @@
|
|
1
1
|
module Webmate
|
2
2
|
class Application < Sinatra::Base
|
3
3
|
# override sinatra's method
|
4
|
-
def route!(base = settings, pass_block = nil)
|
5
|
-
|
6
|
-
|
7
|
-
route_info = base.routes.match(@request.request_method, transport, @request.path)
|
4
|
+
def route!(base = settings, pass_block = nil)
|
5
|
+
route_info = find_route(base.routes, @request)
|
8
6
|
|
9
7
|
# no route case - use default sinatra's processors
|
10
|
-
if
|
8
|
+
if route_info
|
9
|
+
handler = Webmate::Routes::Handler.new(base, @request)
|
10
|
+
handler.handle(route_info)
|
11
|
+
else
|
11
12
|
route_eval(&pass_block) if pass_block
|
12
13
|
route_missing
|
13
14
|
end
|
14
|
-
|
15
|
-
if @request.websocket?
|
16
|
-
unless authorized_to_open_connection?(route_info[:params][:scope])
|
17
|
-
return [401, {}, []]
|
18
|
-
end
|
19
|
-
|
20
|
-
session_id = route_info[:params][:session_id].inspect
|
21
|
-
Webmate::Websockets.subscribe(session_id, @request) do |message|
|
22
|
-
if route_info = base.routes.match(message['method'], 'WS', message.path)
|
23
|
-
request_info = {
|
24
|
-
path: message.path,
|
25
|
-
metadata: message.metadata || {},
|
26
|
-
action: route_info[:action],
|
27
|
-
params: message.params.merge(route_info[:params]),
|
28
|
-
request: @request
|
29
|
-
}
|
30
|
-
# here we should create subscriber who can live
|
31
|
-
# between messages.. but not between requests.
|
32
|
-
response = route_info[:responder].new(request_info).respond
|
33
|
-
|
34
|
-
# result of block will be sent back to user
|
35
|
-
response
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# this response not pass to user - so we keep connection alive.
|
40
|
-
# passing other response will close connection and socket
|
41
|
-
non_pass_response = [-1, {}, []]
|
42
|
-
return non_pass_response
|
43
|
-
|
44
|
-
else # HTTP
|
45
|
-
# this should return correct Rack response..
|
46
|
-
request_info = params_for_responder(route_info)
|
47
|
-
response = route_info[:responder].new(request_info).respond
|
48
|
-
|
49
|
-
return response.rack_format
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# this method prepare data for responder
|
54
|
-
# {
|
55
|
-
# path: '/',
|
56
|
-
# metadata: {},
|
57
|
-
# action: 'index',
|
58
|
-
# params: { test: true }
|
59
|
-
# }
|
60
|
-
def params_for_responder(route_info)
|
61
|
-
# create unified request info
|
62
|
-
# request_info = { path: '/', metadata: {}, action: 'index', params: { test: true } }
|
63
|
-
request_params = parsed_request_params
|
64
|
-
metadata = request_params.delete(:metadata)
|
65
|
-
{
|
66
|
-
path: @request.path,
|
67
|
-
metadata: metadata || {},
|
68
|
-
action: route_info[:action],
|
69
|
-
params: request_params.merge(route_info[:params]),
|
70
|
-
request: @request
|
71
|
-
}
|
72
|
-
end
|
73
|
-
|
74
|
-
# @request.params working only for get params
|
75
|
-
# and params in url line ?key=value
|
76
|
-
def parsed_request_params
|
77
|
-
request_params = HashWithIndifferentAccess.new
|
78
|
-
request_params.merge!(@request.params || {})
|
79
|
-
|
80
|
-
# read post or put params. this will erase params
|
81
|
-
# {code: 123, mode: 123}
|
82
|
-
# "code=123&mode=123"
|
83
|
-
request_body = @request.body.read
|
84
|
-
if request_body.present?
|
85
|
-
body_params = begin
|
86
|
-
JSON.parse(request_body) # {code: 123, mode: 123}
|
87
|
-
rescue JSON::ParserError
|
88
|
-
Rack::Utils.parse_nested_query(request_body) # "code=123&mode=123"
|
89
|
-
end
|
90
|
-
else
|
91
|
-
body_params = {}
|
92
|
-
end
|
93
|
-
|
94
|
-
request_params.merge(body_params)
|
95
15
|
end
|
96
16
|
|
97
|
-
#
|
98
|
-
|
99
|
-
|
17
|
+
# Find matched route by routes collection and request
|
18
|
+
# @param Webmate::Routes::Collection routes
|
19
|
+
# @param Sinatra::Request request
|
20
|
+
def find_route(routes, request)
|
21
|
+
transport = request.websocket? ? 'WS' : 'HTTP'
|
22
|
+
routes.match(request.request_method, transport, request.path)
|
100
23
|
end
|
101
24
|
|
102
25
|
class << self
|
@@ -108,11 +31,11 @@ module Webmate
|
|
108
31
|
|
109
32
|
def define_routes(&block)
|
110
33
|
settings = Webmate::Application
|
111
|
-
unless settings.routes.is_a?(
|
112
|
-
routes =
|
34
|
+
unless settings.routes.is_a?(Routes::Collection)
|
35
|
+
routes = Routes::Collection.new()
|
113
36
|
settings.set(:routes, routes)
|
114
37
|
end
|
115
|
-
settings.routes.
|
38
|
+
settings.routes.define(&block)
|
116
39
|
|
117
40
|
routes
|
118
41
|
end
|
@@ -121,14 +44,6 @@ module Webmate
|
|
121
44
|
channel_name = "some-unique-key-for-app-#{user_id}"
|
122
45
|
end
|
123
46
|
|
124
|
-
def dump(obj)
|
125
|
-
Yajl::Encoder.encode(obj)
|
126
|
-
end
|
127
|
-
|
128
|
-
def restore(str)
|
129
|
-
Yajl::Parser.parse(str)
|
130
|
-
end
|
131
|
-
|
132
47
|
def load_tasks
|
133
48
|
file_path = Pathname.new(__FILE__)
|
134
49
|
load File.join(file_path.dirname, "../../tasks/routes.rake")
|
data/lib/webmate/config.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
Webmate::Application.configure do |config|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
]
|
2
|
+
# these files will be required with high priority
|
3
|
+
config.app.priotity_initialize_files = ["config/config.rb"]
|
4
|
+
# files from these paths will be required on prod env and auto-loaded on dev env
|
5
|
+
config.app.load_paths = ["app/responders", "app/models", "app/services", "app/decorators"]
|
6
|
+
# files from these paths will be required on on any env
|
7
|
+
config.app.initialize_paths = ["app/observers", "app/routes"]
|
6
8
|
config.app.cache_classes = false
|
7
9
|
|
8
10
|
config.app.name = 'webmate'
|
@@ -23,4 +25,5 @@ Webmate::Application.configure do |config|
|
|
23
25
|
|
24
26
|
config.websockets.enabled = true
|
25
27
|
config.websockets.port = 80
|
28
|
+
config.websockets.namespace = 'http_over_websocket'
|
26
29
|
end
|
@@ -55,61 +55,3 @@ module Webmate
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
58
|
-
=begin
|
59
|
-
class BasePresenter
|
60
|
-
include Serializers::Scoped
|
61
|
-
|
62
|
-
attr_accessor :accessor, :resources
|
63
|
-
|
64
|
-
def initialize(resources)
|
65
|
-
raise ArgumentError, "Resources should not be blank" if resources.blank?
|
66
|
-
@resources = resources
|
67
|
-
end
|
68
|
-
|
69
|
-
def to_serializable
|
70
|
-
build_serialized default_resource do |object|
|
71
|
-
attributes object.attributes.keys
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def errors
|
76
|
-
resource_by_name(:errors) || []
|
77
|
-
end
|
78
|
-
|
79
|
-
def to_json(options = {})
|
80
|
-
serialize_resource.to_json
|
81
|
-
end
|
82
|
-
|
83
|
-
private
|
84
|
-
|
85
|
-
def serialize_resource
|
86
|
-
if errors.present?
|
87
|
-
serialize_errors
|
88
|
-
else
|
89
|
-
to_serializable
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def serialize_errors
|
94
|
-
errors = resource_by_name(:errors)
|
95
|
-
build_serialized do
|
96
|
-
namespace 'errors' do
|
97
|
-
errors.each do |error|
|
98
|
-
attribute error.key do
|
99
|
-
error.value
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def resource_by_name(name)
|
107
|
-
@resources.resource(name.to_sym)
|
108
|
-
end
|
109
|
-
|
110
|
-
def default_resource
|
111
|
-
@resources.resource(:default)
|
112
|
-
end
|
113
|
-
|
114
|
-
end
|
115
|
-
=end
|
@@ -85,7 +85,7 @@ module Webmate::Responders
|
|
85
85
|
|
86
86
|
def build_connection
|
87
87
|
EM::Hiredis.connect
|
88
|
-
rescue
|
88
|
+
rescue
|
89
89
|
warn("problem with connections to redis")
|
90
90
|
nil
|
91
91
|
end
|
@@ -119,7 +119,7 @@ module Webmate::Responders
|
|
119
119
|
# this should be prepared data to create socket.io message
|
120
120
|
# without any additional actions
|
121
121
|
packet_data = Webmate::SocketIO::Packets::Message.prepare_packet_data(response)
|
122
|
-
data = Webmate::
|
122
|
+
data = Webmate::JSON.dump(packet_data)
|
123
123
|
|
124
124
|
channels_to_publish.each {|channel_name| publisher.publish(channel_name, data) }
|
125
125
|
end
|
@@ -2,15 +2,6 @@ require 'webmate/responders/abstract'
|
|
2
2
|
module Webmate::Responders
|
3
3
|
class Base < Abstract
|
4
4
|
after_filter :_run_observer_callbacks
|
5
|
-
after_filter :_send_websocket_events
|
6
|
-
|
7
|
-
def _send_websocket_events
|
8
|
-
packet = Webmate::SocketIO::Packets::Message.new(@response.packed)
|
9
|
-
|
10
|
-
#async do
|
11
|
-
# #Webmate::Websockets.publish(params[:channel], packet.to_packet)
|
12
|
-
#end
|
13
|
-
end
|
14
5
|
|
15
6
|
def _run_observer_callbacks
|
16
7
|
async do
|
@@ -11,25 +11,7 @@ module Webmate::Responders
|
|
11
11
|
@path = options[:path] || "/"
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
Yajl::Encoder.new.encode(self.packed)
|
16
|
-
end
|
17
|
-
|
18
|
-
def packed
|
19
|
-
{ action: @action, resource: @resource, response: @data, params: safe_params }
|
20
|
-
end
|
21
|
-
|
22
|
-
def safe_params
|
23
|
-
safe_params = {}
|
24
|
-
params.each do |key, value|
|
25
|
-
if value.is_a?(String) || value.is_a?(Integer)
|
26
|
-
safe_params[key] = value
|
27
|
-
end
|
28
|
-
end
|
29
|
-
safe_params
|
30
|
-
end
|
31
|
-
|
32
|
-
def rack_format
|
14
|
+
def to_rack
|
33
15
|
[@status, {}, @data]
|
34
16
|
end
|
35
17
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Webmate::Routes
|
2
|
+
class Base
|
3
|
+
FIELDS = [:method, :path, :action, :transport, :responder, :route_regexp, :static_params]
|
4
|
+
attr_reader *FIELDS
|
5
|
+
attr_reader :regexp, :substitution_attrs
|
6
|
+
|
7
|
+
# method: GET/POST/PUT/DELETE
|
8
|
+
# path : /user/123/posts/123/comments
|
9
|
+
# transport: HTTP/WS/
|
10
|
+
# responder: class, responsible to 'respond' action
|
11
|
+
# action: method in webmate responders, called to fetch data
|
12
|
+
# static params: additional params hash, which will be passed to responder
|
13
|
+
# for example, { :scope => :user }
|
14
|
+
#
|
15
|
+
def initialize(args)
|
16
|
+
values = args.with_indifferent_access
|
17
|
+
FIELDS.each do |field_name|
|
18
|
+
instance_variable_set("@#{field_name.to_s}", values[field_name])
|
19
|
+
end
|
20
|
+
|
21
|
+
normalize_data
|
22
|
+
create_matching_regexp
|
23
|
+
create_substitution_attrs
|
24
|
+
end
|
25
|
+
|
26
|
+
# method should check coincidence of path pattern and
|
27
|
+
# given path
|
28
|
+
# '/projects/qwerty123/tasks/asdf13/comments/zxcv123'
|
29
|
+
# will be parsed with route
|
30
|
+
# /projects/:project_id/tasks/:task_id/comments/:comment_id
|
31
|
+
# and return
|
32
|
+
# result = {
|
33
|
+
# action: 'read',
|
34
|
+
# responder: CommentsResponder,
|
35
|
+
# params: {
|
36
|
+
# project_id: 'qwerty123',
|
37
|
+
# task_id: :asdf13,
|
38
|
+
# comment_id: :zxcv123
|
39
|
+
# }
|
40
|
+
# }
|
41
|
+
def match(request_path)
|
42
|
+
if regexp.match(request_path)
|
43
|
+
{
|
44
|
+
action: action,
|
45
|
+
responder: responder,
|
46
|
+
params: (static_params || {}).merge(params_from_path(request_path))
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def params_from_path(path)
|
52
|
+
match_data = regexp.match(path)
|
53
|
+
params = {}
|
54
|
+
substitution_attrs.each_with_index do |key, index|
|
55
|
+
if key == :splat
|
56
|
+
params[key] ||= []
|
57
|
+
params[key] += match_data[index.next].split('/')
|
58
|
+
else
|
59
|
+
params[key] = match_data[index.next]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
params
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# /projects/:project_id/tasks/:task_id/comments/:comment_id
|
68
|
+
# result should be
|
69
|
+
# substitution_attrs = [:project_id, :task_id, :comment_id]
|
70
|
+
# route_regexp =
|
71
|
+
# (?-mix:^\/projects\/([\w\d]*)\/tasks\/([\w\d]*)\/comments\/([\w\d]*)\/?$)
|
72
|
+
#
|
73
|
+
# substitute :resource_id elements with regexp group in order
|
74
|
+
# to easy extract
|
75
|
+
def create_substitution_attrs
|
76
|
+
substitutions = path.scan(/\/:(\w*)|\/(\*)/)
|
77
|
+
@substitution_attrs = substitutions.each_with_object([]) do |scan, attrs|
|
78
|
+
if scan[0]
|
79
|
+
attrs << scan[0].to_sym
|
80
|
+
elsif scan[1]
|
81
|
+
attrs << :splat
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def create_matching_regexp
|
87
|
+
regexp_string = path.gsub(/\/:(\w*_id)/) {|t| "/([\\w\\d]*)" }
|
88
|
+
regexp_string = regexp_string.gsub(/\/\*/) {|t| "\/(.*)"}
|
89
|
+
@regexp = Regexp.new("^#{regexp_string}\/?$")
|
90
|
+
end
|
91
|
+
|
92
|
+
# update attributes by following rules
|
93
|
+
# - responder should be a Class, not String
|
94
|
+
# - ..
|
95
|
+
def normalize_data
|
96
|
+
@responder = @responder.to_s.classify.constantize unless @responder.is_a?(Class)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
module Webmate
|
2
|
-
class
|
1
|
+
module Webmate::Routes
|
2
|
+
class Collection
|
3
3
|
TRANSPORTS = [:ws, :http]
|
4
4
|
|
5
5
|
attr_reader :routes
|
@@ -7,22 +7,28 @@ module Webmate
|
|
7
7
|
def initialize
|
8
8
|
@routes = {}
|
9
9
|
@resource_scope = []
|
10
|
-
|
11
|
-
|
10
|
+
|
11
|
+
enable_websockets if configatron.websockets.enabled
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
# Call this method to define routes in application
|
15
|
+
# Examples:
|
16
|
+
# routes = Webmate::Routes::Collection.new
|
17
|
+
# routes.define do
|
18
|
+
# resources :projects
|
19
|
+
# end
|
20
|
+
def define(&block)
|
15
21
|
instance_eval(&block)
|
16
22
|
end
|
17
23
|
|
18
|
-
#
|
19
|
-
# method - GET/POST/PUT/PATCH/DELETE
|
20
|
-
# transport - HTTP / WS [ HTTPS / WSS ]
|
21
|
-
# path - /projects/123/tasks
|
24
|
+
# Get list of matched routes
|
22
25
|
#
|
26
|
+
# @param String method - GET/POST/PUT/PATCH/DELETE
|
27
|
+
# @param String transport - HTTP / WS [ HTTPS / WSS ]
|
28
|
+
# @param String path - /projects/123/tasks
|
29
|
+
# @return [Hash, nil]
|
23
30
|
def match(method, transport, path)
|
24
|
-
|
25
|
-
routes.each do |route|
|
31
|
+
get_routes(method, transport.upcase).each do |route|
|
26
32
|
if info = route.match(path)
|
27
33
|
return info
|
28
34
|
end
|
@@ -30,6 +36,11 @@ module Webmate
|
|
30
36
|
nil
|
31
37
|
end
|
32
38
|
|
39
|
+
# Get routes by method and transport
|
40
|
+
#
|
41
|
+
# @param String method - GET/POST/PUT/PATCH/DELETE
|
42
|
+
# @param String transport - HTTP / WS [ HTTPS / WSS ]
|
43
|
+
# @return Array list of routes
|
33
44
|
def get_routes(method, transport)
|
34
45
|
@routes[method] ||= {}
|
35
46
|
@routes[method][transport] || []
|
@@ -40,32 +51,36 @@ module Webmate
|
|
40
51
|
# if websockets enabled, we should add specific http routes
|
41
52
|
# - for handshake [ get session id ]
|
42
53
|
# - for connection opening [ switch protocol from http to ws ]
|
43
|
-
def
|
54
|
+
def enable_websockets
|
44
55
|
namespace = configatron.websockets.namespace
|
45
|
-
namespace = '
|
56
|
+
namespace = 'http_over_websocket' if namespace.blank?
|
46
57
|
|
47
|
-
route_options = { method: 'GET', transport: ['HTTP']
|
58
|
+
route_options = { method: 'GET', transport: ['HTTP'] }
|
48
59
|
|
49
60
|
# handshake
|
50
|
-
add_route(
|
61
|
+
add_route(route_options.merge(
|
51
62
|
path: "/#{namespace}/:version_id",
|
52
63
|
responder: Webmate::SocketIO::Actions::Handshake,
|
53
|
-
|
64
|
+
action: 'websocket'
|
65
|
+
))
|
54
66
|
|
55
67
|
# transport connection
|
56
|
-
add_route(
|
68
|
+
add_route(route_options.merge(
|
57
69
|
transport: ["WS"],
|
58
70
|
path: "/#{namespace}/:version_id/websocket/:session_id",
|
59
71
|
responder: Webmate::SocketIO::Actions::Connection,
|
60
72
|
action: 'open'
|
61
|
-
))
|
73
|
+
))
|
62
74
|
end
|
63
75
|
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
# route - valid object of Webmate::Route class
|
76
|
+
# Add router object to routes
|
77
|
+
#
|
78
|
+
# @param [Webmate::Routes::Base, Hash] route
|
68
79
|
def add_route(route)
|
80
|
+
unless route.is_a?(Webmate::Routes::Base)
|
81
|
+
route = Webmate::Routes::Base.new(route)
|
82
|
+
end
|
83
|
+
|
69
84
|
# add route to specific node of routes hash
|
70
85
|
@routes[route.method.to_s.upcase] ||= {}
|
71
86
|
route.transport.each do |transport|
|
@@ -74,8 +89,9 @@ module Webmate
|
|
74
89
|
end
|
75
90
|
|
76
91
|
# define methods for separate routes
|
77
|
-
#
|
78
|
-
#
|
92
|
+
#
|
93
|
+
# Examples:
|
94
|
+
# get '/path', to: 'tasks#list', transport: [:http]
|
79
95
|
# resources :projects
|
80
96
|
# member do
|
81
97
|
# get 'read_formatted'
|
@@ -93,7 +109,7 @@ module Webmate
|
|
93
109
|
end
|
94
110
|
route_options[:method] = method_name.to_sym
|
95
111
|
|
96
|
-
add_route(
|
112
|
+
add_route(route_options)
|
97
113
|
end
|
98
114
|
end
|
99
115
|
|
@@ -195,7 +211,7 @@ module Webmate
|
|
195
211
|
#
|
196
212
|
# member do
|
197
213
|
# get "do_on_member"
|
198
|
-
# end
|
214
|
+
# end
|
199
215
|
# prefix /resource_name/resource_id
|
200
216
|
def member(&block)
|
201
217
|
return if @resource_scope.blank?
|
@@ -230,7 +246,7 @@ module Webmate
|
|
230
246
|
|
231
247
|
|
232
248
|
# helper methods
|
233
|
-
# normalize_transport_option
|
249
|
+
# normalize_transport_option
|
234
250
|
# returns array of requested transports, but available ones only
|
235
251
|
def normalized_transport_option(transport = nil)
|
236
252
|
return TRANSPORTS.dup if transport.blank?
|
@@ -250,7 +266,7 @@ module Webmate
|
|
250
266
|
methods.map{|m| m.to_s.downcase.to_sym} & default_methods
|
251
267
|
end
|
252
268
|
|
253
|
-
def define_resource_read_all_method(resource_name, route_args)
|
269
|
+
def define_resource_read_all_method(resource_name, route_args)
|
254
270
|
get "#{path_prefix}/#{resource_name}", route_args.merge(action: :read_all)
|
255
271
|
end
|
256
272
|
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Webmate::Routes
|
2
|
+
class Handler
|
3
|
+
attr_accessor :application, :request
|
4
|
+
|
5
|
+
def initialize(application, request)
|
6
|
+
@application = application
|
7
|
+
@request = request
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle(route_info)
|
11
|
+
if request.websocket?
|
12
|
+
unless websocket_connection_authorized?(request)
|
13
|
+
return [401, {}, []]
|
14
|
+
end
|
15
|
+
|
16
|
+
session_id = route_info[:params][:session_id]
|
17
|
+
Webmate::Websockets.subscribe(session_id, request) do |message|
|
18
|
+
if route_info = application.routes.match(message['method'], 'WS', message.path)
|
19
|
+
request_info = params_from_websoket(route_info, message)
|
20
|
+
# here we should create subscriber who can live
|
21
|
+
# between messages.. but not between requests.
|
22
|
+
route_info[:responder].new(request_info).respond
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# this response not pass to user - so we keep connection alive.
|
27
|
+
# passing other response will close connection and socket
|
28
|
+
[-1, {}, []]
|
29
|
+
|
30
|
+
else # HTTP
|
31
|
+
# this should return correct Rack response..
|
32
|
+
request_info = params_from_http(route_info)
|
33
|
+
response = route_info[:responder].new(request_info).respond
|
34
|
+
response.to_rack
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# this method prepare data for responder from http request
|
39
|
+
# @param Hash request_info = { path: '/', metadata: {}, action: 'index', params: { test: true } }
|
40
|
+
def params_from_http(route_info)
|
41
|
+
# create unified request info
|
42
|
+
request_params = http_body_request_params
|
43
|
+
metadata = request_params.delete(:metadata)
|
44
|
+
{
|
45
|
+
path: request.path,
|
46
|
+
metadata: metadata || {},
|
47
|
+
action: route_info[:action],
|
48
|
+
params: request_params.merge(route_info[:params]),
|
49
|
+
request: request
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
# this method prepare data for responder from http request
|
54
|
+
def params_from_websoket(route_info, message)
|
55
|
+
{
|
56
|
+
path: message.path,
|
57
|
+
metadata: message.metadata || {},
|
58
|
+
action: route_info[:action],
|
59
|
+
params: message.params.merge(route_info[:params])
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get and parse all request params
|
64
|
+
def http_body_request_params
|
65
|
+
request_params = HashWithIndifferentAccess.new
|
66
|
+
request_params.merge!(@request.params || {})
|
67
|
+
|
68
|
+
# read post or put params. this will erase params
|
69
|
+
# {code: 123, mode: 123}
|
70
|
+
# "code=123&mode=123"
|
71
|
+
request_body = @request.body.read
|
72
|
+
if request_body.present?
|
73
|
+
body_params = begin
|
74
|
+
JSON.parse(request_body) # {code: 123, mode: 123}
|
75
|
+
rescue JSON::ParserError
|
76
|
+
Rack::Utils.parse_nested_query(request_body) # "code=123&mode=123"
|
77
|
+
end
|
78
|
+
else
|
79
|
+
body_params = {}
|
80
|
+
end
|
81
|
+
|
82
|
+
request_params.merge(body_params)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Check that client with that scope is authorized to open connection
|
86
|
+
def websocket_connection_authorized?(request)
|
87
|
+
true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -23,8 +23,8 @@ module Webmate
|
|
23
23
|
@packet_data = packet_data.with_indifferent_access
|
24
24
|
end
|
25
25
|
|
26
|
-
# packet should be created by socket.io spec
|
27
|
-
#[message type] ':' [message id ('+')] ':' [message endpoint] (':' [message data])
|
26
|
+
# packet should be created by socket.io spec
|
27
|
+
# [message type] ':' [message id ('+')] ':' [message endpoint] (':' [message data])
|
28
28
|
# and webmate spec
|
29
29
|
# message_data = {
|
30
30
|
# method: GET/POST/...
|
@@ -59,14 +59,13 @@ module Webmate
|
|
59
59
|
end
|
60
60
|
|
61
61
|
# convert response from Responders::Base to socket io message
|
62
|
-
#
|
62
|
+
#
|
63
63
|
def self.build_response_packet(response)
|
64
64
|
new(self.prepare_packet_data(response))
|
65
65
|
end
|
66
66
|
|
67
67
|
def self.prepare_packet_data(response)
|
68
68
|
packet_data = {
|
69
|
-
action: response.action,
|
70
69
|
body: response.data,
|
71
70
|
path: response.path,
|
72
71
|
params: response.params,
|
@@ -75,12 +74,11 @@ module Webmate
|
|
75
74
|
end
|
76
75
|
|
77
76
|
# socket io spec
|
78
|
-
#[message type] ':' [message id ('+')] ':' [message endpoint] (':' [message data])
|
77
|
+
#[message type] ':' [message id ('+')] ':' [message endpoint] (':' [message data])
|
79
78
|
def to_packet
|
80
79
|
data = {
|
81
|
-
|
82
|
-
|
83
|
-
path: path,
|
80
|
+
request: {
|
81
|
+
path: path,
|
84
82
|
metadata: metadata
|
85
83
|
},
|
86
84
|
response: {
|
@@ -88,7 +86,7 @@ module Webmate
|
|
88
86
|
status: status || 200
|
89
87
|
}
|
90
88
|
}
|
91
|
-
encoded_data =
|
89
|
+
encoded_data = JSON.dump(data)
|
92
90
|
[
|
93
91
|
packet_type_id,
|
94
92
|
packet_id,
|
@@ -117,10 +115,6 @@ module Webmate
|
|
117
115
|
packet_data[:path]
|
118
116
|
end
|
119
117
|
|
120
|
-
def action
|
121
|
-
packet_data[:action]
|
122
|
-
end
|
123
|
-
|
124
118
|
def params
|
125
119
|
packet_data[:params]
|
126
120
|
end
|
@@ -137,14 +131,8 @@ module Webmate
|
|
137
131
|
@id ||= generate_packet_id
|
138
132
|
end
|
139
133
|
|
140
|
-
# update counter
|
141
|
-
def packet_id=(new_packet_id)
|
142
|
-
self.class.current_id = new_packet_id
|
143
|
-
@id ||= generate_packet_id
|
144
|
-
end
|
145
|
-
|
146
134
|
# unique packet id
|
147
|
-
# didn't find any influence for now,
|
135
|
+
# didn't find any influence for now,
|
148
136
|
# uniqueness not matter
|
149
137
|
def generate_packet_id
|
150
138
|
self.class.current_id ||= 0
|
data/lib/webmate/version.rb
CHANGED
data/lib/webmate/views/scope.rb
CHANGED
data/lib/webmate/websockets.rb
CHANGED
@@ -2,7 +2,7 @@ module Webmate
|
|
2
2
|
class Websockets
|
3
3
|
class << self
|
4
4
|
def subscribe(session_id, request, &block)
|
5
|
-
user_id = request.env['warden'].user.id
|
5
|
+
user_id = request.env['warden'].try(:user).try(:id)
|
6
6
|
|
7
7
|
request.websocket do |websocket|
|
8
8
|
# subscribe user to redis channel
|
@@ -14,12 +14,14 @@ module Webmate
|
|
14
14
|
end
|
15
15
|
|
16
16
|
websocket.onmessage do |message|
|
17
|
-
|
17
|
+
request = Webmate::SocketIO::Packets::Base.parse(message)
|
18
|
+
response = block.call(request)
|
18
19
|
if response
|
19
20
|
packet = Webmate::SocketIO::Packets::Message.build_response_packet(response)
|
20
21
|
websocket.send(packet.to_packet)
|
21
22
|
else
|
22
|
-
|
23
|
+
packet = Webmate::SocketIO::Packets::Error.build_response_packet("{error: 'empty response for #{message.inspect}'}")
|
24
|
+
websocket.send(packet.to_packet)
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -37,7 +39,7 @@ module Webmate
|
|
37
39
|
warn("user has been subscribed to channel '#{channel_name}'")
|
38
40
|
|
39
41
|
subscriber.on(:message) do |channel, message_data|
|
40
|
-
response_data = Webmate::
|
42
|
+
response_data = Webmate::JSON.parse(message_data)
|
41
43
|
packet = Webmate::SocketIO::Packets::Message.new(response_data)
|
42
44
|
|
43
45
|
websocket.send(packet.to_packet)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class FooResponder; end
|
4
|
+
|
5
|
+
describe Webmate::Application do
|
6
|
+
|
7
|
+
let(:subject) { Webmate::Application }
|
8
|
+
|
9
|
+
describe "#define_routes" do
|
10
|
+
context "responder and action from params" do
|
11
|
+
it "should define applicatio routes" do
|
12
|
+
subject.define_routes do
|
13
|
+
get '/projects', responder: FooResponder, action: 'bar'
|
14
|
+
end
|
15
|
+
route = subject.routes.match('GET', 'HTTP', '/projects')
|
16
|
+
route[:responder].should == FooResponder
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestResponder; end
|
4
|
+
|
5
|
+
def build_route_for(path, action = "any", responder = "test_responder")
|
6
|
+
route_args = {
|
7
|
+
method: "any",
|
8
|
+
transport: "transport",
|
9
|
+
}.merge(
|
10
|
+
path: path,
|
11
|
+
action: action,
|
12
|
+
responder: responder
|
13
|
+
)
|
14
|
+
|
15
|
+
Webmate::Routes::Base.new(route_args)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe Webmate::Routes::Base do
|
19
|
+
describe "#match" do
|
20
|
+
it "should match simple routes" do
|
21
|
+
result = build_route_for('/projects').match("/projects")
|
22
|
+
result.should_not be_nil
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should match empty routes" do
|
26
|
+
result = build_route_for('/').match("/")
|
27
|
+
result.should_not be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should match routes with placements" do
|
31
|
+
result = build_route_for('/projects/:project_id').match("/projects/qwerty")
|
32
|
+
result.should_not be_nil
|
33
|
+
result[:params][:project_id].should == 'qwerty'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should match routes with wildcards" do
|
37
|
+
route = build_route_for('/projects/*')
|
38
|
+
result = build_route_for('/projects/*').match("/projects/qwerty/code")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return nil for unmatched route" do
|
42
|
+
route = build_route_for('/foo').match('/bar')
|
43
|
+
route.should be_nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class FooResponder; end
|
4
|
+
class ProjectsResponder; end
|
5
|
+
|
6
|
+
describe Webmate::Routes::Collection do
|
7
|
+
|
8
|
+
let(:subject) { Webmate::Routes::Collection.new }
|
9
|
+
|
10
|
+
describe "#define" do
|
11
|
+
context "responder and action from params" do
|
12
|
+
it "should allow setting responder and action using params" do
|
13
|
+
subject.define do
|
14
|
+
get '/projects', responder: FooResponder, action: 'bar'
|
15
|
+
end
|
16
|
+
route = subject.match('GET', 'HTTP', '/projects')
|
17
|
+
route[:responder].should == FooResponder
|
18
|
+
route[:action].should == 'bar'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should allow setting responder and action using :to option" do
|
22
|
+
subject.define do
|
23
|
+
get '/projects', to: 'foo#bar'
|
24
|
+
end
|
25
|
+
route = subject.match('GET', 'HTTP', '/projects')
|
26
|
+
route[:responder].should == FooResponder
|
27
|
+
route[:action].to_s.should == 'bar'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "responder and action by resource scope" do
|
32
|
+
before do
|
33
|
+
subject.define do
|
34
|
+
resources :projects
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "index action" do
|
39
|
+
route = subject.match('GET', 'HTTP', '/projects')
|
40
|
+
route[:responder].should == ProjectsResponder
|
41
|
+
route[:action].to_s.should == 'read_all'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "show action" do
|
45
|
+
route = subject.match('GET', 'HTTP', '/projects/1')
|
46
|
+
route[:responder].should == ProjectsResponder
|
47
|
+
route[:action].to_s.should == 'read'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#match" do
|
53
|
+
before do
|
54
|
+
subject.define do
|
55
|
+
get '/foo', responder: FooResponder, action: 'bar'
|
56
|
+
resources :projects, transport: [:http]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should return matched route" do
|
61
|
+
subject.match('GET', 'HTTP', '/foo').should_not be_nil
|
62
|
+
subject.match('GET', 'WS', '/foo').should_not be_nil
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should return route only for matched transport" do
|
66
|
+
subject.match('GET', 'HTTP', '/projects/1').should_not be_nil
|
67
|
+
subject.match('GET', 'WS', '/projects/1').should be_nil
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should return nil if no route found" do
|
71
|
+
subject.match('GET', 'HTTP', '/bar').should be_nil
|
72
|
+
subject.match('GET', 'WS', '/bar').should be_nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webmate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-07-
|
12
|
+
date: 2013-07-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: thin
|
@@ -217,8 +217,9 @@ files:
|
|
217
217
|
- lib/webmate/responders/exceptions.rb
|
218
218
|
- lib/webmate/responders/response.rb
|
219
219
|
- lib/webmate/responders/templates.rb
|
220
|
-
- lib/webmate/
|
221
|
-
- lib/webmate/
|
220
|
+
- lib/webmate/routes/base.rb
|
221
|
+
- lib/webmate/routes/collection.rb
|
222
|
+
- lib/webmate/routes/handler.rb
|
222
223
|
- lib/webmate/socket.io/actions/connection.rb
|
223
224
|
- lib/webmate/socket.io/actions/handshake.rb
|
224
225
|
- lib/webmate/socket.io/packets/ack.rb
|
@@ -231,11 +232,13 @@ files:
|
|
231
232
|
- lib/webmate/socket.io/packets/json.rb
|
232
233
|
- lib/webmate/socket.io/packets/message.rb
|
233
234
|
- lib/webmate/socket.io/packets/noop.rb
|
234
|
-
- lib/webmate/support/
|
235
|
+
- lib/webmate/support/json.rb
|
235
236
|
- lib/webmate/version.rb
|
236
237
|
- lib/webmate/views/scope.rb
|
237
238
|
- lib/webmate/websockets.rb
|
238
|
-
- spec/lib/
|
239
|
+
- spec/lib/application_spec.rb
|
240
|
+
- spec/lib/routes/base_spec.rb
|
241
|
+
- spec/lib/routes/collection_spec.rb
|
239
242
|
- spec/spec_helper.rb
|
240
243
|
- webmate.gemspec
|
241
244
|
homepage: https://github.com/webmate/webmate
|
@@ -253,7 +256,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
253
256
|
version: '0'
|
254
257
|
segments:
|
255
258
|
- 0
|
256
|
-
hash:
|
259
|
+
hash: 1691783446799875159
|
257
260
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
258
261
|
none: false
|
259
262
|
requirements:
|
@@ -262,7 +265,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
262
265
|
version: '0'
|
263
266
|
segments:
|
264
267
|
- 0
|
265
|
-
hash:
|
268
|
+
hash: 1691783446799875159
|
266
269
|
requirements: []
|
267
270
|
rubyforge_project:
|
268
271
|
rubygems_version: 1.8.25
|
@@ -1,91 +0,0 @@
|
|
1
|
-
module Webmate
|
2
|
-
class Route
|
3
|
-
FIELDS = [:method, :path, :action, :transport, :responder, :route_regexp, :static_params]
|
4
|
-
attr_reader *FIELDS
|
5
|
-
|
6
|
-
# method: GET/POST/PUT/DELETE
|
7
|
-
# path : /user/123/posts/123/comments
|
8
|
-
# transport: HTTP/WS/
|
9
|
-
# responder: class, responsible to 'respond' action
|
10
|
-
# action: method in webmate responders, called to fetch data
|
11
|
-
# static params: additional params hash, which will be passed to responder
|
12
|
-
# for example, { :scope => :user }
|
13
|
-
#
|
14
|
-
def initialize(args)
|
15
|
-
values = args.with_indifferent_access
|
16
|
-
FIELDS.each do |field_name|
|
17
|
-
instance_variable_set("@#{field_name.to_s}", values[field_name])
|
18
|
-
end
|
19
|
-
|
20
|
-
normalize_data_if_needed
|
21
|
-
@route_regexp ||= construct_match_regexp
|
22
|
-
end
|
23
|
-
|
24
|
-
# method should check coincidence of path pattern and
|
25
|
-
# given path
|
26
|
-
# '/projects/qwerty123/tasks/asdf13/comments/zxcv123'
|
27
|
-
# will be parsed with route
|
28
|
-
# /projects/:project_id/tasks/:task_id/comments/:comment_id
|
29
|
-
# and return
|
30
|
-
# result = {
|
31
|
-
# action: 'read',
|
32
|
-
# responder: CommentsResponder,
|
33
|
-
# params: {
|
34
|
-
# project_id: 'qwerty123',
|
35
|
-
# task_id: :asdf13,
|
36
|
-
# comment_id: :zxcv123
|
37
|
-
# }
|
38
|
-
# }
|
39
|
-
def match(request_path)
|
40
|
-
if match_data = @route_regexp.match(request_path)
|
41
|
-
route_data = {
|
42
|
-
action: @action,
|
43
|
-
responder: @responder,
|
44
|
-
params: HashWithIndifferentAccess.new(static_params || {})
|
45
|
-
}
|
46
|
-
@substitution_attrs.each_with_index do |key, index|
|
47
|
-
if key == :splat
|
48
|
-
route_data[:params][key] ||= []
|
49
|
-
route_data[:params][key] += match_data[index.next].split('/')
|
50
|
-
else
|
51
|
-
route_data[:params][key] = match_data[index.next]
|
52
|
-
end
|
53
|
-
end
|
54
|
-
route_data
|
55
|
-
else
|
56
|
-
nil # not matched.
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
# /projects/:project_id/tasks/:task_id/comments/:comment_id
|
63
|
-
# result should be
|
64
|
-
# substitution_attrs = [:project_id, :task_id, :comment_id]
|
65
|
-
# route_regexp =
|
66
|
-
# (?-mix:^\/projects\/([\w\d]*)\/tasks\/([\w\d]*)\/comments\/([\w\d]*)\/?$)
|
67
|
-
#
|
68
|
-
# substitute :resource_id elements with regexp group in order
|
69
|
-
# to easy extract
|
70
|
-
def construct_match_regexp
|
71
|
-
substitutions = path.scan(/\/:(\w*)|\/(\*)/)
|
72
|
-
@substitution_attrs = substitutions.each_with_object([]) do |scan, attrs|
|
73
|
-
if scan[0]
|
74
|
-
attrs << scan[0].to_sym
|
75
|
-
elsif scan[1]
|
76
|
-
attrs << :splat
|
77
|
-
end
|
78
|
-
end
|
79
|
-
regexp_string = path.gsub(/\/:(\w*_id)/) {|t| "/([\\w\\d]*)" }
|
80
|
-
regexp_string = regexp_string.gsub(/\/\*/) {|t| "\/(.*)"}
|
81
|
-
Regexp.new("^#{regexp_string}\/?$")
|
82
|
-
end
|
83
|
-
|
84
|
-
# update attributes by following rules
|
85
|
-
# - responder should be a Class, not String
|
86
|
-
# - ..
|
87
|
-
def normalize_data_if_needed
|
88
|
-
@responder = @responder.to_s.classify.constantize unless @responder.is_a?(Class)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
# TODO: this is needed to use latest mongoid with moped, but it doesn't work at this moment
|
2
|
-
|
3
|
-
begin
|
4
|
-
require "moped"
|
5
|
-
rescue LoadError => error
|
6
|
-
raise "Missing EM-Synchrony dependency: gem install moped"
|
7
|
-
end
|
8
|
-
|
9
|
-
module Moped
|
10
|
-
class TimeoutHandler
|
11
|
-
def self.timeout(op_timeout, &block)
|
12
|
-
f = Fiber.current
|
13
|
-
timer = EM::Timer.new(op_timeout) { f.resume(nil) }
|
14
|
-
res = block.call
|
15
|
-
timer.cancel
|
16
|
-
res
|
17
|
-
end
|
18
|
-
end
|
19
|
-
module Sockets
|
20
|
-
module Connectable
|
21
|
-
module ClassMethods
|
22
|
-
def connect(host, port, timeout)
|
23
|
-
TimeoutHandler.timeout(timeout) do
|
24
|
-
sock = new(host, port)
|
25
|
-
#sock.set_encoding('binary')
|
26
|
-
#sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
27
|
-
sock
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class EM_TCP < ::EventMachine::Synchrony::TCPSocket
|
34
|
-
include Connectable
|
35
|
-
|
36
|
-
def connection_completed
|
37
|
-
@opening = false
|
38
|
-
@in_req.succeed(self) if @in_req
|
39
|
-
end
|
40
|
-
end
|
41
|
-
Mutex = ::EventMachine::Synchrony::Thread::Mutex
|
42
|
-
ConditionVariable = ::EventMachine::Synchrony::Thread::ConditionVariable
|
43
|
-
end
|
44
|
-
class Connection
|
45
|
-
def connect
|
46
|
-
@sock = if !!options[:ssl]
|
47
|
-
Sockets::SSL.connect(host, port, timeout)
|
48
|
-
else
|
49
|
-
Sockets::EM_TCP.connect(host, port, timeout)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
# responder to use as param for route creation.
|
4
|
-
# should not be used for another
|
5
|
-
class TestResponder; end
|
6
|
-
|
7
|
-
def build_route_for(path, action = "any", responder = "test_responder")
|
8
|
-
route_args = {
|
9
|
-
method: "any",
|
10
|
-
transport: "transport",
|
11
|
-
}.merge(
|
12
|
-
path: path,
|
13
|
-
action: action,
|
14
|
-
responder: responder
|
15
|
-
)
|
16
|
-
|
17
|
-
Webmate::Route.new(route_args)
|
18
|
-
end
|
19
|
-
|
20
|
-
describe Webmate::Route do
|
21
|
-
it "should match simple routes" do
|
22
|
-
result = build_route_for('/projects').match("/projects")
|
23
|
-
result.should_not be_nil
|
24
|
-
end
|
25
|
-
|
26
|
-
it "should match empty routes" do
|
27
|
-
result = build_route_for('/').match("/")
|
28
|
-
result.should_not be_nil
|
29
|
-
end
|
30
|
-
|
31
|
-
it "should match routes with placements" do
|
32
|
-
result = build_route_for('/projects/:project_id').match("/projects/qwerty")
|
33
|
-
result.should_not be_nil
|
34
|
-
result[:params][:project_id].should == 'qwerty'
|
35
|
-
end
|
36
|
-
|
37
|
-
it "should match routes with wildcards" do
|
38
|
-
route = build_route_for('/projects/*')
|
39
|
-
result = build_route_for('/projects/*').match("/projects/qwerty/code")
|
40
|
-
end
|
41
|
-
end
|