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/config.ru
CHANGED
data/david.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
$:.unshift File.expand_path('lib', File.dirname(__FILE__))
|
2
|
+
require 'david/version'
|
2
3
|
|
3
4
|
Gem::Specification.new do |s|
|
4
5
|
s.name = 'david'
|
@@ -19,8 +20,13 @@ Gem::Specification.new do |s|
|
|
19
20
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
20
21
|
s.require_paths = ['lib']
|
21
22
|
|
22
|
-
s.add_dependency '
|
23
|
-
s.add_dependency '
|
24
|
-
s.add_dependency 'coap', '~> 0'
|
23
|
+
s.add_dependency 'celluloid-io', '~> 0.16', '>= 0.16.1'
|
24
|
+
s.add_dependency 'coap', '~> 0.1'
|
25
25
|
s.add_dependency 'rack', '~> 1.5'
|
26
|
+
|
27
|
+
s.add_development_dependency 'rake'
|
28
|
+
s.add_development_dependency 'rspec', '~> 3.1'
|
29
|
+
|
30
|
+
s.add_development_dependency 'rspec-rails', '~> 3.1'
|
31
|
+
s.add_development_dependency 'rails', '~> 4.2'
|
26
32
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'celluloid/io'
|
2
|
+
require 'ipaddr'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
ifname, af = ARGV
|
6
|
+
|
7
|
+
ifname ||= 'lo'
|
8
|
+
af ||= 'inet6'
|
9
|
+
|
10
|
+
ifaddr = Socket.getifaddrs.select { |x| x.name == ifname }.first
|
11
|
+
ifindex = ifaddr.ifindex
|
12
|
+
|
13
|
+
if af == 'inet6'
|
14
|
+
maddr = 'ff02::fd'
|
15
|
+
# maddr = 'ff05::fd'
|
16
|
+
|
17
|
+
mreq = IPAddr.new(maddr).hton + [ifindex].pack('i_')
|
18
|
+
|
19
|
+
sock = Celluloid::IO::UDPSocket.new(Socket::AF_INET6)
|
20
|
+
sock.to_io.setsockopt(Socket::IPPROTO_IPV6, Socket::IPV6_JOIN_GROUP, mreq)
|
21
|
+
else
|
22
|
+
maddr = '224.0.1.187'
|
23
|
+
|
24
|
+
# Ignore interface for now.
|
25
|
+
mreq = IPAddr.new(maddr).hton + IPAddr.new('0.0.0.0').hton
|
26
|
+
|
27
|
+
sock = Celluloid::IO::UDPSocket.new
|
28
|
+
sock.to_io.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, mreq)
|
29
|
+
end
|
30
|
+
|
31
|
+
puts `netstat -g`
|
32
|
+
|
33
|
+
if af == 'inet6'
|
34
|
+
sock.to_io.setsockopt(Socket::IPPROTO_IPV6, Socket::IPV6_LEAVE_GROUP, mreq)
|
35
|
+
else
|
36
|
+
sock.to_io.setsockopt(Socket::IPPROTO_IP, Socket::IP_DROP_MEMBERSHIP, mreq)
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
|
3
|
+
Foo = Struct.new(:a, :b, :c)
|
4
|
+
Bar = Struct.new(:a, :b, :c)
|
5
|
+
|
6
|
+
data = []
|
7
|
+
|
8
|
+
arrays = {}
|
9
|
+
structs = {}
|
10
|
+
|
11
|
+
1000000.times do
|
12
|
+
a, b, c = ([0]*3).map { rand(0..9999) }
|
13
|
+
data.push [a, b, c]
|
14
|
+
end
|
15
|
+
|
16
|
+
Benchmark.bm do |x|
|
17
|
+
x.report('fill arrays ') do
|
18
|
+
data.each do |d|
|
19
|
+
a, b, c = d
|
20
|
+
arrays[[a, b, c]] = [a, b, c]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
x.report('fill structs') do
|
25
|
+
data.each do |d|
|
26
|
+
a, b, c = d
|
27
|
+
structs[Foo.new(a, b, c)] = Bar.new(a, b, c)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
x.report('read arrays ') do
|
33
|
+
100000.times do
|
34
|
+
a, b, c = data.sample
|
35
|
+
arrays[[a, b, c]]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
x.report('read structs') do
|
40
|
+
100000.times do
|
41
|
+
a, b, c = data.sample
|
42
|
+
structs[Foo.new(a, b, c)]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
File without changes
|
data/lib/david.rb
CHANGED
@@ -2,10 +2,15 @@ module David
|
|
2
2
|
end
|
3
3
|
|
4
4
|
require 'bundler/setup'
|
5
|
-
Bundler.require
|
5
|
+
Bundler.require(:default, :cbor)
|
6
6
|
|
7
7
|
unless defined? JRuby
|
8
|
-
|
8
|
+
begin
|
9
|
+
require 'cbor'
|
10
|
+
rescue LoadError
|
11
|
+
$stderr << "`gem install cbor` for transparent JSON/CBOR conversion "
|
12
|
+
$stderr << "support.\n"
|
13
|
+
end
|
9
14
|
end
|
10
15
|
|
11
16
|
require 'celluloid'
|
@@ -16,7 +21,7 @@ require 'rack'
|
|
16
21
|
|
17
22
|
include CoRE
|
18
23
|
|
19
|
-
|
24
|
+
$:.unshift(File.expand_path(File.dirname(__FILE__)))
|
20
25
|
|
21
26
|
require 'rack/hello_world'
|
22
27
|
require 'rack/handler/david'
|
@@ -24,10 +29,16 @@ require 'rack/handler/coap'
|
|
24
29
|
|
25
30
|
require 'david/guerilla/rack/handler'
|
26
31
|
|
27
|
-
require 'david/
|
32
|
+
require 'david/actor'
|
33
|
+
require 'david/garbage_collector'
|
34
|
+
require 'david/observe'
|
35
|
+
require 'david/request'
|
28
36
|
require 'david/server'
|
37
|
+
require 'david/version'
|
29
38
|
|
30
39
|
if defined? Rails
|
40
|
+
require 'david/rails/action_controller/base'
|
41
|
+
|
31
42
|
require 'david/railties/config'
|
32
43
|
require 'david/railties/middleware'
|
33
44
|
end
|
data/lib/david/actor.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module David
|
2
|
+
module Actor
|
3
|
+
def self.included(base)
|
4
|
+
base.send(:include, Celluloid)
|
5
|
+
end
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def log
|
10
|
+
@log ||= Celluloid.logger
|
11
|
+
@log ||= ::Logger.new(nil)
|
12
|
+
end
|
13
|
+
|
14
|
+
def server
|
15
|
+
Celluloid::Actor[:server]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module David
|
2
|
+
class GarbageCollector
|
3
|
+
include Actor
|
4
|
+
|
5
|
+
def initialize(tick_interval = 10, dedup_timeout = 5)
|
6
|
+
@tick_interval = tick_interval
|
7
|
+
@dedup_timeout = dedup_timeout
|
8
|
+
|
9
|
+
async.run
|
10
|
+
|
11
|
+
log.debug 'GarbageCollector initialized'
|
12
|
+
end
|
13
|
+
|
14
|
+
def clean_dedup_cache(now = Time.now.to_i)
|
15
|
+
server.dedup_cache.delete_if do |k, v|
|
16
|
+
now - v[1] >= @dedup_timeout
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def run
|
23
|
+
loop { sleep @tick_interval; tick }
|
24
|
+
end
|
25
|
+
|
26
|
+
def tick
|
27
|
+
unless server.dedup_cache.empty?
|
28
|
+
log.debug 'GarbageCollector tick'
|
29
|
+
|
30
|
+
clean_dedup_cache
|
31
|
+
log.debug server.dedup_cache
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module David
|
2
|
+
class Observe < Hash
|
3
|
+
include Actor
|
4
|
+
|
5
|
+
alias_method :_delete, :delete
|
6
|
+
alias_method :_include?, :include?
|
7
|
+
|
8
|
+
def initialize(tick_interval = 3)
|
9
|
+
@tick_interval = tick_interval
|
10
|
+
async.run
|
11
|
+
|
12
|
+
log.debug 'Observe initialized'
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(request, env, etag)
|
16
|
+
request.message.mid = nil
|
17
|
+
request.message.options.delete(:observe)
|
18
|
+
|
19
|
+
# TODO Check if Array or Struct is more efficient.
|
20
|
+
self[[request.host, request.token]] ||=
|
21
|
+
[0, request, env, etag, Time.now.to_i]
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(request)
|
25
|
+
_delete([request.host, request.token])
|
26
|
+
end
|
27
|
+
|
28
|
+
def include?(request)
|
29
|
+
_include?([request.host, request.token])
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
self.map { |k, v| [*k, v[2]['PATH_INFO'], v[0]].inspect }.join(', ')
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def bump(key, n, response)
|
39
|
+
self[key][0] = n
|
40
|
+
self[key][3] = response.options[:etag]
|
41
|
+
self[key][4] = Time.now.to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
# TODO If ETag did not change but max-age of last notification is expired,
|
45
|
+
# return empty 2.03.
|
46
|
+
def handle_update(key)
|
47
|
+
n, request, env, etag = self[key]
|
48
|
+
n += 1
|
49
|
+
|
50
|
+
response, options = server.respond(request, env)
|
51
|
+
|
52
|
+
return if response.nil?
|
53
|
+
|
54
|
+
if response.mcode[0] != 2
|
55
|
+
self.delete(request)
|
56
|
+
request(response, request.host, request.port, options)
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
if etag != response.options[:etag]
|
61
|
+
response.options[:observe] = n
|
62
|
+
|
63
|
+
answer = request(response, request.host, request.port, options)
|
64
|
+
|
65
|
+
if !answer.nil? && answer.tt == :rst
|
66
|
+
self.delete(request)
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
bump(key, n, response)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def request(message, host, port, options)
|
75
|
+
answer = nil
|
76
|
+
|
77
|
+
log.debug message.inspect
|
78
|
+
|
79
|
+
begin
|
80
|
+
options.merge!(retransmit: false, socket: server.socket)
|
81
|
+
answer = CoAP::Transmission.request(message, host, port, options).last
|
82
|
+
log.debug answer.inspect
|
83
|
+
rescue Timeout::Error, RuntimeError
|
84
|
+
end
|
85
|
+
|
86
|
+
answer
|
87
|
+
end
|
88
|
+
|
89
|
+
def run
|
90
|
+
loop { tick; sleep @tick_interval }
|
91
|
+
end
|
92
|
+
|
93
|
+
def tick
|
94
|
+
unless self.empty?
|
95
|
+
log.debug 'Observe tick'
|
96
|
+
log.debug self
|
97
|
+
end
|
98
|
+
|
99
|
+
self.each_key { |key| async.handle_update(key) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -2,8 +2,27 @@ module David
|
|
2
2
|
module Railties
|
3
3
|
class Config < Rails::Railtie
|
4
4
|
config.coap = ActiveSupport::OrderedOptions.new
|
5
|
-
|
5
|
+
|
6
|
+
# Blockwise transfer
|
7
|
+
config.coap.block = true
|
8
|
+
|
9
|
+
# Transparent JSON<>CBOR conversion
|
10
|
+
config.coap.cbor = false
|
11
|
+
|
12
|
+
# Default Content-Type if HTTP_ACCEPT is empty
|
13
|
+
config.coap.default_format = nil
|
14
|
+
|
15
|
+
# Multicast
|
16
|
+
config.coap.multicast = true
|
17
|
+
|
18
|
+
# Observe
|
19
|
+
config.coap.observe = true
|
20
|
+
|
21
|
+
# david as default Rack handler (`rails s` starts david)
|
6
22
|
config.coap.only = true
|
23
|
+
|
24
|
+
# Resource Discovery
|
25
|
+
config.coap.resource_discovery = true
|
7
26
|
end
|
8
27
|
end
|
9
28
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
require 'david/
|
1
|
+
require 'david/resource_discovery_proxy'
|
2
|
+
require 'david/show_exceptions'
|
2
3
|
|
3
4
|
module David
|
4
5
|
module Railties
|
@@ -10,21 +11,32 @@ module David
|
|
10
11
|
ActionDispatch::RemoteIp,
|
11
12
|
ActionDispatch::RequestId,
|
12
13
|
ActionDispatch::Session::CookieStore,
|
13
|
-
# ActionDispatch::ShowExceptions,
|
14
14
|
Rack::ConditionalGet,
|
15
|
-
# Rack::ETag,
|
16
15
|
Rack::Head,
|
17
|
-
Rack::Lock,
|
18
16
|
Rack::MethodOverride,
|
19
17
|
Rack::Runtime,
|
20
18
|
]
|
21
19
|
|
22
20
|
initializer 'david.clear_out_middleware' do |app|
|
21
|
+
# Remove middleware not applicable to CoAP
|
23
22
|
if config.coap.only
|
24
|
-
UNWANTED.each { |klass| app.middleware.delete
|
23
|
+
UNWANTED.each { |klass| app.middleware.delete(klass) }
|
25
24
|
end
|
26
25
|
|
27
|
-
|
26
|
+
# Enable multithreading for Rails
|
27
|
+
app.middleware.delete(Rack::Lock)
|
28
|
+
|
29
|
+
# Show exceptions as JSON
|
30
|
+
app.middleware.swap \
|
31
|
+
ActionDispatch::ShowExceptions,
|
32
|
+
David::ShowExceptions
|
33
|
+
|
34
|
+
# Include Resource Discovery middleware
|
35
|
+
if config.coap.resource_discovery
|
36
|
+
app.middleware.insert_after \
|
37
|
+
David::ShowExceptions,
|
38
|
+
David::ResourceDiscoveryProxy
|
39
|
+
end
|
28
40
|
end
|
29
41
|
end
|
30
42
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class Request < Struct.new(:host, :port, :message, :ancillary, :options)
|
2
|
+
def accept
|
3
|
+
message.options[:accept]
|
4
|
+
end
|
5
|
+
|
6
|
+
def block
|
7
|
+
@block ||= if message.options[:block2].nil?
|
8
|
+
CoAP::Block.new(0, false, 1024)
|
9
|
+
else
|
10
|
+
CoAP::Block.new(message.options[:block2]).decode
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def con?
|
15
|
+
message.tt == :con
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete?
|
19
|
+
message.mcode == :delete
|
20
|
+
end
|
21
|
+
|
22
|
+
def etag
|
23
|
+
message.options[:etag]
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_etag?
|
27
|
+
message.options[:etag].nil? && get?
|
28
|
+
end
|
29
|
+
|
30
|
+
def get?
|
31
|
+
message.mcode == :get
|
32
|
+
end
|
33
|
+
|
34
|
+
def idempotent?
|
35
|
+
get? || put? || delete?
|
36
|
+
end
|
37
|
+
|
38
|
+
def mid
|
39
|
+
message.mid
|
40
|
+
end
|
41
|
+
|
42
|
+
def multicast?
|
43
|
+
a = ancillary
|
44
|
+
return false if a.nil?
|
45
|
+
|
46
|
+
return @multicast unless @multicast.nil?
|
47
|
+
|
48
|
+
@multicast =
|
49
|
+
a.cmsg_is?(:IP, :PKTINFO) && a.ip_pktinfo[0].ipv4_multicast? ||
|
50
|
+
a.cmsg_is?(:IPV6, :PKTINFO) && a.ipv6_pktinfo[0].ipv6_multicast?
|
51
|
+
end
|
52
|
+
|
53
|
+
def non?
|
54
|
+
message.tt == :non
|
55
|
+
end
|
56
|
+
|
57
|
+
def observe?
|
58
|
+
!message.options[:observe].nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def post?
|
62
|
+
message.mcode == :post
|
63
|
+
end
|
64
|
+
|
65
|
+
def proxy?
|
66
|
+
!(message.options[:proxy_uri].nil? && message.options[:proxy_scheme].nil?)
|
67
|
+
end
|
68
|
+
|
69
|
+
def put?
|
70
|
+
message.mcode == :put
|
71
|
+
end
|
72
|
+
|
73
|
+
def token
|
74
|
+
message.options[:token]
|
75
|
+
end
|
76
|
+
|
77
|
+
def valid_method?
|
78
|
+
CoAP::METHODS.include?(message.mcode)
|
79
|
+
end
|
80
|
+
end
|