Các ứng dụng Java doanh nghiệp thường xuyên truy cập các API bên ngoài, phân tích dữ liệu hoặc hoạt động trong các mạng doanh nghiệp cách ly — và trong mỗi trường hợp này, sớm hay muộn, câu hỏi về việc định tuyến lưu lượng qua máy chủ proxy sẽ xuất hiện. Cấu hình proxy trong Java có vẻ là một nhiệm vụ đơn giản, cho đến khi bạn gặp phải việc RestTemplate, OkHttp và WebClient hoạt động khác nhau, và các thuộc tính hệ thống JVM không hoạt động ở mọi nơi. Trong hướng dẫn này — các ví dụ mã cụ thể, giải thích các cạm bẫy và khuyến nghị về việc lựa chọn loại proxy cho các nhiệm vụ khác nhau.
Tại sao các ứng dụng Java cần proxy
Java vẫn là một trong những ngôn ngữ chính trong phát triển doanh nghiệp. Các dịch vụ Spring Boot, kiến trúc vi dịch vụ, các tác vụ batch, các pipeline ETL — tất cả đều thường xuyên thực hiện các yêu cầu HTTP ra bên ngoài. Và trong mỗi kịch bản này, proxy giải quyết một nhiệm vụ cụ thể:
- Mạng doanh nghiệp với cổng proxy bắt buộc. Ở nhiều công ty, toàn bộ lưu lượng ra ngoài đều đi qua một proxy doanh nghiệp (Squid, Zscaler, BlueCoat). Nếu không cấu hình, ứng dụng sẽ không thể truy cập internet.
- Phân tích và thu thập dữ liệu. Các dịch vụ thu thập dữ liệu từ các trang web bên ngoài (giá cả, báo giá, tin tức) sử dụng proxy để vượt qua giới hạn tần suất và chặn theo IP.
- Kiểm tra địa lý. Các đội QA kiểm tra cách API hoặc trang web hoạt động từ các khu vực khác nhau — proxy cho phép mô phỏng yêu cầu từ quốc gia cần thiết.
- Vượt qua các hạn chế từ API mục tiêu. Một số API bên ngoài có giới hạn về số lượng yêu cầu từ một IP. Xoay vòng proxy cho phép phân phối tải.
- Bảo mật và ẩn danh. Ẩn IP thực của ứng dụng khi truy cập các dịch vụ bên ngoài — là một thực hành tiêu chuẩn cho các tích hợp nhạy cảm.
Tùy thuộc vào nhiệm vụ, các phương pháp cấu hình proxy trong Java có sự khác biệt đáng kể. Các thuộc tính hệ thống toàn cầu của JVM — là tùy chọn đơn giản nhất, nhưng nó không hoạt động với một số HTTP client. OkHttp và WebClient yêu cầu cấu hình rõ ràng. Chúng ta sẽ xem xét từng trường hợp theo thứ tự.
Cấu hình toàn cầu qua các thuộc tính hệ thống JVM
Cách nhanh nhất để định hướng toàn bộ lưu lượng HTTP của ứng dụng Java qua proxy — là sử dụng các thuộc tính hệ thống của JVM. Điều này hoạt động cho HttpURLConnection tiêu chuẩn và nhiều thư viện sử dụng nó bên trong.
Bạn có thể thiết lập proxy bằng một số cách:
1. Qua các đối số JVM khi khởi động:
java -Dhttp.proxyHost=proxy.example.com \
-Dhttp.proxyPort=8080 \
-Dhttps.proxyHost=proxy.example.com \
-Dhttps.proxyPort=8080 \
-Dhttp.nonProxyHosts="localhost|127.0.0.1|*.internal.corp" \
-jar myapp.jar
2. Qua mã ứng dụng (ví dụ, trong phương thức main hoặc trong @PostConstruct):
System.setProperty("http.proxyHost", "proxy.example.com");
System.setProperty("http.proxyPort", "8080");
System.setProperty("https.proxyHost", "proxy.example.com");
System.setProperty("https.proxyPort", "8080");
// Các ngoại lệ — các host không nên đi qua proxy
System.setProperty("http.nonProxyHosts", "localhost|127.0.0.1|*.internal.corp");
3. Qua tệp application.properties trong Spring Boot (với việc áp dụng sau qua @PostConstruct):
# application.properties proxy.host=proxy.example.com proxy.port=8080 proxy.username=user proxy.password=secret
⚠️ Quan trọng cần biết
Các thuộc tính hệ thống JVM chỉ hoạt động cho HttpURLConnection. OkHttp, Apache HttpClient và Reactor Netty (WebClient) bỏ qua các thuộc tính này — chúng cần cấu hình rõ ràng, được mô tả bên dưới.
Nếu proxy yêu cầu xác thực, hãy sử dụng Authenticator:
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (getRequestorType() == RequestorType.PROXY) {
return new PasswordAuthentication("username", "password".toCharArray());
}
return null;
}
});
Proxy trong Spring Boot: RestTemplate và Apache HttpClient
RestTemplate — là HTTP client đồng bộ tiêu chuẩn trong Spring. Mặc định, nó sử dụng SimpleClientHttpRequestFactory, dựa vào HttpURLConnection và đọc các thuộc tính hệ thống JVM. Nhưng để có cấu hình linh hoạt hơn (thời gian chờ, pool kết nối, proxy với xác thực), nên sử dụng Apache HttpClient làm backend.
Phụ thuộc trong Maven:
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
</dependency>
Cấu hình RestTemplate với proxy qua Apache HttpClient 5:
@Configuration
public class RestTemplateConfig {
@Value("${proxy.host}")
private String proxyHost;
@Value("${proxy.port}")
private int proxyPort;
@Value("${proxy.username:}")
private String proxyUsername;
@Value("${proxy.password:}")
private String proxyPassword;
@Bean
public RestTemplate restTemplate() {
HttpHost proxy = new HttpHost("http", proxyHost, proxyPort);
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
if (!proxyUsername.isEmpty()) {
credentialsProvider.setCredentials(
new AuthScope(proxyHost, proxyPort),
new UsernamePasswordCredentials(proxyUsername, proxyPassword.toCharArray())
);
}
CloseableHttpClient httpClient = HttpClients.custom()
.setProxy(proxy)
.setDefaultCredentialsProvider(credentialsProvider)
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
return new RestTemplate(factory);
}
}
Cấu hình như vậy cho phép bạn đưa các tham số proxy vào application.properties hoặc biến môi trường, điều này rất quan trọng cho các môi trường prod/staging/dev với các proxy khác nhau.
Nếu bạn cần một phần yêu cầu đi qua proxy, và phần còn lại — trực tiếp, hãy tạo hai bean RestTemplate riêng biệt với các tên khác nhau và sử dụng @Qualifier để tiêm cái cần thiết.
Proxy trong Spring WebClient (ngăn xếp phản ứng)
WebClient — là HTTP client phản ứng của Spring, được xây dựng trên Reactor Netty. Nó không đọc các thuộc tính hệ thống JVM cho proxy — cấu hình được thực hiện qua ProxyProvider khi tạo client.
Phụ thuộc (bao gồm trong spring-boot-starter-webflux):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Cấu hình WebClient với HTTP proxy:
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
HttpClient httpClient = HttpClient.create()
.proxy(proxy -> proxy
.type(ProxyProvider.Proxy.HTTP)
.host("proxy.example.com")
.port(8080)
.username("user")
.password(user -> "password")
);
ClientHttpConnector connector =
new ReactorClientHttpConnector(httpClient);
return WebClient.builder()
.clientConnector(connector)
.build();
}
}
Đối với proxy SOCKS5 trong WebClient:
HttpClient httpClient = HttpClient.create()
.proxy(proxy -> proxy
.type(ProxyProvider.Proxy.SOCKS5)
.host("socks5.proxy.example.com")
.port(1080)
.username("user")
.password(user -> "password")
);
💡 Mẹo cấu hình
Trong các ứng dụng phản ứng, thường cần tạo nhiều phiên bản WebClient với các proxy khác nhau cho các dịch vụ bên ngoài khác nhau. Sử dụng WebClient.Builder như một bean prototype và ghi đè các thiết lập qua .mutate().
Cấu hình proxy trong OkHttp
OkHttp — là thư viện HTTP phổ biến, được nhiều ứng dụng Java và Android sử dụng, cũng như một số tích hợp Spring (ví dụ, Feign với backend OkHttp). Nó có cơ chế cấu hình proxy riêng và cũng không đọc các thuộc tính hệ thống JVM.
Cấu hình cơ bản cho proxy HTTP/HTTPS trong OkHttp:
import okhttp3.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
// Tạo proxy
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress("proxy.example.com", 8080)
);
// Xác thực proxy
Authenticator proxyAuthenticator = (route, response) -> {
String credential = Credentials.basic("username", "password");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
};
// Tạo client
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator(proxyAuthenticator)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
// Ví dụ yêu cầu
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
Tích hợp OkHttp với Spring Boot qua bean:
@Configuration
@ConditionalOnProperty(name = "proxy.enabled", havingValue = "true")
public class OkHttpConfig {
@Value("${proxy.host}")
private String proxyHost;
@Value("${proxy.port}")
private int proxyPort;
@Bean
public OkHttpClient okHttpClient() {
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(proxyHost, proxyPort)
);
return new OkHttpClient.Builder()
.proxy(proxy)
.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
.build();
}
}
Lưu ý về @ConditionalOnProperty — điều này cho phép kích hoạt proxy chỉ trong các môi trường cần thiết thông qua cấu hình, mà không cần thay đổi mã.
Nếu bạn sử dụng OkHttp cùng với proxy dân cư để phân tích dữ liệu, điều quan trọng là cấu hình đúng pool kết nối. Proxy dân cư thường có độ trễ cao hơn so với proxy trung tâm dữ liệu, vì vậy hãy tăng thời gian chờ và giảm kích thước pool để không làm cạn kiệt các kết nối có sẵn.
Proxy SOCKS5 trong Java: đặc điểm và ví dụ
SOCKS5 — là một giao thức cấp thấp hơn so với proxy HTTP. Nó hoạt động ở cấp độ vận chuyển và hỗ trợ bất kỳ giao thức nào (HTTP, HTTPS, FTP, v.v.), cũng như phân giải DNS ở phía proxy. Đối với các ứng dụng Java, SOCKS5 đặc biệt hữu ích khi cần proxy không chỉ HTTP mà còn các kết nối TCP khác.
SOCKS5 qua các thuộc tính hệ thống JVM:
System.setProperty("socksProxyHost", "socks5.proxy.example.com");
System.setProperty("socksProxyPort", "1080");
System.setProperty("java.net.socks.username", "user");
System.setProperty("java.net.socks.password", "password");
SOCKS5 trong OkHttp (qua java.net.Proxy):
Proxy socks5Proxy = new Proxy(
Proxy.Type.SOCKS,
new InetSocketAddress("socks5.proxy.example.com", 1080)
);
OkHttpClient client = new OkHttpClient.Builder()
.proxy(socks5Proxy)
.build();
Đặc điểm quan trọng của SOCKS5 và DNS: Mặc định, Java phân giải DNS cục bộ, và qua proxy chỉ truyền địa chỉ IP. Điều này có thể tiết lộ yêu cầu DNS thực tế. Để phân giải DNS cũng đi qua proxy, hãy sử dụng cách tiếp cận sau với OkHttp:
// DNS tùy chỉnh, buộc phân giải qua SOCKS5
OkHttpClient client = new OkHttpClient.Builder()
.proxy(socks5Proxy)
.dns(hostname -> {
// Trả về host chưa phân giải — OkHttp sẽ chuyển nó cho proxy
return Collections.singletonList(InetAddress.getByName(hostname));
})
.build();
Khi nào chọn SOCKS5 so với proxy HTTP
| Tiêu chí | Proxy HTTP | Proxy SOCKS5 |
|---|---|---|
| Giao thức | HTTP/HTTPS | Bất kỳ TCP/UDP nào |
| DNS qua proxy | Phụ thuộc vào triển khai | Được hỗ trợ |
| Tương thích với Java | Natively | Natively (java.net.Proxy.Type.SOCKS) |
| Sử dụng | Cổng doanh nghiệp, web scraping | Ẩn danh, giao thức không chuẩn |
Xoay vòng proxy trong các ứng dụng Java
Nếu ứng dụng thực hiện một số lượng lớn yêu cầu đến một tài nguyên, việc sử dụng một proxy IP sẽ nhanh chóng dẫn đến việc bị chặn. Xoay vòng proxy — chuyển đổi giữa các địa chỉ IP khác nhau — giải quyết vấn đề này. Trong Java, điều này được thực hiện bằng một số cách.
Phương án 1: Xoay vòng qua endpoint proxy xoay vòng
Cách đơn giản nhất — là sử dụng nhà cung cấp proxy với một endpoint duy nhất, tự động thay đổi IP mỗi khi có yêu cầu hoặc qua một khoảng thời gian nhất định. Bạn chỉ cần chỉ định một host và port, và việc xoay vòng diễn ra ở phía nhà cung cấp. Không cần viết mã cho việc xoay vòng.
Phương án 2: Xoay vòng ở phía ứng dụng qua danh sách proxy
@Component
public class ProxyRotator {
private final List<ProxyConfig> proxies;
private final AtomicInteger counter = new AtomicInteger(0);
public ProxyRotator(@Value("${proxy.list}") List<String> proxyList) {
this.proxies = proxyList.stream()
.map(this::parseProxy)
.collect(Collectors.toList());
}
public OkHttpClient getClientWithNextProxy() {
int index = counter.getAndIncrement() % proxies.size();
ProxyConfig config = proxies.get(index);
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(config.getHost(), config.getPort())
);
return new OkHttpClient.Builder()
.proxy(proxy)
.build();
}
private ProxyConfig parseProxy(String proxyStr) {
// Phân tích chuỗi dạng "host:port" hoặc "user:pass@host:port"
String[] parts = proxyStr.split(":");
return new ProxyConfig(parts[0], Integer.parseInt(parts[1]));
}
}
Phương án 3: Xoay vòng qua ProxySelector
Java cung cấp cơ chế tích hợp ProxySelector, cho phép chọn proxy một cách động dựa trên URL đích. Điều này hoạt động với HttpURLConnection và Apache HttpClient:
ProxySelector.setDefault(new ProxySelector() {
private final List<Proxy> proxyPool = Arrays.asList(
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy1.example.com", 8080)),
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy2.example.com", 8080)),
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy3.example.com", 8080))
);
private final AtomicInteger idx = new AtomicInteger(0);
@Override
public List<Proxy> select(URI uri) {
// Có thể thêm logic chọn theo miền
int i = idx.getAndIncrement() % proxyPool.size();
return Collections.singletonList(proxyPool.get(i));
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
// Logic xử lý proxy không khả dụng
log.warn("Proxy failed: {}", sa);
}
});
Loại proxy nào nên chọn cho các nhiệm vụ Java
Việc chọn loại proxy phụ thuộc vào nhiệm vụ cụ thể. Đối với các ứng dụng Java doanh nghiệp, thường xem xét ba loại chính:
| Loại proxy | Tốc độ | Ẩn danh | Tốt nhất cho |
|---|---|---|---|
| Proxy trung tâm dữ liệu | Cao | Trung bình | Phân tích dữ liệu công khai, kiểm tra API, tải cao |
| Proxy dân cư | Trung bình | Cao | Phân tích các trang web bảo mật, kiểm tra địa lý, vượt qua các chặn |
| Proxy di động | Trung bình | Tối đa | Tích hợp với các API di động, dịch vụ có bảo vệ chống gian lận nghiêm ngặt |
Khuyến nghị thực tiễn về các loại nhiệm vụ:
- Cổng doanh nghiệp (Squid, Zscaler): Loại proxy được xác định bởi bộ phận IT. Sử dụng proxy HTTP với xác thực qua Apache HttpClient hoặc các thuộc tính hệ thống JVM.
- Phân tích dữ liệu công khai (tin tức, báo giá, API công khai): Proxy trung tâm dữ liệu là lựa chọn tối ưu về tốc độ và chi phí.
- Phân tích thương mại điện tử (Wildberries, Ozon, Amazon): Proxy dân cư với xoay vòng. Những nền tảng này thường xuyên chặn IP trung tâm dữ liệu.
- Kiểm tra địa lý (kiểm tra địa phương hóa, giá theo khu vực): Proxy dân cư với lựa chọn quốc gia/thành phố.
- Tích hợp với các dịch vụ và ứng dụng di động: Proxy di động — IP từ các mạng di động thực tế mô phỏng người dùng thực tốt nhất.
Các lỗi thường gặp khi cấu hình proxy trong Java và cách khắc phục
Ngay cả các nhà phát triển có kinh nghiệm cũng gặp phải những vấn đề giống nhau khi cấu hình proxy trong Java. Chúng ta sẽ xem xét những vấn đề phổ biến nhất.
1. Proxy đã được cấu hình, nhưng lưu lượng vẫn đi trực tiếp
Nguyên nhân: Bạn đã thiết lập các thuộc tính hệ thống JVM, nhưng đang sử dụng OkHttp hoặc Reactor Netty (WebClient), mà bỏ qua chúng.
Giải pháp: Cấu hình proxy rõ ràng trong cấu hình của HTTP client cụ thể, như đã trình bày trong các phần trên.
2. javax.net.ssl.SSLHandshakeException khi HTTPS qua proxy
Nguyên nhân: Proxy doanh nghiệp thực hiện kiểm tra SSL (MITM), thay thế chứng chỉ. Java không tin tưởng CA doanh nghiệp.
Giải pháp: Nhập chứng chỉ doanh nghiệp vào Java truststore:
keytool -import -trustcacerts \ -alias corporate-ca \ -file corporate-ca.crt \ -keystore $JAVA_HOME/lib/security/cacerts \ -storepass changeit
3. java.net.ConnectException: Kết nối bị từ chối
Nguyên nhân: Host hoặc port của proxy không chính xác; máy chủ proxy không khả dụng; firewall chặn kết nối.
Chẩn đoán:
# Kiểm tra khả năng truy cập của proxy curl -x http://proxy.example.com:8080 https://httpbin.org/ip # Hoặc qua telnet telnet proxy.example.com 8080
4. Proxy hoạt động cho HTTP, nhưng không cho HTTPS
Nguyên nhân: Chỉ có thuộc tính http.proxyHost/Port được thiết lập, nhưng không có https.proxyHost/Port.
Giải pháp: Thiết lập cả hai bộ thuộc tính. Đối với HTTPS qua proxy HTTP, phương thức CONNECT tunneling được sử dụng — hãy đảm bảo rằng proxy hỗ trợ điều này.
5. 407 Proxy Authentication Required
Nguyên nhân: Proxy yêu cầu xác thực, nhưng nó chưa được cấu hình.
Giải pháp: Thêm Authenticator cho các thuộc tính hệ thống JVM hoặc cấu hình CredentialsProvider cho Apache HttpClient / proxyAuthenticator cho OkHttp (các ví dụ ở trên).
6. Rò rỉ bộ nhớ khi xoay vòng proxy
Nguyên nhân: Mỗi yêu cầu tạo một phiên bản mới của OkHttpClient. Mỗi client giữ pool luồng và kết nối riêng.
Giải pháp: Tái sử dụng các client. Tạo một pool từ nhiều client (mỗi client cho một proxy) và xoay vòng chúng, thay vì tạo mới.
7. Bật logging gỡ lỗi để chẩn đoán
# application.properties — bật log để chẩn đoán proxy logging.level.org.apache.http=DEBUG logging.level.org.apache.http.wire=DEBUG logging.level.reactor.netty.http.client=DEBUG # Đối với JVM — thuộc tính hệ thống -Djava.net.debug=all
Kết luận
Cấu hình proxy trong các ứng dụng Java yêu cầu hiểu biết về HTTP client nào đang được sử dụng bên trong. Các thuộc tính hệ thống JVM — là điểm khởi đầu nhanh chóng cho HttpURLConnection, nhưng OkHttp, WebClient và Apache HttpClient yêu cầu cấu hình rõ ràng. Đối với các ứng dụng doanh nghiệp, nên đưa các tham số proxy vào application.properties và sử dụng các profile Spring cho các môi trường khác nhau. Việc xoay vòng proxy tốt nhất nên được ủy quyền cho nhà cung cấp với endpoint xoay vòng — điều này đơn giản và đáng tin cậy hơn là tự thực hiện.
Nếu ứng dụng Java của bạn thực hiện phân tích dữ liệu từ các nền tảng bảo mật hoặc yêu cầu sự ẩn danh cao khi thực hiện các yêu cầu bên ngoài, chúng tôi khuyên bạn nên xem xét proxy dân cư — chúng có IP thực của người dùng gia đình, điều này giảm thiểu đáng kể khả năng bị chặn từ các hệ thống chống gian lận. Đối với việc phân tích dữ liệu công khai có tải cao, proxy trung tâm dữ liệu là lựa chọn tối ưu — chúng cung cấp tốc độ tối đa với chi phí lưu lượng thấp hơn.
```