VolleyMultipartRequest For Image Upload

Jyotishgher Astrology
By -
0

 VolleyMultipartRequest For Image Upload From Android App To PHP API

The 400 Bad Request HTML error is generated directly by the web server (Apache/Nginx) before your PHP script ever runs.

This occurs because Volley's StringRequest sends parameters using application/x-www-form-urlencoded. When you encode an image to a large Base64 string, Volley URL-encodes it, creating a massive continuous form string. Web servers reject oversized URL-encoded POST field payloads with an HTTP 400 error.

VolleyMultipartRequest For Image Upload


Root Causes

  • Excessive Form Body Size: Apache has strict default limits for single POST form parameters (LimitRequestBody and field size limits).

  • Base64 URL-Encoding Bloat: Converting binary data to Base64 increases its size by ~33%. URL-encoding that Base64 string expands it even further.

Use Multipart Volley Request (Best Practice)

For handling binary files without Base64 overhead, implement a custom Volley MultipartRequest class to send image files directly via multipart/form-data.Since a 211 KB file payload (~280 KB Base64) is well within server POST size limits, the HTTP 400 Bad Request is no longer caused by payload size. Instead, it is caused by ModSecurity/WAF blocking Base64 inside form data, or hidden non-ASCII characters in the API URL.

Step 1: Create VolleyMultipartRequest.java

package nanoakhi.rcfinternal.urgency;


import com.android.volley.AuthFailureError;

import com.android.volley.NetworkResponse;

import com.android.volley.Request;

import com.android.volley.Response;

import com.android.volley.toolbox.HttpHeaderParser;


import java.io.ByteArrayOutputStream;

import java.io.DataOutputStream;

import java.io.IOException;

import java.util.Map;


public abstract class VolleyMultipartRequest extends Request<String> {


    private final Response.Listener<String> mListener;

    private final String boundary = "apiclient-" + System.currentTimeMillis();


    public VolleyMultipartRequest(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener) {

        super(method, url, errorListener);

        this.mListener = listener;

    }


    @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 {

            Map<String, String> params = getParams();

            if (params != null && params.size() > 0) {

                for (Map.Entry<String, String> entry : params.entrySet()) {

                    dos.writeBytes("--" + boundary + "\r\n");

                    dos.writeBytes("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n");

                    dos.writeBytes(entry.getValue() + "\r\n");

                }

            }


            Map<String, DataPart> data = getByteData();

            if (data != null && data.size() > 0) {

                for (Map.Entry<String, DataPart> entry : data.entrySet()) {

                    DataPart dp = entry.getValue();

                    dos.writeBytes("--" + boundary + "\r\n");

                    dos.writeBytes("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"; filename=\"" + dp.getFileName() + "\"\r\n");

                    dos.writeBytes("Content-Type: " + dp.getType() + "\r\n\r\n");

                    dos.write(dp.getContent());

                    dos.writeBytes("\r\n");

                }

            }


            dos.writeBytes("--" + boundary + "--\r\n");

            return bos.toByteArray();

        } catch (IOException e) {

            e.printStackTrace();

            return null;

        }

    }


    protected abstract Map<String, DataPart> getByteData() throws AuthFailureError;


    @Override

    protected Response<String> parseNetworkResponse(NetworkResponse response) {

        try {

            String utf8String = new String(response.data, HttpHeaderParser.parseCharset(response.headers, "utf-8"));

            return Response.success(utf8String, HttpHeaderParser.parseCacheHeaders(response));

        } catch (Exception e) {

            return Response.error(new com.android.volley.VolleyError(response));

        }

    }


    @Override

    protected void deliverResponse(String response) {

        mListener.onResponse(response);

    }


    public static class DataPart {

        private String fileName;

        private byte[] content;

        private String type;


        public DataPart(String fileName, byte[] content, String type) {

            this.fileName = fileName;

            this.content = content;

            this.type = type;

        }


        public String getFileName() { return fileName; }

        public byte[] getContent() { return content; }

        public String getType() { return type; }

    }

}

Step 2: Read File via $_FILES in PHP Add this part in PHP API

if (isset($_FILES['URG_IMG']) && $_FILES['URG_IMG']['error'] == UPLOAD_ERR_OK) {

    $image_data = file_get_contents($_FILES['URG_IMG']['tmp_name']);

}

Updated Android Front-End (submitCertificateWithImage) Let suppose you made a method 

private void submitCertificateWithImage() {

    if (!validateFields()) {

        return;

    }

    progressDialog.show();


    VolleyMultipartRequest multipartRequest = new VolleyMultipartRequest(

            Request.Method.POST,

            urgencyDtlEntry_API,

            response -> {

                progressDialog.dismiss();

                try {

                    Log.d("API_RESPONSE", response);

                    JSONObject obj = new JSONObject(response);

                    boolean success = obj.getBoolean("success");

                    String msg = obj.getString("message");


                    Toast.makeText(new_certificate_request.this, msg, Toast.LENGTH_LONG).show();


                    if (success) {

                        finish();

                    }

                } catch (Exception e) {

                    e.printStackTrace();

                    Toast.makeText(new_certificate_request.this, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show();

                }

            },

            error -> {

                progressDialog.dismiss();

                String errorMsg = "Network Error";

                if (error.networkResponse != null && error.networkResponse.data != null) {

                    try {

                        errorMsg = new String(error.networkResponse.data, "UTF-8");

                    } catch (Exception e) {

                        errorMsg = error.getMessage();

                    }

                }

                Log.e("API_ERROR", "Error: " + errorMsg);

                Toast.makeText(new_certificate_request.this, "Error: " + errorMsg, Toast.LENGTH_LONG).show();

            }

    ) {

        @Override

        protected Map<String, String> getParams() {

            Map<String, String> params = new HashMap<>();


            // Standard text fields

            params.put("PL_NUMBER", etPLNumber.getText().toString().trim());

            // Priority

            int selectedPriorityId = rgPriority.getCheckedRadioButtonId();

            String priority = "NORMAL";

            if (selectedPriorityId == R.id.rbUrgent) {

                priority = "URGENT";

            } else if (selectedPriorityId == R.id.rbCritical) {

                priority = "CRITICAL";

            }

            params.put("PRIORITY", priority);

            params.put("FORWARD_TO", selectedEmpNo);

            params.put("WHATSAPP", selectedWhatsapp);


            return params;

        }


        @Override

        protected Map<String, DataPart> getByteData() {

            Map<String, DataPart> params = new HashMap<>();


            if (imagePath != null) {

                try {

                    File imageFile = new File(imagePath);

                    if (imageFile.exists()) {

                        BitmapFactory.Options options = new BitmapFactory.Options();

                        options.inSampleSize = 2;

                        Bitmap bitmap = BitmapFactory.decodeFile(imagePath, options);


                        if (bitmap != null) {

                            ByteArrayOutputStream baos = new ByteArrayOutputStream();

                            bitmap.compress(Bitmap.CompressFormat.JPEG, 70, baos);

                            byte[] imageBytes = baos.toByteArray();


                            // Attach binary data directly without Base64 encoding

                            params.put("URG_IMG", new DataPart("urgency_image.jpg", imageBytes, "image/jpeg"));

                            Log.d("MULTIPART", "Binary image size: " + imageBytes.length + " bytes");

                        }

                    }

                } catch (Exception e) {

                    e.printStackTrace();

                    Log.e("MULTIPART_ERROR", "Error preparing image binary: " + e.getMessage());

                }

            }


            return params;

        }

    };


    multipartRequest.setRetryPolicy(new DefaultRetryPolicy(

            30000,

            DefaultRetryPolicy.DEFAULT_MAX_RETRIES,

            DefaultRetryPolicy.DEFAULT_BACKOFF_MULT

    ));


    Volley.newRequestQueue(this).add(multipartRequest);

}

No Base64 Overhead: Converts raw JPEG bytes into standard stream boundaries (multipart/form-data), which avoids WAF / Apache 400 rejection limits

PHP Temporary Storage: PHP populates the uploaded file into $_FILES['URG_IMG']['tmp_name']

Oracle Compatibility: file_get_contents($_FILES['URG_IMG']['tmp_name']) returns the exact byte stream expected by Oracle's $blob->save($image_data) method.

Tags:

Post a Comment

0Comments

Post a Comment (0)