
io.datakernel.http.HttpServerConnection Maven / Gradle / Ivy
/*
* Copyright (C) 2015-2018 SoftIndex LLC.
*
* 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 io.datakernel.http;
import io.datakernel.bytebuf.ByteBuf;
import io.datakernel.common.concurrent.ThreadLocalCharArray;
import io.datakernel.common.exception.UncheckedException;
import io.datakernel.common.parse.ParseException;
import io.datakernel.common.parse.UnknownFormatException;
import io.datakernel.csp.ChannelSupplier;
import io.datakernel.eventloop.Eventloop;
import io.datakernel.http.AsyncHttpServer.Inspector;
import io.datakernel.net.AsyncTcpSocket;
import io.datakernel.promise.Promise;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.InetAddress;
import java.util.Arrays;
import static io.datakernel.bytebuf.ByteBufStrings.*;
import static io.datakernel.http.HttpHeaders.CONNECTION;
import static io.datakernel.http.HttpMessage.MUST_LOAD_BODY;
import static io.datakernel.http.HttpMethod.*;
/**
* It represents server connection. It can receive {@link HttpRequest requests}
* from {@link AsyncHttpClient clients} and respond to them with
* {@link AsyncServlet async servlet}.
*/
final class HttpServerConnection extends AbstractHttpConnection {
private static final int HEADERS_SLOTS = 256;
private static final int MAX_PROBINGS = 2;
private static final HttpMethod[] METHODS = new HttpMethod[HEADERS_SLOTS];
static {
assert Integer.bitCount(METHODS.length) == 1;
nxt:
for (HttpMethod httpMethod : HttpMethod.values()) {
int hashCode = Arrays.hashCode(httpMethod.bytes);
for (int p = 0; p < MAX_PROBINGS; p++) {
int slot = (hashCode + p) & (METHODS.length - 1);
if (METHODS[slot] == null) {
METHODS[slot] = httpMethod;
continue nxt;
}
}
throw new IllegalArgumentException("HTTP METHODS hash collision, try to increase METHODS size");
}
}
private final InetAddress remoteAddress;
@Nullable
private HttpRequest request;
private final AsyncHttpServer server;
@Nullable
private final Inspector inspector;
private final AsyncServlet servlet;
private final char[] charBuffer;
private final int maxBodySize;
private static final byte[] EXPECT_100_CONTINUE = encodeAscii("100-continue");
private static final byte[] EXPECT_RESPONSE_CONTINUE = encodeAscii("HTTP/1.1 100 Continue\r\n\r\n");
/**
* Creates a new instance of HttpServerConnection
*
* @param eventloop eventloop which will handle its tasks
* @param remoteAddress an address of remote
* @param server server, which uses this connection
* @param servlet servlet for handling requests
*/
HttpServerConnection(Eventloop eventloop, InetAddress remoteAddress, AsyncTcpSocket asyncTcpSocket,
AsyncHttpServer server, AsyncServlet servlet, char[] charBuffer) {
super(eventloop, asyncTcpSocket);
this.server = server;
this.servlet = servlet;
this.remoteAddress = remoteAddress;
this.inspector = server.inspector;
this.charBuffer = charBuffer;
this.maxBodySize = server.maxBodySize;
}
public void serve() {
(pool = server.poolNew).addLastNode(this);
poolTimestamp = eventloop.currentTimeMillis();
socket.read().whenComplete(startLineConsumer);
}
@Override
public void onClosedWithError(@NotNull Throwable e) {
if (inspector != null) {
inspector.onHttpError(remoteAddress, e);
}
}
/**
* This method is called after received line of header.
*
* @param line received line of header.
*/
@SuppressWarnings("PointlessArithmeticExpression")
@Override
protected void onStartLine(byte[] line, int limit) throws ParseException {
switchPool(server.poolReadWrite);
HttpMethod method = getHttpMethod(line);
if (method == null) {
throw new UnknownFormatException(HttpServerConnection.class,
"Unknown HTTP method. First Bytes: " + Arrays.toString(line));
}
int urlStart = method.size + 1;
int urlEnd;
for (urlEnd = urlStart; urlEnd < limit; urlEnd++) {
if (line[urlEnd] == SP) {
break;
}
}
int p;
for (p = urlEnd + 1; p < limit; p++) {
if (line[p] != SP) {
break;
}
}
if (p + 7 < limit) {
boolean http11 = line[p + 0] == 'H' && line[p + 1] == 'T' && line[p + 2] == 'T' && line[p + 3] == 'P'
&& line[p + 4] == '/' && line[p + 5] == '1' && line[p + 6] == '.' && line[p + 7] == '1';
if (http11) {
flags |= KEEP_ALIVE; // keep-alive for HTTP/1.1
}
}
request = new HttpRequest(method,
UrlParser.parse(decodeAscii(line, urlStart, urlEnd - urlStart, ThreadLocalCharArray.ensure(charBuffer, urlEnd - urlStart))));
request.maxBodySize = maxBodySize;
if (method == GET || method == DELETE) {
contentLength = 0;
}
}
@Override
protected void onHeaderBuf(ByteBuf buf) {
//noinspection ConstantConditions
request.addHeaderBuf(buf);
}
private static HttpMethod getHttpMethod(byte[] line) {
boolean get = line[0] == 'G' && line[1] == 'E' && line[2] == 'T' && line[3] == SP;
if (get) {
return GET;
}
boolean post = line[0] == 'P' && line[1] == 'O' && line[2] == 'S' && line[3] == 'T' && line[4] == SP;
if (post) {
return POST;
}
return getHttpMethodFromMap(line);
}
private static HttpMethod getHttpMethodFromMap(byte[] line) {
int hashCode = 1;
for (int i = 0; i < 10; i++) {
byte b = line[i];
if (b == SP) {
for (int p = 0; p < MAX_PROBINGS; p++) {
int slot = (hashCode + p) & (METHODS.length - 1);
HttpMethod method = METHODS[slot];
if (method == null) {
break;
}
if (method.compareTo(line, 0, i)) {
return method;
}
}
return null;
}
hashCode = 31 * hashCode + b;
}
return null;
}
/**
* This method is called after receiving header. It sets its value to request.
*
* @param header received header
*/
@Override
protected void onHeader(HttpHeader header, byte[] array, int off, int len) throws ParseException {
if (header == HttpHeaders.EXPECT) {
if (equalsLowerCaseAscii(EXPECT_100_CONTINUE, array, off, len)) {
socket.write(ByteBuf.wrapForReading(EXPECT_RESPONSE_CONTINUE));
}
}
//noinspection ConstantConditions
if (request.headers.size() >= MAX_HEADERS) {
throw TOO_MANY_HEADERS;
}
request.addHeader(header, array, off, len);
}
private void writeHttpResponse(HttpResponse httpResponse) {
HttpHeaderValue connectionHeader = (flags & KEEP_ALIVE) != 0 ? CONNECTION_KEEP_ALIVE_HEADER : CONNECTION_CLOSE_HEADER;
if (server.maxKeepAliveRequests != 0) {
if (++numberOfKeepAliveRequests >= server.maxKeepAliveRequests) {
connectionHeader = CONNECTION_CLOSE_HEADER;
}
}
httpResponse.addHeader(CONNECTION, connectionHeader);
ByteBuf buf = renderHttpMessage(httpResponse);
if (buf != null) {
if ((flags & KEEP_ALIVE) != 0) {
eventloop.post(() -> writeBuf(buf));
} else {
writeBuf(buf);
}
} else {
writeHttpMessageAsStream(httpResponse);
}
httpResponse.recycle();
}
@Override
protected void onHeadersReceived(@Nullable ByteBuf body, @Nullable ChannelSupplier bodySupplier) {
//noinspection ConstantConditions
request.flags |= MUST_LOAD_BODY;
request.body = body;
request.bodyStream = bodySupplier;
request.setRemoteAddress(remoteAddress);
if (inspector != null) {
inspector.onHttpRequest(request);
}
switchPool(server.poolServing);
HttpRequest request = this.request;
Promise servletResult;
try {
servletResult = servlet.serveAsync(request);
} catch (UncheckedException u) {
servletResult = Promise.ofException(u.getCause());
}
servletResult.whenComplete((response, e) -> {
assert eventloop.inEventloopThread();
if (isClosed()) {
request.recycle();
if (response != null) {
response.recycle();
}
return;
}
if (e == null) {
if (inspector != null) {
inspector.onHttpResponse(request, response);
}
switchPool(server.poolReadWrite);
writeHttpResponse(response);
} else {
if (inspector != null) {
inspector.onServletException(request, e);
}
switchPool(server.poolReadWrite);
writeException(e);
}
if (request.bodyStream != null) {
request.bodyStream.streamTo(BUF_RECYCLER);
request.bodyStream = null;
}
});
}
@Override
protected void onBodyReceived() {
if ((flags & (BODY_SENT | BODY_RECEIVED)) == (BODY_SENT | BODY_RECEIVED) && pool != server.poolServing) {
onHttpMessageComplete();
}
}
@Override
protected void onBodySent() {
if ((flags & (BODY_SENT | BODY_RECEIVED)) == (BODY_SENT | BODY_RECEIVED) && pool != server.poolServing) {
onHttpMessageComplete();
}
}
private void onHttpMessageComplete() {
assert !isClosed();
if (request != null) {
request.recycle();
request = null;
}
if ((flags & KEEP_ALIVE) != 0 && server.keepAliveTimeoutMillis != 0) {
switchPool(server.poolKeepAlive);
flags = 0;
try {
readHttpMessage();
} catch (ParseException e) {
closeWithError(e);
}
} else {
close();
}
}
private void writeException(Throwable e) {
writeHttpResponse(server.formatHttpError(e));
}
@Override
protected void onClosed() {
if (request != null && pool != server.poolServing) {
request.recycle();
request = null;
}
//noinspection ConstantConditions
pool.removeNode(this);
//noinspection AssertWithSideEffects,ConstantConditions
assert (pool = null) == null;
server.onConnectionClosed();
}
@Override
public String toString() {
return "HttpServerConnection{" +
"remoteAddress=" + remoteAddress +
',' + super.toString() +
'}';
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy