= Vert.x Web Client
:toc: left
:lang: ruby
:ruby: ruby

Vert.x Web Client is an asynchronous HTTP and HTTP/2 client.

The Web Client makes easy to do HTTP request/response interactions with a web server, and provides advanced
features like:

* Json body encoding / decoding
* request/response pumping
* request parameters
* unified error handling
* form submissions

The web client does not deprecate the Vert.x Core `link:../../yardoc/Vertx/HttpClient.html[HttpClient]`, indeed it is based on
this client and inherits its configuration and great features like pooling, HTTP/2 support, pipelining support, etc...
The `link:../../yardoc/Vertx/HttpClient.html[HttpClient]` should be used when fine grained control over the HTTP
requests/responses is necessary.

The web client does not provide a WebSocket API, the Vert.x Core `link:../../yardoc/Vertx/HttpClient.html[HttpClient]` should
be used. It also does not handle cookies at the moment.

== Using the web client

To use Vert.x Web Client, add the following dependency to the _dependencies_ section of your build descriptor:

* Maven (in your `pom.xml`):

[source,xml,subs="+attributes"]
----
<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-web-client</artifactId>
 <version>3.6.0.CR2</version>
</dependency>
----

* Gradle (in your `build.gradle` file):

[source,groovy,subs="+attributes"]
----
dependencies {
 compile 'io.vertx:vertx-web-client:3.6.0.CR2'
}
----

== Re-cap on Vert.x core HTTP client

Vert.x Web Client uses the API from Vert.x core, so it's well worth getting familiar with the basic concepts of using
`link:../../yardoc/Vertx/HttpClient.html[HttpClient]` using Vert.x core, if you're not already.

== Creating a web client

You create an `link:../../yardoc/VertxWebClient/WebClient.html[WebClient]` instance with default options as follows

[source,ruby]
----
require 'vertx-web-client/web_client'
client = VertxWebClient::WebClient.create(vertx)

----

If you want to configure options for the client, you create it as follows

[source,ruby]
----
require 'vertx-web-client/web_client'
options = {
  'userAgent' => "My-App/1.2.3"
}
options['keepAlive'] = false
client = VertxWebClient::WebClient.create(vertx, options)

----

Web Client options inherit Http Client options so you can set any one of them.

If your already have an HTTP Client in your application you can also reuse it

[source,ruby]
----
require 'vertx-web-client/web_client'
client = VertxWebClient::WebClient.wrap(httpClient)

----

== Making requests

=== Simple requests with no body

Often, you’ll want to make HTTP requests with no request body. This is usually the case with HTTP GET, OPTIONS
and HEAD requests

[source,ruby]
----
require 'vertx-web-client/web_client'

client = VertxWebClient::WebClient.create(vertx)

