瀏覽代碼

add support for logging XML request conditionally for profiling

Peter Bieringer 2 月之前
父節點
當前提交
64f5e58549

+ 9 - 7
radicale/app/__init__.py

@@ -204,7 +204,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
                              "%s", environ.get("REQUEST_METHOD", "unknown"),
                              environ.get("PATH_INFO", ""), e, exc_info=True)
                 # Make minimal response
-                status, raw_headers, raw_answer = (
+                status, raw_headers, raw_answer, xml_request = (
                     httputils.INTERNAL_SERVER_ERROR)
                 assert isinstance(raw_answer, str)
                 answer = raw_answer.encode("ascii")
@@ -224,12 +224,14 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
         unsafe_path = environ.get("PATH_INFO", "")
         https = environ.get("HTTPS", "")
         profiler = None
+        xml_request = None
 
         context = AuthContext()
 
         """Manage a request."""
         def response(status: int, headers: types.WSGIResponseHeaders,
-                     answer: Union[None, str, bytes]) -> _IntermediateResponse:
+                     answer: Union[None, str, bytes],
+                     xml_request: Union[None, str] = None) -> _IntermediateResponse:
             """Helper to create response from internal types.WSGIResponse"""
             headers = dict(headers)
             content_encoding = "plain"
@@ -468,7 +470,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
             elif self._profiling_per_request_method:
                 self.profiler_per_request_method[request_method].enable()
 
-            status, headers, answer = function(
+            status, headers, answer, xml_request = function(
                 environ, base_prefix, path, user, remote_host, remote_useragent)
 
             # Profiling
@@ -478,13 +480,13 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
             elif self._profiling_per_request_method:
                 self.profiler_per_request_method[request_method].disable()
 
-            if (status, headers, answer) == httputils.NOT_ALLOWED:
+            if (status, headers, answer, xml_request) == httputils.NOT_ALLOWED:
                 logger.info("Access to %r denied for %s", path,
                             repr(user) if user else "anonymous user")
         else:
-            status, headers, answer = httputils.NOT_ALLOWED
+            status, headers, answer, xml_request = httputils.NOT_ALLOWED
 
-        if ((status, headers, answer) == httputils.NOT_ALLOWED and not user and
+        if ((status, headers, answer, xml_request) == httputils.NOT_ALLOWED and not user and
                 not external_login):
             # Unknown or unauthorized user
             logger.debug("Asking client for authentication")
@@ -494,4 +496,4 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
                 "WWW-Authenticate":
                 "Basic realm=\"%s\"" % self._auth_realm})
 
-        return response(status, headers, answer)
+        return response(status, headers, answer, xml_request)

+ 1 - 1
radicale/app/base.py

@@ -93,7 +93,7 @@ class ApplicationBase:
         """Generate XML error response."""
         headers = {"Content-Type": "text/xml; charset=%s" % self._encoding}
         content = self._xml_response(xmlutils.webdav_error(human_tag))
-        return status, headers, content
+        return status, headers, content, None
 
 
 class Access:

+ 1 - 1
radicale/app/delete.py

@@ -110,4 +110,4 @@ class ApplicationPartDelete(ApplicationBase):
             for notification_item in hook_notification_item_list:
                 self._hook.notify(notification_item)
             headers = {"Content-Type": "text/xml; charset=%s" % self._encoding}
-            return client.OK, headers, self._xml_response(xml_answer)
+            return client.OK, headers, self._xml_response(xml_answer), None

+ 1 - 1
radicale/app/get.py

@@ -109,4 +109,4 @@ class ApplicationPartGet(ApplicationBase):
             if content_disposition:
                 headers["Content-Disposition"] = content_disposition
             answer = item.serialize()
-            return client.OK, headers, answer
+            return client.OK, headers, answer, None

+ 1 - 1
radicale/app/mkcalendar.py

@@ -89,4 +89,4 @@ class ApplicationPartMkcalendar(ApplicationBase):
                     logger.warning(
                         "Bad MKCALENDAR request on %r: %s", path, e, exc_info=True)
                     return httputils.BAD_REQUEST
-            return client.CREATED, {}, None
+            return client.CREATED, {}, None, xmlutils.pretty_xml(xml_content)

+ 1 - 1
radicale/app/mkcol.py

@@ -94,4 +94,4 @@ class ApplicationPartMkcol(ApplicationBase):
                         "Bad MKCOL request on %r (type:%s): %s", path, collection_type, e, exc_info=True)
                     return httputils.BAD_REQUEST
             logger.info("MKCOL request %r (type:%s): %s", path, collection_type, "successful")
-            return client.CREATED, {}, None
+            return client.CREATED, {}, None, xmlutils.pretty_xml(xml_content)

