Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SpringBoot3 not processing all Multipart files when Array of files with same fieldName is sent in request #633

Closed
LuisTDev opened this issue Sep 17, 2023 · 3 comments
Assignees

Comments

@LuisTDev
Copy link

LuisTDev commented Sep 17, 2023

To help us debug your issue fill in the basic information below using the options provided

Serverless Java Container version: 2.0.0-M2

Implementations: Spring Boot 3

Framework version: SpringBoot 3.1.2

Frontend service: REST API

Deployment method: SAM, Serverless Framework

Scenario

I am trying to process this Spring Controller method where in "rightFile" I am expecting an Array of files with the same fieldName

@PostMapping("/compare") public List<ProcessedRow> compare( @RequestParam("leftFile") MultipartFile leftFile, @RequestParam("rightFile") List<MultipartFile> rightFile) { ... }

Expected behavior

I should get all files with same fieldname sent in request mapped as a list in rightFile

Actual behavior

I am only getting the last file mapped as a list of 1 MultipartFile in rightFile

Steps to reproduce

1.- create a SAM project with a Java Lambda that has enabled multipart form data in BinaryMediaTypes

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Example XlsComparator API written with SpringBoot with the aws-serverless-java-container library

Globals:
Api:
# API Gateway regional endpoints
EndpointConfiguration: REGIONAL
# Configure API to accept multipart form data
BinaryMediaTypes:
- "multipart~1form-data"
Cors:
AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
AllowMethods: "'GET,POST,OPTIONS'"
AllowOrigin: "'*'"

Resources:

XlsComparatorFunction:
Type: AWS::Serverless::Function
Properties:
Handler: com.lthome.comparexcel.StreamLambdaHandler::handleRequest
Runtime: java17
CodeUri: .
MemorySize: 1512
Policies:
- AWSLambdaBasicExecutionRole
Timeout: 60
SnapStart:
ApplyOn: PublishedVersions
Events:
ApiEvent:
Type: Api
Properties:
Path: /compare
Method: post

2.- Create a Rest Controller in Spring with a POST Method with the following signature
@PostMapping("/compare")
public List compare(
@RequestParam("leftFile") MultipartFile leftFile,
@RequestParam("rightFile") List rightFile) {
...
}

3.- Set the library aws-serverless-java-container and configure it in the project to make the Lambda Integration with our Spring Boot Project, here is an example of StreamLambdaHandler.java used.

public class StreamLambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {
private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;

static {
try {
handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(ComparexcelApplication.class);
} catch (ContainerInitializationException e) {
e.printStackTrace();
throw new RuntimeException("Could not initialize Spring Boot application", e);
}
}

@OverRide
public AwsProxyResponse handleRequest(AwsProxyRequest input, Context context) {
AwsProxyResponse response = null;
response = handler.proxy(input, context);
// Set CORS Headers
response
.getMultiValueHeaders()
.put("Access-Control-Allow-Methods", List.of("GET", "POST", "OPTIONS"));
response
.getMultiValueHeaders()
.put(
"Access-Control-Allow-Headers",
List.of(
"Content-Type",
"X-Amz-Date",
"Authorization",
"X-Api-Key",
"X-Amz-Security-Token"));
response.getMultiValueHeaders().put("Access-Control-Allow-Origin", List.of("*"));
return response;
}
}

4.- Test the method by sending as form data only 1 file in field "leftFile" and 2 Files in field "righFile", you will se that in spring controller we will get only 2 files: 1 for leftFile and 1 for rightFile.

Full log output

There are no errors in Logs but still not getting all Files sent in request in the controller, I believe the issue is in AwsHttpServletRequest.java in this method:

@SuppressFBWarnings({"FILE_UPLOAD_FILENAME", "WEAK_FILENAMEUTILS"})
protected Map<String, Part> getMultipartFormParametersMap() {
if (multipartFormParameters != null) {
return multipartFormParameters;
}
if (!JakartaServletFileUpload.isMultipartContent(this)) { // isMultipartContent also checks the content type
multipartFormParameters = new HashMap<>();
return multipartFormParameters;
}
Timer.start("SERVLET_REQUEST_GET_MULTIPART_PARAMS");
multipartFormParameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

    JakartaServletFileUpload upload = new JakartaServletFileUpload(DiskFileItemFactory.builder().get());

    try {
        **//Note: here we  get the 3 items which is correct**
        List<FileItem> items = upload.parseRequest(this);
        for (FileItem item : items) {
            String fileName = FilenameUtils.getName(item.getName());
            AwsProxyRequestPart newPart = new AwsProxyRequestPart(item.get());
            newPart.setName(item.getFieldName());
            newPart.setSubmittedFileName(fileName);
            newPart.setContentType(item.getContentType());
            newPart.setSize(item.getSize());
            item.getHeaders().getHeaderNames().forEachRemaining(h -> {
                newPart.addHeader(h, item.getHeaders().getHeader(h));
            });

           **//ISSUE: the problem is here when there are two items with the same fieldName we lose the list of items because map is updated with a newPart instead of adding it to a List of newParts ?**
            multipartFormParameters.put(item.getFieldName(), newPart);
        }
    } catch (FileUploadException e) {
        Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS");
        log.error("Could not read multipart upload file", e);
    }
    Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS");
    return multipartFormParameters;
}
logs
@mbfreder
Copy link
Contributor

Thanks for raising the issue. Let me reproduce it and I will get back to you.

@LuisTDev
Copy link
Author

LuisTDev commented Oct 5, 2023

Thanks @mbfreder please let me know if you neet something more to reproduce the error, as I stated I believe the problem is in AwsHttpServletRequest.java int the method "getMultipartFormParametersMap" the result is a: "Map<String, Part>" but I think it should be a: Map< String, List<Part> >

@mbfreder
Copy link
Contributor

mbfreder commented Oct 5, 2023

Hi @LuisTDev. Sorry for the delay. I just realized I wrote the comment last week and forgot to click "Comment". I did reproduce the issue, and you're right about where the problem is. I'll find some time to fix it next week. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants