All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nstream.adapter.http.HttpAdapterUtils Maven / Gradle / Ivy

// Copyright 2015-2024 Nstream, inc.
//
// Licensed under the Redis Source Available License 2.0 (RSALv2) Agreement;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://redis.com/legal/rsalv2-agreement/
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package nstream.adapter.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;
import java.util.function.Function;
import nstream.adapter.common.AdapterUtils;
import nstream.adapter.common.ext.HttpIngressSettings;
import nstream.adapter.common.ingress.AssemblyException;
import swim.http.MediaType;
import swim.structure.Value;

public final class HttpAdapterUtils {

  private HttpAdapterUtils() {
  }

  public static HttpIngressSettings ingressSettingsFromProp(Value prop) {
    final HttpIngressSettings settings = HttpIngressSettings.form().cast(prop);
    return settings == null ? HttpIngressSettings.defaultSettings() : settings;
  }

  /**
   * Constructs an {@link HttpRequest} based on the provided parameters.
   *
   * @param method  the HTTP method (e.g. "GET", "POST")
   * @param endpointUrl  an RFC 2396 compliant request URI
   * @param headers  the request headers
   * @param timeoutMillis  the request timeout duration in milliseconds
   * @return  an {@code HttpRequest}
   * @throws URISyntaxException  if endpointUrl violates RFC 2396
   */
  public static HttpRequest buildHttpRequest(String method, String endpointUrl,
                                             Map headers,
                                             HttpRequest.BodyPublisher body,
                                             long timeoutMillis)
      throws URISyntaxException {
    HttpRequest.Builder builder = HttpRequest.newBuilder(new URI(endpointUrl));
    if (headers != null && !headers.isEmpty()) {
      for (Map.Entry entry : headers.entrySet()) {
        builder = builder.header(entry.getKey(), entry.getValue());
      }
    }
    builder = builder.timeout(Duration.ofMillis(timeoutMillis));
    builder = builder.method(method, body);
    return builder.build();
  }

  /**
   *
   * @param executor
   * @param request
   * @return
   * @throws IOException
   * @throws InterruptedException
   */
  public static HttpResponse executeHttpRequest(HttpClient executor, HttpRequest request)
      throws IOException, InterruptedException {
    return executor.send(request, HttpResponse.BodyHandlers.ofInputStream());
  }

  /**
   * Convenience method to extract an HTTP header from an {@link HttpHeaders}
   * object.
   *
   * @param headers  the {@code HttpHeaders} object holding the desired header
   * @param header  the name of the header of interest
   * @param transform  a single-argument transformation function from the
   *                   header's {@code String} value to the user's desired type
   * @param orElse  the default {@code V} to return if the header is absent
   * @param   the output type of {@code transform}
   * @return  a user-chosen representation of {@code header}
   */
  public static  V extractHeaderValue(HttpHeaders headers, String header,
                                         Function transform, V orElse) {
    return headers.firstValue(header)
        .map(transform)
        .orElse(orElse);
  }

  /**
   * Decides the content encoding to use given an {@link HttpIngressSettings}
   * configuration and a set of received HTTP response headers.
   *
   * 

Conflicting information is resolved first in favor of the {@code * HttpIngressSettings} configuration, then the first matching header value. * * @param settings the {@code HttpIngressSettings} configuration * @param responseHeaders the response headers * @return the {@code HttpIngressSettings}-preferred resolution of the * information in the inputs. */ public static String contentEncoding(HttpIngressSettings settings, HttpHeaders responseHeaders) { final String override = settings.contentEncodingOverride(); if (override != null && !override.isEmpty()) { return override; } return extractHeaderValue(responseHeaders, "content-encoding", Function.identity(), null); } /** * Decides the content type to use given an {@link HttpIngressSettings} * configuration and a set of received HTTP response headers. * *

Conflicting information is resolved first in favor of the {@code * HttpIngressSettings} configuration, then the first matching header value. * * @param settings the {@code HttpIngressSettings} configuration * @param responseHeaders the response headers * @return the {@code HttpIngressSettings}-preferred resolution of the * information in the inputs. */ public static MediaType contentType(HttpIngressSettings settings, HttpHeaders responseHeaders) { final String override = settings.contentTypeOverride(); if (override != null && !override.isEmpty()) { return MediaType.parse(override); } return extractHeaderValue(responseHeaders, "content-type", MediaType::parse, MediaType.applicationJson()); } public static InputStream responseBodyStream(HttpResponse response, String encoding) throws IOException { return AdapterUtils.decodeStream(response.body(), encoding); } public static String minimalContentType(MediaType contentType) { final String subtype = contentType.subtype(); return subtype.substring(subtype.startsWith("x-") ? 2 : 0); } /** * Transforms the encoding-free * @param bodyStream * @param contentType * @return * @throws IOException */ public static Value responseBodyStructure(InputStream bodyStream, MediaType contentType) throws AssemblyException, IOException { if (!contentType.isApplication() && !contentType.isText()) { throw new IllegalArgumentException("Unsupported content type: " + contentType); } final String subtype = minimalContentType(contentType); return AdapterUtils.assembleContent(bodyStream, subtype); } /** * A no-frills singleton {@link HttpClient}. * *

The returned client is initialized with Java's default values and thus * is unsuitable for use alongside, for example, custom proxy selectors and * SSL contexts. The client is lazily created upon the first invocation of * this method, thus occupying no resources if the method is never invoked. * * @return A singleton, threadsafe {@code HttpClient} instance. * @see HttpClient#newHttpClient() */ public static HttpClient defaultHttpClient() { return DefaultHttpClient.instance().client(); } // Thread-safe, efficient pattern for lazy singleton initialization in Java private static final class DefaultHttpClient { private final HttpClient client; private DefaultHttpClient() { this.client = HttpClient.newHttpClient(); } private HttpClient client() { return this.client; } private static class DefaultHttpClientHolder { private static final DefaultHttpClient INSTANCE = new DefaultHttpClient(); } private static DefaultHttpClient instance() { return DefaultHttpClientHolder.INSTANCE; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy