All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.davfx.ninio.http.HttpRequestReader Maven / Gradle / Ivy

package com.davfx.ninio.http;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.davfx.ninio.common.Address;
import com.davfx.ninio.common.ByteBufferHandler;
import com.davfx.ninio.common.CloseableByteBufferHandler;

final class HttpRequestReader implements CloseableByteBufferHandler {
	private static final Logger LOGGER = LoggerFactory.getLogger(HttpRequestReader.class);

	private final LineReader lineReader = new LineReader();
	private boolean headersRead = false;
	private boolean requestLineRead = false;
	private long contentLength;
	private int countRead = 0;
	private HttpRequest.Method requestMethod;
	private String requestPath;
	private boolean http11;
	private boolean enableGzip = false;
	private final Map headers = new HashMap();
	private boolean failClose = false;
	private boolean closed = false;
	private final Address address;
	private final boolean secure;

	private final HttpServerHandler handler;
	private final CloseableByteBufferHandler write;
	
	private final Map headerSanitization = new HashMap();
	
	public HttpRequestReader(Address address, boolean secure, HttpServerHandler handler, CloseableByteBufferHandler write) {
		this.address = address;
		this.secure = secure;
		this.handler = handler;
		this.write = write;

		headerSanitization.put(Http.CONTENT_LENGTH.toLowerCase(), Http.CONTENT_LENGTH);
		headerSanitization.put(Http.CONTENT_ENCODING.toLowerCase(), Http.CONTENT_ENCODING);
		headerSanitization.put(Http.CONTENT_TYPE.toLowerCase(), Http.CONTENT_TYPE);
		headerSanitization.put(Http.ACCEPT_ENCODING.toLowerCase(), Http.ACCEPT_ENCODING);
		headerSanitization.put(Http.TRANSFER_ENCODING.toLowerCase(), Http.TRANSFER_ENCODING);
	}
	
	private void addHeader(String headerLine) throws IOException {
		int i = headerLine.indexOf(Http.HEADER_KEY_VALUE_SEPARATOR);
		if (i < 0) {
			throw new IOException("Invalid header: " + headerLine);
		}
		String key = headerLine.substring(0, i);
		String sanitizedKey = headerSanitization.get(key.toLowerCase());
		if (sanitizedKey != null) {
			key = sanitizedKey;
		}
		String value = headerLine.substring(i + 1).trim();
		headers.put(key, value);
	}
	private void setRequestLine(String requestLine) throws IOException {
		int i = requestLine.indexOf(Http.START_LINE_SEPARATOR);
		if (i < 0) {
			throw new IOException("Invalid request: " + requestLine);
		}
		int j = requestLine.indexOf(Http.START_LINE_SEPARATOR, i + 1);
		if (j < 0) {
			throw new IOException("Invalid request: " + requestLine);
		}
		requestMethod = null;
		String m = requestLine.substring(0, i);
		for (HttpRequest.Method method : HttpRequest.Method.values()) {
			if (method.toString().equals(m)) {
				requestMethod = method;
				break;
			}
		}
		if (requestMethod == null) {
			throw new IOException("Invalid request: " + requestLine);
		}
		requestPath = requestLine.substring(i + 1, j);
		String requestVersion = requestLine.substring(j + 1);
		if (requestVersion.equals(Http.HTTP10)) {
			http11 = false;
		} else if (requestVersion.equals(Http.HTTP11)) {
			http11 = true;
		} else {
			throw new IOException("Unsupported version");
		}
	}
	
	@Override
	public void close() {
		LOGGER.debug("Closing");
		if (failClose) {
			if (!closed) {
				closed = true;
				handler.failed(new IOException("Connection reset by peer"));
			}
		} else {
			if (!closed) {
				closed = true;
				handler.close();
			}
		}
	}
	
	@Override
	public void handle(Address address, ByteBuffer buffer) {
		if (!buffer.hasRemaining()) {
			return;
		}
		try {
			failClose = true;
			
			while (!requestLineRead) {
				String line = lineReader.handle(buffer);
				if (line == null) {
					return;
				}
				LOGGER.trace("Request line: {}", line);
				setRequestLine(line);
				requestLineRead = true;
			}
	
			while (!headersRead) {
				String line = lineReader.handle(buffer);
				if (line == null) {
					return;
				}
				if (line.isEmpty()) {
					LOGGER.trace("Header line empty");
					headersRead = true;
					String accept = headers.get(Http.ACCEPT_ENCODING);
					if (accept != null) {
						String[] list = accept.split("\\" + Http.MULTIPLE_SEPARATOR);
						for (String s : list) {
							String[] v = s.trim().split("\\" + Http.EXTENSION_SEPARATOR);
							if (v.length > 0) {
								if (v[0].trim().equalsIgnoreCase(Http.GZIP)) {
									enableGzip = true;
									break;
								}
							}
						}
					}
					handler.handle(new HttpRequest(address, secure, requestMethod, requestPath, headers));
					String contentLengthValue = headers.get(Http.CONTENT_LENGTH);
					if (contentLengthValue != null) {
						try {
							contentLength = Long.parseLong(contentLengthValue);
						} catch (NumberFormatException e) {
							throw new IOException("Invalid Content-Length: " + contentLengthValue);
						}
					} else {
						contentLength = 0;
						handler.ready(new InnerWrite()); // Yes, can be so cool for ws://
					}
				} else {
					LOGGER.trace("Header line: {}", line);
					addHeader(line);
				}
			}

			if (contentLength == 0) {
				if (buffer.hasRemaining()) {
					handler.handle(null, buffer);
				}
			} else {
				if (countRead < contentLength) {
					if (buffer.hasRemaining()) {
						ByteBuffer d = buffer;
						long toRead = contentLength - countRead;
						if (buffer.remaining() > toRead) {
							d = buffer.duplicate();
							d.limit((int) (buffer.position() + toRead));
							buffer.position((int) (buffer.position() + toRead));
						}
						countRead += d.remaining();
						handler.handle(null, d);
					}
				}
		
				if (countRead == contentLength) {
					failClose = false;
					countRead = 0;
					requestLineRead = false; // another connection possible
					headersRead = false;
					handler.ready(new InnerWrite());
				}
			}
		} catch (IOException e) {
			if (!closed) {
				closed = true;
				write.close();
				handler.failed(e);
			}
		}
	}
	
