Skip to content

Commit 1f09efa

Browse files
Transparent proxy: TP Loader Cloud SDK native integration
1 parent 9d44dd4 commit 1f09efa

File tree

4 files changed

+678
-0
lines changed

4 files changed

+678
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.sap.cloud.sdk.cloudplatform.connectivity;
2+
3+
import java.io.IOException;
4+
import java.net.InetSocketAddress;
5+
import java.net.Socket;
6+
import java.net.UnknownHostException;
7+
8+
import javax.annotation.Nonnull;
9+
10+
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
14+
/**
15+
* Default implementation of {@link NetworkValidator} that performs TCP socket-based connectivity validation.
16+
*
17+
* <p>
18+
* This implementation uses standard Java socket connections to validate that a remote host and port combination is
19+
* reachable and accepting connections. It establishes a temporary TCP connection to the target endpoint and immediately
20+
* closes it upon successful establishment.
21+
*
22+
* <p>
23+
* <strong>Validation Process:</strong>
24+
* <ol>
25+
* <li>Creates a new TCP socket</li>
26+
* <li>Attempts to connect to the specified host and port with a timeout</li>
27+
* <li>Immediately closes the connection if successful</li>
28+
* <li>Throws appropriate exceptions for failures</li>
29+
* </ol>
30+
*
31+
* <p>
32+
* <strong>Timeout Configuration:</strong> The validation uses a fixed timeout of {@value #HOST_REACH_TIMEOUT}
33+
* milliseconds to prevent indefinite blocking on unreachable endpoints.
34+
*
35+
* <p>
36+
* <strong>Error Handling:</strong>
37+
* <ul>
38+
* <li>{@link UnknownHostException} - Host cannot be resolved to an IP address</li>
39+
* <li>{@link IOException} - Network connectivity issues or connection refused</li>
40+
* </ul>
41+
*
42+
* @see NetworkValidator
43+
* @see TransparentProxy
44+
* @since 5.24.0
45+
*/
46+
class DefaultNetworkValidator extends NetworkValidator {
47+
private static final int HOST_REACH_TIMEOUT = 5000;
48+
private static final Logger log = LoggerFactory.getLogger(DefaultNetworkValidator.class);
49+
50+
/**
51+
* {@inheritDoc}
52+
*
53+
* <p>
54+
* This implementation creates a TCP socket connection to the specified host and port to verify connectivity. The
55+
* connection is immediately closed after successful establishment.
56+
*
57+
* @param host {@inheritDoc}
58+
* @param port {@inheritDoc}
59+
* @throws DestinationAccessException {@inheritDoc}
60+
* <p>
61+
* Specific error conditions:
62+
* <ul>
63+
* <li>Host resolution failure - when DNS lookup fails</li>
64+
* <li>Connection failure - when host is unreachable or port is closed</li>
65+
* <li>Network timeouts - when connection attempt exceeds timeout</li>
66+
* </ul>
67+
*/
68+
@Override
69+
void verifyHostConnectivity(@Nonnull final String host, final int port)
70+
throws DestinationAccessException {
71+
log.info("Verifying that transparent proxy host is reachable on {}:{}", host, port);
72+
try (Socket socket = new Socket()) {
73+
socket.connect(new InetSocketAddress(host, port), HOST_REACH_TIMEOUT);
74+
log.info("Successfully verified successfully that transparent proxy host is reachable on {}:{}", host, port);
75+
} catch (final UnknownHostException e) {
76+
throw new DestinationAccessException(String.format("Host [%s] could not be resolved", host), e);
77+
} catch (final IOException e) {
78+
throw new DestinationAccessException(
79+
String.format("Host [%s] on port [%d] is not reachable", host, port),
80+
e);
81+
}
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.sap.cloud.sdk.cloudplatform.connectivity;
2+
3+
import javax.annotation.Nonnull;
4+
5+
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
6+
7+
/**
8+
* Abstract class for validating network connectivity to remote hosts and ports.
9+
*
10+
* <p>
11+
* This abstract class defines the contract for network validation implementations used by transparent proxy components
12+
* to ensure that registered hosts are reachable before completing the registration process.
13+
*
14+
* <p>
15+
* Implementations should perform actual network connectivity tests to verify that the specified host and port
16+
* combination is accessible from the current environment. This validation helps prevent registration of unreachable
17+
* endpoints that would cause runtime failures.
18+
*
19+
* <p>
20+
* <strong>Usage Context:</strong> This abstract class is primarily used by {@link TransparentProxy} during host
21+
* registration to validate network connectivity before accepting the registration.
22+
*
23+
* @see DefaultNetworkValidator
24+
* @see TransparentProxy
25+
* @since 5.24.0
26+
*/
27+
abstract class NetworkValidator
28+
{
29+
/**
30+
* Validates connectivity to the specified host and port combination.
31+
*
32+
* <p>
33+
* This method should attempt to establish a network connection to the given host and port to verify that the
34+
* endpoint is reachable and accepting connections. The validation should complete within a reasonable timeout
35+
* period.
36+
*
37+
* <p>
38+
* If the host cannot be resolved or the connection cannot be established, this method should throw a
39+
* {@link DestinationAccessException} with descriptive error information.
40+
*
41+
* @param host
42+
* the hostname or IP address to validate connectivity to. Must not be null or empty
43+
* @param port
44+
* the port number to test connectivity on. Should be a valid port number (1-65535)
45+
* @throws DestinationAccessException
46+
* if the host cannot be resolved, the connection cannot be established, or any other network-related
47+
* validation failure occurs
48+
* @throws IllegalArgumentException
49+
* if host is null or port is invalid
50+
*/
51+
abstract void verifyHostConnectivity( @Nonnull String host, int port )
52+
throws DestinationAccessException;
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package com.sap.cloud.sdk.cloudplatform.connectivity;
2+
3+
import java.net.URI;
4+
import java.net.URISyntaxException;
5+
6+
import javax.annotation.Nonnull;
7+
8+
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
9+
10+
import io.vavr.control.Try;
11+
import lombok.extern.slf4j.Slf4j;
12+
13+
/**
14+
* A transparent proxy loader that enables routing traffic through a single registered gateway host.
15+
*
16+
* <p>
17+
* This class provides a mechanism to register a proxy gateway that will handle all destination requests transparently.
18+
* Once registered, all destination lookups will be routed through the configured gateway host and port.
19+
*
20+
* <p>
21+
* <strong>Key Features:</strong>
22+
* <ul>
23+
* <li>Single gateway registration - only one proxy can be registered at a time</li>
24+
* <li>Host validation - ensures hosts don't contain paths and are reachable</li>
25+
* <li>Automatic scheme normalization - defaults to HTTP if no scheme provided</li>
26+
* <li>Network connectivity validation before registration</li>
27+
* </ul>
28+
*
29+
* <p>
30+
* <strong>Usage Example:</strong>
31+
*
32+
* <pre>{@code
33+
* // Register with default port 80
34+
* TransparentProxy.register("gateway.svc.cluster.local");
35+
*
36+
* // Register with custom port
37+
* TransparentProxy.register("http://gateway.svc.cluster.local", 8080);
38+
* }</pre>
39+
*
40+
* <p>
41+
* <strong>Thread Safety:</strong> This class uses static state and is not thread-safe. Registration should be performed
42+
* during application initialization.
43+
*
44+
* @since 5.24.0
45+
*/
46+
@Slf4j
47+
public class TransparentProxy implements DestinationLoader {
48+
private static final Integer DEFAULT_PORT = 80;
49+
private static final String SCHEME_SEPARATOR = "://";
50+
private static final String HTTP_SCHEME = org.apache.http.HttpHost.DEFAULT_SCHEME_NAME + SCHEME_SEPARATOR;
51+
private static final String PATH_SEPARATOR = "/";
52+
private static final String PORT_SEPARATOR = ":";
53+
private static final String HOST_CONTAINS_PATH_ERROR_MESSAGE_TEMPLATE =
54+
"Host '%s' contains a path '%s'. Paths are not allowed in host registration.";
55+
static String uri;
56+
static NetworkValidator networkValidator = new DefaultNetworkValidator();
57+
58+
/**
59+
* Registers a transparent proxy gateway using the default port 80.
60+
*
61+
* <p>
62+
* This method registers the specified host as a transparent proxy gateway that will handle all subsequent
63+
* destination requests. The host will be validated for reachability and must not contain any path components.
64+
*
65+
* <p>
66+
* If no scheme is provided, HTTP will be used by default. The final URI will be constructed as:
67+
* {@code <normalized-host>:80}
68+
*
69+
* @param host the gateway host to register (e.g., "gateway.svc.cluster.local") Must not contain paths or be null
70+
* @throws DestinationAccessException if the proxy is already registered, the host contains a path, or the host is not reachable on port 80
71+
* @throws IllegalArgumentException if host is null
72+
* @see #register(String, Integer)
73+
*/
74+
public static void register(@Nonnull final String host) {
75+
registerLoader(host, DEFAULT_PORT);
76+
}
77+
78+
/**
79+
* Registers a transparent proxy gateway with a specified port.
80+
*
81+
* <p>
82+
* This method registers the specified host and port as a transparent proxy gateway that will handle all subsequent
83+
* destination requests. The host will be validated for reachability on the specified port and must not contain any
84+
* path components.
85+
*
86+
* <p>
87+
* If no scheme is provided, HTTP will be used by default. The final URI will be constructed as:
88+
* {@code <normalized-host>:<port>}
89+
*
90+
* @param host the gateway host to register (e.g., "gateway" or "<a href="http://gateway">...</a>") Must not contain
91+
* paths or be null
92+
* @param port the port number to use for the gateway connection. Must not be null and should be a valid port number
93+
* (1-65535)
94+
* @throws DestinationAccessException if the proxy is already registered, the host contains a path, or the host is not reachable on the
95+
* specified port
96+
* @throws IllegalArgumentException if host or port is null
97+
* @see #register(String)
98+
*/
99+
public static void register(@Nonnull final String host, @Nonnull final Integer port) {
100+
registerLoader(host, port);
101+
}
102+
103+
private static void registerLoader(@Nonnull final String host, final Integer port) {
104+
if (uri != null) {
105+
throw new DestinationAccessException(
106+
"TransparentProxy is already registered. Only one registration is allowed.");
107+
}
108+
109+
try {
110+
final String normalizedHost = normalizeHostWithScheme(host);
111+
final String hostForVerification = getHostForVerification(host, normalizedHost);
112+
113+
verifyHostConnectivity(hostForVerification, port);
114+
115+
uri = String.format("%s%s%d", normalizedHost, PORT_SEPARATOR, port);
116+
DestinationAccessor.prependDestinationLoader(new TransparentProxy());
117+
118+
} catch (final URISyntaxException e) {
119+
throw new DestinationAccessException("Invalid host format: " + host, e);
120+
}
121+
}
122+
123+
@Nonnull
124+
private static String getHostForVerification(@Nonnull String host, String normalizedHost) throws URISyntaxException {
125+
final URI parsedUri = new URI(normalizedHost);
126+
127+
final String path = parsedUri.getPath();
128+
if (path != null && !path.isEmpty()) {
129+
throw new DestinationAccessException(
130+
String.format(HOST_CONTAINS_PATH_ERROR_MESSAGE_TEMPLATE, host, path));
131+
}
132+
133+
final String hostForVerification = parsedUri.getHost();
134+
if (hostForVerification == null) {
135+
throw new DestinationAccessException("Invalid host format: " + host);
136+
}
137+
return hostForVerification;
138+
}
139+
140+
@Nonnull
141+
private static String normalizeHostWithScheme(@Nonnull final String host) {
142+
if (host.contains(SCHEME_SEPARATOR)) {
143+
return host;
144+
}
145+
return HTTP_SCHEME + host;
146+
}
147+
148+
private static void verifyHostConnectivity(@Nonnull final String host, final int port) {
149+
networkValidator.verifyHostConnectivity(host, port);
150+
}
151+
152+
@Nonnull
153+
@Override
154+
public Try<Destination> tryGetDestination(@Nonnull final String destinationName) {
155+
return Try.success(TransparentProxyDestination.gateway(destinationName, uri).build());
156+
}
157+
158+
@Nonnull
159+
@Override
160+
public Try<Destination>
161+
tryGetDestination(@Nonnull final String destinationName, @Nonnull DestinationOptions options) {
162+
return Try.success(TransparentProxyDestination.gateway(destinationName, uri).build());
163+
}
164+
165+
}

0 commit comments

Comments
 (0)