Make WordPress Core


Ignore:
Timestamp:
10/20/2020 07:08:48 PM (4 years ago)
Author:
TimothyBlynJacobs
Message:

REST API: Introduce support for batching API requests.

A new route is introduced, batch/v1, that accepts a list of API requests to run. Each request runs in sequence, and the responses are returned in the order they've been received.

Optionally, the require-all-validate validation mode can be used to first validate each request's parameters and only proceed with processing if each request validates successfully.

By default, the batch size is limited to 25 requests. This can be controlled using the rest_get_max_batch_size filter. Clients are strongly encouraged to discover the maximum batch size supported by the server by making an OPTIONS request to the batch/v1 endpoint and inspecting the described arguments.

Additionally, the two new methods, match_request_to_handler and respond_to_request introduced in [48947] now have a protected visibility as we don't want to expose the inner workings of the WP_REST_Server::dispatch API.

Batching is not currently supported for GET requests.

Fixes #50244.
Props andraganescu, zieladam, TimothyBlynJacobs.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/rest-api/class-wp-rest-server.php

    r49109 r49252  
    9595        $this->endpoints = array(
    9696            // Meta endpoints.
    97             '/' => array(
     97            '/'         => array(
    9898                'callback' => array( $this, 'get_index' ),
    9999                'methods'  => 'GET',
     
    101101                    'context' => array(
    102102                        'default' => 'view',
     103                    ),
     104                ),
     105            ),
     106            '/batch/v1' => array(
     107                'callback' => array( $this, 'serve_batch_request_v1' ),
     108                'methods'  => 'POST',
     109                'args'     => array(
     110                    'validation' => array(
     111                        'type'    => 'string',
     112                        'enum'    => array( 'require-all-validate', 'normal' ),
     113                        'default' => 'normal',
     114                    ),
     115                    'requests'   => array(
     116                        'required' => true,
     117                        'type'     => 'array',
     118                        'maxItems' => $this->get_max_batch_size(),
     119                        'items'    => array(
     120                            'type'       => 'object',
     121                            'properties' => array(
     122                                'method'  => array(
     123                                    'type'    => 'string',
     124                                    'enum'    => array( 'POST', 'PUT', 'PATCH', 'DELETE' ),
     125                                    'default' => 'POST',
     126                                ),
     127                                'path'    => array(
     128                                    'type'     => 'string',
     129                                    'required' => true,
     130                                ),
     131                                'body'    => array(
     132                                    'type'                 => 'object',
     133                                    'properties'           => array(),
     134                                    'additionalProperties' => true,
     135                                ),
     136                                'headers' => array(
     137                                    'type'                 => 'object',
     138                                    'properties'           => array(),
     139                                    'additionalProperties' => array(
     140                                        'type'  => array( 'string', 'array' ),
     141                                        'items' => array(
     142                                            'type' => 'string',
     143                                        ),
     144                                    ),
     145                                ),
     146                            ),
     147                        ),
    103148                    ),
    104149                ),
     
    9721017     * @return array|WP_Error The route and request handler on success or a WP_Error instance if no handler was found.
    9731018     */
    974     public function match_request_to_handler( $request ) {
     1019    protected function match_request_to_handler( $request ) {
    9751020        $method = $request->get_method();
    9761021        $path   = $request->get_route();
     
    10591104     * @return WP_REST_Response
    10601105     */
    1061     public function respond_to_request( $request, $route, $handler, $response ) {
     1106    protected function respond_to_request( $request, $route, $handler, $response ) {
    10621107        /**
    10631108         * Filters the response before executing any REST API callbacks.
     
    13981443
    13991444    /**
     1445     * Gets the maximum number of requests that can be included in a batch.
     1446     *
     1447     * @since 5.6.0
     1448     *
     1449     * @return int The maximum requests.
     1450     */
     1451    protected function get_max_batch_size() {
     1452        /**
     1453         * Filters the maximum number of requests that can be included in a batch.
     1454         *
     1455         * @param int $max_size The maximum size.
     1456         */
     1457        return apply_filters( 'rest_get_max_batch_size', 25 );
     1458    }
     1459
     1460    /**
     1461     * Serves the batch/v1 request.
     1462     *
     1463     * @since 5.6.0
     1464     *
     1465     * @param WP_REST_Request $batch_request The batch request object.
     1466     * @return WP_REST_Response The generated response object.
     1467     */
     1468    public function serve_batch_request_v1( WP_REST_Request $batch_request ) {
     1469        $requests = array();
     1470
     1471        foreach ( $batch_request['requests'] as $args ) {
     1472            $parsed_url = wp_parse_url( $args['path'] );
     1473
     1474            if ( false === $parsed_url ) {
     1475                $requests[] = new WP_Error( 'parse_path_failed', __( 'Could not parse the path.' ), array( 'status' => 400 ) );
     1476
     1477                continue;
     1478            }
     1479
     1480            $single_request = new WP_REST_Request( isset( $args['method'] ) ? $args['method'] : 'POST', $parsed_url['path'] );
     1481
     1482            if ( ! empty( $parsed_url['query'] ) ) {
     1483                $query_args = null; // Satisfy linter.
     1484                wp_parse_str( $parsed_url['query'], $query_args );
     1485                $single_request->set_query_params( $query_args );
     1486            }
     1487
     1488            if ( ! empty( $args['body'] ) ) {
     1489                $single_request->set_body_params( $args['body'] );
     1490            }
     1491
     1492            if ( ! empty( $args['headers'] ) ) {
     1493                $single_request->set_headers( $args['headers'] );
     1494            }
     1495
     1496            $requests[] = $single_request;
     1497        }
     1498
     1499        $matches    = array();
     1500        $validation = array();
     1501        $has_error  = false;
     1502
     1503        foreach ( $requests as $single_request ) {
     1504            $match     = $this->match_request_to_handler( $single_request );
     1505            $matches[] = $match;
     1506            $error     = null;
     1507
     1508            if ( is_wp_error( $match ) ) {
     1509                $error = $match;
     1510            }
     1511
     1512            if ( ! $error ) {
     1513                list( $route, $handler ) = $match;
     1514
     1515                if ( isset( $handler['allow_batch'] ) ) {
     1516                    $allow_batch = $handler['allow_batch'];
     1517                } else {
     1518                    $route_options = $this->get_route_options( $route );
     1519                    $allow_batch   = isset( $route_options['allow_batch'] ) ? $route_options['allow_batch'] : false;
     1520                }
     1521
     1522                if ( ! is_array( $allow_batch ) || empty( $allow_batch['v1'] ) ) {
     1523                    $error = new WP_Error(
     1524                        'rest_batch_not_allowed',
     1525                        __( 'The requested route does not support batch requests.' ),
     1526                        array( 'status' => 400 )
     1527                    );
     1528                }
     1529            }
     1530
     1531            if ( ! $error ) {
     1532                $check_required = $single_request->has_valid_params();
     1533                if ( is_wp_error( $check_required ) ) {
     1534                    $error = $check_required;
     1535                }
     1536            }
     1537
     1538            if ( ! $error ) {
     1539                $check_sanitized = $single_request->sanitize_params();
     1540                if ( is_wp_error( $check_sanitized ) ) {
     1541                    $error = $check_sanitized;
     1542                }
     1543            }
     1544
     1545            if ( $error ) {
     1546                $has_error    = true;
     1547                $validation[] = $error;
     1548            } else {
     1549                $validation[] = true;
     1550            }
     1551        }
     1552
     1553        $responses = array();
     1554
     1555        if ( $has_error && 'require-all-validate' === $batch_request['validation'] ) {
     1556            foreach ( $validation as $valid ) {
     1557                if ( is_wp_error( $valid ) ) {
     1558                    $responses[] = $this->envelope_response( $this->error_to_response( $valid ), false )->get_data();
     1559                } else {
     1560                    $responses[] = null;
     1561                }
     1562            }
     1563
     1564            return new WP_REST_Response(
     1565                array(
     1566                    'failed'    => 'validation',
     1567                    'responses' => $responses,
     1568                ),
     1569                WP_Http::MULTI_STATUS
     1570            );
     1571        }
     1572
     1573        foreach ( $requests as $i => $single_request ) {
     1574            $clean_request = clone $single_request;
     1575            $clean_request->set_url_params( array() );
     1576            $clean_request->set_attributes( array() );
     1577            $clean_request->set_default_params( array() );
     1578
     1579            /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
     1580            $result = apply_filters( 'rest_pre_dispatch', null, $this, $clean_request );
     1581
     1582            if ( empty( $result ) ) {
     1583                $match = $matches[ $i ];
     1584                $error = null;
     1585
     1586                if ( is_wp_error( $validation[ $i ] ) ) {
     1587                    $error = $validation[ $i ];
     1588                }
     1589
     1590                if ( is_wp_error( $match ) ) {
     1591                    $result = $this->error_to_response( $match );
     1592                } else {
     1593                    list( $route, $handler ) = $match;
     1594
     1595                    if ( ! $error && ! is_callable( $handler['callback'] ) ) {
     1596                        $error = new WP_Error(
     1597                            'rest_invalid_handler',
     1598                            __( 'The handler for the route is invalid' ),
     1599                            array( 'status' => 500 )
     1600                        );
     1601                    }
     1602
     1603                    $result = $this->respond_to_request( $single_request, $route, $handler, $error );
     1604                }
     1605            }
     1606
     1607            /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
     1608            $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $single_request );
     1609
     1610            $responses[] = $this->envelope_response( $result, false )->get_data();
     1611        }
     1612
     1613        return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS );
     1614    }
     1615
     1616    /**
    14001617     * Sends an HTTP status code.
    14011618     *
Note: See TracChangeset for help on using the changeset viewer.