org.neogroup.httpserver.HttpExchange Maven / Gradle / Ivy
package org.neogroup.httpserver;
import org.neogroup.util.MimeUtils;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.text.MessageFormat;
import java.util.*;
public class HttpExchange {
private static final String QUERY_PARAMETERS_REGEX = "[&]";
private static final String QUERY_PARAMETER_VALUES_REGEX = "[=]";
private static final String FILE_ENCODING_SYSTEM_PROPERTY_NAME = "file.encoding";
private static final String URI_SEPARATOR = "/";
private static final int REQUEST_READ_BUFFER_SIZE = 2048;
private static final byte LINE_SEPARATOR_CR = '\r';
private static final byte LINE_SEPARATOR_LF = '\n';
private static final String STATUS_LINE_FIELD_SEPARATOR = " ";
private static final String STATUS_LINE_TEMPLATE = "HTTP/1.1 {0} {1}\r\n";
private static final String HEADER_LINE_TEMPLATE = "{0}: {1}\r\n";
private static final int HEADER_SEPARATOR = ':';
private static final int HEADERS_WRITE_BUFFER_SIZE = 2048;
private static final int BODY_WRITE_BUFFER_SIZE = 8192;
private final HttpConnection connection;
private HttpSession session;
private Map cookies;
private HttpMethod requestMethod;
private URI requestUri;
private String requestVersion;
private Map> requestHeaders;
private Map requestParameters;
private byte[] requestBody;
private int responseCode;
private Map> responseHeaders;
private ByteBuffer responseBodyBuffer;
private int responseBodySize;
private boolean responseHeadersSent;
/**
* Constructor for the http exchange
* @param connection connection associated with the exchange
*/
protected HttpExchange(HttpConnection connection) {
this.connection = connection;
this.requestHeaders = new LinkedHashMap<>();
this.responseHeaders = new LinkedHashMap<>();
this.responseBodyBuffer = ByteBuffer.allocate(BODY_WRITE_BUFFER_SIZE);
}
/**
* Starts the new http exchange
*/
protected void startNewExchange() throws HttpBadRequestException {
//Clear exchange values
session = null;
cookies = null;
requestMethod = null;
requestUri = null;
requestVersion = null;
requestHeaders.clear();
requestParameters = null;
requestBody = null;
responseHeaders.clear();
responseCode = HttpResponseCode.HTTP_OK;
responseBodyBuffer.clear();
responseHeadersSent = false;
responseBodySize = 0;
//Read request
byte[] readData = null;
try (ByteArrayOutputStream readStream = new ByteArrayOutputStream()) {
int totalReadSize = 0;
int readSize = 0;
ByteBuffer readBuffer = ByteBuffer.allocate(REQUEST_READ_BUFFER_SIZE);
do {
readSize = connection.getChannel().read(readBuffer);
if (readSize == -1) {
throw new HttpException("Socket closed !!");
}
if (readSize > 0) {
readStream.write(readBuffer.array(), 0, readSize);
readBuffer.rewind();
totalReadSize += readSize;
}
} while (readSize > 0);
if (totalReadSize > 0) {
readStream.flush();
readData = readStream.toByteArray();
}
}
catch (Exception ex) {
throw new HttpBadRequestException("Error reading request !!", ex);
}
if (readData != null) {
boolean processedStatusLine = false;
boolean processedRequest = false;
try {
int startLineIndex = 0;
for (int i = 0; i < readData.length - 1; i++) {
if (readData[i] == LINE_SEPARATOR_CR && readData[i + 1] == LINE_SEPARATOR_LF) {
String currentLine = new String(readData, startLineIndex, i - startLineIndex);
startLineIndex = i + 2;
if (!currentLine.isEmpty()) {
if (!processedStatusLine) {
processStatusLine(currentLine);
processedStatusLine = true;
} else {
processHeaderLine(currentLine);
}
} else {
if (processedStatusLine) {
int bodySize = readData.length - (i + 2);
requestBody = Arrays.copyOfRange(readData, i + 2, readData.length);
processedRequest = true;
}
break;
}
}
}
}
catch (Exception exception) {
throw new HttpBadRequestException("Malformed request !!", exception);
}
if (!processedRequest) {
throw new HttpBadRequestException("Incomplete request !!");
}
}
else {
throw new HttpBadRequestException("Empty request !!");
}
}
/**
* Parses a status line
* @param statusLine String with the status line
* @throws Exception
*/
private void processStatusLine (String statusLine) throws Exception {
String[] parts = statusLine.split(STATUS_LINE_FIELD_SEPARATOR);
requestMethod = HttpMethod.valueOf(parts[0]);
requestUri = new URI(parts[1]);
requestVersion = parts[2];
}
/**
* Parses the header line
* @param headerLine String with the header line
* @throws Exception
*/
private void processHeaderLine (String headerLine) throws Exception {
int separatorIndex = headerLine.indexOf(HEADER_SEPARATOR);
String headerName = headerLine.substring(0, separatorIndex);
String headerValue = headerLine.substring(separatorIndex+1).trim();
List headerValues = requestHeaders.get(headerName);
if (headerValues == null) {
headerValues = new ArrayList<>();
requestHeaders.put(headerName, headerValues);
}
headerValues.add(headerValue);
}
/**
* Retrieves the requestMethod of the request
* @return requestMethod
*/
public HttpMethod getRequestMethod() {
return requestMethod;
}
/**
* Retrieves the requestUri of the request
* @return requestUri
*/
public URI getRequestUri() {
return requestUri;
}
/**
* Retrieves the query of the request
* @return query
*/
public String getRequestQuery() {
return requestUri.getRawQuery();
}
/**
* Retrieves the path of the request
* @return path
*/
public String getRequestPath() {
return requestUri.getRawPath();
}
/**
* Retrieve the path parts
* @return array of path parts
*/
public List getRequestPathParts() {
String path = getRequestPath();
String[] pathTokens = path.split(URI_SEPARATOR);
return Arrays.asList(pathTokens);
}
/**
* Retrieves the requestVersion of the request
* @return requestVersion
*/
public String getRequestVersion() {
return requestVersion;
}
/**
* Retrieve the requestHeaders of the request
* @return requestHeaders
*/
public Map> getRequestHeaders() {
return Collections.unmodifiableMap(requestHeaders);
}
/**
* Retrieve all the requestHeaders for a given header name
* @param headerName name of header
* @return List of header values
*/
public List getRequestHeaders (String headerName) {
return requestHeaders.get(headerName);
}
/**
* Indicates if a header name exists or not
* @param headerName name of the header
* @return boolean
*/
public boolean hasRequestHeader (String headerName) {
return requestHeaders.containsKey(headerName);
}
/**
* Retrieve the first header value for the given header name
* @param headerName name of header
* @return header value
*/
public String getRequestHeader (String headerName) {
String value = null;
List headerValues = requestHeaders.get(headerName);
if (headerValues != null) {
value = headerValues.get(0);
}
return value;
}
/**
* Retrieves the requestBody of a request
* @return requestBody
*/
public byte[] getRequestBody() {
return requestBody;
}
/**
* Retrieve the http requestParameters of a request
* @return map of requestParameters
*/
public Map getRequestParameters() {
if (requestParameters == null) {
requestParameters = new HashMap<>();
addParametersFromQuery(requestParameters, getRequestQuery());
String requestContentType = getRequestHeader(HttpHeader.CONTENT_TYPE);
if (requestContentType != null && requestContentType.equals(HttpHeader.APPLICATION_FORM_URL_ENCODED)) {
addParametersFromQuery(requestParameters, new String(getRequestBody()));
}
}
return requestParameters;
}
/**
* Retrieve the value of a parameter
* @param name name of a parameter
* @return value of a parameter
*/
public String getRequestParameter (String name) {
return getRequestParameters().get(name);
}
/**
* Sets a request parameter
* @param name name of the parameter
* @param value value of the parameter
*/
public void setRequestParameter (String name, String value) {
getRequestParameters().put(name, value);
}
/**
* Indicates if a parameter exists or not
* @param name name of the parameter to check
* @return boolean
*/
public boolean hasRequestParameter (String name) {
return getRequestParameters().containsKey(name);
}
/**
* Obtains the response code of a response
* @return int response code
*/
public int getResponseCode() {
return responseCode;
}
/**
* Set response code of a response
* @param responseCode int response code
*/
public void setResponseCode(int responseCode) {
this.responseCode = responseCode;
}
/**
* Retrieve the responseHeaders of a response
* @return Headers of the response
*/
public Map> getResponseHeaders() {
return Collections.unmodifiableMap(responseHeaders);
}
/**
* Add a new header to the response
* @param headerName Header name
* @param headerValue Header value
*/
public void addResponseHeader(String headerName, String headerValue) {
List headerValues = responseHeaders.get(headerName);
if (headerValues == null) {
headerValues = new ArrayList<>();
responseHeaders.put(headerName, headerValues);
}
headerValues.add(headerValue);
}
/**
* Retrieve the value of a header
* @param headerName name of the header
* @return value of the header
*/
public String getResponseHeader (String headerName) {
String value = null;
List headerValues = responseHeaders.get(headerName);
if (headerValues != null) {
value = headerValues.get(0);
}
return value;
}
/**
* Retrieve all the values for a given header
* @param headerName name of the header
* @return values for a header
*/
public List getResponseHeaders (String headerName) {
return responseHeaders.get(headerName);
}
/**
* Remove all responseHeaders with a given name
* @param headerName name of the header
*/
public void removeResponseHeader (String headerName) {
responseHeaders.remove(headerName);
}
/**
* Indicates if the response contains a given header
* @param headerName name of the header
* @return boolean
*/
public boolean hasResponseHeader (String headerName) {
return responseHeaders.containsKey(headerName);
}
/**
* Remove all responseHeaders
*/
public void clearResponseHeaders () {
responseHeaders.clear();
}
/**
* Sets the content of the response
* @param body content of the response
*/
public void setResponseBody(String body) {
setResponseBody(body.getBytes());
}
/**
* Sets the content of the response
* @param body content of the response
*/
public void setResponseBody(byte[] body) {
write(body);
}
/**
* Writes content in the response
* @param text text to write in the response
*/
public void write (String text) {
write(text.getBytes());
}
/**
* Write content in the response
* @param bytes bytes to write in the response
*/
public void write (byte[] bytes) {
responseBodySize += bytes.length;
int remainingBytes = bytes.length;
int writeIndex = 0;
while (remainingBytes > 0) {
int remainingBufferBytes = responseBodyBuffer.remaining();
if (remainingBytes > remainingBufferBytes) {
responseBodyBuffer.put(bytes, writeIndex, remainingBufferBytes);
writeBuffer();
writeIndex += remainingBufferBytes;
remainingBytes -= remainingBufferBytes;
}
else {
responseBodyBuffer.put(bytes, writeIndex, remainingBytes);
break;
}
}
}
/**
* Flushes content in the response
*/
public void flush () {
writeBuffer();
}
/**
* Get a map of cookies by name
* @return map of cookies
*/
private Map getCookiesMap() {
if (cookies == null) {
cookies = new HashMap<>();
String cookieHeader = getRequestHeader(HttpHeader.COOKIE);
if (cookieHeader != null) {
String[] cookieHeaderTokens = cookieHeader.split(";");
for (String cookieHeaderToken : cookieHeaderTokens) {
String[] cookieParts = cookieHeaderToken.trim().split("=");
if (cookieParts.length == 2) {
String cookieName = cookieParts[0];
String cookieValue = cookieParts[1];
cookies.put(cookieName, new HttpCookie(cookieName, cookieValue));
}
else {
String cookieName = cookieParts[0];
cookies.put(cookieName, new HttpCookie(cookieName, ""));
}
}
}
}
return cookies;
}
/**
* Adds a new cookie to the response
* @param cookie cookie to add
*/
public void addCookie (HttpCookie cookie) {
StringBuilder cookieValue = new StringBuilder();
cookieValue.append(cookie.getName());
cookieValue.append("=");
cookieValue.append(cookie.getValue());
if (cookie.getExpires() != null) {
cookieValue.append("; Expires=");
cookieValue.append(HttpServerUtils.formatDate(cookie.getExpires()));
}
if (cookie.getMaxAge() != null) {
cookieValue.append("; Max-Age=");
cookieValue.append(cookie.getMaxAge());
}
if (cookie.getDomain() != null) {
cookieValue.append("; Domain=").append(cookie.getDomain());
}
if (cookie.getPath() != null) {
cookieValue.append("; Path=").append(cookie.getPath());
}
if (cookie.getSecure() != null) {
cookieValue.append("; Secure");
}
if (cookie.getSecure() != null) {
cookieValue.append("; HttpOnly");
}
addResponseHeader(HttpHeader.SET_COOKIE, cookieValue.toString());
getCookiesMap().put(cookie.getName(), cookie);
}
/**
* Obtain a cookie by its name
* @param cookieName name of cookie
* @return http cookie
*/
public HttpCookie getCookie (String cookieName) {
return getCookiesMap().get(cookieName);
}
/**
* Retrieve the cookies of a request
* @return list of cookies
*/
public Collection getCookies () {
return getCookiesMap().values();
}
/**
* Get the current session associated with the exchange
* @return http session
*/
public HttpSession getSession() {
return getSession(false);
}
/**
* Get the current session associated with the exchange
* @param create boolean that indicates if the session must be created
* @return http session
*/
public HttpSession getSession(boolean create) {
if (create) {
session = connection.getServer().createSession(connection);
}
else {
if (session == null) {
session = connection.getServer().getSession(connection);
if (session == null) {
session = connection.getServer().createSession(connection);
}
}
}
return session;
}
/**
* Send responseHeaders with the response
*/
private void sendHeaders () {
if (!responseHeadersSent) {
if (!hasResponseHeader(HttpHeader.CONTENT_TYPE)) {
addResponseHeader(HttpHeader.CONTENT_TYPE, MimeUtils.TEXT_PLAIN);
}
if (!hasResponseHeader(HttpHeader.CONTENT_LENGTH)) {
addResponseHeader(HttpHeader.CONTENT_LENGTH, String.valueOf(responseBodySize));
}
try {
ByteBuffer headersBuffer = ByteBuffer.allocate(HEADERS_WRITE_BUFFER_SIZE);
//Writing status line
headersBuffer.put(MessageFormat.format(STATUS_LINE_TEMPLATE, responseCode, HttpResponseCode.msg(responseCode)).getBytes());
headersBuffer.flip();
connection.getChannel().write(headersBuffer);
//Writing responseHeaders
for (String headerName : responseHeaders.keySet()) {
List headerValues = responseHeaders.get(headerName);
for (String headerValue : headerValues) {
headersBuffer.clear();
headersBuffer.put(MessageFormat.format(HEADER_LINE_TEMPLATE, headerName, headerValue).getBytes());
headersBuffer.flip();
connection.getChannel().write(headersBuffer);
}
}
//Writing separator
headersBuffer.clear();
headersBuffer.put(LINE_SEPARATOR_CR);
headersBuffer.put(LINE_SEPARATOR_LF);
headersBuffer.flip();
connection.getChannel().write(headersBuffer);
}
catch (Throwable ex) {
throw new HttpException("Error writing responseHeaders !!", ex);
}
responseHeadersSent = true;
}
}
/**
* Writes the buffered content
*/
private void writeBuffer() {
sendHeaders();
responseBodyBuffer.flip();
try {
connection.getChannel().write(responseBodyBuffer);
}
catch (Exception ex) {
throw new HttpException("Error writing data !!", ex);
}
responseBodyBuffer.clear();
}
/**
* Retrieve requestParameters from a query string
* @param query query string
*/
private void addParametersFromQuery(Map parameters, String query) {
try {
if (query != null) {
String pairs[] = query.split(QUERY_PARAMETERS_REGEX);
for (String pair : pairs) {
String param[] = pair.split(QUERY_PARAMETER_VALUES_REGEX);
String key = null;
String value = null;
if (param.length > 0) {
key = URLDecoder.decode(param[0], System.getProperty(FILE_ENCODING_SYSTEM_PROPERTY_NAME));
}
if (param.length > 1) {
value = URLDecoder.decode(param[1], System.getProperty(FILE_ENCODING_SYSTEM_PROPERTY_NAME));
}
parameters.put(key, value);
}
}
}
catch (Exception ex) {
throw new HttpException("Error reading request requestParameters !!", ex);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy