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