Retrofit – Solicitud de múltiples partes: el parámetro ‘archivo’ de MultipartFile requerido no está presente

Estoy tratando de enviar archivos al servidor usando Retrofit2. Hago todo de acuerdo con la documentación, pero siempre obtengo un error de 400 servidores.

Intento hacer esto:

RequestBody body = RequestBody.create(MediaType.parse("image/png"), photo); //.......... @Multipart @POST(ADD_PHOTO) Observable addPhoto(@Part("file") RequestBody file); 

… y así:

 MultipartBody.Part part = MultipartBody.Part.createFormData("file", "file", body); //........... @Multipart @POST(ADD_PHOTO) Observable addPhoto(@Part("file") MultipartBody.Part files); 

no importa El resultado es siempre el mismo “Solicitud de múltiples partes: el parámetro ‘Multipart File’ requerido no está presente” – respuesta del servidor.

¡Creo que Spring en el servidor no funciona bien, pero hago el código equivalente en Swift (iOS) y funciona! Aquí el Servidor ve esta parte del ‘archivo’.

 Alamofire.upload(method, endpoint, headers: headers, multipartFormData: { multipartFormData in multipartFormData.appendBodyPart(fileURL: self.filePath!, name: "file") } 

Ahora quiero que funcione en Android con Retrofit. Pero incluso miro en los registros de las solicitudes de Retrofit y, de hecho, no veo ningún texto de ‘archivo’ en los registros.

¿Qué está mal con eso?

Puedes probar el siguiente código de muestra. En esta aplicación de demostración, cargaremos una foto después de seleccionar desde la Galería. ¡Espero eso ayude!

archivo build.gradle :

 dependencies { ... compile 'com.squareup.retrofit2:retrofit:2.0.1' compile 'com.squareup.retrofit2:converter-gson:2.0.1' ... } 

Archivo WebAPIService.java :

 public interface WebAPIService { @Multipart @POST("/api/fileupload") Call postFile(@Part MultipartBody.Part file, @Part("description") RequestBody description); } 

Archivo FileActivity.java :

 ... import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; public class FileActivity extends AppCompatActivity { private final Context mContext = this; private final String API_URL_BASE = "http://serverip:port"; private final String LOG_TAG = "BNK"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_file); selectImage(); // selects a photo from Gallery } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_OK && requestCode == 100) { Uri fileUri = data.getData(); if (fileUri != null) { uploadFile(fileUri); // uploads the file to the web service } } } private void uploadFile(Uri fileUri) { String filePath = getRealPathFromUri(fileUri); if (filePath != null && !filePath.isEmpty()) { File file = new File(filePath); if (file.exists()) { Retrofit retrofit = new Retrofit.Builder() .baseUrl(API_URL_BASE) .build(); WebAPIService service = retrofit.create(WebAPIService.class); // creates RequestBody instance from file RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file); // MultipartBody.Part is used to send also the actual filename MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile); // adds another part within the multipart request String descriptionString = "Sample description"; RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString); // executes the request Call call = service.postFile(body, description); call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { Log.i(LOG_TAG, "success"); } @Override public void onFailure(Call call, Throwable t) { Log.e(LOG_TAG, t.getMessage()); } }); } } } private void selectImage() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); startActivityForResult(intent, 100); } public String getRealPathFromUri(final Uri uri) { // DocumentProvider if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(mContext, uri)) { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; if ("primary".equalsIgnoreCase(type)) { return Environment.getExternalStorageDirectory() + "/" + split[1]; } } // DownloadsProvider else if (isDownloadsDocument(uri)) { final String id = DocumentsContract.getDocumentId(uri); final Uri contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); return getDataColumn(mContext, contentUri, null, null); } // MediaProvider else if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } final String selection = "_id=?"; final String[] selectionArgs = new String[]{ split[1] }; return getDataColumn(mContext, contentUri, selection, selectionArgs); } } // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { // Return the remote address if (isGooglePhotosUri(uri)) return uri.getLastPathSegment(); return getDataColumn(mContext, uri, null, null); } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; } private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = { column }; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int index = cursor.getColumnIndexOrThrow(column); return cursor.getString(index); } } finally { if (cursor != null) cursor.close(); } return null; } private boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } private boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } private boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } private boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); } } 

En mi caso, el servidor no estaba manejando algunos encabezados que está enviando la actualización. Es por eso que tuve que eliminar el encabezado inútil de las solicitudes de actualización. Creé una función de interfaz como esta:

 @POST("my/files/photo/") Call uploadPhoto(@Header("Content-Type") String contentType, @Header("Authorization") String auth, @Body MultipartBody body); 

Y llámalo así:

 ApiClient.ApiInterface client = ApiClient.getClient(); File file = new File(getPathFromUri(fileUri)); RequestBody fileBody = RequestBody.create(MediaType.parse(getContentResolver().getType(fileUri)), file); MultipartBody body = new MultipartBody.Builder().addFormDataPart("file-type", "profile") .addFormDataPart("photo", "image.png", fileBody) .build(); client.uploadPhoto("multipart/form-data; boundary=" + body.boundary(), PrefManager.getInstance().getToken(), body); 

vea los detalles aquí: Cargue la imagen en el servidor usando la modificación 2