@@ -1057,26 +1057,27 @@ def parameter_hoisting_note(method: str,
10571057 ) -> str :
10581058 return fd ('''
10591059 Any of the query parameters documented below can alternatively be passed
1060- as a property of a JSON object in the body of the request. This can be
1061- useful in case the value of the `filters` query parameter causes the URL
1062- to exceed the maximum length of 8192 characters, resulting in a 413
1063- Request Entity Too Large response.
1060+ as a property of a JSON object in the body of the request. This is
1061+ referred to as *parameter hoisting* and can be useful in case the value
1062+ of the `filters` query parameter causes the URL to exceed the maximum
1063+ length of 8192 characters, resulting in a 413 Request Entity Too Large
1064+ response.
10641065
10651066 The request `%s %s?filters={…}`, for example, is equivalent to `%s %s`
1066- with the body `{"filters": "{…}"}` in which any double quotes or
1067- backslash characters inside `…` are escaped with another backslash. That
1068- escaping is the requisite procedure for embedding one JSON structure
1069- inside another.
1067+ with a `Content-Type` header of `application/json` and the body
1068+ `{"filters": "{…}"}` in which any double quotes or backslash characters
1069+ inside `…` are escaped with another backslash. That escaping is the
1070+ requisite procedure for embedding one JSON structure inside another.
10701071 ''' % (method , endpoint , equivalent_method , endpoint ))
10711072
10721073
10731074def repository_search_spec (* , post : bool ):
10741075 id_spec_link = '#operations-Index-get_index__entity_type___entity_id_'
10751076 return {
1076- 'summary' : fd ( f'''
1077- Search an index for entities of interest
1078- { ", with filters provided in the request body" if post else "" } .
1079- ''' ),
1077+ 'summary' : (
1078+ ' Search an index for entities of interest' +
1079+ iif ( post , ', with large parameters provided in the request body' )
1080+ ),
10801081 'deprecated' : post ,
10811082 'description' :
10821083 iif (post , parameter_hoisting_note ('GET' , '/index/files' , 'POST' ) + fd ('''
@@ -1312,14 +1313,34 @@ def get_summary():
13121313 authentication = request .authentication )
13131314
13141315
1315- 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'
13161334 return app .route (
13171335 # The path parameter could be a token *or* an object key, but we don't
13181336 # want to complicate the API with this detail
13191337 ('/fetch' if fetch else '' )
13201338 + ('/manifest/files' if initiate else '/manifest/files/{token}' ),
13211339 # The initial PUT request is idempotent.
1322- 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' ],
13231344 interactive = fetch ,
13241345 cors = True ,
13251346 path_spec = None if initiate else {
@@ -1331,26 +1352,58 @@ def manifest_route(*, fetch: bool, initiate: bool):
13311352 },
13321353 method_spec = {
13331354 'tags' : ['Manifests' ],
1355+ 'deprecated' : curl ,
13341356 'summary' :
13351357 (
13361358 'Initiate the preparation of a manifest'
13371359 if initiate else
13381360 'Determine status of a manifest preparation job'
13391361 ) + (
13401362 ' via XHR' if fetch else ''
1363+ ) + (
1364+ ' as an alternative to PUT for curl users' if curl else ''
13411365 ),
1342- 'description' : fd ('''
1343- Create a manifest preparation job, returning either
1344-
1345- - 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
13461369
1347- - 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
13481371
1349- This endpoint is not suitable for interactive use via the
1350- 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.
13511378
1352- [1]: #operations-Manifests-put_fetch_manifest_files
1353- ''' ) + 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+ )
13541407 if initiate and not fetch else
13551408 fd ('''
13561409 Check on the status of an ongoing manifest preparation job,
@@ -1366,15 +1419,17 @@ def manifest_route(*, fetch: bool, initiate: bool):
13661419 instead.
13671420
13681421 [1]: #operations-Manifests-get_fetch_manifest_files__token_
1369- ''' ) if not initiate and not fetch else fd ('''
1422+ ''' )
1423+ if not initiate and not fetch else
1424+ fd (f'''
13701425 Create a manifest preparation job, returning a 200 status
13711426 response whose JSON body emulates the HTTP headers that would be
1372- found in a response to an equivalent request to the [PUT
1427+ found in a response to an equivalent request to the [{ method }
13731428 /manifest/files][1] endpoint.
13741429
13751430 Whenever client-side JavaScript code is used in a web
13761431 application to request the preparation of a manifest from Azul,
1377- this endpoint should be used instead of [PUT
1432+ this endpoint should be used instead of [{ method }
13781433 /manifest/files][1]. This way, the client can use XHR to make
13791434 the request, retaining full control over the handling of
13801435 redirects and enabling the client to bypass certain limitations
@@ -1384,8 +1439,9 @@ def manifest_route(*, fetch: bool, initiate: bool):
13841439 upper limit on the number of consecutive redirects, before the
13851440 manifest generation job is done.
13861441
1387- [1]: #operations-Manifests-put_manifest_files
1388- ''' ) + 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 )
13891445 if initiate and fetch else
13901446 fd ('''
13911447 Check on the status of an ongoing manifest preparation job,
@@ -1530,10 +1586,10 @@ def manifest_route(*, fetch: bool, initiate: bool):
15301586
15311587 For a detailed description of these properties see the
15321588 documentation for the respective response headers
1533- documented under ''' ) + (fd ('''
1534- [PUT /manifest/files][1].
1589+ documented under ''' ) + (fd (f '''
1590+ [{ method } /manifest/files][1].
15351591
1536- [1]: #operations-Manifests-put_manifest_files
1592+ [1]: #operations-Manifests-{ method . lower () } _manifest_files
15371593 ''' ) if initiate else fd ('''
15381594 [GET /manifest/files/{token}][1].
15391595
@@ -1575,6 +1631,7 @@ def manifest_route(*, fetch: bool, initiate: bool):
15751631 )
15761632
15771633
1634+ @manifest_route (fetch = False , initiate = True , curl = True )
15781635@manifest_route (fetch = False , initiate = True )
15791636def file_manifest ():
15801637 return _file_manifest (fetch = False )
@@ -1597,6 +1654,14 @@ def fetch_file_manifest_with_token(token: str):
15971654
15981655def _file_manifest (fetch : bool , token_or_key : Optional [str ] = None ):
15991656 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' )
16001665 query_params = request .query_params or {}
16011666 _hoist_parameters (query_params , request )
16021667 if token_or_key is None :
0 commit comments