david 0.4.5 → 0.5.0

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/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. **Currently, it is
11
- in a development state and probably not ready for use in production.** It is
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
- ## Usage
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
@@ -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', '>= 0.16.0', '< 0.17'
24
- s.add_runtime_dependency 'celluloid-io', '>= 0.16.1', '< 0.17'
25
- s.add_runtime_dependency 'coap', '>= 0.1'
26
- s.add_runtime_dependency 'rack', '~> 1.6'
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
@@ -13,9 +13,10 @@ unless defined?(JRuby)
13
13
  end
14
14
  end
15
15
 
16
- require 'celluloid'
16
+ require 'celluloid/current'
17
17
  require 'celluloid/io'
18
18
  require 'coap'
19
+ require 'forwardable'
19
20
  require 'ipaddr'
20
21
  require 'rack'
21
22
 
@@ -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
@@ -1,12 +1,15 @@
1
1
  module David
2
- class Observe < Hash
2
+ class Observe
3
+ extend Forwardable
4
+
3
5
  include Actor
4
6
 
5
- alias_method :_delete, :delete
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
- self[[exchange.host, exchange.token]] ||=
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
- _delete([exchange.host, exchange.token])
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
- _include?([exchange.host, exchange.token])
34
+ @store.include?([exchange.host, exchange.token])
28
35
  end
29
36
 
30
37
  def to_s
31
- self.map { |k, v| [*k, v[2]['PATH_INFO'], v[0]].inspect }.join(', ')
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
- self[key][0] = n
38
- self[key][3] = response.options[:etag]
39
- self[key][4] = Time.now.to_i
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 = self[key]
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 self.empty?
96
+ unless @store.empty?
90
97
  log.debug('Observe tick')
91
- log.debug(self)
98
+ log.debug(@store)
92
99
  end
93
100
 
94
- self.each_key do |key|
101
+ @store.each_key do |key|
95
102
  if fiber
96
103
  async.handle_update(key)
97
104
  else
@@ -7,6 +7,7 @@ module David
7
7
  UNWANTED = [
8
8
  ActionDispatch::Cookies,
9
9
  ActionDispatch::DebugExceptions,
10
+ ActionDispatch::Executor,
10
11
  ActionDispatch::Flash,
11
12
  ActionDispatch::RemoteIp,
12
13
  ActionDispatch::RequestId,
@@ -17,7 +17,7 @@ module David
17
17
 
18
18
  def observe
19
19
  # Supervision is only initialized from here in tests.
20
- Observe.supervise_as(:observe) if Celluloid::Actor[:observe].nil?
20
+ Observe.supervise(as: :observe) if Celluloid::Actor[:observe].nil?
21
21
  Celluloid::Actor[:observe]
22
22
  end
23
23
 
@@ -3,7 +3,7 @@ require 'david/resource_discovery'
3
3
  module David
4
4
  class ResourceDiscoveryProxy
5
5
  def initialize(app)
6
- ResourceDiscovery.supervise_as(:discovery, app)
6
+ ResourceDiscovery.supervise(as: :discovery, args: [app])
7
7
  end
8
8
 
9
9
  def call(env)
@@ -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 = Socket.if_up?('en1') ? 'en1' : 'en0'
39
+ ifname = `route get default`.match(/interface: ([a-z0-9]*)$/)[1]
40
40
  ifindex = Socket.if_nametoindex(ifname)
41
41
  end
42
42
 
@@ -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
@@ -1,7 +1,7 @@
1
1
  module David
2
2
  MAJOR = 0
3
- MINOR = 4
4
- PATCH = 5
3
+ MINOR = 5
4
+ PATCH = 0
5
5
  SUFFIX = nil
6
6
 
7
7
  VERSION = [MAJOR, MINOR, PATCH, SUFFIX].compact.join('.')
@@ -2,13 +2,13 @@ module Rack
2
2
  module Handler
3
3
  class David
4
4
  def self.run(app, options={})
5
- g = Celluloid::SupervisionGroup.run!
5
+ g = Celluloid::Supervision::Container.run!
6
6
 
7
- g.supervise_as(:server, ::David::Server, app, options)
8
- g.supervise_as(:gc, ::David::GarbageCollector)
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.supervise_as(:observe, ::David::Observe)
11
+ g.supervise(as: :observe, type: ::David::Observe)
12
12
  end
13
13
 
14
14
  begin
@@ -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('localhost')).to eq('::1') }
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
@@ -1,7 +1,6 @@
1
1
  class EtsisController < ActionController::Base
2
2
  def show
3
- headers['Content-Type'] = 'text/plain'
4
- head 2.05
3
+ head 2.05, content_type: :text
5
4
  end
6
5
 
7
6
  def update
@@ -1,9 +1,9 @@
1
1
  class TestsController < ActionController::Base
2
2
  def benchmark
3
- render text: 'Hello World!'
3
+ render plain: 'Hello World!'
4
4
  end
5
5
 
6
6
  def cbor
7
- render text: params['test'].to_s
7
+ render plain: params['test'].to_json
8
8
  end
9
9
  end
@@ -5,6 +5,7 @@ require 'spec_helper'
5
5
  ETSI::Mandatory::Hobbit,
6
6
  ETSI::Mandatory::NYNY,
7
7
  ETSI::Mandatory::Rack,
8
+ ETSI::Mandatory::Roda.freeze.app,
8
9
  ETSI::Mandatory::Sinatra,
9
10
  Rails.application
10
11
  ].each do |app|
@@ -83,7 +83,7 @@ require 'spec_helper'
83
83
 
84
84
  @t1 = Thread.start do
85
85
  CoAP::Client.new.observe \
86
- '/obs', '::1', port,
86
+ '/obs', localhost, port,
87
87
  ->(s, m) { @answers << m }
88
88
  end
89
89
 
@@ -42,7 +42,7 @@ describe Server::Mapping do
42
42
 
43
43
  let!(:server) { supervised_server(:Port => port) }
44
44
 
45
- subject { client.get('/code', '::1') }
45
+ subject { client.get('/code', localhost) }
46
46
 
47
47
  it 'should be 2.05' do
48
48
  expect(subject).to be_a(CoAP::Message)
@@ -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.supervise_as(: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[key] }
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[key][0]).to eq(n)
135
- expect(subject[key][3]).to eq(response.options[:etag])
136
- expect(subject[key][4]).to be <= Time.now.to_i
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[key]).to eq(nil)
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[key][0]).to eq(1)
170
- expect(subject[key][3]).to eq(dummy2[0].message.options[:etag])
171
- expect(subject[key][4]).to be <= Time.now.to_i
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[key][0]).to eq(1)
192
- expect(subject[key][3]).to eq(dummy2[0].message.options[:etag])
193
- expect(subject[key][4]).to be <= Time.now.to_i
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', '::1', nil,
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('/', '::1') }).to be < max1
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', '::1') }
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', '::1') }
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', '::1') }
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) }