I have a server implemented in Dart and want to parse POST request body. I am using Postman / curl (and Flutter app) to test the server. When I access server using localhost
, the POST request body is processed correctly and server works as expected. However, when I try to access it using a domain name and Nginx handling as a reverse proxy, the server crashes trying to process the request body.
Using the same nginx configuration but NodeJS as a server (in some other project) with body-parser
package, the NodeJS server is able to read body.
The curl
version of the request:
curl --location --request POST 'https://insanichess.com/api/auth/register'
--header 'Content-Type: application/json; charset=utf-8'
--data-raw '{
"email": "[email protected]",
"password": "test_pwd"
}'
Nginx conf file (generated with Certbot):
server {
server_name insanichess.com www.insanichess.com;
location / {
proxy_pass http://localhost:4040;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
listen [::]:443 ssl; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/insanichess.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/insanichess.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}server {
if ($host = www.insanichess.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = insanichess.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name insanichess.com www.insanichess.com;
return 404; # managed by Certbot
}
Important parts of Dart code:
// insanichess_server.dart
/// Starts the server.
Future<void> start() async {
final InternetAddress address = InternetAddress.loopbackIPv4;
final int port =
int.parse(Platform.environment['INSANICHESS_PORT'] ?? '4040');
final HttpServer server = await HttpServer.bind(address, port);
_logger.info('InsanichessServer.create', 'Server listening on port $port');
await _handleRequests(onServer: server);
}
/// Passes every request [onServer] to [_router].
Future<void> _handleRequests({required HttpServer onServer}) async {
await for (final HttpRequest request in onServer) {
await _router.handle(request);
}
}
// auth_controller.dart
Future<void> handleRegistration(HttpRequest request) async {
if (request.method != 'POST' ||
request.contentLength <= 0 ||
request.headers.contentType?.value != ContentType.json.value) {
return respondWithBadRequest(request);
}
final String content = await utf8.decodeStream(request);
final Map<String, dynamic> body = jsonDecode(content);
//...
}
The server crashes with
Unhandled exception:
FormatException: Unexpected end of input (at character 1)
^
#0 _ChunkedJsonParser.fail (dart:convert-patch/convert_patch.dart:1405:5)
#1 _ChunkedJsonParser.close (dart:convert-patch/convert_patch.dart:523:7)
#2 _parseJson (dart:convert-patch/convert_patch.dart:41:10)
#3 JsonDecoder.convert (dart:convert/json.dart:506:36)
#4 JsonCodec.decode (dart:convert/json.dart:157:41)
#5 jsonDecode (dart:convert/json.dart:96:10)
#6 AuthController.handleRegistration (package:insanichess_server/src/controller/api/auth/auth.dart:79:39)
<asynchronous suspension>
#7 ApiRouter.handle (package:insanichess_server/src/router/api/api.dart:29:16)
<asynchronous suspension>
#8 ICRouter.handle (package:insanichess_server/src/router/ic_router.dart:27:16)
<asynchronous suspension>
#9 InsanichessServer._handleRequests (package:insanichess_server/src/insanichess_server.dart:38:7)
<asynchronous suspension>
#10 InsanichessServer.start (package:insanichess_server/src/insanichess_server.dart:32:5)
<asynchronous suspension>
#11 main (file:///home/campovski/insanichess/server/bin/main.dart:17:3)
<asynchronous suspension>
I have tried setting CORS and X-** headers in nginx as suggested in similar StackOverflow answers, however the error was always the same. x-www-form-urlencoded
version did not help either.
Printing content.length
before jsonDecode
gives 0, however printing Content-Length
header value in handler gives correct value (I think it is 63 or something).
This are all the headers that handler receives in request:
user-agent: curl/7.64.1
content-type: application/json; charset=utf-8
connection: upgrade
accept: */*
content-length: 63
host: insanichess.com
I assume the issue is in proxying with nginx, since querying directly on localhost works, however it seems strange that almost identical setup worked with NodeJS as backend. The full source code of the project is available on GitHub in case you want to take a closer look.
2
Answers
For some reason deleting everything from
location /
block in nginx config except forproxy_pass
helped. This config is now ok:It seems like a problem with
final Map<String, dynamic> body = jsonDecode(content);
Or with redirect status on NGINX. You should use 307 response code. Source