I have a very weird problem where about %0,5 of the POST requests to my Spring Boot backend fails with HttpMessageNotReadableException. I will be describing the issue step by step and my findings and sample code.
Some information about the technologies being used:
Client side:
Requests are posted via UnityWebRequest class from both iOS and Android
Payload size is around 1-2 Kb
Payloads are AES GCM encrypted then Base64 encoded strings (this is a product requirement)
No chunked transfer
Server side:
Spring Boot app with embedded Tomcat server. No custom configurations
Deployed via Kubernetes on AWS Classic Load Balancer, again no custom configurations, all default
When I inspect the headers of a failing request I saw that all headers are present as expected, Content-Length is greater than 0 but controllers are not invoked, HttpMessageNotReadableException is thrown after the timeout period (60seconds default). Content-Length is a number divisible by 4.
What I think happening is server thinks the client will send more data and waits until the timeout then closes the connection and throws the exception.
Unfortunately I can't ever reproduce this and I have no idea how it is happening.
Related code sections:
Client Encryption -> Base64
public static string Encrypt(string rawData)
{
if (string.IsNullOrEmpty(rawData))
return string.Empty;
var plainText = Encoding.UTF8.GetBytes(rawData);
var cipherText = InternalEncrypt(plainText, Encoding.UTF8.GetBytes(Key));
return cipherText == null ? string.Empty : Convert.ToBase64String(cipherText);
}
private static byte[] InternalEncrypt(byte[] rawMessage, byte[] key, byte[] nonSecretPayload = null)
{
//User Error Checks
if (key == null || key.Length != KeyBitSize / 8)
{
Log.Instance.Error(String.Format("Key needs to be {0} bit!", KeyBitSize));
return null;
}
if (rawMessage == null || rawMessage.Length == 0)
{
Log.Instance.Error(String.Format("Invalid message!", KeyBitSize));
return null;
}
//Non-secret Payload Optional
nonSecretPayload = nonSecretPayload ?? new byte[] { };
//Using random nonce large enough not to repeat
System.Random rnd = new System.Random();
var nonce = new byte[NonceBitSize / 8];
rnd.NextBytes(nonce);
var cipher = new GcmBlockCipher(new AesFastEngine());
var parameters = new AeadParameters(new KeyParameter(key), MacBitSize, nonce, nonSecretPayload);
cipher.Init(true, parameters);
//Generate Cipher Text With Auth Tag
var cipherText = new byte[cipher.GetOutputSize(rawMessage.Length)];
var len = cipher.ProcessBytes(rawMessage, 0, rawMessage.Length, cipherText, 0);
cipher.DoFinal(cipherText, len);
//Assemble Message
using (var combinedStream = new MemoryStream())
{
using (var binaryWriter = new BinaryWriter(combinedStream))
{
//Prepend Authenticated Payload
binaryWriter.Write(nonSecretPayload);
//Prepend Nonce
binaryWriter.Write(nonce);
//Write Cipher Text
binaryWriter.Write(cipherText);
}
return combinedStream.ToArray();
}
}
Sending the request:
UnityWebRequest request = method == HttpMethod.Get
? UnityWebRequest.Get(url)
: new UnityWebRequest(url);
if (method == HttpMethod.Post)
{
request.method = "POST";
request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(postData));
request.downloadHandler = new DownloadHandlerBuffer();
}
request.SetRequestHeader("Content-Type", "text/plain");
request.timeout = 5;
yield return request.SendWebRequest();Spring controller definition:
@PostMapping(value = "/", consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<String> post(@RequestBody String body)Like I said previously, about %0,5 to %1 of the requests fail, not even reaching controller and throws HttpMessageNotReadableException.
Any idea what I should look at to further debug this issue?