Android HttpUrlConnection: Post Multipart

Я пытаюсь опубликовать тестовый файл для сервлета весеннего отдыха, развернутого на tomcat с помощью Android. Я развиваюсь на Android 4.1.2, но я проверял ту же проблему в 4.0.3.

Проблема в том, что для загрузки файла требуется очень много времени (около 70 секунд для файла 4 МБ), а также в локальной сети. Время равносильно использованию 3g-соединения. Я исключил, что это может быть проблема с сервером: выполнение одного и того же вызова с помощью curl занимает 1/2 секунды, а использование apache в качестве исходных результатов одинаково.

Использование HttpClient отлично работает.

Я использую Spring Android RestClient 1.0.1.RELEASE и, учитывая версию Android и тот факт, что я не переопределяю поведение по умолчанию, он использует HttpUrlConnection вместо HttpClient для создания HTTP-запросов.

Я также применил свой пользовательский ClientHttpRequestFactory, чтобы манипулировать некоторыми деталями SSL-соединения, и я определил свою собственную реализацию ClientHttpRequestInterceptor, чтобы изменить заголовок аутентификации.

Я также установил setBufferRequestBody(false) , чтобы избежать исключения OutOfMemoryException в больших файлах. Но это свойство не влияет на требуемое время.

MyClientHttpRequestFactory :

 public class MyClientHttpRequestFactory extends SimpleClientHttpRequestFactory{ @Override protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { super.prepareConnection(connection, httpMethod); connection.setConnectTimeout(240 * 1000); connection.setReadTimeout(240 * 1000); if ("post".equals(httpMethod.toLowerCase())) { setBufferRequestBody(false); }else { setBufferRequestBody(true); } } @Override protected HttpURLConnection openConnection(URL url, Proxy proxy) throws IOException { final HttpURLConnection httpUrlConnection = super.openConnection(url, proxy); if (url.getProtocol().toLowerCase().equals("https") && settings.selfSignedCert().get()) { try { ((HttpsURLConnection)httpUrlConnection).setSSLSocketFactory(getSSLSocketFactory()); ((HttpsURLConnection)httpUrlConnection).setHostnameVerifier(new NullHostnameVerifier()); } catch (Exception e) { MyLog.e(LOG_TAG, "OpenConnection", e); } } return httpUrlConnection; } 

MyClientHttpRequestInterceptor :

 public class MyClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { final HttpHeaders headers = request.getHeaders(); headers.setAuthorization(new HttpBasicAuthentication( settings.username().get(), settings.password().get())); if (settings.enable_gzip().get()) { headers.setAcceptEncoding(ContentCodingType.GZIP); } return execution.execute(request, body); } } 

И вот мой звонок отдыха:

 List<ClientHttpRequestInterceptor> interceptors = Arrays.asList((ClientHttpRequestInterceptor)myClientHttpRequestInterceptor); RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new FormHttpMessageConverter()); restTemplate.getMessageConverters().add(new StringHttpMessageConverter()); restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); restTemplate.setInterceptors(interceptors); MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>(); parts.add("file", new FileSystemResource("/sdcard/test/4MB_file")); HttpEntity<MultiValueMap> requestEntity = new HttpEntity<MultiValueMap>(parts); restTemplate.exchange(myUrl, HttpMethod.POST, requestEntity, Integer.class).getBody(); } 

Рассматривая исходный код Spring Android, следующие строки кода, которые проходит мой запрос:

 public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); prepareConnection(connection, httpMethod.name()); if (this.bufferRequestBody) { return new SimpleBufferingClientHttpRequest(connection); } else { return new SimpleStreamingClientHttpRequest(connection, this.chunkSize); } } 

Из-за этого. this.bufferRequestBody является false , return new SimpleStreamingClientHttpRequest(connection, this.chunkSize); (С chunkSize = 0)

 SimpleStreamingClientHttpRequest(HttpURLConnection connection, int chunkSize) { this.connection = connection; this.chunkSize = chunkSize; // Bugs with reusing connections in Android versions older than Froyo (2.2) if (olderThanFroyo) { System.setProperty("http.keepAlive", "false"); } } 

а потом:

 ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod()); delegate.getHeaders().putAll(request.getHeaders()); if (body.length > 0) { FileCopyUtils.copy(body, delegate.getBody()); } return delegate.execute(); 

Отсюда вся андроидная подсистема, я думаю ..

Я сбросил трафик tcp и проанализировал его:

 POST /urlWherePost HTTP/1.1 Content-Type: multipart/form-data;boundary=nKwsP85ZyyzSDuAqozCTuZOSxwF1jLAtd0FECUPF Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxxx= Accept-Encoding: gzip User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.1.2; sdk Build/MASTER) Host: 192.168.168.225:8080 Connection: Keep-Alive Content-Length: 4096225 --nKwsP85ZyyzSDuAqozCTuZOSxwF1jLAtd0FECUPF Content-Disposition: form-data; name="file"; filename="4MB_file" Content-Type: application/octet-stream Content-Length: 4096000 

Я попытался воссоздать аналогичный запрос с curl:

 curl --verbose -H "Connection: Keep-Alive" -H "Content-Type: multipart/form-data" -H "Accept-Encoding: gzip" -H "Content-Disposition: form-data; name=\"file\"; filename=\"4MB_file\"" -H "Content-Type: application/octet-stream" --user xxx:xxx -X POST --form file=@4MB_file http://192.168.168.225:8080/urlWherePost 

Но с curl сообщение нормально.

Публикация json-данных не является проблемой (возможно, небольшой размер тела). Но когда я пытаюсь отправить «большие» файлы, время увеличивается.

Глядя в оболочку DDMS, в Network Statistics я также обнаружил, что пропускная способность сети никогда не превышает 250 КБ в TX. Кажется, что есть сапог, но как его расследовать? Где я могу посмотреть, какой параметр я могу изменить?

Спасибо за любое предложение!

Solutions Collecting From Web of "Android HttpUrlConnection: Post Multipart"

Вы пытались использовать метод MultipartEntity? У меня была такая же проблема при загрузке большого количества данных JSON с сервера, но я переключился на этот метод и поймал все данные, которые предоставил мне сервер.

 HttpClient httpclient = new DefaultHttpClient(); HttpPost httppost = new HttpPost("http://myurl.com"); try { MultipartEntity entity = new MultipartEntity(); entity.addPart("type", new StringBody("json")); entity.addPart("data", new JSONObject(data)); httppost.setEntity(entity); HttpResponse response = httpclient.execute(httppost); } catch (ClientProtocolException e) { } catch (IOException e) { } catch (JSONException e){ } 

Чтобы загрузить большие файлы, вы можете использовать эту библиотеку android-async-http

Для простой и простой в использовании, я рекомендую этот lib https://github.com/koush/ion . Я использую его в своем проекте, и он отлично работает.