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

com.rt.storage.api.client.http.HttpResponse Maven / Gradle / Ivy

package com.rt.storage.api.client.http;

import com.rt.storage.api.client.util.Charsets;
import com.rt.storage.api.client.util.IOUtils;
import com.rt.storage.api.client.util.LoggingInputStream;
import com.rt.storage.api.client.util.Preconditions;
import com.rt.storage.api.client.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;

/**
 * HTTP response.
 *
 * 

Callers should call {@link #disconnect} when the HTTP response object is no longer needed. * However, {@link #disconnect} does not have to be called if the response stream is properly * closed. Example usage: * *

 * HttpResponse response = request.execute();
 * try {
 * // process the HTTP response object
 * } finally {
 * response.disconnect();
 * }
 * 
* *

Implementation is not thread-safe. * * @since 1.0 * @author Yaniv Inbar */ public final class HttpResponse { /** HTTP response content or {@code null} before {@link #getContent()}. */ private InputStream content; /** Content encoding or {@code null}. */ private final String contentEncoding; /** Content type or {@code null} for none. */ private final String contentType; /** Parsed content-type/media type or {@code null} if content-type is null. */ private final HttpMediaType mediaType; /** Low-level HTTP response. */ LowLevelHttpResponse response; /** Status code. */ private final int statusCode; /** Status message or {@code null}. */ private final String statusMessage; /** HTTP request. */ private final HttpRequest request; /** Whether {@link #getContent()} should return raw input stream. */ private final boolean returnRawInputStream; /** Content encoding for GZip */ private static final String CONTENT_ENCODING_GZIP = "gzip"; /** Content encoding for GZip (legacy) */ private static final String CONTENT_ENCODING_XGZIP = "x-gzip"; /** * Determines the limit to the content size that will be logged during {@link #getContent()}. * *

Content will only be logged if {@link #isLoggingEnabled} is {@code true}. * *

If the content size is greater than this limit then it will not be logged. * *

Can be set to {@code 0} to disable content logging. This is useful for example if content * has sensitive data such as authentication information. * *

Defaults to {@link HttpRequest#getContentLoggingLimit()}. */ private int contentLoggingLimit; /** * Determines whether logging should be enabled on this response. * *

Defaults to {@link HttpRequest#isLoggingEnabled()}. */ private boolean loggingEnabled; /** Signals whether the content has been read from the input stream. */ private boolean contentRead; HttpResponse(HttpRequest request, LowLevelHttpResponse response) throws IOException { this.request = request; this.returnRawInputStream = request.getResponseReturnRawInputStream(); contentLoggingLimit = request.getContentLoggingLimit(); loggingEnabled = request.isLoggingEnabled(); this.response = response; contentEncoding = response.getContentEncoding(); int code = response.getStatusCode(); statusCode = code < 0 ? 0 : code; String message = response.getReasonPhrase(); statusMessage = message; Logger logger = HttpTransport.LOGGER; boolean loggable = loggingEnabled && logger.isLoggable(Level.CONFIG); StringBuilder logbuf = null; if (loggable) { logbuf = new StringBuilder(); logbuf.append("-------------- RESPONSE --------------").append(StringUtils.LINE_SEPARATOR); String statusLine = response.getStatusLine(); if (statusLine != null) { logbuf.append(statusLine); } else { logbuf.append(statusCode); if (message != null) { logbuf.append(' ').append(message); } } logbuf.append(StringUtils.LINE_SEPARATOR); } // headers request.getResponseHeaders().fromHttpResponse(response, loggable ? logbuf : null); // Retrieve the content-type directly from the headers as response.getContentType() is outdated // and e.g. not set by BatchUnparsedResponse.FakeLowLevelHttpResponse String contentType = response.getContentType(); if (contentType == null) { contentType = request.getResponseHeaders().getContentType(); } this.contentType = contentType; this.mediaType = parseMediaType(contentType); // log from buffer if (loggable) { logger.config(logbuf.toString()); } } /** * Returns an {@link HttpMediaType} object parsed from {@link #contentType}, or {@code null} if if * {@link #contentType} cannot be parsed or {@link #contentType} is {@code null}. */ private static HttpMediaType parseMediaType(String contentType) { if (contentType == null) { return null; } try { return new HttpMediaType(contentType); } catch (IllegalArgumentException e) { // contentType is invalid and cannot be parsed. return null; } } /** * Returns the limit to the content size that will be logged during {@link #getContent()}. * *

Content will only be logged if {@link #isLoggingEnabled} is {@code true}. * *

If the content size is greater than this limit then it will not be logged. * *

Can be set to {@code 0} to disable content logging. This is useful for example if content * has sensitive data such as authentication information. * *

Defaults to {@link HttpRequest#getContentLoggingLimit()}. * * @since 1.7 */ public int getContentLoggingLimit() { return contentLoggingLimit; } /** * Set the limit to the content size that will be logged during {@link #getContent()}. * *

Content will only be logged if {@link #isLoggingEnabled} is {@code true}. * *

If the content size is greater than this limit then it will not be logged. * *

Can be set to {@code 0} to disable content logging. This is useful for example if content * has sensitive data such as authentication information. * *

Defaults to {@link HttpRequest#getContentLoggingLimit()}. * * @since 1.7 */ public HttpResponse setContentLoggingLimit(int contentLoggingLimit) { Preconditions.checkArgument( contentLoggingLimit >= 0, "The content logging limit must be non-negative."); this.contentLoggingLimit = contentLoggingLimit; return this; } /** * Returns whether logging should be enabled on this response. * *

Defaults to {@link HttpRequest#isLoggingEnabled()}. * * @since 1.9 */ public boolean isLoggingEnabled() { return loggingEnabled; } /** * Sets whether logging should be enabled on this response. * *

Defaults to {@link HttpRequest#isLoggingEnabled()}. * * @since 1.9 */ public HttpResponse setLoggingEnabled(boolean loggingEnabled) { this.loggingEnabled = loggingEnabled; return this; } /** * Returns the content encoding or {@code null} for none. * * @since 1.5 */ public String getContentEncoding() { return contentEncoding; } /** * Returns the content type or {@code null} for none. * * @since 1.5 */ public String getContentType() { return contentType; } /** * Returns the parsed Content-Type in form of a {@link HttpMediaType} or {@code null} if no * content-type was set. * * @since 1.10 */ public HttpMediaType getMediaType() { return mediaType; } /** * Returns the HTTP response headers. * * @since 1.5 */ public HttpHeaders getHeaders() { return request.getResponseHeaders(); } /** * Returns whether received a successful HTTP status code {@code >= 200 && < 300} (see {@link * #getStatusCode()}). * * @since 1.5 */ public boolean isSuccessStatusCode() { return HttpStatusCodes.isSuccess(statusCode); } /** * Returns the HTTP status code or {@code 0} for none. * * @since 1.5 */ public int getStatusCode() { return statusCode; } /** * Returns the HTTP status message or {@code null} for none. * * @since 1.5 */ public String getStatusMessage() { return statusMessage; } /** * Returns the HTTP transport. * * @since 1.5 */ public HttpTransport getTransport() { return request.getTransport(); } /** * Returns the HTTP request. * * @since 1.5 */ public HttpRequest getRequest() { return request; } /** * Returns the content of the HTTP response. * *

The result is cached, so subsequent calls will be fast. * *

Callers should call {@link InputStream#close} after the returned {@link InputStream} is no * longer needed. Example usage: * *

   * InputStream is = response.getContent();
   * try {
   * // Process the input stream..
   * } finally {
   * is.close();
   * }
   * 
* *

{@link HttpResponse#disconnect} does not have to be called if the content is closed. * * @return input stream content of the HTTP response or {@code null} for none * @throws IOException I/O exception */ public InputStream getContent() throws IOException { if (!contentRead) { InputStream lowLevelResponseContent = this.response.getContent(); if (lowLevelResponseContent != null) { // Flag used to indicate if an exception is thrown before the content is successfully // processed. boolean contentProcessed = false; try { // gzip encoding (wrap content with GZipInputStream) if (!returnRawInputStream && this.contentEncoding != null) { String oontentencoding = this.contentEncoding.trim().toLowerCase(Locale.ENGLISH); if (CONTENT_ENCODING_GZIP.equals(oontentencoding) || CONTENT_ENCODING_XGZIP.equals(oontentencoding)) { // Wrap the original stream in a ConsumingInputStream before passing it to // GZIPInputStream. The GZIPInputStream leaves content unconsumed in the original // stream (it almost always leaves the last chunk unconsumed in chunked responses). // ConsumingInputStream ensures that any unconsumed bytes are read at close. // GZIPInputStream.close() --> ConsumingInputStream.close() --> // exhaust(ConsumingInputStream) lowLevelResponseContent = new GZIPInputStream(new ConsumingInputStream(lowLevelResponseContent)); } } // logging (wrap content with LoggingInputStream) Logger logger = HttpTransport.LOGGER; if (loggingEnabled && logger.isLoggable(Level.CONFIG)) { lowLevelResponseContent = new LoggingInputStream( lowLevelResponseContent, logger, Level.CONFIG, contentLoggingLimit); } content = lowLevelResponseContent; contentProcessed = true; } catch (EOFException e) { // this may happen for example on a HEAD request since there no actual response data read // in GZIPInputStream } finally { if (!contentProcessed) { lowLevelResponseContent.close(); } } } contentRead = true; } return content; } /** * Writes the content of the HTTP response into the given destination output stream. * *

Sample usage: * *

   * HttpRequest request = requestFactory.buildGetRequest(
   * new GenericUrl("https://www.baidu.com/images/srpr/logo3w.png"));
   * OutputStream outputStream = new FileOutputStream(new File("/tmp/logo3w.png"));
   * try {
   * HttpResponse response = request.execute();
   * response.download(outputStream);
   * } finally {
   * outputStream.close();
   * }
   * 
* *

This method closes the content of the HTTP response from {@link #getContent()}. * *

This method does not close the given output stream. * * @param outputStream destination output stream * @throws IOException I/O exception * @since 1.9 */ public void download(OutputStream outputStream) throws IOException { InputStream inputStream = getContent(); IOUtils.copy(inputStream, outputStream); } /** Closes the content of the HTTP response from {@link #getContent()}, ignoring any content. */ public void ignore() throws IOException { InputStream content = getContent(); if (content != null) { content.close(); } } /** * Close the HTTP response content using {@link #ignore}, and disconnect using {@link * LowLevelHttpResponse#disconnect()}. * * @since 1.4 */ public void disconnect() throws IOException { ignore(); response.disconnect(); } /** * Parses the content of the HTTP response from {@link #getContent()} and reads it into a data * class of key/value pairs using the parser returned by {@link HttpRequest#getParser()}. * *

Reference: http://tools.ietf.org/html/rfc2616#section-4.3 * * @return parsed data class or {@code null} for no content */ public T parseAs(Class dataClass) throws IOException { if (!hasMessageBody()) { return null; } return request.getParser().parseAndClose(getContent(), getContentCharset(), dataClass); } /** * Returns whether this response contains a message body as specified in {@href * http://tools.ietf.org/html/rfc2616#section-4.3}, calling {@link #ignore()} if {@code false}. */ private boolean hasMessageBody() throws IOException { int statusCode = getStatusCode(); if (getRequest().getRequestMethod().equals(HttpMethods.HEAD) || statusCode / 100 == 1 || statusCode == HttpStatusCodes.STATUS_CODE_NO_CONTENT || statusCode == HttpStatusCodes.STATUS_CODE_NOT_MODIFIED) { ignore(); return false; } return true; } /** * Parses the content of the HTTP response from {@link #getContent()} and reads it into a data * type of key/value pairs using the parser returned by {@link HttpRequest#getParser()}. * * @return parsed data type instance or {@code null} for no content * @since 1.10 */ public Object parseAs(Type dataType) throws IOException { if (!hasMessageBody()) { return null; } return request.getParser().parseAndClose(getContent(), getContentCharset(), dataType); } /** * Parses the content of the HTTP response from {@link #getContent()} and reads it into a string. * *

Since this method returns {@code ""} for no content, a simpler check for no content is to * check if {@link #getContent()} is {@code null}. * *

All content is read from the input content stream rather than being limited by the * Content-Length. For the character set, it follows the specification by parsing the "charset" * parameter of the Content-Type header or by default {@code "ISO-8859-1"} if the parameter is * missing. * * @return parsed string or {@code ""} for no content * @throws IOException I/O exception */ public String parseAsString() throws IOException { InputStream content = getContent(); if (content == null) { return ""; } ByteArrayOutputStream out = new ByteArrayOutputStream(); IOUtils.copy(content, out); return out.toString(getContentCharset().name()); } /** * Returns the {@link Charset} specified in the Content-Type of this response or the {@code * "ISO-8859-1"} charset as a default. * * @since 1.10 */ public Charset getContentCharset() { return mediaType == null || mediaType.getCharsetParameter() == null ? Charsets.ISO_8859_1 : mediaType.getCharsetParameter(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy