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