sonatype deployment packaging

This commit is contained in:
Daniel
2024-10-24 00:26:08 +03:00
parent 1d5c7fbc15
commit b19e989b87
17 changed files with 164 additions and 47 deletions

View File

@@ -0,0 +1,74 @@
package io.github.lumijiez.core.config;
public class ServerConfig {
private final int port;
private final int keepAliveTimeout;
private final int maxRequestsPerConnection;
private final int bufferSize;
private final int threadPoolSize;
private ServerConfig(Builder builder) {
this.port = builder.port;
this.keepAliveTimeout = builder.keepAliveTimeout;
this.maxRequestsPerConnection = builder.maxRequestsPerConnection;
this.bufferSize = builder.bufferSize;
this.threadPoolSize = builder.threadPoolSize;
}
public int getPort() {
return port;
}
public int getKeepAliveTimeout() {
return keepAliveTimeout;
}
public int getMaxRequestsPerConnection() {
return maxRequestsPerConnection;
}
public int getBufferSize() {
return bufferSize;
}
public int getThreadPoolSize() {
return threadPoolSize;
}
public static class Builder {
private int port = 8080;
private int keepAliveTimeout = 30000;
private int maxRequestsPerConnection = 1000;
private int bufferSize = 8192;
private int threadPoolSize = Runtime.getRuntime().availableProcessors() * 2;
public Builder port(int port) {
this.port = port;
return this;
}
public ServerConfig build() {
return new ServerConfig(this);
}
public Builder keepAliveTimeout(int i) {
this.keepAliveTimeout = i;
return this;
}
public Builder maxRequestsPerConnection(int i) {
this.maxRequestsPerConnection = i;
return this;
}
public Builder bufferSize(int i) {
this.bufferSize = i;
return this;
}
public Builder threadPoolSize(int i) {
this.threadPoolSize = i;
return this;
}
}
}

View File

@@ -0,0 +1,8 @@
package io.github.lumijiez.core.http;
import java.io.IOException;
@FunctionalInterface
public interface HttpHandler {
void handle(HttpRequest request, HttpResponse response) throws IOException;
}

View File

@@ -0,0 +1,13 @@
package io.github.lumijiez.core.http;
public enum HttpMethod {
GET, POST, PUT, DELETE, HEAD, OPTIONS;
public static HttpMethod fromString(String method) {
try {
return valueOf(method.toUpperCase());
} catch (IllegalArgumentException e) {
throw new UnsupportedOperationException("Unsupported HTTP method: " + method);
}
}
}

View File

@@ -0,0 +1,86 @@
package io.github.lumijiez.core.http;
import io.github.lumijiez.core.util.UrlParser;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class HttpRequest {
private String method;
private String path;
private String httpVersion;
private final Map<String, String> headers;
private UrlParser urlParser;
public HttpRequest(BufferedReader in) throws IOException {
this.headers = new HashMap<>();
parseRequest(in);
}
private void parseRequest(BufferedReader in) throws IOException {
String requestLine = in.readLine();
if (requestLine != null && !requestLine.trim().isEmpty()) {
String[] tokens = requestLine.split(" ");
if (tokens.length == 3) {
this.method = tokens[0];
this.path = tokens[1];
this.httpVersion = tokens[2];
} else {
throw new IOException("Invalid request line format.");
}
}
String headerLine;
while ((headerLine = in.readLine()) != null && !headerLine.trim().isEmpty()) {
int separator = headerLine.indexOf(':');
if (separator > 0) {
String key = headerLine.substring(0, separator).trim().toLowerCase();
String value = headerLine.substring(separator + 1).trim();
headers.put(key, value);
}
}
}
public boolean isKeepAlive() {
String connection = headers.get("connection");
if ("close".equalsIgnoreCase(connection)) {
return false;
}
return "HTTP/1.1".equals(httpVersion) ||
"keep-alive".equalsIgnoreCase(connection);
}
public void setUrlParser(UrlParser urlParser) {
this.urlParser = urlParser;
}
public String getPathParam(String name) {
return urlParser != null ? urlParser.getPathParam(name) : null;
}
public String getQueryParam(String name) {
return urlParser != null ? urlParser.getQueryParam(name) : null;
}
public Map<String, String> getPathParams() {
return urlParser != null ? urlParser.getPathParams() : Map.of();
}
public Map<String, String> getQueryParams() {
return urlParser != null ? urlParser.getQueryParams() : Map.of();
}
public String getMethod() {
return method;
}
public String getPath() {
return path;
}
public String getHttpVersion() {
return httpVersion;
}
}

View File

