BlueMap/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpRequest.java

258 lines
7.1 KiB
Java

package de.bluecolored.bluemap.common.web.http;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HttpRequest {
private static final Pattern REQUEST_PATTERN = Pattern.compile("^(\\w+) (\\S+) (.+)$");
// reading helper
private final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
private final StringBuffer lineBuffer = new StringBuffer();
private boolean complete = false;
private boolean headerComplete = false;
private final List<String> headerLines = new ArrayList<>(20);
// request data
private final InetAddress source;
private String method, address, version;
private final Map<String, HttpHeader> headers = new HashMap<>();
private byte[] data;
// lazy parsed
private String path = null;
private String getParamString = null;
private Map<String, String> getParams = null;
public HttpRequest(InetAddress source) {
this.source = source;
}
public boolean write(ReadableByteChannel channel) throws IOException {
if (complete) return true;
int read = channel.read(byteBuffer);
if (read == 0) return false;
if (read == -1) {
channel.close();
return false;
}
byteBuffer.flip();
try {
// read headers
while (!headerComplete) {
if (!writeLine()) return false;
String line = lineBuffer.toString().stripTrailing();
lineBuffer.setLength(0);
if (line.isEmpty()) {
headerComplete = true;
parseHeaders();
} else {
headerLines.add(line);
}
}
if (hasHeaderValue("transfer-encoding", "chunked")) {
writeChunkedBody();
} else {
HttpHeader contentLengthHeader = getHeader("content-length");
int contentLength = 0;
if (contentLengthHeader != null) {
try {
contentLength = Integer.parseInt(contentLengthHeader.getValue().trim());
} catch (NumberFormatException ex) {
throw new IOException("Invalid HTTP Request: content-length is not a number", ex);
}
}
if (contentLength > 0) {
writeBody(contentLength);
}
}
complete = true;
return true;
} finally {
byteBuffer.compact();
}
}
private void writeChunkedBody() {
// TODO
}
private void writeBody(int length) {
// TODO
}
private void parseHeaders() throws IOException {
if (headerLines.isEmpty()) throw new IOException("Invalid HTTP Request: No Header");
Matcher m = REQUEST_PATTERN.matcher(headerLines.get(0));
if (!m.find()) throw new IOException("Invalid HTTP Request: Request-Pattern not matching");
method = m.group(1);
if (method == null) throw new IOException("Invalid HTTP Request: Request-Pattern not matching (method)");
address = m.group(2);
if (address == null) throw new IOException("Invalid HTTP Request: Request-Pattern not matching (address)");
version = m.group(3);
if (version == null) throw new IOException("Invalid HTTP Request: Request-Pattern not matching (version)");
headers.clear();
for (int i = 1; i < headerLines.size(); i++) {
String line = headerLines.get(i);
if (line.trim().isEmpty()) continue;
String[] kv = line.split(":", 2);
if (kv.length < 2) continue;
headers.put(kv[0].trim().toLowerCase(Locale.ROOT), new HttpHeader(kv[0], kv[1]));
}
}
private boolean writeLine() {
while (lineBuffer.length() <= 0 || lineBuffer.charAt(lineBuffer.length() - 1) != '\n'){
if (!byteBuffer.hasRemaining()) return false;
lineBuffer.append((char) byteBuffer.get());
}
return true;
}
public InetAddress getSource() {
return source;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
this.path = null;
this.getParams = null;
this.getParamString = null;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public Map<String, HttpHeader> getHeaders() {
return headers;
}
public HttpHeader getHeader(String header) {
return this.headers.get(header.toLowerCase(Locale.ROOT));
}
public boolean hasHeaderValue(String key, String value) {
HttpHeader header = getHeader(key);
if (header == null) return false;
return header.contains(value);
}
public byte[] getData() {
return data;
}
public InputStream getDataStream() {
return new ByteArrayInputStream(data);
}
public String getPath() {
if (path == null) parseAddress();
return path;
}
public void setPath(String path) {
this.path = path;
}
public Map<String, String> getGETParams() {
if (getParams == null) parseGetParams();
return getParams;
}
public String getGETParamString() {
if (getParamString == null) parseAddress();
return getParamString;
}
public void setGetParamString(String getParamString) {
this.getParamString = getParamString;
this.getParams = null;
}
private void parseAddress() {
String address = this.getAddress();
if (address.isEmpty()) address = "/";
String[] addressParts = address.split("\\?", 2);
String path = addressParts[0];
this.getParamString = addressParts.length > 1 ? addressParts[1] : "";
this.path = path;
}
private void parseGetParams() {
Map<String, String> getParams = new HashMap<>();
for (String getParam : this.getGETParamString().split("&")){
if (getParam.isEmpty()) continue;
String[] kv = getParam.split("=", 2);
String key = kv[0];
String value = kv.length > 1 ? kv[1] : "";
getParams.put(key, value);
}
this.getParams = getParams;
}
public boolean isComplete() {
return complete;
}
public void clear() {
byteBuffer.clear();
lineBuffer.setLength(0);
complete = false;
headerComplete = false;
headerLines.clear();
method = null;
address = null;
version = null;
headers.clear();
data = null;
path = null;
getParamString = null;
getParams = null;
}
}