/*
* Copyright 2015 Odnoklassniki Ltd, Mail.Ru Group
*
* 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 one.nio.http;
import one.nio.net.Session;
import one.nio.net.Socket;
import one.nio.util.Utf8;
import java.io.IOException;
import java.util.LinkedList;
public class HttpSession extends Session {
private static final int MAX_HEADERS = 48;
private static final int MAX_FRAGMENT_LENGTH = 2048;
private static final int MAX_PIPELINE_LENGTH = 256;
protected final HttpServer server;
protected final LinkedList<Request> pipeline = new LinkedList<Request>();
protected final byte[] fragment = new byte[MAX_FRAGMENT_LENGTH];
protected int fragmentLength;
protected Request parsing;
protected Request handling;
public HttpSession(Socket socket, HttpServer server) {
super(socket);
this.server = server;
}
@Override
public int checkStatus(long currentTime, long keepAlive) {
long lastAccessTime = this.lastAccessTime;
if (lastAccessTime < currentTime - keepAlive) {
if (queueHead == null && handling == null) {
return IDLE;
} else if (lastAccessTime < currentTime - keepAlive * 8) {
return STALE;
}
}
return ACTIVE;
}
@Override
protected void processRead(byte[] buffer) throws IOException {
int length = fragmentLength;
if (length > 0) {
System.arraycopy(fragment, 0, buffer, 0, length);
}
length += super.read(buffer, length, buffer.length - length);
try {
int processed = processHttpBuffer(buffer, length);
length -= processed;
if (length > 0) {
if (length > MAX_FRAGMENT_LENGTH) {
throw new HttpException("Line too long");
}
System.arraycopy(buffer, processed, fragment, 0, length);
}
fragmentLength = length;
} catch (HttpException e) {
if (log.isDebugEnabled()) {
log.debug("Bad request", e);
}
writeError(Response.BAD_REQUEST, e.getMessage());
}
}
protected synchronized int processHttpBuffer(byte[] buffer, int length) throws IOException, HttpException {
int lineStart = 0;
for (int i = 0; i < length; i++) {
if (buffer[i] != '\n') continue;
int lineLength = i - lineStart;
if (i > 0 && buffer[i - 1] == '\r') lineLength--;
if (parsing == null) {
parsing = parseRequest(buffer, lineStart, lineLength);
} else if (lineLength > 0) {
if (parsing.getHeaderCount() < MAX_HEADERS) {
parsing.addHeader(Utf8.read(buffer, lineStart, lineLength));
}
} else {
if (closing) {
return i + 1;
} else if (handling == null) {
server.handleRequest(handling = parsing, this);
} else if (pipeline.size() < MAX_PIPELINE_LENGTH) {
pipeline.addLast(parsing);
} else {
throw new IOException("Pipeline length exceeded");
}
parsing = null;
}
lineStart = i + 1;
}
return lineStart;
}
protected Request parseRequest(byte[] buffer, int start, int length) throws HttpException {
boolean http11 = length > 13 && buffer[start + length - 1] == '1';
if (length > 13 && Utf8.startsWith(Request.VERB_GET, buffer, start)) {
return new Request(Request.METHOD_GET, Utf8.read(buffer, start + 4, length - 13), http11);
} else if (length > 14 && Utf8.startsWith(Request.VERB_POST, buffer, start)) {
return new Request(Request.METHOD_POST, Utf8.read(buffer, start + 5, length - 14), http11);
} else if (length > 14 && Utf8.startsWith(Request.VERB_HEAD, buffer, start)) {
return new Request(Request.METHOD_HEAD, Utf8.read(buffer, start + 5, length - 14), http11);
} else if (length > 17 && Utf8.startsWith(Request.VERB_OPTIONS, buffer, start)) {
return new Request(Request.METHOD_OPTIONS, Utf8.read(buffer, start + 8, length - 17), http11);
}
throw new HttpException("Invalid request");
}
public synchronized void writeResponse(Response response) throws IOException {
if (handling == null) {
throw new IOException("Out of order response");
}
server.incRequestsProcessed();
String connection = handling.getHeader("Connection: ");
boolean keepAlive = handling.isHttp11()
? !"close".equalsIgnoreCase(connection)
: "Keep-Alive".equalsIgnoreCase(connection);
response.addHeader(keepAlive ? "Connection: Keep-Alive" : "Connection: close");
byte[] bytes = response.toBytes(handling.getMethod() != Request.METHOD_HEAD);
super.write(bytes, 0, bytes.length);
if (!keepAlive) scheduleClose();
if ((handling = pipeline.pollFirst()) != null) {
server.handleRequest(handling, this);
}
}
public void writeError(String code, String message) throws IOException {
server.incRequestsRejected();
Response response = new Response(code, message == null ? Response.EMPTY : Utf8.toBytes(message));
response.addHeader("Connection: close");
byte[] bytes = response.toBytes(true);
super.write(bytes, 0, bytes.length);
scheduleClose();
}
}