# Send a GET request
client.get(8080, "myserver.mycompany.com", "/some-uri").send() { |ar_err,ar|
  if (ar_err == nil)
    # Obtain response
    response = ar

    puts "Received response with status code#{response.status_code()}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

# Send a HEAD request
client.head(8080, "myserver.mycompany.com", "/some-uri").send() { |ar_err,ar|
  if (ar_err == nil)
    # Obtain response
    response = ar

    puts "Received response with status code#{response.status_code()}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

You can add query parameters to the request URI in a fluent fashion

[source,ruby]
----
client.get(8080, "myserver.mycompany.com", "/some-uri").add_query_param("param", "param_value").send() { |ar_err,ar|
  if (ar_err == nil)
    # Obtain response
    response = ar

    puts "Received response with status code#{response.status_code()}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

Any request URI parameter will pre-populate the request

[source,ruby]
----
request = client.get(8080, "myserver.mycompany.com", "/some-uri?param1=param1_value&param2=param2_value")

# Add param3
request.add_query_param("param3", "param3_value")

# Overwrite param2
request.set_query_param("param2", "another_param2_value")

----

Setting a request URI discards existing query parameters

[source,ruby]
----
request = client.get(8080, "myserver.mycompany.com", "/some-uri")

# Add param1
request.add_query_param("param1", "param1_value")

# Overwrite param1 and add param2
request.uri("/some-uri?param1=param1_value&param2=param2_value")

----

=== Writing request bodies

When you need to make a request with a body, you use the same API and call then `sendXXX` methods
that expects a body to send.

Use `link:../../yardoc/VertxWebClient/HttpRequest.html#send_buffer-instance_method[sendBuffer]` to send a buffer body

[source,ruby]
----
# Send a buffer to the server using POST, the content-length header will be set for you
client.post(8080, "myserver.mycompany.com", "/some-uri").send_buffer(buffer) { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

----

Sending a single buffer is useful but often you don't want to load fully the content in memory because
it may be too large or you want to handle many concurrent requests and want to use just the minimum
for each request. For this purpose the web client can send `ReadStream<Buffer>` (e.g a
`link:../../yardoc/Vertx/AsyncFile.html[AsyncFile]` is a ReadStream<Buffer>`) with the `link:../../yardoc/VertxWebClient/HttpRequest.html#send_stream-instance_method[sendStream]` method

[source,ruby]
----
# When the stream len is unknown sendStream sends the file to the server using chunked transfer encoding
client.post(8080, "myserver.mycompany.com", "/some-uri").send_stream(stream) { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

----

The web client takes care of setting up the transfer pump for you. Since the length of the stream is not know
the request will use chunked transfer encoding .

When you know the size of the stream, you shall specify before using the `content-length` header

[source,ruby]
----
fs.open("content.txt", {
}) { |fileRes_err,fileRes|
  if (fileRes_err == nil)
    fileStream = fileRes

    fileLen = "1024"

    # Send the file to the server using POST
    client.post(8080, "myserver.mycompany.com", "/some-uri").put_header("content-length", fileLen).send_stream(fileStream) { |ar_err,ar|
      if (ar_err == nil)
        # Ok
      end
    }
  end
}

----

The POST will not be chunked.

==== Json bodies

Often you’ll want to send Json body requests, to send a `link:unavailable[JsonObject]`
use the `link:../../yardoc/VertxWebClient/HttpRequest.html#send_json_object-instance_method[sendJsonObject]`

[source,ruby]
----
client.post(8080, "myserver.mycompany.com", "/some-uri").send_json_object({
  'firstName' => "Dale",
  'lastName' => "Cooper"
}) { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

----

In Java, Groovy or Kotlin, you can use the `link:../../yardoc/VertxWebClient/HttpRequest.html#send_json-instance_method[sendJson]` method that maps
a POJO (Plain Old Java Object) to a Json object using `link:unavailable#encode-class_method[Json.encode]`
method

[source,ruby]
----
client.post(8080, "myserver.mycompany.com", "/some-uri").send_json(Java::ExamplesWebClientExamples::User.new("Dale", "Cooper")) { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

----

NOTE: the `link:unavailable#encode-class_method[Json.encode]` uses the Jackson mapper to encode the object
to Json.

==== Form submissions

You can send http form submissions bodies with the `link:../../yardoc/VertxWebClient/HttpRequest.html#send_form-instance_method[sendForm]`
variant.

[source,ruby]
----
require 'vertx/multi_map'
form = Vertx::MultiMap.case_insensitive_multi_map()
form.set("firstName", "Dale")
form.set("lastName", "Cooper")

# Submit the form as a form URL encoded body
client.post(8080, "myserver.mycompany.com", "/some-uri").send_form(form) { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

----

By default the form is submitted with the `application/x-www-form-urlencoded` content type header. You can set
the `content-type` header to `multipart/form-data` instead

[source,ruby]
----
require 'vertx/multi_map'
form = Vertx::MultiMap.case_insensitive_multi_map()
form.set("firstName", "Dale")
form.set("lastName", "Cooper")

# Submit the form as a multipart form body
client.post(8080, "myserver.mycompany.com", "/some-uri").put_header("content-type", "multipart/form-data").send_form(form) { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

----

If you want to upload files and send attributes, you can create a `link:../../yardoc/VertxWebCommon/MultipartForm.html[MultipartForm]` and
use `link:../../yardoc/VertxWebClient/HttpRequest.html#send_multipart_form-instance_method[sendMultipartForm]`.

[source,ruby]
----
require 'vertx-web-common/multipart_form'
form = VertxWebCommon::MultipartForm.create().attribute("imageDescription", "a very nice image").binary_file_upload("imageFile", "image.jpg", "/path/to/image", "image/jpeg")

# Submit the form as a multipart form body
client.post(8080, "myserver.mycompany.com", "/some-uri").send_multipart_form(form) { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

----

=== Writing request headers

You can write headers to a request using the headers multi-map as follows:

[source,ruby]
----
request = client.get(8080, "myserver.mycompany.com", "/some-uri")
headers = request.headers()
headers.set("content-type", "application/json")
headers.set("other-header", "foo")

----

The headers are an instance of `link:../../yardoc/Vertx/MultiMap.html[MultiMap]` which provides operations for adding,
setting and removing entries. Http headers allow more than one value for a specific key.

You can also write headers using putHeader

[source,ruby]
----
request = client.get(8080, "myserver.mycompany.com", "/some-uri")
request.put_header("content-type", "application/json")
request.put_header("other-header", "foo")

----

=== Reusing requests

The `link:../../yardoc/VertxWebClient/HttpRequest.html#send-instance_method[send]` method can be called multiple times
safely, making it very easy to configure and reuse `link:../../yardoc/VertxWebClient/HttpRequest.html[HttpRequest]` objects

[source,ruby]
----
get = client.get(8080, "myserver.mycompany.com", "/some-uri")
get.send() { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

# Same request again
get.send() { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

----

Beware though that `link:../../yardoc/VertxWebClient/HttpRequest.html[HttpRequest]` instances are mutable.
Therefore you should call the `link:../../yardoc/VertxWebClient/HttpRequest.html#copy-instance_method[copy]` method before modifying a cached instance.

[source,ruby]
----
get = client.get(8080, "myserver.mycompany.com", "/some-uri")
get.send() { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

# The "get" request instance remains unmodified
get.copy().put_header("a-header", "with-some-value").send() { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  end
}

----

=== Timeouts

You can set a timeout for a specific http request using `link:../../yardoc/VertxWebClient/HttpRequest.html#timeout-instance_method[timeout]`.

[source,ruby]
----
client.get(8080, "myserver.mycompany.com", "/some-uri").timeout(5000).send() { |ar_err,ar|
  if (ar_err == nil)
    # Ok
  else
    # Might be a timeout when cause is java.util.concurrent.TimeoutException
  end
}

----

If the request does not return any data within the timeout period an exception will be passed to the response
handler.

== Handling http responses

When the web client sends a request you always deal with a single async result `link:../../yardoc/VertxWebClient/HttpResponse.html[HttpResponse]`.

On a success result the callback happens after the response has been received

[source,ruby]
----
client.get(8080, "myserver.mycompany.com", "/some-uri").send() { |ar_err,ar|
  if (ar_err == nil)

    response = ar

    puts "Received response with status code#{response.status_code()}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

[CAUTION]
====
By default, a Vert.x Web Client request ends with an error only if something wrong happens at the network level.
In other words, a `404 Not Found` response, or a response with the wrong content type, are *not* considered as failures.
Use <<response-predicates, response predicates>> if you want the Web Client to perform sanity checks automatically.
====

WARNING: Responses are fully buffered, use `link:../../yardoc/VertxWebCommon/BodyCodec.html#pipe-class_method[BodyCodec.pipe]`
to pipe the response to a write stream

=== Decoding responses

By default the web client provides an http response body as a `Buffer` and does not apply
any decoding.

Custom response body decoding can be achieved using `link:../../yardoc/VertxWebCommon/BodyCodec.html[BodyCodec]`:

* Plain String
* Json object
* Json mapped POJO
* `link:../../yardoc/Vertx/WriteStream.html[WriteStream]`

A body codec can decode an arbitrary binary data stream into a specific object instance, saving you the decoding
step in your response handlers.

Use `link:../../yardoc/VertxWebCommon/BodyCodec.html#json_object-class_method[BodyCodec.jsonObject]` To decode a Json object:

[source,ruby]
----
require 'vertx-web-common/body_codec'
client.get(8080, "myserver.mycompany.com", "/some-uri").as(VertxWebCommon::BodyCodec.json_object()).send() { |ar_err,ar|
  if (ar_err == nil)
    response = ar

    body = response.body()

    puts "Received response with status code#{response.status_code()} with body #{body}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

In Java, Groovy or Kotlin, custom Json mapped POJO can be decoded

[source,ruby]
----
require 'vertx-web-common/body_codec'
client.get(8080, "myserver.mycompany.com", "/some-uri").as(VertxWebCommon::BodyCodec.json(Java::ExamplesWebClientExamples::User::class)).send() { |ar_err,ar|
  if (ar_err == nil)
    response = ar

    user = response.body()

    puts "Received response with status code#{response.status_code()} with body #{user.get_first_name()} #{user.get_last_name()}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

When large response are expected, use the `link:../../yardoc/VertxWebCommon/BodyCodec.html#pipe-class_method[BodyCodec.pipe]`.
This body codec pumps the response body buffers to a `link:../../yardoc/Vertx/WriteStream.html[WriteStream]`
and signals the success or the failure of the operation in the async result response

[source,ruby]
----
require 'vertx-web-common/body_codec'
client.get(8080, "myserver.mycompany.com", "/some-uri").as(VertxWebCommon::BodyCodec.pipe(writeStream)).send() { |ar_err,ar|
  if (ar_err == nil)

    response = ar

    puts "Received response with status code#{response.status_code()}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

Finally if you are not interested at all by the response content, the `link:../../yardoc/VertxWebCommon/BodyCodec.html#none-class_method[BodyCodec.none]`
simply discards the entire response body

[source,ruby]
----
require 'vertx-web-common/body_codec'
client.get(8080, "myserver.mycompany.com", "/some-uri").as(VertxWebCommon::BodyCodec.none()).send() { |ar_err,ar|
  if (ar_err == nil)

    response = ar

    puts "Received response with status code#{response.status_code()}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

When you don't know in advance the content type of the http response, you can still use the `bodyAsXXX()` methods
that decode the response to a specific type

[source,ruby]
----
client.get(8080, "myserver.mycompany.com", "/some-uri").send() { |ar_err,ar|
  if (ar_err == nil)

    response = ar

    # Decode the body as a json object
    body = response.body_as_json_object()

    puts "Received response with status code#{response.status_code()} with body #{body}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

WARNING: this is only valid for the response decoded as a buffer.

[[response-predicates]]
=== Response predicates

By default, a Vert.x Web Client request ends with an error only if something wrong happens at the network level.

In other words, you must perform sanity checks manually after the response is received:

[source,ruby]
----
client.get(8080, "myserver.mycompany.com", "/some-uri").send() { |ar_err,ar|
  if (ar_err == nil)

    response = ar

    if (response.status_code() == 200 && response.get_header("content-type").==("application/json"))

      # Decode the body as a json object
      body = response.body_as_json_object()
      puts "Received response with status code#{response.status_code()} with body #{body}"

    else
      puts "Something went wrong #{response.status_code()}"
    end

  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

You can trade flexibility for clarity and conciseness using _response predicates_.

`link:../../yardoc/VertxWebClient/ResponsePredicate.html[Response predicates]` can fail a request when the response does
not match a criteria.

The Web Client comes with a set of out of the box predicates ready to use:

[source,ruby]
----
require 'vertx-web-client/response_predicate'
client.get(8080, "myserver.mycompany.com", "/some-uri").expect(VertxWebClient::ResponsePredicate::SC_SUCCESS).expect(VertxWebClient::ResponsePredicate::JSON).send() { |ar_err,ar|
  if (ar_err == nil)

    response = ar

    # Safely decode the body as a json object
    body = response.body_as_json_object()
    puts "Received response with status code#{response.status_code()} with body #{body}"

  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

You can also create custom predicates when existing predicates don't fit your needs:

[source,ruby]
----
require 'vertx-web-client/response_predicate_result'

# Check CORS header allowing to do POST
methodsPredicate = lambda { |resp|
  methods = resp.get_header("Access-Control-Allow-Methods")
  if (methods != nil)
    if (methods.contains?("POST"))
      return VertxWebClient::ResponsePredicateResult.success()
    end
  end
  return VertxWebClient::ResponsePredicateResult.failure("Does not work")
}

# Send pre-flight CORS request
client.request(:OPTIONS, 8080, "myserver.mycompany.com", "/some-uri").put_header("Origin", "Server-b.com").put_header("Access-Control-Request-Method", "POST").expect(methodsPredicate).send() { |ar_err,ar|
  if (ar_err == nil)
    # Process the POST request now
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

TIP: Response predicates are evaluated _before_ the response body is received. Therefore you can't inspect the response body
in a predicate test function.

==== Predefined predicates

As a convenience, the Web Client ships a few predicates for common uses cases .

For status codes, e.g `link:todo[ResponsePredicate.SC_SUCCESS]` to verify that the
response has a `2xx` code, you can also create a custom one:

[source,ruby]
----
require 'vertx-web-client/response_predicate'
client.get(8080, "myserver.mycompany.com", "/some-uri").expect(VertxWebClient::ResponsePredicate.status(200, 202)).send() { |ar_err,ar|
  # ....
}

----

For content types, e.g `link:todo[ResponsePredicate.JSON]` to verify that the
response body contains JSON data, you can also create a custom one:

[source,ruby]
----
require 'vertx-web-client/response_predicate'
client.get(8080, "myserver.mycompany.com", "/some-uri").expect(VertxWebClient::ResponsePredicate.content_type("some/content-type")).send() { |ar_err,ar|
  # ....
}

----

Please refer to the `link:../../yardoc/VertxWebClient/ResponsePredicate.html[ResponsePredicate]` documentation for a full list of predefined predicates.

ifeval::["ruby" == "java"]
==== Creating custom failures

By default, response predicates (including the predefined ones) use a default error converter which discards
the body and conveys a simple message. You can customize the exception class by changing the error converter:

[source,ruby]
----
Code not translatable
----

Many web APIs provide details in error responses.
For example, the https://developer.marvel.com/docs[Marvel API] uses this JSON object format:

[source,javascript]
----
{
 "code": "InvalidCredentials",
 "message": "The passed API key is invalid."
}
----

To avoid losing this information, it is possible to wait for the response body to be fully received before the error
converter is called:

[source,ruby]
----
Code not translatable
----

WARNING: creating exception in Java can have a performance cost when it captures a stack trace, so you might want
        to create exceptions that do not capture the stack trace. By default exceptions are reported using the
        an exception that does not capture the stack trace.

endif::[]

=== Handling 30x redirections

By default the client follows redirections, you can configure the default behavior in the `link:../dataobjects.html#WebClientOptions[WebClientOptions]`:

[source,ruby]
----
require 'vertx-web-client/web_client'

# Change the default behavior to not follow redirects
client = VertxWebClient::WebClient.create(vertx, {
  'followRedirects' => false
})

----

The client will follow at most `16` requests redirections, it can be changed in the same options:

[source,ruby]
----
require 'vertx-web-client/web_client'

# Follow at most 5 redirections
client = VertxWebClient::WebClient.create(vertx, {
  'maxRedirects' => 5
})

----

== Using HTTPS

Vert.x web client can be configured to use HTTPS in exactly the same way as the Vert.x `link:../../yardoc/Vertx/HttpClient.html[HttpClient]`.

You can specify the behavior per request

[source,ruby]
----

client.get(443, "myserver.mycompany.com", "/some-uri").ssl(true).send() { |ar_err,ar|
  if (ar_err == nil)
    # Obtain response
    response = ar

    puts "Received response with status code#{response.status_code()}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

Or using create methods with absolute URI argument

[source,ruby]
----

client.get_abs("https://myserver.mycompany.com:4043/some-uri").send() { |ar_err,ar|
  if (ar_err == nil)
    # Obtain response
    response = ar

    puts "Received response with status code#{response.status_code()}"
  else
    puts "Something went wrong #{ar_err.get_message()}"
  end
}

----

== Sessions management

Vert.x web offers a web session management facility; to use it, you create a 
`link:unavailable[WebClientSession]` for every user (session) and use it instead of the 
`link:../../yardoc/VertxWebClient/WebClient.html[WebClient]`.

=== Creating a WebSession

You create a `link:unavailable[WebClientSession]` instance as follows

[source,ruby]
----
require 'vertx-web-client/web_client'
client = VertxWebClient::WebClient.create(vertx)
session = Java::IoVertxExtWebClient::WebClientSession.create(client)

----

=== Making requests

Once created, a `link:unavailable[WebClientSession]` can be used instead of a 
`link:../../yardoc/VertxWebClient/WebClient.html[WebClient]` to do HTTP(s) requests and automatically manage any cookies received from the server(s)
you are calling.

=== Setting session level headers

You can set any session level headers to be added to every request as follows:

[source,ruby]
----
session = Java::IoVertxExtWebClient::WebClientSession.create(client)
session.add_header("my-jwt-token", jwtToken)

----

The headers will then be added to every request; notice that these headers will be sent to all hosts; if you need
to send different headers to different hosts, you have to add them manually to every single request and not to the 
`link:unavailable[WebClientSession]`.

ifeval::["ruby" == "java"]
include::override/rxjava2.adoc[]
endif::[]