I am uploading file from android to server using HttpUrlConnection. In normal case file upload successful and correct HTTP response code (200) and message captured. But server also validates the file being uploaded and can close connection anytime while uploading which prohibits client to upload unnecessary data to server.
In brief, Server has following validation:
1. File MimeType check: If file MimeType isn't any image MimeType then close connection immediately throwing HTTP response code 415 and message "Unsupported Media Type". This check done immediately after request arrived in server.
2. File size check: While upload is being progressed as stream, if file size more then 5 MB then server close connection by throwing HTTP response code 413 and message "Request entity too large"
Current status:
- If file is image type AND less then 5 MB upload successful.
- If file is not image type OR more then 5 MB unable to get HTTP response code (413 or 415) and message from HttpUrlConnection object. SocketException or IOException occurs and then invoking
connection.getResponseCode()
throws another exception. Why notconnection
object holds response code and message sent from server ?
Code:
package com.example.mahbub.fileuploadrnd;
import android.util.Log;
import com.example.mahbub.fileuploadrnd.util.MimeTypesUtil;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class FileUploadRequest {
private static final String DEBUG_TAG = "FileUploadRequest";
public static final int ERROR_OUT_OF_MEMORY = 0x00;
public static final int ERROR_UNDEFINED = 0x01;
public static final int ERROR_HTTP_ERROR = 0x02;
/**
* The pool of ASCII chars to be used for generating a multipart boundary.
*/
private final static char[] MULTIPART_CHARS =
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final String LINE_END = "\r\n";
private static final String TWO_HYPHENS = "--";
String url;
Map<String,String> mStringParts;
String filePartName;
String filePath;
UploadListener mListener;
public FileUploadRequest(String url, String filePartName, String filePath, UploadListener listener) {
this.url = url;
this.mStringParts = new HashMap<>();
this.filePartName = filePartName;
this.filePath = filePath;
this.mListener = listener;
}
public void execute() {
new Thread(new Runnable() {
@Override
public void run() {
executeRequest();
}
}).start();
}
public void addStringPart(String name, String value) {
this.mStringParts.put(name, value);
}
public void addStringParts(Map<String, String> parts) {
this.mStringParts.putAll(parts);
}
public interface UploadListener {
void onSuccess(int responseCode, String response);
void onError(int errorCode);
void transferred(long transferred, double progress);
}
private void executeRequest() {
HttpURLConnection connection = null;
DataOutputStream outputStream = null;
String boundary = generateBoundary();
try {
URL url = new URL(this.url);
connection = (HttpURLConnection) url.openConnection();
// Allow Inputs & Outputs
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
connection.setChunkedStreamingMode(4 * 1024);
// Enable POST method
connection.setRequestMethod("POST");
// Set header field value
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.writeBytes(TWO_HYPHENS + boundary + LINE_END);
// At first write all string parts
if(mStringParts.size() > 0) {
for (Map.Entry<String, String> entry : mStringParts.entrySet()) {
//entity.addTextBody(entry.getKey(), entry.getValue());
String partData = String.format("Content-Disposition: form-data; name=\"%s\"", entry.getKey()) + LINE_END + LINE_END;
partData += entry.getValue() + LINE_END;
partData += (TWO_HYPHENS + boundary + LINE_END);
outputStream.writeBytes(partData);
}
}
// Write file data
File file = new File(filePath);
FileInputStream fileInputStream = new FileInputStream(file);
String partData = String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"", filePartName, file.getName()) + LINE_END;
partData += String.format("Content-Type: %s", getMimeType(file.getName()));
partData += LINE_END + LINE_END;
outputStream.writeBytes(partData);
// Input stream read buffer
byte[] buffer;
// Max possible buffer size
int maxBufferSize = 5 * 1024; // 5KB
// Bytes available to write in input stream
int bytesAvailable = fileInputStream.available();
Log.d(DEBUG_TAG, "File size: " + bytesAvailable);
// Buffer size
int bufferSize = Math.min(bytesAvailable, maxBufferSize);
// Number of bytes read per read operation
int bytesRead;
// Allocate buffer
buffer = new byte[bufferSize];
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
boolean errorOccured = false;
try {
while(bytesRead > 0) {
try {
outputStream.write(buffer, 0, bufferSize);
} catch (OutOfMemoryError e) {
Log.d(DEBUG_TAG, "OutOfMemoryError occurred");
errorOccured = true;
e.printStackTrace();
if(null != mListener) mListener.onError(ERROR_OUT_OF_MEMORY);
break;
} catch (SocketException e) {
Log.d(DEBUG_TAG, "SocketException occurred");
errorOccured = true;
e.printStackTrace();
if(null != mListener) mListener.onError(ERROR_OUT_OF_MEMORY);
break;
} catch (IOException e) {
Log.d(DEBUG_TAG, "IOException occurred");
errorOccured = true;
e.printStackTrace();
if(null != mListener) mListener.onError(ERROR_OUT_OF_MEMORY);
break;
}
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
double progress = ((double)(file.length()-bytesAvailable) / file.length());
Log.d(DEBUG_TAG, "Progress: " + progress);
if(null != mListener) mListener.transferred(file.length()-bytesAvailable, progress);
}
} catch (Exception e) {
e.printStackTrace();
if(null != mListener) mListener.onError(ERROR_UNDEFINED);
}
if(!errorOccured) {
outputStream.writeBytes(LINE_END);
outputStream.writeBytes(TWO_HYPHENS + boundary + TWO_HYPHENS + LINE_END);
}
// Responses from the server (code and message)
int responseCode = connection.getResponseCode();
String response = connection.getResponseMessage();
Log.d(DEBUG_TAG, "Server Response Code: " + responseCode);
Log.d(DEBUG_TAG, "Server Response Message: " + response);
InputStream inStream = null;
if(responseCode >= 200 && responseCode < 400) inStream = connection.getInputStream();
else inStream = connection.getErrorStream();
String responseString = readStream(inStream);
Log.d(DEBUG_TAG, "responseString: " + responseString);
if(responseCode >= 200 && responseCode < 400) {
if(null != mListener) mListener.onSuccess(responseCode, responseString);
} else {
if(null != mListener) mListener.onError(ERROR_HTTP_ERROR);
}
fileInputStream.close();
outputStream.flush();
outputStream.close();
outputStream = null;
inStream.close();
} catch (Exception e) {
e.printStackTrace();
if(null != mListener) mListener.onError(ERROR_UNDEFINED);
}
}
private String readStream(InputStream iStream) throws IOException {
//build a Stream Reader, it can read char by char
InputStreamReader iStreamReader = new InputStreamReader(iStream);
//build a buffered Reader, so that i can read whole line at once
BufferedReader bReader = new BufferedReader(iStreamReader);
String line = null;
StringBuilder builder = new StringBuilder();
while((line = bReader.readLine()) != null) { //Read till end
builder.append(line);
}
bReader.close(); //close all opened stuff
iStreamReader.close();
//iStream.close(); //EDIT: Let the creator of the stream close it!
// some readers may auto close the inner stream
return builder.toString();
}
private String generateBoundary() {
final StringBuilder buffer = new StringBuilder();
final Random rand = new Random();
final int count = rand.nextInt(11) + 30; // a random size from 30 to 40
for (int i = 0; i < count; i++) {
buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
}
return buffer.toString();
}
/**
* Get mimeType based on file extension
* @param fileName
* @return
*/
private String getMimeType(String fileName) {
String[] parts = fileName.split("\\.");
if(parts.length <= 1) return null;
return MimeTypesUtil.getMimeType(parts[parts.length-1]);
}
}