|
|
@ -21,7 +21,54 @@ import java.util.function.Consumer;
|
|
|
|
import cokr.xit.foundation.Assert;
|
|
|
|
import cokr.xit.foundation.Assert;
|
|
|
|
import cokr.xit.foundation.data.JSON;
|
|
|
|
import cokr.xit.foundation.data.JSON;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**서버와의 http 통신을 지원하는 유틸리티.
|
|
|
|
|
|
|
|
* <p>WebClient는 {@link #get(Consumer) GET}, 또는 {@link #post(Consumer) POST} 방식의 통신을 지원한다. {@link Request 요청의 설정}에 따라
|
|
|
|
|
|
|
|
* <ul><li>동기 / 비동기</li>
|
|
|
|
|
|
|
|
* <li>텍스트 / 파일 다운로드 / JSON</li>
|
|
|
|
|
|
|
|
* </ul>
|
|
|
|
|
|
|
|
* 으로 통신할 수 있다.<br />
|
|
|
|
|
|
|
|
* 다음은 사용례이다.
|
|
|
|
|
|
|
|
* <ul><li>동기식 통신
|
|
|
|
|
|
|
|
* <pre><code> // 텍스트
|
|
|
|
|
|
|
|
* try {
|
|
|
|
|
|
|
|
* HttpResponse{@code <String>} hresp = new WebClient().get(req ->
|
|
|
|
|
|
|
|
* req.uri("http://xit.co.kr")
|
|
|
|
|
|
|
|
* );
|
|
|
|
|
|
|
|
* System.out.println(hresp.body());
|
|
|
|
|
|
|
|
* } catch (Exception e) {
|
|
|
|
|
|
|
|
* e.printStackTrace();
|
|
|
|
|
|
|
|
* }
|
|
|
|
|
|
|
|
* // 파일 다운로드
|
|
|
|
|
|
|
|
* try {
|
|
|
|
|
|
|
|
* HttpResponse{@code <InputStream>} hresp2 = new WebClient().get(req ->
|
|
|
|
|
|
|
|
* req.uri("http://www.xit.co.kr/images/project/homepage/mainVisual.png")
|
|
|
|
|
|
|
|
* .download(true)
|
|
|
|
|
|
|
|
* );
|
|
|
|
|
|
|
|
* InputStream input = hresp2.body(); // -> 파일 저장
|
|
|
|
|
|
|
|
* } catch (Exception e) {
|
|
|
|
|
|
|
|
* e.printStackTrace();
|
|
|
|
|
|
|
|
* } </code></pre>
|
|
|
|
|
|
|
|
* </li>
|
|
|
|
|
|
|
|
* <li>비동기식 통신
|
|
|
|
|
|
|
|
* <pre><code> // 텍스트
|
|
|
|
|
|
|
|
* new WebClient().get(req ->
|
|
|
|
|
|
|
|
* req.uri("http://xit.co.kr")
|
|
|
|
|
|
|
|
* <b>.async(true)</b>
|
|
|
|
|
|
|
|
* <b>.onResponse</b>(hresp -> System.out.println(hresp.body())
|
|
|
|
|
|
|
|
* <b>.onError</b>(e -> e.printStackTrace())
|
|
|
|
|
|
|
|
* );
|
|
|
|
|
|
|
|
* // 파일 다운로드
|
|
|
|
|
|
|
|
* new WebClient().get(req ->
|
|
|
|
|
|
|
|
* req.uri("http://www.xit.co.kr/images/project/homepage/mainVisual.png")
|
|
|
|
|
|
|
|
* <b>.async(true)</b>
|
|
|
|
|
|
|
|
* .download(true)
|
|
|
|
|
|
|
|
* <b>.onDownload</b>(hresp -> {
|
|
|
|
|
|
|
|
* InputStream input = hresp.body(); // -> 파일 저장
|
|
|
|
|
|
|
|
* })
|
|
|
|
|
|
|
|
* <b>.onError</b>(e -> e.printStackTrace())
|
|
|
|
|
|
|
|
* ); </code></pre>
|
|
|
|
|
|
|
|
* </li>
|
|
|
|
|
|
|
|
* </ul>
|
|
|
|
* @author mjkhan
|
|
|
|
* @author mjkhan
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
public class WebClient {
|
|
|
|
public class WebClient {
|
|
|
@ -37,26 +84,46 @@ public class WebClient {
|
|
|
|
|
|
|
|
|
|
|
|
private HttpClient.Redirect followRedirects = HttpClient.Redirect.NORMAL;
|
|
|
|
private HttpClient.Redirect followRedirects = HttpClient.Redirect.NORMAL;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**http 프로토콜의 버젼을 설정한다. 디폴트는 HTTP/2
|
|
|
|
|
|
|
|
* @param version http 프로토콜 버젼
|
|
|
|
|
|
|
|
* @return 현재 WebClient
|
|
|
|
|
|
|
|
*/
|
|
|
|
public WebClient version(HttpClient.Version version) {
|
|
|
|
public WebClient version(HttpClient.Version version) {
|
|
|
|
this.version = version;
|
|
|
|
this.version = version;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**응답 대기 시간(초)을 설정한다. 디폴트는 30초
|
|
|
|
|
|
|
|
* @param seconds 응답 대기 시간(초)
|
|
|
|
|
|
|
|
* @return 현재 WebClient
|
|
|
|
|
|
|
|
*/
|
|
|
|
public WebClient timeout(int seconds) {
|
|
|
|
public WebClient timeout(int seconds) {
|
|
|
|
this.timeout = Duration.ofSeconds(seconds);
|
|
|
|
this.timeout = Duration.ofSeconds(seconds);
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**서버에 대한 인증을 처리할 경우 Authenticator를 설정한다.
|
|
|
|
|
|
|
|
* @param authenticator 인증 처리를 위한 Authenticator
|
|
|
|
|
|
|
|
* @return 현재 WebClient
|
|
|
|
|
|
|
|
*/
|
|
|
|
public WebClient authenticator(Authenticator authenticator) {
|
|
|
|
public WebClient authenticator(Authenticator authenticator) {
|
|
|
|
this.authenticator = authenticator;
|
|
|
|
this.authenticator = authenticator;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**서버와 접속 시 프록시를 사용할 경우 proxy를 설정한다.
|
|
|
|
|
|
|
|
* @param proxy 서버 접속에 사용할 프록시 설정
|
|
|
|
|
|
|
|
* @return 현재 WebClient
|
|
|
|
|
|
|
|
*/
|
|
|
|
public WebClient proxy(ProxySelector proxy) {
|
|
|
|
public WebClient proxy(ProxySelector proxy) {
|
|
|
|
this.proxy = proxy;
|
|
|
|
this.proxy = proxy;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**서버의 응답이 redirect일 경우 대응 방법을 설정한다. 디폴트는 HttpClient.Redirect.NORMAL.
|
|
|
|
|
|
|
|
* @param redirect redirect 응답에 대한 대응 방법
|
|
|
|
|
|
|
|
* @return 현재 WebClient
|
|
|
|
|
|
|
|
*/
|
|
|
|
public WebClient followRedirects(HttpClient.Redirect redirect) {
|
|
|
|
public WebClient followRedirects(HttpClient.Redirect redirect) {
|
|
|
|
this.followRedirects = redirect;
|
|
|
|
this.followRedirects = redirect;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
@ -76,20 +143,25 @@ public class WebClient {
|
|
|
|
return builder.build();
|
|
|
|
return builder.build();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**"GET" 방식의 요청을 전송한다.
|
|
|
|
|
|
|
|
* @param <T> 요청의 body 핸들러 유형
|
|
|
|
|
|
|
|
* @param configurer "GET" 방식 요청의 설정자
|
|
|
|
|
|
|
|
* @return http 응답
|
|
|
|
|
|
|
|
*/
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
public <T> HttpResponse<T> get(Consumer<Config> configurer) {
|
|
|
|
public <T> HttpResponse<T> get(Consumer<Request> configurer) {
|
|
|
|
Config conf = new Config();
|
|
|
|
Request req = new Request();
|
|
|
|
configurer.accept(conf);
|
|
|
|
configurer.accept(req);
|
|
|
|
HttpRequest hreq = conf.get();
|
|
|
|
HttpRequest hreq = req.get();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
if (conf.download)
|
|
|
|
if (req.download)
|
|
|
|
return (HttpResponse<T>)handleRequest(conf.async, hreq, HttpResponse.BodyHandlers.ofInputStream(), conf.downloadHandler);
|
|
|
|
return (HttpResponse<T>)handleRequest(req.async, hreq, HttpResponse.BodyHandlers.ofInputStream(), req.downloadHandler);
|
|
|
|
else
|
|
|
|
else
|
|
|
|
return (HttpResponse<T>)handleRequest(conf.async, hreq, HttpResponse.BodyHandlers.ofString(), conf.textHandler);
|
|
|
|
return (HttpResponse<T>)handleRequest(req.async, hreq, HttpResponse.BodyHandlers.ofString(), req.textHandler);
|
|
|
|
} catch (Throwable e) {
|
|
|
|
} catch (Throwable e) {
|
|
|
|
if (conf.async) {
|
|
|
|
if (req.async) {
|
|
|
|
conf.errorHandler.accept(Assert.rootCause(e));
|
|
|
|
req.errorHandler.accept(Assert.rootCause(e));
|
|
|
|
return null;
|
|
|
|
return null;
|
|
|
|
} else
|
|
|
|
} else
|
|
|
|
throw Assert.runtimeException(e);
|
|
|
|
throw Assert.runtimeException(e);
|
|
|
@ -109,26 +181,35 @@ public class WebClient {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**"POST" 방식의 요청을 전송한다.
|
|
|
|
|
|
|
|
* @param <T> 요청의 body 핸들러 유형
|
|
|
|
|
|
|
|
* @param configurer "POST" 방식 요청의 설정자
|
|
|
|
|
|
|
|
* @return http 응답
|
|
|
|
|
|
|
|
*/
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
public <T> HttpResponse<T> post(Consumer<Config> configurer) {
|
|
|
|
public <T> HttpResponse<T> post(Consumer<Request> configurer) {
|
|
|
|
Config conf = new Config();
|
|
|
|
Request req = new Request();
|
|
|
|
configurer.accept(conf);
|
|
|
|
configurer.accept(req);
|
|
|
|
HttpRequest hreq = conf.post();
|
|
|
|
HttpRequest hreq = req.post();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
if (conf.download)
|
|
|
|
if (req.download)
|
|
|
|
return (HttpResponse<T>)handleRequest(conf.async, hreq, HttpResponse.BodyHandlers.ofInputStream(), conf.downloadHandler);
|
|
|
|
return (HttpResponse<T>)handleRequest(req.async, hreq, HttpResponse.BodyHandlers.ofInputStream(), req.downloadHandler);
|
|
|
|
else
|
|
|
|
else
|
|
|
|
return (HttpResponse<T>)handleRequest(conf.async, hreq, HttpResponse.BodyHandlers.ofString(), conf.textHandler);
|
|
|
|
return (HttpResponse<T>)handleRequest(req.async, hreq, HttpResponse.BodyHandlers.ofString(), req.textHandler);
|
|
|
|
} catch (Throwable e) {
|
|
|
|
} catch (Throwable e) {
|
|
|
|
if (conf.async) {
|
|
|
|
if (req.async) {
|
|
|
|
conf.errorHandler.accept(Assert.rootCause(e));
|
|
|
|
req.errorHandler.accept(Assert.rootCause(e));
|
|
|
|
return null;
|
|
|
|
return null;
|
|
|
|
} else
|
|
|
|
} else
|
|
|
|
throw Assert.runtimeException(e);
|
|
|
|
throw Assert.runtimeException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**응답 유형이 다운로드한 파일일 경우, 해당 파일을 지정한 경로로 저장한다.
|
|
|
|
|
|
|
|
* @param inputStream 다운로드한 파일
|
|
|
|
|
|
|
|
* @param path 저장할 경로
|
|
|
|
|
|
|
|
*/
|
|
|
|
public void write(InputStream inputStream, String path) {
|
|
|
|
public void write(InputStream inputStream, String path) {
|
|
|
|
try (
|
|
|
|
try (
|
|
|
|
InputStream in = inputStream;
|
|
|
|
InputStream in = inputStream;
|
|
|
@ -140,7 +221,23 @@ public class WebClient {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static class Config {
|
|
|
|
/**전송할 http 요청의 설정.
|
|
|
|
|
|
|
|
* <p>Request로 다음 항목들을 설정한다.
|
|
|
|
|
|
|
|
* <ul><li>{@link #uri(String) uri}: 대상 서버의 uri</li>
|
|
|
|
|
|
|
|
* <li>{@link #json(boolean) json}: 서버와 주고받는 데이터의 JSON 여부</li>
|
|
|
|
|
|
|
|
* <li>{@link #async(boolean) async}: 서버와의 통신이 비동기인지 여부</li>
|
|
|
|
|
|
|
|
* <li>{@link #download(boolean) download}: 응답이 파일 다운로드인지 여부</li>
|
|
|
|
|
|
|
|
* <li>{@link #header(String, String) header}: http 헤더</li>
|
|
|
|
|
|
|
|
* <li>{@link #data(String, Object) data}: 서버에 전송하는 데이터</li>
|
|
|
|
|
|
|
|
* </ul>
|
|
|
|
|
|
|
|
* 서버와 비동기로 통신할 경우 다음 항목들을 설정한다.
|
|
|
|
|
|
|
|
* <ul><li>{@link #onResponse(Consumer) onResponse}: 텍스트 응답 핸들러</li>
|
|
|
|
|
|
|
|
* <li>{@link #onDownload(Consumer) onDownload}: 다운로드한 파일 핸들러</li>
|
|
|
|
|
|
|
|
* <li>{@link #onError(Throwable) onError}: 오류 핸들러</li>
|
|
|
|
|
|
|
|
* </ul>
|
|
|
|
|
|
|
|
* @author mjkhan
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public static class Request {
|
|
|
|
private String uri;
|
|
|
|
private String uri;
|
|
|
|
private boolean
|
|
|
|
private boolean
|
|
|
|
async,
|
|
|
|
async,
|
|
|
@ -160,56 +257,107 @@ public class WebClient {
|
|
|
|
t.printStackTrace(System.err);
|
|
|
|
t.printStackTrace(System.err);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
public Config header(String key, String value) {
|
|
|
|
/**요청의 헤더를 설정한다.
|
|
|
|
|
|
|
|
* @param key 헤더이름
|
|
|
|
|
|
|
|
* @param value 헤더값
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request header(String key, String value) {
|
|
|
|
if (headers == null)
|
|
|
|
if (headers == null)
|
|
|
|
headers = new LinkedHashMap<>();
|
|
|
|
headers = new LinkedHashMap<>();
|
|
|
|
headers.put(key, value);
|
|
|
|
headers.put(key, value);
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Config uri(String uri) {
|
|
|
|
/**요청의 uri를 설정한다.
|
|
|
|
|
|
|
|
* @param uri 요청 대상 서버의 uri
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request uri(String uri) {
|
|
|
|
this.uri = uri;
|
|
|
|
this.uri = uri;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Config charset(Charset charset) {
|
|
|
|
/**요청의 문자셋을 설정한다. 디폴트는 UTF-8.
|
|
|
|
|
|
|
|
* @param charset 요청의 문자셋
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request charset(Charset charset) {
|
|
|
|
this.charset = charset;
|
|
|
|
this.charset = charset;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Config async(boolean async) {
|
|
|
|
/**요청이 비동기인지 설정한다. 디폴트는 false(동기).
|
|
|
|
|
|
|
|
* @param async 요청의 비동기 여부
|
|
|
|
|
|
|
|
* <ul><li>요청이 비동기면 true</li>
|
|
|
|
|
|
|
|
* <li>요청이 동기면 false</li>
|
|
|
|
|
|
|
|
* </ul>
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request async(boolean async) {
|
|
|
|
this.async = async;
|
|
|
|
this.async = async;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Config json(boolean json) {
|
|
|
|
/**데이터를 JSON으로 주고받는지 설정한다. 디폴트는 false.
|
|
|
|
|
|
|
|
* @param json 데이터의 JSON 여부
|
|
|
|
|
|
|
|
* <ul><li>데이터를 JSON으로 주고받으면 true</li>
|
|
|
|
|
|
|
|
* <li>그렇지 않으면 false</li>
|
|
|
|
|
|
|
|
* </ul>
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request json(boolean json) {
|
|
|
|
this.json = json;
|
|
|
|
this.json = json;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Config download(boolean download) {
|
|
|
|
/**요청의 응답이 파일인지 설정한다. 디폴트는 false(텍스트).
|
|
|
|
|
|
|
|
* @param download
|
|
|
|
|
|
|
|
* <ul><li>응답으로 file을 다운받으면 true</li>
|
|
|
|
|
|
|
|
* <li>그렇지 않으면 false</li>
|
|
|
|
|
|
|
|
* </ul>
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request download(boolean download) {
|
|
|
|
this.download = download;
|
|
|
|
this.download = download;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Config data(String key, Object value) {
|
|
|
|
/**요청으로 전달할 데이터를 설정한다.
|
|
|
|
|
|
|
|
* @param key 데이터 키(이름)
|
|
|
|
|
|
|
|
* @param value 데이터 값
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request data(String key, Object value) {
|
|
|
|
if (data == null)
|
|
|
|
if (data == null)
|
|
|
|
data = new LinkedHashMap<>();
|
|
|
|
data = new LinkedHashMap<>();
|
|
|
|
data.put(key, value);
|
|
|
|
data.put(key, value);
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Config onResponse(Consumer<HttpResponse<String>> handler) {
|
|
|
|
/**비동기 요청의 텍스트 응답을 처리하는 핸들러를 설정한다.
|
|
|
|
|
|
|
|
* @param handler 비동기 요청의 텍스트 응답을 처리하는 핸들러
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request onResponse(Consumer<HttpResponse<String>> handler) {
|
|
|
|
textHandler = handler;
|
|
|
|
textHandler = handler;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Config onDownload(Consumer<HttpResponse<InputStream>> handler) {
|
|
|
|
/**비동기 요청의 다운로드 파일을 처리하는 핸들러를 설정한다.
|
|
|
|
|
|
|
|
* @param handler 비동기 요청의 다운로드 파일을 처리하는 핸들러
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request onDownload(Consumer<HttpResponse<InputStream>> handler) {
|
|
|
|
downloadHandler = handler;
|
|
|
|
downloadHandler = handler;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Config onError(Consumer<Throwable> handler) {
|
|
|
|
/**비동기 요청의 오류를 처리하는 핸들러를 설정한다.
|
|
|
|
|
|
|
|
* @param handler 오류 핸들러
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request onError(Consumer<Throwable> handler) {
|
|
|
|
errorHandler = handler;
|
|
|
|
errorHandler = handler;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|