Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
21a5b92
feat: Added per Node body size limit
Bernardusz Jun 23, 2026
7c60511
Merge pull request #59 from Bernardusz/feat/routes-maxbodysize
Bernardusz Jun 23, 2026
6568d9a
feat: Implemented LevtusInputStream
Bernardusz Jul 3, 2026
e322fc3
Fix typo 💀🐧
Bernardusz Jul 3, 2026
1aaffde
Merge pull request #60 from Bernardusz/feat/bodystream
Bernardusz Jul 3, 2026
50fb45f
feat: Added 100-Continue implementation
Bernardusz Jul 3, 2026
e7accc6
Merge pull request #61 from Bernardusz/feat/100-continue
Bernardusz Jul 3, 2026
7d555a5
feat: Added zero-copy file sending for Levtus
Bernardusz Jul 7, 2026
08c8cd1
Merge pull request #62 from Bernardusz/feat/zero-copy-io
Bernardusz Jul 7, 2026
9fa35e6
feat: Added capability to stream via Chunked Transfer encoding.
Bernardusz Jul 14, 2026
a2baf90
Merge pull request #63 from Bernardusz/feat/chunked-transfer
Bernardusz Jul 14, 2026
a25d59b
feat: Added keep-alive capability
Bernardusz Jul 17, 2026
9aefa7d
Merge pull request #64 from Bernardusz/feat/chunked-transfer
Bernardusz Jul 17, 2026
4e4727b
Merge pull request #65 from Bernardusz/feat/connection-lifecycle
Bernardusz Jul 17, 2026
0391aae
feat: Added download helper method and Date placeing if absent
Bernardusz Jul 17, 2026
ffd8c0f
Merge pull request #66 from Bernardusz/feat/date-header
Bernardusz Jul 17, 2026
aaae246
refactor: Made Node thread safe
Bernardusz Jul 17, 2026
91bb91c
Merge pull request #67 from Bernardusz/refactor/thread-safety-router
Bernardusz Jul 17, 2026
7ca3aef
refactor: Allow for user defined socket timeout
Bernardusz Jul 17, 2026
35e78a0
Merge pull request #68 from Bernardusz/refactor/socket-timeout
Bernardusz Jul 17, 2026
4cbab7d
refactor: Edit CI
Bernardusz Jul 17, 2026
a3c63ee
Merge pull request #69 from Bernardusz/epic/v0.2.0-streaming
Bernardusz Jul 17, 2026
04d8c39
remove: Removed Qodana CI scanning as my license expired 🐧💀
Bernardusz Jul 17, 2026
9139f33
epic: Ready for v0.2.0
Bernardusz Jul 17, 2026
8beeae7
refactor: Fixed JavaDocs and changed downloadFile methods
Bernardusz Jul 17, 2026
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
8 changes: 8 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# .github/labeler.yml
# Adds the 'java' label to any PR that touches .java files
java:
- '**/*.java'

# Adds the 'ci' label if you touch the workflow files
ci:
- '.github/workflows/**/*'
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ name: "CodeQL Advanced"

on:
push:
branches: [ "main" ]
branches: [ "main", "development" ]
pull_request:
branches: [ "main" ]
branches: [ "main", development ]
schedule:
- cron: '20 2 * * 6'

Expand Down
28 changes: 0 additions & 28 deletions .github/workflows/qodana_code_quality.yml

This file was deleted.

4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ build/

Main.java
keystore.p12
public
public
development/
random.txt
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
![Maven Central](https://img.shields.io/maven-central/v/io.github.bernardusz/levtus-core)
![License](https://img.shields.io/github/license/Bernardusz/levtus)
![Maven Release](https://img.shields.io/github/actions/workflow/status/Bernardusz/levtus/release.yml?label=maven%20release)
[![Qodana](https://github.com/Bernardusz/levtus/actions/workflows/qodana_code_quality.yml/badge.svg?branch=main)](https://github.com/Bernardusz/levtus/actions/workflows/qodana_code_quality.yml)

**Levtus** (Latin: *Levis Conatus* - "Light Effort") is a high-performance, zero-dependency HTTP/1.1 engine built from the ground up for the modern JVM. It is designed to be lightweight, secure, and incredibly fast by leveraging the power of **Java 21+ Virtual Threads (Project Loom)**.

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.bernardusz</groupId>
<artifactId>levtus-core</artifactId>
<version>0.1.1</version>
<version>0.2.0</version>

<name>Levtus Core</name>
<description>A high-performance, zero-dependency HTTP/1.1 engine built for the modern JVM using Virtual Threads.</description>
Expand Down
10 changes: 0 additions & 10 deletions qodana.yaml

This file was deleted.

43 changes: 35 additions & 8 deletions src/main/java/io/github/bernardusz/levtus/Levtus.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.github.bernardusz.levtus.engine.LevtusEngine;
import io.github.bernardusz.levtus.http.LevtusContext;
import io.github.bernardusz.levtus.routing.Middleware;
import io.github.bernardusz.levtus.routing.Node;
import io.github.bernardusz.levtus.routing.Router;
import java.util.function.Consumer;

Expand Down Expand Up @@ -44,9 +45,10 @@ public static Levtus create() {
*
* @param path the exact URL path to match (must not be null, e.g., "/api/users")
* @param handler the functional callback executed upon a successful match (must not be null)
* @return the registered route Node
*/
public void get(String path, Consumer<LevtusContext> handler) {
router.get(path, handler);
public Node get(String path, Consumer<LevtusContext> handler) {
return router.get(path, handler);
}

/**
Expand All @@ -56,29 +58,32 @@ public void get(String path, Consumer<LevtusContext> handler) {
*
* @param path the exact URL path to match (must not be null)
* @param handler the functional callback executed upon a successful match (must not be null)
* @return the registered route Node
*/
public void post(String path, Consumer<LevtusContext> handler) {
router.post(path, handler);
public Node post(String path, Consumer<LevtusContext> handler) {
return router.post(path, handler);
}

/**
* Maps an HTTP PUT request to a specific handler function, typically used for updating resources.
*
* @param path the exact URL path to match (must not be null)
* @param handler the functional callback executed upon a successful match (must not be null)
* @return the registered route Node
*/
public void put(String path, Consumer<LevtusContext> handler) {
router.put(path, handler);
public Node put(String path, Consumer<LevtusContext> handler) {
return router.put(path, handler);
}

/**
* Maps an HTTP DELETE request to a specific handler function.
*
* @param path the exact URL path to match (must not be null)
* @param handler the functional callback executed upon a successful match (must not be null)
* @return the registered route Node
*/
public void delete(String path, Consumer<LevtusContext> handler) {
router.delete(path, handler);
public Node delete(String path, Consumer<LevtusContext> handler) {
return router.delete(path, handler);
}

/**
Expand Down Expand Up @@ -192,6 +197,28 @@ public Levtus staticFiles(String staticFilesPath) {
return this;
}

/**
* Set the initial socket timeout for all incoming HTTP requests
*
* @param initialSocketTimeout the initial socket timeout
* @return The current Levtus instance for method chaining
*/
public Levtus initialSocketTimeout(int initialSocketTimeout) {
engine.setInitialSocketTimeout(initialSocketTimeout);
return this;
}

/**
* Set the processing socket timeout for all incoming HTTP requests
*
* @param processingSocketTimeout the processing socket timeout
* @return The current Levtus instance for method chaining
*/
public Levtus processingSocketTimeout(int processingSocketTimeout) {
engine.setProcessingSocketTimeout(processingSocketTimeout);
return this;
}

/**
* Binds the Levtus application to a specific TCP port and boots the server.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,84 @@
package io.github.bernardusz.levtus.engine;

import io.github.bernardusz.levtus.exception.LevtusHttpException;
import io.github.bernardusz.levtus.exception.http.LevtusHttpException;
import io.github.bernardusz.levtus.http.Request;
import io.github.bernardusz.levtus.http.Response;
import io.github.bernardusz.levtus.routing.Router;
import io.github.bernardusz.levtus.spi.ConnectionHandler;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;

class HttpConnectionHandler implements ConnectionHandler {
final Router router;
final HttpParser parser;
private int maxEmptyLines = 10;
private int maxBodySize = 10 * 1024 * 1024;
private long maxBodySize = 10 * 1024 * 1024;
private int maxHeaderCount = 100;
private int maxLineSize = 8192; // 8 KB Limit
private int maxHeaderSize = 8192; // 8 KB Limit
private String staticFilesPath = "./public";
private long maxChunkSize = 1024 * 1024; // 1 MB Limit
private long maxChunkCount = 1000;
private int initialSocketTimeout = 5000; // Initial request data
private int processingSocketTimeout = 20000; // Processing phase

/**
* The constructor for HttpConnectionHandler.
*
* @param router the router for HTTP Handler
* @param parser the parser for HTTP Parser
*/
public HttpConnectionHandler(Router router, HttpParser parser) {
this.router = router;
this.parser = parser;
}

/**
* The main method that handles a client connection.
*
* @param client the client socket to process
*/
public void handle(Socket client) {
try (client;
BufferedInputStream inputStream = new BufferedInputStream(client.getInputStream());
BufferedOutputStream outputStream = new BufferedOutputStream(client.getOutputStream())) {
client.setSoTimeout(5000);
Response res = new Response(outputStream, staticFilesPath);

OutputStream outputStream = client.getOutputStream()) {
client.setSoTimeout(initialSocketTimeout);
Response res = new Response(outputStream, staticFilesPath, false); // At first, it is false,
// Because we won't continue sending responses after the error
try {
Request req;
while ((req = parser.parseRequest(this, inputStream)) != null) {
res = new Response(outputStream, staticFilesPath);
client.setSoTimeout(20000);
while ((req = parser.parseRequest(this, inputStream, res)) != null) {
res = new Response(outputStream, staticFilesPath, req.isKeepAlive()); // Now when we respond, that's when the isKeepAlive is needed
client.setSoTimeout(processingSocketTimeout);
router.handle(req, res);
if (!res.isSent()) {
res.status(404).send("404 - Not Found");
if (!res.isSent()){
if (res.isChunked()){
res.finishChunkedResponse();
}
else {
res.status(404).send("404 - Not Found");
}
}
try {
if (!req.isCached()) {
inputStream.skipNBytes(req.contentLength() - req.bytesRead());
if (!req.isCached()){
if (req.isChunked()){
req.body();
}
else {
inputStream.skipNBytes(req.contentLength() - req.bytesRead());
}
}
} catch (EOFException e) {
break;
}

if (!req.isKeepAlive()) {
break;
}

}
} catch (IllegalArgumentException e) {
res.status(400).send("400 - Bad Request (Malformed URL)");
Expand All @@ -59,6 +89,8 @@ public void handle(Socket client) {
} catch (Exception e) {
res.status(500).send("500 - Internal Server Error");
throw e;
} finally {
res.finishChunkedResponse();
}

} catch (Exception e) {
Expand Down Expand Up @@ -90,7 +122,7 @@ public void setMaxEmptyLines(int maxEmptyLines) {
*
* @return return the max body size in a request
*/
int getMaxBodySize() {
long getMaxBodySize() {
return maxBodySize;
}

Expand All @@ -99,7 +131,7 @@ int getMaxBodySize() {
*
* @param maxBodySize the max body size
*/
public void setMaxBodySize(int maxBodySize) {
public void setMaxBodySize(long maxBodySize) {
this.maxBodySize = maxBodySize;
}

Expand Down Expand Up @@ -157,6 +189,22 @@ public void setMaxHeaderSize(int maxHeaderSize) {
this.maxHeaderSize = maxHeaderSize;
}

long getMaxChunkSize() {
return maxChunkSize;
}

public void setMaxChunkSize(int maxChunkSize) {
this.maxChunkSize = maxChunkSize;
}

long getMaxChunkCount() {
return maxChunkCount;
}

public void setMaxChunkCount(long maxChunkCount) {
this.maxChunkCount = maxChunkCount;
}

/**
* Sets the path/directory in which all static files are set.
*
Expand All @@ -167,4 +215,41 @@ public void setMaxHeaderSize(int maxHeaderSize) {
public void setStaticFiles(String staticFilesPath) {
this.staticFilesPath = staticFilesPath;
}

/**
* Get the initial socket timeout for all incoming HTTP requests.
*
* @return return the initial socket timeout for all incoming HTTP requests
*/
int getInitialSocketTimeout() {
return initialSocketTimeout;
}

/**
* Set the initial socket timeout for all incoming HTTP requests
*
* @param initialSocketTimeout the initial socket timeout
*/
public void setInitialSocketTimeout(int initialSocketTimeout){
this.initialSocketTimeout = initialSocketTimeout;
}

/**
* Get the processing socket timeout for all incoming HTTP requests.
*
* @return return the processing socket timeout for all incoming HTTP requests
*/
int getProcessingSocketTimeout() {
return processingSocketTimeout;
}

/**
* Set the processing socket timeout for all incoming HTTP requests
*
* @param processingSocketTimeout the processing socket timeout
*/
public void setProcessingSocketTimeout(int processingSocketTimeout){
this.processingSocketTimeout = processingSocketTimeout;
}

}
Loading
Loading