	private final class InnerWrite implements HttpServerHandler.Write {
		private long countWrite = 0;
		private long writeContentLength = -1;
		private boolean chunked = false;
		private GzipWriter gzipWriter;
		private boolean innerClosed = false;
		
		public InnerWrite() {
		}
		
		@Override
		public void close() {
			if (innerClosed) {
				return;
			}
			
			innerClosed = true;
			
			if (gzipWriter != null) {
				gzipWriter.close();
			}
			
			if (http11) {
				if (chunked) {
					write.handle(address, LineReader.toBuffer(Integer.toHexString(0)));
					write.handle(address, LineReader.toBuffer(""));
					return;
				}
				
				if ((writeContentLength >= 0) && (countWrite == writeContentLength)) {
					failClose = false;
					countRead = 0;
					requestLineRead = false; // another connection possible
					headersRead = false;
					return; // keep alive
				}
			}
			
			closed = true;
			
			write.close();
		}
		
		@Override
		public void failed(IOException error) {
			if (innerClosed) {
				return;
			}
			
			closed = true;
			write.close();
		}

		@Override
		public void write(HttpResponse response) {
			if (innerClosed) {
				return;
			}
			
			if (http11) {
				if (enableGzip && Http.GZIP.equalsIgnoreCase(response.getHeaders().get(Http.CONTENT_ENCODING))) {
					gzipWriter = new GzipWriter(new ByteBufferHandler() {
						@Override
						public void handle(Address address, ByteBuffer buffer) {
							doWrite(buffer);
						}
					});
					chunked = true;
					// deflated length != source length
				} else {
					chunked = Http.CHUNKED.equalsIgnoreCase(response.getHeaders().get(Http.TRANSFER_ENCODING));
				}
			}
			
			String contentLengthValue = response.getHeaders().get(Http.CONTENT_LENGTH);
			if (contentLengthValue != null) {
				try {
					writeContentLength = Integer.parseInt(contentLengthValue);
				} catch (NumberFormatException e) {
				}
			} else {
				// Don't fallback anymore because of websockets // chunked = true; // Forced
			}
			
			write.handle(null, LineReader.toBuffer((http11 ? Http.HTTP11 : Http.HTTP10) + Http.START_LINE_SEPARATOR + response.getStatus() + Http.START_LINE_SEPARATOR + response.getReason()));
			for (Map.Entry h : response.getHeaders().entrySet()) {
				String k = h.getKey();
				String v = h.getValue();
				if (gzipWriter != null) {
					if (k.equals(Http.CONTENT_LENGTH)) {
						continue;
					}
					if (k.equals(Http.TRANSFER_ENCODING)) {
						continue;
					}
				}
				if (!http11) {
					if (k.equals(Http.CONTENT_ENCODING)) {
						continue;
					}
				}
				write.handle(null, LineReader.toBuffer(k + Http.HEADER_KEY_VALUE_SEPARATOR + Http.HEADER_BEFORE_VALUE + v));
			}
			if (chunked) {
				write.handle(null, LineReader.toBuffer(Http.TRANSFER_ENCODING + Http.HEADER_KEY_VALUE_SEPARATOR + Http.HEADER_BEFORE_VALUE + Http.CHUNKED));
			}
			write.handle(null, LineReader.toBuffer(""));
		}
		
		@Override
		public void handle(Address address, ByteBuffer buffer) {
			if (innerClosed) {
				return;
			}
			
			if (gzipWriter != null) {
				gzipWriter.handle(buffer);
			} else {
				doWrite(buffer);
			}
		}
		
		private void doWrite(ByteBuffer buffer) {
			if (!buffer.hasRemaining()) {
				return;
			}
			if (chunked) {
				write.handle(null, LineReader.toBuffer(Integer.toHexString(buffer.remaining())));
			}
			countWrite += buffer.remaining();
			write.handle(null, buffer);
			if (chunked) {
				write.handle(null, LineReader.toBuffer(""));
			}
		}
	}
	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy