Skip to content

Commit bc28a4e

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 50bf1a1 + e46bb6b commit bc28a4e

49 files changed

Lines changed: 969 additions & 89 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
target/
22
/.idea/
33
/META-INF
4+
5+
.DS_Store

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Pipeline
4747
4848
FilterChain
4949
50+
Router
51+
5052
Plugin
5153
5254
HttpResponseWriter
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# ADR-002: IP-based Request Filtering
2+
3+
**Date:** 2026-02-25
4+
**Status:** Accepted
5+
**Deciders:** Team juv25d
6+
**Technical:** - PR #59 - Add IP filter to request pipeline
7+
8+
---
9+
10+
## Context
11+
12+
We want to restrict access to the HTTP server based on the client IP address.
13+
This should happen early in the request, before any route/handler/static file logic.
14+
15+
The initial implementation should be simple, easy to configure and safe to use during group development.
16+
17+
---
18+
19+
## Decision
20+
21+
We implement an IP filter as a global filter in the server pipeline.
22+
23+
The filter supports three modes:
24+
25+
- **Whitelist mode:** only IPs explicitly listed are allowed.
26+
- **Blacklist mode:** all IPs are allowed except those explicitly listed.
27+
- **Open mode:** if both whitelist and blacklist are empty, all requests are allowed (useful during development)
28+
29+
The initial implementation uses **exact IP address matching**.
30+
31+
---
32+
33+
## Rationale
34+
35+
The design prioritizes:
36+
- clear and predictable behavior
37+
- simple configuration
38+
- low friction for development environments
39+
40+
Whitelist and blacklist are intentionally treated as separate configuration approaches.
41+
Using both simultaneously can be ambiguous, so the project can either document precedence rules or enforce a single-mode configuration in future iterations.
42+
43+
---
44+
45+
## Consequences
46+
47+
### Positive
48+
- Centralized access control early in the pipeline
49+
- Easy to enable/disable restrictions via configuration
50+
- Works well for group development (open mode)
51+
52+
### Trade-offs
53+
- Exact IP matching only.
54+
- Potential ambiguity if both lists are configured
55+
56+
---
57+
58+
## Alternatives Considered
59+
60+
### Enforce single mode (whitelist or blacklist)
61+
**Pros:** clear API contract
62+
**Cons:** less flexible during experimentation
63+
**Status:** considered for future refinement if the team wants stricter validation
64+
65+
---

docs/adr/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ An ADR can have one of the following statuses:
4747
| ADR | Title | Status | Date |
4848
|-----|-------|--------|------|
4949
| [001](ADR-001-static-file-serving-architecture.md) | Static File Serving Architecture | Accepted | 2026-02-11 |
50+
| [002](ADR-002-ip-based-request-filtering.md) | IP-based Request Filtering | Accepted | 2026-02-25 |
5051

5152
---
5253

