webmate 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|