david 0.4.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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) }