Skip to content

Commit 45cb22a

Browse files
authored
fix: allocate less in interim (#973)
1 parent 553a610 commit 45cb22a

7 files changed

Lines changed: 331 additions & 52 deletions

File tree

src/http/plugins/header-validator.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ export const headerValidator = (options: HeaderValidatorOptions = {}) =>
2828
return payload
2929
}
3030

31-
const headers = Object.entries(reply.getHeaders())
32-
for (const [key, value] of headers) {
31+
const headers = reply.getHeaders()
32+
for (const key in headers) {
33+
if (!Object.prototype.hasOwnProperty.call(headers, key)) {
34+
continue
35+
}
36+
const value = headers[key]
3337
if (typeof value === 'string' && INVALID_HEADER_CHAR_PATTERN.test(value)) {
3438
throw ERRORS.InvalidHeaderChar(key, value)
3539
} else if (Array.isArray(value)) {
36-
for (let item of value) {
40+
for (const item of value) {
3741
if (typeof item === 'string' && INVALID_HEADER_CHAR_PATTERN.test(item)) {
3842
throw ERRORS.InvalidHeaderChar(key, item)
3943
}

src/http/plugins/log-request.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,47 @@ export const logRequest = (options: RequestLoggerOptions) =>
5757
* Adds req.resources and req.operation to the request object
5858
*/
5959
fastify.addHook('preHandler', async (req) => {
60-
const resourceFromParams = Object.values(req.params || {}).join('/')
61-
const resources = getFirstDefined<string[]>(
62-
req.resources,
63-
req.routeOptions.config.resources?.(req),
64-
(req.raw as any).resources,
65-
resourceFromParams ? [resourceFromParams] : ([] as string[])
66-
)
60+
let resources = req.resources
61+
62+
if (resources === undefined) {
63+
resources = req.routeOptions.config.resources?.(req)
64+
}
65+
66+
if (resources === undefined) {
67+
resources = (req.raw as any).resources
68+
}
69+
70+
if (resources === undefined) {
71+
const params = req.params as Record<string, unknown> | undefined
72+
let resourceFromParams = ''
73+
74+
if (params) {
75+
let first = true
76+
for (const key in params) {
77+
if (!Object.prototype.hasOwnProperty.call(params, key)) {
78+
continue
79+
}
80+
81+
if (!first) {
82+
resourceFromParams += '/'
83+
}
84+
85+
const value = params[key]
86+
resourceFromParams += value == null ? '' : String(value)
87+
first = false
88+
}
89+
}
90+
91+
resources = resourceFromParams ? [resourceFromParams] : []
92+
}
6793

6894
if (resources && resources.length > 0) {
69-
resources.map((resource, index) => {
95+
for (let index = 0; index < resources.length; index++) {
96+
const resource = resources[index]
7097
if (!resource.startsWith('/')) {
7198
resources[index] = `/${resource}`
7299
}
73-
})
100+
}
74101
}
75102

76103
req.resources = resources
@@ -170,12 +197,3 @@ function doRequestLog(req: FastifyRequest, options: LogRequestOptions) {
170197
serverTimes: req.serverTimings,
171198
})
172199
}
173-
174-
function getFirstDefined<T>(...values: any[]): T | undefined {
175-
for (const value of values) {
176-
if (value !== undefined) {
177-
return value
178-
}
179-
}
180-
return undefined
181-
}

src/http/routes/s3/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
xmlParser,
1313
} from '../../plugins'
1414
import { s3ErrorHandler } from './error-handler'
15-
import { findArrayPathsInSchemas, getRouter, RequestInput } from './router'
15+
import { findArrayPathsInSchemas, getRouter, RequestInput, RouteQuery } from './router'
1616

1717
const { s3ProtocolEnabled } = getConfig()
1818

@@ -41,7 +41,7 @@ export default async function routes(fastify: FastifyInstance) {
4141
if (
4242
s3Router.matchRoute(route, {
4343
type: req.isIcebergBucket ? 'iceberg' : undefined,
44-
query: (req.query as Record<string, string>) || {},
44+
query: (req.query as RouteQuery) || {},
4545
headers: (req.headers as Record<string, string>) || {},
4646
})
4747
) {

src/http/routes/s3/router.ts

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,18 @@ type Handler<Req extends Schema, Context = unknown> = (
100100
ctx: Context
101101
) => Promise<ResponseType>
102102

103+
export type QuerystringMatch = {
104+
key: string
105+
value: string | undefined
106+
}
107+
108+
export type RouteQuery = Record<string, string | undefined>
109+
103110
type Route<S extends Schema, Context> = {
104111
method: HTTPMethod
105112
type?: string
106113
path: string
107-
querystringMatches: { key: string; value: string }[]
114+
querystringMatches: QuerystringMatch[]
108115
headersMatches: string[]
109116
handler?: Handler<S, Context>
110117
schema: S
@@ -241,7 +248,7 @@ export class Router<Context = unknown, S extends Schema = Schema> {
241248
this.registerRoute('head', url, options, handler as any)
242249
}
243250

244-
parseQueryMatch(query: string) {
251+
parseQueryMatch(query: string): QuerystringMatch {
245252
const [key, value] = query.split('=')
246253
return { key, value }
247254
}
@@ -262,7 +269,7 @@ export class Router<Context = unknown, S extends Schema = Schema> {
262269

263270
matchRoute(
264271
route: Route<S, Context>,
265-
match: { query: Record<string, string>; headers: Record<string, string>; type?: string }
272+
match: { query: RouteQuery; headers: Record<string, string>; type?: string }
266273
) {
267274
const isOfType = match.type ? match.type === route.type : route.type === undefined
268275

@@ -294,31 +301,46 @@ export class Router<Context = unknown, S extends Schema = Schema> {
294301
})
295302
}
296303

297-
protected matchQueryString(
298-
matches: { key: string; value: string }[],
299-
received?: Record<string, string>
300-
) {
301-
const keys = Object.keys(received || {})
302-
if (keys.length === 0 || !received) {
303-
return matches.find((m) => m.key === '*')
304+
protected matchQueryString(matches: QuerystringMatch[], received?: RouteQuery) {
305+
let hasWildcard = false
306+
for (const match of matches) {
307+
if (match.key === '*') {
308+
hasWildcard = true
309+
break
310+
}
304311
}
305312

306-
const foundMatches = matches.every((m) => {
307-
const key = Object.keys(received).find((k) => k === m.key)
308-
return (
309-
(m.key === key && m.value !== undefined && m.value === received[m.key]) ||
310-
(m.key === key && m.value === undefined)
311-
)
312-
})
313+
if (!received) {
314+
return hasWildcard
315+
}
313316

314-
if (foundMatches) {
315-
return true
317+
let hasReceivedQuery = false
318+
for (const key in received) {
319+
if (Object.prototype.hasOwnProperty.call(received, key)) {
320+
hasReceivedQuery = true
321+
break
322+
}
316323
}
317324

318-
if (!foundMatches && matches.find((m) => m.key === '*')) {
319-
return true
325+
if (!hasReceivedQuery) {
326+
return hasWildcard
320327
}
321-
return false
328+
329+
for (const match of matches) {
330+
if (match.key === '*') {
331+
continue
332+
}
333+
334+
if (!Object.prototype.hasOwnProperty.call(received, match.key)) {
335+
return hasWildcard
336+
}
337+
338+
if (match.value !== undefined && match.value !== received[match.key]) {
339+
return hasWildcard
340+
}
341+
}
342+
343+
return true
322344
}
323345
}
324346

src/storage/protocols/s3/s3-handler.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,14 +1319,29 @@ export class S3ProtocolHandler {
13191319
}
13201320

13211321
parseMetadataHeaders(headers: Record<string, unknown>): Record<string, string> | undefined {
1322-
let metadata: Record<string, unknown> | undefined = undefined
1322+
let metadata: Record<string, string> | undefined
1323+
const metadataPrefix = 'x-amz-meta-'
13231324

1324-
Object.keys(headers)
1325-
.filter((key) => key.startsWith('x-amz-meta-'))
1326-
.forEach((key) => {
1327-
if (!metadata) metadata = {}
1328-
metadata[key.replace('x-amz-meta-', '')] = headers[key]
1329-
})
1325+
for (const key in headers) {
1326+
if (!key.startsWith(metadataPrefix)) {
1327+
continue
1328+
}
1329+
1330+
if (!Object.prototype.hasOwnProperty.call(headers, key)) {
1331+
continue
1332+
}
1333+
1334+
const value = headers[key]
1335+
if (typeof value !== 'string') {
1336+
continue
1337+
}
1338+
1339+
if (!metadata) {
1340+
metadata = {}
1341+
}
1342+
1343+
metadata[key.slice(metadataPrefix.length)] = value
1344+
}
13301345

13311346
return metadata
13321347
}

src/test/log-request.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Fastify, { FastifyInstance } from 'fastify'
2+
import { logRequest } from '../http/plugins/log-request'
3+
4+
describe('log-request plugin', () => {
5+
let app: FastifyInstance
6+
7+
beforeEach(async () => {
8+
app = Fastify()
9+
await app.register(logRequest({}))
10+
})
11+
12+
afterEach(async () => {
13+
await app.close()
14+
})
15+
16+
it('derives resources from route params and prefixes them', async () => {
17+
app.get('/bucket/:bucket/object/:name', async (request) => {
18+
return {
19+
resources: request.resources,
20+
}
21+
})
22+
23+
const response = await app.inject({
24+
method: 'GET',
25+
url: '/bucket/demo/object/file.txt',
26+
})
27+
28+
expect(response.statusCode).toBe(200)
29+
expect(response.json()).toEqual({
30+
resources: ['/demo/file.txt'],
31+
})
32+
})
33+
34+
it('prefers configured resources and normalizes missing leading slashes', async () => {
35+
app.get(
36+
'/bucket/:bucket',
37+
{
38+
config: {
39+
resources: () => ['bucket/demo', '/object/demo'],
40+
},
41+
},
42+
async (request) => {
43+
return {
44+
resources: request.resources,
45+
}
46+
}
47+
)
48+
49+
const response = await app.inject({
50+
method: 'GET',
51+
url: '/bucket/demo',
52+
})
53+
54+
expect(response.statusCode).toBe(200)
55+
expect(response.json()).toEqual({
56+
resources: ['/bucket/demo', '/object/demo'],
57+
})
58+
})
59+
})

0 commit comments

Comments
 (0)