+ 1 - 1
radicale/app/move.py

@@ -127,4 +127,4 @@ class ApplicationPartMove(ApplicationBase):
                     logger.warning(
                         "Bad MOVE request on %r: %s", path, e, exc_info=True)
                     return httputils.BAD_REQUEST
-            return client.NO_CONTENT if to_item else client.CREATED, {}, None
+            return client.NO_CONTENT if to_item else client.CREATED, {}, None, None

+ 1 - 1
radicale/app/options.py

@@ -33,4 +33,4 @@ class ApplicationPartOptions(ApplicationBase):
             "Allow": ", ".join(
                 name[3:] for name in dir(self) if name.startswith("do_")),
             "DAV": httputils.DAV_HEADERS}
-        return client.OK, headers, None
+        return client.OK, headers, None, None

+ 1 - 1
radicale/app/propfind.py

@@ -410,4 +410,4 @@ class ApplicationPartPropfind(ApplicationBase):
                                       allowed_items, user, self._encoding)
             if xml_answer is None:
                 return httputils.NOT_ALLOWED
-            return client.MULTI_STATUS, headers, self._xml_response(xml_answer)
+            return client.MULTI_STATUS, headers, self._xml_response(xml_answer), xmlutils.pretty_xml(xml_content)

+ 1 - 1
radicale/app/proppatch.py

@@ -131,4 +131,4 @@ class ApplicationPartProppatch(ApplicationBase):
                     logger.warning(
                         "Bad PROPPATCH request on %r: %s", path, e, exc_info=True)
                     return httputils.BAD_REQUEST
-            return client.MULTI_STATUS, headers, self._xml_response(xml_answer)
+            return client.MULTI_STATUS, headers, self._xml_response(xml_answer), xmlutils.pretty_xml(xml_content)

+ 2 - 2
radicale/app/put.py

@@ -334,7 +334,7 @@ class ApplicationPartPut(ApplicationBase):
                 if (item and item.uid == prepared_item.uid):
                     logger.debug("PUT request updated existing item %r", path)
                     headers = {"ETag": etag}
-                    return client.NO_CONTENT, headers, None
+                    return client.NO_CONTENT, headers, None, None
 
             headers = {"ETag": etag}
-            return client.CREATED, headers, None
+            return client.CREATED, headers, None, None

+ 2 - 2
radicale/app/report.py

@@ -815,7 +815,7 @@ class ApplicationPartReport(ApplicationBase):
                         "Bad REPORT request on %r: %s", path, e, exc_info=True)
                     return httputils.BAD_REQUEST
                 headers = {"Content-Type": "text/calendar; charset=%s" % self._encoding}
-                return status, headers, str(body)
+                return status, headers, str(body), xmlutils.pretty_xml(xml_content)
             else:
                 try:
                     status, xml_answer = xml_report(
@@ -826,4 +826,4 @@ class ApplicationPartReport(ApplicationBase):
                         "Bad REPORT request on %r: %s", path, e, exc_info=True)
                     return httputils.BAD_REQUEST
                 headers = {"Content-Type": "text/xml; charset=%s" % self._encoding}
-                return status, headers, self._xml_response(xml_answer)
+                return status, headers, self._xml_response(xml_answer), xmlutils.pretty_xml(xml_content)

+ 15 - 15
radicale/httputils.py

