Initial commit
This commit is contained in:
commit
58e0d56051
16 changed files with 632 additions and 0 deletions
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
7
.idea/encodings.xml
generated
Normal file
7
.idea/encodings.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
14
.idea/misc.xml
generated
Normal file
14
.idea/misc.xml
generated
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
4
README.md
Normal file
4
README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
### Supply chain
|
||||
- `com.github.docker-java:docker-java:3.4.1` [GitHub](https://github.com/docker-java/docker-java) \
|
||||
Warning: [Numerous CVEs.](https://mvnrepository.com/artifact/com.github.docker-java/docker-java/3.4.1) [Project seems dead](https://github.com/docker-java/docker-java/issues)
|
||||
|
25
pom.xml
Normal file
25
pom.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>eu.m724</groupId>
|
||||
<artifactId>dcdn</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20250107</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
65
src/main/java/eu/m724/Main.java
Normal file
65
src/main/java/eu/m724/Main.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
package eu.m724;
|
||||
|
||||
import eu.m724.docker.DockerEngine;
|
||||
import eu.m724.docker.exception.FailedRequestException;
|
||||
import eu.m724.docker.proxy.TcpSocketProxy;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.StandardProtocolFamily;
|
||||
import java.net.UnixDomainSocketAddress;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
var proxy = new TcpSocketProxy(StandardProtocolFamily.UNIX, UnixDomainSocketAddress.of("/var/run/docker.sock"));
|
||||
proxy.start();
|
||||
System.out.println("Started");
|
||||
|
||||
System.out.printf("Tested in %d ms %n", proxy.test() / 1000000);
|
||||
|
||||
var engine = new DockerEngine(proxy.getURI());
|
||||
engine.ping().join();
|
||||
|
||||
var info = engine.info().join();
|
||||
System.out.println("Docker runtime:");
|
||||
System.out.println("- " + info.getString("ID"));
|
||||
System.out.println("- " + info.getString("OSType") + " " + info.getString("Architecture"));
|
||||
System.out.println("- OS: " + info.getString("OperatingSystem") + " " + info.getString("OSVersion"));
|
||||
System.out.println("- Hostname: " + info.getString("Name"));
|
||||
|
||||
JSONObject containerInfo = null;
|
||||
try {
|
||||
containerInfo = engine.inspectContainer("dcdn_nginx").get();
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof FailedRequestException fre && fre.getResponse().statusCode() == 404) {
|
||||
System.out.println("Container doesn't exist");
|
||||
} else {
|
||||
throw new RuntimeException("Exception getting container info", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (containerInfo != null) {
|
||||
var mounts = containerInfo.getJSONArray("Mounts");
|
||||
System.out.printf("Detected %d mounts:%n", mounts.length());
|
||||
for (int i=0; i<mounts.length(); i++) {
|
||||
var j = mounts.getJSONObject(i);
|
||||
|
||||
System.out.printf("%s %s -> %s %n", j.getString("Name"), j.getString("Source"), j.getString("Destination"));
|
||||
}
|
||||
} else {
|
||||
System.out.println("Creating it");
|
||||
|
||||
var data = new JSONObject()
|
||||
.put("Image", "nginx:1.27");
|
||||
try {
|
||||
containerInfo = engine.createContainer("dcdn_nginx", data).get();
|
||||
// TODO
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Exception creating container", e);
|
||||
}
|
||||
}
|
||||
|
||||
proxy.close();
|
||||
}
|
||||
}
|
37
src/main/java/eu/m724/docker/DockerEngine.java
Normal file
37
src/main/java/eu/m724/docker/DockerEngine.java
Normal file
|
@ -0,0 +1,37 @@
|
|||
package eu.m724.docker;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
public class DockerEngine {
|
||||
private final DockerEngineDAO dao;
|
||||
|
||||
|
||||
public DockerEngine(URI socketUri) {
|
||||
this.dao = new DockerEngineDAO(socketUri);
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> ping() {
|
||||
return dao.requestString("_ping").thenAccept(b -> {
|
||||
if (!b.equals("OK")) {
|
||||
throw new CompletionException(new Exception("Not OK")); // TODO the right Exception?
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<JSONObject> info() {
|
||||
return dao.requestJson("info");
|
||||
}
|
||||
|
||||
public CompletableFuture<JSONObject> inspectContainer(String id) {
|
||||
return dao.requestJson("containers/" + id + "/json");
|
||||
}
|
||||
|
||||
public CompletableFuture<JSONObject> createContainer(String name, JSONObject data) {
|
||||
return dao.requestJson("containers/create", Map.of("name", name), data);
|
||||
}
|
||||
}
|
203
src/main/java/eu/m724/docker/DockerEngineDAO.java
Normal file
203
src/main/java/eu/m724/docker/DockerEngineDAO.java
Normal file
|
@ -0,0 +1,203 @@
|
|||
package eu.m724.docker;
|
||||
|
||||
import eu.m724.docker.exception.FailedRequestException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DockerEngineDAO {
|
||||
private final URI socketUri;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
DockerEngineDAO(URI socketUri) {
|
||||
this.socketUri = socketUri;
|
||||
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).connectTimeout(Duration.ofSeconds(30)).build();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
httpClient.close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a GET request that expects a JSON from the server.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<JSONObject> requestJson(String path) {
|
||||
return requestJson(path, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GET request that expects a JSON from the server.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @param queryParams URL query parameters
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<JSONObject> requestJson(String path, Map<String, Object> queryParams) {
|
||||
return requestJson(path, queryParams, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a POST request with a JSON body that expects a JSON from the server.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @param data the JSON request body
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<JSONObject> requestJson(String path, JSONObject data) {
|
||||
return requestJson(path, null, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a POST request with a JSON body that expects a JSON from the server.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @param queryParams URL query parameters
|
||||
* @param data the JSON request body
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<JSONObject> requestJson(String path, Map<String, Object> queryParams, JSONObject data) {
|
||||
return commonRequest(path, queryParams, data, new JsonBodyHandler());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a GET request that returns the response as String.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<String> requestString(String path) {
|
||||
return requestString(path, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GET request that returns the response as String.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @param queryParams URL query parameters
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<String> requestString(String path, Map<String, Object> queryParams) {
|
||||
return requestString(path, queryParams, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a POST request with a JSON body that returns the response as String.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @param data the JSON request body
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<String> requestString(String path, JSONObject data) {
|
||||
return requestString(path, null, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a POST request with a JSON body that returns the response as String.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @param queryParams URL query parameters
|
||||
* @param data the JSON request body
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<String> requestString(String path, Map<String, Object> queryParams, JSONObject data) {
|
||||
return commonRequest(path, queryParams, data, HttpResponse.BodyHandlers.ofString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GET request that ignores response body.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<Void> requestNoContent(String path) {
|
||||
return requestNoContent(path, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GET request that ignores response body.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @param queryParams URL query parameters
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<Void> requestNoContent(String path, Map<String, Object> queryParams) {
|
||||
return requestNoContent(path, queryParams, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a POST request with a JSON body that ignores response body.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @param data the JSON request body
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<Void> requestNoContent(String path, JSONObject data) {
|
||||
return requestNoContent(path, null, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a POST request with a JSON body that ignores response body.<br>
|
||||
* A {@link FailedRequestException} is thrown by the future if a non-200 status code is returned.
|
||||
*
|
||||
* @param path request path
|
||||
* @param queryParams URL query parameters
|
||||
* @param data the JSON request body
|
||||
* @return the completable future that returns
|
||||
*/
|
||||
public CompletableFuture<Void> requestNoContent(String path, Map<String, Object> queryParams, JSONObject data) {
|
||||
return commonRequest(path, queryParams, data, HttpResponse.BodyHandlers.discarding());
|
||||
}
|
||||
|
||||
|
||||
private HttpRequest createRequest(String path) {
|
||||
return HttpRequest.newBuilder(socketUri.resolve(path)).build();
|
||||
}
|
||||
|
||||
private <T> CompletableFuture<T> commonRequest(String path, Map<String, Object> queryParams, JSONObject data, HttpResponse.BodyHandler<T> bodyHandler) {
|
||||
if (queryParams != null && !queryParams.isEmpty()) {
|
||||
path += '?';
|
||||
path += queryParams.entrySet().stream()
|
||||
.map(e -> e.getKey() + '=' + e.getValue())
|
||||
.collect(Collectors.joining("&"));
|
||||
}
|
||||
|
||||
System.out.println(path);
|
||||
var builder = HttpRequest.newBuilder(socketUri.resolve(path));
|
||||
|
||||
if (data != null) {
|
||||
builder.POST(HttpRequest.BodyPublishers.ofString(data.toString()))
|
||||
.header("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
var request = builder.build();
|
||||
|
||||
return httpClient.sendAsync(request, bodyHandler).thenApply(response -> {
|
||||
if (response.statusCode() / 100 != 2)
|
||||
throw new FailedRequestException(response);
|
||||
return response.body();
|
||||
});
|
||||
}
|
||||
}
|
21
src/main/java/eu/m724/docker/JsonBodyHandler.java
Normal file
21
src/main/java/eu/m724/docker/JsonBodyHandler.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
package eu.m724.docker;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class JsonBodyHandler implements HttpResponse.BodyHandler<JSONObject> {
|
||||
@Override
|
||||
public HttpResponse.BodySubscriber<JSONObject> apply(HttpResponse.ResponseInfo responseInfo) {
|
||||
if (responseInfo.statusCode() == 204) {
|
||||
return HttpResponse.BodySubscribers.replacing(new JSONObject());
|
||||
}
|
||||
|
||||
|
||||
return HttpResponse.BodySubscribers.mapping(
|
||||
HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8),
|
||||
JSONObject::new
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package eu.m724.docker.exception;
|
||||
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
public class FailedRequestException extends CompletionException {
|
||||
private final HttpResponse<?> response;
|
||||
|
||||
public FailedRequestException(HttpResponse<?> response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Code: " + response.statusCode() + " | Body: " + response.body();
|
||||
}
|
||||
|
||||
public HttpResponse<?> getResponse() {
|
||||
return response;
|
||||
}
|
||||
}
|
73
src/main/java/eu/m724/docker/proxy/ConnectionThread.java
Normal file
73
src/main/java/eu/m724/docker/proxy/ConnectionThread.java
Normal file
|
@ -0,0 +1,73 @@
|
|||
package eu.m724.docker.proxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class ConnectionThread extends Thread {
|
||||
private final ExecutorService executorService;
|
||||
private final RemoteSocketFactory remoteSocketFactory;
|
||||
private final Socket clientSocket;
|
||||
|
||||
ConnectionThread(ExecutorService executorService, RemoteSocketFactory remoteSocketFactory, Socket clientSocket) {
|
||||
this.executorService = executorService;
|
||||
this.remoteSocketFactory = remoteSocketFactory;
|
||||
this.clientSocket = clientSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (
|
||||
clientSocket;
|
||||
var remoteSocket = remoteSocketFactory.newChannel()
|
||||
) {
|
||||
System.out.println("connected");
|
||||
inner(remoteSocket);
|
||||
System.out.println("disconnected");
|
||||
} catch (IOException e) {
|
||||
System.err.println("Exception handling client: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void inner(SocketChannel remoteSocket) throws IOException {
|
||||
ByteBuffer clientBuffer = ByteBuffer.allocate(8192);
|
||||
ByteBuffer remoteBuffer = ByteBuffer.allocate(8192);
|
||||
|
||||
try (
|
||||
var clientIn = clientSocket.getInputStream();
|
||||
var clientOut = clientSocket.getOutputStream()
|
||||
) {
|
||||
// Use virtual threads for bidirectional forwarding
|
||||
Future<?> clientToRemote = executorService.submit(() -> {
|
||||
while (clientSocket.isConnected() && remoteSocket.isConnected()) {
|
||||
int bytesRead = clientIn.read(clientBuffer.array());
|
||||
if (bytesRead == -1) break;
|
||||
clientBuffer.limit(bytesRead);
|
||||
remoteSocket.write(clientBuffer);
|
||||
clientBuffer.clear();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
Future<?> remoteToClient = executorService.submit(() -> {
|
||||
while (clientSocket.isConnected() && remoteSocket.isConnected()) {
|
||||
int bytesRead = remoteSocket.read(remoteBuffer);
|
||||
if (bytesRead == -1) break;
|
||||
clientOut.write(remoteBuffer.array(), 0, bytesRead);
|
||||
remoteBuffer.clear();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// Wait for both directions to complete
|
||||
clientToRemote.get();
|
||||
remoteToClient.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new IOException("Transfer interrupted", e);
|
||||
}
|
||||
}
|
||||
}
|
22
src/main/java/eu/m724/docker/proxy/RemoteSocketFactory.java
Normal file
22
src/main/java/eu/m724/docker/proxy/RemoteSocketFactory.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
package eu.m724.docker.proxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.StandardProtocolFamily;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
public class RemoteSocketFactory {
|
||||
private final StandardProtocolFamily protocolFamily;
|
||||
private final SocketAddress address;
|
||||
|
||||
public RemoteSocketFactory(StandardProtocolFamily protocolFamily, SocketAddress address) {
|
||||
this.protocolFamily = protocolFamily;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public SocketChannel newChannel() throws IOException {
|
||||
var channel = SocketChannel.open(protocolFamily);
|
||||
channel.connect(address);
|
||||
return channel;
|
||||
}
|
||||
}
|
33
src/main/java/eu/m724/docker/proxy/ServerRunnable.java
Normal file
33
src/main/java/eu/m724/docker/proxy/ServerRunnable.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
package eu.m724.docker.proxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class ServerRunnable implements Runnable {
|
||||
private final ExecutorService executorService;
|
||||
private final ServerSocket serverSocket;
|
||||
private final RemoteSocketFactory remoteSocketFactory;
|
||||
|
||||
public ServerRunnable(ExecutorService executorService, ServerSocket serverSocket, RemoteSocketFactory remoteSocketFactory) {
|
||||
this.executorService = executorService;
|
||||
this.serverSocket = serverSocket;
|
||||
this.remoteSocketFactory = remoteSocketFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!serverSocket.isClosed() && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
var client = serverSocket.accept();
|
||||
client.setSoTimeout(60_000);
|
||||
|
||||
executorService.execute(new ConnectionThread(executorService, remoteSocketFactory, client));
|
||||
} catch (IOException e) {
|
||||
if (serverSocket.isClosed()) break;
|
||||
|
||||
System.err.println("Exception handling client request: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
src/main/java/eu/m724/docker/proxy/TcpSocketProxy.java
Normal file
60
src/main/java/eu/m724/docker/proxy/TcpSocketProxy.java
Normal file
|
@ -0,0 +1,60 @@
|
|||
package eu.m724.docker.proxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class TcpSocketProxy implements AutoCloseable {
|
||||
private final RemoteSocketFactory remoteSocketFactory;
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
private ExecutorService executorService;
|
||||
|
||||
public TcpSocketProxy(StandardProtocolFamily protocolFamily, SocketAddress address) {
|
||||
this.remoteSocketFactory = new RemoteSocketFactory(protocolFamily, address);
|
||||
}
|
||||
|
||||
public void start() throws IOException {
|
||||
start(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
|
||||
}
|
||||
|
||||
public void start(SocketAddress bindAddress) throws IOException {
|
||||
if (serverSocket != null) return;
|
||||
|
||||
this.serverSocket = new ServerSocket();
|
||||
serverSocket.bind(bindAddress);
|
||||
|
||||
this.executorService = Executors.newVirtualThreadPerTaskExecutor();
|
||||
executorService.execute(new ServerRunnable(executorService, serverSocket, remoteSocketFactory));
|
||||
|
||||
System.out.println("Proxy bound on " + getURI());
|
||||
}
|
||||
|
||||
public long test() throws IOException {
|
||||
var start = System.nanoTime();
|
||||
|
||||
var channel = remoteSocketFactory.newChannel();
|
||||
channel.close();
|
||||
|
||||
var end = System.nanoTime();
|
||||
|
||||
return end - start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (serverSocket != null) serverSocket.close();
|
||||
if (executorService != null) executorService.shutdownNow();
|
||||
}
|
||||
|
||||
public URI getURI() {
|
||||
var address = serverSocket.getInetAddress().getHostAddress();
|
||||
|
||||
if (serverSocket.getInetAddress() instanceof Inet6Address) {
|
||||
address = '[' + address + ']';
|
||||
}
|
||||
|
||||
return URI.create("http://" + address + ":" + serverSocket.getLocalPort());
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue