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

io.github.parthappm.http.server.Server Maven / Gradle / Ivy

package io.github.parthappm.http.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * A server class to implement the HTTP server functionality.
 * The object of this class cannot be created directly, either create an object of HttpServer or HttpsServer class.
 */
public class Server
{
	private final String NAME;
	private final String VERSION;
	private ServerSocket serverSocket;
	private RequestProcessor requestProcessor;
	private int timeoutInMilliSeconds;

	Server()
	{
		this.NAME = "Nebula";
		this.VERSION = "HTTP/1.1";
		this.requestProcessor = new RequestProcessor();
		this.timeoutInMilliSeconds = 30000; // 30 seconds
	}

	void setServerSocket(ServerSocket serverSocket)
	{
		this.serverSocket = serverSocket;
	}

	/**
	 * Setter method to set the RequestProcessor for the server.
	 * @param requestProcessor The instance of the RequestProcessor to use to process client request.
	 * @return The reference of the current object for chaining.
	 */
	public Server setRequestProcessor(RequestProcessor requestProcessor)
	{
		if (requestProcessor != null)
		{
			this.requestProcessor = requestProcessor;
		}
		return this;
	}

	/**
	 * Setter method to set the time to wait data to read from socket before closing.
	 * @param duration Duration to wait before closing the socket connection while waiting for data to be read from socket.
	 * @return The reference of the current object for chaining.
	 */
	public Server setTimeout(Duration duration)
	{
		this.timeoutInMilliSeconds = (int) duration.toMillis();
		return this;
	}

	/**
	 * Start the server and start listening to new connection from clients.
	 */
	public void start()
	{
		new Thread(() -> {
			try
			{
				do
				{
					Socket socket = serverSocket.accept();
					new Thread(() -> handleRequest(socket)).start();
				} while (serverSocket != null);
			}
			catch (IOException e)
			{
				stop();
			}
		}).start();
		System.out.println("Server started...");
	}

	private void handleRequest(Socket socket)
	{
		try
		{
			// setting some string values
			String LINE_SEPARATOR = "\r\n";
			String CONTENT_LENGTH = "Content-Length";
			String CONNECTION = "Connection";
			String CONNECTION_CLOSE = "close";

			socket.setSoTimeout(timeoutInMilliSeconds);
			InputStream is = socket.getInputStream();
			OutputStream os = socket.getOutputStream();

			// keeping the connection open until the connection is closed by the client
			do
			{
				// reading the request line
				String method = readInputStream(is, ' ').toUpperCase();
				String url = readInputStream(is, ' ');
				readInputStream(is, '\n'); // reading the HTTP version

				// reading the request headers
				Map requestHeaders = new HashMap<>();
				do
				{
					String header = readInputStream(is, '\n');
					if (header.equals(""))
					{
						break;
					}
					else
					{
						int colonIndex = header.indexOf(':');
						String key = header.substring(0, colonIndex).trim();
						String value = header.substring(colonIndex + 1).trim();
						requestHeaders.put(key, value);
					}
				} while (true);

				// reading the request body
				byte[] requestBody;
				int bytesRead = 0;
				String contentLength = requestHeaders.get(CONTENT_LENGTH);
				if (contentLength != null)
				{
					int requestContentLength = Integer.parseInt(contentLength);
					requestBody = new byte[requestContentLength];
					do
					{
						int currentBytesRead =  is.read(requestBody, bytesRead, requestContentLength);
						bytesRead += currentBytesRead;
					} while (bytesRead < requestContentLength);
				}
				else
				{
					requestBody = new byte[0];
				}

				// extracting the path, request parameters and reference
				String[] parsedUrl = url.split("[?#]");
				String path = parsedUrl.length >= 1 ? URLDecoder.decode(parsedUrl[0], StandardCharsets.UTF_8) : "";
				String parametersString = parsedUrl.length >= 2 && url.contains("?") ? parsedUrl[1] : "";
				String anchor;
				if (parsedUrl.length >= 2)
				{
					if (parsedUrl.length >= 3)
					{
						anchor = URLDecoder.decode(parsedUrl[2], StandardCharsets.UTF_8);
					}
					else
					{
						anchor = url.contains("#") ? URLDecoder.decode(parsedUrl[1], StandardCharsets.UTF_8) : "";
					}
				}
				else
				{
					anchor = "";
				}
				Map requestParameters = new HashMap<>();
				if (parametersString.length() != 0)
				{
					for (String parameter : parametersString.split("&"))
					{
						String[] keyValue = parameter.split("=");
						if (keyValue.length >= 2)
						{
							requestParameters.put(URLDecoder.decode(keyValue[0], StandardCharsets.UTF_8), URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8));
						}
					}
				}

				// logging the request
				String clientAddress = socket.getInetAddress().getHostAddress();
				Logger.getInstance().log(clientAddress + " " + method + " " + path);

				// generating the response
				Request request = new Request(clientAddress, method, path, requestParameters, anchor, requestHeaders, Arrays.copyOfRange(requestBody, 0, bytesRead));
				Response response = requestProcessor.process(request);

				// sending the response status
				os.write((VERSION + " " + response.statusCode() + " " + response.statusText() + LINE_SEPARATOR).getBytes(StandardCharsets.UTF_8));

				// sending the headers
				Map responseHeaders = response.headers();
				responseHeaders.put("Date", (new Date()).toString());
				responseHeaders.put("Server", NAME);
				responseHeaders.put("Content-Length", String.valueOf(response.body().length));
				for (String key : responseHeaders.keySet())
				{
					if (key != null)
					{
						String value = responseHeaders.get(key);
						if (value != null)
						{
							String line = key + ": " + value + LINE_SEPARATOR;
							os.write(line.getBytes(StandardCharsets.UTF_8));
						}
					}
				}
				os.write(LINE_SEPARATOR.getBytes(StandardCharsets.UTF_8));

				// sending the body
				os.write(response.body());

				// checking the condition
				String connection = requestHeaders.get(CONNECTION);
				if (connection != null && connection.equals(CONNECTION_CLOSE))
				{
					break;
				}
			} while (true);
			socket.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
			try
			{
				socket.close();
			}
			catch (IOException ex)
			{
				ex.printStackTrace();
			}
		}
	}

	private String readInputStream(InputStream is, char endChar) throws IOException
	{
		StringBuilder temp = new StringBuilder();
		do
		{
			int b = is.read();
			if (b != -1)
			{
				temp.append((char) b);
				if (b == endChar)
				{
					break;
				}
			}
		} while (true);
		return temp.toString().trim();
	}

	/**
	 * Stop the server to listen
	 */
	public void stop()
	{
		if (serverSocket != null)
		{
			try
			{
				serverSocket.close();
				System.out.println("Server stopped!");
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
			serverSocket = null;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy