david 0.3.0 → 0.4.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +2 -2
  4. data/Gemfile +8 -2
  5. data/Gemfile.lock +84 -44
  6. data/README.md +87 -5
  7. data/benchmarks/Gemfile +1 -0
  8. data/benchmarks/Gemfile.lock +3 -1
  9. data/benchmarks/coapbench.sh +20 -0
  10. data/benchmarks/quick.sh +2 -0
  11. data/benchmarks/rackup/Gemfile +10 -0
  12. data/benchmarks/rackup/Gemfile.lock +159 -0
  13. data/benchmarks/rackup/grape.ru +18 -0
  14. data/benchmarks/rackup/rack.ru +7 -0
  15. data/benchmarks/rackup/rails.ru +14 -0
  16. data/benchmarks/rps.rb +14 -2
  17. data/benchmarks/stress.sh +17 -0
  18. data/david.gemspec +0 -3
  19. data/experiments/Gemfile +6 -0
  20. data/experiments/concurrency/Gemfile +3 -0
  21. data/experiments/concurrency/stub.rb +88 -0
  22. data/experiments/hash_key.rb +64 -0
  23. data/experiments/string_concat.rb +21 -0
  24. data/experiments/symbol_to_proc.rb +15 -0
  25. data/experiments/thread_safe.rb +40 -0
  26. data/lib/david.rb +8 -4
  27. data/lib/david/actor.rb +1 -11
  28. data/lib/david/app_config.rb +112 -0
  29. data/lib/david/exchange.rb +124 -0
  30. data/lib/david/fake_logger.rb +11 -0
  31. data/lib/david/garbage_collector.rb +9 -17
  32. data/lib/david/guerilla/rack/utils.rb +18 -0
  33. data/lib/david/interop.rb +4 -0
  34. data/lib/david/interop/mandatory_etsi.rb +4 -0
  35. data/lib/david/interop/mandatory_etsi/grape.rb +37 -0
  36. data/lib/david/interop/mandatory_etsi/hobbit.rb +30 -0
  37. data/lib/david/interop/mandatory_etsi/nyny.rb +36 -0
  38. data/lib/david/interop/mandatory_etsi/rack.rb +26 -0
  39. data/lib/david/interop/mandatory_etsi/sinatra.rb +36 -0
  40. data/lib/david/observe.rb +24 -29
  41. data/lib/david/rails/action_controller/base.rb +9 -7
  42. data/lib/david/railties/config.rb +4 -4
  43. data/lib/david/registry.rb +22 -0
  44. data/lib/david/server.rb +56 -56
  45. data/lib/david/server/constants.rb +1 -0
  46. data/lib/david/server/mapping.rb +43 -11
  47. data/lib/david/server/mid_cache.rb +29 -0
  48. data/lib/david/server/multicast.rb +7 -5
  49. data/lib/david/server/respond.rb +53 -43
  50. data/lib/david/server/utility.rb +2 -1
  51. data/lib/david/show_exceptions.rb +12 -8
  52. data/lib/david/transmitter.rb +44 -0
  53. data/lib/david/trap.rb +10 -0
  54. data/lib/david/version.rb +1 -1
  55. data/lib/rack/handler/david.rb +6 -10
  56. data/lib/rack/hello_world.rb +25 -0
  57. data/spec/app_config_spec.rb +56 -0
  58. data/spec/dummy/app/controllers/etsis_controller.rb +26 -0
  59. data/spec/dummy/app/controllers/tests_controller.rb +9 -0
  60. data/spec/dummy/config/application.rb +4 -6
  61. data/spec/dummy/config/environments/development.rb +2 -2
  62. data/spec/dummy/config/environments/test.rb +2 -2
  63. data/spec/dummy/config/routes.rb +13 -53
  64. data/spec/interop/mandatory_spec.rb +100 -0
  65. data/spec/observe_spec.rb +8 -7
  66. data/spec/resource_discovery_spec.rb +3 -3
  67. data/spec/server_spec.rb +60 -15
  68. data/spec/spec_helper.rb +21 -2
  69. metadata +40 -33
  70. data/lib/david/request.rb +0 -80
  71. data/lib/david/server/deduplication.rb +0 -21
  72. data/lib/david/server/options.rb +0 -79
@@ -6,7 +6,8 @@ module David
6
6
  # This can only use each on body and currently does not support streaming.
7
7
  def body_to_string(body)
