Http 제거, WebClient로 교체
parent
20b34f5e61
commit
36031a2da7
@ -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 통신 유틸리티
|
|
||||||
* <p>다음은 사용례이다.
|
|
||||||
* <pre><code> 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));
|
|
||||||
* );</code></pre>
|
|
||||||
* @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<String, String>
|
|
||||||
params,
|
|
||||||
headers;
|
|
||||||
private int timeout;
|
|
||||||
private Consumer<Response> 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<String, String> 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<Response> 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 정상처리 여부
|
|
||||||
* <ul><li>정상처리 됐으면 true</li>
|
|
||||||
* <li>그렇지 않으면 false</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<Config> configurer) {
|
||||||
|
Config conf = new Config();
|
||||||
|
configurer.accept(conf);
|
||||||
|
HttpRequest hreq = conf.get();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!conf.async) {
|
||||||
|
HttpResponse<String> hresp = client().send(hreq, HttpResponse.BodyHandlers.ofString());
|
||||||
|
conf.respHandler.accept(hresp);
|
||||||
|
} else {
|
||||||
|
CompletableFuture<HttpResponse<String>> future = client()
|
||||||
|
.sendAsync(hreq, HttpResponse.BodyHandlers.ofString());
|
||||||
|
HttpResponse<String> hresp = future.get();
|
||||||
|
conf.respHandler.accept(hresp);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw Assert.runtimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void post(Consumer<Config> configurer) {
|
||||||
|
Config conf = new Config();
|
||||||
|
configurer.accept(conf);
|
||||||
|
HttpRequest hreq = conf.post();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!conf.async) {
|
||||||
|
HttpResponse<String> hresp = client().send(hreq, HttpResponse.BodyHandlers.ofString());
|
||||||
|
conf.respHandler.accept(hresp);
|
||||||
|
} else {
|
||||||
|
CompletableFuture<HttpResponse<String>> future =
|
||||||
|
client().sendAsync(hreq, HttpResponse.BodyHandlers.ofString());
|
||||||
|
HttpResponse<String> 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<String, String> headers;
|
||||||
|
private LinkedHashMap<String, Object> data;
|
||||||
|
private Consumer<HttpResponse<String>> 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<HttpResponse<String>> 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<String> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue