kwhitley / itty-router

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

pass extra args to handlers #12

Closed hunterloftis closed 4 years ago

hunterloftis commented 4 years ago

This allows the user to pass arbitrary arguments to handlers via .handle.

For instance, if you'd like your final * route to use Cloudflare's getAssetsFromKV, it needs access to the original event object, not just the request:

import { Router } from 'itty-router'
import { getAssetFromKV } from "@cloudflare/kv-asset-handler"

const router = Router()

router
    .get('/foo', () => new Response('Foo Index!'))
    .get('/foo/:id', ({ params }) => new Response(`Details for item ${params.id}.`))
    .get('*', serveStaticAssets)

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

async function serveStaticAssets(req, event) {
    try { return await getAssetFromKV(event) }
    catch (err) { }
}
kwhitley commented 4 years ago

Thanks @hunterloftis!

1) I think this makes total sense but let me check first to see how many minified characters this adds (since that's part of the game here ;) 2) I've had to address the same issues of course, as I often need access to event.waitUntil to finish processing async tasks after the response. Here's the less-elegant way I've addressed it in the meantime:

const router = Router()

router.get('/foo', async ({ event }) => {
  event.waitUntil(await 3)
  return new Response('Can access the event by embedding it in the request...'))
})

addEventListener('fetch', event => {
  // I abstracted this functionality into a helper method of course...
  event.request.event = event
  event.respondWith(router.handle(event.request))
})
kwhitley commented 4 years ago

Sidenote, this would imply you're using a Worker to serve both your API + static site from a single endpoint/domain, that correct? How's that working out for you? I eventually ditched this concept due to caching issues, and serve my API from a subdomain to avoid the conflicts :(

hunterloftis commented 4 years ago

@kwhitley it only adds 11 bytes:

this branch:

-rwxr-xr-x 1 root root   461 Oct 22 20:50 itty-router.min.js

v1:

-rwxr-xr-x 1 root root  450 Oct 22 20:52 itty-router.min.js

I'm trying out Cloudflare's Durable Objects, so my "API" is a WebSocket connection to an Object. I'm combining routing with static files for top-level site navigation, eg:

const router = Router()
    .get('/token/google', createUserToken, followReturn())
    .get('/sign-out', destroyUserToken, followReturn('/'))
    .post('/table', getUser, requireUser, createTable)
    .get('/table/:id', getUser, requireUser, render(table))
    .upgrade('/table/:id', getUser, requireUser, connectSocket)
    .get('*', serveStaticAssets)

Perhaps I'll run into the same caching issues, since I'm just beginning with cloudflare based on the new durable objects feature. If you have any tips from your experience, I'd love to hear them!

kwhitley commented 4 years ago

This is an example of a perfect PR... :D, simple explanation of problem it solves, tests added, and size increase measured! Thanks man!

Also, regarding your setup - that sounds pretty sick, so please keep me posted on your progress! I'm writing a Dropbox/Cloud-storage backed Instagram-clone (it does more and less than Insta, so that's a tough comparison) at https://slick.af using entirely Cloudflare Workers with KV storage (both for static site content as well as all data), but I've definitely run into issues where multiple requests are seeing different versions of the KV data as things quickly change. Durable Objects sound like they'd be able to perhaps help with this, assuming performance doesn't take too much of a hit (KV lookups are fast, fast, fast). A couple questions:

1) How easy/difficult is it to update the class once deployed and some objects are already based on a pervious version of the same class? For example, if I have User objects live in the wild, and add an "age" attribute/methods, do those existing objects get updated? 2) How fast are reads on this?

hunterloftis commented 4 years ago

Thanks for being so responsive & open to the addition!

I'd love to try slick.af (really: have my wife, an avid instagram user, show me what to do!). Is there a way to get a "collection code?"

For your questions:

  1. That's a great question that I'll have to guess at the answer to, based on my inexperience so far. The design of Durable Objects separates the in-memory state (which, for a particular object instance, is a "global" variable in the best sense of the word... as only one is allowed to exist in the world) from the durable "storage" (I assume, an underlying database somewhere). So when you deploy a new version, my guess is that any stubs that reference an old version will be invalidated, and when new stubs are instantiated they'll use the new model. The new Class definition would then be responsible for any manipulations to the underlying storage that you'd want to make to update it (for instance, adding an "age" attribute with a default value if it's missing).
  2. In my tests, simple reads take about 130ms. However, I've only tested string-derived IDs, which require global look-ups, and the docs suggest using system-generated IDs instead for much faster access.

I don't want to put out misinformation, though, so maybe someone with deeper knowledge like @rita3ko could correct any inaccuracies here.

kwhitley commented 4 years ago

Of course you can [try it]! I'm building Slick as a 100% free service (thanks in part to the stupid-low costs of Cloudflare Workers), with no ads, spam, sketchiness, info-selling, etc. Just hit Sign Up at the top of the main page, and it'll get you started.

A couple caveats though:

1) I'm working on solving a duplication issue (shown if you look at the top of this page): https://slick.af/oay8XxDs. When you dump a bunch of images into your folder, the webhook fires as each image is uploaded to Dropbox, creating multiple workers that are conflicting on what they "see" in KV. Durable Objects may be a possible solution to this, or I create a defer pass where each webhook "waits" for like 20s to see if another firing has replaced it (so only one "sync" command runs when the updates stop flowing in).

2) The functionality is extremely limited in this alpha state while I test with early users... for instance, you'll notice images show "Lorem Ipsum" text for now if no story is present... obviously this will be disabled/toggle-able in the released version, but in the meantime gives me an idea of how text can flow around the images, etc.

3) Editing is not streamlined either, for the same reason as in #1 - updates create an update queue, then fire off all the updates, but the index rebuilding step creates a bunch of parallel workers, each seeing a different state of the KV - the last one to finish "wins", which isn't always the accurate version.

kwhitley commented 4 years ago

Btw just noticed your use of "upgrade" on the router (not to mention the use of middleware) - stoked to see someone leveraging the flexibility of itty!!!