Doua Beri
Doua Beri

Reputation: 10949

Java 11: New HTTP client send POST requests with x-www-form-urlencoded parameters

I'm trying to send a POST request using the new http client api. Is there a built in way to send parameters formatted as x-www-form-urlencoded ?

My current code:

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(url))
        .header("Content-Type", "application/x-www-form-urlencoded")
        .POST(BodyPublishers.ofString("a=get_account&account=" + URLEncoder.encode(account, "UTF-8")))
        .build();

What I'm looking is for a better way to pass the parameters. Something like this:

Params p=new Params();
p.add("a","get_account");
p.add("account",account);

Do I need to build myself this functionality or is something already built in?

I'm using Java 12.

Upvotes: 42

Views: 34323

Answers (6)

deloyar
deloyar

Reputation: 528

I think the following is the best way to achieve this using Java 11:

Map<String, String> parameters = new HashMap<>();
parameters.put("a", "get_account");
parameters.put("account", account);

String form = parameters.entrySet()
    .stream()
    .map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
    .collect(Collectors.joining("&"));

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create(url))
    .header("Content-Type", "application/x-www-form-urlencoded")
    .POST(HttpRequest.BodyPublishers.ofString(form))
    .build();

HttpResponse<?> response = client.send(request, HttpResponse.BodyHandlers.ofString());

System.out.println(response.statusCode() + " " + response.body().toString());

Upvotes: 43

bezbos.
bezbos.

Reputation: 2374

Here is a helper method for converting a Map into a URL encoded form-data payload:

private static String getFormDataAsString(Map<String, String> formData) {
    var formBodyBuilder = new StringBuilder();
    for (Map.Entry<String, String> singleEntry : formData.entrySet()) {
        if (formBodyBuilder.length() > 0) {
            formBodyBuilder.append("&");
        }
        formBodyBuilder.append(URLEncoder.encode(singleEntry.getKey(), StandardCharsets.UTF_8));
        formBodyBuilder.append("=");
        formBodyBuilder.append(URLEncoder.encode(singleEntry.getValue(), StandardCharsets.UTF_8));
    }
    return formBodyBuilder.toString();
}

You can easily use it like so:

var formData = Map.of(
    "key-1", "value-1",
    "key-2", "value-2",
    "key-3", "value-3"
);

var request = HttpRequest.newBuilder()
        .uri(requestUri)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
        .build();

Additional gotchas:

  • Make sure to use POST or PUT as the HTTP method verb, because most servers will ignore the body on GET requests.
  • If you're sending HTML with CSS and/or JS in the request body, make sure to encode it in Base64 format, because many proxies and firewalls will reject your request due to potentially dangerous links or scripts in the payload.

Upvotes: 1

Blazej Tomaszewski
Blazej Tomaszewski

Reputation: 41

Instead of Stream.of you can use more compact String.join (according to Łukasz Olszewski answer):

String form = Map.of("param1", "value1", "param2", "value2")
  .entrySet()
  .stream()
  .map(entry -> String.join("=",
                        URLEncoder.encode(entry.getKey().toString(), StandardCharsets.UTF_8),
                        URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8)))
                .collect(Collectors.joining("&"));
return HttpRequest.BodyPublishers.ofString(form);

Upvotes: 3

Moataz Abdelnasser
Moataz Abdelnasser

Reputation: 522

Check out Methanol. It's got a nice FormBodyPublisher for x-www-form-urlencoded bodies.

var formBody = FormBodyPublisher.newBuilder()
      .query("a", "get_account")
      .query("account", account)
      .build();
var request = MutableRequest.POST("https://example.com", formBody);

// Methanol implements an HttpClient that does nice things to your request/response.
// Here, the Content-Type header will be added for you.
var client = Methanol.create();
var response = client.send(request, BodyHandlers.ofString());

Upvotes: 5

Eliopez
Eliopez

Reputation: 49

As Łukasz Olszewski said , worked correctly :

String params = Map.of(
                    Constants.PARAM_CLIENT_ID, apiObject.getClientId(),
                    Constants.PARAM_SCOPE, apiObject.getScope(),
                    Constants.PARAM_CODE, apiObject.getCode(),
                    Constants.PARAM_REDIRECT_URI, apiObject.getRedirectUri(),
                    Constants.PARAM_GRANT_TYPE, apiObject.getGrantType(),
                    Constants.PARAM_CODE_VERIFIER, apiObject.getCodeVerifier())
                    .entrySet()
                    .stream()
                    .map(entry -> Stream.of(
                            URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8),
                            URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))
                            .collect(Collectors.joining("="))
                    ).collect(Collectors.joining("&"));

HttpResponse<?> response = utils.consumeHttpPostFormUrlEncodedClientByRequestUrl(Constants.URL_BASE + Constants.URL_GET_TOKEN, params);

and consumeHttpPostFormUrlEncodedClientByRequestUrl

public HttpResponse<?> consumeHttpPostFormUrlEncodedClientByRequestUrl(String url, String map) throws IOException, InterruptedException {
        HttpClient httpClient = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                .header("Content-Type", String.valueOf(MediaType.APPLICATION_FORM_URLENCODED))
                .POST(HttpRequest.BodyPublishers.ofString(map))
                .build();
        return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    }

Upvotes: 4

Łukasz Olszewski
Łukasz Olszewski

Reputation: 915

This way could be useful:

String param = Map.of("param1", "value1", "param2", "value2")
      .entrySet()
      .stream()
      .map(entry -> Stream.of(
               URLEncoder.encode(entry.getKey(), UTF_8),
               URLEncoder.encode(entry.getValue(), UTF_8))
                .collect(Collectors.joining("="))
      ).collect(Collectors.joining("&"));

You can use up to 10 pairs (param, value) by Map.of(...). It returns an unmodifiable map.

Upvotes: 5

Related Questions