Repository: fastify/fastify (JavaScript)

Issue:
Case insensitive routes?
For a project I'm working on using Fastify, there are a collection of routing keywords.

- content
- shop
- product
...

Due to URLs that are out in the wild these could be also be requested in sentence case.

With this simple example:

```JavaScript
const fastify = require('fastify')()

// Declare a route
fastify.get('/hello', function (request, reply) {
  reply.send({ hello: 'world' })
})

fastify.listen(3000, (err, address) => {
  if (err) throw err
  fastify.log.info(`server listening on ${address}`)
})
```

I noticed the routes are case sensitive.

```Bash
$ curl -I http://localhost:3000/hello
# HTTP/1.1 200 OK
$ curl -I http://localhost:3000/Hello
# HTTP/1.1 404 Not Found
```

I was unaware of this before, I looked at [find-my-way](https://github.com/delvedor/find-my-way) but couldn't see any options, I assume this is for performance reasons? I understand I could make the routes regular expressions but I know this is expensive.

I just wanted to double check if I was missing something simple, this is a must have for me and I would rather not build a regex if possible.

Thanks,
Robin



Discussion:
This has not been implemented because we did not think about this feature. How are you dealing with it right now?

Would you need this for all routes, or just a few?

I think the best approach would be to add this feature to https://github.com/delvedor/find-my-way in the form of an option that makes all paths lowercase, both during insert and lookup. Ideally this should not have a performance impact apart from an additional `toLowerCase()` when it's added. 
cc @delvedor 
So I actually just discovered this by chance in testing, so currently not handling it but thought about creating regex route as a workaround.

It would be a case of maybe 10 to 20 routes, however each of those routes has a parameter in them before the routing word.

Really this should be handled at the proxy. Only non-conforming servers are case insensitive.
Rightly or wrongly, this is an option in express: https://expressjs.com/en/api.html#express.router

It is wrong:

> When a URI uses components of the generic syntax, the component
   syntax equivalence rules always apply; namely, that the scheme and
   host are case-insensitive and therefore should be normalized to
   lowercase.  For example, the URI <HTTP://www.EXAMPLE.com/> is
   equivalent to <http://www.example.com/>.  The other generic syntax
   components are assumed to be case-sensitive unless specifically
   defined otherwise by the scheme

https://tools.ietf.org/html/rfc3986#section-6.2.2.1
@jsumners I think we should support this, but keep it disabled by default. Case insensitive routes are supported (and enabled by default) in Express: a large of developers expects this features to be available. 

Here is a PR: https://github.com/delvedor/find-my-way/pull/84
Okay. 



Patch:
diff --git a/docs/Factory.md b/docs/Factory.md
index aefcb931429..ad005a2c8fe 100644
--- a/docs/Factory.md
+++ b/docs/Factory.md
@@ -129,3 +129,21 @@ fastify.listen(3000)
 ```
 
 Internally Fastify uses the API of Node core http server, so if you are using a custom server you must be sure to have the same API exposed. If not, you can enhance the sever instance inside the `serverFactory` function before the `return` statement.
+
+<a name="factory-case-sensitive"></a>
+### `caseSensitive`
+
+By default, value equal to `true`, routes are registered as case sensitive. That is, `/foo` is not equivalent to `/Foo`. When set to `false`, routes are registered in a fashion such that `/foo` is equivalent to `/Foo` which is equivalent to `/FOO`.
+
+Setting `caseSensitivy` to `false` will also result in
+all params (and all value matched by regexps) to be lowercased as well.
+
+```js
+fastify.get('/user/:username', (request, reply) => {
+  // Given the URL: /user/NodeJS
+  console.log(request.params.username) // -> 'nodejs'
+})
+```
+
+Please note this setting this option to `false` goes against
+[RFC3986](https://tools.ietf.org/html/rfc3986#section-6.2.2.1).
diff --git a/fastify.js b/fastify.js
index 5d5f7e8f500..2dd7f0966db 100644
--- a/fastify.js
+++ b/fastify.js
@@ -66,7 +66,8 @@ function build (options) {
   const router = FindMyWay({
     defaultRoute: defaultRoute,
     ignoreTrailingSlash: options.ignoreTrailingSlash,
-    maxParamLength: options.maxParamLength
+    maxParamLength: options.maxParamLength,
+    caseSensitive: options.caseSensitive
   })
 
   fastify.printRoutes = router.prettyPrint.bind(router)
diff --git a/package.json b/package.json
index a44e40e47a4..6c3f107547f 100644
--- a/package.json
+++ b/package.json
@@ -65,20 +65,20 @@
     "node": ">=6"
   },
   "devDependencies": {
-    "@types/node": "^9.4.6",
-    "JSONStream": "^1.3.2",
+    "@types/node": "^10.5.1",
+    "JSONStream": "^1.3.3",
     "autocannon": "^2.3.0",
     "branch-comparer": "^0.4.0",
-    "concurrently": "^3.5.1",
+    "concurrently": "^3.6.0",
     "cors": "^2.8.4",
-    "coveralls": "^3.0.0",
+    "coveralls": "^3.0.2",
     "dns-prefetch-control": "^0.1.0",
     "eslint-plugin-typescript": "^0.12.0",
     "fast-json-body": "^1.1.0",
     "fastify-plugin": "^1.1.3",
     "frameguard": "^3.0.0",
     "h2url": "^0.1.2",
-    "helmet": "^3.12.0",
+    "helmet": "^3.12.1",
     "hide-powered-by": "^1.0.0",
     "hsts": "^2.1.0",
     "http-errors": "^1.6.3",
@@ -96,23 +96,23 @@
     "standard": "^11.0.1",
     "tap": "^12.0.0",
     "then-sleep": "^1.0.1",
-    "typescript": "^2.8.3",
+    "typescript": "^2.9.2",
     "typescript-eslint-parser": "^16.0.0",
     "x-xss-protection": "^1.1.0"
   },
   "dependencies": {
-    "@types/pino": "^4.7.1",
+    "@types/pino": "^4.16.0",
     "abstract-logging": "^1.0.0",
-    "ajv": "^6.4.0",
-    "avvio": "^5.4.3",
+    "ajv": "^6.5.2",
+    "avvio": "^5.6.0",
     "end-of-stream": "^1.4.1",
-    "fast-json-stringify": "^1.5.2",
-    "find-my-way": "^1.14.0",
-    "flatstr": "^1.0.5",
+    "fast-json-stringify": "^1.5.4",
+    "find-my-way": "^1.15.0",
+    "flatstr": "^1.0.8",
     "light-my-request": "^2.0.2",
     "middie": "^3.1.0",
-    "pino": "^4.16.1",
-    "tiny-lru": "^1.5.2"
+    "pino": "^4.17.3",
+    "tiny-lru": "^1.6.1"
   },
   "greenkeeper": {
     "ignore": [


Test patch:
diff --git a/test/case-insensitive.test.js b/test/case-insensitive.test.js
new file mode 100644
index 00000000000..83b5b1858ad
--- /dev/null
+++ b/test/case-insensitive.test.js
@@ -0,0 +1,62 @@
+'use strict'
+
+const t = require('tap')
+const test = t.test
+const Fastify = require('..')
+const sget = require('simple-get').concat
+
+test('case insensitive', t => {
+  t.plan(4)
+
+  const fastify = Fastify({
+    caseSensitive: false
+  })
+  t.tearDown(fastify.close.bind(fastify))
+
+  fastify.get('/foo', (req, reply) => {
+    reply.send({ hello: 'world' })
+  })
+
+  fastify.listen(0, err => {
+    t.error(err)
+
+    sget({
+      method: 'GET',
+      url: 'http://localhost:' + fastify.server.address().port + '/FOO'
+    }, (err, response, body) => {
+      t.error(err)
+      t.strictEqual(response.statusCode, 200)
+      t.deepEqual(JSON.parse(body), {
+        hello: 'world'
+      })
+    })
+  })
+})
+
+test('case insensitive inject', t => {
+  t.plan(4)
+
+  const fastify = Fastify({
+    caseSensitive: false
+  })
+  t.tearDown(fastify.close.bind(fastify))
+
+  fastify.get('/foo', (req, reply) => {
+    reply.send({ hello: 'world' })
+  })
+
+  fastify.listen(0, err => {
+    t.error(err)
+
+    fastify.inject({
+      method: 'GET',
+      url: 'http://localhost:' + fastify.server.address().port + '/FOO'
+    }, (err, response) => {
+      t.error(err)
+      t.strictEqual(response.statusCode, 200)
+      t.deepEqual(JSON.parse(response.payload), {
+        hello: 'world'
+      })
+    })
+  })
+})