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

com.netflix.spectator.ipc.http.HttpResponse Maven / Gradle / Ivy

There is a newer version: 1.8.2
Show newest version
/*
 * Copyright 2014-2020 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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 com.netflix.spectator.ipc.http;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;

/**
 * Response for an HTTP request made via {@link HttpRequestBuilder}.
 */
public final class HttpResponse {

  private final int status;
  private final Map> headers;
  private final byte[] data;

  /** Create a new response instance with an empty entity. */
  public HttpResponse(int status, Map> headers) {
    this(status, headers, HttpUtils.EMPTY);
  }

  /** Create a new response instance. */
  public HttpResponse(int status, Map> headers, byte[] data) {
    this.status = status;
    Map> hs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    hs.putAll(headers);
    this.headers = Collections.unmodifiableMap(hs);
    this.data = data;
  }

  /** Return the status code of the response. */
  public int status() {
    return status;
  }

  /**
   * Return the headers for the response as an unmodifiable map with case-insensitive keys.
   */
  public Map> headers() {
    return headers;
  }

  /** Return the value for the first occurrence of a given header or null if not found. */
  public String header(String k) {
    List vs = headers.get(k);
    return (vs == null || vs.isEmpty()) ? null : vs.get(0);
  }

  /**
   * Return the value for a date header. The return value will be null if the header does
   * not exist or if it cannot be parsed correctly as a date.
   */
  public Instant dateHeader(String k) {
    String d = header(k);
    return (d == null) ? null : parseDate(d);
  }

  private Instant parseDate(String d) {
    try {
      return LocalDateTime.parse(d, DateTimeFormatter.RFC_1123_DATE_TIME)
          .atZone(ZoneOffset.UTC)
          .toInstant();
    } catch (Exception e) {
      return null;
    }
  }

  /** Return the entity for the response. */
  public byte[] entity() {
    return data;
  }

  /** Return the entity as a UTF-8 string. */
  public String entityAsString() {
    return new String(data, StandardCharsets.UTF_8);
  }

  /**
   * Returns an input stream for consuming the response entity. If the content encoding is
   * {@code gzip}, then it will automatically wrap with a GZIP stream to decompress. This
   * can be more efficient than using {@link #decompress()} as it avoids creating an intermediate
   * copy of the decompressed data. The caller is responsible for ensuring that the returned
   * stream is closed.
   */
  public InputStream entityInputStream() throws IOException {
    ByteArrayInputStream bais = new ByteArrayInputStream(data);
    String enc = header("Content-Encoding");
    return (enc != null && enc.contains("gzip"))
        ? new GZIPInputStream(bais)
        : bais;
  }

  /** Return a copy of the response with the entity compressed. Typically used for testing. */
  public HttpResponse compress() throws IOException {
    String enc = header("Content-Encoding");
    return (enc == null) ? gzip() : this;
  }

  private HttpResponse gzip() throws IOException {
    Map> newHeaders = new HashMap<>(headers);
    newHeaders.put("Content-Encoding", Collections.singletonList("gzip"));
    return new HttpResponse(status, newHeaders, HttpUtils.gzip(data));
  }

  /** Return a copy of the response with the entity decompressed. */
  public HttpResponse decompress() throws IOException {
    String enc = header("Content-Encoding");
    return (enc != null && enc.contains("gzip")) ? gunzip() : this;
  }

  private HttpResponse gunzip() throws IOException {
    Map> newHeaders = headers.entrySet().stream()
        .filter(e -> !"Content-Encoding".equalsIgnoreCase(e.getKey()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    if (data.length == 0) {
      return new HttpResponse(status, newHeaders);
    } else {
      return new HttpResponse(status, newHeaders, HttpUtils.gunzip(data));
    }
  }

  @Override public String toString() {
    StringBuilder builder = new StringBuilder(50);
    builder.append("HTTP/1.1 ").append(status).append('\n');
    for (Map.Entry> h : headers.entrySet()) {
      for (String v : h.getValue()) {
        builder.append(h.getKey()).append(": ").append(v).append('\n');
      }
    }
    builder.append("\n... ")
        .append(data.length)
        .append(" bytes ...\n");
    return builder.toString();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy