kike
kike

Reputation: 4463

Retrofit. java.net.ProtocolException: expected * bytes but received *

I'm trying to do a Multipart POST request through Retrofit2, where I upload to the API a custom file.

It randomly fails with this Exception:

W/System.err: java.net.ProtocolException: expected 154 bytes but received 634

Does anyone could put some light on it?

This is my code in the interface:

@Multipart
@POST("recordings/{id}/{rec_id}/")
Call<ResponseBody> uploadRecording(@Path("id") String id, @Path("rec_id") String rec_id, @Part MultipartBody.Part bleFile);

In the constructor:

public ApiConnectionManager(Context con){
    Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .create();

    OkHttpClient.Builder client = new OkHttpClient.Builder();
    HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
    loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
    client.addInterceptor(loggingInterceptor);

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(con.getResources().getString(R.string.api_url)) // API url is hidden
            .addConverterFactory(GsonConverterFactory.create(gson))
            .client(client.build())
            .build();

    this.companyAPI = retrofit.create(CompanyAPI.class);
}

and in the upload method:

private void uploadFile(String id, final File bleFile) {
    MediaType MEDIA_TYPE = MediaType.parse("multipart/mixed");
    RequestBody requestBody = RequestBody.create(MEDIA_TYPE,bleFile);
    MultipartBody.Part partFile = MultipartBody.Part.createFormData("file", bleFile.getName(), requestBody);
    String recordingId = bleFile.getName().replace(".BLE","");
    Call<ResponseBody> call = companyAPI.uploadRecording(id, recordingId, partFile);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            Log.d(TAG+"-Upload "+bleFile.getName(),response.message());
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.d(TAG,"FAILED");
            t.printStackTrace();
        }
    });
}

Upvotes: 12

Views: 10794

Answers (6)

ultra.deep
ultra.deep

Reputation: 1819

In my case, I used HttpUrlConnection for Uploading File/Bitmap/Buffer as Multi-Part protocol uploading...

short Answer :

I removed following line in my code and every things worked fine :

// remove this line 
connection.setFixedLengthStreamingMode(dataToUpload.length);

long Answer :

for multi-part upload we must write some extra data such as boundary, twoHyphens (--) , newLine (\r\n)... Therefore, the length of the data will be longer than the length of the data(file/bitmap/buffered).

By deleting the setFixedLengthStreamingMode in connection request, We can solves this problem.

But, If it is necessary to send this file or data length, we must calculate the total length.

Upvotes: 0

Vladimir Fisher
Vladimir Fisher

Reputation: 2719

It means that file what you're trying to send is still creating and it's size changing. You should finalize your file first and then send.

Upvotes: 0

Jian
Jian

Reputation: 3258

I have encountered the same issue and solved by creating a temporary file before upload it.

In Kotlin.

fun createTemoraryFile(context: Context, uri: Uri): File {
    val inputStream = context.contentResolver.openInputStream(uri)
    val f = createTempFile(
        directory = context.cacheDir
        )
    inputStream.copyTo(f.outputStream())
    return f
}

And after upload finished, I deleted the temporary file.

Upvotes: 2

Rami El-bouhi
Rami El-bouhi

Reputation: 441

I faced this problem when I was trying to upload a recording. I solved it by stopping the recording process before calling the web service to upload the file.

objMediaRecorder.stop();
objMediaRecorder.release();
objMediaRecorder = null;

Upvotes: 4

kike
kike

Reputation: 4463

After studying for a while the problem I've realized that the content of the file was always changing (as it is the output of a sensor).

It means that the file that is checked for the HEAD and the file for the BODY may not contain the same data (therefore different length), which caused the mismatch.

I solved this creating a copy of the file and sending it (the copy) instead of the original file.

Upvotes: 4

user7010102
user7010102

Reputation:

Here is what I use for all my requests and have no problems at all. Let me know if it does not work for you. I am assuming POST name for your file is "file".

In the protocol:

@Multipart
@POST
Call<ResponseBody> request(
        @Url String url, // Request URL
        @PartMap Map<String, String> vars, // POST Strings
        @PartMap Map<String, RequestBody> files // POST Files
);

Construct a call:

Map<String, String> vars = new HashMap<>();
Map<String, RequestBody> files = new HashMap<>();

/** Put a string **/

vars.put("name", "value");

/** Put a file **/

String key = String.format(Locale.US, "file\"; filename=\"%s", file.getName());
RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
files.put(key, requestBody);

/** Construct the call **/

Call<ResponseBody> call = mProtocol.request(url, vars, files);

call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            Log.d("Debug", response.body().string());
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            if (call.isCanceled()) Log.d("Debug", "Call Canceled");
            else Log.d("Debug", "Call Failed: " + t.toString());
        }
});

PS: You can use this piece of code to upload multiple files, since it accepts a map of files rather than a single file.

PS#2: Due to several problems I have had with this method, I had to add the below code to make sure it never sends null or empty map.

if (vars == null) {
    vars = new HashMap<>();
    vars.put("01110011", "01101101"); // put whatever you want
}
if (files == null) files = new HashMap<>();

Upvotes: 1

Related Questions