Reputation: 45
I'm trying to post my image using Volley but i'm unable to upload my image to the server. I always get com.volley.ServerError. When I capture the image upload request using Fiddler, it gives me a 500 status/error code.
Here is my code for selecting image from gallery:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data == null) {
Toast.makeText(getActivity(), "No Image Selected", Toast.LENGTH_SHORT).show();
} else if (requestCode == Constants.CHOICE_AVATAR_FROM_GALLERY && resultCode == getActivity().RESULT_OK) {
Bitmap avatar = getBitmapFromData(data);
RestClient restClient = new RestClient(getActivity());
restClient.stringRequest(Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID, Constants.PROFILE_UPDATE_IMAGE_URL, this, imageData);
byte[] inputData=null;
try {
Bundle extra = data.getExtras();
Uri _uri= Uri.parse(extra.get("src_uri").toString());
InputStream iStream = getActivity().getContentResolver().openInputStream(_uri);
inputData = getBytes(iStream);
size = (inputData.length)/(1024*1024);
Log.d("ImageArray", "uri: " + size);
} catch (Exception e) {
e.printStackTrace();
}
if(size < 3){
imageView.setImageBitmap(avatar);
Map<String, String> dataMap = new HashMap<>();
dataMap.put("Email", Utility.sessionEmail(getActivity()));
restClient.multiPartImageUploadRequest(Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID,
url, this, dataMap, inputData);
}else {
imageView.setImageResource(R.drawable.ic_camera);
Toast.makeText(getActivity(), "Image size shuld not exceed 3 MB", Toast.LENGTH_SHORT).show();
}
}
}
public String getPath(Uri uri) {
Cursor cursor = getActivity().getContentResolver().query(uri, null, null, null, null);
cursor.moveToFirst();
String document_id = cursor.getString(0);
document_id = document_id.substring(document_id.lastIndexOf(":") + 1);
cursor.close();
cursor = getActivity().getContentResolver().query(
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
cursor.moveToFirst();
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
return path;
}
public void selectImage(View view) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(getCropIntent(intent), Constants.CHOICE_AVATAR_FROM_GALLERY);
}
private Intent getCropIntent(Intent intent) {
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 320);
intent.putExtra("outputY", 320);
intent.putExtra("return-data", true);
return intent;
}
public Bitmap getBitmapFromData(Intent data) {
Bitmap photo = null;
Uri photoUri = data.getData();
if (photoUri != null) {
photo = BitmapFactory.decodeFile(photoUri.getPath());
path = getPath(photoUri);
uri = photoUri;
Log.d("URI", "" + photoUri);
}
if (photo == null) {
Bundle extra = data.getExtras();
if (extra != null) {
photo = (Bitmap) extra.get("data");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
photo.compress(Bitmap.CompressFormat.JPEG, 90, stream);
}
}
return photo;
}
This is a method to handle upload POST request
public void multiPartImageUploadRequest(int id, String url, ResponseHandler handler, Map<String, String> data, byte[] image){
int request_id = id;
final ResponseHandler responseHandler = handler;
final Map<String, String> params = data;
final byte[] imageData = image;
pDialog = new ProgressDialog(context);
pDialog.setMessage("Uploading Image...");
pDialog.show();
VolleyMultipartRequest request = new VolleyMultipartRequest(Request.Method.POST, url,
new Response.Listener<NetworkResponse>() {
@Override
public void onResponse(NetworkResponse response) {
String resultResponse = new String(response.data);
responseHandler.success(resultResponse, Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID);
pDialog.dismiss();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
responseHandler.failure(error, Constants.PROFILE_IMAGE_UPDATE_REQUEST_ID);
pDialog.dismiss();
error.printStackTrace();
}
}) {
@Override
protected Map<String, String> getParams() {
return params;
}
@Override
protected Map<String, DataPart> getByteData() {
Map<String, DataPart> ImageParams = new HashMap<>();
// file name could found file base or direct access from real path
// for now just get bitmap data from ImageView
ImageParams.put("", new DataPart("file_avatar.jpg", imageData, "image/jpeg"));
return ImageParams;
}
};
request.setRetryPolicy(new DefaultRetryPolicy(30*1000, 1, 1.0f));
RestController.getInstance().addToRequestQueue(request);
}
VolleyMultipartRequest.java
public class VolleyMultipartRequest extends Request<NetworkResponse> {
private final String twoHyphens = "--";
private final String lineEnd = "\r\n";
private final String boundary = "apiclient-" + System.currentTimeMillis();
private Response.Listener<NetworkResponse> mListener;
private Response.ErrorListener mErrorListener;
private Map<String, String> mHeaders;
/**
* Default constructor with predefined header and post method.
*
* @param url request destination
* @param headers predefined custom header
* @param listener on success achieved 200 code from request
* @param errorListener on error http or library timeout
*/
public VolleyMultipartRequest(String url, Map<String, String> headers,
Response.Listener<NetworkResponse> listener,
Response.ErrorListener errorListener) {
super(Method.POST, url, errorListener);
this.mListener = listener;
this.mErrorListener = errorListener;
this.mHeaders = headers;
}
/**
* Constructor with option method and default header configuration.
*
* @param method method for now accept POST and GET only
* @param url request destination
* @param listener on success event handler
* @param errorListener on error event handler
*/
public VolleyMultipartRequest(int method, String url,
Response.Listener<NetworkResponse> listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
this.mListener = listener;
this.mErrorListener = errorListener;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
return (mHeaders != null) ? mHeaders : super.getHeaders();
}
@Override
public String getBodyContentType() {
return "multipart/form-data;boundary=" + boundary;
}
@Override
public byte[] getBody() throws AuthFailureError {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
try {
// populate text payload
Map<String, String> params = getParams();
if (params != null && params.size() > 0) {
textParse(dos, params, getParamsEncoding());
}
// populate data byte payload
Map<String, DataPart> data = getByteData();
if (data != null && data.size() > 0) {
dataParse(dos, data);
}
// close multipart form data after text and file data
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* Custom method handle data payload.
*
* @return Map data part label with data byte
* @throws AuthFailureError
*/
protected Map<String, DataPart> getByteData() throws AuthFailureError {
return null;
}
@Override
protected Response<NetworkResponse> parseNetworkResponse(NetworkResponse response) {
try {
return Response.success(
response,
HttpHeaderParser.parseCacheHeaders(response));
} catch (Exception e) {
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(NetworkResponse response) {
mListener.onResponse(response);
}
@Override
public void deliverError(VolleyError error) {
mErrorListener.onErrorResponse(error);
}
/**
* Parse string map into data output stream by key and value.
*
* @param dataOutputStream data output stream handle string parsing
* @param params string inputs collection
* @param encoding encode the inputs, default UTF-8
* @throws IOException
*/
private void textParse(DataOutputStream dataOutputStream, Map<String, String> params, String encoding) throws IOException {
try {
for (Map.Entry<String, String> entry : params.entrySet()) {
buildTextPart(dataOutputStream, entry.getKey(), entry.getValue());
}
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("Encoding not supported: " + encoding, uee);
}
}
/**
* Parse data into data output stream.
*
* @param dataOutputStream data output stream handle file attachment
* @param data loop through data
* @throws IOException
*/
private void dataParse(DataOutputStream dataOutputStream, Map<String, DataPart> data) throws IOException {
for (Map.Entry<String, DataPart> entry : data.entrySet()) {
buildDataPart(dataOutputStream, entry.getValue(), entry.getKey());
}
}
/**
* Write string data into header and data output stream.
*
* @param dataOutputStream data output stream handle string parsing
* @param parameterName name of input
* @param parameterValue value of input
* @throws IOException
*/
private void buildTextPart(DataOutputStream dataOutputStream, String parameterName, String parameterValue) throws IOException {
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + parameterName + "\"" + lineEnd);
//dataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + lineEnd);
dataOutputStream.writeBytes(lineEnd);
dataOutputStream.writeBytes(parameterValue + lineEnd);
}
/**
* Write data file into header and data output stream.
*
* @param dataOutputStream data output stream handle data parsing
* @param dataFile data byte as DataPart from collection
* @param inputName name of data input
* @throws IOException
*/
private void buildDataPart(DataOutputStream dataOutputStream, DataPart dataFile, String inputName) throws IOException {
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" +
inputName + "\"; filename=\"" + dataFile.getFileName() + "\"" + lineEnd);
if (dataFile.getType() != null && !dataFile.getType().trim().isEmpty()) {
dataOutputStream.writeBytes("Content-Type: " + dataFile.getType() + lineEnd);
Log.d("Name", inputName + " " + dataFile.getFileName());
}
dataOutputStream.writeBytes(lineEnd);
ByteArrayInputStream fileInputStream = new ByteArrayInputStream(dataFile.getContent());
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(lineEnd);
}
/**
* Simple data container use for passing byte file
*/
public class DataPart {
private String fileName;
private byte[] content;
private String type;
/**
* Default data part
*/
public DataPart() {
}
/**
* Constructor with data.
*
* @param name label of data
* @param data byte data
*/
public DataPart(String name, byte[] data) {
fileName = name;
content = data;
}
/**
* Constructor with mime data type.
*
* @param name label of data
* @param data byte data
* @param mimeType mime data like "image/jpeg"
*/
public DataPart(String name, byte[] data, String mimeType) {
fileName = name;
content = data;
type = mimeType;
}
/**
* Getter file name.
*
* @return file name
*/
public String getFileName() {
return fileName;
}
/**
* Setter file name.
*
* @param fileName string file name
*/
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* Getter content.
*
* @return byte file data
*/
public byte[] getContent() {
return content;
}
/**
* Setter content.
*
* @param content byte file data
*/
public void setContent(byte[] content) {
this.content = content;
}
/**
* Getter mime type.
*
* @return mime type
*/
public String getType() {
return type;
}
/**
* Setter mime type.
*
* @param type mime type
*/
public void setType(String type) {
this.type = type;
}
}
}
Upvotes: 3
Views: 5087
Reputation: 3883
So, understanding what exactly is wrong would require some debugging, what I can share is from my experience.
disclaimer: I myself have moved on to using Retrofit2.0, and would recommend anyone to do the same, as it is quicker, easier to use, and significantly more documented (and documented well for that matter), this is from my prior experience with Volley
In my case I needed to upload an image together with a text field, the MultiPart class I built was a little different:
The main thing is I used MultiPartEntityBuilder
which simplifies stuff, and the class itself extends Request<String>
since that was the response type (which is easier to handle usually), so this would be the constructor:
public MultiPartImageRequest(String url, String filePath, Response.Listener<String> listener, Response.ErrorListener errorListener)
{
super(Method.POST, url, errorListener);
this.listener = listener;
this.entityBuilder = MultipartEntityBuilder.create();
this.httpEntity = new MultipartEntity();
setShouldCache(false);
this.file = new File(filePath);
this.entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
this.entityBuilder.setBoundary(generateBoundary());
buildMultipartImageEntity();
}
Created the boundary myself, and built the entity with a binary body containing the file, and my text field, giving the the appropriate content type, like so:
private void buildMultipartImageEntity()
{
try
{
ContentType contentType = ContentType.create("image/jpeg");
entityBuilder.addBinaryBody("userfile", file, contentType, file.getName());
entityBuilder.addTextBody("userid", String.valueOf(SettingsManager.getUserID()), ContentType.TEXT_PLAIN);
httpEntity = entityBuilder.build();
}
catch (Exception e)
{
VolleyLog.e("UnsupportedEncodingException");
}
}
private static final char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private String generateBoundary()
{
StringBuilder buffer = new StringBuilder();
Random rand = new Random();
int count = rand.nextInt(11) + 30;
for (int i = 0; i < count; ++i)
{
buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
}
return buffer.toString();
}
from there, the getBodyContentType and getBody use the httpEntity object:
@Override
public String getBodyContentType()
{
return httpEntity.getContentType().getValue();
}
@Override
public byte[] getBody() throws AuthFailureError
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try
{
httpEntity.writeTo(bos);
}
catch (IOException e)
{
VolleyLog.e("IOException writing to ByteArrayOutputStream");
}
return bos.toByteArray();
}
And another thing I found was the need to parse the response in utf-8, but that might not be needed in your case (depending on the response):
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response)
{
try
{
String responseBody = new String(response.data, "utf-8");
return (Response.success(responseBody, getCacheEntry()));
}
catch (UnsupportedEncodingException e)
{
VolleyLog.e("UnsupportedEncodingException");
return (Response.success("Uploaded, problem with url return", getCacheEntry()));
}
}
Hope this Helps in any way.
Upvotes: 1