Worked example - Debugging HTTP 431 (request header fields too large) in Angular app
The error
I noticed that all my API requests started failing at some point. They all gave the same error in chrome:
GET {localhost url} 431 (Request Header Fields Too Large)
Finding the bug
I had a strong suspicion that this meant the bearer token for my user was too large, because this has been a problem in the past (the application that I'm working on has unusually complex permission requirements, and a user's permissions are encoded in their bearer token). However, I hadn't expected any of my changes to affect the bearer token, so I needed to investigate what was happening.
This is the process I ran through:
- I looked at the chrome developer tools, and from the Application tab I got the bearer token which my application generated. I compared it to the one generated if I reverted my changes. There was no difference between them.
- At this point I thought I must have made a mistake by using the Application tab. Perhaps the token had changed since making the request, and chrome was showing me the latest version of token. I repeated my comparison, but this time used the actual request headers from the chrome Network tab. There was still no difference between the tokens.
- At this point I reread the error, and noticed it's not specific to bearer tokens. The problem could be in any of the headers, so I compared the full set of headers. This time I did find a difference with/without my changes - the port number which was being used to access the API. With my changes (i.e. the broken version of the application), the API was accessed on the same port as the angular app which provides the UI for my application. This is unexpected, as the API is running on a different port.
- I looked at the pending changes I had, and guessed that
app.module.ts
was the file most likely to affect things. I tested this guess by reverting changes to that file only. This fixed the problem. Reinstating my changes caused the problem again. - I still didn't know why my changes in
app.module.ts
were breaking this functionality, so I broke the problem down by commenting out individual changes inapp.module.ts
. I found that the key difference was that I had added a newAPP_INITIALIZER
, and commenting it out resolved the problem. - I wanted to be still more specific, so I reinstated the
APP_INITIALIZER
but passed an empty function intouseFactory
. The API calls still worked, so I now knew that the problem was with the code in my factory function.
Understanding the bug
Why does the code in my factory function change which port is used for API access?
- My initial guess was that the problem is that the app initialiser is making an API call before some other code runs which configures which port the API is accessed on. I decided to look into where that is configured.
- This is in the
AngularConfig
inappsettings.json
, which is used by the constructor of theAppComponent
. - I used console logging to confirm that the app initialiser is run before the app component is constructed.
Resolution - a change of approach
Well, this is not the ending I'd imagined to this blog post. I had thought it might end with something I learned about how angular works, and how to achieve my initial goal. Instead, I changed my implementation to avoid having the problem.
While I don't fully understand the root cause, a key factor was that the APP_INITIALIZER
I had written made an API request. This seems to have affected what port was used for all API access, presumably because this first request took place before the API had been configured properly.
On reflection, I realised I don't actually need the settings from the API until handling an unauthorised request. I had thought I would load the settings when the app loads and have them ready for when I need them. Instead, I changed approach so I will load them the first time they're needed.
Lessons to learn
Was this a failure? No - it's an opportunity to learn something new, or to reinforce a lesson which has already been learnt. Here are a few lessons I thought of:
- Sometimes changing tack is part of the territory with programming. You try something, it causes a problem, and you decide that an alternative approach would be better than trying to resolve the problem. There's no shame in this. It's part of the job.
- I was very glad that I tested my assumptions. I had strong instincts where the problem lay, but they turned out to be incorrect. Instincts are great as a starting point, as they draw on all your experience to get you started, but you can waste a lot of time by failing to question your instinctive assumptions.
- Being methodical while trying to locate the cause of the bug paid huge dividends. Given the cause of the bug wasn't what I'd expected, I could have wasted loads of time trying to guess what the problem was. Being methodical in commenting out changes I'd made then testing whether or not the problem still occurred meant I relatively quickly isolated the relevant change, and was able to start thinking about the solution.
Have you had similar experiences? What lessons have you drawn from them?