Skip to content

Commit 032d91d

Browse files
Merge remote-tracking branch 'origin/main' into Implement-Configurable-Filter-Pipeline-(Global-+-Per‑Route-Filters)
2 parents 3e3a217 + fa1599a commit 032d91d

23 files changed

Lines changed: 1227 additions & 22 deletions

Dockerfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ FROM eclipse-temurin:25-jre-alpine
99
EXPOSE 8080
1010
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
1111
WORKDIR /app/
12-
COPY --from=build /build/target/classes/ /
13-
COPY --from=build /build/target/dependency/ /dependencies/
14-
COPY /www/ /www/
12+
COPY --from=build /build/target/classes/ /app/
13+
COPY --from=build /build/target/dependency/ /app/dependencies/
14+
COPY www/ ./www/
1515
USER appuser
16-
ENTRYPOINT ["java", "-classpath", "/app:/dependencies/*", "org.example.App"]
16+
ENTRYPOINT ["java", "-classpath", "/app:/app/dependencies/*", "org.example.App"]

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@
6565
<artifactId>jackson-dataformat-yaml</artifactId>
6666
<version>3.0.3</version>
6767
</dependency>
68+
<dependency>
69+
<groupId>com.aayushatharva.brotli4j</groupId>
70+
<artifactId>brotli4j</artifactId>
71+
<version>1.20.0</version>
72+
</dependency>
6873

6974
</dependencies>
7075
<build>

src/main/java/org/example/App.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.example.config.AppConfig;
44
import org.example.config.ConfigLoader;
55

6+
import java.net.Socket;
67
import java.nio.file.Path;
78

