import ballerina/http;
import ballerina/log;
import ballerina/mime;

http:Client clientEP = check new ("http://localhost:9090");

//Binds the listener to the service.
service /multiparts on new http:Listener(9090) {

    resource function post decode(http:Request request)
            returns http:Response|http:InternalServerError{
        http:Response response = new;
        // [Extracts bodyparts](https://docs.central.ballerina.io/ballerina/http/latest/http/classes/Request#getBodyParts) from the request.
        var bodyParts = request.getBodyParts();

        if (bodyParts is mime:Entity[]) {
            foreach var part in bodyParts {
                handleContent(part);
            }
            response.setPayload(<@untainted>bodyParts);
            return response;
        } else {
            log:printError(bodyParts.message());
            return {body:"Error in decoding multiparts!"};
        }
    }

    resource function get encode(http:Request req)
            returns http:Response|http:InternalServerError {
        //Create a json body part.
        mime:Entity jsonBodyPart = new;
        jsonBodyPart.setContentDisposition(
                        getContentDispositionForFormData("json part"));
        jsonBodyPart.setJson({"name": "wso2"});
        //Create an `xml` body part as a file upload.
        mime:Entity xmlFilePart = new;
        xmlFilePart.setContentDisposition(
                       getContentDispositionForFormData("xml file part"));
        // This file path is relative to where the ballerina is running.
        // If your file is located outside, please
        // give the absolute file path instead.
        xmlFilePart.setFileAsEntityBody("./files/test.xml",
                                        contentType = mime:APPLICATION_XML);
        // Create an array to hold all the body parts.
        mime:Entity[] bodyParts = [jsonBodyPart, xmlFilePart];
        http:Request request = new;
        // [Set the body parts](https://docs.central.ballerina.io/ballerina/http/latest/http/classes/Request#setBodyParts) to the request.
        // Here the content-type is set as multipart form data.
        // This also works with any other multipart media type.
        // eg:- `multipart/mixed`, `multipart/related` etc.
        // You need to pass the content type that suit your requirement.
        request.setBodyParts(bodyParts, contentType = mime:MULTIPART_FORM_DATA);
        var returnResponse = clientEP->post("/multiparts/decode", request);
        if (returnResponse is http:Response) {
            return returnResponse;
        } else {
            return {body:"Error occurred while sending multipart request!"};
        }
    }
}

// The content logic that handles the body parts vary based on your requirement.
function handleContent(mime:Entity bodyPart) {
    // [Get the media type](https://docs.central.ballerina.io/ballerina/mime/latest/mime/functions#getMediaType) from the body part retrieved from the request.
    var mediaType = mime:getMediaType(bodyPart.getContentType());
    if (mediaType is mime:MediaType) {
        string baseType = mediaType.getBaseType();
        if (mime:APPLICATION_XML == baseType || mime:TEXT_XML == baseType) {
            //[Extracts `xml` data](https://docs.central.ballerina.io/ballerina/mime/latest/mime/classes/Entity#getXml) from the body part.
            var payload = bodyPart.getXml();
            if (payload is xml) {
                log:printInfo(payload.toString());
            } else {
                log:printError(payload.message());
            }
        } else if (mime:APPLICATION_JSON == baseType) {
            //[Extracts `json` data](https://docs.central.ballerina.io/ballerina/mime/latest/mime/classes/Entity#getJson) from the body part.
            var payload = bodyPart.getJson();
            if (payload is json) {
                log:printInfo(payload.toJsonString());
            } else {
                log:printError(payload.message());
            }
        } else if (mime:TEXT_PLAIN == baseType) {
            //[Extracts text data](https://docs.central.ballerina.io/ballerina/mime/latest/mime/classes/Entity#getText) from the body part.
            var payload = bodyPart.getText();
            if (payload is string) {
                log:printInfo(payload);
            } else {
                log:printError(payload.message());
            }
        }
    }
}

function getContentDispositionForFormData(string partName)
                                    returns (mime:ContentDisposition) {
    mime:ContentDisposition contentDisposition = new;
    contentDisposition.name = partName;
    contentDisposition.disposition = "form-data";
    return contentDisposition;
}

Request With Multiparts

Ballerina supports encoding and decoding multipart content in http requests along with nested parts. When you request multiparts from the HTTP inbound request, you get an array of body parts (an array of entities). You can loop through this array and handle the received body parts according to your requirement.

For more information on the underlying module, see the Mime module.

