function objectNotFound(objectName: string): Response {
return new Response(`<html><body>R2 object "<b>${objectName}</b>" not found</body></html>`, {
'content-type': 'text/html; charset=UTF-8'
async fetch(request, env): Promise<Response> {
const url = new URL(request.url)
const objectName = url.pathname.slice(1)
console.log(`${request.method} object ${objectName}: ${request.url}`)
if (request.method === 'GET' || request.method === 'HEAD') {
if (request.method == 'HEAD') {
return new Response(undefined, { status: 400 })
const options: R2ListOptions = {
prefix: url.searchParams.get('prefix') ?? undefined,
delimiter: url.searchParams.get('delimiter') ?? undefined,
cursor: url.searchParams.get('cursor') ?? undefined,
include: ['customMetadata', 'httpMetadata'],
console.log(JSON.stringify(options))
const listing = await env.MY_BUCKET.list(options)
return new Response(JSON.stringify(listing), {headers: {
'content-type': 'application/json; charset=UTF-8',
if (request.method === 'GET') {
const object = await env.MY_BUCKET.get(objectName, {
return objectNotFound(objectName)
const headers = new Headers()
object.writeHttpMetadata(headers)
headers.set('etag', object.httpEtag)
headers.set("content-range", `bytes ${object.range.offset}-${object.range.end ?? object.size - 1}/${object.size}`)
const status = object.body ? (request.headers.get("range") !== null ? 206 : 200) : 304
return new Response(object.body, {
const object = await env.MY_BUCKET.head(objectName)
return objectNotFound(objectName)
const headers = new Headers()
object.writeHttpMetadata(headers)
headers.set('etag', object.httpEtag)
return new Response(null, {
if (request.method === 'PUT' || request.method == 'POST') {
const object = await env.MY_BUCKET.put(objectName, request.body, {
httpMetadata: request.headers,
return new Response(null, {
if (request.method === 'DELETE') {
await env.MY_BUCKET.delete(url.pathname.slice(1))
return new Response(`Unsupported method`, {
} satisfies ExportedHandler<Env>;