Make the http server handle coroutine-making REST servlets (#5475)

This commit is contained in:
Amber Brown 2019-06-29 17:06:55 +10:00 committed by GitHub
parent c7ff297dde
commit f40a7dc41f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 162 additions and 174 deletions

View file

@ -23,13 +23,13 @@ from six.moves import http_client
import jinja2
from jinja2 import TemplateNotFound
from twisted.internet import defer
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from synapse.api.errors import NotFoundError, StoreError, SynapseError
from synapse.config import ConfigError
from synapse.http.server import finish_request, wrap_html_request_handler
from synapse.http.server import (
DirectServeResource,
finish_request,
wrap_html_request_handler,
)
from synapse.http.servlet import parse_string
from synapse.types import UserID
@ -47,7 +47,7 @@ else:
return a == b
class ConsentResource(Resource):
class ConsentResource(DirectServeResource):
"""A twisted Resource to display a privacy policy and gather consent to it
When accessed via GET, returns the privacy policy via a template.
@ -87,7 +87,7 @@ class ConsentResource(Resource):
Args:
hs (synapse.server.HomeServer): homeserver
"""
Resource.__init__(self)
super().__init__()
self.hs = hs
self.store = hs.get_datastore()
@ -118,18 +118,12 @@ class ConsentResource(Resource):
self._hmac_secret = hs.config.form_secret.encode("utf-8")
def render_GET(self, request):
self._async_render_GET(request)
return NOT_DONE_YET
@wrap_html_request_handler
@defer.inlineCallbacks
def _async_render_GET(self, request):
async def _async_render_GET(self, request):
"""
Args:
request (twisted.web.http.Request):
"""
version = parse_string(request, "v", default=self._default_consent_version)
username = parse_string(request, "u", required=False, default="")
userhmac = None
@ -145,7 +139,7 @@ class ConsentResource(Resource):
else:
qualified_user_id = UserID(username, self.hs.hostname).to_string()
u = yield self.store.get_user_by_id(qualified_user_id)
u = await self.store.get_user_by_id(qualified_user_id)
if u is None:
raise NotFoundError("Unknown user")
@ -165,13 +159,8 @@ class ConsentResource(Resource):
except TemplateNotFound:
raise NotFoundError("Unknown policy version")
def render_POST(self, request):
self._async_render_POST(request)
return NOT_DONE_YET
@wrap_html_request_handler
@defer.inlineCallbacks
def _async_render_POST(self, request):
async def _async_render_POST(self, request):
"""
Args:
request (twisted.web.http.Request):
@ -188,12 +177,12 @@ class ConsentResource(Resource):
qualified_user_id = UserID(username, self.hs.hostname).to_string()
try:
yield self.store.user_set_consent_version(qualified_user_id, version)
await self.store.user_set_consent_version(qualified_user_id, version)
except StoreError as e:
if e.code != 404:
raise
raise NotFoundError("Unknown user")
yield self.registration_handler.post_consent_actions(qualified_user_id)
await self.registration_handler.post_consent_actions(qualified_user_id)
try:
self._render_template(request, "success.html")

View file

@ -16,18 +16,20 @@ import logging
from io import BytesIO
from twisted.internet import defer
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from synapse.api.errors import Codes, SynapseError
from synapse.crypto.keyring import ServerKeyFetcher
from synapse.http.server import respond_with_json_bytes, wrap_json_request_handler
from synapse.http.server import (
DirectServeResource,
respond_with_json_bytes,
wrap_json_request_handler,
)
from synapse.http.servlet import parse_integer, parse_json_object_from_request
logger = logging.getLogger(__name__)
class RemoteKey(Resource):
class RemoteKey(DirectServeResource):
"""HTTP resource for retreiving the TLS certificate and NACL signature
verification keys for a collection of servers. Checks that the reported
X.509 TLS certificate matches the one used in the HTTPS connection. Checks
@ -94,13 +96,8 @@ class RemoteKey(Resource):
self.clock = hs.get_clock()
self.federation_domain_whitelist = hs.config.federation_domain_whitelist
def render_GET(self, request):
self.async_render_GET(request)
return NOT_DONE_YET
@wrap_json_request_handler
@defer.inlineCallbacks
def async_render_GET(self, request):
async def _async_render_GET(self, request):
if len(request.postpath) == 1:
server, = request.postpath
query = {server.decode("ascii"): {}}
@ -114,20 +111,15 @@ class RemoteKey(Resource):
else:
raise SynapseError(404, "Not found %r" % request.postpath, Codes.NOT_FOUND)
yield self.query_keys(request, query, query_remote_on_cache_miss=True)
def render_POST(self, request):
self.async_render_POST(request)
return NOT_DONE_YET
await self.query_keys(request, query, query_remote_on_cache_miss=True)
@wrap_json_request_handler
@defer.inlineCallbacks
def async_render_POST(self, request):
async def _async_render_POST(self, request):
content = parse_json_object_from_request(request)
query = content["server_keys"]
yield self.query_keys(request, query, query_remote_on_cache_miss=True)
await self.query_keys(request, query, query_remote_on_cache_miss=True)
@defer.inlineCallbacks
def query_keys(self, request, query, query_remote_on_cache_miss=False):

View file

@ -14,31 +14,28 @@
# limitations under the License.
#
from twisted.internet import defer
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from synapse.http.server import respond_with_json, wrap_json_request_handler
from synapse.http.server import (
DirectServeResource,
respond_with_json,
wrap_json_request_handler,
)
class MediaConfigResource(Resource):
class MediaConfigResource(DirectServeResource):
isLeaf = True
def __init__(self, hs):
Resource.__init__(self)
super().__init__()
config = hs.get_config()
self.clock = hs.get_clock()
self.auth = hs.get_auth()
self.limits_dict = {"m.upload.size": config.max_upload_size}
def render_GET(self, request):
self._async_render_GET(request)
return NOT_DONE_YET
@wrap_json_request_handler
@defer.inlineCallbacks
def _async_render_GET(self, request):
yield self.auth.get_user_by_req(request)
async def _async_render_GET(self, request):
await self.auth.get_user_by_req(request)
respond_with_json(request, 200, self.limits_dict, send_cors=True)
def render_OPTIONS(self, request):

View file

@ -14,37 +14,31 @@
# limitations under the License.
import logging
from twisted.internet import defer
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
import synapse.http.servlet
from synapse.http.server import set_cors_headers, wrap_json_request_handler
from synapse.http.server import (
DirectServeResource,
set_cors_headers,
wrap_json_request_handler,
)
from ._base import parse_media_id, respond_404
logger = logging.getLogger(__name__)
class DownloadResource(Resource):
class DownloadResource(DirectServeResource):
isLeaf = True
def __init__(self, hs, media_repo):
Resource.__init__(self)
super().__init__()
self.media_repo = media_repo
self.server_name = hs.hostname
# this is expected by @wrap_json_request_handler
self.clock = hs.get_clock()
def render_GET(self, request):
self._async_render_GET(request)
return NOT_DONE_YET
@wrap_json_request_handler
@defer.inlineCallbacks
def _async_render_GET(self, request):
async def _async_render_GET(self, request):
set_cors_headers(request)
request.setHeader(
b"Content-Security-Policy",
@ -58,7 +52,7 @@ class DownloadResource(Resource):
)
server_name, media_id, name = parse_media_id(request)
if server_name == self.server_name:
yield self.media_repo.get_local_media(request, media_id, name)
await self.media_repo.get_local_media(request, media_id, name)
else:
allow_remote = synapse.http.servlet.parse_boolean(
request, "allow_remote", default=True
@ -72,4 +66,4 @@ class DownloadResource(Resource):
respond_404(request)
return
yield self.media_repo.get_remote_media(request, server_name, media_id, name)
await self.media_repo.get_remote_media(request, server_name, media_id, name)

View file

@ -32,12 +32,11 @@ from canonicaljson import json
from twisted.internet import defer
from twisted.internet.error import DNSLookupError
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from synapse.api.errors import Codes, SynapseError
from synapse.http.client import SimpleHttpClient
from synapse.http.server import (
DirectServeResource,
respond_with_json,
respond_with_json_bytes,
wrap_json_request_handler,
@ -58,11 +57,11 @@ _charset_match = re.compile(br"<\s*meta[^>]*charset\s*=\s*([a-z0-9-]+)", flags=r
_content_type_match = re.compile(r'.*; *charset="?(.*?)"?(;|$)', flags=re.I)
class PreviewUrlResource(Resource):
class PreviewUrlResource(DirectServeResource):
isLeaf = True
def __init__(self, hs, media_repo, media_storage):
Resource.__init__(self)
super().__init__()
self.auth = hs.get_auth()
self.clock = hs.get_clock()
@ -98,16 +97,11 @@ class PreviewUrlResource(Resource):
def render_OPTIONS(self, request):
return respond_with_json(request, 200, {}, send_cors=True)
def render_GET(self, request):
self._async_render_GET(request)
return NOT_DONE_YET
@wrap_json_request_handler
@defer.inlineCallbacks
def _async_render_GET(self, request):
async def _async_render_GET(self, request):
# XXX: if get_user_by_req fails, what should we do in an async render?
requester = yield self.auth.get_user_by_req(request)
requester = await self.auth.get_user_by_req(request)
url = parse_string(request, "url")
if b"ts" in request.args:
ts = parse_integer(request, "ts")
@ -159,7 +153,7 @@ class PreviewUrlResource(Resource):
else:
logger.info("Returning cached response")
og = yield make_deferred_yieldable(observable.observe())
og = await make_deferred_yieldable(defer.maybeDeferred(observable.observe))
respond_with_json_bytes(request, 200, og, send_cors=True)
@defer.inlineCallbacks

View file

@ -17,10 +17,12 @@
import logging
from twisted.internet import defer
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from synapse.http.server import set_cors_headers, wrap_json_request_handler
from synapse.http.server import (
DirectServeResource,
set_cors_headers,
wrap_json_request_handler,
)
from synapse.http.servlet import parse_integer, parse_string
from ._base import (
@ -34,11 +36,11 @@ from ._base import (
logger = logging.getLogger(__name__)
class ThumbnailResource(Resource):
class ThumbnailResource(DirectServeResource):
isLeaf = True
def __init__(self, hs, media_repo, media_storage):
Resource.__init__(self)
super().__init__()
self.store = hs.get_datastore()
self.media_repo = media_repo
@ -47,13 +49,8 @@ class ThumbnailResource(Resource):
self.server_name = hs.hostname
self.clock = hs.get_clock()
def render_GET(self, request):
self._async_render_GET(request)
return NOT_DONE_YET
@wrap_json_request_handler
@defer.inlineCallbacks
def _async_render_GET(self, request):
async def _async_render_GET(self, request):
set_cors_headers(request)
server_name, media_id, _ = parse_media_id(request)
width = parse_integer(request, "width", required=True)
@ -63,21 +60,21 @@ class ThumbnailResource(Resource):
if server_name == self.server_name:
if self.dynamic_thumbnails:
yield self._select_or_generate_local_thumbnail(
await self._select_or_generate_local_thumbnail(
request, media_id, width, height, method, m_type
)
else:
yield self._respond_local_thumbnail(
await self._respond_local_thumbnail(
request, media_id, width, height, method, m_type
)
self.media_repo.mark_recently_accessed(None, media_id)
else:
if self.dynamic_thumbnails:
yield self._select_or_generate_remote_thumbnail(
await self._select_or_generate_remote_thumbnail(
request, server_name, media_id, width, height, method, m_type
)
else:
yield self._respond_remote_thumbnail(
await self._respond_remote_thumbnail(
request, server_name, media_id, width, height, method, m_type
)
self.media_repo.mark_recently_accessed(server_name, media_id)

View file

@ -15,22 +15,24 @@
import logging
from twisted.internet import defer
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from synapse.api.errors import SynapseError
from synapse.http.server import respond_with_json, wrap_json_request_handler
from synapse.http.server import (
DirectServeResource,
respond_with_json,
wrap_json_request_handler,
)
from synapse.http.servlet import parse_string
logger = logging.getLogger(__name__)
class UploadResource(Resource):
class UploadResource(DirectServeResource):
isLeaf = True
def __init__(self, hs, media_repo):
Resource.__init__(self)
super().__init__()
self.media_repo = media_repo
self.filepaths = media_repo.filepaths
@ -41,18 +43,13 @@ class UploadResource(Resource):
self.max_upload_size = hs.config.max_upload_size
self.clock = hs.get_clock()
def render_POST(self, request):
self._async_render_POST(request)
return NOT_DONE_YET
def render_OPTIONS(self, request):
respond_with_json(request, 200, {}, send_cors=True)
return NOT_DONE_YET
@wrap_json_request_handler
@defer.inlineCallbacks
def _async_render_POST(self, request):
requester = yield self.auth.get_user_by_req(request)
async def _async_render_POST(self, request):
requester = await self.auth.get_user_by_req(request)
# TODO: The checks here are a bit late. The content will have
# already been uploaded to a tmp file at this point
content_length = request.getHeader(b"Content-Length").decode("ascii")
@ -81,7 +78,7 @@ class UploadResource(Resource):
# disposition = headers.getRawHeaders(b"Content-Disposition")[0]
# TODO(markjh): parse content-dispostion
content_uri = yield self.media_repo.create_content(
content_uri = await self.media_repo.create_content(
media_type, upload_name, request.content, content_length, requester.user
)

View file

@ -18,34 +18,27 @@ import logging
import saml2
from saml2.client import Saml2Client
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from synapse.api.errors import CodeMessageException
from synapse.http.server import wrap_html_request_handler
from synapse.http.server import DirectServeResource, wrap_html_request_handler
from synapse.http.servlet import parse_string
from synapse.rest.client.v1.login import SSOAuthHandler
logger = logging.getLogger(__name__)
class SAML2ResponseResource(Resource):
class SAML2ResponseResource(DirectServeResource):
"""A Twisted web resource which handles the SAML response"""
isLeaf = 1
def __init__(self, hs):
Resource.__init__(self)
super().__init__()
self._saml_client = Saml2Client(hs.config.saml2_sp_config)
self._sso_auth_handler = SSOAuthHandler(hs)
def render_POST(self, request):
self._async_render_POST(request)
return NOT_DONE_YET
@wrap_html_request_handler
def _async_render_POST(self, request):
async def _async_render_POST(self, request):
resp_bytes = parse_string(request, "SAMLResponse", required=True)
relay_state = parse_string(request, "RelayState", required=True)