Nitro's cached handler renders from a header-less copy of the request, so under swr route rules useJournalApi, /rss.xml and the sitemap source forwarded only the Nuxt peer and every cache miss shared one Journal API rate-limit bucket (deferred in Phase 16). A Nitro plugin records x-forwarded-for plus the peer in event.context.journalForwardedFor on the request hook; the three callers forward it. Same key as the host app's workaround, which keeps working. The acme SSR smoke's x-forwarded-for check on the now route-cached playground is the test.
37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
// /rss.xml: the Journal API's RSS 2.0 feed, served from the site origin.
|
|
// The visitor's x-forwarded-for is forwarded (plus the peer address) so the
|
|
// API's rate limiter keeps one bucket per visitor. 404 when journal.rss is
|
|
// false, 502 when the API does not answer.
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const config = useRuntimeConfig(event)
|
|
if ((config.public.journal as { rss?: boolean }).rss === false) {
|
|
throw createError({ statusCode: 404, statusMessage: 'Not found' })
|
|
}
|
|
const base = (config.journal as { apiBase: string }).apiBase
|
|
|
|
// Recorded by server/plugins/journal-client-address.ts before any route
|
|
// cache hides the request headers.
|
|
const forwarded =
|
|
(event.context.journalForwardedFor as string | undefined) ||
|
|
[getRequestHeader(event, 'x-forwarded-for'), getRequestIP(event)]
|
|
.filter((v): v is string => typeof v === 'string' && v !== '')
|
|
.join(', ')
|
|
|
|
let body: string
|
|
try {
|
|
body = await $fetch<string>('/rss', {
|
|
baseURL: base,
|
|
responseType: 'text',
|
|
headers: forwarded ? { 'x-forwarded-for': forwarded } : {},
|
|
})
|
|
} catch {
|
|
setResponseStatus(event, 502)
|
|
setResponseHeader(event, 'content-type', 'text/plain; charset=utf-8')
|
|
return 'The feed is unavailable.'
|
|
}
|
|
|
|
setResponseHeader(event, 'content-type', 'application/rss+xml; charset=utf-8')
|
|
return body
|
|
})
|