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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +24 -0
- data/Gemfile +11 -7
- data/Gemfile.lock +155 -19
- data/README.md +21 -3
- data/Rakefile +5 -0
- data/TODO.md +73 -0
- data/benchmarks/Gemfile +4 -0
- data/benchmarks/Gemfile.lock +31 -0
- data/benchmarks/rps.rb +20 -0
- data/bin/david +9 -4
- data/config.ru +4 -0
- data/david.gemspec +10 -4
- data/experiments/mcast.rb +37 -0
- data/experiments/structs.rb +45 -0
- data/{test.rb → experiments/test.rb} +0 -0
- data/lib/david.rb +15 -4
- data/lib/david/actor.rb +18 -0
- data/lib/david/garbage_collector.rb +35 -0
- data/lib/david/observe.rb +102 -0
- data/lib/david/rails/action_controller/base.rb +11 -0
- data/lib/david/railties/config.rb +20 -1
- data/lib/david/railties/middleware.rb +18 -6
- data/lib/david/request.rb +80 -0
- data/lib/david/resource_discovery.rb +92 -0
- data/lib/david/resource_discovery_proxy.rb +13 -0
- data/lib/david/server.rb +72 -27
- data/lib/david/server/constants.rb +48 -0
- data/lib/david/server/deduplication.rb +21 -0
- data/lib/david/server/mapping.rb +64 -12
- data/lib/david/server/multicast.rb +54 -0
- data/lib/david/server/options.rb +32 -0
- data/lib/david/server/respond.rb +146 -0
- data/lib/david/server/utility.rb +1 -6
- data/lib/david/show_exceptions.rb +52 -0
- data/lib/david/version.rb +2 -1
- data/lib/rack/handler/david.rb +16 -6
- data/lib/rack/hello_world.rb +23 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +29 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +78 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/guerilla_rack_handler_spec.rb +16 -0
- data/spec/mapping_spec.rb +56 -0
- data/spec/observe_spec.rb +111 -0
- data/spec/perf/server_perf_spec.rb +15 -9
- data/spec/resource_discovery_spec.rb +65 -0
- data/spec/server_spec.rb +306 -0
- data/spec/spec_helper.rb +43 -1
- data/spec/utility_spec.rb +8 -0
- metadata +195 -38
- data/lib/david/server/response.rb +0 -124
- data/lib/david/well_known.rb +0 -59
data/lib/david/server/options.rb
CHANGED
@@ -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
|
data/lib/david/server/utility.rb
CHANGED
@@ -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
|
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
|
data/lib/david/version.rb
CHANGED
data/lib/rack/handler/david.rb
CHANGED
@@ -9,12 +9,18 @@ module Rack
|
|
9
9
|
def self.run(app, options={})
|
10
10
|
options = DEFAULT_OPTIONS.merge(options)
|
11
11
|
|
12
|
-
|
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
|
-
|
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
|
-
'
|
26
|
-
'
|
27
|
-
'
|
28
|
-
'
|
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
|
data/lib/rack/hello_world.rb
CHANGED
@@ -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
|
data/spec/dummy/Rakefile
ADDED
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
|
+
*/
|
File without changes
|
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>
|
data/spec/dummy/bin/rake
ADDED