@@ -0,0 +1,37 @@
package io.github.lumijiez.core.http;
import io.github.lumijiez.logging.Logger;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class HttpResponse {
private final BufferedWriter out;
public HttpResponse(BufferedWriter out) {
this.out = out;
}
public void sendResponse(HttpStatus status, String message) throws IOException {
Logger.info("HTTP", "Outgoing: " + status.getCode() + " " + message);
out.write("HTTP/1.1 " + status.getCode() + " " + status.getMessage());
out.write("\r\n");
out.write("Content-Type: text/plain");
out.write("\r\n");
out.write("Content-Length: " + message.getBytes(StandardCharsets.UTF_8).length);
out.write("\r\n");
out.write("Connection: keep-alive");
out.write("\r\n");
out.write("Keep-Alive: timeout=30");
out.write("\r\n");
out.write("\r\n");
out.write(message);
out.flush();
}
}

View File

@@ -0,0 +1,159 @@
package io.github.lumijiez.core.http;
import io.github.lumijiez.core.config.ServerConfig;
import io.github.lumijiez.core.middleware.Middleware;
import io.github.lumijiez.core.routing.Router;
import io.github.lumijiez.logging.Logger;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpServer {
private boolean running;
private final int port;
private ServerSocket serverSocket;
private final ExecutorService threadPool;
private final Router router;
private static int KEEP_ALIVE_TIMEOUT = 30000;
private static int MAX_REQUESTS_PER_CONNECTION = 1000;
private static int BUFFER_SIZE = 8192;
public HttpServer(ServerConfig config) {
this.running = false;
this.port = config.getPort();
KEEP_ALIVE_TIMEOUT = config.getKeepAliveTimeout();
MAX_REQUESTS_PER_CONNECTION = config.getMaxRequestsPerConnection();
BUFFER_SIZE = config.getBufferSize();
this.router = new Router();
this.threadPool = Executors.newCachedThreadPool();
}
public void addMiddleware(Middleware middleware) {
this.router.addMiddleware(middleware);
}
public void GET(String path, HttpHandler handler) {
router.addRoute(HttpMethod.GET, path, handler);
}
public void POST(String path, HttpHandler handler) {
router.addRoute(HttpMethod.POST, path, handler);
}
public void start() {
try {
serverSocket = new ServerSocket(port);
running = true;
Logger.info("HTTP", "Server started on port " + port);
while (running) {
try {
Socket clientSocket = serverSocket.accept();
// Logger.info("HTTP", "Client connected " + clientSocket.getInetAddress());
threadPool.submit(() -> handleClient(clientSocket));
} catch (IOException e) {
if (running) {
Logger.error("HTTP", "Error accepting client connection: " + e.getMessage());
}
}
}
} catch (IOException e) {
Logger.error("HTTP", "Error starting server: " + e.getMessage());
} finally {
stop();
}
}
protected void handleClient(Socket clientSocket) {
try {
clientSocket.setSoTimeout(KEEP_ALIVE_TIMEOUT);
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()),
BUFFER_SIZE
);
BufferedWriter out = new BufferedWriter(
new OutputStreamWriter(clientSocket.getOutputStream()),
BUFFER_SIZE
);
int requestCount = 0;
boolean keepAlive = true;
while (keepAlive && requestCount < MAX_REQUESTS_PER_CONNECTION && running) {
try {
if (!in.ready()) {
// Thread.sleep(10);
continue;
}
HttpRequest request = new HttpRequest(in);
if (request.getMethod() == null || request.getPath() == null) {
break;
}
HttpResponse response = new HttpResponse(out);
Logger.info("HTTP", String.format(
"Incoming [%d]: %s %s (keep-alive: %s)",
requestCount + 1,
request.getMethod(),
request.getPath(),
request.isKeepAlive()
));
router.handleRequest(request, response);
keepAlive = request.isKeepAlive();
requestCount++;
out.flush();
} catch (SocketTimeoutException e) {
Logger.info("HTTP", "Keep-alive timeout reached");
break;
} catch (IOException e) {
if (running) {
Logger.error("HTTP", "Error processing request: " + e.getMessage());
}
break;
}
}
} catch (IOException e) {
if (running) {
Logger.error("HTTP", "Error handling client: " + e.getMessage());
}
} finally {
try {
clientSocket.close();
Logger.info("HTTP", "Connection closed gracefully");
} catch (IOException e) {
Logger.error("HTTP", "Error closing socket: " + e.getMessage());
}
}
}
public void stop() {
running = false;
if (serverSocket != null) {
try {
serverSocket.close();
Logger.info("HTTP", "Server stopped");
} catch (IOException e) {
Logger.error("HTTP", "Error stopping server: " + e.getMessage());
}
}
threadPool.shutdownNow();
}
public boolean isRunning() {
return running;
}
}

