lukeed / trouter

:fish: A fast, small-but-mighty, familiar fish...errr, router*
MIT License
634 stars 23 forks source link

params need to be decoded? #9

Closed ianstormtaylor closed 5 years ago

ianstormtaylor commented 5 years ago

If a param is sent with a , in it for instance, it will show up as %2C after being added to the params object. I think they need to be URL decoded.

lukeed commented 5 years ago

Hey, for sure! But that's not Trouter's responsibility.

This component is meant for storing routes with their handlers, and then returning a digest of that route when asked to find match(es). For that, it only cares about the HTTP method & adequate path "stuffs" to satisfy the pattern definition. The contents of the pattern are irrelevant (to Trouter).

That means that URL decoding must happen before a match is requested. Again, since Trouter doesn't necessarily care about the URL details, it'll work with & parse whatever you send it.

Here's how I'm using it in (the next version of) Polka: https://github.com/lukeed/polka/blob/next/packages/polka/index.js#L53-L54

And here's similar setup with Polkadot, which is basically a micro alternative: https://github.com/lukeed/polkadot/blob/master/examples/with-router/router.js

Polkadot also handles decoding internally, though polkadot@next has a better URL parser in it.

ianstormtaylor commented 5 years ago

So anyone who wants to use the library has to then post-process the params to make a call to decodeURIComponent themselves? Or at what point in those examples is polkadot decoding the path? I'm not finding it.

If you have a route like:

/items/:id

And you make a request to:

/items/0dk2,3039

Then the pathname of the URL will be:

/items/0dk2%2C3039

The params object will end up as:

{
  id: '0dk2%2C3039',
}

Which isn't really what you expect when getting the params back from the router? I'd have expected that it would at some point be decoded. Otherwise it seems very error prone.

lukeed commented 5 years ago

Well, no, you'd preprocess the URL before calling trouter.find. Trouter doesn't have a listen() method on it, so it's not designed to be the only component of your stack. You still have to pass HTTP requests to it, so while in that process, you decode the URL. Trouter will then send back params that are decoded segments of the fully decoded URL you sent it.

const Trouter = require('trouter');
const { createServer } = require('http');

const router = new Trouter();
router.get('/items/:id', (req, res) => {
    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
    res.end(req.params.id);
});

createServer((req, res) => {
    // Here, call "@polka/url" OR manually decode
    // For demo only: manually decode here to skip dependency magic
    req.url = decodeURIComponent(req.url);

    // 99% routing only cares about pathnames, so trim it
    const idx = req.url.indexOf('?');
    req.path = idx === -1 ? req.url : req.url.substring(0, idx);

    // Find the handler(s) and params. (We are decoded)
    const { params, handlers } = router.find(req.method, req.path);
    req.params = params;

    // Demo shortcut – doesn't matter from here on
    if (handlers.length > 0) {
        handlers[0](req, res);
    } else {
        res.statusCode = 404;
        res.end('Not found');
    }
}).listen(8080);
curl http://localhost:8080/items/f%C3%B8%C3%B8%C3%9Far
# => føøßar%

curl http://localhost:8080/items/0dk2%2C3039
# => 0dk2,3039%

curl http://localhost:8080/items/0dk2,3039
# => 0dk2,3039%

Also, Polkadot does its decoding here via the @polka/url component.

These are all modular pieces of a full HTTP stack.

ianstormtaylor commented 5 years ago

Ahh, I missed that branch in @polka/url. Thank you, that explains it.

lukeed commented 5 years ago

Ah yeah, sorry! Most of the goodies are in next right now, including the correct way of decoding.

Teh whole Polka-ecosystem is moving towards a 1.0, promoting all this stuff into stable ASAP