Skip to content

Commit ea2527c

Browse files
Add asset backend proxy pipeline through authenticated s3 and fastly io (#668)
1 parent 70b8aba commit ea2527c

52 files changed

Lines changed: 9185 additions & 704 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/integration-tests/Cargo.lock

Lines changed: 5 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/trusted-server-adapter-fastly/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ workspace = true
99
[dependencies]
1010
async-trait = { workspace = true }
1111
base64 = { workspace = true }
12+
bytes = { workspace = true }
1213
chrono = { workspace = true }
1314
edgezero-adapter-fastly = { workspace = true, features = ["fastly"] }
1415
edgezero-core = { workspace = true }

crates/trusted-server-adapter-fastly/src/main.rs

Lines changed: 147 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ use trusted_server_core::geo::GeoInfo;
2727
use trusted_server_core::integrations::{IntegrationRegistry, ProxyDispatchInput};
2828
use trusted_server_core::platform::RuntimeServices;
2929
use 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
};
3334
use 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

Comments
 (0)