Я пытаюсь опубликовать тестовый файл в spring сервлете отдыха, развернутом на 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);
    }
}
И вот мой вызов Rest:
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();
Отсюда вся подсистема Android. Думаю..
Я сбросил трафик 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 [email protected]_file 
    http://192.168.168.225:8080/urlWherePost
но с curl сообщение нормально.
Публикация json-данных не является проблемой (возможно, небольшой размер тела). Но когда я пытаюсь отправить "большие" файлы, время увеличивается.
Глядя в оболочку DDMS, по статистике сети я также обнаружил, что пропускная способность сети никогда не превышает 250 КБ в TX. Кажется, что есть загрузочное окно, но как его исследовать? Где я могу посмотреть, какой параметр я могу изменить?
Спасибо за любое предложение!