From 31875114cf9c8c28feb1d8cd0d3e9f9f13a6fb04 Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:40:32 -0700 Subject: [PATCH 01/13] update HttpResponse.java Refs #38 --- .../com/wintermindset/http/HttpResponse.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/main/java/com/wintermindset/http/HttpResponse.java b/src/main/java/com/wintermindset/http/HttpResponse.java index e08ac49..3062941 100644 --- a/src/main/java/com/wintermindset/http/HttpResponse.java +++ b/src/main/java/com/wintermindset/http/HttpResponse.java @@ -7,6 +7,23 @@ import com.wintermindset.io.BufferedOutput; +/** + * Represents an HTTP/1.1 response. + * + *

This class is responsible for building and serializing HTTP responses. + * It stores the status line, headers and optional response body and provides + * helper methods for writing the response to a {@link BufferedOutput}.

+ * + *

Typical usage:

+ * + *
+ * HttpResponse resp = HttpResponse.ok("Hello world");
+ * resp.writeTo(out, true);
+ * 
+ * + *

The implementation is intentionally minimal and designed for a small + * custom HTTP server.

+ */ public final class HttpResponse { private int status = 200; @@ -14,32 +31,85 @@ public final class HttpResponse { private byte[] body = new byte[0]; private final Map headers = new LinkedHashMap<>(); + /** + * Creates a new HTTP response with a default {@code Server} header. + */ public HttpResponse() { header("Server", "Java-Loom"); } + /** + * Sets the HTTP status line. + * + * @param status HTTP status code + * @param reason reason phrase + * @return current response instance + */ public HttpResponse status(int status, String reason) { this.status = status; this.reason = reason; return this; } + /** + * Sets a text response body encoded as UTF-8. + * + *

This method also automatically sets the + * {@code Content-Type: text/plain; charset=utf-8} header.

+ * + * @param body response body string + * @return current response instance + */ public HttpResponse body(String body) { this.body = body.getBytes(StandardCharsets.UTF_8); header("Content-Type", "text/plain; charset=utf-8"); return this; } + /** + * Sets a binary response body. + * + * @param body response body bytes + * @return current response instance + */ public HttpResponse body(byte[] body) { this.body = body; return this; } + /** + * Adds or replaces an HTTP header. + * + * @param name header name + * @param value header value + * @return current response instance + */ public HttpResponse header(String name, String value) { headers.put(name, value); return this; } + /** + * Writes the HTTP response to the provided output. + * + *

This method serializes the full HTTP message including:

+ * + * + *

The method automatically sets:

+ * + * + * @param out output writer + * @param keepAlive whether the connection should remain open + * @throws IOException if writing fails + */ public void writeTo(BufferedOutput out, boolean keepAlive) throws IOException { out.writeAscii("HTTP/1.1 "); out.writeAscii(Integer.toString(status)); @@ -61,18 +131,41 @@ public void writeTo(BufferedOutput out, boolean keepAlive) throws IOException { out.flush(); } + /** + * Creates a standard {@code 200 OK} response with a text body. + * + * @param body response text + * @return HTTP response + */ public static HttpResponse ok(String body) { return new HttpResponse().status(200, "OK").body(body); } + /** + * Creates a {@code 400 Bad Request} response. + * + * @param msg error message + * @return HTTP response + */ public static HttpResponse badRequest(String msg) { return new HttpResponse().status(400, "Bad Request").body(msg); } + /** + * Creates a {@code 404 Not Found} response. + * + * @return HTTP response + */ public static HttpResponse notFound() { return new HttpResponse().status(404, "Not Found").body("Not Found"); } + /** + * Creates a {@code 500 Internal Server Error} response. + * + * @param msg error message + * @return HTTP response + */ public static HttpResponse internalError(String msg) { return new HttpResponse().status(500, "Internal Server Error").body(msg); } From 8158d996621686fa845f00ec4174da151a419621 Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:41:01 -0700 Subject: [PATCH 02/13] update HttpRequestParser.java Refs #38 --- .../wintermindset/http/HttpRequestParser.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/main/java/com/wintermindset/http/HttpRequestParser.java b/src/main/java/com/wintermindset/http/HttpRequestParser.java index bfc69ba..860e068 100644 --- a/src/main/java/com/wintermindset/http/HttpRequestParser.java +++ b/src/main/java/com/wintermindset/http/HttpRequestParser.java @@ -5,8 +5,44 @@ import com.wintermindset.io.BufferedInput; import com.wintermindset.io.BufferedInput.ByteSlice; +/** + * Parser for HTTP/1.1 requests. + * + *

This class reads raw bytes from {@link BufferedInput} and converts them + * into a {@link HttpRequest} object. The parser processes:

+ * + *
    + *
  • request line (method, path, version)
  • + *
  • HTTP headers
  • + *
  • optional message body
  • + *
+ * + *

The implementation is intentionally minimal and intended for educational + * purposes (e.g. implementing a simple HTTP server).

+ * + *

Limitations:

+ *
    + *
  • Only supports bodies with Content-Length
  • + *
  • No chunked encoding support
  • + *
  • No validation of HTTP version
  • + *
+ */ public final class HttpRequestParser { + /** + * Parses an HTTP request from the given buffered input stream. + * + *

The method reads:

+ *
    + *
  1. The request line
  2. + *
  3. All HTTP headers
  4. + *
  5. The request body (if {@code Content-Length} is present)
  6. + *
+ * + * @param in buffered input source containing the raw HTTP request + * @return parsed {@link HttpRequest} + * @throws IOException if the request cannot be read or the request line is empty + */ public HttpRequest parse(BufferedInput in) throws IOException { HttpRequest req = new HttpRequest(); ByteSlice requestLine = in.readLineSlice(); @@ -31,6 +67,19 @@ public HttpRequest parse(BufferedInput in) throws IOException { return req; } + /** + * Parses the HTTP request line. + * + *

Example request line:

+ * + *
+     * GET /index.html HTTP/1.1
+     * 
+ * + * @param slice byte slice containing the request line + * @param req request object to populate + * @throws IllegalArgumentException if the request line format is invalid + */ private void parseRequestLine(ByteSlice slice, HttpRequest req) { int p1 = indexOf(slice, (byte) ' '); int p2 = indexOf(slice, (byte) ' ', p1 + 1); @@ -42,6 +91,18 @@ private void parseRequestLine(ByteSlice slice, HttpRequest req) { req.version = ascii(slice, p2 + 1, slice.length - p2 - 1); } + /** + * Parses a single HTTP header line and stores it in the request. + * + *

Example header:

+ * + *
+     * Content-Type: application/json
+     * 
+ * + * @param slice byte slice containing the header line + * @param req request object to update + */ private void parseHeader(ByteSlice slice, HttpRequest req) { int colon = indexOf(slice, (byte) ':'); if (colon <= 0) { @@ -52,15 +113,37 @@ private void parseHeader(ByteSlice slice, HttpRequest req) { req.headers.put(name, value); } + /** + * Extracts the {@code Content-Length} header value. + * + * @param req parsed request + * @return body length in bytes, or {@code 0} if the header is not present + */ private int getContentLength(HttpRequest req) { String v = req.headers.get("content-length"); return v == null ? 0 : Integer.parseInt(v); } + /** + * Finds the first occurrence of a byte in the slice. + * + * @param slice source byte slice + * @param b byte to search for + * @return index of the byte or {@code -1} if not found + */ private int indexOf(ByteSlice slice, byte b) { return indexOf(slice, b, 0); } + /** + * Finds the first occurrence of a byte in the slice starting + * from the specified position. + * + * @param slice source byte slice + * @param b byte to search for + * @param from starting position + * @return index of the byte or {@code -1} if not found + */ private int indexOf(ByteSlice slice, byte b, int from) { for (int i = from; i < slice.length; i++) { if (slice.data[slice.offset + i] == b) { @@ -70,6 +153,17 @@ private int indexOf(ByteSlice slice, byte b, int from) { return -1; } + /** + * Converts a region of a byte slice to a {@link String}. + * + *

Used for decoding ASCII parts of the HTTP request such as + * method, path, version and header fields.

+ * + * @param s source slice + * @param off offset inside the slice + * @param len number of bytes + * @return decoded string + */ private String ascii(ByteSlice s, int off, int len) { return new String(s.data, s.offset + off, len); } From b1562d28c01c734ca7e5fc7290e57fb20f86ec8d Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:43:22 -0700 Subject: [PATCH 03/13] update HttpRequest.java Refs #38 --- .../com/wintermindset/http/HttpRequest.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main/java/com/wintermindset/http/HttpRequest.java b/src/main/java/com/wintermindset/http/HttpRequest.java index 97abd05..3ff2c4e 100644 --- a/src/main/java/com/wintermindset/http/HttpRequest.java +++ b/src/main/java/com/wintermindset/http/HttpRequest.java @@ -3,14 +3,54 @@ import java.util.HashMap; import java.util.Map; +/** + * Represents a parsed HTTP request. + * + *

This class stores the basic components of an HTTP request: + * the request line, headers, and optional message body.

+ * + *

The request is typically created by {@link HttpRequestParser} + * after parsing raw bytes received from a client connection.

+ * + *

Example HTTP request:

+ * + *
+ * GET /index.html HTTP/1.1
+ * Host: example.com
+ * Connection: keep-alive
+ * 
+ */ public final class HttpRequest { + /** HTTP method (e.g. GET, POST, PUT, DELETE). */ public String method; + + /** Request path or URI (e.g. "/index.html"). */ public String path; + + /** HTTP protocol version (e.g. "HTTP/1.1"). */ public String version; + + /** Map of HTTP headers. */ public final Map headers = new HashMap<>(); + + /** Optional request body. Empty for most GET requests. */ public byte[] body; + /** + * Determines whether the connection should remain open + * after this request according to HTTP semantics. + * + *

Rules implemented:

+ *
    + *
  • HTTP/1.1 uses persistent connections by default
  • + *
  • {@code Connection: close} disables keep-alive
  • + *
  • HTTP/1.0 requires {@code Connection: keep-alive}
  • + *
+ * + * @return {@code true} if the connection should be kept alive, + * otherwise {@code false} + */ public boolean keepAlive() { String connection = headers.getOrDefault("connection", ""); if ("HTTP/1.1".equals(version)) { From fd68cd2b2680946430260b734cb8721f8540bbd0 Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:44:40 -0700 Subject: [PATCH 04/13] update HttpConnection.java Refs #38 --- .../wintermindset/http/HttpConnection.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/main/java/com/wintermindset/http/HttpConnection.java b/src/main/java/com/wintermindset/http/HttpConnection.java index b1343b2..a9432c6 100644 --- a/src/main/java/com/wintermindset/http/HttpConnection.java +++ b/src/main/java/com/wintermindset/http/HttpConnection.java @@ -7,6 +7,28 @@ import java.io.IOException; import java.net.Socket; +/** + * Represents a single HTTP connection with a client. + * + *

This class is responsible for the full lifecycle of request handling + * over a TCP socket. It reads incoming HTTP requests, delegates processing + * to a {@link Handler}, and writes HTTP responses back to the client.

+ * + *

The connection supports persistent HTTP/1.1 connections (keep-alive) + * and may process multiple requests sequentially over the same socket.

+ * + *

Typical workflow:

+ *
    + *
  1. Read and parse an HTTP request
  2. + *
  3. Pass the request to a {@link Handler}
  4. + *
  5. Write the generated {@link HttpResponse}
  6. + *
  7. Repeat while the connection remains keep-alive
  8. + *
+ * + *

If a parsing error occurs, a {@code 400 Bad Request} response is sent. + * If the handler throws an exception, a {@code 500 Internal Server Error} + * response is returned.

+ */ public final class HttpConnection implements Runnable { private final Socket socket; @@ -15,6 +37,13 @@ public final class HttpConnection implements Runnable { private final BufferedOutput out; private final HttpRequestParser parser = new HttpRequestParser(); + /** + * Creates a new HTTP connection handler. + * + * @param socket client socket + * @param handler request handler + * @throws IOException if socket streams cannot be initialized + */ public HttpConnection(Socket socket, Handler handler) throws IOException { this.socket = socket; this.handler = handler; @@ -22,6 +51,20 @@ public HttpConnection(Socket socket, Handler handler) throws IOException { this.out = new BufferedOutput(socket.getOutputStream()); } + /** + * Main connection loop. + * + *

This method processes HTTP requests sequentially until the + * connection is closed or the client disables keep-alive.

+ * + *

Execution steps:

+ *
    + *
  • Parse an incoming HTTP request
  • + *
  • Delegate processing to the application handler
  • + *
  • Send the generated HTTP response
  • + *
  • Continue if the connection is persistent
  • + *
+ */ @Override public void run() { try (socket) { From 524e6dbe0e20b565082c11223bec08e0f6d734d0 Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:47:02 -0700 Subject: [PATCH 05/13] update BufferedInput.java Refs #38 --- .../com/wintermindset/io/BufferedInput.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/main/java/com/wintermindset/io/BufferedInput.java b/src/main/java/com/wintermindset/io/BufferedInput.java index 50c84c1..f0e2f3e 100644 --- a/src/main/java/com/wintermindset/io/BufferedInput.java +++ b/src/main/java/com/wintermindset/io/BufferedInput.java @@ -4,6 +4,25 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; +/** + * Buffered reader optimized for HTTP protocol parsing. + * + *

This class wraps an {@link InputStream} and provides efficient methods + * for reading HTTP request components such as CRLF-terminated lines and + * fixed-length bodies.

+ * + *

The implementation minimizes object allocations by returning lightweight + * {@link ByteSlice} views over the internal buffer instead of copying data + * when reading header lines.

+ * + *

Main features:

+ *
    + *
  • CRLF-based line reading (HTTP header format)
  • + *
  • Zero-copy line access via {@link ByteSlice}
  • + *
  • Buffered body reading
  • + *
  • Size limits for headers and bodies to prevent abuse
  • + *
+ */ public final class BufferedInput { private static final int DEFAULT_BUFFER_SIZE = 8 * 1024; @@ -16,15 +35,37 @@ public final class BufferedInput { private int limit = 0; private int scanPos = 0; + /** + * Creates a buffered reader with the default buffer size. + * + * @param in underlying input stream + */ public BufferedInput(InputStream in) { this(in, DEFAULT_BUFFER_SIZE); } + /** + * Creates a buffered reader with a custom buffer size. + * + * @param in underlying input stream + * @param bufferSize buffer size in bytes + */ public BufferedInput(InputStream in, int bufferSize) { this.in = in; this.buffer = new byte[bufferSize]; } + /** + * Reads a CRLF-terminated line from the input and returns it as a + * {@link ByteSlice}. + * + *

The returned slice references the internal buffer and does not + * allocate additional memory.

+ * + * @return slice representing the line (without CRLF) + * @throws IOException if the line exceeds {@link #MAX_HEADER_SIZE} + * or the connection is closed + */ public ByteSlice readLineSlice() throws IOException { while (true) { for (int i = scanPos; i + 1 < limit; i++) { @@ -43,11 +84,25 @@ public ByteSlice readLineSlice() throws IOException { } } + /** + * Reads a CRLF-terminated line and converts it to an ASCII string. + * + * @return decoded string + * @throws IOException if reading fails + */ public String readLine() throws IOException { ByteSlice slice = readLineSlice(); return slice.toStringAscii(); } + /** + * Reads a fixed-length HTTP body. + * + * @param length number of bytes to read + * @return body bytes + * @throws IOException if the body exceeds {@link #MAX_BODY_SIZE} + * or the connection is closed + */ public byte[] readBody(int length) throws IOException { if (length > MAX_BODY_SIZE) { throw new IOException("HTTP body too large"); @@ -66,6 +121,12 @@ public byte[] readBody(int length) throws IOException { return body; } + /** + * Fills the internal buffer with additional data from the stream. + * + * @throws IOException if the client closes the connection or the request + * exceeds allowed size limits + */ private void fill() throws IOException { if (position > 0) { compact(); @@ -80,6 +141,10 @@ private void fill() throws IOException { } } + /** + * Compacts the buffer by moving unread data to the beginning. + * This frees space for additional reads. + */ private void compact() { int remaining = limit - position; System.arraycopy(buffer, position, buffer, 0, remaining); @@ -88,10 +153,21 @@ private void compact() { scanPos = 0; } + /** + * Closes the underlying input stream. + * + * @throws IOException if closing fails + */ public void close() throws IOException { in.close(); } + /** + * Lightweight immutable view over a portion of a byte array. + * + *

This class avoids copying data by referencing the original buffer + * along with an offset and length.

+ */ public static final class ByteSlice { public final byte[] data; public final int offset; @@ -103,6 +179,11 @@ public static final class ByteSlice { this.length = length; } + /** + * Converts the slice to an ASCII string. + * + * @return decoded string + */ public String toStringAscii() { return new String(data, offset, length, StandardCharsets.US_ASCII); } From 9a9b4f2bba28c96d6ed0cab2ac6db549a8189084 Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:48:46 -0700 Subject: [PATCH 06/13] update BufferedOutput.java Refs #38 --- .../com/wintermindset/io/BufferedOutput.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/main/java/com/wintermindset/io/BufferedOutput.java b/src/main/java/com/wintermindset/io/BufferedOutput.java index 6f88c44..2ca1dea 100644 --- a/src/main/java/com/wintermindset/io/BufferedOutput.java +++ b/src/main/java/com/wintermindset/io/BufferedOutput.java @@ -4,6 +4,26 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; +/** + * Buffered writer optimized for HTTP response serialization. + * + *

This class wraps an {@link OutputStream} and provides buffered + * write operations to reduce the number of system calls when sending + * data over a network connection.

+ * + *

The buffer accumulates written bytes and flushes them to the + * underlying stream when the buffer becomes full or when + * {@link #flush()} is explicitly called.

+ * + *

Typical usage:

+ * + *
+ * BufferedOutput out = new BufferedOutput(socket.getOutputStream());
+ * out.writeAscii("HTTP/1.1 200 OK");
+ * out.writeCRLF();
+ * out.flush();
+ * 
+ */ public final class BufferedOutput { private static final int DEFAULT_BUFFER_SIZE = 8 * 1024; @@ -12,19 +32,47 @@ public final class BufferedOutput { private final byte[] buffer; private int position = 0; + /** + * Creates a buffered output wrapper with the default buffer size. + * + * @param out underlying output stream + */ public BufferedOutput(OutputStream out) { this(out, DEFAULT_BUFFER_SIZE); } + /** + * Creates a buffered output wrapper with a custom buffer size. + * + * @param out underlying output stream + * @param bufferSize size of the internal buffer + */ public BufferedOutput(OutputStream out, int bufferSize) { this.out = out; this.buffer = new byte[bufferSize]; } + /** + * Writes the entire byte array to the buffer. + * + * @param data data to write + * @throws IOException if writing fails + */ public void write(byte[] data) throws IOException { write(data, 0, data.length); } + /** + * Writes a portion of a byte array to the buffer. + * + *

If the buffer becomes full, it is automatically flushed to the + * underlying stream.

+ * + * @param data source byte array + * @param off starting offset + * @param len number of bytes to write + * @throws IOException if writing fails + */ public void write(byte[] data, int off, int len) throws IOException { int offset = off; while (len > 0) { @@ -41,23 +89,52 @@ public void write(byte[] data, int off, int len) throws IOException { } } + /** + * Writes an ASCII string to the buffer. + * + * @param s ASCII string + * @throws IOException if writing fails + */ public void writeAscii(String s) throws IOException { write(s.getBytes(StandardCharsets.US_ASCII)); } + /** + * Writes a CRLF sequence ({@code \r\n}). + * + *

This sequence is used as a line terminator in HTTP messages.

+ * + * @throws IOException if writing fails + */ public void writeCRLF() throws IOException { write(new byte[]{'\r', '\n'}); } + /** + * Flushes buffered data to the underlying stream. + * + * @throws IOException if flushing fails + */ public void flush() throws IOException { flushInternal(); out.flush(); } + /** + * Closes the underlying output stream. + * + * @throws IOException if closing fails + */ public void close() throws IOException { out.close(); } + /** + * Writes buffered data to the underlying stream and + * resets the buffer position. + * + * @throws IOException if writing fails + */ private void flushInternal() throws IOException { if (position > 0) { out.write(buffer, 0, position); From a4715881e54e68836c355266266e9a60932c7d3e Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:49:37 -0700 Subject: [PATCH 07/13] update HandlerFactory.java Refs #38 --- .../wintermindset/handler/HandlerFactory.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/main/java/com/wintermindset/handler/HandlerFactory.java b/src/main/java/com/wintermindset/handler/HandlerFactory.java index a075ef4..03a472b 100644 --- a/src/main/java/com/wintermindset/handler/HandlerFactory.java +++ b/src/main/java/com/wintermindset/handler/HandlerFactory.java @@ -1,7 +1,42 @@ package com.wintermindset.handler; +/** + * Factory responsible for creating {@link Handler} instances + * using reflection. + * + *

This class allows dynamic loading of request handlers + * by their fully qualified class name. The target class must:

+ * + *
    + *
  • implement the {@link Handler} interface
  • + *
  • provide a public no-argument constructor
  • + *
+ * + *

Typical usage:

+ * + *
+ * Handler handler =
+ *     HandlerFactory.fromClassName("com.example.MyHandler");
+ * 
+ * + *

This approach is commonly used for simple plugin-like + * architectures where handlers are configured externally + * (e.g. via configuration files).

+ */ public final class HandlerFactory { + /** + * Creates a {@link Handler} instance from a class name. + * + *

The method loads the class using {@link Class#forName(String)} + * and instantiates it using its default constructor.

+ * + * @param className fully qualified handler class name + * @return instantiated handler + * + * @throws IllegalArgumentException if the class does not implement {@link Handler} + * @throws RuntimeException if the class cannot be loaded or instantiated + */ public static Handler fromClassName(String className) { try { Class clazz = Class.forName(className); From 69b097a7f87b84a689439855dece77e9ca364a09 Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:50:20 -0700 Subject: [PATCH 08/13] update Handler.java Refs #38 --- .../com/wintermindset/handler/Handler.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wintermindset/handler/Handler.java b/src/main/java/com/wintermindset/handler/Handler.java index a313582..b5db74f 100644 --- a/src/main/java/com/wintermindset/handler/Handler.java +++ b/src/main/java/com/wintermindset/handler/Handler.java @@ -3,7 +3,33 @@ import com.wintermindset.http.HttpRequest; import com.wintermindset.http.HttpResponse; +/** + * Functional interface representing an HTTP request handler. + * + *

A {@code Handler} processes an incoming {@link HttpRequest} + * and produces a corresponding {@link HttpResponse}. Implementations + * typically contain the application logic of the server.

+ * + *

Handlers are invoked by the HTTP server infrastructure + * (e.g. a connection handler) after a request has been parsed.

+ * + *

Example implementation:

+ * + *
+ * public class HelloHandler implements Handler {
+ *     public HttpResponse handle(HttpRequest req) {
+ *         return HttpResponse.ok("Hello world");
+ *     }
+ * }
+ * 
+ */ public interface Handler { - + + /** + * Processes an HTTP request and produces a response. + * + * @param req parsed HTTP request + * @return HTTP response to send back to the client + */ HttpResponse handle(HttpRequest req); } \ No newline at end of file From 5b6e52b4c69331655914edd04f8899ab723de49a Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:51:21 -0700 Subject: [PATCH 09/13] update Server.java Refs #38 --- .../java/com/wintermindset/core/Server.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/main/java/com/wintermindset/core/Server.java b/src/main/java/com/wintermindset/core/Server.java index 9bbbe2d..f23d178 100644 --- a/src/main/java/com/wintermindset/core/Server.java +++ b/src/main/java/com/wintermindset/core/Server.java @@ -13,17 +13,53 @@ import com.wintermindset.handler.Handler; import com.wintermindset.http.HttpConnection; + +/** + * Minimal HTTP server implementation. + * + *

This class is responsible for accepting incoming TCP connections + * and delegating them to {@link HttpConnection} instances for processing.

+ * + *

The server uses Java virtual threads (Project Loom) via + * {@link Executors#newVirtualThreadPerTaskExecutor()} to handle + * connections concurrently with a lightweight threading model.

+ * + *

Main responsibilities:

+ *
    + *
  • Open a {@link ServerSocket}
  • + *
  • Accept incoming client connections
  • + *
  • Create a connection handler for each socket
  • + *
  • Execute handlers using virtual threads
  • + *
+ * + *

The actual request processing logic is delegated to a + * {@link Handler} implementation.

+ */ public final class Server { private final int port; private final Handler handler; private static final Logger LOGGER = LogManager.getLogger(Server.class); + /** + * Creates a new server instance using the provided configuration. + * + * @param config server configuration containing port and handler + */ public Server(ServerConfig config) { this.port = config.port; this.handler = config.handler; } + /** + * Starts the HTTP server. + * + *

The method opens a {@link ServerSocket} and continuously accepts + * incoming connections. Each accepted socket is handled by a + * {@link HttpConnection} running in its own virtual thread.

+ * + * @throws IOException if the server socket cannot be created or fails + */ public void start() throws IOException { ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); try (ServerSocket serverSocket = new ServerSocket(port)) { From 4d1924b7686295025289a3542480dc0572480deb Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:52:11 -0700 Subject: [PATCH 10/13] update ServerConfig.java Refs #38 --- .../wintermindset/config/ServerConfig.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/main/java/com/wintermindset/config/ServerConfig.java b/src/main/java/com/wintermindset/config/ServerConfig.java index afa661f..2b70ce5 100644 --- a/src/main/java/com/wintermindset/config/ServerConfig.java +++ b/src/main/java/com/wintermindset/config/ServerConfig.java @@ -2,8 +2,36 @@ import com.wintermindset.handler.Handler; +/** + * Configuration object for the HTTP server. + * + *

This class contains basic settings required to start the server, + * such as the listening port and the request {@link Handler} that + * processes incoming HTTP requests.

+ * + *

The configuration is typically created during application startup + * and passed to the server instance.

+ * + *

Example usage:

+ * + *
+ * ServerConfig config = new ServerConfig();
+ * config.port = 8080;
+ * config.handler = new HelloHandler();
+ * 
+ */ public final class ServerConfig { + /** + * Port on which the HTTP server will listen. + * + *

Default value: {@code 8080}.

+ */ public int port = 8080; + + /** + * Application-level request handler responsible for + * processing incoming HTTP requests. + */ public Handler handler; } \ No newline at end of file From 01d209d55a5c04c35a608cb8de8c40dd16d006ce Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:53:06 -0700 Subject: [PATCH 11/13] update ConfigReader.java Refs #38 --- .../wintermindset/config/ConfigReader.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/main/java/com/wintermindset/config/ConfigReader.java b/src/main/java/com/wintermindset/config/ConfigReader.java index 1d159fc..a46e011 100644 --- a/src/main/java/com/wintermindset/config/ConfigReader.java +++ b/src/main/java/com/wintermindset/config/ConfigReader.java @@ -7,8 +7,41 @@ import com.wintermindset.handler.HandlerFactory; +/** + * Utility class responsible for loading and parsing server configuration + * from resource files. + * + *

The configuration file is expected to be located on the application + * classpath and follow a simple {@code key: value} format.

+ * + *

Example configuration file:

+ * + *
+ * # Server configuration
+ * port: 8080
+ * handler: com.example.MyHandler
+ * 
+ * + *

Supported configuration keys:

+ *
    + *
  • {@code port} – server listening port
  • + *
  • {@code handler} – fully qualified class name of the request handler
  • + *
+ * + *

Lines starting with {@code #} and empty lines are ignored.

+ */ public final class ConfigReader { + /** + * Loads a {@link ServerConfig} instance from a configuration file + * located in the application resources. + * + * @param resourceName resource file name (e.g. {@code server.conf}) + * @return parsed server configuration + * + * @throws IllegalArgumentException if the resource cannot be found + * @throws RuntimeException if parsing fails + */ public static ServerConfig loadFromResources(String resourceName) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); try (InputStream in = cl.getResourceAsStream(resourceName)) { @@ -25,6 +58,17 @@ public static ServerConfig loadFromResources(String resourceName) { } } + + /** + * Parses the configuration content from a reader. + * + *

The method reads the configuration line by line and applies + * recognized keys to the {@link ServerConfig} object.

+ * + * @param in input reader + * @return parsed configuration + * @throws IOException if reading fails + */ private static ServerConfig parse(InputStreamReader in) throws IOException { ServerConfig cfg = new ServerConfig(); try (BufferedReader r = new BufferedReader(in)) { @@ -46,6 +90,13 @@ private static ServerConfig parse(InputStreamReader in) throws IOException { return cfg; } + /** + * Applies a configuration key-value pair to the {@link ServerConfig}. + * + * @param cfg configuration object + * @param key configuration key + * @param value configuration value + */ private static void apply(ServerConfig cfg, String key, String value) { switch (key) { case "port" -> cfg.port = Integer.parseInt(value); From 6f55abbbe68ab5cd8935f9db71e9aa1631b1705e Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:54:04 -0700 Subject: [PATCH 12/13] update App.java Refs #38 --- src/main/java/com/wintermindset/App.java | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/java/com/wintermindset/App.java b/src/main/java/com/wintermindset/App.java index 3b214f0..7a8df11 100644 --- a/src/main/java/com/wintermindset/App.java +++ b/src/main/java/com/wintermindset/App.java @@ -9,10 +9,34 @@ import com.wintermindset.config.ServerConfig; import com.wintermindset.core.Server; +/** + * Application entry point. + * + *

This class is responsible for bootstrapping the HTTP server. + * It performs the following steps:

+ * + *
    + *
  1. Load server configuration from resources
  2. + *
  3. Create the {@link Server} instance
  4. + *
  5. Start the server
  6. + *
+ * + *

The configuration file is expected to be located in the application + * resources and defines settings such as the server port and the request + * handler implementation.

+ */ public class App { private static final Logger LOGGER = LogManager.getLogger(App.class); + /** + * Main application entry point. + * + *

The method loads the server configuration, initializes the server, + * and starts accepting HTTP connections.

+ * + * @param args command-line arguments (not used) + */ public static void main(String[] args) { try { LOGGER.info("Loading server config"); From 843f12147499b9750e4e271d2944f7204711d338 Mon Sep 17 00:00:00 2001 From: wintermindset Date: Sat, 14 Mar 2026 00:55:26 -0700 Subject: [PATCH 13/13] update CalcHandler.java Closes #38 --- .../wintermindset/handler/CalcHandler.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/main/java/com/wintermindset/handler/CalcHandler.java b/src/main/java/com/wintermindset/handler/CalcHandler.java index d686dfa..8b5404a 100644 --- a/src/main/java/com/wintermindset/handler/CalcHandler.java +++ b/src/main/java/com/wintermindset/handler/CalcHandler.java @@ -9,10 +9,54 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +/** + * HTTP handler that exposes a simple calculator endpoint. + * + *

The handler processes {@code GET} requests sent to the + * {@code /calc} path and performs a basic arithmetic operation + * using query parameters.

+ * + *

Supported query parameters:

+ *
    + *
  • {@code x} – first operand
  • + *
  • {@code y} – second operand
  • + *
  • {@code op} – arithmetic operator
  • + *
+ * + *

Supported operators:

+ *
    + *
  • {@code +} addition
  • + *
  • {@code -} subtraction
  • + *
  • {@code *} multiplication
  • + *
  • {@code /} division
  • + *
+ * + *

Example request:

+ * + *
+ * GET /calc?x=10&y=5&op=+
+ * 
+ * + *

Response:

+ * + *
+ * 15.0
+ * 
+ */ public final class CalcHandler implements Handler { private static final Logger LOGGER = LogManager.getLogger(); + /** + * Processes an incoming HTTP request. + * + *

The handler validates the request method and path, + * extracts query parameters, performs the requested + * arithmetic operation, and returns the result.

+ * + * @param req parsed HTTP request + * @return HTTP response containing the calculation result + */ public HttpResponse handle(HttpRequest req) { if (!"GET".equals(req.method)) { return HttpResponse.badRequest("Only GET supported"); @@ -43,6 +87,31 @@ public HttpResponse handle(HttpRequest req) { } } + /** + * Parses query parameters from the request path. + * + *

The method extracts the query string portion of the URL + * and converts it into a map of key-value pairs.

+ * + *

Example:

+ * + *
+     * /calc?x=10&y=5&op=+
+     * 
+ * + * becomes: + * + *
+     * {
+     *   x=10,
+     *   y=5,
+     *   op=+
+     * }
+     * 
+ * + * @param path request path containing the query string + * @return map of parsed query parameters + */ private Map parseQuery(String path) { Map map = new HashMap<>(); int q = path.indexOf('?');