Repository: vert-x3/vertx-web
Issue: NPE when userAgentEnabled is set to false

### Version

4.0.0 (and in previous betas)

### Context

I have a SecretsClient that is based on VaultConfigStore and like that code uses SlimVaultClient to access secrets in Vault. It only uses loginWithAppRole for creating/renewing the client token.

The configuration passed thru to the SlimVaultClient includes the setting `userAgentEnabled  = false`. When attempting to access the vault store, it will first attempt to get a token and it's at this point that the request fails with

```
java.lang.NullPointerException
	at io.vertx.ext.web.client.impl.HttpContext.handleSendRequest(HttpContext.java:469)
	at io.vertx.ext.web.client.impl.HttpContext.execute(HttpContext.java:332)
	at io.vertx.ext.web.client.impl.HttpContext.next(HttpContext.java:322)
	at io.vertx.ext.web.client.impl.HttpContext.fire(HttpContext.java:289)
	at io.vertx.ext.web.client.impl.HttpContext.sendRequest(HttpContext.java:193)
	at io.vertx.ext.web.client.impl.HttpContext.handlePrepareRequest(HttpContext.java:404)
	at io.vertx.ext.web.client.impl.HttpContext.execute(HttpContext.java:329)
	at io.vertx.ext.web.client.impl.HttpContext.next(HttpContext.java:322)
	at io.vertx.ext.web.client.impl.HttpContext.fire(HttpContext.java:289)
	at io.vertx.ext.web.client.impl.HttpContext.prepareRequest(HttpContext.java:180)
	at io.vertx.ext.web.client.impl.HttpRequestImpl.send(HttpRequestImpl.java:310)
	at io.vertx.ext.web.client.impl.HttpRequestImpl.sendJsonObject(HttpRequestImpl.java:290)
	at io.vertx.config.vault.client.SlimVaultClient.loginWithAppRole(SlimVaultClient.java:234)
```

If the setting is changed to `userAgentEnabled  = true` then the request proceeds OK.

Whilst the bug was found in the vertx-config project, I think the error is in the processing of requests via the underlying WebClient _within_ the SlimVaultClient.

```java
// HttpContext line 459
private void handleSendRequest() {
    if (request.headers != null) {
      MultiMap headers = requestOptions.getHeaders();
      if (headers == null) {
        headers = MultiMap.caseInsensitiveMultiMap();
        requestOptions.setHeaders(headers);
      }
      headers.addAll(request.headers);
    }
    if (contentType != null) {
      // !! at this point the options headers is always null when userAgentEnabled  = true !!
      String prev = requestOptions.getHeaders().get(HttpHeaders.CONTENT_TYPE);
      if (prev == null) {
        requestOptions.addHeader(HttpHeaders.CONTENT_TYPE, contentType);
      } else {
        contentType = prev;
      }
    }
```

### Do you have a reproducer?

```java
@test fun testSlimVaultClient() {
    runBlocking {
        val client = SlimVaultClient(vertx, JsonObject()
                .put("host", "localhost")
                .put("port", 8500)
                .put("ssl", false)
                .put("userAgentEnabled", false))

        val promise = (vertx as VertxInternal).promise<Void>()

        println("Login")
        client.loginWithAppRole("some.role", "some.secret") {
            println("Handling")
            if (it.failed()) {
                promise.fail(it.cause())
            } else {
                it.succeeded()
            }
        }

        println("Waiting")
        promise.await()
    }
}
```

### Steps to reproduce

1. attempt to run the test above
2. when `userAgentEnabled  = true`, only _Login_ is printed
3. when `userAgentEnabled  = false`, _Login_ _Waiting_ and _Handling_ are printed

### Extra

* same code used to work in 4.0.0-milestone5 


Changed files:
vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpRequestImpl.java, vertx-web-client/src/test/java/io/vertx/ext/web/client/WebClientTest.java

diff --git a/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpRequestImpl.java b/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpRequestImpl.java
index b83629198..a2c760d77 100644
--- a/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpRequestImpl.java
+++ b/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpRequestImpl.java
@@ -79,6 +79,8 @@ public class HttpRequestImpl<T> implements HttpRequest<T> {
     this.options = options;
     if (options.isUserAgentEnabled()) {
       headers = HttpHeaders.set(HttpHeaders.USER_AGENT, options.getUserAgent());
+    } else {
+      headers = HttpHeaders.headers();
     }
   }
 
@@ -92,7 +94,7 @@ public class HttpRequestImpl<T> implements HttpRequest<T> {
     this.host = other.host;
     this.timeout = other.timeout;
     this.uri = other.uri;
-    this.headers = other.headers != null ? HttpHeaders.headers().addAll(other.headers) : null;
+    this.headers = other.headers != null ? HttpHeaders.headers().addAll(other.headers) : HttpHeaders.headers();
     this.params = other.params != null ? HttpHeaders.headers().addAll(other.params) : null;
     this.codec = other.codec;
     this.followRedirects = other.followRedirects;
diff --git a/vertx-web-client/src/test/java/io/vertx/ext/web/client/WebClientTest.java b/vertx-web-client/src/test/java/io/vertx/ext/web/client/WebClientTest.java
index 3b0f6dabb..8f7912e44 100644
--- a/vertx-web-client/src/test/java/io/vertx/ext/web/client/WebClientTest.java
+++ b/vertx-web-client/src/test/java/io/vertx/ext/web/client/WebClientTest.java
@@ -33,7 +33,6 @@ import org.junit.Test;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.net.ConnectException;
-import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.util.*;
@@ -135,6 +134,32 @@ public class WebClientTest extends WebClientTestBase {
     testRequest(client -> client.get("somehost", "somepath"), req -> assertEquals(Collections.emptyList(), req.headers().getAll(HttpHeaders.USER_AGENT)));
   }
 
+  @Test
+  public void testSendingJsonWithUserAgentDisabled() throws Exception {
+    waitFor(2);
+    server.requestHandler(req -> {
+      assertEquals(Collections.emptyList(), req.headers().getAll(HttpHeaders.USER_AGENT));
+      try {
+        complete();
+      } finally {
+        req.response().end();
+      }
+    });
+    startServer();
+
+    JsonObject payload = new JsonObject().put("meaning", 42);
+
+    WebClientOptions clientOptions = new WebClientOptions()
+      .setDefaultHost(DEFAULT_HTTP_HOST)
+      .setDefaultPort(DEFAULT_HTTP_PORT)
+      .setUserAgentEnabled(false);
+
+    WebClient agentFreeClient = WebClient.create(vertx, clientOptions);
+    HttpRequest<Buffer> builder = agentFreeClient.post("somehost", "somepath");
+    builder.sendJson(payload, onSuccess(resp -> complete()));
+    await();
+  }
+
   @Test
   public void testUserAgentHeaderOverride() throws Exception {
     testRequest(client -> client.get("somehost", "somepath").putHeader(HttpHeaders.USER_AGENT.toString(), "smith"), req -> assertEquals(Collections.singletonList("smith"), req.headers().getAll(HttpHeaders.USER_AGENT)));