I’ve setup Nginx with reverse proxy to my django backend api. The requests from Postman work perfectly fine but on Flutter I get a HTTP 400 157 from the nginx server while performing a GET request.
Upon inspecting the log , I see the following :
client sent invalid header line: "x3a..." while reading client request headers
Here is my nginx.conf.
worker_processes auto;
events {
worker_connections 1024;
}
http {
log_format custom '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" $request_time';
#Note that these are defined outside of the server block altho they don't necessarily need to be
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=my_zone:10m inactive=60m;
proxy_cache_key "$scheme$request_method$host$request_uri";
default_type application/octet-stream;
include /etc/nginx/mime.types;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 4096;
upstream my-django-rest-api {
server 172.16.16.1:8080; # The internal Django API service
}
server {
listen 1652;
# Access & Error Logs
access_log /var/log/nginx/access.log custom;
error_log /var/log/nginx/error.log;
location / {
proxy_pass http://my-django-rest-api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache my_zone;
add_header X-Proxy-Cache $upstream_cache_status;
proxy_buffering on;
proxy_cache_valid 200 302 60m;
proxy_cache_valid 404 1m;
# WebSocket support (nginx 1.4)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
What Do I need to do on the Nginx end before I start troubleshooting
it from the Frontend ?What exactly is "x3a…" which is termed as Invalid in header line?
Further to the above, I’m adding the headers that are sent for each Postman and flutter for the same GET request:
Postman Headers for a GET request
Key Value
Host <calculated when request is sent>
User-Agent PostmanRuntime/7.42
Accept */*
Accept Encoding gzip,deflate,br
Connection keep-alive
Authorization Token f384996c63b4****************
Flutter side functions while request is sent
Future<HttpieResponse> get(url,
{Map<String, String>? headers,
Map<String, dynamic>? queryParameters,
bool? appendLanguageHeader,
bool? appendAuthorizationToken}) async {
var finalHeaders = _getHeadersWithConfig(
headers: headers,
appendLanguageHeader: appendLanguageHeader,
appendAuthorizationToken: appendAuthorizationToken);
if (queryParameters != null && queryParameters.keys.isNotEmpty) {
url = url + _makeQueryString(queryParameters);
}
Response? response;
try {
response = await client.get(Uri.parse(url), headers: finalHeaders);
} catch (error) {
_handleRequestError(error);
}
if (response != null) {
return HttpieResponse(response);
}
throw ();
}
Map<String, String> _getHeadersWithConfig(
{Map<String, String>? headers = const {},
bool? appendLanguageHeader,
bool? appendAuthorizationToken}) {
headers = headers ?? {};
Map<String, String> finalHeaders = Map.from(headers);
appendLanguageHeader = appendLanguageHeader ?? true;
appendAuthorizationToken = appendAuthorizationToken ?? false;
if (appendLanguageHeader) finalHeaders['Accept-Language'] = _getLanguage();
if (appendAuthorizationToken && authorizationToken != null) {
finalHeaders['Authorization'] = 'Token $authorizationToken';
}
if (magicHeaderName != null && magicHeaderValue != null) {
finalHeaders[magicHeaderName!] = magicHeaderValue!;
}
return finalHeaders;
}
String _getLanguage() {
return _localizationService.getLocale().languageCode.toLowerCase();
}
.env.json
{
"API_URL": "http://24.XX.XX.XX:70/",
"MAGIC_HEADER_NAME": "",
"MAGIC_HEADER_VALUE": "",
}
2
Answers
x3a is the Ascii code for a colon. My suspicion is that the headers array somehow included a semicolon in the header key, which isn’t allowed. You can only put it in the value.
Presumably the empty
magicHeaderName
andmagicHeaderValue
result in the following line being added to the request headers:Which is not allowed, specifically RFC 9110 defines that the HTTP header name must be non-empty:
The
1*
means "at least one", so an empty name is not allowed.