CoAP/coapthon/layers/resourcelayer.py
Björn Freise 5bebe167a9 Cleanup
- removed obsolete imports
- Location-Path and Location-Query only in answers with "Created" Status
- Fixed / for root-resource as RemoteResource for reverse-proxy
- Fixed parsing of long options in serializer.py
- Commented out unit tests reactivated
- Removed default logging
2019-04-23 11:44:04 +02:00

566 lines
24 KiB
Python

from coapthon import defines
from coapthon.messages.response import Response
from coapthon.resources.resource import Resource
__author__ = 'Giacomo Tanganelli'
class ResourceLayer(object):
"""
Handles the Resources.
"""
def __init__(self, parent):
"""
Initialize a Resource Layer.
:type parent: CoAP
:param parent: the CoAP server
"""
self._parent = parent
def edit_resource(self, transaction, path):
"""
Render a POST on an already created resource.
:param path: the path of the resource
:param transaction: the transaction
:return: the transaction
"""
resource_node = self._parent.root[path]
transaction.resource = resource_node
# If-Match
if transaction.request.if_match:
if None not in transaction.request.if_match and str(transaction.resource.etag) \
not in transaction.request.if_match:
transaction.response.code = defines.Codes.PRECONDITION_FAILED.number
return transaction
method = getattr(resource_node, "render_POST", None)
try:
resource = method(request=transaction.request)
except NotImplementedError:
try:
method = getattr(resource_node, "render_POST_advanced", None)
ret = method(request=transaction.request, response=transaction.response)
if isinstance(ret, tuple) and len(ret) == 2 and isinstance(ret[1], Response) \
and isinstance(ret[0], Resource):
# Advanced handler
resource, response = ret
resource.changed = True
resource.observe_count += 1
transaction.resource = resource
transaction.response = response
if transaction.response.code is None:
transaction.response.code = defines.Codes.CREATED.number
return transaction
elif isinstance(ret, tuple) and len(ret) == 3 and isinstance(ret[1], Response) \
and isinstance(ret[0], Resource):
# Advanced handler separate
resource, response, callback = ret
ret = self._handle_separate_advanced(transaction, callback)
if not isinstance(ret, tuple) or \
not (isinstance(ret[0], Resource) and isinstance(ret[1], Response)): # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
resource, response = ret
resource.changed = True
resource.observe_count += 1
transaction.resource = resource
transaction.response = response
if transaction.response.code is None:
transaction.response.code = defines.Codes.CREATED.number
return transaction
else:
raise NotImplementedError
except NotImplementedError:
transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
return transaction
if isinstance(resource, Resource):
pass
elif isinstance(resource, tuple) and len(resource) == 2:
resource, callback = resource
resource = self._handle_separate(transaction, callback)
if not isinstance(resource, Resource): # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
else: # pragma: no cover
# Handle error
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
if resource.path is None:
resource.path = path
resource.observe_count = resource_node.observe_count
if resource is resource_node:
transaction.response.code = defines.Codes.CHANGED.number
else:
transaction.response.code = defines.Codes.CREATED.number
resource.changed = True
resource.observe_count += 1
transaction.resource = resource
assert(isinstance(resource, Resource))
if resource.etag is not None:
transaction.response.etag = resource.etag
if transaction.response.code == defines.Codes.CREATED.number:
# Only on CREATED according to RFC 7252 Chapter 5.8.2 POST
transaction.response.location_path = resource.path
if resource.location_query is not None and len(resource.location_query) > 0:
transaction.response.location_query = resource.location_query
transaction.response.payload = None
self._parent.root[resource.path] = resource
return transaction
def add_resource(self, transaction, parent_resource, lp):
"""
Render a POST on a new resource.
:param transaction: the transaction
:param parent_resource: the parent of the resource
:param lp: the location_path attribute of the resource
:return: the response
"""
method = getattr(parent_resource, "render_POST", None)
try:
resource = method(request=transaction.request)
except NotImplementedError:
try:
method = getattr(parent_resource, "render_POST_advanced", None)
ret = method(request=transaction.request, response=transaction.response)
if isinstance(ret, tuple) and len(ret) == 2 and isinstance(ret[1], Response) \
and isinstance(ret[0], Resource):
# Advanced handler
resource, response = ret
resource.path = lp
resource.changed = True
self._parent.root[resource.path] = resource
transaction.resource = resource
transaction.response = response
if transaction.response.code is None:
transaction.response.code = defines.Codes.CREATED.number
return transaction
elif isinstance(ret, tuple) and len(ret) == 3 and isinstance(ret[1], Response) \
and isinstance(ret[0], Resource):
# Advanced handler separate
resource, response, callback = ret
ret = self._handle_separate_advanced(transaction, callback)
if not isinstance(ret, tuple) or \
not (isinstance(ret[0], Resource) and isinstance(ret[1], Response)): # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
resource, response = ret
resource.path = lp
resource.changed = True
self._parent.root[resource.path] = resource
transaction.resource = resource
transaction.response = response
if transaction.response.code is None:
transaction.response.code = defines.Codes.CREATED.number
return transaction
else:
raise NotImplementedError
except NotImplementedError:
transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
return transaction
if isinstance(resource, Resource):
pass
elif isinstance(resource, tuple) and len(resource) == 2:
resource, callback = resource
resource = self._handle_separate(transaction, callback)
if not isinstance(resource, Resource): # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
else: # pragma: no cover
# Handle error
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
resource.path = lp
if resource.etag is not None:
transaction.response.etag = resource.etag
transaction.response.location_path = resource.path
if resource.location_query is not None and len(resource.location_query) > 0:
transaction.response.location_query = resource.location_query
transaction.response.code = defines.Codes.CREATED.number
transaction.response.payload = None
assert (isinstance(resource, Resource))
if resource.etag is not None:
transaction.response.etag = resource.etag
if resource.max_age is not None:
transaction.response.max_age = resource.max_age
resource.changed = True
transaction.resource = resource
self._parent.root[resource.path] = resource
return transaction
def create_resource(self, path, transaction):
"""
Render a POST request.
:param path: the path of the request
:param transaction: the transaction
:return: the response
"""
t = self._parent.root.with_prefix(path)
max_len = 0
imax = None
for i in t:
if i == path:
# Resource already present
return self.edit_resource(transaction, path)
elif len(i) > max_len:
imax = i
max_len = len(i)
lp = path
parent_resource = self._parent.root[imax]
if parent_resource.allow_children:
return self.add_resource(transaction, parent_resource, lp)
else:
transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
return transaction
def update_resource(self, transaction):
"""
Render a PUT request.
:param transaction: the transaction
:return: the response
"""
# If-Match
if transaction.request.if_match:
if None not in transaction.request.if_match and str(transaction.resource.etag) \
not in transaction.request.if_match:
transaction.response.code = defines.Codes.PRECONDITION_FAILED.number
return transaction
# If-None-Match
if transaction.request.if_none_match:
transaction.response.code = defines.Codes.PRECONDITION_FAILED.number
return transaction
method = getattr(transaction.resource, "render_PUT", None)
try:
resource = method(request=transaction.request)
except NotImplementedError:
try:
method = getattr(transaction.resource, "render_PUT_advanced", None)
ret = method(request=transaction.request, response=transaction.response)
if isinstance(ret, tuple) and len(ret) == 2 and isinstance(ret[1], Response) \
and isinstance(ret[0], Resource):
# Advanced handler
resource, response = ret
resource.changed = True
resource.observe_count += 1
transaction.resource = resource
transaction.response = response
if transaction.response.code is None:
transaction.response.code = defines.Codes.CHANGED.number
return transaction
elif isinstance(ret, tuple) and len(ret) == 3 and isinstance(ret[1], Response) \
and isinstance(ret[0], Resource):
# Advanced handler separate
resource, response, callback = ret
ret = self._handle_separate_advanced(transaction, callback)
if not isinstance(ret, tuple) or \
not (isinstance(ret[0], Resource) and isinstance(ret[1], Response)): # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
resource, response = ret
resource.changed = True
resource.observe_count += 1
transaction.resource = resource
transaction.response = response
if transaction.response.code is None:
transaction.response.code = defines.Codes.CHANGED.number
return transaction
else:
raise NotImplementedError
except NotImplementedError:
transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
return transaction
if isinstance(resource, Resource):
pass
elif isinstance(resource, tuple) and len(resource) == 2:
resource, callback = resource
resource = self._handle_separate(transaction, callback)
if not isinstance(resource, Resource): # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
else: # pragma: no cover
# Handle error
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
if resource.etag is not None:
transaction.response.etag = resource.etag
transaction.response.code = defines.Codes.CHANGED.number
transaction.response.payload = None
assert (isinstance(resource, Resource))
if resource.etag is not None:
transaction.response.etag = resource.etag
if resource.max_age is not None:
transaction.response.max_age = resource.max_age
resource.changed = True
resource.observe_count += 1
transaction.resource = resource
return transaction
def _handle_separate(self, transaction, callback):
# Handle separate
if not transaction.request.acknowledged:
self._parent._send_ack(transaction)
transaction.request.acknowledged = True
resource = callback(request=transaction.request)
return resource
def _handle_separate_advanced(self, transaction, callback):
# Handle separate
if not transaction.request.acknowledged:
self._parent._send_ack(transaction)
transaction.request.acknowledged = True
return callback(request=transaction.request, response=transaction.response)
def delete_resource(self, transaction, path):
"""
Render a DELETE request.
:param transaction: the transaction
:param path: the path
:return: the response
"""
resource = transaction.resource
method = getattr(resource, 'render_DELETE', None)
try:
ret = method(request=transaction.request)
except NotImplementedError:
try:
method = getattr(transaction.resource, "render_DELETE_advanced", None)
ret = method(request=transaction.request, response=transaction.response)
if isinstance(ret, tuple) and len(ret) == 2 and isinstance(ret[1], Response) \
and isinstance(ret[0], bool):
# Advanced handler
delete, response = ret
if delete:
del self._parent.root[path]
transaction.response = response
if transaction.response.code is None:
transaction.response.code = defines.Codes.DELETED.number
return transaction
elif isinstance(ret, tuple) and len(ret) == 3 and isinstance(ret[1], Response) \
and isinstance(ret[0], Resource):
# Advanced handler separate
resource, response, callback = ret
ret = self._handle_separate_advanced(transaction, callback)
if not isinstance(ret, tuple) or \
not (isinstance(ret[0], bool) and isinstance(ret[1], Response)): # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
delete, response = ret
if delete:
del self._parent.root[path]
transaction.response = response
if transaction.response.code is None:
transaction.response.code = defines.Codes.DELETED.number
return transaction
else:
raise NotImplementedError
except NotImplementedError:
transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
return transaction
if isinstance(ret, bool):
pass
elif isinstance(ret, tuple) and len(ret) == 2:
resource, callback = ret
ret = self._handle_separate(transaction, callback)
if not isinstance(ret, bool): # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
else: # pragma: no cover
# Handle error
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
if ret:
del self._parent.root[path]
transaction.response.code = defines.Codes.DELETED.number
transaction.response.payload = None
transaction.resource.deleted = True
else: # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
def get_resource(self, transaction):
"""
Render a GET request.
:param transaction: the transaction
:return: the transaction
"""
method = getattr(transaction.resource, 'render_GET', None)
transaction.resource.actual_content_type = None
# Accept
if transaction.request.accept is not None:
transaction.resource.actual_content_type = transaction.request.accept
# Render_GET
try:
resource = method(request=transaction.request)
except NotImplementedError:
try:
method = getattr(transaction.resource, "render_GET_advanced", None)
ret = method(request=transaction.request, response=transaction.response)
if isinstance(ret, tuple) and len(ret) == 2 and isinstance(ret[1], Response) \
and isinstance(ret[0], Resource):
# Advanced handler
resource, response = ret
transaction.resource = resource
transaction.response = response
if transaction.response.code is None:
transaction.response.code = defines.Codes.CONTENT.number
return transaction
elif isinstance(ret, tuple) and len(ret) == 3 and isinstance(ret[1], Response) \
and isinstance(ret[0], Resource):
# Advanced handler separate
resource, response, callback = ret
ret = self._handle_separate_advanced(transaction, callback)
if not isinstance(ret, tuple) or \
not (isinstance(ret[0], Resource) and isinstance(ret[1], Response)): # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
resource, response = ret
transaction.resource = resource
transaction.response = response
if transaction.response.code is None:
transaction.response.code = defines.Codes.CONTENT.number
return transaction
else:
raise NotImplementedError
except NotImplementedError:
transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
return transaction
if isinstance(resource, Resource):
pass
elif isinstance(resource, tuple) and len(resource) == 2:
resource, callback = resource
resource = self._handle_separate(transaction, callback)
if not isinstance(resource, Resource): # pragma: no cover
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction
else: # pragma: no cover
# Handle error
transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
return transaction.response
if resource.etag in transaction.request.etag:
transaction.response.code = defines.Codes.VALID.number
else:
transaction.response.code = defines.Codes.CONTENT.number
try:
transaction.response.payload = resource.payload
if resource.actual_content_type is not None \
and resource.actual_content_type != defines.Content_types["text/plain"]:
transaction.response.content_type = resource.actual_content_type
except KeyError:
transaction.response.code = defines.Codes.NOT_ACCEPTABLE.number
return transaction.response
assert(isinstance(resource, Resource))
if resource.etag is not None:
transaction.response.etag = resource.etag
if resource.max_age is not None:
transaction.response.max_age = resource.max_age
transaction.resource = resource
return transaction
def discover(self, transaction):
"""
Render a GET request to the .well-know/core link.
:param transaction: the transaction
:return: the transaction
"""
transaction.response.code = defines.Codes.CONTENT.number
payload = ""
for i in self._parent.root.dump():
if i == "/":
continue
resource = self._parent.root[i]
if resource.visible:
ret = self.valid(transaction.request.uri_query, resource.attributes)
if ret:
payload += self.corelinkformat(resource)
transaction.response.payload = payload
transaction.response.content_type = defines.Content_types["application/link-format"]
return transaction
@staticmethod
def valid(query, attributes):
query = query.split("&")
for q in query:
q = str(q)
assert(isinstance(q, str))
tmp = q.split("=")
if len(tmp) > 1:
k = tmp[0]
v = tmp[1]
if k in attributes:
if v == attributes[k]:
continue
else:
return False
else:
return False
return True
@staticmethod
def corelinkformat(resource):
"""
Return a formatted string representation of the corelinkformat in the tree.
:return: the string
"""
msg = "<" + resource.path + ">;"
assert(isinstance(resource, Resource))
keys = sorted(list(resource.attributes.keys()))
for k in keys:
method = getattr(resource, defines.corelinkformat[k], None)
if method is not None and method != "":
v = method
msg = msg[:-1] + ";" + str(v) + ","
else:
v = resource.attributes[k]
if v is not None:
msg = msg[:-1] + ";" + k + "=" + v + ","
return msg