Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple file upload & json object in a single POST Request on Play Framework

I have a Windows Phone 8 client that makes the following post request:

public async Task<string> DoPostRequestAsync(String URI, JSonWriter jsonObject, ObservableCollection<byte[]> attachments)
    {
        var client = new RestClient(DefaultUri);

        var request = new RestRequest(URI, Method.POST);
        request.AddParameter("application/json; charset=utf-8", jsonObject.ToString(), ParameterType.RequestBody);

        // add files to upload
        foreach (var a in attachments)
            request.AddFile("picture", a, "file.jpg");

        var content = await client.GetResponseAsync(request);

        return content;
    }

From the RestSharp documentation i read that by adding files to a request it's automatically made as a "multipart/form-data" request.

The controller for the upload operation in Play 2.1 is as follows:

@BodyParser.Of(BodyParser.Json.class)
public static Result createMessage() {
    JsonNode json = request().body().asJson();
    ObjectNode result = Json.newObject();
    String userId = json.findPath("userId").getTextValue();
    String rayz = json.findPath("message").getTextValue();

    Http.MultipartFormData body = request().body().asMultipartFormData();
    Http.MultipartFormData.FilePart picture = body.getFile("picture");

    if (picture != null) {
        String fileName = picture.getFilename();
        String contentType = picture.getContentType();
        File file = picture.getFile();

        result.put("status", "success");
        result.put("message", "Created message!");
        return badRequest(result);
    } else {
        result.put("status", "error");
        result.put("message", "Message cannot be created!");
        return badRequest(result);
    }
}

Note that on the application.conf i have set the following in order to increase the size limit (seems not be working):

# Application settings
# ~~~~~
parsers.text.maxLength=102400K

Now, each time I am trying to make the POST request i noticed on the debugger that IsMaxSizeEsceeded variable is always true and that the multipart variable is null. When i tried to just upload one file nu using the following controller everything seemed to work normally. The size wasn't a problem and the multipart variable was set.

public static Result singleUpload() {
    ObjectNode result = Json.newObject();

    Http.MultipartFormData body = request().body().asMultipartFormData();
    Http.MultipartFormData.FilePart picture = body.getFile("picture");
    if (picture != null) {
        String fileName = picture.getFilename();
        String contentType = picture.getContentType();
        File file = picture.getFile();
        result.put("status", "success");
        result.put("message", "File uploaded!");
        return badRequest(result);
    } else {
        result.put("status", "error");
        result.put("message", "File cannot be uploaded!");
        return badRequest(result);
    }
}

The problem is that the attachments/files should be sent/uploaded along with the JSON object to the server in a single POST Request and NOT separately.

Has anyone faced similar problems before? Is it possible to achieve this - Send a json object and multiple files to be uploaded on the server in a single POST request with Play 2.1?

like image 648
George Nikolaides Avatar asked Feb 16 '23 23:02

George Nikolaides


1 Answers

Found the way to do it..posting it in case someone else tries to do something similar in the future.

So first of all the request from RestSharp client must be done in the following way:

public async Task<string> DoPostRequestWithAttachmentsAsync(String ext, JSonWriter jsonObject, ObservableCollection<byte[]> attachments) {
    var client = new RestClient(DefaultUri);
    var request = new RestRequest(ext, Method.POST);

    request.RequestFormat = DataFormat.Json;
    request.AddParameter("json", jsonObject.ToString(), ParameterType.GetOrPost);

    // add files to upload
    foreach (var a in attachments)
        request.AddFile("attachment", a, "someFileName");

    var content = await client.GetResponseAsync(request);

    if (content.StatusCode != HttpStatusCode.OK)
            return <error>;

    return content.Content;
}

Now moving into Play the controller is as follows:

public static Result createMessage() {
    List<Http.MultipartFormData.FilePart> attachments;
    String json_str;

    Http.MultipartFormData body = request().body().asMultipartFormData();

    // If the body is multipart get the json object asMultipartFormData()
    if (body!=null) {
        json_str = request().body().asMultipartFormData().asFormUrlEncoded().get("json")[0];
        attachments= body.getFiles();
    }
    // Else, if the body is not multipart get the json object asFormUrlEncoded()
    else {
        json_str = request().body().asFormUrlEncoded().get("json")[0];
        attachments = Collections.emptyList();
    }

    // Parse the Json Object
    JsonNode json = Json.parse(json_str);

    // Iterate through the uploaded files and save them to the server
    for (Http.MultipartFormData.FilePart o : attachments)
        FileManager.SaveAttachmentToServer(o.getContentType(), o.getFile());

    return ok();
}

So the mistakes in the RestSharp side seemed to be the ParameterType.RequestBody on the json object; and in the controller the size doesn't really changes anything.. but the important part is not to use the @BodyParser.Of(BodyParser.Json.class) since that would convert the whole request body into a json object. That combined with the files that are being send to the server triggers the isMaxSizeExceeded flag.

Finally the multipart file handling in the controller is as shown above, only tricky part is that the attachments are optional meaning that this must be handled.

like image 145
George Nikolaides Avatar answered Feb 18 '23 13:02

George Nikolaides