pom.xml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
<junit.jupiter.version>6.0.2</junit.jupiter.version>
2222
<assertj.core.version>3.27.7</assertj.core.version>
2323
<mockito.version>5.21.0</mockito.version>
24+
<errorprone.version>2.45.0</errorprone.version>
25+
<nullaway.version>0.13.1</nullaway.version>
2426
<build.time>${maven.build.timestamp}</build.time>
2527
<argLine/>
2628
</properties>
@@ -43,6 +45,12 @@
4345
<version>${mockito.version}</version>
4446
<scope>test</scope>
4547
</dependency>
48+
<dependency>
49+
<groupId>com.tngtech.archunit</groupId>
50+
<artifactId>archunit</artifactId>
51+
<version>1.4.1</version>
52+
<scope>test</scope>
53+
</dependency>
4654
<dependency>
4755
<groupId>org.yaml</groupId>
4856
<artifactId>snakeyaml</artifactId>
@@ -66,6 +74,11 @@
6674
<version>1.21.4</version>
6775
<scope>test</scope>
6876
</dependency>
77+
<dependency>
78+
<groupId>org.jspecify</groupId>
79+
<artifactId>jspecify</artifactId>
80+
<version>1.0.0</version>
81+
</dependency>
6982
</dependencies>
7083
<build>
7184
<plugins>
@@ -78,6 +91,36 @@
7891
<groupId>org.apache.maven.plugins</groupId>
7992
<artifactId>maven-compiler-plugin</artifactId>
8093
<version>3.15.0</version>
94+
<configuration>
95+
<fork>true</fork>
96+
<compilerArgs>
97+
<arg>-XDcompilePolicy=simple</arg>
98+
<arg>--should-stop=ifError=FLOW</arg>
99+
<arg>-Xplugin:ErrorProne -Xep:NullAway:ERROR -XepOpt:NullAway:AnnotatedPackages=org.juv25d</arg>
100+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
101+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
102+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
103+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
104+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
105+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
106+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
107+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
108+
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
109+
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
110+
</compilerArgs>
111+
<annotationProcessorPaths>
112+
<path>
113+
<groupId>com.google.errorprone</groupId>
114+
<artifactId>error_prone_core</artifactId>
115+
<version>${errorprone.version}</version>
116+
</path>
117+
<path>
118+
<groupId>com.uber.nullaway</groupId>
119+
<artifactId>nullaway</artifactId>
120+
<version>${nullaway.version}</version>
121+
</path>
122+
</annotationProcessorPaths>
123+
</configuration>
81124
</plugin>
82125
<plugin>
83126
<groupId>org.apache.maven.plugins</groupId>

src/main/java/org/juv25d/App.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.juv25d.router.SimpleRouter;
88
import org.juv25d.util.ConfigLoader;
99

10+
import java.util.logging.Level;
1011
import java.util.logging.Logger;
1112

1213
public class App {
@@ -28,12 +29,19 @@ public static void main(String[] args) {
2829
Server server = new Server(
2930
config.getPort(),
3031
logger,
31-
handlerFactory,
32-
pipeline
32+
handlerFactory
3333
);
3434

3535
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
36-
logger.info("Server shutting down...");
36+
logger.info("Shutting down...");
37+
try {
38+
pipeline.stop();
39+
logger.info("Shutdown successful");
40+
}catch (Exception ex){
41+
logger.log(Level.SEVERE, "Error stopping server", ex);
42+
}finally {
43+
logger.info("Shutdown hook finished");
44+
}
3745
}));
3846

3947
server.start();

src/main/java/org/juv25d/ConnectionHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public void run() {
3232
var out = socket.getOutputStream();
3333

3434
HttpRequest parsed = httpParser.parse(in);
35-
String remoteIp = socket.getInetAddress().getHostAddress();
35+
java.net.InetAddress inetAddress = socket.getInetAddress();
36+
String remoteIp = (inetAddress != null) ? inetAddress.getHostAddress() : "UNKNOWN";
3637

3738
HttpRequest request = new HttpRequest(
3839
parsed.method(),

src/main/java/org/juv25d/ConnectionHandlerFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
import java.net.Socket;
44

55
public interface ConnectionHandlerFactory {
6-
Runnable create(Socket socket, Pipeline pipeline);
6+
Runnable create(Socket socket);
77
}

src/main/java/org/juv25d/DefaultConnectionHandlerFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public DefaultConnectionHandlerFactory(HttpParser httpParser, Logger logger, Pip
1717
}
1818

1919
@Override
20-
public Runnable create(Socket socket, Pipeline pipeline) {
21-
return new ConnectionHandler(socket, httpParser, logger, pipeline);
20+
public Runnable create(Socket socket) {
21+
return new ConnectionHandler(socket, httpParser, logger, this.pipeline);
2222
}
2323
}

src/main/java/org/juv25d/Pipeline.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ public FilterChainImpl createChain(HttpRequest request) {
2222
List<Filter> filters = matcher.match(request);
2323
return new FilterChainImpl(filters, router);
2424
}
25+
26+
public void stop() {
27+
matcher.destroy();
28+
}
2529
}

0 commit comments

Comments
 (0)