Альтернативные решения проблемы не срабатывания таймаута при чтении тела ответа в HttpClient
При скачивании файлов через java.net.http.HttpClient (Java 11+) столкнулся с проблемой: при использовании BodyHandlers.ofInputStream() таймаут, заданный через HttpRequest.timeout(), не распространяется на чтение тела ответа. Метод stream.read() блокирует поток навсегда, если предположительно сервер или прокси перестали слать данные, держат соединение открытым и не отправляют ошибки. Если данные перестают поступать в процессе чтения тела, read() висит бесконечно.
На просторах интернета обнаружился вот этот баг‑реквест: https://bugs.openjdk.org/browse/JDK-8258397 Где написано: The request timeout will be cancelled as soon as the response headers have been read. There is currently no timeout covering the reception of the body. Я так понимаю, это значит, что HttpRequest.timeout() покрывает только запрос + получение заголовков. Как только client.send() вернул HttpResponse — таймаут перестаёт действовать. Для BodyHandlers.ofInputStream() тело читается лениво, и read() может висеть вечно. Насколько я могу судить, раз баг не закрыт — в HttpClient просто нет стандартных механизмов, позволяющих решить эту проблему. Пришлось сделать watchdog‑поток на корутинах, который по таймауту закрывает stream, разблокируя read(). Но подозреваю, что должно быть более элегантное решение. Вопрос: есть ли какие‑либо более практичные решения этой проблемы? Возможно, более удачная альтернатива HttpClient? fun execute(uri: URI, proxy: Proxy, path: String) { val result = DownloadResult() val connectTimeoutValue = 10_000L val readTimeoutValue = 10_000L val client = HttpClient.newBuilder() .connectTimeout(Duration.ofMillis(connectTimeoutValue)) .sslContext(SSLContext.getDefault()) .followRedirects(HttpClient.Redirect.NORMAL) .proxy(object : ProxySelector() { override fun select(uri: URI): List<Proxy> { return listOf(proxy) } override fun connectFailed(uri: URI, socketAddress: SocketAddress, ioe: IOException) {} }).build() val request = HttpRequest.newBuilder().uri(uri) .method("GET", HttpRequest.BodyPublishers.noBody()) .timeout(Duration.ofMillis(readTimeoutValue)) //…. .build() val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) try { val contentEncoding = response.headers().firstValue("Content-Encoding").orElse(null) val stream = when (contentEncoding) { "identity", null, "" -> BufferedInputStream(response.body()) "gzip" -> GZIPInputStream(response.body()) else -> throw UnsupportedOperationException("$contentEncoding not supported") } //костыль с дополнительным потоком который прерывает блокирующую операцию по таймауту val watchdogException = AtomicReference<Exception?>(null) val scope = CoroutineScope(Dispatchers.IO) try { val lastReadTime = AtomicLong(System.currentTimeMillis()) scope.launch { while (true) { if (System.currentTimeMillis() — lastReadTime.get() > readTimeoutValue) { watchdogException.set(SocketTimeoutException("Timeout")) try { stream.close() } catch (_: Exception) { } break } delay(1000) } } //конец костыля RandomAccessFile(File(path), "rw").use { fileAccess -> fileAccess.channel.use { channel -> val lock = channel.tryLock() ?: throw IllegalStateException("File is locked: $path") lock.use { val readBuffer = ByteArray(1024) while (true) { val bytesRead = stream.read( readBuffer, 0, readBuffer.size ) // <=== вечное ожидание на блокирующей операции if (bytesRead == -1) break lastReadTime.set(System.currentTimeMillis()) fileAccess.write(readBuffer, 0, bytesRead) } } } } } catch (t: Throwable) { if (watchdogException.get() != null) { result.exception = watchdogException.get() } else { if (t is Exception) result.exception = t else result.exception = RuntimeException(t.message) } } finally { scope.cancel() } } catch (ex: Exception) { result.exception = ex } }
Скорочтение и развитие памяти для детей 5–14 лет. Пробное занятие бесплатно.
Стартум: скорочтение — подробнееЕщё вариант: Как научить ребёнка делать уроки самостоятельноИсточник: ru.stackoverflow.com — ru.stackoverflow.com/questions/1626266/%D0%90%D0%BB%D1%8C%D1%82%D0%B5%D1%80%D0%BD%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B5-%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F-%D0%BF%D1%80%D0%BE%D0%B1%D0%BB%D0%B5%D0%BC%D1%8B-%D0%BD%D0%B5-%D1%81%D1%80%D0%B0%D0%B1%D0%B0%D1%82%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-%D1%82%D0%B0%D0%B9%D0%BC%D0%B0%D1%83%D1%82%D0%B0-%D0%BF%D1%80%D0%B8-%D1%87%D1%82%D0%B5%D0%BD%D0%B8%D0%B8-%D1%82%D0%B5%D0%BB%D0%B0-%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%B0