kwhitley / itty-router

A little router.
MIT License
1.75k stars 78 forks source link

Access to FetchEvent in routes/middleware #86

Closed jamesarosen closed 2 years ago

jamesarosen commented 2 years ago

There are some middleware use-cases that rely on the FetchEvent. For example, here's a Cloudflare middleware that normalizes the User-Agent, caches based on the normalized UA, and instructs downstream caches to vary on User-Agent:

async function varyOnNormalizedUserAgent(request) {
  const url = new URL(request.url)
  const ua = url.searchParams.get('ua') || UA.normalize(request.headers.get('User-Agent'))
  url.searchParams.set('ua', ua)

  const upstreamRequest = new Request(url, request)
  let originResponse = await next(event) // see #85 
  originResponse = new Response(originResponse.body, originResponse) // mutable copy

  const responseForCache = originResponse.clone()
  fetchEvent.waitUntil(caches.default.put(upstreamRequest, responseForCache))

  originResponse.headers.append('Vary', 'User-Agent')
  reutrn originResponse
}

Is the recommended approach to attach the FetchEvent to the Request so upstream handlers have access to it?

kwhitley commented 2 years ago

Before modules syntax, that's exactly what I did. Upstream, I would just embed the event (not pretty, but functional):

addEventListener('fetch', event => {
  event.request.event = event
  event.respondWith(router.handle(event.request))
})

...and with the new(er) CF modules syntax, they natively send request, env params to handlers, which works perfectly fine with itty (you can pass ANY params after the request to the handle function, and those will be passed to all handlers.

const router = Router()

router.get('/', (request, env) => {
  // now have access to the env (where CF bindings like durables, KV, etc now are)
})

export default {
  fetch: router.handle // because the handle signature matches CF, nothing more is needed
}

Hope this helps! Let me know if you have issues with it!