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.
- 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