finos/symphony-bdk-java

Limitation for multipart/form-data form

imadamahmid1 opened this issue · 1 comments

Feature Request

The current httpClient of bdk doesn't support multiple values for a given form key.
With the current httpClient of bdk, this works:

httpClient.formParam("file", new File("path/to/myFile/");

But when we keep adding other files like the snippet bellow doesn't work because we keep overriding the map containing the parameters:

httpClient.formParam("file", new File("path/to/myFirstFile/");
httpClient.formParam("file", new File("path/to/mySecondFile/");

Description of Problem:

I checked the code and it seems like the form params are stored in a map, and the value of "file" will be overridden each time which will prevent us from having a field with many files. The implementation ideally would support adding of multiple files under the same key and use boundary to separate the files under the field file.

Ideally we want to to be able to add several files in one field of form data in an iterative way, like this:

httpClient.formParam("file", new File("path/to/myFirstFile/");
httpClient.formParam("file", new File("path/to/mySecondFile/");
httpClient.formParam("file", new File("path/to/myThirdFile/");

Potential Solutions:

N/A

We could think of several solutions:

  • user passes a List<File> as form param and we update the ApiClientWebClient and ApiClientJersey2 implementations to handle a list of files (list of strings are already handled for urlencoded forms):
    private void serializeMultiPartDataEntry(String paramKey, Object paramValue,
    MultiValueMap<String, Object> formValueMap) {
    if (paramValue instanceof File) {
    File file = (File) paramValue;
    formValueMap.add(paramKey, new FileSystemResource(file));
    } else if (paramValue instanceof ApiClientBodyPart[]) {
    for (ApiClientBodyPart bodyPart : (ApiClientBodyPart[]) paramValue) {
    serializeApiClientBodyPart(paramKey, bodyPart, formValueMap);
    }
    } else if (paramValue instanceof ApiClientBodyPart) {
    serializeApiClientBodyPart(paramKey, (ApiClientBodyPart) paramValue, formValueMap);
    } else {
    formValueMap.add(paramKey, parameterToString(paramValue));
    }
    }
    and
    private Entity<?> serializeMultiPartFormDataEntity(Map<String, Object> formParams) {
    FormDataMultiPart multiPart = new FormDataMultiPart();
    for (final Entry<String, Object> param : formParams.entrySet()) {
    // if part is a File
    if (param.getValue() instanceof File) {
    final File file = (File) param.getValue();
    final FormDataContentDisposition contentDisposition = FormDataContentDisposition
    .name(param.getKey())
    .fileName(file.getName())
    .size(file.length())
    .build();
    final FormDataBodyPart streamPart = new FormDataBodyPart(
    contentDisposition,
    file,
    MediaType.APPLICATION_OCTET_STREAM_TYPE
    );
    multiPart = (FormDataMultiPart) multiPart.bodyPart(streamPart);
    }
    // if part is a ApiClientBodyPart[]
    else if (param.getValue() instanceof ApiClientBodyPart[]) {
    for (ApiClientBodyPart attachment : (ApiClientBodyPart[]) param.getValue()) {
    final StreamDataBodyPart streamPart =
    new StreamDataBodyPart(param.getKey(), attachment.getContent(), attachment.getFilename());
    multiPart = (FormDataMultiPart) multiPart.bodyPart(streamPart);
    }
    }
    // if part is a single ApiClientBodyPart
    else if (param.getValue() instanceof ApiClientBodyPart) {
    final ApiClientBodyPart part = (ApiClientBodyPart) param.getValue();
    final StreamDataBodyPart streamPart =
    new StreamDataBodyPart(param.getKey(), part.getContent(), part.getFilename());
    multiPart = (FormDataMultiPart) multiPart.bodyPart(streamPart);
    } else {
    multiPart = multiPart.field(param.getKey(), this.parameterToString(param.getValue()));
    }
    }
    return Entity.entity(multiPart, MultiPartMediaTypes.createFormData());
    }

    It has the advantage of not breaking the API
  • or we update the HttpClient.RequestConfig class so that it contains a MultiValuedMap as formParams and we pass a Map<String, List> as formParams in the invokeApi method:
    We will still need to do the above (handing a List<File> as formParam value)
    However this will break the current contract/API (note that MultiValuedMap<K,V> extends Map<K, List<V>>)