Skip to content

Commit c4075b3

Browse files
committed
[1/2] [a] Fix: Can't use curl to download a single manifest in one invocation (#5918)
Add support for POST requests to the manifest endpoint
1 parent 1f0e9db commit c4075b3

File tree

4 files changed

+1496
-37
lines changed

4 files changed

+1496
-37
lines changed

lambdas/service/app.py

Lines changed: 87 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -857,10 +857,10 @@ def parameter_hoisting_note(method: str,
857857
def repository_search_spec(*, post: bool):
858858
id_spec_link = '#operations-Index-get_index__entity_type___entity_id_'
859859
return {
860-
'summary': fd(f'''
861-
Search an index for entities of interest
862-
{", with filters provided in the request body" if post else ""}.
863-
'''),
860+
'summary': (
861+
'Search an index for entities of interest' +
862+
iif(post, ', with large parameters provided in the request body')
863+
),
864864
'deprecated': post,
865865
'description':
866866
iif(post, parameter_hoisting_note('GET', '/index/files', 'POST') + fd('''
@@ -1096,14 +1096,34 @@ def get_summary():
10961096
authentication=request.authentication)
10971097

10981098

1099-
def manifest_route(*, fetch: bool, initiate: bool):
1099+
post_manifest_example_url = (
1100+
f'{app.base_url}/manifest/files'
1101+
f'?catalog={list(config.catalogs.keys())[0]}'
1102+
'&filters={…}'
1103+
f'&format={app.metadata_plugin.manifest_formats[0].value}'
1104+
)
1105+
1106+
1107+
def manifest_route(*, fetch: bool, initiate: bool, curl: bool = False):
1108+
if initiate:
1109+
if curl:
1110+
assert not fetch
1111+
method = 'POST'
1112+
else:
1113+
method = 'PUT'
1114+
else:
1115+
assert not curl
1116+
method = 'GET'
11001117
return app.route(
11011118
# The path parameter could be a token *or* an object key, but we don't
11021119
# want to complicate the API with this detail
11031120
('/fetch' if fetch else '')
11041121
+ ('/manifest/files' if initiate else '/manifest/files/{token}'),
11051122
# The initial PUT request is idempotent.
1106-
methods=['PUT' if initiate else 'GET'],
1123+
methods=[method],
1124+
# In order to support requests made with `curl` and its `--data` option,
1125+
# we accept the `application/x-www-form-urlencoded` content-type.
1126+
content_types=['application/json', 'application/x-www-form-urlencoded'],
11071127
interactive=fetch,
11081128
cors=True,
11091129
path_spec=None if initiate else {
@@ -1115,26 +1135,58 @@ def manifest_route(*, fetch: bool, initiate: bool):
11151135
},
11161136
spec={
11171137
'tags': ['Manifests'],
1138+
'deprecated': curl,
11181139
'summary':
11191140
(
11201141
'Initiate the preparation of a manifest'
11211142
if initiate else
11221143
'Determine status of a manifest preparation job'
11231144
) + (
11241145
' via XHR' if fetch else ''
1146+
) + (
1147+
' as an alternative to PUT for curl users' if curl else ''
11251148
),
1126-
'description': fd('''
1127-
Create a manifest preparation job, returning either
1128-
1129-
- a 301 redirect to the URL of the status of that job or
1149+
'description': (
1150+
fd('''
1151+
Create a manifest preparation job, returning either
11301152
1131-
- a 302 redirect to the URL of an already prepared manifest.
1153+
- a 301 redirect to the URL of the status of that job or
11321154
1133-
This endpoint is not suitable for interactive use via the
1134-
Swagger UI. Please use [PUT /fetch/manifest/files][1] instead.
1155+
- a 302 redirect to the URL of an already prepared manifest.
1156+
''')
1157+
+ iif(not curl, fd(f'''
1158+
This endpoint is not suitable for interactive use via the
1159+
Swagger UI. Please use [{method} /fetch/manifest/files][1]
1160+
instead.
11351161
1136-
[1]: #operations-Manifests-put_fetch_manifest_files
1137-
''') + parameter_hoisting_note('PUT', '/manifest/files', 'PUT')
1162+
[1]: #operations-Manifests-{method.lower()}_fetch_manifest_files
1163+
'''))
1164+
+ parameter_hoisting_note(method, '/manifest/files', method)
1165+
+ iif(curl, fd(f'''
1166+
Requests to this endpoint are idempotent, so PUT would be
1167+
the more standards-compliant method to use. POST is offered
1168+
as a convenience for `curl` users, exploiting the fact that
1169+
`curl` drops to GET when following a redirect in response to
1170+
a POST, but not a PUT request. This is the only reason for
1171+
the deprecation of this endpoint and there are currently no
1172+
plans to remove it.
1173+
1174+
To use this endpoint with `curl`, pass the `--location` and
1175+
`--data` options. This makes `curl` automatically follow the
1176+
intermediate redirects to the GET /manifest/files endpoint,
1177+
and ultimately to the URL that yields the manifest. Example:
1178+
1179+
```
1180+
curl --data "" --location {post_manifest_example_url}
1181+
```
1182+
1183+
In order to facilitate this, a POST request to this endpoint
1184+
may have a `Content-Type` header of
1185+
`application/x-www-form-urlencoded`, which is what the
1186+
`--data` option sends. The body must be empty in that case
1187+
and parameters cannot be hoisted as described above.
1188+
'''))
1189+
)
11381190
if initiate and not fetch else
11391191
fd('''
11401192
Check on the status of an ongoing manifest preparation job,
@@ -1150,15 +1202,17 @@ def manifest_route(*, fetch: bool, initiate: bool):
11501202
instead.
11511203
11521204
[1]: #operations-Manifests-get_fetch_manifest_files__token_
1153-
''') if not initiate and not fetch else fd('''
1205+
''')
1206+
if not initiate and not fetch else
1207+
fd(f'''
11541208
Create a manifest preparation job, returning a 200 status
11551209
response whose JSON body emulates the HTTP headers that would be
1156-
found in a response to an equivalent request to the [PUT
1210+
found in a response to an equivalent request to the [{method}
11571211
/manifest/files][1] endpoint.
11581212
11591213
Whenever client-side JavaScript code is used in a web
11601214
application to request the preparation of a manifest from Azul,
1161-
this endpoint should be used instead of [PUT
1215+
this endpoint should be used instead of [{method}
11621216
/manifest/files][1]. This way, the client can use XHR to make
11631217
the request, retaining full control over the handling of
11641218
redirects and enabling the client to bypass certain limitations
@@ -1168,8 +1222,9 @@ def manifest_route(*, fetch: bool, initiate: bool):
11681222
upper limit on the number of consecutive redirects, before the
11691223
manifest generation job is done.
11701224
1171-
[1]: #operations-Manifests-put_manifest_files
1172-
''') + parameter_hoisting_note('PUT', '/fetch/manifest/files', 'PUT')
1225+
[1]: #operations-Manifests-{method.lower()}_manifest_files
1226+
''')
1227+
+ parameter_hoisting_note(method, '/fetch/manifest/files', method)
11731228
if initiate and fetch else
11741229
fd('''
11751230
Check on the status of an ongoing manifest preparation job,
@@ -1314,10 +1369,10 @@ def manifest_route(*, fetch: bool, initiate: bool):
13141369
13151370
For a detailed description of these properties see the
13161371
documentation for the respective response headers
1317-
documented under ''') + (fd('''
1318-
[PUT /manifest/files][1].
1372+
documented under ''') + (fd(f'''
1373+
[{method} /manifest/files][1].
13191374
1320-
[1]: #operations-Manifests-put_manifest_files
1375+
[1]: #operations-Manifests-{method.lower()}_manifest_files
13211376
''') if initiate else fd('''
13221377
[GET /manifest/files/{token}][1].
13231378
@@ -1359,6 +1414,7 @@ def manifest_route(*, fetch: bool, initiate: bool):
13591414
)
13601415

13611416

1417+
@manifest_route(fetch=False, initiate=True, curl=True)
13621418
@manifest_route(fetch=False, initiate=True)
13631419
def file_manifest():
13641420
return _file_manifest(fetch=False)
@@ -1381,6 +1437,14 @@ def fetch_file_manifest_with_token(token: str):
13811437

13821438
def _file_manifest(fetch: bool, token_or_key: str | None = None):
13831439
request = app.current_request
1440+
post = request.method == 'POST'
1441+
if (
1442+
post
1443+
and request.headers.get('content-type') == 'application/x-www-form-urlencoded'
1444+
and request.raw_body != b''
1445+
):
1446+
raise BRE('The body must be empty for a POST request of content-type '
1447+
'`application/x-www-form-urlencoded` to this endpoint')
13841448
query_params = request.query_params or {}
13851449
_hoist_parameters(query_params, request)
13861450
if token_or_key is None:

0 commit comments

Comments
 (0)