Implemented support for HTTP HEAD and OPTIONS methods

parent 333c9182
......@@ -23,6 +23,7 @@ CONFIG_HTTP = {
"SERVER_NAME": "jetconf-h2",
"UPLOAD_SIZE_LIMIT": 1,
"LISTEN_LOCALHOST_ONLY": False,
"AC_ALLOW_ORIGIN": "http://localhost:3000",
"PORT": 8443,
"SERVER_SSL_CERT": "server.crt",
......
......@@ -427,9 +427,11 @@ class BaseDatastore:
sdh = STATE_DATA_HANDLES.get_handler(state_root_sch_pth)
if sdh is not None:
if isinstance(sdh, ContainerNodeHandlerBase):
state_handler_val = sdh.generate_node(ii_gen, staging)
state_handler_val = sdh.generate_node(ii_gen, rpc.username, staging)
elif isinstance(sdh, ListNodeHandlerBase):
state_handler_val = sdh.generate_item(ii_gen, staging)
print("node={}".format(node))
print("iigen={}".format(ii_gen))
state_handler_val = sdh.generate_item(ii_gen, rpc.username, staging)
nm_name = state_root_sn.qual_name[0]
node = node.put_member(nm_name, state_handler_val, raw=True).up()
......
......@@ -8,8 +8,8 @@ from colorlog import error, warning as warn, info
from urllib.parse import parse_qs
from typing import Dict, List, Optional
from yangson.exceptions import YangsonException
from yangson.schemanode import NonexistentSchemaNode, ContainerNode, ListNode, GroupNode, LeafListNode, LeafNode
from yangson.exceptions import YangsonException, NonexistentSchemaNode
from yangson.schemanode import ContainerNode, ListNode, GroupNode, LeafListNode, LeafNode
from yangson.instance import NonexistentInstance, InstanceValueError, RootNode
from yangson.datatype import YangTypeError
......@@ -106,7 +106,7 @@ def _get(ds: BaseDatastore, pth: str, username: str, yl_data: bool=False, stagin
rpc1 = RpcInfo()
rpc1.username = username
rpc1.path = url_path
rpc1.path = url_path.rstrip("/")
rpc1.qs = query_string
try:
......@@ -189,7 +189,7 @@ def create_get_api(ds: BaseDatastore):
warn(username + " not allowed to access NACM data")
http_resp = HttpResponse.empty(HttpStatus.Forbidden)
else:
http_resp = _get(ds.nacm.nacm_ds, username, api_pth)
http_resp = _get(ds.nacm.nacm_ds, api_pth, username)
elif ns == "ietf-yang-library":
http_resp = _get(ds, api_pth, username, yl_data=True)
else:
......@@ -260,7 +260,7 @@ def _post(ds: BaseDatastore, pth: str, username: str, data: str) -> HttpResponse
rpc1 = RpcInfo()
rpc1.username = username
rpc1.path = url_path
rpc1.path = url_path.rstrip("/")
rpc1.qs = query_string
try:
......@@ -326,7 +326,7 @@ def _put(ds: BaseDatastore, pth: str, username: str, data: str) -> HttpResponse:
rpc1 = RpcInfo()
rpc1.username = username
rpc1.path = url_path
rpc1.path = url_path.rstrip("/")
try:
json_data = json.loads(data) if len(data) > 0 else {}
......@@ -386,7 +386,7 @@ def _delete(ds: BaseDatastore, pth: str, username: str) -> HttpResponse:
rpc1 = RpcInfo()
rpc1.username = username
rpc1.path = url_path
rpc1.path = url_path.rstrip("/")
try:
ds.lock_data(username)
......@@ -494,3 +494,13 @@ def create_api_op(ds: BaseDatastore):
return http_resp
return api_op_closure
def options_api(headers: OrderedDict, data: Optional[str], client_cert: SSLCertT) -> HttpResponse:
info("api_options: {}".format(headers[":path"]))
headers_extra = OrderedDict()
headers_extra["Allow"] = "GET, PUT, POST, OPTIONS, HEAD, DELETE"
http_resp = HttpResponse(HttpStatus.Ok, bytes(), CT_PLAIN, extra_headers=headers_extra)
return http_resp
......@@ -104,7 +104,7 @@ class H2Protocol(asyncio.Protocol):
headers = request_data.headers
http_method = headers[":method"]
if http_method in ("GET", "DELETE"):
if http_method in ("GET", "DELETE", "OPTIONS", "HEAD"):
self.run_request_handler(headers, event.stream_id, None)
elif http_method in ("PUT", "POST"):
body = request_data.data.getvalue().decode('utf-8')
......@@ -131,27 +131,35 @@ class H2Protocol(asyncio.Protocol):
if dts:
self.transport.write(dts)
def max_chunk_size(self, stream_id: int):
return min(self.conn.max_outbound_frame_size, self.conn.local_flow_control_window(stream_id))
# Find and run handler for specific URI and HTTP method
def run_request_handler(self, headers: OrderedDict, stream_id: int, data: Optional[str]):
url_path = headers[":path"].split("?")[0]
method = headers[":method"]
if method == "HEAD":
h = h2_handlers.get_handler("GET", url_path)
else:
h = h2_handlers.get_handler(method, url_path)
h = h2_handlers.get_handler(headers[":method"], url_path)
if not h:
self.send_response(HttpResponse.empty(HttpStatus.BadRequest), stream_id)
else:
# Run handler and send HTTP response
resp = h(headers, data, self.client_cert)
if method == "HEAD":
resp.data = bytes()
self.send_response(resp, stream_id)
def max_chunk_size(self, stream_id: int):
return min(self.conn.max_outbound_frame_size, self.conn.local_flow_control_window(stream_id))
def send_response(self, resp: HttpResponse, stream_id: int):
resp_headers = (
(":status", resp.status_code),
("content-type", resp.content_type),
("content-length", str(len(resp.data))),
("server", CONFIG_HTTP["SERVER_NAME"]),
("Content-Type", resp.content_type),
("Content-Length", str(len(resp.data))),
("Server", CONFIG_HTTP["SERVER_NAME"]),
("Access-Control-Allow-Origin", CONFIG_HTTP["AC_ALLOW_ORIGIN"])
)
if resp.extra_headers:
......@@ -242,6 +250,7 @@ class RestServer:
self.http_handlers.register_handler(lambda m, p: (m == "PUT") and (p.startswith(API_ROOT_data)), api_put)
self.http_handlers.register_handler(lambda m, p: (m == "DELETE") and (p.startswith(API_ROOT_data)), api_delete)
self.http_handlers.register_handler(lambda m, p: (m == "POST") and (p.startswith(API_ROOT_ops)), api_op)
self.http_handlers.register_handler(lambda m, p: (m == "OPTIONS") and (p.startswith(API_ROOT_data)), handlers.options_api)
h2_handlers = self.http_handlers
......
......@@ -340,7 +340,6 @@ class ZoneDataStateHandler(ListNodeHandlerBase):
for pos in found_positions:
rrset_out.pop(pos)
return zone_data
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment