vert-x3 / vertx-web

HTTP web applications for Vert.x
Apache License 2.0
1.11k stars 534 forks source link

HttpRequest.sendStream() doesn't handle exceptions from underlying HttpClientRequest #1447

Open guidbona opened 5 years ago

guidbona commented 5 years ago

See block starting at

https://github.com/vert-x3/vertx-web/blob/6b6dd6d48e2ca65ff40f95754d067755c52a6548/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpContext.java#L470

Happens setting a timeout on the request, but I guess all other exceptions are lost as well. This means the handler of the HttpRequest is never called.

guidbona commented 5 years ago

It seems that the issue:

java-stranger commented 4 years ago

Also affects sendForm() and sendStream().

I found the reason: HttpContext uses pipe mechanism to write the body:

if (body instanceof ReadStream<?>) {
        ReadStream<Buffer> stream = (ReadStream<Buffer>) body;
        if (request.headers == null || !request.headers.contains(HttpHeaders.CONTENT_LENGTH)) {
          req.setChunked(true);
        }
        stream.pipeTo(req, ar -> {
          if (ar.failed()) {
            responseFuture.tryFail(ar.cause());
            req.reset();
          }
        });

In the Pipe constructor, endHandler of the stream is set to complete the future:

    src.endHandler(result::tryComplete);
    src.exceptionHandler(result::tryFail);

Later, when we start the pipe operation in public void to(), there's an exception handler set for the request destination:

    ws.exceptionHandler(err -> result.tryFail(new WriteException(err)));

However, it is called too late, when the result future is already completed, because we finished reading the stream! That's why all exceptions in the request are ignored.

As a workaround you can use sendJson(), sendJsonObject() or sendBuffer, because they don't use this pipe mechanism to write the request body, and allow the exceptions to be propagated:

} else {
        Buffer buffer;
        if (body instanceof Buffer) {
          buffer = (Buffer) body;
        } else if (body instanceof JsonObject) {
          buffer = Buffer.buffer(((JsonObject)body).encode());
        } else {
          buffer = Buffer.buffer(Json.encode(body));
        }
        req.exceptionHandler(responseFuture::tryFail);
        req.end(buffer);
      }