david 0.4.0 → 0.4.1
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/Gemfile.lock +6 -6
- data/README.md +52 -8
- data/benchmarks/Gemfile.lock +3 -3
- data/benchmarks/plot.r +24 -0
- data/benchmarks/rackup/Gemfile.lock +4 -4
- data/benchmarks/stress.sh +2 -0
- data/benchmarks/tocsv.rb +22 -0
- data/david.gemspec +3 -3
- data/lib/david/app_config.rb +1 -4
- data/lib/david/exchange.rb +4 -4
- data/lib/david/fake_logger.rb +9 -8
- data/lib/david/guerilla/rack/handler.rb +13 -1
- data/lib/david/observe.rb +10 -4
- data/lib/david/registry.rb +12 -8
- data/lib/david/server/constants.rb +2 -0
- data/lib/david/server/mapping.rb +5 -0
- data/lib/david/server/multicast.rb +10 -9
- data/lib/david/server/respond.rb +2 -2
- data/lib/david/server.rb +23 -8
- data/lib/david/version.rb +1 -1
- data/lib/rack/hello_world.rb +1 -14
- data/spec/observe_spec.rb +87 -2
- data/spec/spec_helper.rb +3 -1
- metadata +48 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef49e2a7d5047bfbaf7a2146d2998495c7571f74
|
4
|
+
data.tar.gz: cc09675dd5021fe2a49c43224f0e5d5b713f5d9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 21ffffe1fd57e7444916ee097205cfee75d1c560bddd672ece840f6a22836a13739a3755e438a70880660b9fcc81e7b93833e99d5bc13e8c83e40d5e50022f2e
|
7
|
+
data.tar.gz: d3de9b5d60d1f13cf43fa47f05c49fd6dee9cb8ceeec8693e512fb3c0196dfca258d64930dc176d010c72165abdd6a5428029f11e13790465773c077e21dadf0
|
data/Gemfile.lock
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
david (0.4.
|
4
|
+
david (0.4.1)
|
5
5
|
celluloid-io (~> 0.16, >= 0.16.1)
|
6
|
-
coap (
|
7
|
-
rack (~> 1.
|
6
|
+
coap (>= 0.1)
|
7
|
+
rack (~> 1.6)
|
8
8
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
@@ -73,7 +73,7 @@ GEM
|
|
73
73
|
docile (1.1.5)
|
74
74
|
equalizer (0.0.9)
|
75
75
|
erubis (2.7.0)
|
76
|
-
globalid (0.3.
|
76
|
+
globalid (0.3.1)
|
77
77
|
activesupport (>= 4.1.0)
|
78
78
|
grape (0.10.1)
|
79
79
|
activesupport
|
@@ -171,7 +171,7 @@ GEM
|
|
171
171
|
rspec-expectations (~> 3.2.0)
|
172
172
|
rspec-mocks (~> 3.2.0)
|
173
173
|
rspec-support (~> 3.2.0)
|
174
|
-
rspec-support (3.2.
|
174
|
+
rspec-support (3.2.1)
|
175
175
|
ruby-prof (0.15.3)
|
176
176
|
simplecov (0.9.1)
|
177
177
|
docile (~> 1.1.0)
|
@@ -221,7 +221,7 @@ DEPENDENCIES
|
|
221
221
|
nyny
|
222
222
|
rails (~> 4.2.0)
|
223
223
|
rake
|
224
|
-
rspec (~> 3.
|
224
|
+
rspec (~> 3.2)
|
225
225
|
rspec-rails (~> 3.2.0)
|
226
226
|
ruby-prof
|
227
227
|
sinatra
|
data/README.md
CHANGED
@@ -8,7 +8,8 @@
|
|
8
8
|
|
9
9
|
David is a CoAP server with Rack interface to bring the illustrious family of
|
10
10
|
Rack compatible web frameworks into the Internet of Things. **Currently, it is
|
11
|
-
in a development state and probably not ready for use in production.**
|
11
|
+
in a development state and probably not ready for use in production.** It is
|
12
|
+
tested with MRI >= 1.9, JRuby, and Rubinius.
|
12
13
|
|
13
14
|
## Usage
|
14
15
|
|
@@ -20,19 +21,34 @@ It will hook into Rack and make itself the default handler, so running `rails
|
|
20
21
|
s` starts David. If you want to start WEBrick for example, you can do so by
|
21
22
|
executing `rails s webrick`.
|
22
23
|
|
24
|
+
After the server is started, the Rails application is available at
|
25
|
+
`coap://[::1]:3000/` by default.
|
26
|
+
|
27
|
+
[Copper](https://addons.mozilla.org/de/firefox/addon/copper-270430/) is a CoAP
|
28
|
+
client for Firefox and can be used for development. The [Ruby coap
|
29
|
+
gem](https://github.com/nning/coap) is used by David for example for message
|
30
|
+
parsing and also includes a command line utility (named `coap`) that can also
|
31
|
+
be used for development.
|
32
|
+
|
23
33
|
As [CoAP](https://tools.ietf.org/html/rfc7252) is a protocol for constrained
|
24
34
|
environments and machine to machine communications, returning HTML from your
|
25
35
|
controllers will not be of much use. JSON for example is more suitable in that
|
26
|
-
context.
|
27
|
-
|
36
|
+
context. The Accept header is set to "application/json" by default, so that
|
37
|
+
Rails responds with the JSON resource representation. David works well with the
|
38
|
+
default ways to handle JSON responses from controllers such as `render json:`.
|
39
|
+
You can also utilize [Jbuilder templates](https://github.com/rails/jbuilder)
|
40
|
+
for easy generation of more complex JSON structures.
|
28
41
|
|
29
42
|
[CBOR](https://tools.ietf.org/html/rfc7049) can be used to compress your JSON.
|
30
|
-
|
31
|
-
|
43
|
+
Automatic transcoding between JSON and CBOR is activated by setting the Rack
|
44
|
+
environment option `CBOR` or `config.coap.cbor` in your Rails application
|
32
45
|
config to `true`.
|
33
46
|
|
34
47
|
## Tested Rack Frameworks
|
35
48
|
|
49
|
+
By providing a Rack interface, David does not only work with Rails but also
|
50
|
+
with the following Rack compatible web frameworks.
|
51
|
+
|
36
52
|
* [Grape](https://github.com/intridea/grape)
|
37
53
|
* [Hobbit](https://github.com/patriciomacadden/hobbit)
|
38
54
|
* [NYNY](https://github.com/alisnic/nyny)
|
@@ -42,20 +58,45 @@ config to `true`.
|
|
42
58
|
|
43
59
|
## Configuration
|
44
60
|
|
61
|
+
The following table lists available configuration options for the CoAP server.
|
62
|
+
Rack keys can be specified with the `-O` option of `rackup`. The listed Rails
|
63
|
+
keys can be accessed for example from the `config/application.rb` file of your
|
64
|
+
Rails application.
|
65
|
+
|
45
66
|
| Rack key | Rails key | Default | Semantics |
|
46
67
|
|--- |--- |--- |--- |
|
47
|
-
| Block | coap.block | true | Blockwise transfers
|
68
|
+
| Block | coap.block | true | [Blockwise transfers](https://tools.ietf.org/html/draft-ietf-core-block-16) |
|
48
69
|
| CBOR | coap.cbor | false | JSON/CBOR transcoding |
|
49
70
|
| DefaultFormat | coap.default_format | | Default Content-Type |
|
50
71
|
| Host | | ::1 / :: | Server listening host |
|
51
72
|
| Log | | info | Log level (none or debug) |
|
52
73
|
| MinimalMapping | | false | Minimal HTTP status codes mapping |
|
53
74
|
| Multicast | coap.multicast | true | Multicast support |
|
54
|
-
| Observe | coap.observe | true | Observe support
|
75
|
+
| Observe | coap.observe | true | [Observe support](https://tools.ietf.org/html/draft-ietf-core-observe-16) |
|
55
76
|
| | coap.only | true | Removes (HTTP) middleware |
|
56
77
|
| Port | | 5683 | Server listening port |
|
57
78
|
| | coap.resource_discovery | true | Provision of `.well-known/core` |
|
58
79
|
|
80
|
+
The server can be started with debug log level for example with the following
|
81
|
+
command provided that a rackup config file (`config.ru`) exists like in a Rails
|
82
|
+
application.
|
83
|
+
|
84
|
+
rackup -O Log=debug
|
85
|
+
|
86
|
+
In a Rails application, CBOR transcoding is activated for any controller and
|
87
|
+
action by inserting the third line of the following code into
|
88
|
+
`config/application.rb`.
|
89
|
+
|
90
|
+
module Example
|
91
|
+
class Application < Rails::Application
|
92
|
+
config.coap.cbor = true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
In Copper for example the default block size for Blockwise Transfers is set to
|
97
|
+
64 bytes. That's even small for most exception messages. It is recommended to
|
98
|
+
set the block size to the maximum (1024B) during development.
|
99
|
+
|
59
100
|
## Discovery
|
60
101
|
|
61
102
|
The [CoAP Discovery](https://tools.ietf.org/html/rfc7252#section-7) will be
|
@@ -82,6 +123,9 @@ further documentation.)
|
|
82
123
|
|
83
124
|
## Rack environment
|
84
125
|
|
126
|
+
David sets the following server (and protocol) specific Rack environment
|
127
|
+
entries that can be read from your Rack application if necessary.
|
128
|
+
|
85
129
|
| Key | Value class | Semantics |
|
86
130
|
|--- |--- |--- |
|
87
131
|
| coap.version | Integer | Protocol version of CoAP request |
|
@@ -92,7 +136,7 @@ further documentation.)
|
|
92
136
|
|
93
137
|
## Benchmarks
|
94
138
|
|
95
|
-
David handles about
|
139
|
+
David handles about 11.000 requests per second (tested in MRI 2.2.0 with up to
|
96
140
|
10.000 concurrent clients on a single core of a Core i7-3520M CPU running Linux
|
97
141
|
3.18.5).
|
98
142
|
|
data/benchmarks/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../../coap
|
3
3
|
specs:
|
4
|
-
coap (0.1.
|
4
|
+
coap (0.1.1)
|
5
5
|
celluloid-io (~> 0.16, >= 0.16.1)
|
6
6
|
resolv-ipv6favor (~> 0)
|
7
7
|
|
@@ -11,9 +11,9 @@ GEM
|
|
11
11
|
benchmark-ips (2.1.1)
|
12
12
|
celluloid (0.16.0)
|
13
13
|
timers (~> 4.0.0)
|
14
|
-
celluloid-io (0.16.
|
14
|
+
celluloid-io (0.16.2)
|
15
15
|
celluloid (>= 0.16.0)
|
16
|
-
nio4r (>= 1.
|
16
|
+
nio4r (>= 1.1.0)
|
17
17
|
hitimes (1.2.2)
|
18
18
|
hitimes (1.2.2-java)
|
19
19
|
nio4r (1.1.0)
|
data/benchmarks/plot.r
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/lib/R/bin/Rscript
|
2
|
+
|
3
|
+
library(methods)
|
4
|
+
library(ggplot2)
|
5
|
+
|
6
|
+
args = commandArgs(TRUE)
|
7
|
+
values = read.table(file=args[1], sep=",", header=T)
|
8
|
+
|
9
|
+
x = values$concurrent
|
10
|
+
y1 = values$throughput
|
11
|
+
y2 = values$loss
|
12
|
+
|
13
|
+
df = data.frame(x, y1)
|
14
|
+
|
15
|
+
g = ggplot(df, aes(x, y1)) +
|
16
|
+
scale_x_log10() +
|
17
|
+
geom_line(aes(y=y1)) +
|
18
|
+
ylab("Requests per second") +
|
19
|
+
xlab("Concurrent clients (log.)") +
|
20
|
+
theme(panel.background = element_rect(fill='white', colour='black'))
|
21
|
+
|
22
|
+
path = paste(args[1], "pdf", sep=".")
|
23
|
+
print(path)
|
24
|
+
ggsave(g, file=path)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../..
|
3
3
|
specs:
|
4
|
-
david (0.4.
|
4
|
+
david (0.4.1.pre)
|
5
5
|
celluloid-io (~> 0.16, >= 0.16.1)
|
6
|
-
coap (
|
7
|
-
rack (~> 1.
|
6
|
+
coap (>= 0.1)
|
7
|
+
rack (~> 1.6)
|
8
8
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
@@ -55,7 +55,7 @@ GEM
|
|
55
55
|
celluloid-io (0.16.2)
|
56
56
|
celluloid (>= 0.16.0)
|
57
57
|
nio4r (>= 1.1.0)
|
58
|
-
coap (0.1.
|
58
|
+
coap (0.1.1)
|
59
59
|
celluloid-io (~> 0.16, >= 0.16.1)
|
60
60
|
resolv-ipv6favor (~> 0)
|
61
61
|
coercible (1.0.0)
|
data/benchmarks/stress.sh
CHANGED
data/benchmarks/tocsv.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
def process_line(line)
|
4
|
+
line.split(', ').map { |x| x.split('=')[1] }
|
5
|
+
end
|
6
|
+
|
7
|
+
def process_lines(lines)
|
8
|
+
lines.map! { |line| process_line(line) }
|
9
|
+
lines[0].zip(*lines[1..2]).map { |x| avg(*x.map(&:to_f)) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def avg(*args)
|
13
|
+
args.reduce(:+) / args.size.to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
CSV.open(ARGV[0] + '.csv', 'wb') do |csv|
|
17
|
+
csv << %w[concurrent loss throughput]
|
18
|
+
File.readlines(ARGV[0]).each_slice(3) do |lines|
|
19
|
+
conc, _, total, loss, throughput = process_lines(lines)
|
20
|
+
csv << [conc, (loss/total)*100, throughput].map { |x| x.round(5) }
|
21
|
+
end
|
22
|
+
end
|
data/david.gemspec
CHANGED
@@ -21,9 +21,9 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.require_paths = ['lib']
|
22
22
|
|
23
23
|
s.add_dependency 'celluloid-io', '~> 0.16', '>= 0.16.1'
|
24
|
-
s.add_dependency 'coap', '
|
25
|
-
s.add_dependency 'rack', '~> 1.
|
24
|
+
s.add_dependency 'coap', '>= 0.1'
|
25
|
+
s.add_dependency 'rack', '~> 1.6'
|
26
26
|
|
27
27
|
s.add_development_dependency 'rake'
|
28
|
-
s.add_development_dependency 'rspec', '~> 3.
|
28
|
+
s.add_development_dependency 'rspec', '~> 3.2'
|
29
29
|
end
|
data/lib/david/app_config.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'david/fake_logger'
|
2
|
-
|
3
1
|
module David
|
4
2
|
class AppConfig < Hash
|
5
3
|
DEFAULT_OPTIONS = {
|
@@ -47,12 +45,11 @@ module David
|
|
47
45
|
end
|
48
46
|
|
49
47
|
def choose_log(value)
|
50
|
-
return FakeLogger.new if value == 'none'
|
51
|
-
|
52
48
|
log = ::Logger.new($stderr)
|
53
49
|
|
54
50
|
log.level = ::Logger::INFO
|
55
51
|
log.level = ::Logger::DEBUG if value == 'debug'
|
52
|
+
log.level = ::Logger::FATAL if value == 'none'
|
56
53
|
|
57
54
|
log.formatter = proc do |sev, time, prog, msg|
|
58
55
|
"#{time.strftime('[%Y-%m-%d %H:%M:%S]')} #{sev} #{msg}\n"
|
data/lib/david/exchange.rb
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
module David
|
2
2
|
class Exchange < Struct.new(:host, :port, :message, :ancillary, :options)
|
3
3
|
include Registry
|
4
|
-
|
5
|
-
def ==(other)
|
6
|
-
mid == other.mid && token == other.token
|
7
|
-
end
|
8
4
|
|
9
5
|
def accept
|
10
6
|
message.options[:accept]
|
@@ -77,6 +73,10 @@ module David
|
|
77
73
|
!message.options[:observe].nil?
|
78
74
|
end
|
79
75
|
|
76
|
+
def ping?
|
77
|
+
con? && message.mcode == [0, 0]
|
78
|
+
end
|
79
|
+
|
80
80
|
def post?
|
81
81
|
message.mcode == :post
|
82
82
|
end
|
data/lib/david/fake_logger.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module David
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
class FakeLogger
|
3
|
+
def initialize
|
4
|
+
Celluloid.logger = nil
|
5
|
+
end
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
[:info, :debug, :warn, :error, :fatal].each do |method|
|
8
|
+
define_method(method) { |*args| }
|
9
|
+
define_method("#{method}?".to_sym) { false }
|
10
|
+
end
|
11
|
+
end
|
11
12
|
end
|
@@ -14,8 +14,20 @@ module Rack
|
|
14
14
|
elsif ENV.include?("RACK_HANDLER")
|
15
15
|
self.get(ENV["RACK_HANDLER"])
|
16
16
|
else
|
17
|
-
|
17
|
+
# Return David as handler unless Rails is loaded and config.coap.only
|
18
|
+
# is set to false.
|
19
|
+
return Rack::Handler::David unless rails_coap_only
|
20
|
+
|
21
|
+
# Original Rack handler order.
|
22
|
+
pick ['thin', 'puma', 'webrick']
|
18
23
|
end
|
19
24
|
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def self.rails_coap_only
|
29
|
+
defined?(Rails) && Rails.application &&
|
30
|
+
!Rails.application.config.coap.only
|
31
|
+
end
|
20
32
|
end
|
21
33
|
end
|
data/lib/david/observe.rb
CHANGED
@@ -73,11 +73,11 @@ module David
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def transmit(exchange, message, options)
|
76
|
-
log.debug
|
76
|
+
log.debug(message.inspect)
|
77
77
|
|
78
78
|
begin
|
79
79
|
server.socket.send(message.to_wire, 0, exchange.host, exchange.port)
|
80
|
-
rescue Timeout::Error, RuntimeError
|
80
|
+
rescue Timeout::Error, RuntimeError, Errno::ENETUNREACH
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
@@ -85,13 +85,19 @@ module David
|
|
85
85
|
every(@tick_interval) { tick }
|
86
86
|
end
|
87
87
|
|
88
|
-
def tick
|
88
|
+
def tick(fiber = true)
|
89
89
|
unless self.empty?
|
90
90
|
log.debug 'Observe tick'
|
91
91
|
log.debug self
|
92
92
|
end
|
93
93
|
|
94
|
-
self.each_key
|
94
|
+
self.each_key do |key|
|
95
|
+
if fiber
|
96
|
+
async.handle_update(key)
|
97
|
+
else
|
98
|
+
handle_update(key)
|
99
|
+
end
|
100
|
+
end
|
95
101
|
end
|
96
102
|
end
|
97
103
|
end
|
data/lib/david/registry.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
|
+
require 'david/fake_logger'
|
2
|
+
|
1
3
|
module David
|
2
4
|
module Registry
|
3
5
|
protected
|
4
6
|
|
5
7
|
def log
|
6
|
-
@log ||=
|
7
|
-
|
8
|
+
@log ||= server.log
|
9
|
+
# In some tests no server actor is present
|
10
|
+
rescue NoMethodError
|
11
|
+
@log ||= FakeLogger.new
|
8
12
|
end
|
9
13
|
|
10
|
-
def gc
|
11
|
-
|
12
|
-
end
|
14
|
+
# def gc
|
15
|
+
# Celluloid::Actor[:gc]
|
16
|
+
# end
|
13
17
|
|
14
|
-
def observe
|
15
|
-
|
16
|
-
end
|
18
|
+
# def observe
|
19
|
+
# Celluloid::Actor[:observe]
|
20
|
+
# end
|
17
21
|
|
18
22
|
def server
|
19
23
|
Celluloid::Actor[:server]
|
data/lib/david/server/mapping.rb
CHANGED
@@ -3,7 +3,7 @@ module David
|
|
3
3
|
# See https://tools.ietf.org/html/rfc7252#section-12.8
|
4
4
|
module Multicast
|
5
5
|
def multicast_initialize!
|
6
|
-
@socket.to_io.setsockopt(
|
6
|
+
@socket.to_io.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
|
7
7
|
|
8
8
|
if ipv6?
|
9
9
|
maddrs = ['ff02::fd', 'ff05::fd']
|
@@ -18,17 +18,17 @@ module David
|
|
18
18
|
setsockopts_ipv4
|
19
19
|
end
|
20
20
|
|
21
|
-
log.debug
|
21
|
+
log.debug("Joined multicast groups: #{maddrs.join(', ')}")
|
22
22
|
rescue Errno::ENODEV, Errno::EADDRNOTAVAIL
|
23
|
-
log.warn
|
23
|
+
log.warn('Multicast initialization failure: Device not found.')
|
24
24
|
@options[:Multicast] = false
|
25
25
|
end
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
29
|
def multicast_listen_ipv4(address)
|
30
|
-
|
31
|
-
|
30
|
+
@socket.to_io.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP,
|
31
|
+
IPAddr.new(address).hton + IPAddr.new('0.0.0.0').hton)
|
32
32
|
end
|
33
33
|
|
34
34
|
def multicast_listen_ipv6(address)
|
@@ -40,16 +40,17 @@ module David
|
|
40
40
|
ifindex = Socket.if_nametoindex(ifname)
|
41
41
|
end
|
42
42
|
|
43
|
-
|
44
|
-
|
43
|
+
@socket.to_io.setsockopt(Socket::IPPROTO_IPV6, Socket::IPV6_JOIN_GROUP,
|
44
|
+
IPAddr.new(address).hton + [ifindex].pack('i_'))
|
45
45
|
end
|
46
46
|
|
47
47
|
def setsockopts_ipv4
|
48
|
-
@socket.to_io.setsockopt(
|
48
|
+
@socket.to_io.setsockopt(Socket::IPPROTO_IP, Socket::IP_PKTINFO, 1)
|
49
49
|
end
|
50
50
|
|
51
51
|
def setsockopts_ipv6
|
52
|
-
@socket.to_io.setsockopt(
|
52
|
+
@socket.to_io.setsockopt(Socket::IPPROTO_IPV6,
|
53
|
+
Socket::IPV6_RECVPKTINFO, 1)
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
data/lib/david/server/respond.rb
CHANGED
@@ -31,7 +31,7 @@ module David
|
|
31
31
|
cbor = CBOR.load(exchange.message.payload)
|
32
32
|
|
33
33
|
body = body_to_json(cbor)
|
34
|
-
body = body.force_encoding(
|
34
|
+
body = body.force_encoding(ASCII_8BIT) # Rack::Lint insisted...
|
35
35
|
|
36
36
|
env[COAP_CBOR] = cbor
|
37
37
|
env[CONTENT_LENGTH] = body.bytesize
|
@@ -46,7 +46,7 @@ module David
|
|
46
46
|
# No error responses on multicast exchanges.
|
47
47
|
return if exchange.multicast? && !(200..299).include?(code)
|
48
48
|
|
49
|
-
ct = headers[HTTP_CONTENT_TYPE]
|
49
|
+
ct = media_type_strip(headers[HTTP_CONTENT_TYPE])
|
50
50
|
body = body_to_string(body)
|
51
51
|
|
52
52
|
if @options[:CBOR] && ct == CONTENT_TYPE_JSON
|
data/lib/david/server.rb
CHANGED
@@ -11,7 +11,7 @@ module David
|
|
11
11
|
include Multicast
|
12
12
|
include Respond
|
13
13
|
|
14
|
-
attr_reader :socket
|
14
|
+
attr_reader :log, :socket
|
15
15
|
|
16
16
|
finalizer :shutdown
|
17
17
|
|
@@ -19,6 +19,7 @@ module David
|
|
19
19
|
@app = app.respond_to?(:new) ? app.new : app
|
20
20
|
@mid_cache = {}
|
21
21
|
@options = AppConfig.new(options)
|
22
|
+
@log = @options[:Log]
|
22
23
|
|
23
24
|
host, port = @options.values_at(:Host, :Port)
|
24
25
|
|
@@ -50,6 +51,18 @@ module David
|
|
50
51
|
|
51
52
|
private
|
52
53
|
|
54
|
+
def answer(exchange, key = nil)
|
55
|
+
@socket.send(exchange.message.to_wire, 0, exchange.host, exchange.port)
|
56
|
+
|
57
|
+
if log.info?
|
58
|
+
log.info('-> ' + exchange.to_s)
|
59
|
+
log.debug(exchange.message.inspect)
|
60
|
+
end
|
61
|
+
|
62
|
+
key ||= exchange.key
|
63
|
+
cache_add(key, exchange.message) if exchange.ack?
|
64
|
+
end
|
65
|
+
|
53
66
|
def dispatch(*args)
|
54
67
|
data, sender, _, anc = args
|
55
68
|
|
@@ -67,6 +80,8 @@ module David
|
|
67
80
|
log.info('<- ' + exchange.to_s)
|
68
81
|
log.debug(message.inspect)
|
69
82
|
|
83
|
+
pong(exchange) and return if exchange.ping?
|
84
|
+
|
70
85
|
key = exchange.key
|
71
86
|
cached = cache_get(key)
|
72
87
|
|
@@ -86,13 +101,8 @@ module David
|
|
86
101
|
end
|
87
102
|
|
88
103
|
unless response.nil?
|
89
|
-
|
90
|
-
|
91
|
-
exchange.message = response if log.info?
|
92
|
-
log.info('-> ' + exchange.to_s)
|
93
|
-
log.debug(response.inspect)
|
94
|
-
|
95
|
-
cache_add(key, response) if response.tt == :ack
|
104
|
+
exchange.message = response
|
105
|
+
answer(exchange, key)
|
96
106
|
end
|
97
107
|
end
|
98
108
|
|
@@ -100,6 +110,11 @@ module David
|
|
100
110
|
IPAddr.new(@options[:Host]).ipv6?
|
101
111
|
end
|
102
112
|
|
113
|
+
def pong(exchange)
|
114
|
+
exchange.message.tt = :ack
|
115
|
+
answer(exchange)
|
116
|
+
end
|
117
|
+
|
103
118
|
def shutdown
|
104
119
|
@socket.close unless @socket.nil?
|
105
120
|
end
|
data/lib/david/version.rb
CHANGED
data/lib/rack/hello_world.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
module Rack
|
2
2
|
class HelloWorld
|
3
|
-
# include Celluloid
|
4
|
-
|
5
3
|
def call(env)
|
6
4
|
dup._call(env)
|
7
5
|
end
|
@@ -12,11 +10,6 @@ module Rack
|
|
12
10
|
end
|
13
11
|
|
14
12
|
return case env['PATH_INFO']
|
15
|
-
when '/.well-known/core'
|
16
|
-
[200,
|
17
|
-
{'Content-Type' => 'application/link-format'},
|
18
|
-
['</hello>;rt="hello";ct=0']
|
19
|
-
]
|
20
13
|
when '/echo/accept'
|
21
14
|
[200,
|
22
15
|
{'Content-Type' => env['HTTP_ACCEPT'], 'Content-Length' => '0'},
|
@@ -28,12 +21,6 @@ module Rack
|
|
28
21
|
{'Content-Type' => 'text/plain', 'Content-Length' => '12'},
|
29
22
|
['Hello World!']
|
30
23
|
]
|
31
|
-
when '/wait'
|
32
|
-
sleep 10
|
33
|
-
[200,
|
34
|
-
{'Content-Type' => 'text/plain'},
|
35
|
-
['You waited!']
|
36
|
-
]
|
37
24
|
when '/value'
|
38
25
|
@@value ||= 0
|
39
26
|
@@value += 1
|
@@ -80,7 +67,7 @@ module Rack
|
|
80
67
|
|
81
68
|
[200,
|
82
69
|
{
|
83
|
-
'Content-Type' => 'application/json',
|
70
|
+
'Content-Type' => 'application/json; charset=utf8',
|
84
71
|
'Content-Length' => body.bytesize.to_s
|
85
72
|
},
|
86
73
|
[body]
|
data/spec/observe_spec.rb
CHANGED
@@ -19,8 +19,13 @@ describe Observe do
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
let(:dummy1)
|
23
|
-
|
22
|
+
let(:dummy1) do
|
23
|
+
[@exchange1, {'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/'}, '1']
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:dummy2) do
|
27
|
+
[@exchange2, {'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/hello'}, '1']
|
28
|
+
end
|
24
29
|
|
25
30
|
subject { Celluloid::Actor[:observe] }
|
26
31
|
|
@@ -30,6 +35,11 @@ describe Observe do
|
|
30
35
|
let!(:key) { [dummy1[0].host, dummy1[0].token] }
|
31
36
|
let!(:value) { subject[key] }
|
32
37
|
|
38
|
+
it '#to_s' do
|
39
|
+
s = '["127.0.0.1", ' + dummy1[0].token.to_s + ', "/", 0]'
|
40
|
+
expect(subject.to_s).to eq(s)
|
41
|
+
end
|
42
|
+
|
33
43
|
context 'key' do
|
34
44
|
it 'presence' do
|
35
45
|
expect(subject.size).to eq(1)
|
@@ -109,4 +119,79 @@ describe Observe do
|
|
109
119
|
describe '#tick' do
|
110
120
|
# Couldn't get mocking to work decently.
|
111
121
|
end
|
122
|
+
|
123
|
+
describe '#bump' do
|
124
|
+
let!(:key) { [dummy1[0].host, dummy1[0].token] }
|
125
|
+
|
126
|
+
let(:n) { rand(0xff) }
|
127
|
+
let(:response) { dummy1[0].message }
|
128
|
+
|
129
|
+
before { subject.add(*dummy1) }
|
130
|
+
|
131
|
+
it 'shall change entry' do
|
132
|
+
subject.send(:bump, key, n, response)
|
133
|
+
|
134
|
+
expect(subject[key][0]).to eq(n)
|
135
|
+
expect(subject[key][3]).to eq(response.options[:etag])
|
136
|
+
expect(subject[key][4]).to be <= Time.now.to_i
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '#handle_update' do
|
141
|
+
let(:port) { random_port }
|
142
|
+
|
143
|
+
let!(:server) { supervised_server(:Port => port) }
|
144
|
+
|
145
|
+
context 'error (4.04)' do
|
146
|
+
let!(:key) { [dummy1[0].host, dummy1[0].token] }
|
147
|
+
|
148
|
+
before do
|
149
|
+
dummy1[0].port = port
|
150
|
+
subject.add(*dummy1)
|
151
|
+
subject.send(:handle_update, key)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'delete' do
|
155
|
+
expect(subject[key]).to eq(nil)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'update (2.05)' do
|
160
|
+
let!(:key) { [dummy2[0].host, dummy2[0].token] }
|
161
|
+
|
162
|
+
before do
|
163
|
+
dummy2[0].port = port
|
164
|
+
subject.add(*dummy2)
|
165
|
+
subject.send(:handle_update, key)
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'bumped' do
|
169
|
+
expect(subject[key][0]).to eq(1)
|
170
|
+
expect(subject[key][3]).to eq(dummy2[0].message.options[:etag])
|
171
|
+
expect(subject[key][4]).to be <= Time.now.to_i
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe '#tick' do
|
177
|
+
let(:port) { random_port }
|
178
|
+
|
179
|
+
let!(:server) { supervised_server(:Port => port) }
|
180
|
+
|
181
|
+
context 'update (2.05)' do
|
182
|
+
let!(:key) { [dummy2[0].host, dummy2[0].token] }
|
183
|
+
|
184
|
+
before do
|
185
|
+
dummy2[0].port = port
|
186
|
+
subject.add(*dummy2)
|
187
|
+
subject.send(:tick, false)
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'bumped' do
|
191
|
+
expect(subject[key][0]).to eq(1)
|
192
|
+
expect(subject[key][3]).to eq(dummy2[0].message.options[:etag])
|
193
|
+
expect(subject[key][4]).to be <= Time.now.to_i
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
112
197
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -51,7 +51,9 @@ module David
|
|
51
51
|
|
52
52
|
app = options.delete(:app) || Rack::HelloWorld
|
53
53
|
|
54
|
-
|
54
|
+
David::Server.supervise_as(:server, app, defaults.merge(options))
|
55
|
+
|
56
|
+
server = Celluloid::Actor[:server]
|
55
57
|
server.async.run
|
56
58
|
|
57
59
|
server
|
metadata
CHANGED
@@ -1,91 +1,91 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: david
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- henning mueller
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: celluloid-io
|
15
|
-
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0.16'
|
20
|
-
- -
|
20
|
+
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: 0.16.1
|
23
|
-
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
26
|
requirements:
|
25
|
-
- - ~>
|
27
|
+
- - "~>"
|
26
28
|
- !ruby/object:Gem::Version
|
27
29
|
version: '0.16'
|
28
|
-
- -
|
30
|
+
- - ">="
|
29
31
|
- !ruby/object:Gem::Version
|
30
32
|
version: 0.16.1
|
31
|
-
prerelease: false
|
32
|
-
type: :runtime
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: coap
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - ~>
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '0.1'
|
40
35
|
requirement: !ruby/object:Gem::Requirement
|
41
36
|
requirements:
|
42
|
-
- -
|
37
|
+
- - ">="
|
43
38
|
- !ruby/object:Gem::Version
|
44
39
|
version: '0.1'
|
45
|
-
prerelease: false
|
46
40
|
type: :runtime
|
47
|
-
|
48
|
-
name: rack
|
41
|
+
prerelease: false
|
49
42
|
version_requirements: !ruby/object:Gem::Requirement
|
50
43
|
requirements:
|
51
|
-
- -
|
44
|
+
- - ">="
|
52
45
|
- !ruby/object:Gem::Version
|
53
|
-
version: '1
|
46
|
+
version: '0.1'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rack
|
54
49
|
requirement: !ruby/object:Gem::Requirement
|
55
50
|
requirements:
|
56
|
-
- - ~>
|
51
|
+
- - "~>"
|
57
52
|
- !ruby/object:Gem::Version
|
58
|
-
version: '1.
|
59
|
-
prerelease: false
|
53
|
+
version: '1.6'
|
60
54
|
type: :runtime
|
61
|
-
|
62
|
-
name: rake
|
55
|
+
prerelease: false
|
63
56
|
version_requirements: !ruby/object:Gem::Requirement
|
64
57
|
requirements:
|
65
|
-
- -
|
58
|
+
- - "~>"
|
66
59
|
- !ruby/object:Gem::Version
|
67
|
-
version: '
|
60
|
+
version: '1.6'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rake
|
68
63
|
requirement: !ruby/object:Gem::Requirement
|
69
64
|
requirements:
|
70
|
-
- -
|
65
|
+
- - ">="
|
71
66
|
- !ruby/object:Gem::Version
|
72
67
|
version: '0'
|
73
|
-
prerelease: false
|
74
68
|
type: :development
|
75
|
-
|
76
|
-
name: rspec
|
69
|
+
prerelease: false
|
77
70
|
version_requirements: !ruby/object:Gem::Requirement
|
78
71
|
requirements:
|
79
|
-
- -
|
72
|
+
- - ">="
|
80
73
|
- !ruby/object:Gem::Version
|
81
|
-
version: '
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rspec
|
82
77
|
requirement: !ruby/object:Gem::Requirement
|
83
78
|
requirements:
|
84
|
-
- - ~>
|
79
|
+
- - "~>"
|
85
80
|
- !ruby/object:Gem::Version
|
86
|
-
version: '3.
|
87
|
-
prerelease: false
|
81
|
+
version: '3.2'
|
88
82
|
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.2'
|
89
89
|
description: |-
|
90
90
|
David is a CoAP server with Rack interface to bring the
|
91
91
|
illustrious family of Rack compatible web frameworks into the Internet of
|
@@ -96,8 +96,8 @@ executables:
|
|
96
96
|
extensions: []
|
97
97
|
extra_rdoc_files: []
|
98
98
|
files:
|
99
|
-
- .gitignore
|
100
|
-
- .travis.yml
|
99
|
+
- ".gitignore"
|
100
|
+
- ".travis.yml"
|
101
101
|
- Gemfile
|
102
102
|
- Gemfile.lock
|
103
103
|
- LICENSE
|
@@ -107,6 +107,7 @@ files:
|
|
107
107
|
- benchmarks/Gemfile
|
108
108
|
- benchmarks/Gemfile.lock
|
109
109
|
- benchmarks/coapbench.sh
|
110
|
+
- benchmarks/plot.r
|
110
111
|
- benchmarks/quick.sh
|
111
112
|
- benchmarks/rackup/Gemfile
|
112
113
|
- benchmarks/rackup/Gemfile.lock
|
@@ -115,6 +116,7 @@ files:
|
|
115
116
|
- benchmarks/rackup/rails.ru
|
116
117
|
- benchmarks/rps.rb
|
117
118
|
- benchmarks/stress.sh
|
119
|
+
- benchmarks/tocsv.rb
|
118
120
|
- bin/david
|
119
121
|
- config.ru
|
120
122
|
- david.gemspec
|
@@ -220,24 +222,24 @@ homepage: https://github.com/nning/david
|
|
220
222
|
licenses:
|
221
223
|
- GPL-3.0
|
222
224
|
metadata: {}
|
223
|
-
post_install_message:
|
225
|
+
post_install_message:
|
224
226
|
rdoc_options: []
|
225
227
|
require_paths:
|
226
228
|
- lib
|
227
229
|
required_ruby_version: !ruby/object:Gem::Requirement
|
228
230
|
requirements:
|
229
|
-
- -
|
231
|
+
- - ">="
|
230
232
|
- !ruby/object:Gem::Version
|
231
233
|
version: '0'
|
232
234
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
233
235
|
requirements:
|
234
|
-
- -
|
236
|
+
- - ">="
|
235
237
|
- !ruby/object:Gem::Version
|
236
238
|
version: '0'
|
237
239
|
requirements: []
|
238
|
-
rubyforge_project:
|
239
|
-
rubygems_version: 2.
|
240
|
-
signing_key:
|
240
|
+
rubyforge_project:
|
241
|
+
rubygems_version: 2.4.5
|
242
|
+
signing_key:
|
241
243
|
specification_version: 4
|
242
244
|
summary: CoAP server with Rack interface.
|
243
245
|
test_files:
|