david 0.3.0.pre → 0.3.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +24 -0
  4. data/Gemfile +11 -7
  5. data/Gemfile.lock +155 -19
  6. data/README.md +21 -3
  7. data/Rakefile +5 -0
  8. data/TODO.md +73 -0
  9. data/benchmarks/Gemfile +4 -0
  10. data/benchmarks/Gemfile.lock +31 -0
  11. data/benchmarks/rps.rb +20 -0
  12. data/bin/david +9 -4
  13. data/config.ru +4 -0
  14. data/david.gemspec +10 -4
  15. data/experiments/mcast.rb +37 -0
  16. data/experiments/structs.rb +45 -0
  17. data/{test.rb → experiments/test.rb} +0 -0
  18. data/lib/david.rb +15 -4
  19. data/lib/david/actor.rb +18 -0
  20. data/lib/david/garbage_collector.rb +35 -0
  21. data/lib/david/observe.rb +102 -0
  22. data/lib/david/rails/action_controller/base.rb +11 -0
  23. data/lib/david/railties/config.rb +20 -1
  24. data/lib/david/railties/middleware.rb +18 -6
  25. data/lib/david/request.rb +80 -0
  26. data/lib/david/resource_discovery.rb +92 -0
  27. data/lib/david/resource_discovery_proxy.rb +13 -0
  28. data/lib/david/server.rb +72 -27
  29. data/lib/david/server/constants.rb +48 -0
  30. data/lib/david/server/deduplication.rb +21 -0
  31. data/lib/david/server/mapping.rb +64 -12
  32. data/lib/david/server/multicast.rb +54 -0
  33. data/lib/david/server/options.rb +32 -0
  34. data/lib/david/server/respond.rb +146 -0
  35. data/lib/david/server/utility.rb +1 -6
  36. data/lib/david/show_exceptions.rb +52 -0
  37. data/lib/david/version.rb +2 -1
  38. data/lib/rack/handler/david.rb +16 -6
  39. data/lib/rack/hello_world.rb +23 -0
  40. data/spec/dummy/Rakefile +6 -0
  41. data/spec/dummy/app/assets/images/.keep +0 -0
  42. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  43. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  44. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  45. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  46. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  47. data/spec/dummy/app/mailers/.keep +0 -0
  48. data/spec/dummy/app/models/.keep +0 -0
  49. data/spec/dummy/app/models/concerns/.keep +0 -0
  50. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  51. data/spec/dummy/bin/bundle +3 -0
  52. data/spec/dummy/bin/rails +4 -0
  53. data/spec/dummy/bin/rake +4 -0
  54. data/spec/dummy/config.ru +4 -0
  55. data/spec/dummy/config/application.rb +29 -0
  56. data/spec/dummy/config/boot.rb +5 -0
  57. data/spec/dummy/config/database.yml +25 -0
  58. data/spec/dummy/config/environment.rb +5 -0
  59. data/spec/dummy/config/environments/development.rb +37 -0
  60. data/spec/dummy/config/environments/production.rb +78 -0
  61. data/spec/dummy/config/environments/test.rb +39 -0
  62. data/spec/dummy/config/initializers/assets.rb +8 -0
  63. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  65. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  66. data/spec/dummy/config/initializers/inflections.rb +16 -0
  67. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  68. data/spec/dummy/config/initializers/session_store.rb +3 -0
  69. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  70. data/spec/dummy/config/locales/en.yml +23 -0
  71. data/spec/dummy/config/routes.rb +58 -0
  72. data/spec/dummy/config/secrets.yml +22 -0
  73. data/spec/dummy/db/test.sqlite3 +0 -0
  74. data/spec/dummy/lib/assets/.keep +0 -0
  75. data/spec/dummy/log/.keep +0 -0
  76. data/spec/dummy/public/404.html +67 -0
  77. data/spec/dummy/public/422.html +67 -0
  78. data/spec/dummy/public/500.html +66 -0
  79. data/spec/dummy/public/favicon.ico +0 -0
  80. data/spec/guerilla_rack_handler_spec.rb +16 -0
  81. data/spec/mapping_spec.rb +56 -0
  82. data/spec/observe_spec.rb +111 -0
  83. data/spec/perf/server_perf_spec.rb +15 -9
  84. data/spec/resource_discovery_spec.rb +65 -0
  85. data/spec/server_spec.rb +306 -0
  86. data/spec/spec_helper.rb +43 -1
  87. data/spec/utility_spec.rb +8 -0
  88. metadata +195 -38
  89. data/lib/david/server/response.rb +0 -124
  90. data/lib/david/well_known.rb +0 -59
