Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e90620e
[SCI-22111][NGX][NGL][NIR][Security] Switch to https for Profile Builder
AnkitManeSciAps Nov 14, 2025
4904102
Updated password and added PbCertificatePath
AnkitManeSciAps Nov 17, 2025
1cf7831
Add fallback logic
AnkitManeSciAps Nov 17, 2025
80e3a7b
undo unwanted changes
AnkitManeSciAps Nov 17, 2025
adc8f59
Add better logs
AnkitManeSciAps Nov 17, 2025
da6e47f
Added new certificate with correct password
AnkitManeSciAps Nov 17, 2025
56fc247
Moving stuff around to solve dependency issues
AnkitManeSciAps Nov 17, 2025
8f8c7eb
Fix dependency issue
AnkitManeSciAps Nov 17, 2025
f02c73b
Address comment fixe
AnkitManeSciAps Nov 17, 2025
cba498a
Simplify logic
AnkitManeSciAps Nov 17, 2025
c9e1578
More fixes for backwards compatibility
AnkitManeSciAps Nov 18, 2025
e9969b1
Add support for TLSv1.3
AnkitManeSciAps Nov 19, 2025
a28f54d
Fix ssl exception issue
AnkitManeSciAps Nov 24, 2025
839db8b
Fix Socket.shutdownOutput issue
AnkitManeSciAps Nov 26, 2025
c4e2f10
Comment fixes
AnkitManeSciAps Dec 2, 2025
de98b48
Fix speed issue
AnkitManeSciAps Dec 12, 2025
fb48db1
Add client side auth
AnkitManeSciAps Feb 17, 2026
7227a27
Fix security issues
AnkitManeSciAps Jul 7, 2026
02d2140
Add logs for validation
AnkitManeSciAps Jul 9, 2026
3a32752
shutdown active remote connections
AnkitManeSciAps Jul 20, 2026
bfb0566
Improve socket close code in SSLSafeHttpServerConnection
AnkitManeSciAps Jul 21, 2026
a378fa2
Move handshake on thread
AnkitManeSciAps Jul 24, 2026
4085623
Move socket verification on WorkerTask
AnkitManeSciAps Jul 24, 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
178 changes: 146 additions & 32 deletions miniweb-core/src/main/java/com/devsmart/miniweb/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,23 @@
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Stream;

import javax.net.ServerSocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;

