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