@@ -7,6 +7,10 @@ module David
7
7
  send(('choose_' + name.to_s).to_sym, value)
8
8
  end
9
9
 
10
+ def choose_block(value)
11
+ default_to_true(:block, value)
12
+ end
13
+
10
14
  def choose_cbor(value)
11
15
  if value.nil? && defined? Rails
12
16
  value = Rails.application.config.coap.cbor
@@ -15,6 +19,14 @@ module David
15
19
  !!value
16
20
  end
17
21
 
22
+ def choose_default_format(value)
23
+ if value.nil? && defined? Rails
24
+ value = Rails.application.config.coap.default_format
25
+ end
26
+
27
+ value.nil? ? 'application/json' : value
28
+ end
29
+
18
30
  # Rails starts on 'localhost' since 4.2.0.beta1
19
31
  # (Resolv class seems not to consider /etc/hosts)
20
32
  def choose_host(value)
@@ -42,6 +54,26 @@ module David
42
54
 
43
55
  logger
44
56
  end
57
+
58
+ def choose_mcast(value)
59
+ default_to_true(:multicast, value)
60
+ end
61
+
62
+ def choose_observe(value)
63
+ default_to_true(:observe, value)
64
+ end
65
+
66
+ def default_to_true(key, value)
67
+ if value.nil? && defined? Rails
68
+ value = Rails.application.config.coap.send(key)
69
+ end
70
+
71
+ return true if value.nil? || value.to_s == 'true'
72
+
73
+ false
74
+ end
75
+
76
+ module_function :default_to_true
45
77
  end
46
78
  end
47
79
  end
