2011-01-22 4 views
35

При использовании HttpURLConnection необходимо ли закрывать InputStream, если мы не «получаем» и не используем его?Безопасное использование HttpURLConnection

i.e. это сейф?

HttpURLConnection conn = (HttpURLConnection) uri.getURI().toURL().openConnection(); 
conn.connect(); 
// check for content type I don't care about 
if (conn.getContentType.equals("image/gif") return; 
// get stream and read from it 
InputStream is = conn.getInputStream(); 
try { 
    // read from is 
} finally { 
    is.close(); 
} 

Во-вторых, это безопасно закрыть InputStream , прежде чем все это содержание было полностью прочитать?

Есть ли риск оставить базовое гнездо в состоянии ESTABLISHED или даже CLOSE_WAIT?

ответ

25

это безопасно закрыть InputStream , прежде чем все это содержание было чтения

Вы должны прочитать все данные во входном потоке, прежде чем закрыть так что базовое TCP-соединение будет кэшироваться. Я прочитал, что это не должно требоваться в последней Java, но всегда было поручено прочитать весь ответ для повторного использования соединения.

Проверить этот пост: keep-alive in java6

+0

Очень интересно. Этот вопрос на самом деле является фоном для проблемы, с которой я сталкиваюсь, когда я вижу LOTS из сокетов CLOSE_WAIT для одного и того же IP-адреса, но из-за кэширования (я не вызываю URLConnection.disconnect() явно). Я ожидаю, что будет только один, который следует использовать повторно. – Joel

+3

@Joel: вызывая 'HttpUrlConnection.disconnect()' базовый tcp-сокет закрыт. Закрывая входной поток, базовый tcp-сокет объединяется для последующего повторного использования.Единственное предостережение состоит в том, что весь ответ (ИЛИ весь ответ об ошибке) должен быть прочитан из входного потока, чтобы кеширование tcp было кэшировано. Это всегда было рекомендовано, несмотря на то, что вам действительно нужны все данные из потока. Проверьте сообщение в своем ответе – Cratylus

+0

Что делать, если вы не читаете ЛЮБОЙ данных, как в моем первом примере? Я предполагаю, что вам все равно нужно закрыть IS, но он все равно не будет кэшироваться, если данные не будут прочитаны, но все равно закрыто. – Joel

1

При использовании HttpURLConnection необходимо ли закрывать InputStream, если мы не «получаем» и не используем его?

Да, его всегда необходимо закрыть.

i.e. это сейф?

Не 100%, вы рискуете получить NPE. Безопаснее является:

InputStream is = null; 
try { 
    is = conn.getInputStream() 
    // read from is 
} finally { 
    if (is != null) { 
     is.close(); 
    } 
} 
+1

Второй вопрос касался основного состояния сокета, я преднамеренно опубликовал неполный фрагмент относительно полной безопасности кода во время выполнения. Я действительно хочу знать, есть ли опасность сокета, оставшегося в CLOSE_WAIT или ESTABLISED, закрывая сокет до того, как будет прочитано все содержимое, – Joel

+0

Или 'IOUtils.closeQuietly (is)' – Kirby

6

Если вы действительно хотите, чтобы убедиться, что соединение близко вы должны вызвать conn.disconnect().

Открытые соединения, которые вы наблюдаете, связаны с подключением HTTP 1.1 (также известный как HTTP Persistent Connections). Если сервер поддерживает HTTP 1.1 и не отправляет Connection: close в заголовок ответа, Java не сразу закрывает подстилающее TCP-соединение при закрытии входного потока. Вместо этого он держит его открытым и пытается повторно использовать его для следующего HTTP-запроса на тот же сервер.

Если вы не хотите этого вообще можно установить системное свойство http.keepAlive ложь:

System.setProperty("http.keepAlive","false"); 
+1

Спасибо. Предполагая, что соединение не используется, вы знаете, как долго он кэшируется до закрытия, и есть ли способ контролировать этот период таймаута? – Joel

14

Вот некоторая информация, касающаяся Keep-Alive кэш. Вся эта информация относится к Java 6, но, вероятно, также верна для многих предыдущих и более поздних версий.

Из того, что я могу сказать, код сводится к:

  1. Если удаленный сервер посылает «Keep-Alive» заголовок со значением «тайм-аут», который может быть разобрано как положительное целое число, что для таймаута используется количество секунд.
  2. Если удаленный сервер отправляет заголовок «Keep-Alive», но он не имеет значения «timeout», которое может быть проанализировано как положительное целое число и «usingProxy» имеет значение true, тогда таймаут составляет 60 секунд.
  3. Во всех остальных случаях тайм-аут составляет 5 секунд.

Эта логика делится между двумя местами: вокруг линии 725 sun.net.www.http.HttpClient (в методе «parseHTTPHeader»), и вокруг линии 120 sun.net.www.http.KeepAliveCache (в методе «положить»).


Итак, есть два способа управления таймаут:

  1. управления удаленным сервером и настроить его, чтобы отправить заголовок Keep-Alive с собственным полем таймаута
  2. Измените JDK исходный код и создать свой собственный.

Можно было бы подумать, что можно было бы поменять кажущееся произвольное пятисекундное умолчание без перекомпиляции внутренних классов JDK, но это не так. В 2005 году был подан документ bug с просьбой об этом, но ВС отказалась его предоставить.

+3

Хорошее исследование плохо документированного предмета. Спасибо, что поделились. –

31

Согласно http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-keepalive.html и исходный код OpenJDK.

(Когда Keepalive == TRUE)

Если клиент называется HttpURLConnection.getInputSteam(). закрыть(), более поздний вызов HttpURLConnection. Отсортировать() будет NOT close Сокет. то есть гнездо повторно используется (кэшировано)

Если клиент не вызывает функцию close(), вызов disconnect() закроет InputSteam и закроет Socket.

Для повторного использования гнезда, просто позвоните в InputStream Закрыть(). Не называть HttpURLConnection отключить().

2

Вы также должны закрыть поток ошибок, если запрос HTTP не удается (ничего, кроме 200):

try { 
    ... 
} 
catch (IOException e) { 
    connection.getErrorStream().close(); 
} 

Если вы не сделаете это, все запросы, которые не возвращают 200 (например, тайм-аут) будет утечка один разъем. См. http://scotte.github.io/2015/01/httpurlconnection-socket-leak/ для более подробной информации.

+1

Не совсем уверен в этом - этот последний исходный код (JDK 8u74) читает 'public InputStream getErrorStream() { return null; } ' – FelixJongleur42

Смежные вопросы