david 0.3.0 → 0.4.0

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