Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Implement handling of HEAD requests #7999

Merged
merged 8 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/7999.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a long standing bug where HEAD requests were not properly rendered.
clokep marked this conversation as resolved.
Show resolved Hide resolved
10 changes: 9 additions & 1 deletion synapse/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,14 @@ def render(self, request):
defer.ensureDeferred(self._async_render_wrapper(request))
return NOT_DONE_YET

def _async_render_HEAD(self, request):
"""
The default handling of HEAD method is to treat it as a GET request.

This is an async version of twisted.web.resource.Resource.render_HEAD.
"""
return self._async_render_GET(request)
clokep marked this conversation as resolved.
Show resolved Hide resolved

@wrap_async_request_handler
async def _async_render_wrapper(self, request: SynapseRequest):
"""This is a wrapper that delegates to `_async_render` and handles
Expand Down Expand Up @@ -579,7 +587,7 @@ def set_cors_headers(request: Request):
"""
request.setHeader(b"Access-Control-Allow-Origin", b"*")
request.setHeader(
b"Access-Control-Allow-Methods", b"GET, POST, PUT, DELETE, OPTIONS"
b"Access-Control-Allow-Methods", b"GET, HEAD, POST, PUT, DELETE, OPTIONS"
)
request.setHeader(
b"Access-Control-Allow-Headers",
Expand Down
10 changes: 9 additions & 1 deletion synapse/http/servlet.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ class attribute containing a pre-compiled regular expression. The automatic
instance methods associated with the corresponding HTTP method:

on_GET
on_HEAD
on_PUT
on_POST
on_DELETE
Expand All @@ -284,13 +285,20 @@ def register(self, http_server):
if hasattr(self, "PATTERNS"):
patterns = self.PATTERNS

for method in ("GET", "PUT", "POST", "OPTIONS", "DELETE"):
for method in ("GET", "HEAD", "PUT", "POST", "OPTIONS", "DELETE"):
if hasattr(self, "on_%s" % (method,)):
servlet_classname = self.__class__.__name__
method_handler = getattr(self, "on_%s" % (method,))
http_server.register_paths(
method, patterns, method_handler, servlet_classname
)

# If there's no HEAD handler, but there is a GET handler,
# register it again.
if method == "GET" and not hasattr(self, "on_HEAD"):
http_server.register_paths(
"HEAD", patterns, method_handler, servlet_classname
)

else:
raise NotImplementedError("RestServlet must register something.")
16 changes: 16 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,19 @@ def callback(request, **kwargs):
self.assertEqual(location_headers, [b"/no/over/there"])
cookies_headers = [v for k, v in headers if k == b"Set-Cookie"]
self.assertEqual(cookies_headers, [b"session=yespls"])

def test_head_request(self):
"""A head request should work by being turned into a GET request."""

def callback(request):
request.write(b"response")
request.finish()

res = WrapHtmlRequestHandlerTests.TestResource()
res.callback = callback

request, channel = make_request(self.reactor, b"HEAD", b"/path")
render(request, res, self.reactor)

self.assertEqual(channel.result["code"], b"200")
self.assertNotIn("body", channel.result)