Skip to main content

junobuild_storage/http/
response.rs

1use crate::constants::{
2    RESPONSE_STATUS_CODE_308, RESPONSE_STATUS_CODE_404, RESPONSE_STATUS_CODE_406,
3    RESPONSE_STATUS_CODE_500,
4};
5use crate::http::headers::build_redirect_headers;
6use crate::http::types::{HeaderField, HttpResponse, StatusCode};
7use crate::http::utils::{
8    build_encodings, build_response_headers, build_response_redirect_headers, streaming_strategy,
9};
10use crate::strategies::{StorageCertificateStrategy, StorageStateStrategy};
11use crate::types::config::{StorageConfigIFrame, StorageConfigRedirect};
12use crate::types::store::Asset;
13use junobuild_collections::types::rules::Memory;
14
15#[allow(clippy::too_many_arguments)]
16pub fn build_asset_response(
17    requested_url: String,
18    requested_headers: Vec<HeaderField>,
19    certificate_version: Option<u16>,
20    asset: Option<(Asset, Memory)>,
21    rewrite_source: Option<String>,
22    status_code: StatusCode,
23    storage_state: &impl StorageStateStrategy,
24    certificate: &impl StorageCertificateStrategy,
25) -> HttpResponse {
26    match asset {
27        Some((asset, memory)) => {
28            let encodings = build_encodings(requested_headers);
29
30            for encoding_type in encodings.iter() {
31                if let Some(encoding) = asset.encodings.get(encoding_type) {
32                    let headers = build_response_headers(
33                        &requested_url,
34                        &asset,
35                        encoding,
36                        encoding_type,
37                        &certificate_version,
38                        &rewrite_source,
39                        &storage_state.get_config(),
40                        certificate.get_pruned_labeled_sigs_root_hash_tree(),
41                    );
42
43                    let Asset { key, .. } = &asset;
44
45                    match headers {
46                        Ok(headers) => {
47                            // Note: We need to return the body regardless if the requested method is GET or HEAD.
48                            // It seems that the Boundary Nodes are expecting a body for HEAD requests otherwise
49                            // some checks are failing on their side and requests end in 503.
50                            let body = storage_state.get_content_chunks(encoding, 0, &memory);
51
52                            // TODO: support for HTTP response 304
53                            // On hold til DFINITY foundation implements:
54                            // "Add etag support to icx-proxy" - https://dfinity.atlassian.net/browse/BOUN-446
55                            // See const STATUS_CODES_TO_CERTIFY: [u16; 2] = [200, 304]; in sdk certified asset canister for implementation reference
56
57                            match body {
58                                Some(body) => {
59                                    return HttpResponse {
60                                        body: body.clone(),
61                                        headers: headers.clone(),
62                                        status_code,
63                                        streaming_strategy: streaming_strategy(
64                                            key,
65                                            encoding,
66                                            encoding_type,
67                                            &headers,
68                                            &memory,
69                                        ),
70                                    }
71                                }
72                                None => {
73                                    return HttpResponse {
74                                        body: vec![],
75                                        headers: headers.clone(),
76                                        status_code,
77                                        streaming_strategy: None,
78                                    }
79                                }
80                            }
81                        }
82                        Err(err) => {
83                            return error_response(
84                                RESPONSE_STATUS_CODE_406,
85                                ["Permission denied. Invalid headers. ", err].join(""),
86                            );
87                        }
88                    }
89                }
90            }
91
92            error_response(
93                RESPONSE_STATUS_CODE_500,
94                "No asset encoding found.".to_string(),
95            )
96        }
97        None => error_response(RESPONSE_STATUS_CODE_404, "No asset found.".to_string()),
98    }
99}
100
101pub fn build_redirect_response(
102    requested_url: String,
103    certificate_version: Option<u16>,
104    redirect: &StorageConfigRedirect,
105    iframe: &StorageConfigIFrame,
106    certificate: &impl StorageCertificateStrategy,
107) -> HttpResponse {
108    let headers = build_response_redirect_headers(
109        &requested_url,
110        &redirect.location,
111        iframe,
112        &certificate_version,
113        certificate.get_pruned_labeled_sigs_root_hash_tree(),
114    )
115    .unwrap();
116
117    HttpResponse {
118        body: Vec::new().clone(),
119        headers: headers.clone(),
120        status_code: redirect.status_code,
121        streaming_strategy: None,
122    }
123}
124
125pub fn build_redirect_raw_response(
126    redirect_url: &str,
127    iframe: &StorageConfigIFrame,
128) -> HttpResponse {
129    let headers = build_redirect_headers(redirect_url, iframe);
130
131    HttpResponse {
132        body: Vec::new().clone(),
133        headers: headers.clone(),
134        status_code: RESPONSE_STATUS_CODE_308,
135        streaming_strategy: None,
136    }
137}
138
139pub fn error_response(status_code: StatusCode, body: String) -> HttpResponse {
140    HttpResponse {
141        body: body.as_bytes().to_vec(),
142        headers: Vec::new(),
143        status_code,
144        streaming_strategy: None,
145    }
146}