Skip to content

Commit cfe7311

Browse files
authored
Minecraft 26.1 (#779)
* Switch webserver implementation from single-thread nio to blocking IO using a virtual-thread per connection * Require Java 25, initial fabric 26.1 support * Fix github-action java version * Fix junit setup and move lombok and jetbrains-annotation depedency dclarations * Reenable paper, sponge and spigot * Initial neoforge 25.1 support, migrate use of deprecated gradle features * Prepare for 26.1 release
1 parent 02faeb4 commit cfe7311

38 files changed

Lines changed: 587 additions & 732 deletions

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
uses: actions/setup-java@v5
2727
with:
2828
distribution: 'temurin'
29-
java-version: 21
29+
java-version: 25
3030
cache: 'gradle'
3131
- name: Tests
3232
run: ./gradlew spotlessCheck test --stacktrace
@@ -44,7 +44,7 @@ jobs:
4444
uses: actions/setup-java@v5
4545
with:
4646
distribution: 'temurin'
47-
java-version: 21
47+
java-version: 25
4848
cache: 'gradle'
4949
- name: Build with Gradle
5050
run: ./gradlew release --stacktrace

buildSrc/src/main/kotlin/bluemap.java.gradle.kts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ plugins {
55
id ( "com.diffplug.spotless" )
66
}
77

8+
var libs = project.extensions.getByType(VersionCatalogsExtension::class).named("libs")
9+
10+
dependencies {
11+
compileOnly ( libs.findLibrary("jetbrains-annotations").get() )
12+
compileOnly ( libs.findLibrary("lombok").get() )
13+
14+
annotationProcessor ( libs.findLibrary("lombok").get() )
15+
16+
testImplementation( platform(libs.findLibrary("junit-bom").get()) )
17+
testImplementation( libs.findBundle("junit-jupiter").get() )
18+
testAnnotationProcessor ( libs.findLibrary("lombok").get() )
19+
20+
testRuntimeOnly( libs.findBundle("junit-runtime").get() )
21+
testRuntimeOnly ( libs.findLibrary("lombok").get() )
22+
}
23+
824
tasks.withType(JavaCompile::class).configureEach {
925
options.encoding = "utf-8"
1026
}
@@ -15,7 +31,7 @@ tasks.withType(AbstractArchiveTask::class).configureEach {
1531
}
1632

1733
java {
18-
toolchain.languageVersion = JavaLanguageVersion.of(21)
34+
toolchain.languageVersion = JavaLanguageVersion.of(25)
1935
withSourcesJar()
2036
withJavadocJar()
2137
}

common/build.gradle.kts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,6 @@ dependencies {
1414

1515
compileOnly ( libs.bluecommands.brigadier )
1616
compileOnly ( libs.brigadier )
17-
18-
compileOnly ( libs.jetbrains.annotations )
19-
compileOnly ( libs.lombok )
20-
21-
annotationProcessor ( libs.lombok )
22-
23-
// tests
24-
testImplementation ( libs.junit.core )
25-
testRuntimeOnly ( libs.junit.engine )
26-
testRuntimeOnly ( libs.lombok )
27-
testAnnotationProcessor ( libs.lombok )
2817
}
2918

3019
node {

common/src/main/java/de/bluecolored/bluemap/common/web/BlueMapResponseModifier.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ public HttpResponse handle(HttpRequest request) {
4949
HttpResponse response = delegate.handle(request);
5050

5151
HttpStatusCode status = response.getStatusCode();
52-
if (status.getCode() >= 400 && !response.hasData()){
53-
response.setData(status.getCode() + " - " + status.getMessage() + "\n" + this.serverName);
52+
if (status.getCode() >= 400 && response.getBody() != null){
53+
response.setBody(status.getCode() + " - " + status.getMessage() + "\n" + this.serverName);
5454
}
5555

5656
response.addHeader("Server", this.serverName);

common/src/main/java/de/bluecolored/bluemap/common/web/FileRequestHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private HttpResponse generateResponse(HttpRequest request) throws IOException {
8686
// redirect to have correct relative paths
8787
if (Files.isDirectory(filePath) && !request.getPath().endsWith("/")) {
8888
HttpResponse response = new HttpResponse(HttpStatusCode.SEE_OTHER);
89-
response.addHeader("Location", "/" + path + "/" + (request.getGETParamString().isEmpty() ? "" : "?" + request.getGETParamString()));
89+
response.addHeader("Location", "/" + path + "/" + (request.getRawQueryString().isEmpty() ? "" : "?" + request.getRawQueryString()));
9090
return response;
9191
}
9292

@@ -152,7 +152,7 @@ private HttpResponse generateResponse(HttpRequest request) throws IOException {
152152

153153
//send response
154154
try {
155-
response.setData(Files.newInputStream(filePath));
155+
response.setBody(Files.newInputStream(filePath));
156156
return response;
157157
} catch (FileNotFoundException e) {
158158
return new HttpResponse(HttpStatusCode.NOT_FOUND);

common/src/main/java/de/bluecolored/bluemap/common/web/JsonDataRequestHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public HttpResponse handle(HttpRequest request) {
4848
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
4949
response.addHeader("Cache-Control", "no-cache");
5050
response.addHeader("Content-Type", "application/json");
51-
response.setData(dataSupplier.get());
51+
response.setBody(dataSupplier.get());
5252
return response;
5353
}
5454

common/src/main/java/de/bluecolored/bluemap/common/web/LoggingRequestHandler.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
import lombok.NonNull;
3232
import lombok.Setter;
3333

34-
import java.net.URI;
35-
3634
@Getter @Setter
3735
@AllArgsConstructor
3836
public class LoggingRequestHandler implements HttpRequestHandler {
@@ -65,7 +63,9 @@ public HttpResponse handle(HttpRequest request) {
6563
}
6664

6765
String method = request.getMethod();
68-
URI address = request.getAddress();
66+
String path = request.getPath();
67+
String queryString = request.getRawQueryString();
68+
String address = queryString == null ? path : path + "?" + queryString;
6969
String version = request.getVersion();
7070

7171
// run request
@@ -81,7 +81,7 @@ public HttpResponse handle(HttpRequest request) {
8181
source,
8282
xffSource,
8383
method,
84-
address.toString(),
84+
address,
8585
version,
8686
statusCode,
8787
statusMessage

common/src/main/java/de/bluecolored/bluemap/common/web/MapStorageRequestHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private void writeToResponse(CompressedInputStream data, HttpResponse response,
122122
request.hasHeaderValue("Accept-Encoding", compression.getId())
123123
) {
124124
response.addHeader("Content-Encoding", compression.getId());
125-
response.setData(data);
125+
response.setBody(data);
126126
} else if (
127127
compression != Compression.GZIP &&
128128
!response.hasHeaderValue("Content-Type", "image/png") &&
@@ -134,9 +134,9 @@ private void writeToResponse(CompressedInputStream data, HttpResponse response,
134134
data.decompress().transferTo(os);
135135
}
136136
byte[] compressedData = byteOut.toByteArray();
137-
response.setData(new ByteArrayInputStream(compressedData));
137+
response.setBody(new ByteArrayInputStream(compressedData));
138138
} else {
139-
response.setData(data.decompress());
139+
response.setBody(data.decompress());
140140
}
141141
}
142142

common/src/main/java/de/bluecolored/bluemap/common/web/http/HttpConnection.java

Lines changed: 36 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -25,134 +25,57 @@
2525
package de.bluecolored.bluemap.common.web.http;
2626

2727
import de.bluecolored.bluemap.core.logger.Logger;
28+
import lombok.RequiredArgsConstructor;
2829

30+
import java.io.BufferedInputStream;
31+
import java.io.BufferedOutputStream;
32+
import java.io.EOFException;
2933
import java.io.IOException;
30-
import java.net.InetAddress;
31-
import java.net.InetSocketAddress;
32-
import java.net.SocketAddress;
33-
import java.nio.channels.Channel;
34-
import java.nio.channels.SelectableChannel;
35-
import java.nio.channels.SelectionKey;
36-
import java.nio.channels.SocketChannel;
37-
import java.util.concurrent.CompletableFuture;
38-
import java.util.concurrent.Executor;
34+
import java.net.Socket;
35+
import java.net.SocketTimeoutException;
3936

40-
public class HttpConnection implements SelectionConsumer {
37+
public class HttpConnection implements Runnable {
4138

39+
private final Socket socket;
40+
private final HttpRequestInputStream requestIn;
41+
private final HttpResponseOutputStream responseOut;
4242
private final HttpRequestHandler requestHandler;
43-
private final Executor responseHandlerExecutor;
44-
private HttpRequest request;
45-
private CompletableFuture<HttpResponse> futureResponse;
46-
private HttpResponse response;
4743

48-
public HttpConnection(HttpRequestHandler requestHandler) {
49-
this(requestHandler, Runnable::run); //run synchronously
50-
}
51-
52-
public HttpConnection(HttpRequestHandler requestHandler, Executor responseHandlerExecutor) {
44+
public HttpConnection(Socket socket, HttpRequestHandler requestHandler) throws IOException {
45+
this.socket = socket;
5346
this.requestHandler = requestHandler;
54-
this.responseHandlerExecutor = responseHandlerExecutor;
55-
}
56-
57-
@Override
58-
public void accept(SelectionKey selectionKey) {
59-
if (!selectionKey.isValid()) return;
6047

61-
SelectableChannel selChannel = selectionKey.channel();
62-
63-
if (!(selChannel instanceof SocketChannel)) return;
64-
SocketChannel channel = (SocketChannel) selChannel;
48+
this.requestIn = new HttpRequestInputStream(new BufferedInputStream(socket.getInputStream()), socket.getInetAddress());
49+
this.responseOut = new HttpResponseOutputStream(new BufferedOutputStream(socket.getOutputStream()));
50+
}
6551

52+
public void run() {
6653
try {
54+
while (socket.isConnected() && !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown()) {
55+
HttpRequest request = requestIn.read();
56+
if (request == null) continue;
6757

68-
if (request == null) {
69-
SocketAddress remote = channel.getRemoteAddress();
70-
InetAddress remoteInet = null;
71-
if (remote instanceof InetSocketAddress)
72-
remoteInet = ((InetSocketAddress) remote).getAddress();
73-
74-
request = new HttpRequest(remoteInet);
75-
}
76-
77-
// receive request
78-
if (!request.write(channel)) {
79-
if (!selectionKey.isValid()) return;
80-
selectionKey.interestOps(SelectionKey.OP_READ);
81-
return;
82-
}
83-
84-
// process request
85-
if (futureResponse == null) {
86-
futureResponse = CompletableFuture.supplyAsync(
87-
() -> requestHandler.handle(request),
88-
responseHandlerExecutor
89-
);
90-
futureResponse.handle((response, error) -> {
91-
if (error != null) {
92-
Logger.global.logError("Unexpected error handling request", error);
93-
response = new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
94-
}
95-
96-
try {
97-
response.read(channel); // do an initial read to trigger response sending intent
98-
this.response = response;
99-
} catch (IOException e) {
100-
handleIOException(channel, e);
101-
}
102-
103-
return null;
104-
});
105-
}
106-
107-
if (response == null) return;
108-
if (!selectionKey.isValid()) return;
109-
110-
// send response
111-
if (!response.read(channel)){
112-
selectionKey.interestOps(SelectionKey.OP_WRITE);
113-
return;
58+
try (HttpResponse response = requestHandler.handle(request)) {
59+
responseOut.write(response);
60+
}
11461
}
115-
116-
// reset to accept new request
117-
request.clear();
118-
response.close();
119-
futureResponse = null;
120-
response = null;
121-
selectionKey.interestOps(SelectionKey.OP_READ);
122-
62+
} catch (EOFException | SocketTimeoutException ignore) {
63+
// ignore known exceptions that happen when browsers or us close the connection
12364
} catch (IOException e) {
124-
handleIOException(channel, e);
125-
}
126-
}
127-
128-
private void handleIOException(Channel channel, IOException e) {
129-
request.clear();
130-
131-
if (response != null) {
65+
if ( // ignore known exceptions that happen when browsers close the connection
66+
e.getMessage() == null ||
67+
!e.getMessage().equals("Broken pipe")
68+
) {
69+
Logger.global.logDebug("Exception in HttpConnection: " + e);
70+
}
71+
} catch (Exception e) {
72+
Logger.global.logDebug("Exception in HttpConnection: " + e);
73+
} finally {
13274
try {
133-
response.close();
134-
} catch (IOException e2) {
135-
Logger.global.logWarning("Failed to close response: " + e2);
75+
socket.close();
76+
} catch (IOException e) {
77+
Logger.global.logDebug("Exception closing HttpConnection: " + e);
13678
}
137-
response = null;
138-
}
139-
140-
if (futureResponse != null) {
141-
futureResponse.thenAccept(response -> {
142-
try {
143-
response.close();
144-
} catch (IOException e2) {
145-
Logger.global.logWarning("Failed to close response: " + e2);
146-
}
147-
});
148-
futureResponse = null;
149-
}
150-
151-
Logger.global.logDebug("Failed to process selection: " + e);
152-
try {
153-
channel.close();
154-
} catch (IOException e2) {
155-
Logger.global.logWarning("Failed to close channel" + e2);
15679
}
15780
}
15881

0 commit comments

Comments
 (0)