package com.android.volley.request; import com.android.volley.DefaultRetryPolicy; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.Response.ErrorListener; import com.android.volley.Response.Listener; import com.android.volley.Response.ProgressListener; import com.android.volley.error.AuthFailureError; import com.android.volley.misc.MultiPartParam; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; import static com.android.volley.misc.MultipartUtils.BINARY; import static com.android.volley.misc.MultipartUtils.BOUNDARY_PREFIX; import static com.android.volley.misc.MultipartUtils.COLON_SPACE; import static com.android.volley.misc.MultipartUtils.CONTENT_TYPE_MULTIPART; import static com.android.volley.misc.MultipartUtils.CONTENT_TYPE_OCTET_STREAM; import static com.android.volley.misc.MultipartUtils.CRLF; import static com.android.volley.misc.MultipartUtils.FILENAME; import static com.android.volley.misc.MultipartUtils.FORM_DATA; import static com.android.volley.misc.MultipartUtils.HEADER_CONTENT_DISPOSITION; import static com.android.volley.misc.MultipartUtils.HEADER_CONTENT_TRANSFER_ENCODING; import static com.android.volley.misc.MultipartUtils.HEADER_CONTENT_TYPE; import static com.android.volley.misc.MultipartUtils.SEMICOLON_SPACE; import static com.android.volley.misc.MultipartUtils.getContentLengthForMultipartRequest; /** * A request for making a Multi Part request * * @param <T> Response expected */ public abstract class MultiPartRequest<T> extends Request<T> implements ProgressListener{ private static final String PROTOCOL_CHARSET = "utf-8"; private int curTime; private String boundaryPrefixed; private Listener<T> mListener; private ProgressListener mProgressListener; private Map<String, MultiPartParam> mMultipartParams = null; private Map<String, String> mFileUploads = null; public static final int TIMEOUT_MS = 30000; private boolean isFixedStreamingMode; /** * Creates a new request with the given method. * * @param method the request {@link Method} to use * @param url URL to fetch the string at * @param listener Listener to receive the String response * @param errorListener Error listener, or null to ignore errors */ public MultiPartRequest(int method, String url, Listener<T> listener, ErrorListener errorListener) { super(method, url, Priority.NORMAL, errorListener, new DefaultRetryPolicy(TIMEOUT_MS, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); mListener = listener; mMultipartParams = new HashMap<String, MultiPartParam>(); mFileUploads = new HashMap<String, String>(); curTime = (int) (System.currentTimeMillis() / 1000); boundaryPrefixed = BOUNDARY_PREFIX + curTime; } /** * Get the protocol charset */ public String getProtocolCharset() { return PROTOCOL_CHARSET; } /** * Get the Content Length */ public int getContentLength() { return getContentLengthForMultipartRequest(getBoundryPrefixed(), getMultipartParams(), getFilesToUpload()); } @Override public String getBodyContentType() { return String.format(CONTENT_TYPE_MULTIPART, getProtocolCharset(), getBoundry()); } @Override abstract protected Response<T> parseNetworkResponse(NetworkResponse response); @Override protected void deliverResponse(T response) { if(null != mListener){ mListener.onResponse(response); } } /** * Set listener for tracking download progress * * @param listener */ public void setOnProgressListener(ProgressListener listener){ mProgressListener = listener; } @Override public void onProgress(long transferredBytes, long totalSize) { if(null != mProgressListener){ mProgressListener.onProgress(transferredBytes, totalSize); } } public boolean isFixedStreamingMode() { return isFixedStreamingMode; } public void setFixedStreamingMode(boolean isFixedStreamingMode) { this.isFixedStreamingMode = isFixedStreamingMode; } /** * Get the boundry prefixed */ public String getBoundryPrefixed() { return boundaryPrefixed; } /** * Get the boundry */ public int getBoundry() { return curTime; } /** * Add a parameter to be sent in the multipart request * * @param name The name of the paramter * @param contentType The content type of the paramter * @param value the value of the paramter * @return The Multipart request for chaining calls */ public MultiPartRequest<T> addMultipartParam(String name, String contentType, String value) { mMultipartParams.put(name, new MultiPartParam(contentType, value)); return this; } /** * Add a string parameter to be sent in the multipart request * * @param name The name of the paramter * @param value the value of the paramter * @return The Multipart request for chaining calls */ public MultiPartRequest<T> addStringParam(String name, String value) { mMultipartParams.put(name, new MultiPartParam("text/plain", value)); return this; } /** * Add a file to be uploaded in the multipart request * * @param name The name of the file key * @param filePath The path to the file. This file MUST exist. * @return The Multipart request for chaining method calls */ public MultiPartRequest<T> addFile(String name, String filePath) { mFileUploads.put(name, filePath); return this; } /** * Get all the multipart params for this request * * @return A map of all the multipart params NOT including the file uploads */ public Map<String, MultiPartParam> getMultipartParams() { return mMultipartParams; } /** * Get all the files to be uploaded for this request * * @return A map of all the files to be uploaded for this request */ public Map<String, String> getFilesToUpload() { return mFileUploads; } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); try { buildParts(dos); return bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return super.getBody(); } private void buildParts(DataOutputStream dos) throws IOException { Map<String, MultiPartParam> multipartParams = getMultipartParams(); Map<String, String> filesToUpload = getFilesToUpload(); for (Map.Entry<String, MultiPartParam> multipartParam : multipartParams.entrySet()) { MultiPartParam param = multipartParam.getValue(); buildStringPart(dos, multipartParam.getKey(), param); } for (Map.Entry<String, String> fileToUpload : filesToUpload.entrySet()) { File file = new File(fileToUpload.getValue()); if (!file.exists()) { throw new IOException(String.format("File not found: %s", file.getAbsolutePath())); } else if (file.isDirectory()) { throw new IOException(String.format("File is a directory: %s", file.getAbsolutePath())); } buildDataPart(dos, fileToUpload.getKey(), file); } // close multipart form data after text and file data dos.writeBytes(getBoundryPrefixed() + BOUNDARY_PREFIX); dos.writeBytes(CRLF); } private void buildStringPart(DataOutputStream dataOutputStream, String key, MultiPartParam param) throws IOException { dataOutputStream.writeBytes(getBoundryPrefixed()); dataOutputStream.writeBytes(CRLF); dataOutputStream.writeBytes(String.format(HEADER_CONTENT_DISPOSITION + COLON_SPACE + FORM_DATA, key)); dataOutputStream.writeBytes(CRLF); dataOutputStream.writeBytes(HEADER_CONTENT_TYPE + COLON_SPACE + param.contentType); dataOutputStream.writeBytes(CRLF); dataOutputStream.writeBytes(CRLF); dataOutputStream.writeBytes(param.value); dataOutputStream.writeBytes(CRLF); } private void buildDataPart(DataOutputStream dataOutputStream, String key, File file) throws IOException { dataOutputStream.writeBytes(getBoundryPrefixed()); dataOutputStream.writeBytes(CRLF); dataOutputStream.writeBytes(String.format(HEADER_CONTENT_DISPOSITION + COLON_SPACE + FORM_DATA + SEMICOLON_SPACE + FILENAME, key, file.getName())); dataOutputStream.writeBytes(CRLF); dataOutputStream.writeBytes(HEADER_CONTENT_TYPE + COLON_SPACE + CONTENT_TYPE_OCTET_STREAM); dataOutputStream.writeBytes(CRLF); dataOutputStream.writeBytes(HEADER_CONTENT_TRANSFER_ENCODING + COLON_SPACE + BINARY); dataOutputStream.writeBytes(CRLF); dataOutputStream.writeBytes(CRLF); FileInputStream fileInputStream = new FileInputStream(file); int bytesAvailable = fileInputStream.available(); int maxBufferSize = 1024 * 1024; int bufferSize = Math.min(bytesAvailable, maxBufferSize); byte[] buffer = new byte[bufferSize]; int bytesRead = fileInputStream.read(buffer, 0, bufferSize); while (bytesRead > 0) { dataOutputStream.write(buffer, 0, bufferSize); bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = fileInputStream.read(buffer, 0, bufferSize); } dataOutputStream.writeBytes(CRLF); } }