@@ -0,0 +1,146 @@
1
+ require 'david/server/constants'
2
+ require 'david/server/mapping'
3
+ require 'david/server/utility'
4
+
5
+ module David
6
+ class Server
7
+ module Respond
8
+ include Constants
9
+ include Mapping
10
+ include Utility
11
+
12
+ def respond(request, env = nil)
13
+ block_enabled = @block && request.get?
14
+
15
+ if block_enabled
16
+ # Fail if m set.
17
+ if request.block.more && !request.multicast?
18
+ return error(request, 4.05)
19
+ end
20
+ end
21
+
22
+ return error(request, 5.05) if request.proxy?
23
+
24
+ env ||= basic_env(request)
25
+
26
+ code, headers, body = @app.call(env)
27
+
28
+ # No error responses on multicast requests.
29
+ return if request.multicast? && !(200..299).include?(code)
30
+
31
+ ct = headers[HTTP_CONTENT_TYPE]
32
+ body = body_to_string(body)
33
+
34
+ body.close if body.respond_to?(:close)
35
+
36
+ if @cbor && ct == 'application/json'
37
+ begin
38
+ body = body_to_cbor(body)
39
+ ct = CONTENT_TYPE_CBOR
40
+ rescue JSON::ParserError
41
+ end
42
+ end
43
+
44
+ # No response on request for non-existent block.
45
+ return if block_enabled && !request.block.included_by?(body)
46
+
47
+ cf = CoAP::Registry.convert_content_format(ct)
48
+ etag = etag_to_coap(headers, 4)
49
+ loc = location_to_coap(headers)
50
+ ma = max_age_to_coap(headers)
51
+ mcode = code_to_coap(code)
52
+ size = headers[HTTP_CONTENT_LENGTH].to_i
53
+
54
+ # App returned cf different from accept
55
+ return error(request, 4.06) if request.accept && request.accept != cf
56
+
57
+ response = initialize_response(request, mcode)
58
+
59
+ response.options[:content_format] = cf
60
+ response.options[:etag] = etag
61
+ response.options[:location_path] = loc unless loc.nil?
62
+ response.options[:max_age] = ma.to_i unless ma.nil?
63
+
64
+ if @observe && handle_observe(request, env, etag)
65
+ response.options[:observe] = 0
66
+ end
67
+
68
+ if block_enabled
69
+ block = request.block.dup
70
+ block.set_more!(body)
71
+
72
+ response.payload = block.chunk(body)
73
+ response.options[:block2] = block.encode
74
+ # response.options[:size2] = size if size != 0
75
+ else
76
+ response.payload = body
77
+ end
78
+
79
+ [response, {}]
80
+ end
81
+
82
+ private
83
+
84
+ def basic_env(request)
85
+ m = request.message
86
+
87
+ {
88
+ REMOTE_ADDR => request.host,
89
+ REMOTE_PORT => request.port.to_s,
90
+ REQUEST_METHOD => method_to_http(m.mcode),
91
+ SCRIPT_NAME => EMPTY_STRING,
92
+ PATH_INFO => path_encode(m.options[:uri_path]),
93
+ QUERY_STRING => query_encode(m.options[:uri_query])
94
+ .gsub(/^\?/, ''),
95
+ SERVER_NAME => @host,
96
+ SERVER_PORT => @port.to_s,
97
+ CONTENT_LENGTH => m.payload.bytesize.to_s,
98
+ CONTENT_TYPE => EMPTY_STRING,
99
+ HTTP_ACCEPT => accept_to_http(request),
100
+ RACK_VERSION => [1, 2],
101
+ RACK_URL_SCHEME => RACK_URL_SCHEME_HTTP,
102
+ RACK_INPUT => StringIO.new(m.payload),
103
+ RACK_ERRORS => $stderr,
104
+ RACK_MULTITHREAD => true,
105
+ RACK_MULTIPROCESS => true,
106
+ RACK_RUN_ONCE => false,
107
+ RACK_LOGGER => @logger,
108
+ COAP_VERSION => 1,
109
+ COAP_MULTICAST => request.multicast?,
110
+ COAP_DTLS => COAP_DTLS_NOSEC,
111
+ COAP_DTLS_ID => EMPTY_STRING,
112
+ }
113
+ end
114
+
115
+ def error(request, mcode)
116
+ [initialize_response(request, mcode), retransmit: false]
117
+ end
118
+
119
+ def handle_observe(request, env, etag)
120
+ return unless request.get? && request.observe?
121
+
122
+ if request.message.options[:observe] == 0
123
+ observe.add(request, env, etag)
124
+ true
125
+ else
126
+ observe.delete(request)
127
+ false
128
+ end
129
+ end
130
+
131
+ def initialize_response(request, mcode = 2.00)
132
+ type = request.con? ? :ack : :non
133
+
134
+ CoAP::Message.new \
135
+ tt: type,
136
+ mcode: mcode,
137
+ mid: request.message.mid || SecureRandom.random_number(0xffff),
138
+ token: request.token
139
+ end
140
+
141
+ def observe
142
+ Celluloid::Actor[:observe]
143
+ end
144
+ end
145
+ end
146
+ end
@@ -6,14 +6,9 @@ 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
10
  s.chomp
11
11
  end
12
-
13
- def content_type(options)
14
- ct = options['Content-Type']
15
- ct.split(';').first unless ct.nil?
16
- end
17
12
  end
18
13
  end
19
14
  end
@@ -0,0 +1,52 @@
1
+ module David
2
+ class ShowExceptions
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ dup._call(env)
9
+ end
10
+
11
+ def _call(env)
12
+ @app.call(env)
13
+ rescue Exception => exception
14
+ if env['action_dispatch.show_exceptions'] == false
15
+ raise exception
16
+ end
17
+
18
+ @env = env
19
+
20
+ render_exception(exception)
21
+ end
22
+
23
+ private
24
+
25
+ def render_exception(exception)
26
+ body = {
27
+ error: exception.class.to_s,
28
+ message: exception.message
29
+ }
30
+
31
+ log(:info, [body[:error], body[:message]].join("\n"))
32
+
33
+ body = body.to_json
34
+
35
+ code = 500
36
+ code = 404 if exception.is_a?(ActiveRecord::RecordNotFound)
37
+
38
+ [code,
39
+ {
40
+ 'Content-Type' => 'application/json',
41
+ 'Content-Length' => body.bytesize.to_s
42
+ },
43
+ [body]
44
+ ]
45
+ end
46
+
47
+ def log(level, message)
48
+ @logger ||= @env['rack.logger']
49
+ @logger.send(level, message) if @logger
50
+ end
51
+ end
52
+ end
@@ -2,6 +2,7 @@ module David
2
2
  MAJOR = 0
