Skip to content

Commit 1b37e5f

Browse files
committed
Bump version to 1.0.4 and improve server lifecycle management
Version Updates: - Bump module version from 1.0.3 to 1.0.4 - Upgrade tinystruct dependency from 1.7.10 to 1.7.12 - Upgrade central-publishing-maven-plugin from 0.7.0 to 0.8.0 Server Lifecycle Improvements: - Add proper shutdown hook registration before server start - Store ChannelFuture as instance variable for proper cleanup - Improve stop() method with channel close synchronization - Remove redundant shutdown hook and finally block - Disable template requirement in init() SSL Configuration Enhancement: - Extract SSL configuration to dedicated configureSsl() method - Add support for custom SSL certificates via configuration - Read certificate/key paths from settings (ssl.certificate.path, ssl.key.path) - Fall back to self-signed certificate with warning if paths not provided - Add logging for production SSL configuration recommendations Bug Fixes: - Fix SSE Content-Type header from invalid "text/event-stream, application/json" to proper "text/event-stream; charset=utf-8" - Simplify exceptionCaught() method signature by removing Context parameter Code Quality: - Add missing File import - Improve error handling in channel close with InterruptedException handling - Better separation of concerns with SSL configuration extraction
1 parent 9d42cf1 commit 1b37e5f

File tree

3 files changed

+53
-20
lines changed

3 files changed

+53
-20
lines changed

pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<name>netty http server module</name>
77
<groupId>org.tinystruct</groupId>
88
<artifactId>tinystruct-netty-http-server</artifactId>
9-
<version>1.0.3</version>
9+
<version>1.0.4</version>
1010
<description>A tinystruct-based module to enable netty http server support.</description>
1111
<url>https://tinystruct.org</url>
1212
<licenses>
@@ -32,7 +32,7 @@
3232
<maven.compiler.target>17</maven.compiler.target>
3333
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3434
<netty.version>4.2.7.Final</netty.version>
35-
<tinystruct.version>1.7.10</tinystruct.version>
35+
<tinystruct.version>1.7.12</tinystruct.version>
3636
<jupiter.version>6.0.1</jupiter.version>
3737
</properties>
3838

@@ -233,7 +233,7 @@
233233
<plugin>
234234
<groupId>org.sonatype.central</groupId>
235235
<artifactId>central-publishing-maven-plugin</artifactId>
236-
<version>0.7.0</version>
236+
<version>0.8.0</version>
237237
<extensions>true</extensions>
238238
<configuration>
239239
<publishingServerId>central</publishingServerId>

