forked from nodejs/release-cloudflare-worker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
r2Provider.ts
124 lines (108 loc) · 3.38 KB
/
r2Provider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { R2_RETRY_LIMIT } from '../constants/limits';
import { Context } from '../context';
import { objectHasBody } from '../utils/object';
import {
GetFileResult,
HeadResult,
HttpHeaders,
ReadDirectoryResult,
Provider,
} from './provider';
import { S3Provider } from './s3Provider';
function buildHttpHeadersFromR2HttpMetadata(object: R2Object): HttpHeaders {
const { httpMetadata } = object;
return {
etag: object.httpEtag,
'accept-range': 'bytes',
// https://github.com/nodejs/build/blob/e3df25d6a23f033db317a53ab1e904c953ba1f00/ansible/www-standalone/resources/config/nodejs.org?plain=1#L194-L196
'access-control-allow-origin': 'TODO', // TODO,
'cache-control': '', // TODO
expires: httpMetadata?.cacheExpiry?.toUTCString(),
'last-modified': object.uploaded.toUTCString(),
'content-language': httpMetadata?.contentLanguage,
'content-disposition': httpMetadata?.contentDisposition,
'content-length': object.size.toString(),
};
}
type R2ProviderCtorOptions = {
ctx: Context;
fallbackProvider?: Provider;
};
/**
* Provides assets from our R2 prod bucket.
*/
export class R2Provider implements Provider {
private ctx: Context;
private fallbackProvider?: Provider;
constructor({ ctx, fallbackProvider }: R2ProviderCtorOptions) {
this.ctx = ctx;
this.fallbackProvider = fallbackProvider;
}
async head(request: Request, path: string): Promise<HeadResult> {
let object: R2Object | null = null;
let r2Error: unknown = undefined;
for (let i = 0; i < R2_RETRY_LIMIT; i++) {
try {
object = await this.ctx.env.R2_BUCKET.head(path);
} catch (err) {
console.error(`R2Provider.head error: ${err}`);
r2Error = err;
}
}
if (r2Error !== undefined) {
this.ctx.sentry.captureException(r2Error);
if (this.fallbackProvider !== undefined) {
return this.fallbackProvider.head(request, path);
} else {
throw r2Error;
}
}
if (object === null) {
return { exists: false };
}
return {
exists: true,
httpStatus: 200,
httpHeaders: buildHttpHeadersFromR2HttpMetadata(object),
};
}
async getFile(request: Request, path: string): Promise<GetFileResult> {
let object: R2Object | null = null;
let r2Error: unknown = undefined;
for (let i = 0; i < R2_RETRY_LIMIT; i++) {
try {
object = await this.ctx.env.R2_BUCKET.get(path, {
onlyIf: request.headers,
range: request.headers,
});
} catch (err) {
console.error(`R2Provider.getFile error: ${err}`);
r2Error = err;
}
}
if (r2Error !== undefined) {
this.ctx.sentry.captureException(r2Error);
if (this.fallbackProvider !== undefined) {
return this.fallbackProvider.getFile(request, path);
} else {
throw r2Error;
}
}
if (object === null) {
return { exists: false };
}
return {
exists: true,
body: objectHasBody(object) ? object.body : undefined,
httpStatus: 200, // TODO return proper http status code
httpHeaders: buildHttpHeadersFromR2HttpMetadata(object),
};
}
readDirectory(request: Request, path: string): Promise<ReadDirectoryResult> {
const s3Provider = new S3Provider({
ctx: this.ctx,
fallbackProvider: this.fallbackProvider,
});
return s3Provider.readDirectory(request, path);
}
}