View File

@@ -0,0 +1,68 @@
package io.github.lumijiez.core.http;
public enum HttpStatus {
// 1xx Informational
CONTINUE(100, "Continue"),
SWITCHING_PROTOCOLS(101, "Switching Protocols"),
PROCESSING(102, "Processing"),
// 2xx Success
OK(200, "OK"),
CREATED(201, "Created"),
ACCEPTED(202, "Accepted"),
NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),
NO_CONTENT(204, "No Content"),
RESET_CONTENT(205, "Reset Content"),
PARTIAL_CONTENT(206, "Partial Content"),
// 3xx Redirection
MULTIPLE_CHOICES(300, "Multiple Choices"),
MOVED_PERMANENTLY(301, "Moved Permanently"),
FOUND(302, "Found"),
SEE_OTHER(303, "See Other"),
NOT_MODIFIED(304, "Not Modified"),
TEMPORARY_REDIRECT(307, "Temporary Redirect"),
PERMANENT_REDIRECT(308, "Permanent Redirect"),
// 4xx Client Errors
BAD_REQUEST(400, "Bad Request"),
UNAUTHORIZED(401, "Unauthorized"),
PAYMENT_REQUIRED(402, "Payment Required"),
FORBIDDEN(403, "Forbidden"),
NOT_FOUND(404, "Not Found"),
METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
NOT_ACCEPTABLE(406, "Not Acceptable"),
PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
REQUEST_TIMEOUT(408, "Request Timeout"),
CONFLICT(409, "Conflict"),
GONE(410, "Gone"),
LENGTH_REQUIRED(411, "Length Required"),
PRECONDITION_FAILED(412, "Precondition Failed"),
PAYLOAD_TOO_LARGE(413, "Payload Too Large"),
URI_TOO_LONG(414, "URI Too Long"),
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
RANGE_NOT_SATISFIABLE(416, "Range Not Satisfiable"),
EXPECTATION_FAILED(417, "Expectation Failed"),
I_AM_A_TEAPOT(418, "I'm a teapot"),
TOO_MANY_REQUESTS(429, "Too Many Requests"),
// 5xx Server Errors
INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
NOT_IMPLEMENTED(501, "Not Implemented"),
BAD_GATEWAY(502, "Bad Gateway"),
SERVICE_UNAVAILABLE(503, "Service Unavailable"),
GATEWAY_TIMEOUT(504, "Gateway Timeout"),
HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported");
private final int code;
private final String message;
HttpStatus(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() { return code; }
public String getMessage() { return message; }
}

View File

@@ -0,0 +1,10 @@
package io.github.lumijiez.core.middleware;
import io.github.lumijiez.core.http.HttpRequest;
import io.github.lumijiez.core.http.HttpResponse;
import java.io.IOException;
public interface Chain {
void next(HttpRequest request, HttpResponse response) throws IOException;
}

View File

@@ -0,0 +1,10 @@
package io.github.lumijiez.core.middleware;
import io.github.lumijiez.core.http.HttpRequest;
import io.github.lumijiez.core.http.HttpResponse;
import java.io.IOException;
public interface Middleware {
void process(HttpRequest request, HttpResponse response, Chain chain) throws IOException;
}

View File

@@ -0,0 +1,6 @@
package io.github.lumijiez.core.routing;
import io.github.lumijiez.core.http.HttpHandler;
import io.github.lumijiez.core.http.HttpMethod;
public record Route(HttpMethod method, String path, HttpHandler handler) { }

View File

@@ -0,0 +1,61 @@
package io.github.lumijiez.core.routing;
import io.github.lumijiez.core.http.*;
import io.github.lumijiez.core.middleware.Chain;
import io.github.lumijiez.core.middleware.Middleware;
import io.github.lumijiez.core.util.UrlParser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class Router {
private final List<Route> routes = new ArrayList<>();
private final List<Middleware> middleware = new ArrayList<>();
public void addMiddleware(Middleware middleware) {
this.middleware.add(middleware);
}
public void addRoute(HttpMethod method, String pattern, HttpHandler handler) {
routes.add(new Route(method, pattern, handler));
}
public void handleRequest(HttpRequest request, HttpResponse response) throws IOException {
Chain chain = new Chain() {
private int index = 0;
@Override
public void next(HttpRequest request, HttpResponse response) throws IOException {
if (index < middleware.size()) {
middleware.get(index++).process(request, response, this);
} else {
executeHandler(request, response);
}
}
};
chain.next(request, response);
}
private void executeHandler(HttpRequest request, HttpResponse response) throws IOException {
UrlParser urlParser = new UrlParser(request.getPath());
Route matchedRoute = null;
for (Route route : routes) {
if (route.method().name().equals(request.getMethod()) &&
urlParser.matchesPattern(route.path())) {
matchedRoute = route;
break;
}
}
if (matchedRoute != null) {
request.setUrlParser(urlParser);
matchedRoute.handler().handle(request, response);
} else {
response.sendResponse(HttpStatus.NOT_FOUND, "Not Found");
}
}
}

