david 0.4.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +10 -6
- data/Gemfile +7 -3
- data/Gemfile.lock +180 -145
- data/README.md +17 -5
- data/david.gemspec +5 -5
- data/lib/david.rb +2 -1
- data/lib/david/etsi/mandatory/roda.rb +36 -0
- data/lib/david/observe.rb +21 -14
- data/lib/david/railties/middleware.rb +1 -0
- data/lib/david/registry.rb +1 -1
- data/lib/david/resource_discovery_proxy.rb +1 -1
- data/lib/david/server/multicast.rb +1 -1
- data/lib/david/server/utility.rb +2 -1
- data/lib/david/version.rb +2 -2
- data/lib/rack/handler/david.rb +4 -4
- data/spec/app_config_spec.rb +2 -1
- data/spec/dummy/app/controllers/etsis_controller.rb +1 -2
- data/spec/dummy/app/controllers/tests_controller.rb +2 -2
- data/spec/interop/mandatory_spec.rb +1 -0
- data/spec/interop/optional_spec.rb +1 -1
- data/spec/mapping_spec.rb +1 -1
- data/spec/observe_spec.rb +15 -15
- data/spec/perf/server_perf_spec.rb +1 -1
- data/spec/resource_discovery_spec.rb +3 -3
- data/spec/server_spec.rb +16 -16
- data/spec/spec_helper.rb +17 -6
- metadata +18 -31
- data/spec/dummy/config/initializers/assets.rb +0 -8
data/README.md
CHANGED
@@ -7,11 +7,10 @@
|
|
7
7
|
[![Code Climate](https://img.shields.io/codeclimate/github/nning/david.svg)](https://codeclimate.com/github/nning/david)
|
8
8
|
|
9
9
|
David is a CoAP server with Rack interface to bring the illustrious family of
|
10
|
-
Rack compatible web frameworks into the Internet of Things.
|
11
|
-
|
12
|
-
tested with MRI >= 1.9, JRuby, and Rubinius.
|
10
|
+
Rack compatible web frameworks into the Internet of Things. It is tested with
|
11
|
+
MRI >= 1.9, JRuby, and Rubinius.
|
13
12
|
|
14
|
-
##
|
13
|
+
## Quick Start
|
15
14
|
|
16
15
|
Just include David in your Gemfile!
|
17
16
|
|
@@ -21,8 +20,21 @@ It will hook into Rack and make itself the default handler, so running `rails
|
|
21
20
|
s` starts David. If you want to start WEBrick for example, you can do so by
|
22
21
|
executing `rails s webrick`.
|
23
22
|
|
23
|
+
**For now, you have to remove the `web-console` gem from the Gemfile (which is
|
24
|
+
HTTP specific anyway) if you use Rails/David in CoAP only mode.** You probably
|
25
|
+
also want to disable [CSRF
|
26
|
+
protection](http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html)
|
27
|
+
by removing the `protect_from_forgery` line from
|
28
|
+
`app/controllers/application_controller.rb` (or use `:null_session` if you know
|
29
|
+
what you are doing).
|
30
|
+
|
31
|
+
The [`coap-rails-dummy`](https://github.com/nning/coap-rails-dummy) repository
|
32
|
+
documents [changes to a newly generated Ruby on Rails application for a quick
|
33
|
+
start](https://github.com/nning/coap-rails-dummy/compare/initial...master).
|
34
|
+
|
24
35
|
After the server is started, the Rails application is available at
|
25
|
-
`coap://[::1]:3000/` by default.
|
36
|
+
`coap://[::1]:3000/` by default. (Although you have to set a route for `/` in
|
37
|
+
`config/routes.rb`, of course.)
|
26
38
|
|
27
39
|
[Copper](https://addons.mozilla.org/de/firefox/addon/copper-270430/) is a CoAP
|
28
40
|
client for Firefox and can be used for development. The [Ruby coap
|
data/david.gemspec
CHANGED
@@ -20,11 +20,11 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
21
21
|
s.require_paths = ['lib']
|
22
22
|
|
23
|
-
s.add_runtime_dependency 'celluloid', '
|
24
|
-
s.add_runtime_dependency 'celluloid-io', '
|
25
|
-
s.add_runtime_dependency 'coap', '
|
26
|
-
s.add_runtime_dependency 'rack', '~>
|
23
|
+
s.add_runtime_dependency 'celluloid', '~> 0.17.3'
|
24
|
+
s.add_runtime_dependency 'celluloid-io', '~> 0.17.3'
|
25
|
+
s.add_runtime_dependency 'coap', '~> 0.1'
|
26
|
+
s.add_runtime_dependency 'rack', '~> 2.0'
|
27
27
|
|
28
|
-
s.add_development_dependency 'rake'
|
28
|
+
s.add_development_dependency 'rake', '~> 0'
|
29
29
|
s.add_development_dependency 'rspec', '~> 3.2'
|
30
30
|
end
|
data/lib/david.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
module David::ETSI::Mandatory
|
2
|
+
class Roda < ::Roda
|
3
|
+
plugin :all_verbs
|
4
|
+
plugin :default_headers, 'Content-Type' => 'text/plain'
|
5
|
+
|
6
|
+
route do |r|
|
7
|
+
r.get 'test' do
|
8
|
+
response.status = 2.05
|
9
|
+
end
|
10
|
+
|
11
|
+
r.post :test do
|
12
|
+
response.status = 2.01
|
13
|
+
end
|
14
|
+
|
15
|
+
r.put :test do
|
16
|
+
response.status = 2.04
|
17
|
+
end
|
18
|
+
|
19
|
+
r.delete :test do
|
20
|
+
response.status = 2.02
|
21
|
+
end
|
22
|
+
|
23
|
+
r.on 'seg1' do
|
24
|
+
r.on 'seg2' do
|
25
|
+
r.get 'seg3' do
|
26
|
+
response.status = 2.05
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
r.get 'query' do
|
32
|
+
response.status = 2.05
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/david/observe.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
module David
|
2
|
-
class Observe
|
2
|
+
class Observe
|
3
|
+
extend Forwardable
|
4
|
+
|
3
5
|
include Actor
|
4
6
|
|
5
|
-
|
6
|
-
alias_method :_include?, :include?
|
7
|
+
def_delegators :@store, :first, :keys, :size
|
7
8
|
|
8
9
|
def initialize(tick_interval = 3)
|
9
10
|
@tick_interval = tick_interval
|
11
|
+
@store = {}
|
12
|
+
|
10
13
|
async.run
|
11
14
|
|
12
15
|
log.debug('Observe initialized')
|
@@ -15,34 +18,38 @@ module David
|
|
15
18
|
def add(exchange, env, etag)
|
16
19
|
exchange.message.options.delete(:observe)
|
17
20
|
|
18
|
-
|
21
|
+
@store[[exchange.host, exchange.token]] ||=
|
19
22
|
[0, exchange, env, etag, Time.now.to_i]
|
20
23
|
end
|
21
24
|
|
22
25
|
def delete(exchange)
|
23
|
-
|
26
|
+
@store.delete([exchange.host, exchange.token])
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(key)
|
30
|
+
@store[key]
|
24
31
|
end
|
25
32
|
|
26
33
|
def include?(exchange)
|
27
|
-
|
34
|
+
@store.include?([exchange.host, exchange.token])
|
28
35
|
end
|
29
36
|
|
30
37
|
def to_s
|
31
|
-
|
38
|
+
@store.map { |k, v| [*k, v[2]['PATH_INFO'], v[0]].inspect }.join(', ')
|
32
39
|
end
|
33
40
|
|
34
41
|
private
|
35
42
|
|
36
43
|
def bump(key, n, response)
|
37
|
-
|
38
|
-
|
39
|
-
|
44
|
+
@store[key][0] = n
|
45
|
+
@store[key][3] = response.options[:etag]
|
46
|
+
@store[key][4] = Time.now.to_i
|
40
47
|
end
|
41
48
|
|
42
49
|
# TODO If ETag did not change but max-age of last notification is expired,
|
43
50
|
# return empty 2.03.
|
44
51
|
def handle_update(key)
|
45
|
-
n, exchange, env, etag =
|
52
|
+
n, exchange, env, etag = @store[key]
|
46
53
|
n += 1
|
47
54
|
|
48
55
|
response, options = server.respond(exchange, env)
|
@@ -86,12 +93,12 @@ module David
|
|
86
93
|
end
|
87
94
|
|
88
95
|
def tick(fiber = true)
|
89
|
-
unless
|
96
|
+
unless @store.empty?
|
90
97
|
log.debug('Observe tick')
|
91
|
-
log.debug(
|
98
|
+
log.debug(@store)
|
92
99
|
end
|
93
100
|
|
94
|
-
|
101
|
+
@store.each_key do |key|
|
95
102
|
if fiber
|
96
103
|
async.handle_update(key)
|
97
104
|
else
|
data/lib/david/registry.rb
CHANGED
@@ -17,7 +17,7 @@ module David
|
|
17
17
|
|
18
18
|
def observe
|
19
19
|
# Supervision is only initialized from here in tests.
|
20
|
-
Observe.
|
20
|
+
Observe.supervise(as: :observe) if Celluloid::Actor[:observe].nil?
|
21
21
|
Celluloid::Actor[:observe]
|
22
22
|
end
|
23
23
|
|
@@ -36,7 +36,7 @@ module David
|
|
36
36
|
|
37
37
|
# http://lists.apple.com/archives/darwin-kernel/2014/Mar/msg00012.html
|
38
38
|
if OS.osx?
|
39
|
-
ifname =
|
39
|
+
ifname = `route get default`.match(/interface: ([a-z0-9]*)$/)[1]
|
40
40
|
ifindex = Socket.if_nametoindex(ifname)
|
41
41
|
end
|
42
42
|
|
data/lib/david/server/utility.rb
CHANGED
@@ -5,8 +5,9 @@ module David
|
|
5
5
|
|
6
6
|
# This can only use each on body and currently does not support streaming.
|
7
7
|
def body_to_string(body)
|
8
|
+
p body
|
8
9
|
s = ''
|
9
|
-
body.each { |line| s << line << "\r\n" }
|
10
|
+
body.each { |line| s << line.to_s << "\r\n" }
|
10
11
|
body.close if body.respond_to?(:close)
|
11
12
|
s.chomp
|
12
13
|
end
|
data/lib/david/version.rb
CHANGED
data/lib/rack/handler/david.rb
CHANGED
@@ -2,13 +2,13 @@ module Rack
|
|
2
2
|
module Handler
|
3
3
|
class David
|
4
4
|
def self.run(app, options={})
|
5
|
-
g = Celluloid::
|
5
|
+
g = Celluloid::Supervision::Container.run!
|
6
6
|
|
7
|
-
g.
|
8
|
-
g.
|
7
|
+
g.supervise(as: :server, type: ::David::Server, args: [app, options])
|
8
|
+
g.supervise(as: :gc, type: ::David::GarbageCollector)
|
9
9
|
|
10
10
|
unless options[:Observe] == 'false'
|
11
|
-
g.
|
11
|
+
g.supervise(as: :observe, type: ::David::Observe)
|
12
12
|
end
|
13
13
|
|
14
14
|
begin
|
data/spec/app_config_spec.rb
CHANGED
@@ -15,7 +15,8 @@ describe AppConfig do
|
|
15
15
|
it { expect(method.call(nil)).to eq(nil) }
|
16
16
|
it { expect(method.call('::')).to eq('::') }
|
17
17
|
it { expect(method.call('::1')).to eq('::1') }
|
18
|
-
it { expect(method.call('
|
18
|
+
it { expect(method.call('127.0.0.1')).to eq('127.0.0.1') }
|
19
|
+
it { expect(%w[::1 127.0.0.1]).to include(method.call('localhost')) }
|
19
20
|
end
|
20
21
|
|
21
22
|
describe '#choose_port' do
|
data/spec/mapping_spec.rb
CHANGED
data/spec/observe_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
Celluloid.logger = ENV['DEBUG'].nil? ? nil : Logger.new($stdout)
|
4
4
|
|
5
5
|
describe Observe do
|
6
|
-
let!(:observe) { Observe.
|
6
|
+
let!(:observe) { Observe.supervise(as: :observe) }
|
7
7
|
|
8
8
|
# TODO Replace this with factory.
|
9
9
|
before do
|
@@ -33,7 +33,7 @@ describe Observe do
|
|
33
33
|
let!(:add) { subject.add(*dummy1) }
|
34
34
|
|
35
35
|
let!(:key) { [dummy1[0].host, dummy1[0].token] }
|
36
|
-
let!(:value) { subject
|
36
|
+
let!(:value) { subject.get(key) }
|
37
37
|
|
38
38
|
it '#to_s' do
|
39
39
|
s = '["127.0.0.1", ' + dummy1[0].token.to_s + ', "/", 0]'
|
@@ -131,16 +131,16 @@ describe Observe do
|
|
131
131
|
it 'shall change entry' do
|
132
132
|
subject.send(:bump, key, n, response)
|
133
133
|
|
134
|
-
expect(subject
|
135
|
-
expect(subject
|
136
|
-
expect(subject
|
134
|
+
expect(subject.get(key)[0]).to eq(n)
|
135
|
+
expect(subject.get(key)[3]).to eq(response.options[:etag])
|
136
|
+
expect(subject.get(key)[4]).to be <= Time.now.to_i
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
140
140
|
describe '#handle_update' do
|
141
141
|
let(:port) { random_port }
|
142
142
|
|
143
|
-
let!(:server) { supervised_server(:Port => port) }
|
143
|
+
let!(:server) { supervised_server(:Host => '0.0.0.0', :Port => port) }
|
144
144
|
|
145
145
|
context 'error (4.04)' do
|
146
146
|
let!(:key) { [dummy1[0].host, dummy1[0].token] }
|
@@ -152,7 +152,7 @@ describe Observe do
|
|
152
152
|
end
|
153
153
|
|
154
154
|
it 'delete' do
|
155
|
-
expect(subject
|
155
|
+
expect(subject.get(key)).to eq(nil)
|
156
156
|
end
|
157
157
|
end
|
158
158
|
|
@@ -166,9 +166,9 @@ describe Observe do
|
|
166
166
|
end
|
167
167
|
|
168
168
|
it 'bumped' do
|
169
|
-
expect(subject
|
170
|
-
expect(subject
|
171
|
-
expect(subject
|
169
|
+
expect(subject.get(key)[0]).to eq(1)
|
170
|
+
expect(subject.get(key)[3]).to eq(dummy2[0].message.options[:etag])
|
171
|
+
expect(subject.get(key)[4]).to be <= Time.now.to_i
|
172
172
|
end
|
173
173
|
end
|
174
174
|
end
|
@@ -176,7 +176,7 @@ describe Observe do
|
|
176
176
|
describe '#tick' do
|
177
177
|
let(:port) { random_port }
|
178
178
|
|
179
|
-
let!(:server) { supervised_server(:Port => port) }
|
179
|
+
let!(:server) { supervised_server(:Host => '0.0.0.0', :Port => port) }
|
180
180
|
|
181
181
|
context 'update (2.05)' do
|
182
182
|
let!(:key) { [dummy2[0].host, dummy2[0].token] }
|
@@ -188,9 +188,9 @@ describe Observe do
|
|
188
188
|
end
|
189
189
|
|
190
190
|
it 'bumped' do
|
191
|
-
expect(subject
|
192
|
-
expect(subject
|
193
|
-
expect(subject
|
191
|
+
expect(subject.get(key)[0]).to eq(1)
|
192
|
+
expect(subject.get(key)[3]).to eq(dummy2[0].message.options[:etag])
|
193
|
+
expect(subject.get(key)[4]).to be <= Time.now.to_i
|
194
194
|
end
|
195
195
|
end
|
196
196
|
end
|
@@ -208,7 +208,7 @@ describe Observe do
|
|
208
208
|
|
209
209
|
@t1 = Thread.start do
|
210
210
|
client.observe \
|
211
|
-
'/value',
|
211
|
+
'/value', localhost, nil,
|
212
212
|
->(s, m) { @answers << m }
|
213
213
|
end
|
214
214
|
|
@@ -16,7 +16,7 @@ describe Server, 'performance', performance: true do
|
|
16
16
|
|
17
17
|
# Stolen from thin.
|
18
18
|
it "should handle GET request in less than #{max1 = 0.0045} seconds" do
|
19
|
-
expect(Benchmark.realtime { client.get('/',
|
19
|
+
expect(Benchmark.realtime { client.get('/', localhost) }).to be < max1
|
20
20
|
end
|
21
21
|
|
22
22
|
after do
|
@@ -12,7 +12,7 @@ describe David::ResourceDiscovery do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
context 'ordinary request' do
|
15
|
-
subject { client.get('/.well-known/core',
|
15
|
+
subject { client.get('/.well-known/core', localhost) }
|
16
16
|
|
17
17
|
context 'response' do
|
18
18
|
let(:links) { CoRE::Link.parse_multiple(subject.payload) }
|
@@ -37,7 +37,7 @@ describe David::ResourceDiscovery do
|
|
37
37
|
|
38
38
|
context 'filtered request' do
|
39
39
|
context 'match' do
|
40
|
-
subject { client.get('/.well-known/core?href=new',
|
40
|
+
subject { client.get('/.well-known/core?href=new', localhost) }
|
41
41
|
|
42
42
|
context 'response' do
|
43
43
|
let(:links) { CoRE::Link.parse_multiple(subject.payload) }
|
@@ -50,7 +50,7 @@ describe David::ResourceDiscovery do
|
|
50
50
|
end
|
51
51
|
|
52
52
|
context 'no match' do
|
53
|
-
subject { client.get('/.well-known/core?href=foo',
|
53
|
+
subject { client.get('/.well-known/core?href=foo', localhost) }
|
54
54
|
|
55
55
|
context 'response' do
|
56
56
|
let(:links) { CoRE::Link.parse_multiple(subject.payload) }
|