david 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|