我正在尝试通过 Retrofit2 执行多部分 POST 请求,在那里我将自定义文件上传到 API。
它随机失败,出现以下异常:
W/System.err: java.net.ProtocolException: expected 154 bytes but received 634
有人可以对此有所了解吗?
这是我在界面中的代码:
@Multipart
@POST("recordings/{id}/{rec_id}/")
Call<ResponseBody> uploadRecording(@Path("id") String id, @Path("rec_id") String rec_id, @Part MultipartBody.Part bleFile);
在构造函数中:
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);
}
并在上传方法中:
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();
}
});
}
在研究了一段时间的问题后,我意识到文件的内容总是在变化(因为它是传感器的输出(。
这意味着检查 HEAD 的文件和 BODY 的文件可能不包含相同的数据(因此长度不同(,这导致了不匹配。
我解决了这个问题,创建了文件的副本并发送它(副本(而不是原始文件。
我在尝试上传录音时遇到了这个问题。我通过在调用 Web 服务上传文件之前停止录制过程来解决它。
objMediaRecorder.stop();
objMediaRecorder.release();
objMediaRecorder = null;
我遇到了同样的问题,并通过在上传之前创建一个临时文件来解决。
在科特林。
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
}
上传完成后,我删除了临时文件。
这是我用于所有请求的内容,完全没有问题。如果它对您不起作用,请告诉我。我假设您文件的 POST 名称是"文件"。
在协议中:
@Multipart
@POST
Call<ResponseBody> request(
@Url String url, // Request URL
@PartMap Map<String, String> vars, // POST Strings
@PartMap Map<String, RequestBody> files // POST Files
);
构造调用:
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:您可以使用这段代码上传多个文件,因为它接受文件映射而不是单个文件。
PS#2:由于我在使用此方法时遇到的几个问题,我不得不添加以下代码以确保它永远不会发送空或空映射。
if (vars == null) {
vars = new HashMap<>();
vars.put("01110011", "01101101"); // put whatever you want
}
if (files == null) files = new HashMap<>();
这意味着您尝试发送的文件仍在创建中,并且大小正在发生变化。您应该先完成文件,然后再发送。
就我而言,我使用 HttpUrlConnection
上传文件/位图/缓冲区作为多部分协议上传...
简短回答:
我删除了代码中的以下行,一切都运行良好:
// remove this line
connection.setFixedLengthStreamingMode(dataToUpload.length);
长答案:
对于多部分上传,我们必须写入一些额外的数据,例如 boundary
, twoHyphens (--)
, newLine (rn)
...因此,数据的长度将比数据的长度(文件/位图/缓冲(长。
通过删除连接请求中的setFixedLengthStreamingMode
,我们可以解决这个问题。
但是,如果需要发送此文件或数据长度,我们必须计算总长度。