import ballerina/http;
import ballerina/log;
import ballerina/mime;
http:Client clientEP = check new ("http://localhost:9090");
service /multiparts on new http:Listener(9090) {

Binds the listener to the service.

    resource function post decode(http:Request request)
            returns http:Response|http:InternalServerError{
        http:Response response = new;
        var bodyParts = request.getBodyParts();

Extracts bodyparts from the request.

        if (bodyParts is mime:Entity[]) {
            foreach var part in bodyParts {
                handleContent(part);
            }
            response.setPayload(<@untainted>bodyParts);
            return response;
        } else {
            log:printError(bodyParts.message());
            return {body:"Error in decoding multiparts!"};
        }
    }
    resource function get encode(http:Request req)
            returns http:Response|http:InternalServerError {
        mime:Entity jsonBodyPart = new;
        jsonBodyPart.setContentDisposition(
                        getContentDispositionForFormData("json part"));
        jsonBodyPart.setJson({"name": "wso2"});

Create a json body part.

        mime:Entity xmlFilePart = new;
        xmlFilePart.setContentDisposition(
                       getContentDispositionForFormData("xml file part"));

Create an xml body part as a file upload.

        xmlFilePart.setFileAsEntityBody("./files/test.xml",
                                        contentType = mime:APPLICATION_XML);

This file path is relative to where the ballerina is running. If your file is located outside, please give the absolute file path instead.

        mime:Entity[] bodyParts = [jsonBodyPart, xmlFilePart];
        http:Request request = new;

Create an array to hold all the body parts.

        request.setBodyParts(bodyParts, contentType = mime:MULTIPART_FORM_DATA);
        var returnResponse = clientEP->post("/multiparts/decode", request);
        if (returnResponse is http:Response) {
            return returnResponse;
        } else {
            return {body:"Error occurred while sending multipart request!"};
        }
    }
}

Set the body parts to the request. Here the content-type is set as multipart form data. This also works with any other multipart media type. eg:- multipart/mixed, multipart/related etc. You need to pass the content type that suit your requirement.

function handleContent(mime:Entity bodyPart) {

The content logic that handles the body parts vary based on your requirement.

    var mediaType = mime:getMediaType(bodyPart.getContentType());
    if (mediaType is mime:MediaType) {
        string baseType = mediaType.getBaseType();
        if (mime:APPLICATION_XML == baseType || mime:TEXT_XML == baseType) {

Get the media type from the body part retrieved from the request.

            var payload = bodyPart.getXml();
            if (payload is xml) {
                log:printInfo(payload.toString());
            } else {
                log:printError(payload.message());
            }
        } else if (mime:APPLICATION_JSON == baseType) {

Extracts xml data from the body part.

            var payload = bodyPart.getJson();
            if (payload is json) {
                log:printInfo(payload.toJsonString());
            } else {
                log:printError(payload.message());
            }
        } else if (mime:TEXT_PLAIN == baseType) {

Extracts json data from the body part.

            var payload = bodyPart.getText();
            if (payload is string) {
                log:printInfo(payload);
            } else {
                log:printError(payload.message());
            }
        }
    }
}

Extracts text data from the body part.

function getContentDispositionForFormData(string partName)
                                    returns (mime:ContentDisposition) {
    mime:ContentDisposition contentDisposition = new;
    contentDisposition.name = partName;
    contentDisposition.disposition = "form-data";
    return contentDisposition;
}
# In the directory, which contains the `.bal` file, create a directory named `file`,
# and add an XML files named `test.xml` in it.
# To start the service, navigate to the directory that contains the
# `.bal` file and use the `bal run` command below.
bal run request_with_multiparts.bal
[ballerina/http] started HTTP/WS listener 0.0.0.0:9090
time = 2021-01-21 22:00:17,167 level = INFO  module = "" message = "{"name":"ballerina"}"
time = 2021-01-21 22:01:18,466 level = INFO  module = "" message = "{"name":"wso2"}"
time = 2021-01-21 22:01:18,682 level = INFO  module = "" message = "<ballerinalang>
    <version>0.963</version>
    <test>test xml file to be used as a file part</test>
</ballerinalang>"
^C[ballerina/http] stopped HTTP/WS listener 0.0.0.0:9090
# Start multipartDemoService
# The cURL command, which you need to execute to decode a multipart request
curl -F "part1={\"name\":\"ballerina\"};type=application/json" http://localhost:9090/multiparts/decode -H "Content-Type: multipart/mixed" -H 'Expect:'
--f710b4a02896b88a
content-disposition: attachment;name="part1"
content-type: application/json
content-id: 0
{"name":"ballerina"}
--f710b4a02896b88a--
# The cURL command, which you need to execute to encode the parts of the body and send a multipart request via the Ballerina service
curl -v http://localhost:9090/multiparts/encode
> GET /multiparts/encode HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: multipart/form-data; boundary=bd7547c98465dae2
< date: Wed, 23 Sep 2020 10:20:17 +0530
< server: ballerina
< content-length: 398
<
--bd7547c98465dae2
content-disposition: form-data;name="json part"
content-type: application/json
content-id: 0
{"name":"wso2"}
--bd7547c98465dae2
content-disposition: form-data;name="xml file part"
content-type: application/xml
content-id: 1
<ballerinalang>
    <version>0.963</version>
    <test>test xml file to be used as a file part</test>
</ballerinalang>
--bd7547c98465dae2--
* Connection #0 to host localhost left intact
* Closing connection 0