package com.zy.acs.hex.utils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Minimal OkHttp wrapper: GET / POST only.
*
* - fluent API (get / postJson / postForm)
* - default singleton instance (thread-safe)
* - per-request headers + default headers
* - simple response wrapper with tookMs
* - optional trust-all SSL (ONLY for internal test)
*/
@Slf4j
public final class HttpGo {
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private final OkHttpClient client;
private final Map defaultHeaders;
private HttpGo(OkHttpClient client, Map defaultHeaders) {
this.client = Objects.requireNonNull(client, "client");
this.defaultHeaders = defaultHeaders == null
? Collections.emptyMap()
: Collections.unmodifiableMap(new LinkedHashMap<>(defaultHeaders));
}
/**
* Shared default instance (safe SSL by default).
*/
public static HttpGo defaults() {
return Holder.DEFAULT;
}
public static Builder builder() {
return new Builder();
}
// ===================== GET =====================
public HttpResponse get(String url) throws IOException {
return get(url, null, null);
}
public HttpResponse get(String url, Map queryParams) throws IOException {
return get(url, null, queryParams);
}
public HttpResponse get(String url, Map headers, Map queryParams) throws IOException {
HttpUrl parsed = HttpUrl.parse(url);
if (parsed == null) throw new IllegalArgumentException("Invalid url: " + url);
HttpUrl.Builder ub = parsed.newBuilder();
if (queryParams != null) {
queryParams.forEach((k, v) -> {
if (k != null && v != null) ub.addQueryParameter(k, v);
});
}
Request.Builder rb = new Request.Builder().url(ub.build()).get();
applyHeaders(rb, headers);
return execute(rb.build());
}
// ===================== POST =====================
/**
* POST JSON string payload (null/blank -> "{}").
*/
public HttpResponse postJson(String url, String json) throws IOException {
return postJson(url, null, json);
}
/**
* POST JSON string payload (null/blank -> "{}").
*/
public HttpResponse postJson(String url, Map headers, String json) throws IOException {
String payload = (json == null || json.trim().isEmpty()) ? "{}" : json;
RequestBody body = RequestBody.create(payload, JSON);
Request.Builder rb = new Request.Builder().url(url).post(body);
applyHeaders(rb, headers);
// ensure content-type unless caller overrides
if (rb.build().header("Content-Type") == null) {
rb.header("Content-Type", "application/json; charset=utf-8");
}
return execute(rb.build());
}
/**
* POST x-www-form-urlencoded fields.
*/
public HttpResponse postForm(String url, Map formFields) throws IOException {
return postForm(url, null, formFields);
}
/**
* POST x-www-form-urlencoded fields.
*/
public HttpResponse postForm(String url, Map headers, Map formFields) throws IOException {
FormBody.Builder fb = new FormBody.Builder(DEFAULT_CHARSET);
if (formFields != null) {
formFields.forEach((k, v) -> {
if (k != null && v != null) fb.add(k, v);
});
}
Request.Builder rb = new Request.Builder().url(url).post(fb.build());
applyHeaders(rb, headers);
return execute(rb.build());
}
// ===================== Internals =====================
private HttpResponse execute(Request request) throws IOException {
long start = System.nanoTime();
try (Response resp = client.newCall(request).execute()) {
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
String body = null;
ResponseBody rb = resp.body();
if (rb != null) body = rb.string(); // one-shot
return new HttpResponse(resp.code(), resp.headers(), body, tookMs);
} catch (IOException e) {
log.error("HttpGo request failed: {} {}", request.method(), request.url(), e);
throw e;
}
}
private void applyHeaders(Request.Builder rb, Map headers) {
// defaults first, then per-request overrides
if (defaultHeaders != null) {
defaultHeaders.forEach((k, v) -> {
if (k != null && v != null) rb.header(k, v);
});
}
if (headers != null) {
headers.forEach((k, v) -> {
if (k != null && v != null) rb.header(k, v);
});
}
}
private static final class Holder {
private static final HttpGo DEFAULT = HttpGo.builder().build();
}
// ===================== Response =====================
public static final class HttpResponse {
private final int statusCode;
private final Headers headers;
private final String body;
private final long tookMs;
public HttpResponse(int statusCode, Headers headers, String body, long tookMs) {
this.statusCode = statusCode;
this.headers = headers == null ? new Headers.Builder().build() : headers;
this.body = body;
this.tookMs = tookMs;
}
public int statusCode() {
return statusCode;
}
public Headers headers() {
return headers;
}
public String body() {
return body;
}
public long tookMs() {
return tookMs;
}
public boolean is2xx() {
return statusCode >= 200 && statusCode < 300;
}
public String header(String name) {
return headers.get(name);
}
@Override
public String toString() {
return "HttpResponse{status=" + statusCode + ", tookMs=" + tookMs
+ ", bodyLen=" + (body == null ? 0 : body.length()) + "}";
}
}
// ===================== Builder =====================
public static final class Builder {
private Duration connectTimeout = Duration.ofSeconds(10);
private Duration readTimeout = Duration.ofSeconds(20);
private Duration writeTimeout = Duration.ofSeconds(20);
private boolean trustAllSsl = false;
private final Map defaultHeaders = new LinkedHashMap<>();
public Builder defaultHeader(String name, String value) {
if (name != null && value != null) defaultHeaders.put(name, value);
return this;
}
public Builder connectTimeout(Duration d) {
if (d != null) connectTimeout = d;
return this;
}
public Builder readTimeout(Duration d) {
if (d != null) readTimeout = d;
return this;
}
public Builder writeTimeout(Duration d) {
if (d != null) writeTimeout = d;
return this;
}
/**
* Trust ALL certificates. ONLY for internal testing/self-signed endpoints.
*/
public Builder trustAllSsl(boolean enable) {
this.trustAllSsl = enable;
return this;
}
public HttpGo build() {
OkHttpClient.Builder cb = new OkHttpClient.Builder()
.connectTimeout(connectTimeout.toMillis(), TimeUnit.MILLISECONDS)
.readTimeout(readTimeout.toMillis(), TimeUnit.MILLISECONDS)
.writeTimeout(writeTimeout.toMillis(), TimeUnit.MILLISECONDS);
if (trustAllSsl) {
TrustAll trustAll = new TrustAll();
cb.sslSocketFactory(trustAll.sslSocketFactory, trustAll.trustManager)
.hostnameVerifier((hostname, session) -> true);
}
return new HttpGo(cb.build(), defaultHeaders);
}
}
// ===================== Trust-all SSL helper =====================
private static final class TrustAll {
final X509TrustManager trustManager;
final SSLSocketFactory sslSocketFactory;
TrustAll() {
try {
this.trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
this.sslSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
throw new IllegalStateException("Failed to init trust-all SSL", e);
}
}
}
// ======================== tools ========================
public String buildUrl(String host, Integer port, String path) {
return buildUrl(host, port, path, false);
}
public String buildUrl(String host, Integer port, String path, boolean ssl) {
String p = (path == null) ? "" : (path.startsWith("/") ? path : ("/" + path));
return (ssl ? "https" : "http") + "://" + host + ":" + port + p;
}
// ===================== Demo (main) =====================
public static void main(String[] args) throws Exception {
HttpGo http = HttpGo.builder()
.connectTimeout(Duration.ofSeconds(8))
.readTimeout(Duration.ofSeconds(15))
.defaultHeader("User-Agent", "HttpGo/1.0")
// .trustAllSsl(true) // ONLY if you really need it
.build();
// 1) GET with query params + per-request headers
String getUrl = "https://httpbin.org/get";
Map query = new HashMap<>();
query.put("q", "vincent");
query.put("page", "1");
Map headers = new HashMap<>();
headers.put("X-Trace-Id", "trace-001");
HttpResponse r1 = http.get(getUrl, headers, query);
System.out.println("GET status=" + r1.statusCode() + ", tookMs=" + r1.tookMs());
System.out.println(r1.body());
// 2) POST JSON
String postUrl = "https://httpbin.org/post";
String json = "{\"name\":\"Vincent\",\"role\":\"engineer\"}";
Map postHeaders = new HashMap<>();
postHeaders.put("X-Trace-Id", "trace-002");
HttpResponse r2 = http.postJson(postUrl, postHeaders, json);
System.out.println("POST(JSON) status=" + r2.statusCode() + ", tookMs=" + r2.tookMs());
System.out.println(r2.body());
// 3) POST Form
Map form = new HashMap<>();
form.put("username", "vincent");
form.put("password", "123456");
HttpResponse r3 = http.postForm(postUrl, null, form);
System.out.println("POST(Form) status=" + r3.statusCode() + ", tookMs=" + r3.tookMs());
System.out.println(r3.body());
}
}