Skip to content

Commit 7c8e598

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 e0b46b3 commit 7c8e598

File tree

4 files changed

+1494
-38
lines changed

4 files changed

+1494
-38
lines changed

lambdas/service/app.py

Lines changed: 87 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,10 +1074,10 @@ def parameter_hoisting_note(method: str,
10741074
def repository_search_spec(*, post: bool):
10751075
id_spec_link = '#operations-Index-get_index__entity_type___entity_id_'
10761076
return {
1077-
'summary': fd(f'''
1078-
Search an index for entities of interest
1079-
{", with filters provided in the request body" if post else ""}.
1080-
'''),
1077+
'summary': (
1078+
'Search an index for entities of interest' +
1079+
iif(post, ', with large parameters provided in the request body')
1080+
),
10811081
'deprecated': post,
10821082
'description':
10831083
iif(post, parameter_hoisting_note('GET', '/index/files', 'POST') + fd('''
@@ -1313,14 +1313,34 @@ def get_summary():
13131313
authentication=request.authentication)
13141314

13151315

1316-
def manifest_route(*, fetch: bool, initiate: bool):
1316+
post_manifest_example_url = (
1317+
f'{app.base_url}/manifest/files'
1318+
f'?catalog={list(config.catalogs.keys())[0]}'
1319+
'&filters={…}'
1320+
f'&format={app.metadata_plugin.manifest_formats[0].value}'
1321+
)
1322+
1323+
1324+
def manifest_route(*, fetch: bool, initiate: bool, curl: bool = False):
1325+
if initiate:
1326+
if curl:
1327+
assert not fetch
1328+
method = 'POST'
1329+
else:
1330+
method = 'PUT'
1331+
else:
1332+
assert not curl
1333+
method = 'GET'
13171334
return app.route(
13181335
# The path parameter could be a token *or* an object key, but we don't
13191336
# want to complicate the API with this detail
13201337
('/fetch' if fetch else '')
13211338
+ ('/manifest/files' if initiate else '/manifest/files/{token}'),
13221339
# The initial PUT request is idempotent.
1323-
methods=['PUT' if initiate else 'GET'],
1340+
methods=[method],
1341+
# In order to support requests made with `curl` and its `--data` option,
1342+
# we accept the `application/x-www-form-urlencoded` content-type.
1343+
content_types=['application/json', 'application/x-www-form-urlencoded'],
13241344
interactive=fetch,
13251345
cors=True,
13261346
path_spec=None if initiate else {
@@ -1332,26 +1352,58 @@ def manifest_route(*, fetch: bool, initiate: bool):
13321352
},
13331353
method_spec={
13341354
'tags': ['Manifests'],
1355+
'deprecated': curl,
13351356
'summary':
13361357
(
13371358
'Initiate the preparation of a manifest'
13381359
if initiate else
13391360
'Determine status of a manifest preparation job'
13401361
) + (
13411362
' via XHR' if fetch else ''
1363+
) + (
1364+
' as an alternative to PUT for curl users' if curl else ''
13421365
),
1343-
'description': fd('''
1344-
Create a manifest preparation job, returning either
1345-
1346-
- a 301 redirect to the URL of the status of that job or
1366+
'description': (
1367+
fd('''
1368+
Create a manifest preparation job, returning either
13471369
1348-
- a 302 redirect to the URL of an already prepared manifest.
1370+
- a 301 redirect to the URL of the status of that job or
13491371
1350-
This endpoint is not suitable for interactive use via the
1351-
Swagger UI. Please use [PUT /fetch/manifest/files][1] instead.
1372+
- a 302 redirect to the URL of an already prepared manifest.
1373+
''')
1374+
+ iif(not curl, fd(f'''
1375+
This endpoint is not suitable for interactive use via the
1376+
Swagger UI. Please use [{method} /fetch/manifest/files][1]
1377+
instead.
13521378
1353-
[1]: #operations-Manifests-put_fetch_manifest_files
1354-
''') + parameter_hoisting_note('PUT', '/manifest/files', 'PUT')
1379+
[1]: #operations-Manifests-{method.lower()}_fetch_manifest_files
1380+
'''))
1381+
+ parameter_hoisting_note(method, '/manifest/files', method)
1382+
+ iif(curl, fd(f'''
1383+
Requests to this endpoint are idempotent, so PUT would be
1384+
the more standards-compliant method to use. POST is offered
1385+
as a convenience for `curl` users, exploiting the fact that
1386+
`curl` drops to GET when following a redirect in response to
1387+
a POST, but not a PUT request. This is the only reason for
1388+
the deprecation of this endpoint and there are currently no
1389+
plans to remove it.
1390+
1391+
To use this endpoint with `curl`, pass the `--location` and
1392+
`--data` options. This makes `curl` automatically follow the
1393+
intermediate redirects to the GET /manifest/files endpoint,
1394+
and ultimately to the URL that yields the manifest. Example:
1395+
1396+
```
1397+
curl --data "" --location {post_manifest_example_url}
1398+
```
1399+
1400+
In order to facilitate this, a POST request to this endpoint
1401+
may have a `Content-Type` header of
1402+
`application/x-www-form-urlencoded`, which is what the
1403+
`--data` option sends. The body must be empty in that case
1404+
and parameters cannot be hoisted as described above.
1405+
'''))
1406+
)
13551407
if initiate and not fetch else
13561408
fd('''
13571409
Check on the status of an ongoing manifest preparation job,
@@ -1367,15 +1419,17 @@ def manifest_route(*, fetch: bool, initiate: bool):
13671419
instead.
13681420
13691421
[1]: #operations-Manifests-get_fetch_manifest_files__token_
1370-
''') if not initiate and not fetch else fd('''
1422+
''')
1423+
if not initiate and not fetch else
1424+
fd(f'''
13711425
Create a manifest preparation job, returning a 200 status
13721426
response whose JSON body emulates the HTTP headers that would be
1373-
found in a response to an equivalent request to the [PUT
1427+
found in a response to an equivalent request to the [{method}
13741428
/manifest/files][1] endpoint.
13751429
13761430
Whenever client-side JavaScript code is used in a web
13771431
application to request the preparation of a manifest from Azul,
1378-
this endpoint should be used instead of [PUT
1432+
this endpoint should be used instead of [{method}
13791433
/manifest/files][1]. This way, the client can use XHR to make
13801434
the request, retaining full control over the handling of
13811435
redirects and enabling the client to bypass certain limitations
@@ -1385,8 +1439,9 @@ def manifest_route(*, fetch: bool, initiate: bool):
13851439
upper limit on the number of consecutive redirects, before the
13861440
manifest generation job is done.
13871441
1388-
[1]: #operations-Manifests-put_manifest_files
1389-
''') + parameter_hoisting_note('PUT', '/fetch/manifest/files', 'PUT')
1442+
[1]: #operations-Manifests-{method.lower()}_manifest_files
1443+
''')
1444+
+ parameter_hoisting_note(method, '/fetch/manifest/files', method)
13901445
if initiate and fetch else
13911446
fd('''
13921447
Check on the status of an ongoing manifest preparation job,
@@ -1531,10 +1586,10 @@ def manifest_route(*, fetch: bool, initiate: bool):
15311586
15321587
For a detailed description of these properties see the
15331588
documentation for the respective response headers
1534-
documented under ''') + (fd('''
1535-
[PUT /manifest/files][1].
1589+
documented under ''') + (fd(f'''
1590+
[{method} /manifest/files][1].
15361591
1537-
[1]: #operations-Manifests-put_manifest_files
1592+
[1]: #operations-Manifests-{method.lower()}_manifest_files
15381593
''') if initiate else fd('''
15391594
[GET /manifest/files/{token}][1].
15401595
@@ -1576,6 +1631,7 @@ def manifest_route(*, fetch: bool, initiate: bool):
15761631
)
15771632

15781633

1634+
@manifest_route(fetch=False, initiate=True, curl=True)
15791635
@manifest_route(fetch=False, initiate=True)
15801636
def file_manifest():
15811637
return _file_manifest(fetch=False)
@@ -1598,6 +1654,14 @@ def fetch_file_manifest_with_token(token: str):
15981654

15991655
def _file_manifest(fetch: bool, token_or_key: Optional[str] = None):
16001656
request = app.current_request
1657+
post = request.method == 'POST'
1658+
if (
1659+
post
1660+
and request.headers.get('content-type') == 'application/x-www-form-urlencoded'
1661+
and request.raw_body != b''
1662+
):
1663+
raise BRE('The body must be empty for a POST request of content-type '
1664+
'`application/x-www-form-urlencoded` to this endpoint')
16011665
query_params = request.query_params or {}
16021666
_hoist_parameters(query_params, request)
16031667
if token_or_key is None:

0 commit comments

Comments
 (0)