src/main/java/org/tinystruct/handler/HttpRequestHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ private SSEPushManager getAppropriatePushManager(boolean isMCP) {
247247
private void handleSSE(final ChannelHandlerContext ctx, final Request<FullHttpRequest, Object> request,
248248
Response<FullHttpResponse, FullHttpResponse> response, final Context context, boolean keepAlive) {
249249
// Set SSE headers using the existing response infrastructure
250-
response.addHeader(Header.CONTENT_TYPE.name(), "text/event-stream, application/json");
250+
response.addHeader(Header.CONTENT_TYPE.name(), "text/event-stream; charset=utf-8");
251251
response.addHeader(Header.CACHE_CONTROL.name(), "no-cache");
252252
response.addHeader(Header.CONNECTION.name(), "keep-alive");
253253
response.addHeader(Header.TRANSFER_ENCODING.name(), "chunked");

src/main/java/org/tinystruct/system/NettyHttpServer.java

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.tinystruct.system.annotation.Action;
4343
import org.tinystruct.system.annotation.Argument;
4444

45+
import java.io.File;
4546
import java.util.Objects;
4647
import java.util.logging.Level;
4748
import java.util.logging.Logger;
@@ -71,6 +72,7 @@ public class NettyHttpServer extends AbstractApplication implements Bootstrap {
7172
private final EventLoopGroup workgroup;
7273
private final Logger logger = Logger.getLogger(NettyHttpServer.class.getName());
7374
private int port = 8080;
75+
private ChannelFuture future;
7476

7577
public NettyHttpServer() {
7678
if (Epoll.isAvailable()) {
@@ -84,7 +86,7 @@ public NettyHttpServer() {
8486

8587
@Override
8688
public void init() {
87-
89+
this.setTemplateRequired(false);
8890
}
8991

9092
@Override
@@ -129,6 +131,12 @@ public void start() throws ApplicationException {
129131
}
130132
}
131133

134+
// Add shutdown hook BEFORE starting server
135+
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
136+
logger.info("Shutting down HTTP server...");
137+
stop();
138+
}));
139+
132140
System.out.println(ApplicationManager.call("--logo", null, Action.Mode.CLI));
133141

134142
String charsetName = null;
@@ -156,8 +164,7 @@ public void start() throws ApplicationException {
156164
// Configure SSL.
157165
final SslContext sslCtx;
158166
if (SSL) {
159-
SelfSignedCertificate ssc = new SelfSignedCertificate();
160-
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
167+
sslCtx = configureSsl(settings);
161168
} else {
162169
sslCtx = null;
163170
}
@@ -190,7 +197,7 @@ public void initChannel(SocketChannel ch) {
190197
}).option(ChannelOption.SO_BACKLOG, 1024).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000).childOption(ChannelOption.SO_KEEPALIVE, false).childOption(ChannelOption.TCP_NODELAY, true);
191198

192199
// Bind and start to accept incoming connections.
193-
ChannelFuture future = bootstrap.bind(port).sync();
200+
future = bootstrap.bind(port).sync();
194201
logger.info("Netty server (" + port + ") startup in " + (System.currentTimeMillis() - start) + " ms");
195202

196203
// Open the default browser
@@ -200,32 +207,58 @@ public void initChannel(SocketChannel ch) {
200207
// Keep the server running
201208
logger.info("Server is running. Press Ctrl+C to stop.");
202209

203-
// Add shutdown hook
204-
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
205-
logger.info("Shutting down HTTP server...");
206-
stop();
207-
}));
208-
209210
// Wait until the server socket is closed.
210211
future.channel().closeFuture().sync();
211212
} catch (Exception e) {
212213
throw new ApplicationException(e.getMessage(), e.getCause());
213-
} finally {
214-
this.stop();
215214
}
216215
}
217216

218217
@Override
219218
public void stop() {
219+
// Close the channel first
220+
if (future != null && future.channel().isOpen()) {
221+
try {
222+
future.channel().close().sync();
223+
} catch (InterruptedException e) {
224+
logger.log(Level.WARNING, "Interrupted while closing channel", e);
225+
Thread.currentThread().interrupt();
226+
}
227+
}
228+
229+
// Then shutdown event loops gracefully
220230
bossgroup.shutdownGracefully();
221231
workgroup.shutdownGracefully();
222232
}
223233

224-
@Action(value = "error", description = "Error page")
225-
public Object exceptionCaught() throws ApplicationException {
226-
Request request = (Request) getContext().getAttribute(HTTP_REQUEST);
227-
Response response = (Response) getContext().getAttribute(HTTP_RESPONSE);
234+
private SslContext configureSsl(Configuration<String> settings) throws Exception {
235+
if (!SSL) {
236+
return null;
237+
}
238+
239+
String certPath = settings.get("ssl.certificate.path");
240+
String keyPath = settings.get("ssl.key.path");
228241

242+
if (!certPath.isEmpty() && !keyPath.isEmpty()) {
243+
// Use provided certificate
244+
return SslContextBuilder.forServer(
245+
new File(certPath),
246+
new File(keyPath)
247+
).build();
248+
} else {
249+
// Fall back to self-signed certificate
250+
logger.warning("Using self-signed certificate. " +
251+
"Configure ssl.certificate.path and ssl.key.path for production.");
252+
SelfSignedCertificate ssc = new SelfSignedCertificate();
253+
return SslContextBuilder.forServer(
254+
ssc.certificate(),
255+
ssc.privateKey()
256+
).build();
257+
}
258+
}
259+
260+
@Action(value = "error", description = "Error page")
261+
public Object exceptionCaught(Request request, Response response) throws ApplicationException {
229262
Reforward reforward = new Reforward(request, response);
230263
this.setVariable("from", reforward.getFromURL());
231264

0 commit comments

Comments
 (0)