Popular Posts

Multipart File Uploader

Untitled

Uploading large files as bytes stream from the standalone clients (like android device) using HttpURLConnection may cause memory issue or affects the performance.

RFC 1341 specification gives you deeper look of how the multipart messages would work.

In the case of multiple part file uploads, in which one or more different sets of data are combined in a single body,

a 'multipart' Content-Type field must appear in the entity’s header.

The body must then contain one or more "body parts," each preceded by an encapsulation boundary, and the last one followed by a closing boundary.

Each part starts with an encapsulation boundary, and then contains a body part consisting of header area, a blank line, and a body area.

Thus a body part is similar to an RFC 822 message in syntax, but different in meaning.

Below is the visual representation of how the multipart data is needs to be created.

--*****1421045301108***** (encapsulation boundary)
Content-Disposition: form-data; name=" file "; filename="multiparttest.pgz"
Content-Type: multipart/form-data (1)
Content-Transfer-Encoding: binary
######################################
actual file data goes here. as bytes
understand how mime types work.
######################################
--*****1421045301108***** (3)
Content-Disposition: form-data; name="messageId" (1)
Content-Type: text/plain
ff8081814ac846f3014ac92b758f00eb
--*****1421045301108*****
Content-Disposition: form-data; name="fileName" (2)
Content-Type: text/plain
multiparttest.pgz
--*****1421045301108*****--
1 Each part should have Content-type header to specify the content of the message.
The absence of a Content-Type header field implies that the encapsulation is plain US-ASCII text.
1 The only header fields that have defined meaning for body parts are those the names of which begin with "Content-".

All other header fields are generally to be ignored in body parts.

Although they should generally be retained in mail processing, they may be discarded by gateways if necessary.

1 Each body part is preceded by an encapsulation boundary. The encapsulation boundary MUST NOT appear inside any of the encapsulated parts.

Thus, it is crucial that the composing agent be able to choose and specify the unique boundary that will separate the parts.

The Content-Type field for multipart entities requires one parameter, "boundary", which is used to specify the encapsulation boundary.

The encapsulation boundary is defined as a line consisting entirely of two hyphen characters ("-", decimal code 45) followed by the boundary parameter value from the Content-Type header field.

Thus, a typical multipart Content-Type header field might look like this:

Content-Type: multipart/mixed;boundary=*****1421045301108*****
you can specify on HttpURLConnection as shown below
HttpURLConnection.setRequestProperty( "Content-Type", "multipart/form-data; boundary=" + boundary );

This indicates that the entity consists of several parts, each itself with a structure that is syntactically identical to an RFC 822 message,

except that the header area might be completely empty, and that the parts are each preceded by the line

--*****1421045301108*****

The encapsulation boundary must occur at the beginning of a line, i.e., following a CRLF,

and that that initial CRLF is considered to be part of the encapsulation boundary rather than part of the preceding part.

The boundary must be followed immediately either by another CRLF and the header fields for the next part, or by two CRLFs,

in which case there are no header fields for the next part (and it is therefore assumed to be of Content-Type text/plain).

Encapsulation boundaries must not appear within the encapsulations, and must be no longer than 70 characters, not counting the two leading hyphens

The encapsulation boundary following the last body part is a distinguished delimiter that indicates that no further body parts will follow.

Such a delimiter is identical to the previous delimiters, with the addition of two more hyphens at the end of the line:

--*****1421045301108*****--
MultipartFormUtil.java
package com.tvajjala.documentation.upload;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;


/**
 * <p>
 * this lets you send multi part file from the standalone client like android without any timeout/memory issues.
 * </p>
 * MultipartFormUtil.java
 *
 * @author ThirupathiReddy Vajjala
 */
public class MultipartFormUtil {

    /**
     * The main method.
     *
     * @param args the arguments
     * @throws Exception the exception
     */
    public static void main(final String[] args) throws Exception {

        final long l = System.currentTimeMillis();

        final URL url = new URL("http://localhost:8084/multipart-demo/api/upload-file");

        final Map<String, String> headers = new HashMap<String, String>();

        /* set deviceId , networkId */

        headers.put("userId", "1112");
        headers.put("patientId", "1112");
        headers.put("deviceId", "57575fyfyfy655653");
        headers.put("networkId", "4028e4c64983a0d5014983a0e7be0101");

        // headers.put( "x-auth-token", "OUI0RDoxNDE3NDMyNzU4OTMxOjE5Mi4xNjguMjE1LjE3Ng==" );

        headers.put("User-Agent", "  Android Device ");

        /* add other fields information */
        final Properties formData = new Properties();
        formData.setProperty("recordingId", "ff8081814ac846f3014ac92b758f00eb");

        final File file = new File("D:/docs/multiparttest.pgz");

        final String response = MultipartFormUtil.submit(url, headers, formData, file);

        final long diff = System.currentTimeMillis() - l;
        System.out.println(diff);
        System.out.println(response);

    }

