david 0.3.0.pre → 0.3.0

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