Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package io.fusionauth.http.server;

import javax.net.ServerSocketFactory;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
Expand Down Expand Up @@ -44,6 +46,8 @@ public class HTTPListenerConfiguration {

private final boolean tls;

private final ServerSocketFactory serverSocketFactory;

/**
* Stores the configuration for a single HTTP listener for the server. This constructor sets up a non-TLS based listener that binds to all
* the network interfaces of the server.
Expand All @@ -56,6 +60,7 @@ public HTTPListenerConfiguration(int port) {
this.tls = false;
this.certificateChain = null;
this.privateKey = null;
this.serverSocketFactory = null;
}

/**
Expand All @@ -75,6 +80,7 @@ public HTTPListenerConfiguration(int port, String certificate, String privateKey
this.tls = true;
this.certificateChain = SecurityTools.parseCertificates(certificate);
this.privateKey = SecurityTools.parsePrivateKey(privateKey);
this.serverSocketFactory = null;
}

/**
Expand All @@ -93,6 +99,7 @@ public HTTPListenerConfiguration(int port, Certificate certificate, PrivateKey p
this.tls = true;
this.certificateChain = new Certificate[]{certificate};
this.privateKey = privateKey;
this.serverSocketFactory = null;
}

/**
Expand All @@ -112,6 +119,7 @@ public HTTPListenerConfiguration(int port, Certificate[] certificateChain, Priva
this.tls = true;
this.certificateChain = certificateChain;
this.privateKey = privateKey;
this.serverSocketFactory = null;
}

/**
Expand All @@ -128,6 +136,7 @@ public HTTPListenerConfiguration(InetAddress bindAddress, int port) {
this.tls = false;
this.certificateChain = null;
this.privateKey = null;
this.serverSocketFactory = null;
}

/**
Expand All @@ -150,6 +159,7 @@ public HTTPListenerConfiguration(InetAddress bindAddress, int port, String certi
this.tls = true;
this.certificateChain = SecurityTools.parseCertificates(certificate);
this.privateKey = SecurityTools.parsePrivateKey(privateKey);
this.serverSocketFactory = null;
}

/**
Expand All @@ -170,6 +180,59 @@ public HTTPListenerConfiguration(InetAddress bindAddress, int port, Certificate
this.tls = true;
this.certificateChain = new Certificate[]{certificate};
this.privateKey = privateKey;
this.serverSocketFactory = null;
}

/**
* Stores the configuration for a listener that uses a custom server socket factory.
*
* @param bindAddress The bind address of this listener.
* @param port The port of this listener.
* @param tls Whether the custom factory provides a TLS-enabled server socket.
* @param serverSocketFactory The custom factory used to create an unbound server socket.
*/
public HTTPListenerConfiguration(InetAddress bindAddress, int port, boolean tls, ServerSocketFactory serverSocketFactory) {
Objects.requireNonNull(bindAddress);
Objects.requireNonNull(serverSocketFactory);

this.bindAddress = bindAddress;
this.port = port;
this.tls = tls;
this.certificateChain = null;
this.privateKey = null;
this.serverSocketFactory = serverSocketFactory;
}

/**
* Stores the configuration for a non-TLS listener that uses a custom server socket factory.
*
* @param bindAddress The bind address of this listener.
* @param port The port of this listener.
* @param serverSocketFactory The custom factory used to create an unbound server socket.
*/
public HTTPListenerConfiguration(InetAddress bindAddress, int port, ServerSocketFactory serverSocketFactory) {
this(bindAddress, port, false, serverSocketFactory);
}

/**
* Stores the configuration for a non-TLS listener that uses a custom server socket factory and binds to all network interfaces.
*
* @param port The port of this listener.
* @param serverSocketFactory The custom factory used to create an unbound server socket.
*/
public HTTPListenerConfiguration(int port, ServerSocketFactory serverSocketFactory) {
this(allInterfaces(), port, false, serverSocketFactory);
}

/**
* Stores the configuration for a listener that uses a custom server socket factory and binds to all network interfaces.
*
* @param port The port of this listener.
* @param tls Whether the custom factory provides a TLS-enabled server socket.
* @param serverSocketFactory The custom factory used to create an unbound server socket.
*/
public HTTPListenerConfiguration(int port, boolean tls, ServerSocketFactory serverSocketFactory) {
this(allInterfaces(), port, tls, serverSocketFactory);
}

public InetAddress getBindAddress() {
Expand Down Expand Up @@ -200,7 +263,14 @@ public boolean isTLS() {
return tls;
}

private InetAddress allInterfaces() {
/**
* @return The custom server socket factory, or null when the server should use its default factory.
*/
public ServerSocketFactory getServerSocketFactory() {
return serverSocketFactory;
}

private static InetAddress allInterfaces() {
try {
boolean ipv6Supported = Collections.list(NetworkInterface.getNetworkInterfaces()).stream()
.map(NetworkInterface::getInterfaceAddresses)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ public HTTPServerThread(HTTPServerConfiguration configuration, HTTPListenerConfi
this.minimumWriteThroughput = configuration.getMinimumWriteThroughput();
this.cleaner = new HTTPServerCleanerThread();

if (listener.isTLS()) {
if (listener.getServerSocketFactory() != null) {
this.socket = listener.getServerSocketFactory().createServerSocket();
} else if (listener.isTLS()) {
SSLContext context = SecurityTools.serverContext(listener.getCertificateChain(), listener.getPrivateKey());
this.socket = context.getServerSocketFactory().createServerSocket();
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public void run() {

while (true) {
logger.trace("[{}] Running HTTP worker. Block while we wait to read the preamble", Thread.currentThread().threadId());
request = new HTTPRequest(configuration.getContextPath(), listener.getCertificate() != null ? "https" : "http", listener.getPort(), socket.getInetAddress().getHostAddress());
request = new HTTPRequest(configuration.getContextPath(), listener.isTLS() ? "https" : "http", listener.getPort(), socket.getInetAddress().getHostAddress());

// Create a deep copy of the MultipartConfiguration so that the request may optionally modify the configuration on a per-request basis.
request.getMultiPartStreamProcessor().setMultipartConfiguration(new MultipartConfiguration(configuration.getMultipartConfiguration()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2026, FusionAuth, All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fusionauth.http.server.internal;

import javax.net.ServerSocketFactory;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.concurrent.atomic.AtomicInteger;

import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;

import io.fusionauth.http.server.HTTPListenerConfiguration;
import io.fusionauth.http.server.HTTPServerConfiguration;

/** Tests for custom server socket factories. */
public class HTTPServerThreadTest {
@Test
public void customServerSocketFactoryIsUsedForTLSListener() throws Exception {
assertCustomServerSocketFactoryIsUsed(true);
}

@Test
public void customServerSocketFactoryIsUsedForPlainListener() throws Exception {
assertCustomServerSocketFactoryIsUsed(false);
}

private void assertCustomServerSocketFactoryIsUsed(boolean tls) throws Exception {
AtomicInteger createCalls = new AtomicInteger();
ServerSocketFactory factory = new ServerSocketFactory() {
@Override
public ServerSocket createServerSocket() throws IOException {
createCalls.incrementAndGet();
return new ServerSocket();
}

@Override
public ServerSocket createServerSocket(int port) {
throw new UnsupportedOperationException();
}

@Override
public ServerSocket createServerSocket(int port, int backlog) {
throw new UnsupportedOperationException();
}

@Override
public ServerSocket createServerSocket(int port, int backlog, InetAddress bindAddress) {
throw new UnsupportedOperationException();
}
};

HTTPListenerConfiguration listener = new HTTPListenerConfiguration(InetAddress.getLoopbackAddress(), 0, tls, factory);
HTTPServerThread serverThread = new HTTPServerThread(new HTTPServerConfiguration(), listener);
try {
assertEquals(listener.isTLS(), tls);
assertEquals(listener.getServerSocketFactory(), factory);
assertEquals(createCalls.get(), 1);
} finally {
serverThread.shutdown();
}
}
}