View File

@@ -0,0 +1,78 @@
package io.github.lumijiez.core.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class UrlParser {
private final String path;
private final Map<String, String> queryParams;
private final Map<String, String> pathParams;
public UrlParser(String rawUrl) {
this.queryParams = new HashMap<>();
this.pathParams = new HashMap<>();
String[] urlParts = rawUrl.split("\\?", 2);
this.path = urlParts[0];
if (urlParts.length > 1) {
parseQueryParams(urlParts[1]);
}
}
private void parseQueryParams(String queryString) {
String[] pairs = queryString.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=", 2);
if (keyValue.length == 2) {
queryParams.put(keyValue[0], keyValue[1]);
} else if (keyValue.length == 1) {
queryParams.put(keyValue[0], "");
}
}
}
public boolean matchesPattern(String pattern) {
String[] patternParts = pattern.split("/");
String[] pathParts = this.path.split("/");
if (patternParts.length != pathParts.length) {
return false;
}
for (int i = 0; i < patternParts.length; i++) {
String patternPart = patternParts[i];
String pathPart = pathParts[i];
if (patternPart.startsWith(":")) {
String paramName = patternPart.substring(1);
pathParams.put(paramName, pathPart);
} else if (!patternPart.equals(pathPart)) {
return false;
}
}
return true;
}
public String getPath() {
return path;
}
public String getQueryParam(String name) {
return queryParams.get(name);
}
public String getPathParam(String name) {
return pathParams.get(name);
}
public Map<String, String> getQueryParams() {
return Collections.unmodifiableMap(queryParams);
}
public Map<String, String> getPathParams() {
return Collections.unmodifiableMap(pathParams);
}
}

View File

@@ -0,0 +1,30 @@
package io.github.lumijiez.example;
import io.github.lumijiez.core.config.ServerConfig;
import io.github.lumijiez.core.http.HttpServer;
import io.github.lumijiez.core.http.HttpStatus;
import io.github.lumijiez.logging.Logger;
public class Main {
public static void main(String[] args) {
ServerConfig config = new ServerConfig.Builder()
.port(8080)
.keepAliveTimeout(30000)
.build();
HttpServer server = new HttpServer(config);
server.addMiddleware((req, res, chain) -> {
Logger.info("MIDDLEWARE", "Request: " + req.getMethod() + " " + req.getPath());
chain.next(req, res);
});
server.GET("/test/:lel/", (req, res) -> {
Logger.info("PATH", req.getPathParam("lel"));
Logger.info("QUERY", req.getQueryParam("lol"));
res.sendResponse(HttpStatus.OK, "All good, lil bro");
});
server.start();
}
}

View File

@@ -0,0 +1,60 @@
package io.github.lumijiez.logging;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Logger {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
public enum LogLevel {
DEBUG, INFO, WARN, ERROR
}
private static final Logger instance = new Logger(LogLevel.INFO);
private LogLevel currentLogLevel;
private Logger(LogLevel level) {
this.currentLogLevel = level;
}
public static Logger getInstance() {
return instance;
}
public void log(LogLevel level, String source, String message) {
if (level.ordinal() >= currentLogLevel.ordinal()) {
String timestamp = LocalDateTime.now().format(formatter);
System.out.println("[" + timestamp + "][" + level + "][" + source + "] " + message);
}
}
public static void debug(String source, String message) {
getInstance().log(LogLevel.DEBUG, source, message);
}
public static void info(String source, String message) {
getInstance().log(LogLevel.INFO, source, message);
}
public static void warn(String source, String message) {
getInstance().log(LogLevel.WARN, source, message);
}
public static void error(String source, String message) {
getInstance().log(LogLevel.ERROR, source, message);
}
public static void error(String source, String message, Throwable throwable) {
getInstance().log(LogLevel.ERROR, source, message + ": " + throwable.getMessage());
// throwable.printStackTrace();
}
public void setLogLevel(LogLevel level) {
this.currentLogLevel = level;
}
public LogLevel getLogLevel() {
return currentLogLevel;
}
}