Skip to content
24 changes: 24 additions & 0 deletions src/main/java/com/wintermindset/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,34 @@
import com.wintermindset.config.ServerConfig;
import com.wintermindset.core.Server;

/**
* Application entry point.
*
* <p>This class is responsible for bootstrapping the HTTP server.
* It performs the following steps:</p>
*
* <ol>
* <li>Load server configuration from resources</li>
* <li>Create the {@link Server} instance</li>
* <li>Start the server</li>
* </ol>
*
* <p>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.</p>
*/
public class App {

private static final Logger LOGGER = LogManager.getLogger(App.class);

/**
* Main application entry point.
*
* <p>The method loads the server configuration, initializes the server,
* and starts accepting HTTP connections.</p>
*
* @param args command-line arguments (not used)
*/
public static void main(String[] args) {
try {
LOGGER.info("Loading server config");
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/com/wintermindset/config/ConfigReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,41 @@

import com.wintermindset.handler.HandlerFactory;

/**
* Utility class responsible for loading and parsing server configuration
* from resource files.
*
* <p>The configuration file is expected to be located on the application
* classpath and follow a simple {@code key: value} format.</p>
*
* <p>Example configuration file:</p>
*
* <pre>
* # Server configuration
* port: 8080
* handler: com.example.MyHandler
* </pre>
*
* <p>Supported configuration keys:</p>
* <ul>
* <li>{@code port} – server listening port</li>
* <li>{@code handler} – fully qualified class name of the request handler</li>
* </ul>
*
* <p>Lines starting with {@code #} and empty lines are ignored.</p>
*/
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)) {
Expand All @@ -25,6 +58,17 @@ public static ServerConfig loadFromResources(String resourceName) {
}
}


/**
* Parses the configuration content from a reader.
*
* <p>The method reads the configuration line by line and applies
* recognized keys to the {@link ServerConfig} object.</p>
*
* @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)) {
Expand All @@ -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);
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/wintermindset/config/ServerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,36 @@

import com.wintermindset.handler.Handler;

/**
* Configuration object for the HTTP server.
*
* <p>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.</p>
*
* <p>The configuration is typically created during application startup
* and passed to the server instance.</p>
*
* <p>Example usage:</p>
*
* <pre>
* ServerConfig config = new ServerConfig();
* config.port = 8080;
* config.handler = new HelloHandler();
* </pre>
*/
public final class ServerConfig {

/**
* Port on which the HTTP server will listen.
*
* <p>Default value: {@code 8080}.</p>
*/
public int port = 8080;

/**
* Application-level request handler responsible for
* processing incoming HTTP requests.
*/
public Handler handler;
}
36 changes: 36 additions & 0 deletions src/main/java/com/wintermindset/core/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,53 @@
import com.wintermindset.handler.Handler;
import com.wintermindset.http.HttpConnection;


/**
* Minimal HTTP server implementation.
*
* <p>This class is responsible for accepting incoming TCP connections
* and delegating them to {@link HttpConnection} instances for processing.</p>
*
* <p>The server uses Java virtual threads (Project Loom) via
* {@link Executors#newVirtualThreadPerTaskExecutor()} to handle
* connections concurrently with a lightweight threading model.</p>
*
* <p>Main responsibilities:</p>
* <ul>
* <li>Open a {@link ServerSocket}</li>
* <li>Accept incoming client connections</li>
* <li>Create a connection handler for each socket</li>
* <li>Execute handlers using virtual threads</li>
* </ul>
*
* <p>The actual request processing logic is delegated to a
* {@link Handler} implementation.</p>
*/
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.
*
* <p>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.</p>
*
* @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)) {
Expand Down
69 changes: 69 additions & 0 deletions src/main/java/com/wintermindset/handler/CalcHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,54 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* HTTP handler that exposes a simple calculator endpoint.
*
* <p>The handler processes {@code GET} requests sent to the
* {@code /calc} path and performs a basic arithmetic operation
* using query parameters.</p>
*
* <p>Supported query parameters:</p>
* <ul>
* <li>{@code x} – first operand</li>
* <li>{@code y} – second operand</li>
* <li>{@code op} – arithmetic operator</li>
* </ul>
*
* <p>Supported operators:</p>
* <ul>
* <li>{@code +} addition</li>
* <li>{@code -} subtraction</li>
* <li>{@code *} multiplication</li>
* <li>{@code /} division</li>
* </ul>
*
* <p>Example request:</p>
*
* <pre>
* GET /calc?x=10&y=5&op=+
* </pre>
*
* <p>Response:</p>
*
* <pre>
* 15.0
* </pre>
*/
public final class CalcHandler implements Handler {

private static final Logger LOGGER = LogManager.getLogger();

/**
* Processes an incoming HTTP request.
*
* <p>The handler validates the request method and path,
* extracts query parameters, performs the requested
* arithmetic operation, and returns the result.</p>
*
* @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");
Expand Down Expand Up @@ -43,6 +87,31 @@ public HttpResponse handle(HttpRequest req) {
}
}

/**
* Parses query parameters from the request path.
*
* <p>The method extracts the query string portion of the URL
* and converts it into a map of key-value pairs.</p>
*
* <p>Example:</p>
*
* <pre>
* /calc?x=10&y=5&op=+
* </pre>
*
* becomes:
*
* <pre>
* {
* x=10,
* y=5,
* op=+
* }
* </pre>
*
* @param path request path containing the query string
* @return map of parsed query parameters
*/
private Map<String, String> parseQuery(String path) {
Map<String, String> map = new HashMap<>();
int q = path.indexOf('?');
Expand Down
28 changes: 27 additions & 1 deletion src/main/java/com/wintermindset/handler/Handler.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,33 @@
import com.wintermindset.http.HttpRequest;
import com.wintermindset.http.HttpResponse;

/**
* Functional interface representing an HTTP request handler.
*
* <p>A {@code Handler} processes an incoming {@link HttpRequest}
* and produces a corresponding {@link HttpResponse}. Implementations
* typically contain the application logic of the server.</p>
*
* <p>Handlers are invoked by the HTTP server infrastructure
* (e.g. a connection handler) after a request has been parsed.</p>
*
* <p>Example implementation:</p>
*
* <pre>
* public class HelloHandler implements Handler {
* public HttpResponse handle(HttpRequest req) {
* return HttpResponse.ok("Hello world");
* }
* }
* </pre>
*/
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);
}
35 changes: 35 additions & 0 deletions src/main/java/com/wintermindset/handler/HandlerFactory.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
package com.wintermindset.handler;

/**
* Factory responsible for creating {@link Handler} instances
* using reflection.
*
* <p>This class allows dynamic loading of request handlers
* by their fully qualified class name. The target class must:</p>
*
* <ul>
* <li>implement the {@link Handler} interface</li>
* <li>provide a public no-argument constructor</li>
* </ul>
*
* <p>Typical usage:</p>
*
* <pre>
* Handler handler =
* HandlerFactory.fromClassName("com.example.MyHandler");
* </pre>
*
* <p>This approach is commonly used for simple plugin-like
* architectures where handlers are configured externally
* (e.g. via configuration files).</p>
*/
public final class HandlerFactory {

/**
* Creates a {@link Handler} instance from a class name.
*
* <p>The method loads the class using {@link Class#forName(String)}
* and instantiates it using its default constructor.</p>
*
* @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);
Expand Down
Loading
Loading