org.littleshoot.proxy.impl.ProxyUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of littleproxy Show documentation
Show all versions of littleproxy Show documentation
LittleProxy is a high performance HTTP proxy written in Java and using the Netty networking framework.
package org.littleshoot.proxy.impl;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.TimeZone;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utilities for the proxy.
*/
public class ProxyUtils {
private static final Logger LOG = LoggerFactory.getLogger(ProxyUtils.class);
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
/**
* Date format pattern used to parse HTTP date headers in RFC 1123 format.
*/
private static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
private static final String hostName;
static {
try {
final InetAddress localAddress = NetworkUtils.getLocalHost();
hostName = localAddress.getHostName();
} catch (final UnknownHostException e) {
LOG.error("Could not lookup host", e);
throw new IllegalStateException("Could not determine host!", e);
}
final StringBuilder sb = new StringBuilder();
sb.append("Via: 1.1 ");
sb.append(hostName);
sb.append("\r\n");
}
// Should never be constructed.
private ProxyUtils() {
}
// Schemes are case-insensitive:
// http://tools.ietf.org/html/rfc3986#section-3.1
private static Pattern HTTP_PREFIX = Pattern.compile("^https?://.*",
Pattern.CASE_INSENSITIVE);
/**
* Strips the host from a URI string. This will turn "http://host.com/path"
* into "/path".
*
* @param uri
* The URI to transform.
* @return A string with the URI stripped.
*/
public static String stripHost(final String uri) {
if (!HTTP_PREFIX.matcher(uri).matches()) {
// It's likely a URI path, not the full URI (i.e. the host is
// already stripped).
return uri;
}
final String noHttpUri = StringUtils.substringAfter(uri, "://");
final int slashIndex = noHttpUri.indexOf("/");
if (slashIndex == -1) {
return "/";
}
final String noHostUri = noHttpUri.substring(slashIndex);
return noHostUri;
}
/**
* Formats the given date according to the RFC 1123 pattern.
*
* @param date
* The date to format.
* @return An RFC 1123 formatted date string.
*
* @see #PATTERN_RFC1123
*/
public static String formatDate(final Date date) {
return formatDate(date, PATTERN_RFC1123);
}
/**
* Formats the given date according to the specified pattern. The pattern
* must conform to that used by the {@link SimpleDateFormat simple date
* format} class.
*
* @param date
* The date to format.
* @param pattern
* The pattern to use for formatting the date.
* @return A formatted date string.
*
* @throws IllegalArgumentException
* If the given date pattern is invalid.
*
* @see SimpleDateFormat
*/
public static String formatDate(final Date date, final String pattern) {
if (date == null)
throw new IllegalArgumentException("date is null");
if (pattern == null)
throw new IllegalArgumentException("pattern is null");
final SimpleDateFormat formatter = new SimpleDateFormat(pattern,
Locale.US);
formatter.setTimeZone(GMT);
return formatter.format(date);
}
/**
* Creates a Date formatted for HTTP headers for the current time.
*
* @return The formatted HTTP date.
*/
public static String httpDate() {
return formatDate(new Date());
}
/**
* If an HttpObject implements the market interface LastHttpContent, it
* represents the last chunk of a transfer.
*
* @see io.netty.handler.codec.http.LastHttpContent
*
* @param httpObject
* @return
*
*/
public static boolean isLastChunk(final HttpObject httpObject) {
return httpObject instanceof LastHttpContent;
}
/**
* If an HttpObject is not the last chunk, then that means there are other
* chunks that will follow.
*
* @see io.netty.handler.codec.http.FullHttpMessage
*
* @param httpObject
* @return
*/
public static boolean isChunked(final HttpObject httpObject) {
return !isLastChunk(httpObject);
}
/**
* Parses the host and port an HTTP request is being sent to.
*
* @param httpRequest
* The request.
* @return The host and port string.
*/
public static String parseHostAndPort(final HttpRequest httpRequest) {
final String uriHostAndPort = parseHostAndPort(httpRequest.getUri());
return uriHostAndPort;
}
/**
* Parses the host and port an HTTP request is being sent to.
*
* @param uri
* The URI.
* @return The host and port string.
*/
public static String parseHostAndPort(final String uri) {
final String tempUri;
if (!HTTP_PREFIX.matcher(uri).matches()) {
// Browsers particularly seem to send requests in this form when
// they use CONNECT.
tempUri = uri;
} else {
// We can't just take a substring from a hard-coded index because it
// could be either http or https.
tempUri = StringUtils.substringAfter(uri, "://");
}
final String hostAndPort;
if (tempUri.contains("/")) {
hostAndPort = tempUri.substring(0, tempUri.indexOf("/"));
} else {
hostAndPort = tempUri;
}
return hostAndPort;
}
/**
* Make a copy of the response including all mutable fields.
*
* @param original
* The original response to copy from.
* @return The copy with all mutable fields from the original.
*/
public static HttpResponse copyMutableResponseFields(
final HttpResponse original) {
HttpResponse copy = null;
if (original instanceof DefaultFullHttpResponse) {
ByteBuf content = ((DefaultFullHttpResponse) original).content();
copy = new DefaultFullHttpResponse(original.getProtocolVersion(),
original.getStatus(), content);
} else {
copy = new DefaultHttpResponse(original.getProtocolVersion(),
original.getStatus());
}
final Collection headerNames = original.headers().names();
for (final String name : headerNames) {
final List values = original.headers().getAll(name);
copy.headers().set(name, values);
}
return copy;
}
/**
* Adds the Via header to specify that the message has passed through the
* proxy.
*
* @param msg
* The HTTP message.
*/
public static void addVia(final HttpMessage msg) {
final StringBuilder sb = new StringBuilder();
sb.append(msg.getProtocolVersion().majorVersion());
sb.append(".");
sb.append(msg.getProtocolVersion().minorVersion());
sb.append(".");
sb.append(hostName);
final List vias;
if (msg.headers().contains(HttpHeaders.Names.VIA)) {
vias = msg.headers().getAll(HttpHeaders.Names.VIA);
vias.add(sb.toString());
} else {
vias = Arrays.asList(sb.toString());
}
msg.headers().set(HttpHeaders.Names.VIA, vias);
}
/**
* Returns true
if the specified string is either "true" or
* "on" ignoring case.
*
* @param val
* The string in question.
* @return true
if the specified string is either "true" or
* "on" ignoring case, otherwise false
.
*/
public static boolean isTrue(final String val) {
return checkTrueOrFalse(val, "true", "on");
}
/**
* Returns true
if the specified string is either "false" or
* "off" ignoring case.
*
* @param val
* The string in question.
* @return true
if the specified string is either "false" or
* "off" ignoring case, otherwise false
.
*/
public static boolean isFalse(final String val) {
return checkTrueOrFalse(val, "false", "off");
}
public static boolean extractBooleanDefaultFalse(final Properties props,
final String key) {
final String throttle = props.getProperty(key);
if (StringUtils.isNotBlank(throttle)) {
return throttle.trim().equalsIgnoreCase("true");
}
return false;
}
public static boolean extractBooleanDefaultTrue(final Properties props,
final String key) {
final String throttle = props.getProperty(key);
if (StringUtils.isNotBlank(throttle)) {
return throttle.trim().equalsIgnoreCase("true");
}
return true;
}
public static int extractInt(final Properties props, final String key) {
final String readThrottleString = props.getProperty(key);
if (StringUtils.isNotBlank(readThrottleString) &&
NumberUtils.isNumber(readThrottleString)) {
return Integer.parseInt(readThrottleString);
}
return -1;
}
public static boolean isCONNECT(HttpObject httpObject) {
return httpObject instanceof HttpRequest
&& HttpMethod.CONNECT.equals(((HttpRequest) httpObject)
.getMethod());
}
private static boolean checkTrueOrFalse(final String val,
final String str1, final String str2) {
final String str = val.trim();
return StringUtils.isNotBlank(str)
&& (str.equalsIgnoreCase(str1) || str.equalsIgnoreCase(str2));
}
}