![JAR search and dependency download from the Maven repository](/logo.png)
com.github.netty.protocol.servlet.util.HttpHeaderUtil Maven / Gradle / Ivy
package com.github.netty.protocol.servlet.util;
import io.netty.handler.codec.http.*;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author wangzihao
*/
public class HttpHeaderUtil {
private static final int ARRAY_SIZE = 128;
private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE];
private static final String CONTENT_TYPE_URLENCODED = "application/x-www-form-urlencoded";
static {
boolean[] IS_CONTROL = new boolean[ARRAY_SIZE];
boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++) {
// Control> 0-31, 127
if (i < 32 || i == 127) {
IS_CONTROL[i] = true;
}
// Separator
if (i == '(' || i == ')' || i == '<' || i == '>' || i == '@' ||
i == ',' || i == ';' || i == ':' || i == '\\' || i == '\"' ||
i == '/' || i == '[' || i == ']' || i == '?' || i == '=' ||
i == '{' || i == '}' || i == ' ' || i == '\t') {
IS_SEPARATOR[i] = true;
}
// Token: Anything 0-127 that is not a control and not a separator
if (!IS_CONTROL[i] && !IS_SEPARATOR[i]) {
IS_TOKEN[i] = true;
}
}
}
public static List splitProtocolsHeader(CharSequence header) {
StringBuilder builder = new StringBuilder(header.length());
List protocols = new ArrayList<>(2);
for (int i = 0; i < header.length(); ++i) {
char c = header.charAt(i);
if (Character.isWhitespace(c)) {
continue;
}
if (c == ',') {
protocols.add(builder.toString());
builder.setLength(0);
} else {
builder.append((char) Character.toLowerCase((int) c & 0xff));
}
}
if (builder.length() > 0) {
protocols.add(builder.toString());
}
return protocols;
}
public static boolean isFormUrlEncoder(String contentType) {
if (contentType == null || contentType.length() < CONTENT_TYPE_URLENCODED.length()) {
return false;
}
for (int i = 0, len = CONTENT_TYPE_URLENCODED.length(); i < len; i++) {
char c1 = contentType.charAt(i);
char c2 = CONTENT_TYPE_URLENCODED.charAt(i);
if (c1 == c2) {
continue;
}
if (Character.toLowerCase(c1) == Character.toLowerCase(c2)) {
continue;
}
return false;
}
return true;
}
/**
* Returns {@code true} if and only if the connection can remain open and
* thus 'kept alive'. This methods respects the value of the
* {@code "Connection"} header first and then the return value of
* {@link HttpVersion#isKeepAliveDefault()}.
*
* @param message message
* @return boolean isKeepAlive
*/
public static boolean isKeepAlive(HttpRequest message) {
HttpHeaders headers = message.headers();
Object connectionValueObj = headers.get(HttpHeaderConstants.CONNECTION);
//如果协议支持
if (message.getProtocolVersion().isKeepAliveDefault()) {
//不包含close就是保持
return connectionValueObj == null || "".equals(connectionValueObj) ||
!HttpHeaderConstants.CLOSE.toString().equalsIgnoreCase(connectionValueObj.toString());
} else {
//如果协议不支持, 有keep-alive就是保持
return connectionValueObj != null &&
HttpHeaderConstants.KEEP_ALIVE.toString().equalsIgnoreCase(connectionValueObj.toString());
}
}
/**
* Sets the value of the {@code "Connection"} header depending on the
* protocol version of the specified message. This getMethod sets or removes
* the {@code "Connection"} header depending on what the default keep alive
* mode of the message's protocol version is, as specified by
* {@link HttpVersion#isKeepAliveDefault()}.
*
* - If the connection is kept alive by default:
*
* - set to {@code "close"} if {@code keepAlive} is {@code false}.
* - remove otherwise.
*
* - If the connection is closed by default:
*
* - set to {@code "keep-alive"} if {@code keepAlive} is {@code true}.
* - remove otherwise.
*
*
*
* @param message message
* @param keepAlive keepAlive
*/
public static void setKeepAlive(HttpResponse message, boolean keepAlive) {
HttpHeaders h = message.headers();
if (message.protocolVersion().isKeepAliveDefault()) {
if (keepAlive) {
h.remove(HttpHeaderConstants.CONNECTION);
} else {
h.set(HttpHeaderConstants.CONNECTION, HttpHeaderConstants.CLOSE);
}
} else {
if (keepAlive) {
h.set(HttpHeaderConstants.CONNECTION, HttpHeaderConstants.KEEP_ALIVE);
} else {
h.remove(HttpHeaderConstants.CONNECTION);
}
}
}
public static boolean isUnsupportedExpectation(HttpMessage message) {
if (!isExpectHeaderValid(message)) {
return false;
}
final String expectValue = message.headers().get(HttpHeaderNames.EXPECT);
return expectValue != null && !HttpHeaderValues.CONTINUE.toString().equalsIgnoreCase(expectValue);
}
private static boolean isExpectHeaderValid(final HttpMessage message) {
/*
* Expect: 100-continue is for requests only and it works only on HTTP/1.1 or later. Note further that RFC 7231
* section 5.1.1 says "A server that receives a 100-continue expectation in an HTTP/1.0 request MUST ignore
* that expectation."
*/
return message instanceof HttpRequest &&
message.protocolVersion().compareTo(HttpVersion.HTTP_1_1) >= 0;
}
/**
* Returns the length of the content. Please note that this value is
* not retrieved from {@link HttpContent#content()} but from the
* {@code "Content-Length"} header, and thus they are independent from each
* other.
*
* @param message message
* @param defaultValue defaultValue
* @return the content length or {@code defaultValue} if this message does
* not have the {@code "Content-Length"} header or its value is not
* a number
*/
public static long getContentLength(HttpMessage message, long defaultValue) {
String str = message.headers().get(HttpHeaderConstants.CONTENT_LENGTH);
if (str != null && str.length() > 0) {
return Long.parseLong(str);
}
// We know the content length if it's a Web Socket message even if
// Content-Length header is missing.
long webSocketContentLength = getWebSocketContentLength(message);
if (webSocketContentLength != -1) {
return webSocketContentLength;
}
// Otherwise we don't.
return defaultValue;
}
/**
* Returns the content length of the specified web socket message. If the
* specified message is not a web socket message, {@code -1} is returned.
*/
private static int getWebSocketContentLength(HttpMessage message) {
// WebSockset messages have constant content-lengths.
HttpHeaders h = message.headers();
if (message instanceof HttpRequest) {
HttpRequest req = (HttpRequest) message;
if (HttpMethod.GET.equals(req.method()) &&
h.contains(HttpHeaderConstants.SEC_WEBSOCKET_KEY1) &&
h.contains(HttpHeaderConstants.SEC_WEBSOCKET_KEY2)) {
return 8;
}
} else if (message instanceof HttpResponse) {
HttpResponse res = (HttpResponse) message;
if (res.status().code() == HttpResponseStatus.SWITCHING_PROTOCOLS.code() &&
h.contains(HttpHeaderConstants.SEC_WEBSOCKET_ORIGIN) &&
h.contains(HttpHeaderConstants.SEC_WEBSOCKET_LOCATION)) {
return 16;
}
}
// Not a web socket message
return -1;
}
/**
* Checks to see if the transfer encoding in a specified {@link HttpMessage} is chunked
*
* @param headers The message to check
* @return True if transfer encoding is chunked, otherwise false
*/
public static boolean isTransferEncodingChunked(HttpHeaders headers) {
return headers.contains(HttpHeaderConstants.TRANSFER_ENCODING, HttpHeaderConstants.CHUNKED, true);
}
/**
* Sets the block transport header
*
* @param headers The header of the set
* @param chunked Whether to block transmission, is to add header information, otherwise remove header information
*/
public static void setTransferEncodingChunked(HttpHeaders headers, boolean chunked) {
if (chunked) {
headers.set(HttpHeaderConstants.TRANSFER_ENCODING, HttpHeaderConstants.CHUNKED);
headers.remove(HttpHeaderConstants.CONTENT_LENGTH);
} else {
List values = headers.getAll(HttpHeaderConstants.TRANSFER_ENCODING);
if (values.isEmpty()) {
return;
}
Iterator valuesIt = values.iterator();
while (valuesIt.hasNext()) {
String value = String.valueOf(valuesIt.next());
if (HttpHeaderConstants.CHUNKED.toString().equalsIgnoreCase(value)) {
valuesIt.remove();
}
}
if (values.isEmpty()) {
headers.remove(HttpHeaderConstants.TRANSFER_ENCODING);
} else {
headers.set(HttpHeaderConstants.TRANSFER_ENCODING, values);
}
}
}
private static boolean isToken(int c) {
// Fast for correct values, slower for incorrect ones
if (c < 0 || c >= IS_TOKEN.length) {
return false;
}
return IS_TOKEN[c];
}
// Skip any LWS and position to read the next character. The next character
// is returned as being able to 'peek()' it allows a small optimisation in
// some cases.
private static int skipLws(Reader input) throws IOException {
input.mark(1);
int c = input.read();
while (c == 32 || c == 9 || c == 10 || c == 13) {
input.mark(1);
c = input.read();
}
input.reset();
return c;
}
private static SkipResult skipConstant(Reader input, String constant) throws IOException {
int len = constant.length();
skipLws(input);
input.mark(len);
int c = input.read();
for (int i = 0; i < len; i++) {
if (i == 0 && c == -1) {
return SkipResult.EOF;
}
if (c != constant.charAt(i)) {
input.reset();
return SkipResult.NOT_FOUND;
}
if (i != (len - 1)) {
c = input.read();
}
}
return SkipResult.FOUND;
}
/**
* @return the token if one was found, the empty string if no data was
* available to read or null
if data other than a
* token was found
*/
private static String readToken(Reader input) throws IOException {
StringBuilder result = new StringBuilder();
skipLws(input);
input.mark(1);
int c = input.read();
while (c != -1 && isToken(c)) {
result.append((char) c);
input.mark(1);
c = input.read();
}
// Use mark(1)/reset() rather than skip(-1) since skip() is a NOP
// once the end of the String has been reached.
input.reset();
if (c != -1 && result.length() == 0) {
return null;
} else {
return result.toString();
}
}
/**
* @return the digits if any were found, the empty string if no data was
* found or if data other than digits was found
*/
private static String readDigits(Reader input) throws IOException {
StringBuilder result = new StringBuilder();
skipLws(input);
input.mark(1);
int c = input.read();
while (c != -1 && Character.isDigit(c)) {
result.append((char) c);
input.mark(1);
c = input.read();
}
// Use mark(1)/reset() rather than skip(-1) since skip() is a NOP
// once the end of the String has been reached.
input.reset();
return result.toString();
}
/**
* @return the number if digits were found, -1 if no data was found
* or if data other than digits was found
*/
private static long readLong(Reader input) throws IOException {
String digits = readDigits(input);
if (digits.length() == 0) {
return -1;
}
return Long.parseLong(digits);
}
public enum SkipResult {
FOUND,
NOT_FOUND,
EOF
}
public static class Entry {
public final long start;
public final long end;
public Entry(long start, long end) {
this.start = start;
this.end = end;
}
}
public static class Ranges {
public final String units;
public final List entries;
private Ranges(String units, List entries) {
this.units = units;
this.entries = entries;
}
/**
* Parses a Range header from an HTTP header.
*
* @param input a reader over the header text
* @return a set of ranges parsed from the input, or null if not valid
* @throws IOException if there was a problem reading the input
*/
public static Ranges parse(StringReader input) throws IOException {
// Units (required)
String units = readToken(input);
if (units == null || units.isEmpty()) {
return null;
}
// Must be followed by '='
if (skipConstant(input, "=") == SkipResult.NOT_FOUND) {
return null;
}
// Range entries
List entries = new ArrayList<>();
SkipResult skipResult;
do {
long start = readLong(input);
// Must be followed by '-'
if (skipConstant(input, "-") == SkipResult.NOT_FOUND) {
return null;
}
long end = readLong(input);
if (start == -1 && end == -1) {
// Invalid range
return null;
}
entries.add(new Entry(start, end));
skipResult = skipConstant(input, ",");
if (skipResult == SkipResult.NOT_FOUND) {
// Invalid range
return null;
}
} while (skipResult == SkipResult.FOUND);
// There must be at least one entry
if (entries.isEmpty()) {
return null;
}
return new Ranges(units, entries);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy