For quite some time, I have put off working on the core servlet stack. Currently, the specification has reached version 6. That is something I was not aware of. The changes to race conditions in Servlet Specification 6 were the most significant feature I was looking forward to. Boot/Spring hides the underlying servlet code by masking the underlying servlet architecture in order to hide the underlying servlet code. In this blog, we explore servlet context information. A large file is being uploaded to Azure Blob storage in the scenario I am simulating. With the Async context set to true, I am using asynchronous operations. With the old servlet specs, I used to constantly run into race conditions, which required the Rentrant lock API to be used.
Async requests that were having issues have been fixed in Servlet 6.0.
Initially, I was tipped off to Azure uploads by a bug in the documentation regarding isAsyncStarted. Here is a sample code base for async file uploads that addresses the revised specification.
In this sample code am using the Azure SAS tokens for uploads.
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter printWriter = resp.getWriter();
final String path = "/tmp";
final Part filePart = req.getPart("file");
final String fileName = getFileName(filePart);
AsyncContext asyncContext = req.startAsync();
asyncContext.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent asyncEvent) {
System.out.println("AsyncServlet on complete method triggered");
}
@Override
public void onTimeout(AsyncEvent asyncEvent) {
System.out.println("AsyncServlet Timeout method triggered");
}
@Override
public void onError(AsyncEvent asyncEvent) {
System.out.println("AsyncServlet Error method triggered");
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) {
System.out.println("Start Async method triggered");
}
});
The Async code stack kicks off the upload process, and the advanced governance method on complete fires when the file is uploaded without any issues. BURP will be used later in the model to simulate a load.
The utility method to upload the file is
BlobServiceClient blobServiceClient = new BlobServiceClientBuilder()
.endpoint("https://sathishsample.blob.core.windows.net/")
.sasToken(SAS_TOKEN)
.buildClient();
BlobContainerClient blobContainerClient = blobServiceClient.getBlobContainerClient("someblobazure");
System.out.println("\nListing blobs...");
for (BlobItem blobItem : blobContainerClient.listBlobs()) {
System.out.println("\t" + blobItem.getName());
}
BlobClient blobClient = blobContainerClient.getBlobClient(fileName);
LOGGER.debug("Going to file information to " + path + "/" + fileName);
blobClient.uploadFromFile(path + "/" + fileName);
LOGGER.debug("After file information to " + path + "/" + fileName);
Here are some upload tests from the BURP intruder extension, I set the payload to be 40. I did not want the system to try too many times for a traffic charge.
POST /sathishjee/asyncServlet HTTP/1.1
Host: localhost:9090
Content-Length: 3589313
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="109", "Not_A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
Origin: http://localhost:9090
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryytifQ4MEWCLFlQSi
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.75 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:9090/sathishjee/asyncupload.jsp
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: JSESSIONID=FF256D10506839ED7C5AC9FA7539C9E5
Connection: close
BURP intruder shows the successful uploads in the aync request method, but the success is only indicated when the first upload comes in. The Servlet does not encounter the race condition as it did in the previous servlet specification, so there is a graceful exit to the upload.
As the servlet specification has evolved, I believe it is possible to use Spring for everything and build a one-off utility to handle some async operations. You can find the entire code on GitHub.