Skip to content

Commit

Permalink
Added: Http proxy configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
djuarezgf committed Nov 8, 2024
1 parent f50c436 commit ed7e770
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.0.1 - 2024-11-06]
## [0.0.1 - 2024-11-08]
### Added
- First version of the project
- Spring Application
Expand Down Expand Up @@ -147,3 +147,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Log bridgeheads sorted
- Replace hyphen in frontend sites
- Human Readable Bridgehead for frontend dto
- Http proxy configuration
4 changes: 4 additions & 0 deletions src/main/java/de/samply/app/ProjectManagerConst.java
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ public class ProjectManagerConst {
public final static String JWKS_URI_PROPERTY = "spring.security.oauth2.client.provider.oidc.jwk-set-uri";
public final static String REGISTERED_BRIDGEHEADS = "bridgeheads";
public final static String FRONTEND_CONFIG = "frontend";
public final static String HTTP_PROXY_PREFIX = "http.proxy";
public final static String HTTPS_PROXY_PREFIX = "https.proxy";

// Exporter
public final static String SECURITY_ENABLED = "SECURITY_ENABLED";
Expand Down Expand Up @@ -461,6 +463,8 @@ public class ProjectManagerConst {
public final static String CUSTOM_PROJECT_CONFIGURATION = "CUSTOM";
public final static String EMAIL_SERVICE = "EMAIL_SERVICE";
public final static String HYPHEN = "minus";
public final static String HTTP_PROTOCOL_SCHEMA = "http";
public final static String HTTPS_PROTOCOL_SCHEMA = "https";


}
15 changes: 15 additions & 0 deletions src/main/java/de/samply/proxy/HttpProxyConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package de.samply.proxy;

import de.samply.app.ProjectManagerConst;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = ProjectManagerConst.HTTP_PROXY_PREFIX)
public class HttpProxyConfiguration extends ProxyConfiguration {

public HttpProxyConfiguration() {
this.setSchema(ProjectManagerConst.HTTP_PROTOCOL_SCHEMA);
}

}
15 changes: 15 additions & 0 deletions src/main/java/de/samply/proxy/HttpsProxyConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package de.samply.proxy;

import de.samply.app.ProjectManagerConst;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = ProjectManagerConst.HTTPS_PROXY_PREFIX)
public class HttpsProxyConfiguration extends ProxyConfiguration {

public HttpsProxyConfiguration() {
this.setSchema(ProjectManagerConst.HTTPS_PROTOCOL_SCHEMA);
}

}
85 changes: 85 additions & 0 deletions src/main/java/de/samply/proxy/ProxyConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package de.samply.proxy;

import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Data
public class ProxyConfiguration {

private String host;
private Integer port;
private String schema;
private String username;
private String password;
private String url;

// Comma-separated list of hosts or IPs to bypass the proxy
private String noProxy;
private String noProxyPattern;

// Convenience method to get noProxy as a List
public List<String> getNoProxyList() {
return noProxy != null ? Arrays.asList(noProxy.split(",")) : List.of();
}

@PostConstruct
public void init() throws MalformedURLException {
if (StringUtils.hasText(this.url)) {
URL url = new URL(this.url);
this.schema = url.getProtocol();
this.host = url.getHost();
this.port = url.getPort();
setProxyUserAndPassword(url.getUserInfo());
if (this.noProxy != null) {
this.noProxyPattern = createNonProxyPattern(this.noProxy);
}
}
if (isConfigured()) {
String schema = (this.schema != null) ? this.schema : "";
log.info(schema + " Proxy configured:");
log.info("\t-Host: " + this.host);
log.info("\t-Port: " + this.port);
if (username != null && password != null) {
log.info("\t-Username: " + username);
}
if (noProxy != null) {
log.info("\t-NoProxy: " + noProxy);
}
}
}

private void setProxyUserAndPassword(String userInfo) {
if (userInfo != null) {
String[] credentials = userInfo.split(":");
if (credentials.length == 2) {
this.username = credentials[0];
this.password = credentials[1];
}
}
}

public boolean isConfigured() {
return StringUtils.hasText(this.host) && this.port != null;
}

// Method to create a regex pattern for non-proxy hosts
private static String createNonProxyPattern(String nonProxyHosts) {
// Split the comma-separated list into individual host patterns and build the regex
return Arrays.stream(nonProxyHosts.split(","))
.map(String::trim) // Trim whitespace
.map(host -> host.replace(".", "\\.") // Escape dots to literal
.replace("*", ".*")) // Convert '*' to '.*' for wildcard matching
.collect(Collectors.joining("|")); // Join with '|' to match any pattern
}


}
39 changes: 36 additions & 3 deletions src/main/java/de/samply/utils/WebClientFactory.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package de.samply.utils;

