| 117 | * @public |
| 118 | */ |
| 119 | export async function handleUserAssetGet({ |
| 120 | request, |
| 121 | bucket, |
| 122 | objectName, |
| 123 | context, |
| 124 | }: { |
| 125 | request: IRequest |
| 126 | bucket: R2BucketLike |
| 127 | objectName: string |
| 128 | context: ExecutionContext |
| 129 | }): Promise<Response> { |
| 130 | // this cache automatically handles range responses etc. |
| 131 | const cacheKey = new Request(request.url, { headers: request.headers }) |
| 132 | const cachedResponse = await caches.default.match(cacheKey) |
| 133 | if (cachedResponse) { |
| 134 | return cachedResponse |
| 135 | } |
| 136 | |
| 137 | const object = await retry( |
| 138 | () => bucket.get(objectName, { range: request.headers, onlyIf: request.headers }), |
| 139 | TRANSIENT_RETRY_OPTIONS |
| 140 | ) |
| 141 | |
| 142 | if (!object) { |
| 143 | return notFound() |
| 144 | } |
| 145 | |
| 146 | const headers = new Headers() |
| 147 | object.writeHttpMetadata(headers) |
| 148 | |
| 149 | // assets are immutable, so we can cache them basically forever: |
| 150 | headers.set('cache-control', 'public, max-age=31536000, immutable') |
| 151 | headers.set('etag', object.httpEtag) |
| 152 | |
| 153 | // we set CORS headers so all clients can access assets. we do this here so our `cors` helper in |
| 154 | // worker.ts doesn't try to set extra cors headers on responses that have been read from the |
| 155 | // cache, which isn't allowed by cloudflare. |
| 156 | headers.set('access-control-allow-origin', '*') |
| 157 | |
| 158 | // Prevent XSS from user-uploaded SVGs (or any file served with an executable content-type). |
| 159 | // This is critical when assets are served from the same origin as the app. |
| 160 | headers.set('content-security-policy', "default-src 'none'") |
| 161 | headers.set('x-content-type-options', 'nosniff') |
| 162 | |
| 163 | // cloudflare doesn't set the content-range header automatically in writeHttpMetadata, so we |
| 164 | // need to do it ourselves. |
| 165 | let contentRange |
| 166 | if (object.range) { |
| 167 | if ('suffix' in object.range) { |
| 168 | const start = object.size - object.range.suffix |
| 169 | const end = object.size - 1 |
| 170 | contentRange = `bytes ${start}-${end}/${object.size}` |
| 171 | } else { |
| 172 | const start = object.range.offset ?? 0 |
| 173 | const end = object.range.length ? start + object.range.length - 1 : object.size - 1 |
| 174 | if (start !== 0 || end !== object.size - 1) { |
| 175 | contentRange = `bytes ${start}-${end}/${object.size}` |
| 176 | } |