Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 9646519

Browse files
fix: fix metadata in local server (#112)
1 parent f04eaa5 commit 9646519

File tree

2 files changed

+148
-99
lines changed

2 files changed

+148
-99
lines changed

‎src/server.test.ts‎

Lines changed: 70 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,6 @@ const siteID = '9a003659-aaaa-0000-aaaa-63d3720d8621'
3131
const token = 'my-very-secret-token'
3232

3333
test('Reads and writes from the file system', async () => {
34-
const directory = await tmp.dir()
35-
const server = new BlobsServer({
36-
directory: directory.path,
37-
token,
38-
})
39-
const { port } = await server.start()
40-
const blobs = getStore({
41-
edgeURL: `http://localhost:${port}`,
42-
name: 'mystore',
43-
token,
44-
siteID,
45-
})
4634
const metadata = {
4735
features: {
4836
blobs: true,
@@ -51,27 +39,82 @@ test('Reads and writes from the file system', async () => {
5139
name: 'Netlify',
5240
}
5341

54-
await blobs.set('simple-key', 'value 1')
55-
expect(await blobs.get('simple-key')).toBe('value 1')
42+
// Store #1: Edge access
43+
const directory1 = await tmp.dir()
44+
const server1 = new BlobsServer({
45+
directory: directory1.path,
46+
token,
47+
})
48+
const { port: port1 } = await server1.start()
49+
const store1 = getStore({
50+
edgeURL: `http://localhost:${port1}`,
51+
name: 'mystore1',
52+
token,
53+
siteID,
54+
})
55+
56+
// Store #2: API access
57+
const directory2 = await tmp.dir()
58+
const server2 = new BlobsServer({
59+
directory: directory2.path,
60+
token,
61+
})
62+
const { port: port2 } = await server2.start()
63+
const store2 = getStore({
64+
apiURL: `http://localhost:${port2}`,
65+
name: 'mystore2',
66+
token,
67+
siteID,
68+
})
5669

57-
await blobs.set('simple-key', 'value 2', { metadata })
58-
expect(await blobs.get('simple-key')).toBe('value 2')
70+
for (const store of [store1, store2]) {
71+
const list1 = await store.list()
72+
expect(list1.blobs).toEqual([])
73+
expect(list1.directories).toEqual([])
5974

60-
await blobs.set('parent/child', 'value 3')
61-
expect(await blobs.get('parent/child')).toBe('value 3')
62-
expect(await blobs.get('parent')).toBe(null)
75+
await store.set('simple-key', 'value 1')
76+
expect(await store.get('simple-key')).toBe('value 1')
6377

64-
constentry=await blobs.getWithMetadata('simple-key')
65-
expect(entry?.metadata).toEqual(metadata)
78+
await store.set('simple-key','value 2',{ metadata })
79+
expect(awaitstore.get('simple-key')).toBe('value 2')
6680

67-
const entryMetadata = await blobs.getMetadata('simple-key')
68-
expect(entryMetadata?.metadata).toEqual(metadata)
81+
const list2 = await store.list()
82+
expect(list2.blobs.length).toBe(1)
83+
expect(list2.blobs[0].key).toBe('simple-key')
84+
expect(list2.directories).toEqual([])
6985

70-
await blobs.delete('simple-key')
71-
expect(await blobs.get('simple-key')).toBe(null)
86+
await store.set('parent/child', 'value 3')
87+
expect(await store.get('parent/child')).toBe('value 3')
88+
expect(await store.get('parent')).toBe(null)
7289

73-
await server.stop()
74-
await fs.rm(directory.path, { force: true, recursive: true })
90+
const entry = await store.getWithMetadata('simple-key')
91+
expect(entry?.metadata).toEqual(metadata)
92+
93+
const entryMetadata = await store.getMetadata('simple-key')
94+
expect(entryMetadata?.metadata).toEqual(metadata)
95+
96+
const childEntryMetdata = await store.getMetadata('parent/child')
97+
expect(childEntryMetdata?.metadata).toEqual({})
98+
99+
expect(await store.getWithMetadata('does-not-exist')).toBe(null)
100+
expect(await store.getMetadata('does-not-exist')).toBe(null)
101+
102+
await store.delete('simple-key')
103+
expect(await store.get('simple-key')).toBe(null)
104+
expect(await store.getMetadata('simple-key')).toBe(null)
105+
expect(await store.getWithMetadata('simple-key')).toBe(null)
106+
107+
const list3 = await store.list()
108+
expect(list3.blobs.length).toBe(1)
109+
expect(list3.blobs[0].key).toBe('parent/child')
110+
expect(list3.directories).toEqual([])
111+
}
112+
113+
await server1.stop()
114+
await fs.rm(directory1.path, { force: true, recursive: true })
115+
116+
await server2.stop()
117+
await fs.rm(directory2.path, { force: true, recursive: true })
75118
})
76119

77120
test('Separates keys from different stores', async () => {
@@ -218,44 +261,3 @@ test('Lists entries', async () => {
218261

219262
expect(parachutesSongs2.directories).toEqual([])
220263
})
221-
222-
test('Supports the API access interface', async () => {
223-
const directory = await tmp.dir()
224-
const server = new BlobsServer({
225-
directory: directory.path,
226-
token,
227-
})
228-
const { port } = await server.start()
229-
const blobs = getStore({
230-
apiURL: `http://localhost:${port}`,
231-
name: 'mystore',
232-
token,
233-
siteID,
234-
})
235-
const metadata = {
236-
features: {
237-
blobs: true,
238-
functions: true,
239-
},
240-
name: 'Netlify',
241-
}
242-
243-
await blobs.set('simple-key', 'value 1')
244-
expect(await blobs.get('simple-key')).toBe('value 1')
245-
246-
await blobs.set('simple-key', 'value 2', { metadata })
247-
expect(await blobs.get('simple-key')).toBe('value 2')
248-
249-
await blobs.set('parent/child', 'value 3')
250-
expect(await blobs.get('parent/child')).toBe('value 3')
251-
expect(await blobs.get('parent')).toBe(null)
252-
253-
const entry = await blobs.getWithMetadata('simple-key')
254-
expect(entry?.metadata).toEqual(metadata)
255-
256-
await blobs.delete('simple-key')
257-
expect(await blobs.get('simple-key')).toBe(null)
258-
259-
await server.stop()
260-
await fs.rm(directory.path, { force: true, recursive: true })
261-
})

‎src/server.ts‎

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /