Resolving the Issue of Nginx+Tomcat Returning 400 Error with Special Characters in the URL
I encountered an unusual problem where I would receive a 400 error when accessing Tomcat through Nginx if my request URL contained square brackets. The error page did not provide any specific error message, and there were no corresponding error logs in the backend.(English version Translated by GPT-3.5, 返回中文)
Problem Description
Nginx Configuration
1 | location /files/ { |
Tomcat Servlet Configuration
1 | @WebServlet(name = "FileServlet", urlPatterns = "/file/*", loadOnStartup = 1) |
Request without square brackets (No Issue)
1 | http://host/files/folder1/folder2 |
Request with square brackets (Causing the Issue)
1 | http://host/files/folder1/folder2/01-[1]-file.file |
Error Page
The error page did not provide any specific error message. However, if I replace the square brackets with curly brackets, the following error is displayed.
Troubleshooting Process
Research
After extensively searching on Google, I found several suggested solutions. Most of them recommended removing the URI from the proxy_pass directive, while others mentioned:
- A large request header
- Missing proxy_set_header Host $host directive
However, none of these solutions resolved my issue, as my request URL and header were relatively small, and I had already included the proxy_set_header Host $host directive.
Enabling Tomcat Debug Mode
Since there were no error logs and the servlet did not receive the request, I decided to enable Tomcat’s Debug mode myself. Enabling debug mode is simple, just add the following line to /${tomcatRoot}/conf/logging.properties
:
1 | .level = FINE |
I added it at the following location:
1 | ... |
After starting Tomcat with this configuration, numerous debug logs were printed. Then, when I made the request again, I found the following log entry:
1 | ..... |
At this point, the URL I passed into the browser was already URL-encoded. I attempted to encode the portion after “01-[1]-file.file” for a second time, resulting in “01-%255B1%255D-file.file” (URL-encoded twice). I then made the request and observed the following URL:
1 | ... |
I noticed that this URL was exactly the same as the one entered in the browser. Therefore, I concluded that Nginx did not decode the second URL segment, while the first URL segment was decoded correctly by Nginx.
Identifying the Problem
Upon reviewing the proxy_pass documentation on the official Nginx website, Module ngx_http_proxy_module, I found the following relevant information:
When proxy_pass points to a specified URL, the matching portion of the URL is replaced before forwarding the request to the backend web service.
location /name/ {
proxy_pass http://127.0.0.1/remote/;
}This means that a request to http://host/name/aaa will be forwarded to http://127.0.0.1/remote/aaa.
If proxy_pass does not include any URI, the request is forwarded to the backend web service in its original form without any modification or matching:
location /some/path/ {
proxy_pass http://127.0.0.1;
}This means that a request to http://host/some/path/ will be forwarded to http://127.0.0.1/some/path/.
When using variables in the proxy_pass directive:
location /name/ {
proxy_pass http://127.0.0.1$request_uri;
}This means that a request to http://host/name/aaa/bbb will be forwarded to http://127.0.0.1/name/aaa/bbb.
In cases like these, the URI is passed to the server as is.
Solution
According to the documentation, I have two possible solutions (the majority suggests the first one):
1 | location /apps/files/ { |
or
1 | location /apps { |
For the first solution, if I want to access /apps/image, I would have to configure it separately. Therefore, the second solution satisfies my requirements. However, I have yet to find a solution for scenarios like the following:
Unresolved Scenario
1 | http://host/content/folder1/folder2/01[1].file |