import de.samply.app.ProjectManagerConst;
import de.samply.proxy.HttpProxyConfiguration;
import de.samply.proxy.HttpsProxyConfiguration;
import de.samply.proxy.ProxyConfiguration;
import io.netty.channel.ChannelOption;
import io.netty.channel.epoll.EpollChannelOption;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import reactor.netty.transport.ProxyProvider;

import java.time.Duration;
import java.util.Optional;

@Component
public class WebClientFactory {
Expand All @@ -22,6 +27,8 @@ public class WebClientFactory {
private final int webClientTcpKeepIntervalInSeconds;
private final int webClientTcpKeepConnetionNumberOfTries;
private final int webClientBufferSizeInBytes;
private Optional<ProxyConfiguration> httpProxyConfiguration = Optional.empty();
private Optional<ProxyConfiguration> httpsProxyConfiguration = Optional.empty();

public WebClientFactory(
@Value(ProjectManagerConst.WEBCLIENT_REQUEST_TIMEOUT_IN_SECONDS_SV) Integer webClientRequestTimeoutInSeconds,
Expand All @@ -31,7 +38,9 @@ public WebClientFactory(
@Value(ProjectManagerConst.WEBCLIENT_TCP_KEEP_CONNECTION_NUMBER_OF_TRIES_SV) Integer webClientTcpKeepConnetionNumberOfTries,
@Value(ProjectManagerConst.WEBCLIENT_MAX_NUMBER_OF_RETRIES_SV) Integer webClientMaxNumberOfRetries,
@Value(ProjectManagerConst.WEBCLIENT_TIME_IN_SECONDS_AFTER_RETRY_WITH_FAILURE_SV) Integer webClientTimeInSecondsAfterRetryWithFailure,
@Value(ProjectManagerConst.WEBCLIENT_BUFFER_SIZE_IN_BYTES_SV) Integer webClientBufferSizeInBytes
@Value(ProjectManagerConst.WEBCLIENT_BUFFER_SIZE_IN_BYTES_SV) Integer webClientBufferSizeInBytes,
HttpProxyConfiguration httpProxyConfiguration,
HttpsProxyConfiguration httpsProxyConfiguration
) {
this.webClientMaxNumberOfRetries = webClientMaxNumberOfRetries;
this.webClientTimeInSecondsAfterRetryWithFailure = webClientTimeInSecondsAfterRetryWithFailure;
Expand All @@ -41,10 +50,21 @@ public WebClientFactory(
this.webClientTcpKeepIntervalInSeconds = webClientTcpKeepIntervalInSeconds;
this.webClientTcpKeepConnetionNumberOfTries = webClientTcpKeepConnetionNumberOfTries;
this.webClientBufferSizeInBytes = webClientBufferSizeInBytes;

setHttpProxies(httpProxyConfiguration, httpsProxyConfiguration);
}

private void setHttpProxies(HttpProxyConfiguration httpProxyConfiguration, HttpsProxyConfiguration httpsProxyConfiguration) {
if (httpProxyConfiguration.isConfigured()) {
this.httpProxyConfiguration = Optional.of(httpProxyConfiguration);
}
if (httpsProxyConfiguration.isConfigured()) {
this.httpsProxyConfiguration = Optional.of(httpsProxyConfiguration);
}
}

public WebClient createWebClient(String baseUrl) {
return WebClient.builder()
WebClient.Builder webClientBuilder = WebClient.builder()
.codecs(codecs -> codecs.defaultCodecs().maxInMemorySize(webClientBufferSizeInBytes))
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
Expand All @@ -55,9 +75,22 @@ public WebClient createWebClient(String baseUrl) {
.option(EpollChannelOption.TCP_KEEPINTVL, webClientTcpKeepIntervalInSeconds)
.option(EpollChannelOption.TCP_KEEPCNT, webClientTcpKeepConnetionNumberOfTries)
))
.baseUrl(baseUrl).build();
.baseUrl(baseUrl);
httpsProxyConfiguration.ifPresent(proxyConfig -> addProxy(webClientBuilder, proxyConfig));
httpProxyConfiguration.ifPresent(proxyConfig -> addProxy(webClientBuilder, proxyConfig));
return webClientBuilder.build();
}

private void addProxy(WebClient.Builder webClientBuilder, ProxyConfiguration proxyConfiguration) {
webClientBuilder.clientConnector(new ReactorClientHttpConnector(HttpClient.create()
.proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP)
.host(proxyConfiguration.getHost())
.port(proxyConfiguration.getPort())
.username(proxyConfiguration.getUsername())
.password(password -> proxyConfiguration.getPassword()) // Use Function.identity() here if needed
.nonProxyHosts(proxyConfiguration.getNoProxyPattern())
)));
}

public int getWebClientMaxNumberOfRetries() {
return webClientMaxNumberOfRetries;
Expand Down

0 comments on commit ed7e770

Please sign in to comment.