lib/david/server/respond.rb
require 'david/server/constants'
require 'david/server/mapping'
require 'david/server/utility'
module David
class Server
module Respond
include CoAP::Coding
include Constants
include Mapping
include Registry
include Utility
def respond(exchange, env = nil)
block_enabled = @options[:Block] && exchange.get?
if block_enabled
# Fail if m set.
if exchange.block.more && !exchange.multicast?
return error(exchange, 4.05)
end
end
return error(exchange, 5.05) if exchange.proxy?
env ||= basic_env(exchange)
if @options[:CBOR] && exchange.cbor?
begin
cbor = CBOR.load(exchange.message.payload)
body = body_to_json(cbor)
body = body.force_encoding(ASCII_8BIT) # Rack::Lint insisted...
env[COAP_CBOR] = cbor
env[CONTENT_LENGTH] = body.bytesize.to_s
env[CONTENT_TYPE] = CONTENT_TYPE_JSON
env[RACK_INPUT] = StringIO.new(body)
rescue EOFError, CBOR::MalformedFormatError
end
end
code, headers, body = @app.call(env)
# No error responses on multicast exchanges.
return if exchange.multicast? && !(200..299).include?(code)
ct = media_type_strip(headers[HTTP_CONTENT_TYPE])
body = body_to_string(body)
if @options[:CBOR] && ct == CONTENT_TYPE_JSON
begin
body = body_to_cbor(body)
ct = CONTENT_TYPE_CBOR
rescue JSON::ParserError
end
end
# No response on exchange for non-existent block.
return if block_enabled && !exchange.block.included_by?(body)
cf = CoAP::Registry.convert_content_format(ct)
etag = etag_to_coap(headers, 4)
loc = location_to_coap(headers)
ma = max_age_to_coap(headers)
mcode = code_to_coap(code)
# App returned cf different from accept
return error(exchange, 4.06) if exchange.accept && exchange.accept != cf
response = initialize_response(exchange, mcode)
response.options[:content_format] = cf
response.options[:etag] = etag
response.options[:location_path] = loc unless loc.nil?
response.options[:max_age] = ma.to_i unless ma.nil?
if @options[:Observe] && handle_observe(exchange, env, etag)
response.options[:observe] = 0
end
if block_enabled
block = exchange.block.dup
block.set_more!(body)
response.payload = block.chunk(body)
response.options[:block2] = block.encode
else
response.payload = body
end
[response, {}]
end
private
def basic_env(exchange)
m = exchange.message
{
REMOTE_ADDR => exchange.host,
REMOTE_PORT => exchange.port.to_s,
REQUEST_METHOD => method_to_http(m.mcode),
SCRIPT_NAME => EMPTY_STRING,
PATH_INFO => path_encode(m.options[:uri_path]),
QUERY_STRING => query_encode(m.options[:uri_query])
.gsub(/^\?/, ''),
SERVER_NAME => @options[:Host],
SERVER_PORT => @options[:Port].to_s,
CONTENT_LENGTH => m.payload.bytesize.to_s,
CONTENT_TYPE => EMPTY_STRING,
HTTP_ACCEPT => accept_to_http(exchange),
RACK_VERSION => [1, 2],
RACK_URL_SCHEME => RACK_URL_SCHEME_HTTP,
RACK_INPUT => StringIO.new(m.payload),
RACK_ERRORS => $stderr,
RACK_MULTITHREAD => true,
RACK_MULTIPROCESS => true,
RACK_RUN_ONCE => false,
RACK_LOGGER => @options[:Log],
COAP_VERSION => 1,
COAP_MULTICAST => exchange.multicast?,
COAP_DTLS => COAP_DTLS_NOSEC,
COAP_DTLS_ID => EMPTY_STRING,
}
end
def error(exchange, mcode)
[initialize_response(exchange, mcode), retransmit: false]
end
def handle_observe(exchange, env, etag)
return unless exchange.get? && exchange.observe?
if exchange.message.options[:observe] == 0
observe.add(exchange, env, etag)
true
else
observe.delete(exchange)
false
end
end
def initialize_response(exchange, mcode = 2.05)
type = exchange.con? ? :ack : :non
CoAP::Message.new \
tt: type,
mcode: mcode,
mid: exchange.message.mid || SecureRandom.random_number(0xffff),
token: exchange.token
end
end
end
end