8
8
  s = ''
9
- body.each { |line| s << line + "\r\n" }
9
+ body.each { |line| s << line << "\r\n" }
10
+ body.close if body.respond_to?(:close)
10
11
  s.chomp
11
12
  end
12
13
  end
@@ -22,18 +22,23 @@ module David
22
22
 
23
23
  private
24
24
 
25
- def render_exception(exception)
25
+ def render_exception(e)
26
26
  body = {
27
- error: exception.class.to_s,
28
- message: exception.message
27
+ error: e.class.to_s,
28
+ message: e.message
29
29
  }
30
30
 
31
- log(:info, [body[:error], body[:message]].join("\n"))
31
+ log.error([body[:error], body[:message]].join(': '))
32
32
 
33
33
  body = body.to_json
34
34
 
35
- code = 500
36
- code = 404 if exception.is_a?(ActiveRecord::RecordNotFound)
35
+ code = if defined?(ActiveRecord) && e.is_a?(ActiveRecord::RecordNotFound)
36
+ 404
37
+ elsif e.is_a?(ActionController::RoutingError)
38
+ 404
39
+ else
40
+ 500
41
+ end
37
42
 
38
43
  [code,
39
44
  {
@@ -44,9 +49,8 @@ module David
44
49
  ]
45
50
  end
46
51
 
47
- def log(level, message)
52
+ def log
48
53
  @logger ||= @env['rack.logger']
49
- @logger.send(level, message) if @logger
50
54
  end
51
55
  end
52
56
  end
@@ -0,0 +1,44 @@
1
+ module David
2
+ class Transmitter
3
+ include Registry
4
+
5
+ AF_INET6 = 'AF_INET6'.freeze
6
+
7
+ def initialize(socket)
8
+ @log = Celluloid.logger
9
+ @socket = socket || server.socket
10
+ end
11
+
12
+ # TODO Retransmissions
13
+ def send(exchange)
14
+ host = normalize_host(exchange.host)
15
+
16
+ @socket.send(exchange.message.to_wire, 0, host, exchange.port)
17
+
18
+ @log.info('-> ' + exchange.to_s)
19
+ @log.debug(exchange.message.inspect)
20
+ end
21
+
22
+ private
23
+
24
+ def ipv6?
25
+ @socket.addr[0] == AF_INET6
26
+ end
27
+
28
+ def normalize_host(host)
29
+ ip = IPAddr.new(host)
30
+
31
+ if ipv6? && ip.ipv4?
32
+ ip = ip.ipv4_mapped
33
+ end
34
+ rescue ArgumentError
35
+ begin
36
+ host = Resolv.getaddress(host)
37
+ retry
38
+ rescue Resolv::ResolvError
39
+ end
40
+ else
41
+ ip.to_s
42
+ end
43
+ end
44
+ end
data/lib/david/trap.rb ADDED
@@ -0,0 +1,10 @@
1
+ class Trap
2
+ def initialize(value)
3
+ @value = value
4
+ end
5
+
6
+ def method_missing(method, *args)
7
+ p method, caller[0]
8
+ @value.send(method, *args)
9
+ end
10
+ end
data/lib/david/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module David
2
2
  MAJOR = 0
3
- MINOR = 3
3
+ MINOR = 4
4
4
  PATCH = 0
5
5
  SUFFIX = nil
6
6
 
@@ -1,22 +1,18 @@
1
1
  module Rack
2
2
  module Handler
3
3
  class David
4
- DEFAULT_OPTIONS = {
5
- :Host => ENV['RACK_ENV'] == 'development' ? '::1' : '::',
6
- :Port => ::CoAP::PORT
7
- }
8
-
9
4
  def self.run(app, options={})
10
- options = DEFAULT_OPTIONS.merge(options)
11
-
12
5
  g = Celluloid::SupervisionGroup.run!
13
6
 
14
7
  g.supervise_as(:server, ::David::Server, app, options)
15
- g.supervise_as(:observe, ::David::Observe) if options[:Observe] != false
16
8
  g.supervise_as(:gc, ::David::GarbageCollector)
17
9
 
10
+ unless options[:Observe] == 'false'
11
+ g.supervise_as(:observe, ::David::Observe)
12
+ end
13
+
18
14
  begin
19
- sleep
15
+ Celluloid::Actor[:server].run
20
16
  rescue Interrupt
21
17
  Celluloid.logger.info 'Terminated'
22
18
  Celluloid.logger = nil
@@ -25,7 +21,7 @@ module Rack
25
21
  end
26
22
 
27
23
  def self.valid_options
28
- host, port = DEFAULT_OPTIONS.values_at(:Host, :Port)
24
+ host, port = AppConfig::DEFAULT_OPTIONS.values_at(:Host, :Port)
29
25
 
30
26
  {
31
27
  'Block=BOOLEAN' => 'Support for blockwise transfer (default: true)',
@@ -60,6 +60,31 @@ module Rack
60
60
  {'Content-Type' => 'text/plain'},
61
61
  [Time.now.to_s]
62
62
  ]
63
+ when '/cbor'
64
+ require 'json'
65
+
66
+ body = JSON.parse(env['rack.input'].read).to_s +
67
+ env['coap.cbor'].to_s
68
+
69
+ [200,
70
+ {
71
+ 'Content-Type' => 'text/plain',
72
+ 'Content-Length' => body.bytesize.to_s
73
+ },
74
+ [body]
75
+ ]
76
+ when '/json'
77
+ require 'json'
78
+
79
+ body = {'Hello' => 'World!'}.to_json
80
+
81
+ [200,
82
+ {
83
+ 'Content-Type' => 'application/json',
84
+ 'Content-Length' => body.bytesize.to_s
85
+ },
86
+ [body]
87
+ ]
63
88
  else
64
89
  [404, {}, ['']]
65
90
  end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe AppConfig do
4
+ let(:port) { random_port }
5
+
6
+ let!(:server) { supervised_server(:Port => port, :CBOR => true) }
7
+
8
+ it { expect(subject).to be_a(Hash) }
9
+
10
+ describe '#choose_host' do
11
+ let(:method) do
12
+ ->(*args) { subject.send(:choose_host, *args) }
13
+ end
14
+
15
+ it { expect(method.call(nil)).to eq(nil) }
16
+ it { expect(method.call('::')).to eq('::') }
17
+ it { expect(method.call('::1')).to eq('::1') }
18
+ it { expect(method.call('localhost')).to eq('::1') }
19
+ end
20
+
21
+ describe '#choose_port' do
22
+ let(:method) do
23
+ ->(*args) { subject.send(:choose_port, *args) }
24
+ end
25
+
26
+ it { expect(method.call(nil)).to eq(nil) }
27
+ it { expect(method.call('1')).to be_a(Fixnum) }
28
+ it { expect(method.call('1')).to eq(1) }
29
+ end
30
+
31
+ describe '#default_to_true' do
32
+ let(:method) do
33
+ ->(*args) { subject.send(:default_to_true, *args) }
34
+ end
35
+
36
+ it { expect(method.call(:block, nil)).to eq(true) }
37
+ it { expect(method.call(:block, true)).to eq(true) }
38
+ it { expect(method.call(:block, 'true')).to eq(true) }
39
+
40
+ it { expect(method.call(:block, false)).to eq(false) }
41
+ it { expect(method.call(:block, 'false')).to eq(false) }
42
+ end
43
+
44
+ describe '#default_to_false' do
45
+ let(:method) do
46
+ ->(*args) { subject.send(:default_to_false, *args) }
47
+ end
48
+
49
+ it { expect(method.call(:cbor, nil)).to eq(false) }
50
+ it { expect(method.call(:cbor, false)).to eq(false) }
51
+ it { expect(method.call(:cbor, 'false')).to eq(false) }
52
+
53
+ it { expect(method.call(:cbor, true)).to eq(true) }
54
+ it { expect(method.call(:cbor, 'true')).to eq(true) }
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ class EtsisController < ActionController::Base
2
+ def show
3
+ headers['Content-Type'] = 'text/plain'
4
+ head 2.05
5
+ end
6
+
7
+ def update
8
+ head 2.01
9
+ end
10
+
11
+ def create
12
+ head 2.04
13
+ end
14
+
15
+ def destroy
16
+ head 2.02
17
+ end
18
+
19
+ def seg
20
+ show
21
+ end
22
+
23
+ def query
24
+ show
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ class TestsController < ActionController::Base
2
+ def benchmark
3
+ render text: 'Hello World!'
4
+ end
5
+
6
+ def cbor
7
+ render text: params['test'].to_s
8
+ end
9
+ end
@@ -1,11 +1,11 @@
1
1
  require File.expand_path('../boot', __FILE__)
2
2
 
3
3
  # Pick the frameworks you want:
4
- require "active_record/railtie"
4
+ # require "active_record/railtie"
5
5
  require "action_controller/railtie"
6
- require "action_mailer/railtie"
7
- require "action_view/railtie"
8
- require "sprockets/railtie"
6
+ # require "action_mailer/railtie"
7
+ # require "action_view/railtie"
8
+ # require "sprockets/railtie"
9
9
 
10
10
  Bundler.require(*Rails.groups)
11
11
 
@@ -22,8 +22,6 @@ module Dummy
22
22
  # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
23
23
  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
24
24
  # config.i18n.default_locale = :de
25
-
26
- config.coap.cbor = false
27
25
  end
28
26
  end
29
27
 
@@ -14,13 +14,13 @@ Rails.application.configure do
14
14
  config.action_controller.perform_caching = false
15
15
 
16
16
  # Don't care if the mailer can't send.
17
- config.action_mailer.raise_delivery_errors = false
17
+ # config.action_mailer.raise_delivery_errors = false
18
18
 
19
19
  # Print deprecation notices to the Rails logger.
20
20
  config.active_support.deprecation = :log
21
21
 
22
22
  # Raise an error on page load if there are pending migrations.
23
- config.active_record.migration_error = :page_load
23
+ # config.active_record.migration_error = :page_load
24
24
 
25
25
  # Debug mode disables concatenation and preprocessing of assets.
26
26
  # This option may cause significant delays in view rendering with a large
@@ -21,7 +21,7 @@ Rails.application.configure do
21
21
  config.action_controller.perform_caching = false
22
22
 
23
23
  # Raise exceptions instead of rendering exception templates.
24
- config.action_dispatch.show_exceptions = false
24
+ config.action_dispatch.show_exceptions = true
25
25
 
26
26
  # Disable request forgery protection in test environment.
27
27
  config.action_controller.allow_forgery_protection = false
@@ -29,7 +29,7 @@ Rails.application.configure do
29
29
  # Tell Action Mailer not to deliver emails to the real world.
30
30
  # The :test delivery method accumulates sent emails in the
31
31
  # ActionMailer::Base.deliveries array.
32
- config.action_mailer.delivery_method = :test
32
+ # config.action_mailer.delivery_method = :test
33
33
 
34
34
  # Print deprecation notices to the stderr.
35
35
  config.active_support.deprecation = :stderr
@@ -1,58 +1,18 @@
1
1
  Rails.application.routes.draw do
2
- # The priority is based upon order of creation: first created -> highest priority.
3
- # See how all your routes lay out with "rake routes".
4
-
5
- # You can have the root of your site routed with "root"
6
- # root 'welcome#index'
7
-
8
- # Example of regular route:
9
- # get 'products/:id' => 'catalog#view'
10
-
11
- # Example of named route that can be invoked with purchase_url(id: product.id)
12
- # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
13
-
14
- # Example resource route (maps HTTP verbs to controller actions automatically):
15
- # resources :products
16
-
17
- # Example resource route with options:
18
- # resources :products do
19
- # member do
20
- # get 'short'
21
- # post 'toggle'
22
- # end
23
- #
24
- # collection do
25
- # get 'sold'
26
- # end
27
- # end
28
-
29
- # Example resource route with sub-resources:
30
- # resources :products do
31
- # resources :comments, :sales
32
- # resource :seller
33
- # end
34
-
35
- # Example resource route with more complex sub-resources:
36
- # resources :products do
37
- # resources :comments
38
- # resources :sales do
39
- # get 'recent', on: :collection
40
- # end
41
- # end
2
+ # For testing resource discovery middleware
3
+ resources :things
42
4
 
43
- # Example resource route with concerns:
44
- # concern :toggleable do
45
- # post 'toggle'
46
- # end
47
- # resources :posts, concerns: :toggleable
48
- # resources :photos, concerns: :toggleable
5
+ # Requests per second benchmark
6
+ get 'hello' => 'tests#benchmark'
49
7
 
50
- # Example resource route within a namespace:
51
- # namespace :admin do
52
- # # Directs /admin/products/* to Admin::ProductsController
53
- # # (app/controllers/admin/products_controller.rb)
54
- # resources :products
55
- # end
8
+ # CBOR transcoding tests
9
+ get 'cbor' => 'tests#cbor'
56
10
 
57
- resources :things
11
+ # ETSI Plugtests
12
+ get 'test' => 'etsis#show'
13
+ post 'test' => 'etsis#update'
14
+ put 'test' => 'etsis#create'
15
+ delete 'test' => 'etsis#destroy'
16
+ get 'seg1/seg2/seg3' => 'etsis#seg'
17
+ get 'query' => 'etsis#query'
58
18
  end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ [
4
+ Interop::MandatoryETSI::Grape,
5
+ Interop::MandatoryETSI::Hobbit,
6
+ Interop::MandatoryETSI::NYNY,
7
+ Interop::MandatoryETSI::Rack,
8
+ Interop::MandatoryETSI::Sinatra,
9
+ Rails.application
10
+ ].each do |app|
11
+ describe "ETSI Plugstests, Mandatory, #{app.to_s.split('::').last}" do
12
+ let!(:server) { supervised_server(:MinimalMapping => true, app: app) }
13
+
14
+ [:con, :non].each do |tt|
15
+ context tt do
16
+ it 'TD_COAP_CORE_0{1,5}' do
17
+ mid, response = req(:get, '/test', tt: tt)
18
+
19
+ expect(response).to be_a(CoAP::Message)
20
+ expect(response.mcode).to eq([2, 5])
21
+ expect(response.mid).to eq(mid)
22
+ expect(response.options[:content_format]).to eq(0)
23
+ end
24
+
25
+ it 'TD_COAP_CORE_0{2,6}' do
26
+ mid, response = req(:post, '/test', tt: tt, payload: 'foo',
27
+ content_format: 0)
28
+
29
+ expect(response).to be_a(CoAP::Message)
30
+ expect(response.mcode).to eq([2, 1])
31
+ expect(response.mid).to eq(mid)
32
+ end
33
+
34
+ it 'TD_COAP_CORE_0{3,7}' do
35
+ mid, response = req(:put, '/test', tt: tt, payload: 'foo',
36
+ content_format: 0)
37
+
38
+ expect(response).to be_a(CoAP::Message)
39
+ expect(response.mcode).to eq([2, 4])
40
+ expect(response.mid).to eq(mid)
41
+ end
42
+
43
+ it 'TD_COAP_CORE_0{4,8}' do
44
+ mid, response = req(:delete, '/test', tt: tt)
45
+
46
+ expect(response).to be_a(CoAP::Message)
47
+ expect(response.mcode).to eq([2, 2])
48
+ expect(response.mid).to eq(mid)
49
+ end
50
+ end
51
+ end
52
+
53
+ context 'TD_COAP_CORE_09' do
54
+ pending
55
+ end
56
+
57
+ it 'TD_COAP_CORE_10' do
58
+ token = rand(0xffffffff)
59
+ mid, response = req(:get, '/test', token: token)
60
+
61
+ expect(response).to be_a(CoAP::Message)
62
+ expect(response.mcode).to eq([2, 5])
63
+ expect(response.mid).to eq(mid)
64
+ expect(response.options[:content_format]).to eq(0)
65
+ expect(response.options[:token]).to eq(token)
66
+ end
67
+
68
+ it 'TD_COAP_CORE_11' do
69
+ mid, response = req(:get, '/test')
70
+
71
+ expect(response).to be_a(CoAP::Message)
72
+ expect(response.mcode).to eq([2, 5])
73
+ expect(response.mid).to eq(mid)
74
+ expect(response.options[:content_format]).to eq(0)
75
+ expect(response.options[:token]).to eq(0)
76
+ end
77
+
78
+ it 'TD_COAP_CORE_12' do
79
+ mid, response = req(:get, '/seg1/seg2/seg3')
80
+
81
+ expect(response).to be_a(CoAP::Message)
82
+ expect(response.mcode).to eq([2, 5])
83
+ expect(response.mid).to eq(mid)
84
+ expect(response.options[:content_format]).to eq(0)
85
+ end
86
+
87
+ it 'TD_COAP_CORE_13' do
88
+ mid, response = req(:get, '/query', uri_query: ['foo=1', 'bar=2'])
89
+
90
+ expect(response).to be_a(CoAP::Message)
91
+ expect(response.mcode).to eq([2, 5])
92
+ expect(response.mid).to eq(mid)
93
+ expect(response.options[:content_format]).to eq(0)
94
+ end
95
+
96
+ after do
97
+ server.terminate
98
+ end
99
+ end
100
+ end