Skip to content
Merged

Dev #40

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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