@@ -27,8 +27,9 @@ use trusted_server_core::geo::GeoInfo;
2727use trusted_server_core:: integrations:: { IntegrationRegistry , ProxyDispatchInput } ;
2828use trusted_server_core:: platform:: RuntimeServices ;
2929use trusted_server_core:: proxy:: {
30- handle_first_party_click, handle_first_party_proxy, handle_first_party_proxy_rebuild,
31- handle_first_party_proxy_sign,
30+ handle_asset_proxy_request, handle_first_party_click, handle_first_party_proxy,
31+ handle_first_party_proxy_rebuild, handle_first_party_proxy_sign, stream_asset_body,
32+ AssetProxyCachePolicy ,
3233} ;
3334use trusted_server_core:: publisher:: {
3435 handle_publisher_request, handle_tsjs_dynamic, stream_publisher_body, PublisherResponse ,
@@ -325,6 +326,9 @@ async fn route_request(
325326 let path = req. get_path ( ) . to_string ( ) ;
326327 let method = req. get_method ( ) . clone ( ) ;
327328
329+ let mut asset_cache_policy = AssetProxyCachePolicy :: OriginControlled ;
330+ let mut should_finalize_ec = true ;
331+
328332 // Match known routes and handle them
329333 let ( result, organic_route) = match ( method, path. as_str ( ) ) {
330334 // Serve the tsjs library
@@ -387,18 +391,22 @@ async fn route_request(
387391 }
388392
389393 // tsjs endpoints
390- ( Method :: GET , "/first-party/proxy" ) => {
391- ( handle_first_party_proxy ( settings, req) . await , false )
392- }
393- ( Method :: GET , "/first-party/click" ) => {
394- ( handle_first_party_click ( settings, req) . await , false )
395- }
396- ( Method :: GET , "/first-party/sign" ) | ( Method :: POST , "/first-party/sign" ) => {
397- ( handle_first_party_proxy_sign ( settings, req) . await , false )
398- }
399- ( Method :: POST , "/first-party/proxy-rebuild" ) => {
400- ( handle_first_party_proxy_rebuild ( settings, req) . await , false )
401- }
394+ ( Method :: GET , "/first-party/proxy" ) => (
395+ handle_first_party_proxy ( settings, runtime_services, req) . await ,
396+ false ,
397+ ) ,
398+ ( Method :: GET , "/first-party/click" ) => (
399+ handle_first_party_click ( settings, runtime_services, req) . await ,
400+ false ,
401+ ) ,
402+ ( Method :: GET , "/first-party/sign" ) | ( Method :: POST , "/first-party/sign" ) => (
403+ handle_first_party_proxy_sign ( settings, runtime_services, req) . await ,
404+ false ,
405+ ) ,
406+ ( Method :: POST , "/first-party/proxy-rebuild" ) => (
407+ handle_first_party_proxy_rebuild ( settings, runtime_services, req) . await ,
408+ false ,
409+ ) ,
402410 ( m, path) if integration_registry. has_route ( & m, path) => {
403411 let result = integration_registry
404412 . handle_proxy ( ProxyDispatchInput {
@@ -407,6 +415,7 @@ async fn route_request(
407415 settings,
408416 kv : kv_graph. as_ref ( ) ,
409417 ec_context : & mut ec_context,
418+ services : runtime_services,
410419 req,
411420 } )
412421 . await
@@ -418,78 +427,121 @@ async fn route_request(
418427 ( result, true )
419428 }
420429
421- // No known route matched, proxy to publisher origin as fallback
422- _ => {
423- log:: info!(
424- "No known route matched for path: {}, proxying to publisher origin" ,
425- path
426- ) ;
430+ // No known route matched, proxy to an asset origin or publisher origin as fallback
431+ ( method, _) => {
432+ let matched_asset_route = matches ! ( method, Method :: GET | Method :: HEAD )
433+ . then ( || settings. asset_route_for_path ( & path) )
434+ . flatten ( ) ;
435+
436+ if let Some ( asset_route) = matched_asset_route {
437+ should_finalize_ec = false ;
438+ log:: info!( "No explicit route matched; proxying via configured asset route" ) ;
439+ let result =
440+ match handle_asset_proxy_request ( settings, runtime_services, req, asset_route)
441+ . await
442+ {
443+ Ok ( asset_response) => {
444+ asset_cache_policy = asset_response. cache_policy ( ) ;
445+ let ( mut response, stream_body) =
446+ asset_response. into_response_and_body ( ) ;
447+ if let Some ( body) = stream_body {
448+ finalize_response ( settings, geo_info. as_ref ( ) , & mut response) ;
449+ asset_cache_policy. apply_after_route_finalization ( & mut response) ;
450+
451+ let mut streaming_body = response. stream_to_client ( ) ;
452+ if let Err ( err) = stream_asset_body ( body, & mut streaming_body) . await
453+ {
454+ log:: error!( "Asset streaming failed: {err:?}" ) ;
455+ drop ( streaming_body) ;
456+ } else if let Err ( err) = streaming_body. finish ( ) {
457+ log:: error!( "Failed to finish asset streaming body: {err}" ) ;
458+ }
459+
460+ return Ok ( RouteOutcome {
461+ response : None ,
462+ pull_sync_context : None ,
463+ } ) ;
464+ }
465+ Ok ( response)
466+ }
467+ Err ( e) => {
468+ asset_cache_policy = AssetProxyCachePolicy :: NoStorePrivate ;
469+ Err ( e)
470+ }
471+ } ;
472+ ( result, false )
473+ } else {
474+ log:: info!(
475+ "No known route matched for path: {}, proxying to publisher origin" ,
476+ path
477+ ) ;
427478
428- match handle_publisher_request (
429- settings,
430- integration_registry,
431- runtime_services,
432- kv_graph. as_ref ( ) ,
433- & mut ec_context,
434- req,
435- ) {
436- Ok ( PublisherResponse :: Stream {
437- mut response,
438- body,
439- params,
440- } ) => {
441- // Publisher fallback has multiple delivery modes.
442- // EC finalization is header-only, so it must happen before
443- // headers are committed on the streaming path.
444- ec_finalize_response (
445- settings,
446- & ec_context,
447- finalize_kv_graph. as_ref ( ) ,
448- partner_registry,
449- eids_cookie. as_deref ( ) ,
450- sharedid_cookie. as_deref ( ) ,
451- & mut response,
452- ) ;
453- finalize_response ( settings, geo_info. as_ref ( ) , & mut response) ;
454-
455- let mut streaming_body = response. stream_to_client ( ) ;
456- let mut stream_succeeded = false ;
457- if let Err ( err) = stream_publisher_body (
479+ match handle_publisher_request (
480+ settings,
481+ integration_registry,
482+ runtime_services,
483+ kv_graph. as_ref ( ) ,
484+ & mut ec_context,
485+ req,
486+ ) {
487+ Ok ( PublisherResponse :: Stream {
488+ mut response,
458489 body,
459- & mut streaming_body,
460- & params,
461- settings,
462- integration_registry,
463- ) {
464- // Headers are already committed. Log and abort rather
465- // than trying to replace the response mid-stream.
466- log:: error!( "Streaming processing failed: {err:?}" ) ;
467- drop ( streaming_body) ;
468- } else if let Err ( err) = streaming_body. finish ( ) {
469- log:: error!( "Failed to finish streaming body: {err}" ) ;
470- } else {
471- stream_succeeded = true ;
490+ params,
491+ } ) => {
492+ // Publisher fallback has multiple delivery modes.
493+ // EC finalization is header-only, so it must happen before
494+ // headers are committed on the streaming path.
495+ ec_finalize_response (
496+ settings,
497+ & ec_context,
498+ finalize_kv_graph. as_ref ( ) ,
499+ partner_registry,
500+ eids_cookie. as_deref ( ) ,
501+ sharedid_cookie. as_deref ( ) ,
502+ & mut response,
503+ ) ;
504+ finalize_response ( settings, geo_info. as_ref ( ) , & mut response) ;
505+
506+ let mut streaming_body = response. stream_to_client ( ) ;
507+ let mut stream_succeeded = false ;
508+ if let Err ( err) = stream_publisher_body (
509+ body,
510+ & mut streaming_body,
511+ & params,
512+ settings,
513+ integration_registry,
514+ ) {
515+ // Headers are already committed. Log and abort rather
516+ // than trying to replace the response mid-stream.
517+ log:: error!( "Streaming processing failed: {err:?}" ) ;
518+ drop ( streaming_body) ;
519+ } else if let Err ( err) = streaming_body. finish ( ) {
520+ log:: error!( "Failed to finish streaming body: {err}" ) ;
521+ } else {
522+ stream_succeeded = true ;
523+ }
524+
525+ let pull_sync_context = if is_real_browser && stream_succeeded {
526+ build_pull_sync_context ( & ec_context)
527+ } else {
528+ None
529+ } ;
530+
531+ return Ok ( RouteOutcome {
532+ response : None ,
533+ pull_sync_context,
534+ } ) ;
535+ }
536+ Ok ( PublisherResponse :: PassThrough { mut response, body } ) => {
537+ response. set_body ( body) ;
538+ ( Ok ( response) , true )
539+ }
540+ Ok ( PublisherResponse :: Buffered ( response) ) => ( Ok ( response) , true ) ,
541+ Err ( e) => {
542+ log:: error!( "Failed to proxy to publisher origin: {:?}" , e) ;
543+ ( Err ( e) , true )
472544 }
473-
474- let pull_sync_context = if is_real_browser && stream_succeeded {
475- build_pull_sync_context ( & ec_context)
476- } else {
477- None
478- } ;
479-
480- return Ok ( RouteOutcome {
481- response : None ,
482- pull_sync_context,
483- } ) ;
484- }
485- Ok ( PublisherResponse :: PassThrough { mut response, body } ) => {
486- response. set_body ( body) ;
487- ( Ok ( response) , true )
488- }
489- Ok ( PublisherResponse :: Buffered ( response) ) => ( Ok ( response) , true ) ,
490- Err ( e) => {
491- log:: error!( "Failed to proxy to publisher origin: {:?}" , e) ;
492- ( Err ( e) , true )
493545 }
494546 }
495547 }
@@ -503,17 +555,20 @@ async fn route_request(
503555 // Bot gate still suppresses generated EC writes and pull sync via
504556 // `kv_graph = None`, but withdrawal finalization uses `finalize_kv_graph`
505557 // so revocation tombstones are not lost for bot-classified clients.
506- ec_finalize_response (
507- settings,
508- & ec_context,
509- finalize_kv_graph. as_ref ( ) ,
510- partner_registry,
511- eids_cookie. as_deref ( ) ,
512- sharedid_cookie. as_deref ( ) ,
513- & mut response,
514- ) ;
558+ if should_finalize_ec {
559+ ec_finalize_response (
560+ settings,
561+ & ec_context,
562+ finalize_kv_graph. as_ref ( ) ,
563+ partner_registry,
564+ eids_cookie. as_deref ( ) ,
565+ sharedid_cookie. as_deref ( ) ,
566+ & mut response,
567+ ) ;
568+ }
515569
516570 finalize_response ( settings, geo_info. as_ref ( ) , & mut response) ;
571+ asset_cache_policy. apply_after_route_finalization ( & mut response) ;
517572
518573 let pull_sync_context = if is_real_browser && organic_route && route_succeeded {
519574 // Pull sync is intentionally refreshed only from successful organic
0 commit comments