    /**
     * Submit.
     *
     * @param url      the url
     * @param headers  the headers
     * @param formData the form data
     * @param file     the file
     * @return the string
     * @throws Exception the exception
     */
    public static String submit(final URL url, final Map<String, String> headers, final Properties formData, final File file) throws Exception {

        HttpURLConnection connection;
        DataOutputStream outputStream;

        final StringBuffer visualize = new StringBuffer();
        try (final FileInputStream fileInputStream = new FileInputStream(file)) {

            final String boundary = MultipartFormUtil.CONSTANT + Long.toString(System.currentTimeMillis()) + MultipartFormUtil.CONSTANT;

            int bytesRead;
            int bytesAvailable;
            int bufferSize;

            final byte[] buffer;

            final int maxBufferSize = 1 * 1024 * 1024;

            connection = (HttpURLConnection) url.openConnection();

            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setUseCaches(false);

            connection.setRequestMethod("POST");
            connection.setRequestProperty("Connection", "Keep-Alive");
            connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

            /* headers info */

            for (final Entry<String, String> header : headers.entrySet()) {
                connection.addRequestProperty(header.getKey(), header.getValue());
            }

            outputStream = new DataOutputStream(connection.getOutputStream());

            outputStream.writeBytes(MultipartFormUtil.TWO_HYPHEN + boundary + MultipartFormUtil.LINE_FEED);
            visualize.append(MultipartFormUtil.TWO_HYPHEN + boundary + MultipartFormUtil.LINE_FEED);

            /* name=file server side you need to retrieve using this name */

            // outputStream.writeBytes(
            // "Content-Disposition: form-data; name=\"" + "file" +
            // "\"; filename=\"" + file.getName() + "\""
            // + MultipartFormUtil.LINE_FEED );

            outputStream.writeBytes(MessageFormat.format("Content-Disposition: form-data; name=\" file \"; filename=\" {0} \" {1}", file.getName(),
                    MultipartFormUtil.LINE_FEED));
            visualize.append(MessageFormat.format("Content-Disposition: form-data; name=\" file \"; filename=\" {0} \" {1}", file.getName(),
                    MultipartFormUtil.LINE_FEED));

            outputStream.writeBytes("Content-Type: multipart/form-data" + MultipartFormUtil.LINE_FEED);
            visualize.append("Content-Type: multipart/form-data" + MultipartFormUtil.LINE_FEED);
            outputStream.writeBytes("Content-Transfer-Encoding: binary" + MultipartFormUtil.LINE_FEED);
            visualize.append("Content-Transfer-Encoding: binary" + MultipartFormUtil.LINE_FEED);
            outputStream.writeBytes(MultipartFormUtil.LINE_FEED);
            visualize.append(MultipartFormUtil.LINE_FEED);
            bytesAvailable = fileInputStream.available();
            // visualize.append( "  file bytes  goes here" );
            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            buffer = new byte[bufferSize];

            bytesRead = fileInputStream.read(buffer, 0, bufferSize);

            while (bytesRead > 0) {
                outputStream.write(buffer, 0, bufferSize);
                visualize.append(new String(buffer));
                bytesAvailable = fileInputStream.available();
                bufferSize = Math.min(bytesAvailable, maxBufferSize);
                bytesRead = fileInputStream.read(buffer, 0, bufferSize);
            }

            outputStream.writeBytes(MultipartFormUtil.LINE_FEED);
            visualize.append(MultipartFormUtil.LINE_FEED);

            formData.put("fileName", file.getName());
            for (final Entry<Object, Object> formFields : formData.entrySet()) {
                outputStream.writeBytes(MultipartFormUtil.TWO_HYPHEN + boundary + MultipartFormUtil.LINE_FEED);
                outputStream.writeBytes("Content-Disposition: form-data; name=\"" + formFields.getKey() + "\"" + MultipartFormUtil.LINE_FEED);
                outputStream.writeBytes("Content-Type: text/plain" + MultipartFormUtil.LINE_FEED);
                outputStream.writeBytes(MultipartFormUtil.LINE_FEED);
                outputStream.writeBytes(String.valueOf(formFields.getValue()));
                outputStream.writeBytes(MultipartFormUtil.LINE_FEED);

                visualize.append(MultipartFormUtil.TWO_HYPHEN + boundary + MultipartFormUtil.LINE_FEED);
                visualize.append("Content-Disposition: form-data; name=\"" + formFields.getKey() + "\"" + MultipartFormUtil.LINE_FEED);
                visualize.append("Content-Type: text/plain" + MultipartFormUtil.LINE_FEED);
                visualize.append(MultipartFormUtil.LINE_FEED);
                visualize.append(String.valueOf(formFields.getValue()));
                visualize.append(MultipartFormUtil.LINE_FEED);

            }

            outputStream.writeBytes(MultipartFormUtil.TWO_HYPHEN + boundary + MultipartFormUtil.TWO_HYPHEN + MultipartFormUtil.LINE_FEED);

            visualize.append(MultipartFormUtil.TWO_HYPHEN + boundary + MultipartFormUtil.TWO_HYPHEN + MultipartFormUtil.LINE_FEED);

            System.err.println(visualize.toString().replaceAll(MultipartFormUtil.LINE_FEED, "CRLF" + MultipartFormUtil.LINE_FEED));
            final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));

            final StringBuilder responseBuffer = new StringBuilder();

            String line;

            while ((line = reader.readLine()) != null) {
                responseBuffer.append(line);
            }

            return responseBuffer.toString();
        }
    }

    /**
     * CONSTANT
     */
    private static final String CONSTANT = "*****";

    /* line feed for content disposition */
    /**
     * The Constant LINE_FEED.
     */
    private static final String LINE_FEED = "\r\n";

    /* line separator in the content disposition */
    /**
     * The Constant TWO_HYPHEN.
     */
    private static final String TWO_HYPHEN = "--";
}

No comments:

Post a Comment