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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b97cbebfb17845f2a5fa678fa026b40c2a843c40
4
- data.tar.gz: 8cee683c847e3ea1f1a93a501904dcdcb7be2312
3
+ metadata.gz: ef49e2a7d5047bfbaf7a2146d2998495c7571f74
4
+ data.tar.gz: cc09675dd5021fe2a49c43224f0e5d5b713f5d9c
5
5
  SHA512:
6
- metadata.gz: 43be48293b3b0c45eabfe106aadbb6dc120db4224d01b09d752dedddb7525e22f03289b1497d28aa5e3c2c5a0a6ea614e9f433df1789b14bda8c5183b904fa1b
7
- data.tar.gz: fa3af2ac69c7a12c4f9d67c70c0f261ef4a7dc3a216e4b6054f719233b45d30877335679baaf664a1f3e4eee80cce67fd943871f2d33d5090d49b57963f94349
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.0)
4
+ david (0.4.1)
5
5
  celluloid-io (~> 0.16, >= 0.16.1)
6
- coap (~> 0.1)
7
- rack (~> 1.5)
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.0)
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.0)
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.1)
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. You can also utilize [JBuilder
27
- templates](https://github.com/rails/jbuilder) for easy JSON generation.
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
- You can activate automatic transcoding between JSON and CBOR by setting the
31
- Rack environment option `CBOR` or `config.coap.cbor` in your Rails application
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 10.000 requests per second (tested in MRI 2.2.0 with up to
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
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../../coap
3
3
  specs:
4
- coap (0.1.0)
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.1)
14
+ celluloid-io (0.16.2)
15
15
  celluloid (>= 0.16.0)
16
- nio4r (>= 1.0.0)
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.0.pre)
4
+ david (0.4.1.pre)
5
5
  celluloid-io (~> 0.16, >= 0.16.1)
6
- coap (~> 0.1)
7
- rack (~> 1.5)
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.0)
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
@@ -1,6 +1,8 @@
1
1
  #!/bin/bash
2
2
  # (60+(28*3*(30+15)))/60=64
3
3
 
4
+ ulimit -n 10240
5
+
4
6
  uri="coap://[::1]:5683/hello"
5
7
 
6
8
  ./coapbench.sh -c 1000 -t 60 $uri > /dev/null 2>&1
@@ -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', '~> 0.1'
25
- s.add_dependency 'rack', '~> 1.5'
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.1'
28
+ s.add_development_dependency 'rspec', '~> 3.2'
29
29
  end
@@ -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"
@@ -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
@@ -1,11 +1,12 @@
1
1
  module David
2
- class FakeLogger
3
- def initialize
4
- Celluloid.logger = nil
5
- end
2
+ class FakeLogger
3
+ def initialize
4
+ Celluloid.logger = nil
5
+ end
6
6
 
7
- [:info, :debug, :warn, :error, :fatal].each do |method|
8
- define_method(method) { |*args| }
9
- end
10
- end
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
- pick ['david', 'thin', 'puma', 'webrick']
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 message.inspect
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 { |key| async.handle_update(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
@@ -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 ||= Celluloid.logger
7
- @log ||= ::Logger.new(nil)
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
- Celluloid::Actor[:gc]
12
- end
14
+ # def gc
15
+ # Celluloid::Actor[:gc]
16
+ # end
13
17
 
14
- def observe
15
- Celluloid::Actor[:observe]
16
- end
18
+ # def observe
19
+ # Celluloid::Actor[:observe]
20
+ # end
17
21
 
18
22
  def server
19
23
  Celluloid::Actor[:server]
@@ -44,6 +44,8 @@ module David
44
44
  HTTP_CONTENT_TYPE = 'Content-Type'.freeze
45
45
  HTTP_ETAG = 'ETag'.freeze
46
46
  HTTP_LOCATION = 'Location'.freeze
47
+
48
+ ASCII_8BIT = 'ASCII-8BIT'.freeze
47
49
  end
48
50
  end
49
51
  end
@@ -98,6 +98,11 @@ module David
98
98
  nil
99
99
  end
100
100
 
101
+ def media_type_strip(media_type)
102
+ return nil if media_type.nil?
103
+ media_type.split(';')[0]
104
+ end
105
+
101
106
  def method_to_http(method)
102
107
  method.to_s.upcase
103
108
  end
@@ -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(:SOL_SOCKET, :SO_REUSEADDR, 1)
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 "Joined multicast groups: #{maddrs.join(', ')}"
21
+ log.debug("Joined multicast groups: #{maddrs.join(', ')}")
22
22
  rescue Errno::ENODEV, Errno::EADDRNOTAVAIL
23
- log.warn 'Multicast initialization failure: Device not found.'
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
- mreq = IPAddr.new(address).hton + IPAddr.new('0.0.0.0').hton
31
- @socket.to_io.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, mreq)
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
- mreq = IPAddr.new(address).hton + [ifindex].pack('i_')
44
- @socket.to_io.setsockopt(:IPPROTO_IPV6, :IPV6_JOIN_GROUP, mreq)
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(:IPPROTO_IP, :IP_PKTINFO, 1)
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(:IPPROTO_IPV6, :IPV6_RECVPKTINFO, 1)
52
+ @socket.to_io.setsockopt(Socket::IPPROTO_IPV6,
53
+ Socket::IPV6_RECVPKTINFO, 1)
53
54
  end
54
55
  end
55
56
  end
@@ -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('ASCII-8BIT') # Rack::Lint insisted...
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
- @socket.send(response.to_wire, 0, exchange.host, exchange.port)
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
@@ -1,7 +1,7 @@
1
1
  module David
2
2
  MAJOR = 0
3
3
  MINOR = 4
4
- PATCH = 0
4
+ PATCH = 1
5
5
  SUFFIX = nil
6
6
 
7
7
  VERSION = [MAJOR, MINOR, PATCH, SUFFIX].compact.join('.')
@@ -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) { [@exchange1, {'PATH_INFO' => '/'}, '1'] }
23
- let(:dummy2) { [@exchange2, {'PATH_INFO' => '/'}, '1'] }
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
- server = David::Server.new(app, defaults.merge(options))
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.0
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-05 00:00:00.000000000 Z
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
- version_requirements: !ruby/object:Gem::Requirement
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
- requirement: !ruby/object:Gem::Requirement
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
- - !ruby/object:Gem::Dependency
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.5'
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.5'
59
- prerelease: false
53
+ version: '1.6'
60
54
  type: :runtime
61
- - !ruby/object:Gem::Dependency
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: '0'
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
- - !ruby/object:Gem::Dependency
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: '3.1'
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.1'
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.1.9
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: