shove 0.52 → 1.0.1
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 +5 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +63 -0
- data/README.markdown +263 -0
- data/Rakefile +47 -0
- data/bin/shove +128 -106
- data/lib/shove/app.rb +78 -0
- data/lib/shove/app_directory.rb +104 -0
- data/lib/shove/client/callback.rb +24 -0
- data/lib/shove/client/channel.rb +81 -0
- data/lib/shove/client/connection.rb +167 -0
- data/lib/shove/http/channel_context.rb +60 -0
- data/lib/shove/http/client_context.rb +82 -0
- data/lib/shove/http/request.rb +94 -0
- data/lib/shove/http/response.rb +30 -0
- data/lib/shove/protocol.rb +53 -0
- data/lib/shove.rb +60 -78
- data/shove.gemspec +11 -20
- data/spec/app_directory_spec.rb +66 -0
- data/spec/cassettes/should_authorize_oneself.yml +24 -0
- data/spec/cassettes/should_be_able_to_authorize_with_the_server.yml +24 -0
- data/spec/cassettes/should_cancel_a_binding.yml +24 -0
- data/spec/cassettes/should_configure_the_default.yml +24 -0
- data/spec/cassettes/should_configure_the_from_the_previous_test.yml +24 -0
- data/spec/cassettes/should_create_a_channel_context.yml +24 -0
- data/spec/cassettes/should_deny_a_connection.yml +24 -0
- data/spec/cassettes/should_deny_a_control_to_a_client.yml +24 -0
- data/spec/cassettes/should_deny_a_publishing_to_a_client.yml +24 -0
- data/spec/cassettes/should_deny_a_subscriptions_to_a_client.yml +24 -0
- data/spec/cassettes/should_deny_publishing_on_a_channel_context.yml +24 -0
- data/spec/cassettes/should_deny_subscriptions_on_a_channel_context.yml +24 -0
- data/spec/cassettes/should_get_a_set_of_nodes_for_the_network.yml +24 -0
- data/spec/cassettes/should_get_a_subscribe_granted_event.yml +24 -0
- data/spec/cassettes/should_grant_a_connection.yml +24 -0
- data/spec/cassettes/should_grant_a_control_to_a_client.yml +24 -0
- data/spec/cassettes/should_grant_a_publishing_to_a_client.yml +24 -0
- data/spec/cassettes/should_grant_a_subscriptions_to_a_client.yml +24 -0
- data/spec/cassettes/should_grant_publishing_on_a_channel_context.yml +24 -0
- data/spec/cassettes/should_grant_subscriptions_on_a_channel_context.yml +24 -0
- data/spec/cassettes/should_publish.yml +24 -0
- data/spec/cassettes/should_publish_on_a_channel_context.yml +24 -0
- data/spec/cassettes/should_publish_to_a_client.yml +24 -0
- data/spec/cassettes/should_receive_an_unsubscribe_event.yml +24 -0
- data/spec/cassettes/should_receive_messages_on_a_channel.yml +24 -0
- data/spec/cassettes/should_send_a_connect_op.yml +24 -0
- data/spec/cassettes/should_send_a_connect_op_with_an_id.yml +24 -0
- data/spec/cassettes/should_spawn_a_client.yml +24 -0
- data/spec/cassettes/should_subscribe_to_a_channel.yml +24 -0
- data/spec/cassettes/should_trigger_a_connect_denied_event.yml +24 -0
- data/spec/cassettes/should_trigger_a_connect_event.yml +24 -0
- data/spec/cassettes/should_trigger_a_disconnect_event.yml +24 -0
- data/spec/cassettes/should_trigger_an_error_event.yml +24 -0
- data/spec/cassettes/should_unsubscribe_from_a_channel.yml +24 -0
- data/spec/cassettes/should_update_the_default_app.yml +24 -0
- data/spec/helper.rb +60 -0
- data/spec/shove_client_spec.rb +194 -0
- data/spec/shove_http_spec.rb +142 -0
- metadata +111 -81
- data/README.md +0 -71
- data/lib/shove/client.rb +0 -54
- data/lib/shove/request.rb +0 -80
- data/lib/shove/response.rb +0 -23
- data/spec/shove_spec.rb +0 -40
data/lib/shove/app.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module Shove
|
2
|
+
class App
|
3
|
+
|
4
|
+
attr_accessor :config
|
5
|
+
|
6
|
+
# create an API client
|
7
|
+
# +config+ optional Confstruct
|
8
|
+
# +&block+ config block
|
9
|
+
# Example:
|
10
|
+
# Shove::App.new do
|
11
|
+
# app_id "myappid"
|
12
|
+
# app_key "myappkey"
|
13
|
+
# end
|
14
|
+
def initialize config=Confstruct::Configuration.new, &block
|
15
|
+
@config = config
|
16
|
+
configure(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def configure params={}, &block
|
20
|
+
if params
|
21
|
+
@config.configure params
|
22
|
+
end
|
23
|
+
|
24
|
+
if block
|
25
|
+
@config.configure(&block)
|
26
|
+
end
|
27
|
+
|
28
|
+
unless @config.app_id && @config.app_key
|
29
|
+
raise ShoveException.new("App ID and App Key are required")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# is the app valid?
|
34
|
+
# do the app_id and app_key work with the remote
|
35
|
+
def valid?
|
36
|
+
!request("validate").exec_sync(:get).error?
|
37
|
+
end
|
38
|
+
|
39
|
+
# get a list of websocket hosts
|
40
|
+
def hosts
|
41
|
+
request("hosts").exec_sync(:get).parse
|
42
|
+
end
|
43
|
+
|
44
|
+
# create a channel context for acting on a channel
|
45
|
+
# +name+ the name of the channel
|
46
|
+
def channel name
|
47
|
+
Http::ChannelContext.new(self, name)
|
48
|
+
end
|
49
|
+
|
50
|
+
# create a cleint context for acting on a client
|
51
|
+
# +id+ the id of the client
|
52
|
+
def client id
|
53
|
+
Http::ClientContext.new(self, id)
|
54
|
+
end
|
55
|
+
|
56
|
+
# the base URL based on the settings
|
57
|
+
def url
|
58
|
+
"#{@config.api_url}/apps/#{@config.app_id}"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create a default request object with the base URL
|
62
|
+
# +path+ extra path info
|
63
|
+
def request path
|
64
|
+
Http::Request.new("#{url}/#{path}", @config)
|
65
|
+
end
|
66
|
+
|
67
|
+
####
|
68
|
+
|
69
|
+
# Connect to shove as a client in the current process
|
70
|
+
# +id+ optional shove id to supply
|
71
|
+
def connect id=nil
|
72
|
+
client = Client::Connection.new(self, id)
|
73
|
+
client.connect
|
74
|
+
client
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
|
2
|
+
module Shove
|
3
|
+
|
4
|
+
# Used for storing and accessing shove credentials
|
5
|
+
# from the command line utility
|
6
|
+
class AppDirectory
|
7
|
+
|
8
|
+
attr_accessor :apps, :app
|
9
|
+
|
10
|
+
def initialize io=STDIN, path=File.expand_path("~/.shove.yml")
|
11
|
+
@io = io
|
12
|
+
@path = path
|
13
|
+
@app = nil
|
14
|
+
@apps = {}
|
15
|
+
load!
|
16
|
+
end
|
17
|
+
|
18
|
+
def put app_id, app_key
|
19
|
+
@apps[app_id] = app_key
|
20
|
+
@app = app_id if @app.nil?
|
21
|
+
save
|
22
|
+
end
|
23
|
+
|
24
|
+
def key app_id
|
25
|
+
get_config(app_id)[:app_key]
|
26
|
+
end
|
27
|
+
|
28
|
+
def default= app_id
|
29
|
+
unless app_id.nil?
|
30
|
+
@app = app_id
|
31
|
+
save
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def default
|
36
|
+
@app ? get_config(@app) : get_config
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_config app_id=nil
|
40
|
+
config = ENV["SHOVE_ENV"] == "development" ? {
|
41
|
+
:api_url => "http://api.shove.dev:8080",
|
42
|
+
:ws_url => "ws://shove.dev:9000"
|
43
|
+
} : {}
|
44
|
+
|
45
|
+
if !@app.nil? && app_id.nil?
|
46
|
+
config[:app_id] = @app
|
47
|
+
config[:app_key] = key(@app)
|
48
|
+
elsif app_id.nil? || !@apps.key?(app_id)
|
49
|
+
|
50
|
+
puts "We need some information to continue"
|
51
|
+
|
52
|
+
if app_id.nil?
|
53
|
+
config[:app_id] = getinput "Enter App Id"
|
54
|
+
else
|
55
|
+
config[:app_id] = app_id
|
56
|
+
end
|
57
|
+
|
58
|
+
loop do
|
59
|
+
config[:app_key] = getinput "Enter App Key"
|
60
|
+
|
61
|
+
Shove.configure config
|
62
|
+
if Shove.valid?
|
63
|
+
puts "App Settings accepted. Moving on..."
|
64
|
+
break
|
65
|
+
else
|
66
|
+
puts "App Settings invalid, please try again"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
put config[:app_id], config[:app_key]
|
71
|
+
else
|
72
|
+
config[:app_id] = app_id
|
73
|
+
config[:app_key] = @apps[app_id]
|
74
|
+
end
|
75
|
+
|
76
|
+
config
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def save
|
82
|
+
File.open(@path, "w") do |f|
|
83
|
+
f << {
|
84
|
+
"app" => @app,
|
85
|
+
"apps" => @apps
|
86
|
+
}.to_yaml
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def getinput text
|
91
|
+
print "#{text}: "
|
92
|
+
@io.gets.strip
|
93
|
+
end
|
94
|
+
|
95
|
+
def load!
|
96
|
+
if FileTest.exist?(@path)
|
97
|
+
tmp = YAML.load_file(@path)
|
98
|
+
@app = tmp["app"]
|
99
|
+
@apps = tmp["apps"]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module Shove
|
3
|
+
module Client
|
4
|
+
# Represents a callback that can be canceled
|
5
|
+
class Callback
|
6
|
+
|
7
|
+
def initialize group, block
|
8
|
+
@group = group
|
9
|
+
@block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
def call *args
|
13
|
+
@block.call(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def cancel
|
17
|
+
@group.delete self
|
18
|
+
@block = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Shove
|
2
|
+
module Client
|
3
|
+
class Channel
|
4
|
+
|
5
|
+
include Protocol
|
6
|
+
|
7
|
+
attr_accessor :name
|
8
|
+
|
9
|
+
# Create a new channel
|
10
|
+
# +name+ The name of the channel
|
11
|
+
def initialize name, conn
|
12
|
+
@conn = conn
|
13
|
+
@name = name
|
14
|
+
@callbacks = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Bind a block to an event
|
18
|
+
# +event+ the event name
|
19
|
+
# +block+ the callback
|
20
|
+
def on event, &block
|
21
|
+
unless @callbacks.has_key?(event)
|
22
|
+
@callbacks[event] = []
|
23
|
+
end
|
24
|
+
result = Callback.new(@callbacks[event], block)
|
25
|
+
@callbacks[event] << result
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
# Process a message for the channel
|
30
|
+
# +message+ the message in question
|
31
|
+
def process message
|
32
|
+
op = message["opcode"]
|
33
|
+
data = message["data"]
|
34
|
+
|
35
|
+
case op
|
36
|
+
when PUBLISH
|
37
|
+
emit("message", data)
|
38
|
+
when SUBSCRIBE_GRANTED
|
39
|
+
emit("subscribe")
|
40
|
+
when SUBSCRIBE_DENIED
|
41
|
+
emit("subscribe_denied")
|
42
|
+
when PUBLISH_DENIED
|
43
|
+
emit("publish_denied")
|
44
|
+
when PUBLISH_GRANTED
|
45
|
+
emit("publish_granted")
|
46
|
+
when UNSUBSCRIBE_COMPLETE
|
47
|
+
emit("unsubscribe")
|
48
|
+
@callbacks.clear
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
# publish a message on the channel
|
54
|
+
# +msg+ the message to publish. It must implement to_s
|
55
|
+
def publish msg
|
56
|
+
@conn.send_data :opcode => PUBLISH, :channel => @name, :data => msg.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
# subscribe to the channel, by sending to the remote
|
60
|
+
def subscribe
|
61
|
+
@conn.send_data :opcode => SUBSCRIBE, :channel => @name
|
62
|
+
end
|
63
|
+
|
64
|
+
# unsubscribe from the channel
|
65
|
+
def unsubscribe
|
66
|
+
@conn.send_data :opcode => UNSUBSCRIBE, :channel => @name
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def emit event, *args
|
72
|
+
if @callbacks.key?(event)
|
73
|
+
@callbacks[event].each do |cb|
|
74
|
+
cb.call(*args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module Shove
|
2
|
+
module Client
|
3
|
+
class Connection
|
4
|
+
|
5
|
+
include Protocol
|
6
|
+
|
7
|
+
attr_accessor :id, :socket, :url
|
8
|
+
|
9
|
+
# Create a Publisher
|
10
|
+
# +app+ the app
|
11
|
+
def initialize app, id
|
12
|
+
@id = id
|
13
|
+
@app = app
|
14
|
+
@parser = Yajl::Parser.new(:symbolize_keys => true)
|
15
|
+
@config = app.config
|
16
|
+
@hosts = app.hosts
|
17
|
+
@channels = {}
|
18
|
+
@events = {}
|
19
|
+
@queue = []
|
20
|
+
@forcedc = false
|
21
|
+
@connected = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def authorize app_key=nil
|
25
|
+
send_data :opcode => AUTHORIZE, :data => (app_key || @config.app_key)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Connect to the shove stream server
|
29
|
+
def connect
|
30
|
+
|
31
|
+
if @connected
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
@socket = EM::WebSocketClient.new(url)
|
36
|
+
@socket.onopen do
|
37
|
+
@connected = true
|
38
|
+
send_data :opcode => CONNECT, :data => @id
|
39
|
+
until @queue.empty? do
|
40
|
+
send_data @queue.shift
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@socket.onmessage do |m|
|
45
|
+
process Yajl::Parser.parse(m)
|
46
|
+
end
|
47
|
+
|
48
|
+
@socket.onclose do
|
49
|
+
@connected = false
|
50
|
+
unless @forcedc
|
51
|
+
reconnect
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Disconnect form the server
|
57
|
+
def disconnect
|
58
|
+
@forcedc = true
|
59
|
+
@socket.disconnect
|
60
|
+
end
|
61
|
+
|
62
|
+
# Bind to events for a given channel.
|
63
|
+
# +channel+ the channel name to subscribe to
|
64
|
+
# +event+ the event name to bind to
|
65
|
+
# +block+ the block which is called when a message is received
|
66
|
+
def on event, &block
|
67
|
+
unless @events.key?(event)
|
68
|
+
@events[event] = []
|
69
|
+
end
|
70
|
+
@events[event] << block
|
71
|
+
end
|
72
|
+
|
73
|
+
# Fetch a channel
|
74
|
+
# +name+ the name of the channel
|
75
|
+
def channel name
|
76
|
+
unless @channels.key?(name)
|
77
|
+
@channels[name] = Channel.new(name, self)
|
78
|
+
if name != "direct"
|
79
|
+
@channels[name].subscribe
|
80
|
+
end
|
81
|
+
end
|
82
|
+
@channels[name]
|
83
|
+
end
|
84
|
+
|
85
|
+
def url
|
86
|
+
if @config.ws_url
|
87
|
+
@url = "#{@config.ws_url}/#{@config.app_id}"
|
88
|
+
else
|
89
|
+
if @hosts.empty?
|
90
|
+
raise "Error fetching hosts for app #{@app_id}"
|
91
|
+
end
|
92
|
+
@url = "ws://#{@hosts.first}.shove.io/#{@config.app_id}"
|
93
|
+
end
|
94
|
+
@url
|
95
|
+
end
|
96
|
+
|
97
|
+
def send_data data
|
98
|
+
if @connected
|
99
|
+
@socket.send_data(Yajl::Encoder.encode(data))
|
100
|
+
else
|
101
|
+
@queue << data
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def reconnect
|
108
|
+
@reconnecting = true
|
109
|
+
end
|
110
|
+
|
111
|
+
def emit event, *args
|
112
|
+
if @events.key?(event)
|
113
|
+
@events[event].each do |block|
|
114
|
+
block.call(*args)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def process message
|
120
|
+
|
121
|
+
op = message["opcode"]
|
122
|
+
data = message["data"]
|
123
|
+
channel = message["channel"]
|
124
|
+
|
125
|
+
case op
|
126
|
+
when CONNECT_GRANTED
|
127
|
+
@id = data
|
128
|
+
emit "connect", @id
|
129
|
+
when CONNECT_DENIED
|
130
|
+
@id = data
|
131
|
+
emit "connect_denied", @id
|
132
|
+
when DISCONNECT
|
133
|
+
@closing = true
|
134
|
+
emit "disconnect", data
|
135
|
+
when ERROR
|
136
|
+
emit "error", data
|
137
|
+
when PUBLISH
|
138
|
+
channel = channel =~ /direct/ ? "direct" : channel
|
139
|
+
if @channels.key?(channel)
|
140
|
+
@channels[channel].process(message)
|
141
|
+
end
|
142
|
+
when SUBSCRIBE_GRANTED,
|
143
|
+
SUBSCRIBE_DENIED,
|
144
|
+
PUBLISH_GRANTED,
|
145
|
+
PUBLISH_DENIED,
|
146
|
+
UNSUBSCRIBE_COMPLETE
|
147
|
+
if @channels.key?(channel)
|
148
|
+
@channels[channel].process(message)
|
149
|
+
end
|
150
|
+
when DISCONNECT_COMPLETE
|
151
|
+
@closing = true
|
152
|
+
emit "disconnecting"
|
153
|
+
when AUTHORIZE_COMPLETE
|
154
|
+
else
|
155
|
+
#TODO: logger
|
156
|
+
puts "Unknown opcode"
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
def host
|
162
|
+
@hosts.first
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Shove
|
2
|
+
module Http
|
3
|
+
class ChannelContext
|
4
|
+
|
5
|
+
attr_accessor :app, :channel
|
6
|
+
|
7
|
+
def initialize app, channel
|
8
|
+
@app = app
|
9
|
+
@channel = channel
|
10
|
+
end
|
11
|
+
|
12
|
+
# publish a message on the channel
|
13
|
+
# +message+ the message to publish
|
14
|
+
# +block+ called on response
|
15
|
+
def publish message, &block
|
16
|
+
if @channel == "*"
|
17
|
+
raise ShoveException.new("Cannot publish to *")
|
18
|
+
elsif message.size > 8096
|
19
|
+
raise ShoveException.new("Max message size is 8,096 bytes")
|
20
|
+
end
|
21
|
+
@app.request("publish?channel=#{@channel}").post(message, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# grant subscription on the channel
|
25
|
+
# +client+ the client to grant
|
26
|
+
# +block+ called on response
|
27
|
+
def grant_subscribe client, &block
|
28
|
+
control_request("grant_subscribe", client, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
# grant publishing on the current channel
|
32
|
+
# +client+ the client to grant
|
33
|
+
# +block+ called on response
|
34
|
+
def grant_publish client, &block
|
35
|
+
control_request("grant_publish", client, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
# deny subscription on the channel
|
39
|
+
# +client+ the client to deny
|
40
|
+
# +block+ called on response
|
41
|
+
def deny_subscribe client, &block
|
42
|
+
control_request("deny_subscribe", client, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# deny publishing on the channel
|
46
|
+
# +client+ the client to deny
|
47
|
+
# +block+ called on response
|
48
|
+
def deny_publish client, &block
|
49
|
+
control_request("deny_publish", client, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def control_request action, client, &block
|
55
|
+
@app.request("#{action}?channel=#{@channel}&client=#{client}").post(&block)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Shove
|
2
|
+
module Http
|
3
|
+
class ClientContext
|
4
|
+
|
5
|
+
attr_accessor :app, :id
|
6
|
+
|
7
|
+
def initialize app, id
|
8
|
+
@app = app
|
9
|
+
@id = id
|
10
|
+
end
|
11
|
+
|
12
|
+
# publish a message directly to the client
|
13
|
+
# +message+ the message to publish
|
14
|
+
# +block+ called on response
|
15
|
+
def publish message, &block
|
16
|
+
@app.request("publish?channel=direct:#{@id}").post(message, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
# grant connection to client
|
20
|
+
# use this in cases where the app disallows any activity
|
21
|
+
# on newly established clients.
|
22
|
+
# +block+ called on response
|
23
|
+
def grant_connect &block
|
24
|
+
@app.request("grant_connect?client=#{@id}").post(&block)
|
25
|
+
end
|
26
|
+
|
27
|
+
# grant control to client
|
28
|
+
# turns the client into a full blown control client, allowing
|
29
|
+
# them to subscribe and publish to all channels, as well
|
30
|
+
# as grant and deny actions to other clients
|
31
|
+
# +block+ called on response
|
32
|
+
def grant_control &block
|
33
|
+
@app.request("grant_control?client=#{@id}").post(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
# grant connection to client
|
37
|
+
# use this to kick a client
|
38
|
+
# +block+ called on response
|
39
|
+
def deny_connect &block
|
40
|
+
@app.request("deny_connect?client=#{@id}").post(&block)
|
41
|
+
end
|
42
|
+
|
43
|
+
# grant connection to client
|
44
|
+
# use this to revoke power. Clients will never have
|
45
|
+
# control by default, so this would always be called
|
46
|
+
# after calling grant_control
|
47
|
+
# +block+ called on response
|
48
|
+
def deny_control &block
|
49
|
+
@app.request("deny_control?client=#{@id}").post(&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
# grant subscription to client
|
53
|
+
# +channel+ the channel to grant subscription on
|
54
|
+
# +block+ called on response
|
55
|
+
def grant_subscribe channel, &block
|
56
|
+
@app.request("grant_subscribe?channel=#{channel}&client=#{@id}").post(&block)
|
57
|
+
end
|
58
|
+
|
59
|
+
# grant publishing to client
|
60
|
+
# +channel+ the channel to grant publishing on
|
61
|
+
# +block+ called on response
|
62
|
+
def grant_publish channel, &block
|
63
|
+
@app.request("grant_publish?channel=#{channel}&client=#{@id}").post(&block)
|
64
|
+
end
|
65
|
+
|
66
|
+
# deny subscription to client
|
67
|
+
# +channel+ the channel to deny subscription on
|
68
|
+
# +block+ called on response
|
69
|
+
def deny_subscribe channel, &block
|
70
|
+
@app.request("deny_subscribe?channel=#{channel}&client=#{@id}").post(&block)
|
71
|
+
end
|
72
|
+
|
73
|
+
# deny publishing to client
|
74
|
+
# +channel+ the channel to deny publishing on
|
75
|
+
# +block+ called on response
|
76
|
+
def deny_publish channel, &block
|
77
|
+
@app.request("deny_publish?channel=#{channel}&client=#{@id}").post(&block)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Shove
|
2
|
+
module Http
|
3
|
+
class Request
|
4
|
+
|
5
|
+
include EventMachine::HttpEncoding
|
6
|
+
|
7
|
+
attr_accessor :url, :key, :headers
|
8
|
+
|
9
|
+
def initialize url, config
|
10
|
+
@url = url
|
11
|
+
@key = config.app_key
|
12
|
+
end
|
13
|
+
|
14
|
+
# HTTP Delete request
|
15
|
+
def delete &block
|
16
|
+
exec :delete, &block
|
17
|
+
end
|
18
|
+
|
19
|
+
# HTTP Post request for new content
|
20
|
+
def post params={}, &block
|
21
|
+
exec :post, params, &block
|
22
|
+
end
|
23
|
+
|
24
|
+
# HTTP Put request for updates
|
25
|
+
def put params={}, &block
|
26
|
+
exec :put, params, &block
|
27
|
+
end
|
28
|
+
|
29
|
+
# HTTP Get request
|
30
|
+
def get &block
|
31
|
+
exec :get, &block
|
32
|
+
end
|
33
|
+
|
34
|
+
def exec_sync method, params={}, &block
|
35
|
+
uri = URI.parse(url)
|
36
|
+
|
37
|
+
req = {
|
38
|
+
:post => Net::HTTP::Post,
|
39
|
+
:put => Net::HTTP::Put,
|
40
|
+
:get => Net::HTTP::Get,
|
41
|
+
:delete => Net::HTTP::Delete
|
42
|
+
}[method].new(uri.path + (uri.query ? "?#{uri.query}" : ""))
|
43
|
+
|
44
|
+
req.basic_auth "", key
|
45
|
+
|
46
|
+
res = Net::HTTP.start(uri.host, uri.port) { |http|
|
47
|
+
http.request(req, normalize_body(params))
|
48
|
+
}
|
49
|
+
|
50
|
+
result = Response.new(res.code, res.body, res.code.to_i >= 400)
|
51
|
+
if block_given?
|
52
|
+
block.call(result)
|
53
|
+
end
|
54
|
+
|
55
|
+
result
|
56
|
+
end
|
57
|
+
|
58
|
+
def exec_async method, params={}, &block
|
59
|
+
http = EventMachine::HttpRequest.new(url).send(method, {
|
60
|
+
:body => params,
|
61
|
+
:head => {
|
62
|
+
:authorization => ["", key]
|
63
|
+
}
|
64
|
+
})
|
65
|
+
|
66
|
+
# handle error
|
67
|
+
http.errback {
|
68
|
+
block.call(Response.new(http.response_header.status, http.response, true))
|
69
|
+
}
|
70
|
+
|
71
|
+
# handle success
|
72
|
+
http.callback {
|
73
|
+
status = http.response_header.status
|
74
|
+
block.call(Response.new(status, http.response, status >= 400))
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
# exec a HTTP request, and callback with
|
79
|
+
# a response via block
|
80
|
+
def exec method, params={}, &block
|
81
|
+
if EM.reactor_running?
|
82
|
+
exec_async(method, params, &block)
|
83
|
+
else
|
84
|
+
exec_sync(method, params, &block)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def normalize_body(body)
|
89
|
+
body.is_a?(Hash) ? form_encode_body(body) : body
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|