@@ -71,28 +71,48 @@ export class BlobsServer {
7171 }
7272
7373 async delete ( req : http . IncomingMessage , res : http . ServerResponse ) {
74+ const apiMatch = this . parseAPIRequest ( req )
75+
76+ if ( apiMatch ) {
77+ return this . sendResponse ( req , res , 200 , JSON . stringify ( { url : apiMatch . url . toString ( ) } ) )
78+ }
79+
7480 const url = new URL ( req . url ?? '' , this . address )
75- const { dataPath, key } = this . getLocalPaths ( url )
81+ const { dataPath, key, metadataPath } = this . getLocalPaths ( url )
7682
7783 if ( ! dataPath || ! key ) {
7884 return this . sendResponse ( req , res , 400 )
7985 }
8086
87+ // Try to delete the metadata file, if one exists.
88+ try {
89+ await fs . rm ( metadataPath , { force : true , recursive : true } )
90+ } catch {
91+ // no-op
92+ }
93+
94+ // Delete the data file.
8195 try {
82- await fs . rm ( dataPath , { recursive : true } )
96+ await fs . rm ( dataPath , { force : true , recursive : true } )
8397 } catch ( error : unknown ) {
84- if ( isNodeError ( error ) && error . code === 'ENOENT' ) {
85- return this . sendResponse ( req , res , 404 )
98+ // An `ENOENT` error means we have tried to delete a key that doesn't
99+ // exist, which shouldn't be treated as an error.
100+ if ( ! isNodeError ( error ) || error . code !== 'ENOENT' ) {
101+ return this . sendResponse ( req , res , 500 )
86102 }
87-
88- return this . sendResponse ( req , res , 500 )
89103 }
90104
91- return this . sendResponse ( req , res , 200 )
105+ return this . sendResponse ( req , res , 204 )
92106 }
93107
94108 async get ( req : http . IncomingMessage , res : http . ServerResponse ) {
95- const url = new URL ( req . url ?? '' , this . address )
109+ const apiMatch = this . parseAPIRequest ( req )
110+ const url = apiMatch ?. url ?? new URL ( req . url ?? '' , this . address )
111+
112+ if ( apiMatch ?. key ) {
113+ return this . sendResponse ( req , res , 200 , JSON . stringify ( { url : apiMatch . url . toString ( ) } ) )
114+ }
115+
96116 const { dataPath, key, metadataPath, rootPath } = this . getLocalPaths ( url )
97117
98118 if ( ! dataPath || ! metadataPath ) {
@@ -135,29 +155,29 @@ export class BlobsServer {
135155 }
136156
137157 async head ( req : http . IncomingMessage , res : http . ServerResponse ) {
138- const url = new URL ( req . url ?? '' , this . address )
158+ const url = this . parseAPIRequest ( req ) ?. url ?? new URL ( req . url ?? '' , this . address )
139159 const { dataPath, key, metadataPath } = this . getLocalPaths ( url )
140160
141161 if ( ! dataPath || ! metadataPath || ! key ) {
142162 return this . sendResponse ( req , res , 400 )
143163 }
144164
145- const headers : Record < string , string > = { }
146-
147165 try {
148166 const rawData = await fs . readFile ( metadataPath , 'utf8' )
149167 const metadata = JSON . parse ( rawData )
150168 const encodedMetadata = encodeMetadata ( metadata )
151169
152170 if ( encodedMetadata ) {
153- headers [ METADATA_HEADER_INTERNAL ] = encodedMetadata
171+ res . setHeader ( METADATA_HEADER_INTERNAL , encodedMetadata )
154172 }
155173 } catch ( error ) {
174+ if ( isNodeError ( error ) && ( error . code === 'ENOENT' || error . code === 'ISDIR' ) ) {
175+ return this . sendResponse ( req , res , 404 )
176+ }
177+
156178 this . logDebug ( 'Could not read metadata file:' , error )
157- }
158179
159- for ( const name in headers ) {
160- res . setHeader ( name , headers [ name ] )
180+ return this . sendResponse ( req , res , 500 )
161181 }
162182
163183 res . end ( )
@@ -182,9 +202,13 @@ export class BlobsServer {
182202 try {
183203 await BlobsServer . walk ( { directories, path : dataPath , prefix, rootPath, result } )
184204 } catch ( error ) {
185- this . logDebug ( 'Could not perform list:' , error )
205+ // If the directory is not found, it just means there are no entries on
206+ // the store, so that shouldn't be treated as an error.
207+ if ( ! isNodeError ( error ) || error . code !== 'ENOENT' ) {
208+ this . logDebug ( 'Could not perform list:' , error )
186209
187- return this . sendResponse ( req , res , 500 )
210+ return this . sendResponse ( req , res , 500 )
211+ }
188212 }
189213
190214 res . setHeader ( 'content-type' , 'application/json' )
@@ -193,6 +217,12 @@ export class BlobsServer {
193217 }
194218
195219 async put ( req : http . IncomingMessage , res : http . ServerResponse ) {
220+ const apiMatch = this . parseAPIRequest ( req )
221+
222+ if ( apiMatch ) {
223+ return this . sendResponse ( req , res , 200 , JSON . stringify ( { url : apiMatch . url . toString ( ) } ) )
224+ }
225+
196226 const url = new URL ( req . url ?? '' , this . address )
197227 const { dataPath, key, metadataPath } = this . getLocalPaths ( url )
198228
@@ -263,19 +293,6 @@ export class BlobsServer {
263293 return this . sendResponse ( req , res , 403 )
264294 }
265295
266- const apiURLMatch = req . url . match ( API_URL_PATH )
267-
268- // If this matches an API URL, return a signed URL.
269- if ( apiURLMatch ) {
270- const fullURL = new URL ( req . url , this . address )
271- const storeName = fullURL . searchParams . get ( 'context' ) ?? DEFAULT_STORE
272- const key = apiURLMatch . groups ?. key as string
273- const siteID = apiURLMatch . groups ?. site_id as string
274- const url = `${ this . address } /${ siteID } /${ storeName } /${ key } ?signature=${ this . tokenHash } `
275-
276- return this . sendResponse ( req , res , 200 , JSON . stringify ( { url } ) )
277- }
278-
279296 switch ( req . method ) {
280297 case 'DELETE' :
281298 return this . delete ( req , res )
@@ -295,8 +312,38 @@ export class BlobsServer {
295312 }
296313 }
297314
315+ /**
316+ * Tries to parse a URL as being an API request and returns the different
317+ * components, such as the store name, site ID, key, and signed URL.
318+ */
319+ parseAPIRequest ( req : http . IncomingMessage ) {
320+ if ( ! req . url ) {
321+ return null
322+ }
323+
324+ const apiURLMatch = req . url . match ( API_URL_PATH )
325+
326+ if ( ! apiURLMatch ) {
327+ return null
328+ }
329+
330+ const fullURL = new URL ( req . url , this . address )
331+ const storeName = fullURL . searchParams . get ( 'context' ) ?? DEFAULT_STORE
332+ const key = apiURLMatch . groups ?. key
333+ const siteID = apiURLMatch . groups ?. site_id as string
334+ const urlPath = [ siteID , storeName , key ] . filter ( Boolean ) as string [ ]
335+ const url = new URL ( `/${ urlPath . join ( '/' ) } ?signature=${ this . tokenHash } ` , this . address )
336+
337+ return {
338+ key,
339+ siteID,
340+ storeName,
341+ url,
342+ }
343+ }
344+
298345 sendResponse ( req : http . IncomingMessage , res : http . ServerResponse , status : number , body ?: string ) {
299- this . logDebug ( `${ req . method } ${ req . url } : ${ status } ` )
346+ this . logDebug ( `${ req . method } ${ req . url } ${ status } ` )
300347
301348 res . writeHead ( status )
302349 res . end ( body )
0 commit comments