org.htmlunit.WebResponseData Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xlt Show documentation
Show all versions of xlt Show documentation
XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.
/*
* Copyright (c) 2002-2024 Gargoyle Software 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
* https://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 org.htmlunit;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import org.apache.commons.io.ByteOrderMark;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BOMInputStream;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.brotli.dec.BrotliInputStream;
import org.htmlunit.util.MimeType;
import org.htmlunit.util.NameValuePair;
/**
* Simple data object to simplify WebResponse creation.
*
* @author Brad Clarke
* @author Daniel Gredler
* @author Ahmed Ashour
* @author Ronald Brill
*/
public class WebResponseData implements Serializable {
private static final Log LOG = LogFactory.getLog(WebResponseData.class);
private final int statusCode_;
private final String statusMessage_;
private final List responseHeaders_;
private final DownloadedContent downloadedContent_;
/**
* Constructs with a raw byte[] (mostly for testing).
*
* @param body Body of this response
* @param statusCode Status code from the server
* @param statusMessage Status message from the server
* @param responseHeaders Headers in this response
*/
public WebResponseData(final byte[] body, final int statusCode, final String statusMessage,
final List responseHeaders) {
this(new DownloadedContent.InMemory(body), statusCode, statusMessage, responseHeaders);
}
/**
* Constructs without data stream for subclasses that override getBody().
*
* @param statusCode Status code from the server
* @param statusMessage Status message from the server
* @param responseHeaders Headers in this response
*/
protected WebResponseData(final int statusCode,
final String statusMessage, final List responseHeaders) {
this(ArrayUtils.EMPTY_BYTE_ARRAY, statusCode, statusMessage, responseHeaders);
}
/**
* Constructor.
* @param downloadedContent the downloaded content
* @param statusCode Status code from the server
* @param statusMessage Status message from the server
* @param responseHeaders Headers in this response
*/
public WebResponseData(final DownloadedContent downloadedContent, final int statusCode, final String statusMessage,
final List responseHeaders) {
statusCode_ = statusCode;
statusMessage_ = statusMessage;
responseHeaders_ = Collections.unmodifiableList(responseHeaders);
downloadedContent_ = downloadedContent;
}
private InputStream getStream(final ByteOrderMark... bomHeaders) throws IOException {
InputStream stream = downloadedContent_.getInputStream();
if (downloadedContent_.isEmpty()) {
return stream;
}
final List headers = getResponseHeaders();
final String encoding = getHeader(headers, "content-encoding");
if (encoding != null) {
boolean isGzip = StringUtils.contains(encoding, "gzip") && !"no-gzip".equals(encoding);
if ("gzip-only-text/html".equals(encoding)) {
isGzip = MimeType.TEXT_HTML.equals(getHeader(headers, "content-type"));
}
if (isGzip) {
try {
stream = new GZIPInputStream(stream);
}
catch (final IOException e) {
LOG.error("Reading gzip encodec content failed.", e);
stream.close();
stream = IOUtils.toInputStream(
"\n"
+ "Problem loading page \n"
+ "\n"
+ "Content Encoding Error
\n"
+ "The page you are trying to view cannot be shown because"
+ " it uses an invalid or unsupported form of compression.
\n"
+ "\n"
+ "", ISO_8859_1);
}
if (stream != null && bomHeaders != null) {
stream = new BOMInputStream(stream, bomHeaders);
}
return stream;
}
if ("br".equals(encoding)) {
try {
stream = new BrotliInputStream(stream);
}
catch (final IOException e) {
LOG.error("Reading Brotli encodec content failed.", e);
stream.close();
stream = IOUtils.toInputStream(
"\n"
+ "Problem loading page \n"
+ "\n"
+ "Content Encoding Error
\n"
+ "The page you are trying to view cannot be shown because"
+ " it uses an invalid or unsupported form of compression.
\n"
+ "\n"
+ "", ISO_8859_1);
}
return stream;
}
if (StringUtils.contains(encoding, "deflate")) {
boolean zlibHeader = false;
if (stream.markSupported()) { // should be always the case as the content is in a byte[] or in a file
stream.mark(2);
final byte[] buffer = new byte[2];
final int byteCount = stream.read(buffer, 0, 2);
zlibHeader = byteCount == 2 && (((buffer[0] & 0xff) << 8) | (buffer[1] & 0xff)) == 0x789c;
stream.reset();
}
if (zlibHeader) {
stream = new InflaterInputStream(stream);
}
else {
stream = new InflaterInputStream(stream, new Inflater(true));
}
return stream;
}
}
if (stream != null && bomHeaders != null) {
stream = new BOMInputStream(stream, bomHeaders);
}
return stream;
}
private static String getHeader(final List headers, final String name) {
for (final NameValuePair header : headers) {
final String headerName = header.getName().trim();
if (name.equalsIgnoreCase(headerName)) {
return header.getValue();
}
}
return null;
}
/**
* Returns the response body.
* This may cause memory problem for very large responses.
* @return response body
*/
public byte[] getBody() {
try (InputStream is = getInputStream()) {
return IOUtils.toByteArray(is);
}
catch (final IOException e) {
throw new RuntimeException(e); // shouldn't we allow the method to throw IOException?
}
}
/**
* Returns a new {@link InputStream} allowing to read the downloaded content.
* @return the associated InputStream
* @throws IOException in case of IO problems
*/
public InputStream getInputStream() throws IOException {
return getStream((ByteOrderMark[]) null);
}
/**
* INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
*
* @param bomHeaders the supported bomHeaders
* @return the associated InputStream wrapped with a bom input stream if applicable
* @throws IOException in case of IO problems
*/
public InputStream getInputStreamWithBomIfApplicable(final ByteOrderMark... bomHeaders) throws IOException {
return getStream(bomHeaders);
}
/**
* @return response headers
*/
public List getResponseHeaders() {
return responseHeaders_;
}
/**
* @return response status code
*/
public int getStatusCode() {
return statusCode_;
}
/**
* @return response status message
*/
public String getStatusMessage() {
return statusMessage_;
}
/**
* Returns length of the content data.
* @return the length
*/
public long getContentLength() {
return downloadedContent_.length();
}
/**
* Clean up the downloaded content.
*/
public void cleanUp() {
downloadedContent_.cleanUp();
}
}