89
public class App {
@@ -16,7 +17,7 @@ public static void main(String[] args) {
1617

1718
int port = resolvePort(args, appConfig.server().port());
1819

19-
new TcpServer(port).start();
20+
new TcpServer(port, ConnectionHandler::new).start();
2021
}
2122

2223
static int resolvePort(String[] args, int configPort) {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.example;
2+
3+
import java.net.Socket;
4+
5+
public interface ConnectionFactory {
6+
ConnectionHandler create(Socket socket);
7+
}
Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
package org.example;
22

3+
import org.example.config.AppConfig;
4+
import org.example.filter.IpFilter;
35
import org.example.httpparser.HttpParser;
6+
import org.example.httpparser.HttpRequest;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import org.example.filter.Filter;
10+
import org.example.filter.FilterChainImpl;
11+
import org.example.http.HttpResponseBuilder;
12+
import org.example.config.ConfigLoader;
413

514
import java.io.IOException;
615
import java.net.Socket;
@@ -9,34 +18,115 @@ public class ConnectionHandler implements AutoCloseable {
918

1019
Socket client;
1120
String uri;
21+
private final List<Filter> filters;
22+
String webRoot;
1223

1324
public ConnectionHandler(Socket client) {
1425
this.client = client;
26+
this.filters = buildFilters();
27+
this.webRoot = null;
28+
}
29+
30+
public ConnectionHandler(Socket client, String webRoot) {
31+
this.client = client;
32+
this.webRoot = webRoot;
33+
this.filters = buildFilters();
34+
}
35+
36+
private List<Filter> buildFilters() {
37+
List<Filter> list = new ArrayList<>();
38+
AppConfig config = ConfigLoader.get();
39+
AppConfig.IpFilterConfig ipFilterConfig = config.ipFilter();
40+
if (Boolean.TRUE.equals(ipFilterConfig.enabled())) {
41+
list.add(createIpFilterFromConfig(ipFilterConfig));
42+
}
43+
// Add more filters here...
44+
return list;
1545
}
1646

1747
public void runConnectionHandler() throws IOException {
18-
StaticFileHandler sfh = new StaticFileHandler();
48+
StaticFileHandler sfh;
49+
50+
if (webRoot != null) {
51+
sfh = new StaticFileHandler(webRoot);
52+
} else {
53+
sfh = new StaticFileHandler();
54+
}
55+
1956
HttpParser parser = new HttpParser();
2057
parser.setReader(client.getInputStream());
2158
parser.parseRequest();
2259
parser.parseHttp();
60+
61+
HttpRequest request = new HttpRequest(
62+
parser.getMethod(),
63+
parser.getUri(),
64+
parser.getVersion(),
65+
parser.getHeadersMap(),
66+
""
67+
);
68+
69+
String clientIp = client.getInetAddress().getHostAddress();
70+
request.setAttribute("clientIp", clientIp);
71+
72+
HttpResponseBuilder response = applyFilters(request);
73+
74+
int statusCode = response.getStatusCode();
75+
if (statusCode == HttpResponseBuilder.SC_FORBIDDEN ||
76+
statusCode == HttpResponseBuilder.SC_BAD_REQUEST) {
77+
byte[] responseBytes = response.build();
78+
client.getOutputStream().write(responseBytes);
79+
client.getOutputStream().flush();
80+
return;
81+
}
82+
2383
resolveTargetFile(parser.getUri());
2484
sfh.sendGetRequest(client.getOutputStream(), uri);
2585
}
2686

87+
private HttpResponseBuilder applyFilters(HttpRequest request) {
88+
HttpResponseBuilder response = new HttpResponseBuilder();
89+
90+
FilterChainImpl chain = new FilterChainImpl(filters);
91+
chain.doFilter(request, response);
92+
93+
return response;
94+
}
95+
2796
private void resolveTargetFile(String uri) {
28-
if (uri.matches("/$")) { //matches(/)
97+
if (uri == null || "/".equals(uri)) {
2998
this.uri = "index.html";
30-
} else if (uri.matches("^(?!.*\\.html$).*$")) {
31-
this.uri = uri.concat(".html");
3299
} else {
33-
this.uri = uri;
100+
this.uri = uri.startsWith("/") ? uri.substring(1) : uri;
34101
}
35-
36102
}
37103

38104
@Override
39105
public void close() throws Exception {
40106
client.close();
41107
}
42-
}
108+
109+
private IpFilter createIpFilterFromConfig(AppConfig.IpFilterConfig config) {
110+
IpFilter filter = new IpFilter();
111+
112+
// Set mode
113+
if ("ALLOWLIST".equalsIgnoreCase(config.mode())) {
114+
filter.setMode(IpFilter.FilterMode.ALLOWLIST);
115+
} else {
116+
filter.setMode(IpFilter.FilterMode.BLOCKLIST);
117+
}
118+
119+
// Add blocked IPs
120+
for (String ip : config.blockedIps()) {
121+
filter.addBlockedIp(ip);
122+
}
123+
124+
// Add allowed IPs
125+
for (String ip : config.allowedIps()) {
126+
filter.addAllowedIp(ip);
127+
}
128+
129+
filter.init();
130+
return filter;
131+
}
132+
}

src/main/java/org/example/TcpServer.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package org.example;
22

3+
import org.example.http.HttpResponseBuilder;
4+
35
import java.io.IOException;
6+
import java.io.OutputStream;
7+
import java.io.PrintWriter;
48
import java.net.ServerSocket;
59
import java.net.Socket;
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.Map;
612

713
public class TcpServer {
814

915
private final int port;
16+
private final ConnectionFactory connectionFactory;
1017

11-
public TcpServer(int port) {
18+
public TcpServer(int port, ConnectionFactory connectionFactory) {
1219
this.port = port;
20+
this.connectionFactory = connectionFactory;
1321
}
1422

1523
public void start() {
@@ -26,11 +34,42 @@ public void start() {
2634
}
2735
}
2836

29-
private void handleClient(Socket client) {
30-
try (ConnectionHandler connectionHandler = new ConnectionHandler(client)) {
31-
connectionHandler.runConnectionHandler();
37+
protected void handleClient(Socket client) {
38+
try(client){
39+
processRequest(client);
40+
} catch (Exception e) {
41+
throw new RuntimeException("Failed to close socket", e);
42+
}
43+
}
44+
45+
private void processRequest(Socket client) throws Exception {
46+
ConnectionHandler handler = null;
47+
try{
48+
handler = connectionFactory.create(client);
49+
handler.runConnectionHandler();
3250
} catch (Exception e) {
33-
throw new RuntimeException("Error handling client connection " + e);
51+
handleInternalServerError(client);
52+
} finally {
53+
if(handler != null)
54+
handler.close();
55+
}
56+
}
57+
58+
59+
private void handleInternalServerError(Socket client){
60+
HttpResponseBuilder response = new HttpResponseBuilder();
61+
response.setStatusCode(HttpResponseBuilder.SC_INTERNAL_SERVER_ERROR);
62+
response.setHeaders(Map.of("Content-Type", "text/plain; charset=utf-8"));
63+
response.setBody("⚠️ Internal Server Error 500 ⚠️");
64+
65+
if (!client.isClosed()) {
66+
try {
67+
OutputStream out = client.getOutputStream();
68+
out.write(response.build());
69+
out.flush();
70+
} catch (IOException e) {
71+
System.err.println("Failed to send 500 response: " + e.getMessage());
72+
}
3473
}
3574
}
3675
}

src/main/java/org/example/config/AppConfig.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@
66
@JsonIgnoreProperties(ignoreUnknown = true)
77
public record AppConfig(
88
@JsonProperty("server") ServerConfig server,
9-
@JsonProperty("logging") LoggingConfig logging
9+
@JsonProperty("logging") LoggingConfig logging,
10+
@JsonProperty("ipFilter") IpFilterConfig ipFilter
1011
) {
1112
public static AppConfig defaults() {
12-
return new AppConfig(ServerConfig.defaults(), LoggingConfig.defaults());
13+
return new AppConfig(
14+
ServerConfig.defaults(),
15+
LoggingConfig.defaults(),
16+
IpFilterConfig.defaults()
17+
);
1318
}
1419

1520
public AppConfig withDefaultsApplied() {
1621
ServerConfig serverConfig = (server == null ? ServerConfig.defaults() : server.withDefaultsApplied());
1722
LoggingConfig loggingConfig = (logging == null ? LoggingConfig.defaults() : logging.withDefaultsApplied());
18-
return new AppConfig(serverConfig, loggingConfig);
23+
IpFilterConfig ipFilterConfig = (ipFilter == null ? IpFilterConfig.defaults() : ipFilter.withDefaultsApplied()); // ← LÄGG TILL
24+
return new AppConfig(serverConfig, loggingConfig, ipFilterConfig); // ← UPPDATERA DENNA RAD
1925
}
2026

2127
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -50,4 +56,24 @@ public LoggingConfig withDefaultsApplied() {
5056
return new LoggingConfig(lvl);
5157
}
5258
}
59+
60+
@JsonIgnoreProperties(ignoreUnknown = true)
61+
public record IpFilterConfig(
62+
@JsonProperty("enabled") Boolean enabled,
63+
@JsonProperty("mode") String mode,
64+
@JsonProperty("blockedIps") java.util.List<String> blockedIps,
65+
@JsonProperty("allowedIps") java.util.List<String> allowedIps
66+
) {
67+
public static IpFilterConfig defaults() {
68+
return new IpFilterConfig(false, "BLOCKLIST", java.util.List.of(), java.util.List.of());
69+
}
70+
71+
public IpFilterConfig withDefaultsApplied() {
72+
Boolean e = (enabled == null) ? false : enabled;
73+
String m = (mode == null || mode.isBlank()) ? "BLOCKLIST" : mode;
74+
java.util.List<String> blocked = (blockedIps == null) ? java.util.List.of() : blockedIps;
75+
java.util.List<String> allowed = (allowedIps == null) ? java.util.List.of() : allowedIps;
76+
return new IpFilterConfig(e, m, blocked, allowed);
77+
}
78+
}
5379
}

src/main/java/org/example/config/ConfigLoader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ private static ObjectMapper createMapperFor(Path configPath) {
6565
}
6666
}
6767

68-
static void resetForTests() {
68+
public static void resetForTests() {
6969
cached = null;
7070
}
7171
}

0 commit comments

Comments
 (0)