public class Server {

Expand All @@ -43,17 +56,41 @@ public class Server {

private ServerSocket mServerSocket;
private SocketListener mListenThread;
private boolean mRunning = false;
private final Set<RemoteConnection> mActiveConnections = Collections.newSetFromMap(new ConcurrentHashMap<>());
private volatile boolean mRunning = false;
private SSLContext mSslContext;
private boolean mSslEnabled = false;
private String[] mEnabledProtocols = new String[0];

public void configureSslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) {
try {
mSslContext = SSLContext.getInstance("TLS");
mSslContext.init(keyManagers, trustManagers, null);
mSslEnabled = true;
} catch (Exception e) {
mSslContext = null;
mSslEnabled = false;
throw new IllegalStateException("Could not initialize SSLContext", e);
}
}

public void start() throws IOException {
if (mRunning) {
LOGGER.warn("server already running");
return;
}

mServerSocket = new ServerSocket();
mServerSocket = ServerSocketFactory.getDefault().createServerSocket();
mServerSocket.setReuseAddress(true);
mServerSocket.bind(new InetSocketAddress(port));

if (mSslEnabled) {
List<String> supported = Arrays.asList(mSslContext.getSupportedSSLParameters().getProtocols());
mEnabledProtocols = Stream.of("TLSv1.3", "TLSv1.2")
.filter(supported::contains)
.toArray(String[]::new);
}

if (mIsDebugBuild) {
LOGGER.info("Server started listening on {}", mServerSocket.getLocalSocketAddress());
}
Expand All @@ -73,11 +110,23 @@ public void shutdown() {
} catch (IOException e) {
LOGGER.error("Could not close socket:", e);
}
// Force-close accepted connections: keep-alive clients hold sockets open with a
// worker thread blocked in handleRequest(), and would otherwise get one more
// response served with this server's stale routes after shutdown.
for (RemoteConnection remoteConnection : mActiveConnections) {
try {
remoteConnection.connection.shutdown();
} catch (IOException e) {
LOGGER.warn("Error shutting down connection {}: {}", remoteConnection.connection, e.getMessage());
}
}
try {
mListenThread.join();
mListenThread = null;
} catch (InterruptedException e) {
LOGGER.error("", e);
LOGGER.error("Interrupted waiting for listener thread shutdown", e);
Thread.currentThread().interrupt();
} finally {
mListenThread = null;
}
LOGGER.info("Server shutdown");
}
Expand All @@ -86,15 +135,35 @@ public void shutdown() {
private class WorkerTask implements Runnable {

private final HttpService httpservice;
private final RemoteConnection remoteConnection;
private Socket socket;

public WorkerTask(HttpService service, RemoteConnection connection) {
WorkerTask(HttpService service, Socket socket) {
httpservice = service;
remoteConnection = connection;
this.socket = socket;
}

@Override
public void run() {
SocketAddress remoteAddress = socket.getRemoteSocketAddress();
SSLSafeHttpServerConnection connection = new SSLSafeHttpServerConnection();
RemoteConnection remoteConnection;
try {
if (mSslEnabled) {
socket = startSsl(socket);
}
connection.bind(socket, new BasicHttpParams());
remoteConnection = new RemoteConnection(socket.getInetAddress(), connection);
mActiveConnections.add(remoteConnection);
} catch (SSLException e) {
LOGGER.warn("TLS handshake failed from {}: {}", remoteAddress, e.getMessage());
closeSocket(socket);
return;
} catch (Exception e) {
LOGGER.warn("Failed to establish connection from {}: {}", remoteAddress, e.getMessage());
closeSocket(socket);
return;
}

try {
while(mRunning && remoteConnection.connection.isOpen()) {
BasicHttpContext context = new BasicHttpContext();
Expand All @@ -115,16 +184,15 @@ public void run() {
} catch (Exception e){
LOGGER.warn("unknown error - {}", remoteConnection.connection, e);
} finally {
mActiveConnections.remove(remoteConnection);
LOGGER.info("Closing connection {}", remoteConnection.connection);
closeConnection();
}
}

public void closeConnection() {
try {
remoteConnection.connection.close();
} catch (IOException e){
LOGGER.error("", e);
try {
if (remoteConnection.connection.isOpen()) {
remoteConnection.connection.close();
}
} catch (UnsupportedOperationException | IOException e) {
LOGGER.error("Error closing connection", e);
}
}
}
}
Expand Down Expand Up @@ -156,28 +224,74 @@ public void run() {
if (mIsDebugBuild) {
LOGGER.info("accepting connection from: {}", socket.getRemoteSocketAddress());
}

DefaultHttpServerConnection connection = new DefaultHttpServerConnection();
connection.bind(socket, new BasicHttpParams());
RemoteConnection remoteConnection = new RemoteConnection(socket.getInetAddress(), connection);

mWorkerThreads.execute(new WorkerTask(httpService, remoteConnection));
} catch (SocketTimeoutException e) {
continue;
mWorkerThreads.execute(new WorkerTask(httpService, socket));
socket = null; // handed off to the worker; this loop must not close it
} catch (SocketException e) {
LOGGER.info("SocketListener shutting down");
mRunning = false;
} catch (IOException e) {
LOGGER.error("", e);
LOGGER.error("I/O error", e);
mRunning = false;
}
}
if (socket != null) {
try {
socket.close();
LOGGER.info("Connection is closed properly");
} catch (IOException e) {
LOGGER.error("Can't close connection. Reason: ", e);
closeSocket(socket);
}
}

private static void closeSocket(Socket socket) {
if (socket != null) {
try {
socket.close();
LOGGER.info("Connection is closed properly");
} catch (IOException e) {
LOGGER.error("Can't close connection. Reason: ", e);
}
}
}

private SSLSocket startSsl(Socket socket) throws IOException {
String peerIp = socket.getInetAddress().getHostAddress();
SSLSocket sslSocket = (SSLSocket) mSslContext.getSocketFactory()
.createSocket(socket, peerIp, socket.getPort(), true);
try {
sslSocket.setUseClientMode(false);
sslSocket.setNeedClientAuth(true);
if (mEnabledProtocols.length > 0) {
sslSocket.setEnabledProtocols(mEnabledProtocols);
}
return sslSocket;
} catch (RuntimeException e) {
closeSocket(sslSocket);
throw e;
}
}

// HttpCore's DefaultHttpServerConnection.close() calls Socket.shutdownOutput(), which Android's SSL
// socket implementation does not support and other layers may have already closed. This subclass
// makes close() tolerant of both so a normal teardown never surfaces as a spurious error.
public static class SSLSafeHttpServerConnection extends DefaultHttpServerConnection {
@Override
public void close() throws IOException {
// Already closed (e.g. force-closed by shutdown()): nothing to do, and re-closing would only
// risk another shutdownOutput() failure.
Socket socket = getSocket();
if (socket != null && socket.isClosed()) {
return;
}
try {
super.close();
} catch (UnsupportedOperationException e) {
// Catch the specific Android error
// verify the socket exists and force-close it to prevent leaks
if (getSocket() != null) {
getSocket().close();
Comment on lines +272 to +287

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

https://square.github.io/okhttp/changelogs/changelog/#version-521

close() method attempts to call shutdownOutput() which fails and throw the exception and thus to avoid it I had to override the implementation and catch it in and close force-close the socket to prevent leaks

}
} catch (IOException e) {
// The socket was closed concurrently (e.g. during shutdown) so the buffered flush in
// super.close() failed; release the socket without escalating this to an error.
Socket current = getSocket();
if (current != null && !current.isClosed()) {
current.close();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package com.devsmart.miniweb;



import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpRequestHandlerResolver;
import com.devsmart.miniweb.handlers.FileSystemRequestHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;

import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;

public class ServerBuilder {

private int mPort = 8080;
Expand All @@ -19,6 +22,8 @@ public class ServerBuilder {
private UriRequestHandlerResolver mUriMapper = new UriRequestHandlerResolver();
private Gson mGson = new GsonBuilder().create();
private boolean mIsDebugBuild;
private KeyManager[] mKeyManagers;
private TrustManager[] mTrustManagers;

public ServerBuilder setDebugBuild(boolean isDebug) {
mIsDebugBuild = isDebug;
Expand Down Expand Up @@ -87,7 +92,14 @@ public Server create() {
mRequestHandler = mUriMapper;
}
server.requestHandlerResolver = mRequestHandler;

if (mKeyManagers != null && mTrustManagers != null) {
server.configureSslContext(mKeyManagers, mTrustManagers);
}
return server;
}

public void setSslConfigs(KeyManager[] keyManagers, TrustManager[] trustManagers) {
mKeyManagers = keyManagers;
mTrustManagers = trustManagers;
}
}