Index: src/wp-includes/rest-api/class-wp-rest-request.php
===================================================================
--- src/wp-includes/rest-api/class-wp-rest-request.php	(revision 36671)
+++ src/wp-includes/rest-api/class-wp-rest-request.php	(working copy)
@@ -934,4 +934,48 @@
 			unset( $this->params[ $type ][ $offset ] );
 		}
 	}
+
+	/**
+	 * Gets a WP_REST_Request object from a full URL.
+	 *
+	 * @since 4.5.0
+	 *
+	 * @param string $url URL with protocol, domain, path and query args.
+	 * @return WP_REST_Request|false WP_REST_Request object on success, false on failure.
+	 */
+	public static function from_url( $url ) {
+		$bits = parse_url( $url );
+		$route = null;
+		$query_params = array();
+
+		if ( ! empty( $bits['query'] ) ) {
+			wp_parse_str( $bits['query'], $query_params );
+		}
+
+		if ( get_option( 'permalink_structure' ) ) {
+			$api_root = untrailingslashit( rest_url() );
+			$api_url_part = substr( $url, strlen( $api_root ) );
+			$parsed = parse_url( $api_url_part );
+			if ( ! empty( $parsed['path'] ) ) {
+				$route = $parsed['path'];
+			}
+		} elseif ( ! empty( $query_params['rest_route'] ) ) {
+			$route = $query_params['rest_route'];
+			unset( $query_params['rest_route'] );
+		}
+
+		$request = false;
+		if ( ! empty( $route ) ) {
+			$request = new WP_REST_Request( 'GET', $route );
+			$request->set_query_params( $query_params );
+		}
+
+		/**
+		 * Filter the request generated from a URL.
+		 *
+		 * @param WP_REST_Request|false $request Generated request object, or false if URL could not be parsed.
+		 * @param string $url URL the request was generated from.
+		 */
+		return apply_filters( 'rest_request_from_url', $request, $url );
+	}
 }
Index: tests/phpunit/tests/rest-api/rest-request.php
===================================================================
--- tests/phpunit/tests/rest-api/rest-request.php	(revision 36671)
+++ tests/phpunit/tests/rest-api/rest-request.php	(working copy)
@@ -420,4 +420,32 @@
 	public function _return_wp_error_on_validate_callback() {
 		return new WP_Error( 'some-error', 'This is not valid!' );
 	}
+
+	/**
+	 * @dataProvider data_rest_request_from_url
+	 */
+	public function test_rest_request_from_url( $permalink_structure, $original_url ) {
+		update_option( 'permalink_structure', $permalink_structure );
+		$url = add_query_arg( 'foo', 'bar', rest_url( '/wp/v2/posts/1' ) );
+		$this->assertEquals( $original_url, $url );
+		$request = WP_REST_Request::from_url( $url );
+		$this->assertInstanceOf( 'WP_REST_Request', $request );
+		$this->assertEquals( '/wp/v2/posts/1', $request->get_route() );
+		$this->assertEqualSets( array(
+			'foo' => 'bar',
+		), $request->get_query_params() );
+	}
+
+	public function data_rest_request_from_url() {
+		return array(
+			array(
+				'permalink_structure' => '/%post_name%/',
+				'original_url'        => 'http://' . WP_TESTS_DOMAIN . '/wp-json/wp/v2/posts/1?foo=bar',
+			),
+			array(
+				'permalink_structure' => '',
+				'original_url'        => 'http://' . WP_TESTS_DOMAIN . '/?rest_route=%2Fwp%2Fv2%2Fposts%2F1&foo=bar',
+			),
+		);
+	}
 }