@@ -49,42 +49,42 @@ else:
 
 NOT_ALLOWED: types.WSGIResponse = (
     client.FORBIDDEN, (("Content-Type", "text/plain"),),
-    "Access to the requested resource forbidden.")
+    "Access to the requested resource forbidden.", None)
 FORBIDDEN: types.WSGIResponse = (
     client.FORBIDDEN, (("Content-Type", "text/plain"),),
-    "Action on the requested resource refused.")
+    "Action on the requested resource refused.", None)
 BAD_REQUEST: types.WSGIResponse = (
-    client.BAD_REQUEST, (("Content-Type", "text/plain"),), "Bad Request")
+    client.BAD_REQUEST, (("Content-Type", "text/plain"),), "Bad Request", None)
 NOT_FOUND: types.WSGIResponse = (
     client.NOT_FOUND, (("Content-Type", "text/plain"),),
-    "The requested resource could not be found.")
+    "The requested resource could not be found.", None)
 CONFLICT: types.WSGIResponse = (
     client.CONFLICT, (("Content-Type", "text/plain"),),
-    "Conflict in the request.")
+    "Conflict in the request.", None)
 METHOD_NOT_ALLOWED: types.WSGIResponse = (
     client.METHOD_NOT_ALLOWED, (("Content-Type", "text/plain"),),
-    "The method is not allowed on the requested resource.")
+    "The method is not allowed on the requested resource.", None)
 PRECONDITION_FAILED: types.WSGIResponse = (
     client.PRECONDITION_FAILED,
-    (("Content-Type", "text/plain"),), "Precondition failed.")
+    (("Content-Type", "text/plain"),), "Precondition failed.", None)
 REQUEST_TIMEOUT: types.WSGIResponse = (
     client.REQUEST_TIMEOUT, (("Content-Type", "text/plain"),),
-    "Connection timed out.")
+    "Connection timed out.", None)
 REQUEST_ENTITY_TOO_LARGE: types.WSGIResponse = (
     client.REQUEST_ENTITY_TOO_LARGE, (("Content-Type", "text/plain"),),
-    "Request body too large.")
+    "Request body too large.", None)
 REMOTE_DESTINATION: types.WSGIResponse = (
     client.BAD_GATEWAY, (("Content-Type", "text/plain"),),
-    "Remote destination not supported.")
+    "Remote destination not supported.", None)
 DIRECTORY_LISTING: types.WSGIResponse = (
     client.FORBIDDEN, (("Content-Type", "text/plain"),),
-    "Directory listings are not supported.")
+    "Directory listings are not supported.", None)
 INSUFFICIENT_STORAGE: types.WSGIResponse = (
     client.INSUFFICIENT_STORAGE, (("Content-Type", "text/plain"),),
-    "Insufficient Storage.  Please contact the administrator.")
+    "Insufficient Storage.  Please contact the administrator.", None)
 INTERNAL_SERVER_ERROR: types.WSGIResponse = (
     client.INTERNAL_SERVER_ERROR, (("Content-Type", "text/plain"),),
-    "A server error occurred.  Please contact the administrator.")
+    "A server error occurred.  Please contact the administrator.", None)
 
 DAV_HEADERS: str = "1, 2, 3, calendar-access, addressbook, extended-mkcol"
 
@@ -159,7 +159,7 @@ def read_request_body(configuration: "config.Configuration",
 def redirect(location: str, status: int = client.FOUND) -> types.WSGIResponse:
     return (status,
             {"Location": location, "Content-Type": "text/plain"},
-            "Redirected to %s" % location)
+            "Redirected to %s" % location, None)
 
 
 def _serve_traversable(
@@ -214,7 +214,7 @@ def _serve_traversable(
         # adjust on the fly default main.js of InfCloud installation
         logger.debug("Adjust on-the-fly default InfCloud main.js in served page: %r", path)
         answer = answer.replace(b"'InfCloud - the open source CalDAV/CardDAV web client'", b"'InfCloud - the open source CalDAV/CardDAV web client - served through Radicale CalDAV/CardDAV server'")
-    return client.OK, headers, answer
+    return client.OK, headers, answer, None
 
 
 def serve_resource(

+ 2 - 2
radicale/tests/custom/web.py

@@ -28,9 +28,9 @@ class Web(web.BaseWeb):
 
     def get(self, environ: types.WSGIEnviron, base_prefix: str, path: str,
             user: str) -> types.WSGIResponse:
-        return client.OK, {"Content-Type": "text/plain"}, "custom"
+        return client.OK, {"Content-Type": "text/plain"}, "custom", None
 
     def post(self, environ: types.WSGIEnviron, base_prefix: str, path: str,
              user: str) -> types.WSGIResponse:
         content = httputils.read_request_body(self.configuration, environ)
-        return client.OK, {"Content-Type": "text/plain"}, "echo:" + content
+        return client.OK, {"Content-Type": "text/plain"}, "echo:" + content, None

+ 1 - 1
radicale/types.py

@@ -20,7 +20,7 @@ from typing import (Any, Callable, ContextManager, Iterator, List, Mapping,
                     runtime_checkable)
 
 WSGIResponseHeaders = Union[Mapping[str, str], Sequence[Tuple[str, str]]]
-WSGIResponse = Tuple[int, WSGIResponseHeaders, Union[None, str, bytes]]
+WSGIResponse = Tuple[int, WSGIResponseHeaders, Union[None, str, bytes], Union[None, str]]
 WSGIEnviron = Mapping[str, Any]
 WSGIStartResponse = Callable[[str, List[Tuple[str, str]]], Any]
 

+ 1 - 1
radicale/web/none.py

@@ -32,4 +32,4 @@ class Web(web.BaseWeb):
         assert pathutils.sanitize_path(path) == path
         if path != "/.web":
             return httputils.redirect(base_prefix + "/.web")
-        return client.OK, {"Content-Type": "text/plain"}, "Radicale works!"
+        return client.OK, {"Content-Type": "text/plain"}, "Radicale works!", None