1818 */
1919class Static_Site_Importer_URL_Site_Collector {
2020
21- private const DEFAULT_MAX_PAGES = 20 ;
22- private const DEFAULT_MAX_ASSETS = 200 ;
23- private const DEFAULT_MAX_TOTAL_BYTES = 52428800 ;
24- private const MAX_PAGES = 250 ;
25- private const MAX_ASSETS = 2000 ;
26- private const MAX_TOTAL_BYTES = 268435456 ;
27- private const MAX_RESPONSE_BYTES = 10485760 ;
28- private const MAX_SITEMAP_DOCUMENTS = 100 ;
29- private const MAX_DISCOVERED_ROUTES = 5000 ;
30- // The hardened socket transport is blocking, so one PHP invocation can safely admit one request at a time.
31- private const SAME_ORIGIN_CONCURRENCY = 1 ;
32- private const CROSS_ORIGIN_CONCURRENCY = 1 ;
21+ private const DEFAULT_MAX_PAGES = 20 ;
22+ private const DEFAULT_MAX_ASSETS = 200 ;
23+ private const DEFAULT_MAX_TOTAL_BYTES = 52428800 ;
24+ private const MAX_PAGES = 250 ;
25+ private const MAX_ASSETS = 2000 ;
26+ private const MAX_TOTAL_BYTES = 268435456 ;
27+ private const MAX_RESPONSE_BYTES = 10485760 ;
28+ private const MAX_SITEMAP_DOCUMENTS = 100 ;
29+ private const MAX_DISCOVERED_ROUTES = 5000 ;
30+ private const SAME_ORIGIN_CONCURRENCY = 2 ;
31+ private const CROSS_ORIGIN_CONCURRENCY = 4 ;
3332
3433 /**
3534 * Collect a public static site.
@@ -48,6 +47,7 @@ public static function collect( string $url, array $args = array(), ?callable $f
4847 $ max_pages = min ( self ::MAX_PAGES , max ( 1 , (int ) ( $ args ['max_pages ' ] ?? self ::DEFAULT_MAX_PAGES ) ) );
4948 $ max_assets = min ( self ::MAX_ASSETS , max ( 0 , (int ) ( $ args ['max_assets ' ] ?? self ::DEFAULT_MAX_ASSETS ) ) );
5049 $ max_total_bytes = min ( self ::MAX_TOTAL_BYTES , max ( 1 , (int ) ( $ args ['max_total_bytes ' ] ?? self ::DEFAULT_MAX_TOTAL_BYTES ) ) );
50+ $ use_many_fetcher = null === $ fetcher ;
5151 $ fetcher = $ fetcher ?? static fn ( string $ resource_url , array $ fetch_args ) => Static_Site_Importer_URL_Fetcher::fetch ( $ resource_url , $ fetch_args );
5252 $ fetcher = self ::scheduled_fetcher ( $ fetcher , $ args );
5353 $ fetch_args = array_intersect_key ( $ args , array_flip ( array ( 'timeout ' ) ) );
@@ -91,9 +91,10 @@ public static function collect( string $url, array $args = array(), ?callable $f
9191 }
9292 }
9393
94+ $ page_fetcher = self ::prefetched_fetcher ( array_slice ( $ page_queue , 0 , $ max_pages ), array_merge ( $ fetch_args , array ( 'content_types ' => array ( 'text/html ' , 'application/xhtml+xml ' ) ) ), $ fetcher , $ use_many_fetcher , $ args );
9495 while ( $ page_queue && self ::resource_count ( $ resources , 'html ' ) < $ max_pages ) {
9596 $ page_url = array_shift ( $ page_queue );
96- $ response = $ fetcher ( $ page_url , array_merge ( $ fetch_args , array ( 'content_types ' => array ( 'text/html ' , 'application/xhtml+xml ' ) ) ) );
97+ $ response = $ page_fetcher ( $ page_url , array_merge ( $ fetch_args , array ( 'content_types ' => array ( 'text/html ' , 'application/xhtml+xml ' ) ) ) );
9798 $ response = self ::without_cache_marker ( $ response );
9899 if ( is_wp_error ( $ response ) ) {
99100 if ( $ page_url === $ entry_url ) {
@@ -176,9 +177,10 @@ public static function collect( string $url, array $args = array(), ?callable $f
176177 }
177178 }
178179
180+ $ asset_fetcher = self ::prefetched_fetcher ( array_slice ( $ asset_queue , 0 , $ max_assets ), array_merge ( $ fetch_args , array ( 'content_types ' => array () ) ), $ fetcher , $ use_many_fetcher , $ args );
179181 while ( $ asset_queue && self ::resource_count ( $ resources , 'asset ' ) < $ max_assets ) {
180182 $ asset_url = array_shift ( $ asset_queue );
181- $ response = $ fetcher ( $ asset_url , array_merge ( $ fetch_args , array ( 'content_types ' => array () ) ) );
183+ $ response = $ asset_fetcher ( $ asset_url , array_merge ( $ fetch_args , array ( 'content_types ' => array () ) ) );
182184 $ response = self ::without_cache_marker ( $ response );
183185 if ( is_wp_error ( $ response ) ) {
184186 if ( $ preserve_failed_assets ) {
@@ -872,6 +874,55 @@ private static function external_asset_samples( array $external_assets ): array
872874 return $ samples ;
873875 }
874876
877+ /** @return callable */
878+ private static function prefetched_fetcher ( array $ urls , array $ fetch_args , callable $ fetcher , bool $ use_many_fetcher , array $ args ): callable {
879+ if ( ! $ use_many_fetcher || ! $ urls ) {
880+ return $ fetcher ;
881+ }
882+ $ responses = self ::fetch_batch ( $ urls , $ fetch_args , $ args );
883+ return static fn ( string $ url , array $ request_args ) => $ responses [ $ url ] ?? $ fetcher ( $ url , $ request_args );
884+ }
885+
886+ /** @return array<string,array|WP_Error> */
887+ private static function fetch_batch ( array $ urls , array $ fetch_args , array $ args ): array {
888+ $ attempts = min ( 3 , max ( 1 , (int ) ( $ args ['fetch_attempts ' ] ?? 2 ) ) );
889+ $ delay = min ( 2000 , max ( 0 , (int ) ( $ args ['request_delay_ms ' ] ?? 0 ) ) );
890+ $ pending = array_values ( $ urls );
891+ $ responses = array ();
892+ for ( $ attempt = 0 ; $ pending && $ attempt < $ attempts ; $ attempt ++ ) {
893+ $ requests = array ();
894+ foreach ( $ pending as $ url ) {
895+ $ requests [ $ url ] = array (
896+ 'url ' => $ url ,
897+ 'args ' => $ fetch_args ,
898+ );
899+ }
900+ $ many_args = array (
901+ 'concurrency ' => self ::CROSS_ORIGIN_CONCURRENCY ,
902+ 'per_origin_concurrency ' => self ::SAME_ORIGIN_CONCURRENCY ,
903+ );
904+ if ( isset ( $ args ['_static_site_importer_fetch_deadline ' ] ) ) {
905+ $ many_args ['deadline ' ] = (float ) $ args ['_static_site_importer_fetch_deadline ' ];
906+ }
907+ if ( isset ( $ args ['_static_site_importer_fetch_clock ' ] ) && is_callable ( $ args ['_static_site_importer_fetch_clock ' ] ) ) {
908+ $ many_args ['clock ' ] = $ args ['_static_site_importer_fetch_clock ' ];
909+ }
910+ if ( isset ( $ args ['_static_site_importer_fetch_many_transport ' ] ) && is_array ( $ args ['_static_site_importer_fetch_many_transport ' ] ) ) {
911+ $ many_args ['transport ' ] = $ args ['_static_site_importer_fetch_many_transport ' ];
912+ }
913+ $ batch = Static_Site_Importer_URL_Fetcher::fetch_many ( $ requests , $ many_args );
914+ $ pending = array ();
915+ foreach ( $ batch as $ url => $ response ) {
916+ $ responses [ $ url ] = $ response ;
917+ if ( is_wp_error ( $ response ) && $ attempt + 1 < $ attempts ) {
918+ self ::delay ( $ delay , $ args );
919+ $ pending [] = $ url ;
920+ }
921+ }
922+ }
923+ return $ responses ;
924+ }
925+
875926 /** @return callable */
876927 private static function scheduled_fetcher ( callable $ fetch_resource , array $ args ): callable {
877928 $ fetch_attempts = min ( 3 , max ( 1 , (int ) ( $ args ['fetch_attempts ' ] ?? 2 ) ) );
0 commit comments