3
3
  MINOR = 3
4
4
  PATCH = 0
5
+ SUFFIX = nil
5
6
 
6
- VERSION = [MAJOR, MINOR, PATCH, 'pre'].join('.').freeze
7
+ VERSION = [MAJOR, MINOR, PATCH, SUFFIX].compact.join('.')
7
8
  end
@@ -9,12 +9,18 @@ module Rack
9
9
  def self.run(app, options={})
10
10
  options = DEFAULT_OPTIONS.merge(options)
11
11
 
12
- supervisor = ::David::Server.supervise_as(:david, app, options)
12
+ g = Celluloid::SupervisionGroup.run!
13
+
14
+ g.supervise_as(:server, ::David::Server, app, options)
15
+ g.supervise_as(:observe, ::David::Observe) if options[:Observe] != false
16
+ g.supervise_as(:gc, ::David::GarbageCollector)
13
17
 
14
18
  begin
15
19
  sleep
16
20
  rescue Interrupt
17
- supervisor.terminate
21
+ Celluloid.logger.info 'Terminated'
22
+ Celluloid.logger = nil
23
+ g.terminate
18
24
  end
19
25
  end
20
26
 
@@ -22,10 +28,14 @@ module Rack
22
28
  host, port = DEFAULT_OPTIONS.values_at(:Host, :Port)
23
29
 
24
30
  {
25
- 'Host=HOST' => "Hostname to listen on (default: #{host})",
26
- 'Port=PORT' => "Port to listen on (default: #{port})",
27
- 'CBOR=BOOLEAN' => 'Transparent JSON/CBOR conversion.',
28
- 'Log=LOG' => 'Change logging (debug|none).'
31
+ 'Block=BOOLEAN' => 'Support for blockwise transfer (default: true)',
32
+ 'CBOR=BOOLEAN' => 'Transparent JSON/CBOR conversion (default: false)',
33
+ 'DefaultFormat=F' => 'Content-Type if CoAP accept option on request is undefined',
34
+ 'Host=HOST' => "Hostname to listen on (default: #{host})",
35
+ 'Log=LOG' => 'Change logging (debug|none)',
36
+ 'Multicast=BOOLEAN' => 'Multicast support (default: true)',
37
+ 'Observe=BOOLEAN' => 'Observe support (default: true)',
38
+ 'Port=PORT' => "Port to listen on (default: #{port})"
29
39
  }
30
40
  end
31
41
  end
@@ -17,6 +17,11 @@ module Rack
17
17
  {'Content-Type' => 'application/link-format'},
18
18
  ['</hello>;rt="hello";ct=0']
19
19
  ]
20
+ when '/echo/accept'
21
+ [200,
22
+ {'Content-Type' => env['HTTP_ACCEPT'], 'Content-Length' => '0'},
23
+ []
24
+ ]
20
25
  when '/hello'
21
26
  [200,
22
27
  # If Content-Length is not given, Rack assumes chunked transfer.
@@ -37,6 +42,24 @@ module Rack
37
42
  {'Content-Type' => 'text/plain'},
38
43
  ["#{@@value}"]
39
44
  ]
45
+ when '/block'
46
+ n = 17
47
+ [200,
48
+ {'Content-Type' => 'text/plain', 'Content-Length' => n.to_s},
49
+ ['+'*n]
50
+ ]
51
+ when '/code'
52
+ [2.05,
53
+ {'Content-Type' => 'text/plain'},
54
+ []
55
+ ]
56
+ when '/time'
57
+ # Rack::ETag does not add an ETag header, if response code other than
58
+ # 200 or 201, so CoAP/Float return codes do not work, here.
59
+ [200,
60
+ {'Content-Type' => 'text/plain'},
61
+ [Time.now.to_s]
62
+ ]
40
63
  else
41
64
  [404, {}, ['']]
42
65
  end
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
File without changes
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,5 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Prevent CSRF attacks by raising an exception.
3
+ # For APIs, you may want to use :null_session instead.
4
+ protect_from_forgery with: :exception
5
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
File without changes
File without changes
File without changes
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Dummy</title>
5
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6
+ <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../config/boot'
3
+ require 'rake'
4
+ Rake.application.run