miércoles, 18 de enero de 2023

Http Client 5: Manual básico

Ya hemos hecho otros posts sobre esta gran librería, sobre configuración y uso de la interfaz Fluent. Hoy veremos un manual básico sobre la nueva versión, HTTP Client 5 en su versión clásica. Y cuatro sencillos ejemplos para su uso más común. 

Veremos el ejemplo más básico y completo, con una llamada HTTP Post. En 5 sencillos pasos, podemos invocar el método y obtener su respuesta. Incluso en métodos que no necesitan enviar datos, como GET o DELETE se puede hacer incluso en menos pasos. 

public static String post(final String url, final String jsonBody) {
  // 1. Create HTTP Method
  HttpPost httpPost = new HttpPost(url);
  // 2. Set payload and content-type
  httpPost.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
  String result = null;
  // 3. Create HTTP client
  try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
    // 4. Execute the method through the HTTP client
    try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
      // 5. Read Response
      result = EntityUtils.toString(response.getEntity());
      log.info("Status Code: " + response.getCode() + " " + response.getReasonPhrase());
    }
  } catch (IOException | ParseException e) {
    log.error(e.getMessage(), e);
  }
  return result;
}

El siguiente punto añadir un CookieStore que nos permita almacenar las cookies que nos envíe el servidor a través de la cabecera 'set-cookie'. Y que nos permita devolver dicha cookie al servidor. Muy útil para los casos en que se necesita una comunicación con el servidor, sobre todo con tareas de login.

private static CookieStore cookieStore = new BasicCookieStore();
private static CloseableHttpClient httpclient = HttpClients.custom().setDefaultCookieStore(cookieStore).build();

public static String postWithCookieStore(final String url, final String jsonBody) {
  // 1. Create HTTP Method
  HttpPost httpPost = new HttpPost(url);
  // 2. Set payload and content-type
  httpPost.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
  String result = null;
  try {
    // 3. Execute the method through the HTTP client
    try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
      // 4. Read Response
      result = EntityUtils.toString(response.getEntity());
      log.info("Status Code: " + response.getCode() + " " + response.getReasonPhrase());
    }
  } catch (IOException | ParseException e) {
    log.error(e.getMessage(), e);
  }
  return result;
}

Ahora veremos cómo realizar una llamada con autenticación de usuario y contraseña. Y aunque siempre se puede añadir la cabecera a mano en el método HTTP, esta librería tiene sus propias clases que permiten realizar la autenticación de una forma más segura. 

public static String getWithBasicAuth(final String url, final String user, final String pass)
    throws URISyntaxException, IOException, ParseException {
  String result = null;
  URI uri = new URI(url);
  // 1. Create a Basic Credentials provider to authenticate the call
  final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
  AuthScope authScope = new AuthScope(uri.getHost(), uri.getPort());
  credsProvider.setCredentials(authScope, new UsernamePasswordCredentials(user, pass.toCharArray()));
  // 2. Add the credentials to the HHTP Client to use it in the call
  try (final CloseableHttpClient httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build()) {
    final HttpGet httpget = new HttpGet(url);
    try (final CloseableHttpResponse response = httpclient.execute(httpget)) {
      result = EntityUtils.toString(response.getEntity());
    }
  } catch (IOException | ParseException e) {
    log.error(e.getMessage(), e);
  }
  return result;
} 

Con BasicCredentialsProvider, el cliente web no adjuntará la cabecera de autenticación a menos que reciba un código de petición 401. Aquí podemos ver una prueba de su funcionamiento con wiremock.

final String book = "{\"name\":\"Dune\",\"author\":\"Frank Herbert\"}";
final String bookUrl = "http://localhost:57001/book";
final String basicAuth = "Basic dXNlcjpwYXNz";

@RegisterExtension
static WireMockExtension wm1 = WireMockExtension.newInstance().options(wireMockConfig().port(57001).notifier(new ConsoleNotifier(true))).build();

@Test
public void getTest(final WireMockRuntimeInfo wmRuntimeInfo) throws URISyntaxException, ParseException, IOException {
  wm1.stubFor(get("/book/1")
      .willReturn(aResponse().withStatus(401).withHeader("Connection", "keep-alive").withHeader("WWW-Authenticate", "Basic realm=\"Fake Realm\"")));
  wm1.stubFor(get("/book/1").withHeader("Authorization", equalToIgnoreCase(basicAuth)).willReturn(ok().withBody(book)));
  String result = HttpClientUtil.getWithBasicAuth(bookUrl + "/1", "user", "pass");
  assertThat(result, equalTo(book));
  wm1.verify(2, getRequestedFor(urlEqualTo("/book/1")));
  wm1.verify(1, getRequestedFor(urlEqualTo("/book/1")).withoutHeader("Authorization"));
  wm1.verify(1, getRequestedFor(urlEqualTo("/book/1")).withHeader("Authorization", equalToIgnoreCase(basicAuth)));
}

El último ejemplo será crear un cliente que invocar a cualquier endpoint con certificado autofirmado y por tanto no de confianza. No hace falta mencionar, que aunque esto es muy util en entornos de desarrollo, no se debe realizar nunca en entornos productivos

public static String trustedAllPost(final String url) {
  String result = null;
  try {
    // 1. Create SSLContextBuilder to trust in every host
    SSLContextBuilder builder = new SSLContextBuilder();
    builder.loadTrustMaterial(null, (chain, authType) -> true);
    // 2. Create a SSLConnectionSocketFactory to not verify any hostname
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build(), new NoopHostnameVerifier());
    HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(sslsf).build();
    // 3. Associate both configurations through HttpClientConnectionManager to the HTTP client
    try (CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build()) {
      HttpPost method = new HttpPost(url);
      try (CloseableHttpResponse response = httpclient.execute(method)) {
        result = EntityUtils.toString(response.getEntity());
      }
    }
  } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException | IOException | ParseException e) {
    log.error(e.getMessage(), e);
  }
  return result;
}

No hay comentarios:

Publicar un comentario