Repository: fastify/fastify (JavaScript)

Issue:
Accept-Version is always undefined
### Prerequisites

- [X] I have written a descriptive issue title
- [X] I have searched existing issues to ensure the bug has not already been reported


### Fastify version

3.25.3

### Plugin version

_No response_

### Node.js version

16.13.1

### Operating system

Linux

### Operating system version (i.e. 20.04, 11.3, 10)

Ubuntu 20.04.3 LTS

### Description

I don't want to use [constraints](https://www.fastify.io/docs/latest/Reference/Routes/#constraints).

### Steps to Reproduce

```js
const fastify = require('fastify')
const server = fastify()
server.addHook('onRequest', async (req, reply) => {
  console.log(req.headers)
})
server.listen(8080)
```

```sh
curl \
  --header 'Accept-Version: 1.0' \
  http://localhost:8080
```

```
{
  host: 'localhost:8080',
  'user-agent': 'curl/7.68.0',
  accept: '*/*',
  'accept-version': undefined
}
```

### Expected Behavior

```
{
  host: 'localhost:8080',
  'user-agent': 'curl/7.68.0',
  accept: '*/*',
  'accept-version': '1.0'
}
```


Discussion:
I cannot reproduce your problem. It is probably something is wrong on your end or you do not provide the correct repro.
Here is what I use to test.
```js
const Fastify = require("fastify");
const fastify = Fastify();
fastify.addHook("onRequest", async (request, reply) => {
  console.log(request.headers);
});
fastify.get("/", async function (request) {
  return request.headers;
});
fastify.listen(8080);
```

![image](https://user-images.githubusercontent.com/23028015/149667998-b935779a-ad27-41a4-a62c-7641a2a13247.png)

What? You changed my example and said it can't be reproduced? If you can't get the `Accept-Version` without adding `.get(...)`, then it's already a problem.
Your word can be more polite. And as I am changed the repro, that's why I also posted inside the comment.
It is true that without a actual route added, it will be `undefined`.
If I give an example, you should reproduce that example, isn't it?
Would you like to send a PR to fix this? Thanks!
@mcollina I think it is an intended behavior? 
Not sure why it needs to be removed, but I have tested it do not affect the result **(not include the performance test)** if we remove those line.

https://github.com/fastify/fastify/blob/ae3320de7fd9ba2e6d68349c89f28de643b49f16/fastify.js#L577-L582
I do not have a clue. After the above (rude) messages from the OP, I'm not willing to engage in this issue, nor I think it's worth our time. I'm willing to see a PR that makes this behave like the OP want and then make a call. I do not think that's likely to come from them and I'm ok for the stalebot to close this without further discussion.
No problem, would love to see this issue exist in v4.



Patch:
diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md
index 525fd0cb66b..46b192de879 100644
--- a/docs/Reference/Request.md
+++ b/docs/Reference/Request.md
@@ -57,6 +57,9 @@ This operation will add to the request headers the new values that can be read
 calling `request.headers.bar`. Moreover, you can still access the standard
 request's headers with the `request.raw.headers` property.
 
+> Note: For performance reason on `not found` route, you may see that we will add
+an extra property `Symbol('fastify.RequestAcceptVersion')` on the headers.
+
 ```js
 fastify.post('/:params', options, function (request, reply) {
   console.log(request.body)
diff --git a/fastify.js b/fastify.js
index 78c5d776543..14f787bfd87 100644
--- a/fastify.js
+++ b/fastify.js
@@ -16,6 +16,7 @@ const {
   kLogSerializers,
   kHooks,
   kSchemaController,
+  kRequestAcceptVersion,
   kReplySerializerDefault,
   kContentTypeParser,
   kReply,
@@ -576,6 +577,10 @@ function fastify (options) {
   // req and res are Node.js core objects
   function defaultRoute (req, res) {
     if (req.headers['accept-version'] !== undefined) {
+      // we remove the accept-version header for performance result
+      // because we do not want to go through the constraint checking
+      // the usage of symbol here to prevent any colision on custom header name
+      req.headers[kRequestAcceptVersion] = req.headers['accept-version']
       req.headers['accept-version'] = undefined
     }
     fourOhFour.router.lookup(req, res)
diff --git a/lib/route.js b/lib/route.js
index 9d5be2c3c6a..a3b9913adf6 100644
--- a/lib/route.js
+++ b/lib/route.js
@@ -8,6 +8,7 @@ const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTI
 const { normalizeSchema } = require('./schemas')
 const { parseHeadOnSendHandlers } = require('./headRoute')
 const warning = require('./warnings')
+const { kRequestAcceptVersion } = require('./symbols')
 
 const {
   compileSchemasForValidation,
@@ -344,6 +345,12 @@ function buildRouting (options) {
       }
     }
 
+    // we revert the changes in defaultRoute
+    if (req.headers[kRequestAcceptVersion] !== undefined) {
+      req.headers['accept-version'] = req.headers[kRequestAcceptVersion]
+      req.headers[kRequestAcceptVersion] = undefined
+    }
+
     const id = req.headers[requestIdHeader] || genReqId(req)
 
     const loggerBinding = {
diff --git a/lib/symbols.js b/lib/symbols.js
index f5dd03ff3fe..0e058b1cb25 100644
--- a/lib/symbols.js
+++ b/lib/symbols.js
@@ -21,6 +21,7 @@ const keys = {
   kReply: Symbol('fastify.Reply'),
   kRequest: Symbol('fastify.Request'),
   kRequestPayloadStream: Symbol('fastify.RequestPayloadStream'),
+  kRequestAcceptVersion: Symbol('fastify.RequestAcceptVersion'),
   kCanSetNotFoundHandler: Symbol('fastify.canSetNotFoundHandler'),
   kFourOhFour: Symbol('fastify.404'),
   kFourOhFourLevelInstance: Symbol('fastify.404LogLevelInstance'),


Test patch:
diff --git a/test/versioned-routes.test.js b/test/versioned-routes.test.js
index b3759222ac7..3f92b164bba 100644
--- a/test/versioned-routes.test.js
+++ b/test/versioned-routes.test.js
@@ -296,10 +296,25 @@ test('Shorthand route declaration', t => {
   })
 })
 
-test('The not found handler should not use the Accept-Version header', t => {
-  t.plan(4)
+test('The not found handler should not erase the Accept-Version header', t => {
+  t.plan(13)
   const fastify = Fastify()
 
+  fastify.addHook('onRequest', function (req, reply, done) {
+    t.same(req.headers['accept-version'], '2.x')
+    done()
+  })
+
+  fastify.addHook('preValidation', function (req, reply, done) {
+    t.same(req.headers['accept-version'], '2.x')
+    done()
+  })
+
+  fastify.addHook('preHandler', function (req, reply, done) {
+    t.same(req.headers['accept-version'], '2.x')
+    done()
+  })
+
   fastify.route({
     method: 'GET',
     url: '/',
@@ -310,7 +325,16 @@ test('The not found handler should not use the Accept-Version header', t => {
   })
 
   fastify.setNotFoundHandler(function (req, reply) {
-    t.notOk(req.headers['accept-version'])
+    t.same(req.headers['accept-version'], '2.x')
+    // we check if the symbol is exposed on key or not
+    for (const key in req.headers) {
+      t.same(typeof key, 'string')
+    }
+
+    for (const key of Object.keys(req.headers)) {
+      t.same(typeof key, 'string')
+    }
+
     reply.code(404).send('not found handler')
   })