diff --git a/src/main/java/cokr/xit/foundation/web/Http.java b/src/main/java/cokr/xit/foundation/web/Http.java
deleted file mode 100644
index 39fd0c1..0000000
--- a/src/main/java/cokr/xit/foundation/web/Http.java
+++ /dev/null
@@ -1,301 +0,0 @@
-package cokr.xit.foundation.web;
-
-import java.io.BufferedReader;
-import java.io.DataOutputStream;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-
-import cokr.xit.foundation.AbstractComponent;
-
-/**Http 통신 유틸리티
- *
다음은 사용례이다.
- *
new Http().get(
- * new Http.Conf()
- * .url("https://...")
- * // .header("Content-Type", "application/x-www-form-urlencoded")
- * .param("param0", "value0")
- * .param("param1", "value1")
- * .onResponse(resp -> System.out.println(resp));
- * );
- * @author mjkhan
- */
-public class Http extends AbstractComponent {
- public static final String FORM = "application/x-www-form-urlencoded";
- public static final String JSON = "application/json";
- /**http 통신 시 설정하는 옵션
- * @author mjkhan
- */
- public static class Conf {
- private String charset;
- private String url;
- private String method;
- private LinkedHashMap
- params,
- headers;
- private int timeout;
- private Consumer handler = System.out::println;
-
- /**파라미터 인코딩에 사용하는 문자셋의 이름을 반환한다.
- * @return 파라미터 인코딩에 사용하는 문자셋의 이름
- */
- private String charset() {
- return ifEmpty(charset, () -> "UTF-8");
- }
-
- /**파라미터 인코딩에 사용하는 문자셋의 이름을 설정한다.
- * @param charset 파라미터 인코딩에 사용하는 문자셋의 이름. 디폴트는 UTF-8.
- * @return 현재 Conf
- */
- public Conf charset(String charset) {
- this.charset = charset;
- return this;
- }
-
- /**http로 접속할 URL을 반환한다.
- * @return URL
- * @throws Exception
- */
- private URL url() throws Exception {
- return new URL(notEmpty(url, "url"));
- }
-
- /**http로 접속할 url을 설정한다.
- * @param url url
- * @return Conf
- */
- public Conf url(String url) {
- this.url = url;
- return this;
- }
-
- /**http 연결 방법을 반환한다.
- * @return http 연결 방법. 디폴트는 "GET"
- */
- private String method() {
- return ifEmpty(method, "GET");
- }
-
- /**설정한 파라미터 이름과 값들을 인코딩하여 반환한다.
- * @return 인코딩한 파라미터 이름과 값
- */
- private String params() {
- return !isEmpty(params) ? params.entrySet().stream().map(this::encode).collect(Collectors.joining("&")) : "";
- }
-
- /**주어진 맵의 엔트리를 설정한 문자셋으로 인코딩하여 반환한다.
- * @param entry 맵 엔트리
- * @return 인코딩한 맵 엔트리
- */
- private String encode(Map.Entry entry) {
- try {
- return URLEncoder.encode(entry.getKey(), charset()) + "=" + URLEncoder.encode(entry.getValue(), charset());
- } catch (Exception e) {
- throw runtimeException(e);
- }
- }
-
- /**파라미터를 설정한다.
- * @param name 파라미터 이름
- * @param value 파라미터 값
- * @return
- */
- public Conf param(String name, String value) {
- if (params == null)
- params = new LinkedHashMap<>();
- params.put(name, value);
- return this;
- }
-
- /**헤더를 설정한다.
- * @param name 헤더 이름
- * @param value 헤더 값
- * @return
- */
- public Conf header(String name, String value) {
- if (headers == null)
- headers = new LinkedHashMap<>();
- headers.put(name, value);
- return this;
- }
-
- /**설정한 헤더들을 http에 설정한다.
- * @param http HttpURLConnection
- */
- private void setHeaders(HttpURLConnection http) {
- if (isEmpty(headers)) return;
-
- headers.forEach(http::setRequestProperty);
- }
-
- /**http 접속 시 타임아웃을 반환한다. 디폴트는 10000
- * @return http 접속 시 타임아웃
- */
- private int timeout() {
- return Math.max(timeout, 10000);
- }
-
- /**http 접속 시 타임아웃을 설정한다.
- * @param timeout http 접속 시 타임아웃
- * @return 현재 Conf
- */
- public Conf timeout(int timeout) {
- this.timeout = timeout;
- return this;
- }
-
- /**http 요청의 응답을 처리할 핸들러를 설정한다.
- * @param handler http 요청의 응답을 처리할 핸들러
- * @return 현재 Conf
- */
- public Conf onResponse(Consumer handler) {
- this.handler = handler;
- return this;
- }
- }
-
- /**http 요청의 응답
- * @author mjkhan
- */
- public static class Response {
- private int status;
- private String response;
-
- /**http 요청의 응답 상태를 반환한다.
- * @return http 요청의 응답 상태
- */
- public int getStatus() {
- return status;
- }
-
- /**http 요청이 정상처리 응답을 받았는지 반환한다.
- * @return 정상처리 여부
- * - 정상처리 됐으면 true
- * - 그렇지 않으면 false
- *
- */
- public boolean isSuccess() {
- return status <= 299;
- }
-
- /**http 요청의 응답을 반환한다.
- * @return http 요청의 응답
- */
- public String getResponse() {
- return response;
- }
-
- @Override
- public String toString() {
- return String.format("status: %d, success: %s, response: %s", status, isSuccess(), response);
- }
- }
-
- /**지정한 url로 GET 요청을 한다.
- * @param conf 요청 시 설정할 옵션
- */
- public void get(Conf conf) {
- if (conf == null)
- conf = new Conf();
- conf.method = "GET";
- request(conf);
- }
-
- /**지정한 url로 POST 요청을 한다.
- * @param conf 요청 시 설정할 옵션
- */
- public void post(Conf conf) {
- if (conf == null)
- conf = new Conf();
- conf.method = "POST";
- request(conf);
- }
-
- /**지정한 url로 요청을 보내고, 이 때 conf를 옵션으로 설정한다.
- * @param url url
- * @param conf 요청 시 설정할 옵션
- */
- private void request(Conf conf) {
- HttpURLConnection http = null;
- try {
- http = (HttpURLConnection)conf.url().openConnection();
- http.setRequestMethod(conf.method());
- http.setUseCaches(false);
- conf.setHeaders(http);
- http.setConnectTimeout(conf.timeout());
- http.setReadTimeout(conf.timeout());
-
- setParams(conf, http);
-
- Response resp = getResponse(http);
- if (conf.handler != null)
- conf.handler.accept(resp);
- } catch (Exception e) {
- if (conf.handler != null) {
- conf.handler.accept(getResponse(e));
- }
- } finally {
- if (http != null)
- http.disconnect();
- }
- }
-
- /**http 요청을 할 때 파라미터를 설정한다.
- * @param conf Http.Conf
- * @param http HttpURLConnection
- * @throws Exception
- */
- private void setParams(Conf conf, HttpURLConnection http) throws Exception {
- String params = conf.params();
- if (isEmpty(params)) return;
-
- http.setDoOutput(true);
- DataOutputStream out = new DataOutputStream(http.getOutputStream());
- out.writeBytes(params);
- out.flush();
- out.close();
- }
-
- /**http 요청의 응답을 추출한다.
- * @param http HttpURLConnection
- * @return http 요청의 응답
- * @throws Exception
- */
- private Response getResponse(HttpURLConnection http) throws Exception {
- Response resp = new Response();
- resp.status = http.getResponseCode();
-
- try (
- InputStream input = resp.isSuccess() ? http.getInputStream() : http.getErrorStream();
- InputStreamReader inputStreamReader = new InputStreamReader(input);
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
- ) {
- String str = "";
- StringBuilder buff = new StringBuilder();
- while ((str = bufferedReader.readLine()) != null) {
- buff.append(str);
- }
- resp.response = buff.toString();
- return resp;
- } catch (Exception e) {
- return getResponse(e);
- }
- }
-
- /**http 요청 중 오류가 발생했을 때 응답을 반환한다.
- * @param t Throwable
- * @return http 요청 중 오류가 발생했을 때 응답
- */
- private Response getResponse(Throwable t) {
- Response resp = new Response();
- resp.status = 500;
- resp.response = rootCause(t).getMessage();
- return resp;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/cokr/xit/foundation/web/WebClient.java b/src/main/java/cokr/xit/foundation/web/WebClient.java
new file mode 100644
index 0000000..04df14d
--- /dev/null
+++ b/src/main/java/cokr/xit/foundation/web/WebClient.java
@@ -0,0 +1,216 @@
+package cokr.xit.foundation.web;
+
+import java.net.Authenticator;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+import cokr.xit.foundation.Assert;
+import cokr.xit.foundation.data.JSON;
+
+/**
+ * @author mjkhan
+ */
+public class WebClient {
+ private static final String
+ FORM_DATA = "application/x-www-form-urlencoded",
+ JSON_DATA = "application/json";
+// MULTIPART = "multipart/form-data";
+
+ private HttpClient.Version version = HttpClient.Version.HTTP_2;
+ private Duration timeout = Duration.ofSeconds(30);
+ private Authenticator authenticator;
+ private ProxySelector proxy;
+
+ private HttpClient.Redirect followRedirects = HttpClient.Redirect.NORMAL;
+
+ public WebClient version(HttpClient.Version version) {
+ this.version = version;
+ return this;
+ }
+
+ public WebClient timeout(int seconds) {
+ this.timeout = Duration.ofSeconds(seconds);
+ return this;
+ }
+
+ public WebClient authenticator(Authenticator authenticator) {
+ this.authenticator = authenticator;
+ return this;
+ }
+
+ public WebClient proxy(ProxySelector proxy) {
+ this.proxy = proxy;
+ return this;
+ }
+
+ public WebClient followRedirects(HttpClient.Redirect redirect) {
+ this.followRedirects = redirect;
+ return this;
+ }
+
+ private HttpClient client() {
+ HttpClient.Builder builder = HttpClient.newBuilder()
+ .version(version)
+ .connectTimeout(timeout)
+ .followRedirects(followRedirects);
+
+ if (authenticator != null)
+ builder.authenticator(authenticator);
+ if (proxy != null)
+ builder.proxy(proxy);
+
+ return builder.build();
+ }
+
+ public void get(Consumer configurer) {
+ Config conf = new Config();
+ configurer.accept(conf);
+ HttpRequest hreq = conf.get();
+
+ try {
+ if (!conf.async) {
+ HttpResponse hresp = client().send(hreq, HttpResponse.BodyHandlers.ofString());
+ conf.respHandler.accept(hresp);
+ } else {
+ CompletableFuture> future = client()
+ .sendAsync(hreq, HttpResponse.BodyHandlers.ofString());
+ HttpResponse hresp = future.get();
+ conf.respHandler.accept(hresp);
+ }
+ } catch (Throwable e) {
+ throw Assert.runtimeException(e);
+ }
+
+ }
+
+ public void post(Consumer configurer) {
+ Config conf = new Config();
+ configurer.accept(conf);
+ HttpRequest hreq = conf.post();
+
+ try {
+ if (!conf.async) {
+ HttpResponse hresp = client().send(hreq, HttpResponse.BodyHandlers.ofString());
+ conf.respHandler.accept(hresp);
+ } else {
+ CompletableFuture> future =
+ client().sendAsync(hreq, HttpResponse.BodyHandlers.ofString());
+ HttpResponse hresp = future.get();
+ conf.respHandler.accept(hresp);
+ }
+ } catch (Throwable e) {
+ throw Assert.runtimeException(e);
+ }
+ }
+
+ public static class Config {
+ private String uri;
+ private boolean
+ async,
+ json;
+ private Charset charset = StandardCharsets.UTF_8;
+ private LinkedHashMap headers;
+ private LinkedHashMap data;
+ private Consumer> respHandler = hresp -> {
+ HttpHeaders headers = hresp.headers();
+ headers.map().forEach((k, v) -> System.out.println(k + " = " + v));
+ System.out.println("status: " + hresp.statusCode());
+ System.out.println(hresp.body());
+ };
+
+ public Config header(String key, String value) {
+ if (headers == null)
+ headers = new LinkedHashMap<>();
+ headers.put(key, value);
+ return this;
+ }
+
+ public Config uri(String uri) {
+ this.uri = uri;
+ return this;
+ }
+
+ public Config charset(Charset charset) {
+ this.charset = charset;
+ return this;
+ }
+
+ public Config async(boolean async) {
+ this.async = async;
+ return this;
+ }
+
+ public Config json(boolean json) {
+ this.json = json;
+ return this;
+ }
+
+ public Config data(String key, Object value) {
+ if (data == null)
+ data = new LinkedHashMap<>();
+ data.put(key, value);
+ return this;
+ }
+
+ public Config onResponse(Consumer> handler) {
+ respHandler = handler;
+ return this;
+ }
+
+ HttpRequest get() {
+ String queryString = getParams();
+ if (!queryString.isEmpty())
+ queryString = !uri.contains("?") ? "?" + queryString : "&" + queryString;
+
+ HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uri + queryString))
+ .GET();
+
+ if (json)
+ builder.header("Accept", JSON_DATA);
+ if (headers != null)
+ headers.forEach((k, v) -> builder.header(k, v));
+
+ return builder.build();
+ }
+
+ private String getParams() {
+ if (data == null) return "";
+
+ List params = data.entrySet().stream()
+ .map(entry -> String.format("%s=%s", encode(entry.getKey()), encode((String)entry.getValue())))
+ .toList();
+ return String.join("&", params);
+ }
+
+ private String inJSON() {
+ return data != null ? new JSON().stringify(data) : "";
+ }
+
+ private String encode(String str) {
+ return URLEncoder.encode(str, charset);
+ }
+
+ HttpRequest post() {
+ HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uri))
+ .header("Content-Type", !json ? FORM_DATA : JSON_DATA)
+ .POST(HttpRequest.BodyPublishers.ofString(!json ? getParams() : inJSON()));
+
+ if (headers != null)
+ headers.forEach((k, v) -> builder.header(k, v));
+
+ return builder.build();
+ }
+ }
+}
\ No newline at end of file