Make WordPress Core


Ignore:
Timestamp:
10/20/2020 07:08:48 PM (6 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/tests/phpunit/tests/rest-api/rest-server.php

    r48947 r49252  
    16181618        }
    16191619
     1620        /**
     1621         * @ticket       50244
     1622         * @dataProvider data_batch_v1_optin
     1623         */
     1624        public function test_batch_v1_optin( $allow_batch, $allowed ) {
     1625                $args = array(
     1626                        'methods'             => 'POST',
     1627                        'callback'            => static function () {
     1628                                return new WP_REST_Response( 'data' );
     1629                        },
     1630                        'permission_callback' => '__return_true',
     1631                );
     1632
     1633                if ( null !== $allow_batch ) {
     1634                        $args['allow_batch'] = $allow_batch;
     1635                }
     1636
     1637                register_rest_route(
     1638                        'test-ns/v1',
     1639                        '/test',
     1640                        $args
     1641                );
     1642
     1643                $request = new WP_REST_Request( 'POST', '/batch/v1' );
     1644                $request->set_body_params(
     1645                        array(
     1646                                'requests' => array(
     1647                                        array(
     1648                                                'path' => '/test-ns/v1/test',
     1649                                        ),
     1650                                ),
     1651                        )
     1652                );
     1653
     1654                $response = rest_do_request( $request );
     1655
     1656                $this->assertEquals( 207, $response->get_status() );
     1657
     1658                if ( $allowed ) {
     1659                        $this->assertEquals( 'data', $response->get_data()['responses'][0]['body'] );
     1660                } else {
     1661                        $this->assertEquals( 'rest_batch_not_allowed', $response->get_data()['responses'][0]['body']['code'] );
     1662                }
     1663        }
     1664
     1665        public function data_batch_v1_optin() {
     1666                return array(
     1667                        'missing'             => array( null, false ),
     1668                        'invalid type'        => array( true, false ),
     1669                        'invalid type string' => array( 'v1', false ),
     1670                        'wrong version'       => array( array( 'version1' => true ), false ),
     1671                        'false version'       => array( array( 'v1' => false ), false ),
     1672                        'valid'               => array( array( 'v1' => true ), true ),
     1673                );
     1674        }
     1675
     1676        /**
     1677         * @ticket 50244
     1678         */
     1679        public function test_batch_v1_pre_validation() {
     1680                register_rest_route(
     1681                        'test-ns/v1',
     1682                        '/test',
     1683                        array(
     1684                                'methods'             => 'POST',
     1685                                'callback'            => static function ( $request ) {
     1686                                        $project = $request['project'];
     1687                                        update_option( 'test_project', $project );
     1688
     1689                                        return new WP_REST_Response( $project );
     1690                                },
     1691                                'permission_callback' => '__return_true',
     1692                                'allow_batch'         => array( 'v1' => true ),
     1693                                'args'                => array(
     1694                                        'project' => array(
     1695                                                'type' => 'string',
     1696                                                'enum' => array( 'gutenberg', 'WordPress' ),
     1697                                        ),
     1698                                ),
     1699                        )
     1700                );
     1701
     1702                $request = new WP_REST_Request( 'POST', '/batch/v1' );
     1703                $request->set_body_params(
     1704                        array(
     1705                                'validation' => 'require-all-validate',
     1706                                'requests'   => array(
     1707                                        array(
     1708                                                'path' => '/test-ns/v1/test',
     1709                                                'body' => array(
     1710                                                        'project' => 'gutenberg',
     1711                                                ),
     1712                                        ),
     1713                                        array(
     1714                                                'path' => '/test-ns/v1/test',
     1715                                                'body' => array(
     1716                                                        'project' => 'buddypress',
     1717                                                ),
     1718                                        ),
     1719                                ),
     1720                        )
     1721                );
     1722
     1723                $response = rest_get_server()->dispatch( $request );
     1724                $data     = $response->get_data();
     1725
     1726                $this->assertEquals( 207, $response->get_status() );
     1727                $this->assertArrayHasKey( 'failed', $data );
     1728                $this->assertEquals( 'validation', $data['failed'] );
     1729                $this->assertCount( 2, $data['responses'] );
     1730                $this->assertNull( $data['responses'][0] );
     1731                $this->assertEquals( 400, $data['responses'][1]['status'] );
     1732                $this->assertFalse( get_option( 'test_project' ) );
     1733        }
     1734
     1735        /**
     1736         * @ticket 50244
     1737         */
     1738        public function test_batch_v1_pre_validation_all_successful() {
     1739                register_rest_route(
     1740                        'test-ns/v1',
     1741                        '/test',
     1742                        array(
     1743                                'methods'             => 'POST',
     1744                                'callback'            => static function ( $request ) {
     1745                                        return new WP_REST_Response( $request['project'] );
     1746                                },
     1747                                'permission_callback' => '__return_true',
     1748                                'allow_batch'         => array( 'v1' => true ),
     1749                                'args'                => array(
     1750                                        'project' => array(
     1751                                                'type' => 'string',
     1752                                                'enum' => array( 'gutenberg', 'WordPress' ),
     1753                                        ),
     1754                                ),
     1755                        )
     1756                );
     1757
     1758                $request = new WP_REST_Request( 'POST', '/batch/v1' );
     1759                $request->set_body_params(
     1760                        array(
     1761                                'validation' => 'require-all-validate',
     1762                                'requests'   => array(
     1763                                        array(
     1764                                                'path' => '/test-ns/v1/test',
     1765                                                'body' => array(
     1766                                                        'project' => 'gutenberg',
     1767                                                ),
     1768                                        ),
     1769                                        array(
     1770                                                'path' => '/test-ns/v1/test',
     1771                                                'body' => array(
     1772                                                        'project' => 'WordPress',
     1773                                                ),
     1774                                        ),
     1775                                ),
     1776                        )
     1777                );
     1778
     1779                $response = rest_get_server()->dispatch( $request );
     1780                $data     = $response->get_data();
     1781
     1782                $this->assertEquals( 207, $response->get_status() );
     1783                $this->assertArrayNotHasKey( 'failed', $data );
     1784                $this->assertCount( 2, $data['responses'] );
     1785                $this->assertEquals( 'gutenberg', $data['responses'][0]['body'] );
     1786                $this->assertEquals( 'WordPress', $data['responses'][1]['body'] );
     1787        }
     1788
     1789        /**
     1790         * @ticket 50244
     1791         */
     1792        public function test_batch_v1() {
     1793                register_rest_route(
     1794                        'test-ns/v1',
     1795                        '/test/(?P<id>[\d+])',
     1796                        array(
     1797                                'methods'             => array( 'POST', 'DELETE' ),
     1798                                'callback'            => function ( WP_REST_Request $request ) {
     1799                                        $this->assertEquals( 'DELETE', $request->get_method() );
     1800                                        $this->assertEquals( '/test-ns/v1/test/5', $request->get_route() );
     1801                                        $this->assertEquals( array( 'id' => '5' ), $request->get_url_params() );
     1802                                        $this->assertEquals( array( 'query' => 'param' ), $request->get_query_params() );
     1803                                        $this->assertEquals( array( 'project' => 'gutenberg' ), $request->get_body_params() );
     1804                                        $this->assertEquals( array( 'my_header' => array( 'my-value' ) ), $request->get_headers() );
     1805
     1806                                        return new WP_REST_Response( 'test' );
     1807                                },
     1808                                'permission_callback' => '__return_true',
     1809                                'allow_batch'         => array( 'v1' => true ),
     1810                        )
     1811                );
     1812
     1813                $request = new WP_REST_Request( 'POST', '/batch/v1' );
     1814                $request->set_body_params(
     1815                        array(
     1816                                'requests' => array(
     1817                                        array(
     1818                                                'method'  => 'DELETE',
     1819                                                'path'    => '/test-ns/v1/test/5?query=param',
     1820                                                'headers' => array(
     1821                                                        'My-Header' => 'my-value',
     1822                                                ),
     1823                                                'body'    => array(
     1824                                                        'project' => 'gutenberg',
     1825                                                ),
     1826                                        ),
     1827                                ),
     1828                        )
     1829                );
     1830
     1831                $response = rest_get_server()->dispatch( $request );
     1832
     1833                $this->assertEquals( 207, $response->get_status() );
     1834                $this->assertEquals( 'test', $response->get_data()['responses'][0]['body'] );
     1835        }
     1836
     1837        /**
     1838         * @ticket 50244
     1839         */
     1840        public function test_batch_v1_partial_error() {
     1841                register_rest_route(
     1842                        'test-ns/v1',
     1843                        '/test',
     1844                        array(
     1845                                'methods'             => 'POST',
     1846                                'callback'            => static function ( $request ) {
     1847                                        $project = $request['project'];
     1848                                        update_option( 'test_project', $project );
     1849
     1850                                        return new WP_REST_Response( $project );
     1851                                },
     1852                                'permission_callback' => '__return_true',
     1853                                'allow_batch'         => array( 'v1' => true ),
     1854                                'args'                => array(
     1855                                        'project' => array(
     1856                                                'type' => 'string',
     1857                                                'enum' => array( 'gutenberg', 'WordPress' ),
     1858                                        ),
     1859                                ),
     1860                        )
     1861                );
     1862
     1863                $request = new WP_REST_Request( 'POST', '/batch/v1' );
     1864                $request->set_body_params(
     1865                        array(
     1866                                'requests' => array(
     1867                                        array(
     1868                                                'path' => '/test-ns/v1/test',
     1869                                                'body' => array(
     1870                                                        'project' => 'gutenberg',
     1871                                                ),
     1872                                        ),
     1873                                        array(
     1874                                                'path' => '/test-ns/v1/test',
     1875                                                'body' => array(
     1876                                                        'project' => 'buddypress',
     1877                                                ),
     1878                                        ),
     1879                                ),
     1880                        )
     1881                );
     1882
     1883                $response = rest_get_server()->dispatch( $request );
     1884                $data     = $response->get_data();
     1885
     1886                $this->assertEquals( 207, $response->get_status() );
     1887                $this->assertArrayNotHasKey( 'failed', $data );
     1888                $this->assertCount( 2, $data['responses'] );
     1889                $this->assertEquals( 'gutenberg', $data['responses'][0]['body'] );
     1890                $this->assertEquals( 400, $data['responses'][1]['status'] );
     1891                $this->assertEquals( 'gutenberg', get_option( 'test_project' ) );
     1892        }
     1893
     1894
     1895        /**
     1896         * @ticket 50244
     1897         */
     1898        public function test_batch_v1_max_requests() {
     1899                add_filter(
     1900                        'rest_get_max_batch_size',
     1901                        static function() {
     1902                                return 5;
     1903                        }
     1904                );
     1905
     1906                $GLOBALS['wp_rest_server'] = null;
     1907                add_filter( 'wp_rest_server_class', array( $this, 'filter_wp_rest_server_class' ) );
     1908                $GLOBALS['wp_rest_server'] = rest_get_server();
     1909
     1910                register_rest_route(
     1911                        'test-ns/v1',
     1912                        '/test/(?P<id>[\d+])',
     1913                        array(
     1914                                'methods'             => array( 'POST', 'DELETE' ),
     1915                                'callback'            => function ( WP_REST_Request $request ) {
     1916                                        return new WP_REST_Response( 'test' );
     1917                                },
     1918                                'permission_callback' => '__return_true',
     1919                                'allow_batch'         => array( 'v1' => true ),
     1920                        )
     1921                );
     1922
     1923                $request = new WP_REST_Request( 'POST', '/batch/v1' );
     1924                $request->set_body_params(
     1925                        array(
     1926                                'requests' => array_fill( 0, 6, array( 'path' => '/test-ns/v1/test/5' ) ),
     1927                        )
     1928                );
     1929
     1930                $response = rest_get_server()->dispatch( $request );
     1931                $this->assertEquals( 400, $response->get_status() );
     1932        }
     1933
    16201934        public function _validate_as_integer_123( $value, $request, $key ) {
    16211935                if ( ! is_int( $value ) ) {
Note: See TracChangeset for help on using the changeset viewer.