|
|
@ -2,6 +2,7 @@ package cokr.xit.foundation.web;
|
|
|
|
|
|
|
|
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
|
|
|
import java.math.BigInteger;
|
|
|
|
import java.net.Authenticator;
|
|
|
|
import java.net.Authenticator;
|
|
|
|
import java.net.ProxySelector;
|
|
|
|
import java.net.ProxySelector;
|
|
|
|
import java.net.URI;
|
|
|
|
import java.net.URI;
|
|
|
@ -9,12 +10,19 @@ import java.net.URLEncoder;
|
|
|
|
import java.net.http.HttpClient;
|
|
|
|
import java.net.http.HttpClient;
|
|
|
|
import java.net.http.HttpHeaders;
|
|
|
|
import java.net.http.HttpHeaders;
|
|
|
|
import java.net.http.HttpRequest;
|
|
|
|
import java.net.http.HttpRequest;
|
|
|
|
|
|
|
|
import java.net.http.HttpRequest.BodyPublisher;
|
|
|
|
|
|
|
|
import java.net.http.HttpRequest.BodyPublishers;
|
|
|
|
import java.net.http.HttpResponse;
|
|
|
|
import java.net.http.HttpResponse;
|
|
|
|
import java.nio.charset.Charset;
|
|
|
|
import java.nio.charset.Charset;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
|
|
|
import java.nio.file.Files;
|
|
|
|
|
|
|
|
import java.nio.file.Path;
|
|
|
|
import java.time.Duration;
|
|
|
|
import java.time.Duration;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
import java.util.Random;
|
|
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
|
import java.util.function.Consumer;
|
|
|
|
import java.util.function.Consumer;
|
|
|
|
|
|
|
|
|
|
|
@ -74,7 +82,8 @@ import cokr.xit.foundation.data.JSON;
|
|
|
|
public class WebClient {
|
|
|
|
public class WebClient {
|
|
|
|
private static final String
|
|
|
|
private static final String
|
|
|
|
FORM_DATA = "application/x-www-form-urlencoded",
|
|
|
|
FORM_DATA = "application/x-www-form-urlencoded",
|
|
|
|
JSON_DATA = "application/json";
|
|
|
|
JSON_DATA = "application/json",
|
|
|
|
|
|
|
|
XML_DATA = "text/xml";
|
|
|
|
// MULTIPART = "multipart/form-data";
|
|
|
|
// MULTIPART = "multipart/form-data";
|
|
|
|
|
|
|
|
|
|
|
|
private HttpClient.Version version = HttpClient.Version.HTTP_2;
|
|
|
|
private HttpClient.Version version = HttpClient.Version.HTTP_2;
|
|
|
@ -238,14 +247,38 @@ public class WebClient {
|
|
|
|
* @author mjkhan
|
|
|
|
* @author mjkhan
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
public static class Request {
|
|
|
|
public static class Request {
|
|
|
|
|
|
|
|
public static enum ContentType {
|
|
|
|
|
|
|
|
FORM("application/x-www-form-urlencoded"),
|
|
|
|
|
|
|
|
JSON("application/json"),
|
|
|
|
|
|
|
|
XML("text/xml"),
|
|
|
|
|
|
|
|
PLAIN("text/plain"),
|
|
|
|
|
|
|
|
MULTIPART("multipart/form-data");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final String type;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private ContentType(String type) {
|
|
|
|
|
|
|
|
this.type = type;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static ContentType typeOf(String type) {
|
|
|
|
|
|
|
|
if (Assert.isEmpty(type)) return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (ContentType content: values())
|
|
|
|
|
|
|
|
if (type.equals(content.type))
|
|
|
|
|
|
|
|
return content;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
throw new IllegalArgumentException(type);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String uri;
|
|
|
|
private String uri;
|
|
|
|
private boolean
|
|
|
|
private boolean
|
|
|
|
async,
|
|
|
|
async,
|
|
|
|
json,
|
|
|
|
// json,
|
|
|
|
download;
|
|
|
|
download;
|
|
|
|
private Charset charset = StandardCharsets.UTF_8;
|
|
|
|
private Charset charset = StandardCharsets.UTF_8;
|
|
|
|
private LinkedHashMap<String, String> headers;
|
|
|
|
private LinkedHashMap<String, String> headers;
|
|
|
|
private LinkedHashMap<String, Object> data;
|
|
|
|
private LinkedHashMap<String, Object> keyValues;
|
|
|
|
private Consumer<HttpResponse<String>> textHandler = hresp -> {
|
|
|
|
private Consumer<HttpResponse<String>> textHandler = hresp -> {
|
|
|
|
HttpHeaders headers = hresp.headers();
|
|
|
|
HttpHeaders headers = hresp.headers();
|
|
|
|
headers.map().forEach((k, v) -> System.out.println(k + " = " + v));
|
|
|
|
headers.map().forEach((k, v) -> System.out.println(k + " = " + v));
|
|
|
@ -269,6 +302,21 @@ public class WebClient {
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Request contentType(ContentType type) {
|
|
|
|
|
|
|
|
return header("Content-Type", type.type);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Request.ContentType contentType() {
|
|
|
|
|
|
|
|
if (headers == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
List<String> found = headers.entrySet().stream()
|
|
|
|
|
|
|
|
.filter(entry -> "Content-Type".equals(entry.getKey()))
|
|
|
|
|
|
|
|
.map(entry -> entry.getValue())
|
|
|
|
|
|
|
|
.toList();
|
|
|
|
|
|
|
|
String type = !found.isEmpty() ? found.get(0) : null;
|
|
|
|
|
|
|
|
return ContentType.typeOf(type);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**요청의 uri를 설정한다.
|
|
|
|
/**요청의 uri를 설정한다.
|
|
|
|
* @param uri 요청 대상 서버의 uri
|
|
|
|
* @param uri 요청 대상 서버의 uri
|
|
|
|
* @return 현재 Request
|
|
|
|
* @return 현재 Request
|
|
|
@ -305,11 +353,11 @@ public class WebClient {
|
|
|
|
* <li>그렇지 않으면 false</li>
|
|
|
|
* <li>그렇지 않으면 false</li>
|
|
|
|
* </ul>
|
|
|
|
* </ul>
|
|
|
|
* @return 현재 Request
|
|
|
|
* @return 현재 Request
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request json(boolean json) {
|
|
|
|
public Request json(boolean json) {
|
|
|
|
this.json = json;
|
|
|
|
this.json = json;
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**요청의 응답이 파일인지 설정한다. 디폴트는 false(텍스트).
|
|
|
|
/**요청의 응답이 파일인지 설정한다. 디폴트는 false(텍스트).
|
|
|
|
* @param download
|
|
|
|
* @param download
|
|
|
@ -323,18 +371,38 @@ public class WebClient {
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Object bodyData() {
|
|
|
|
|
|
|
|
if (Assert.isEmpty(keyValues)) return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object value = keyValues.remove("body");
|
|
|
|
|
|
|
|
if (value != null)
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**요청으로 전달할 데이터를 설정한다.
|
|
|
|
/**요청으로 전달할 데이터를 설정한다.
|
|
|
|
* @param key 데이터 키(이름)
|
|
|
|
* @param key 데이터 키(이름)
|
|
|
|
* @param value 데이터 값
|
|
|
|
* @param value 데이터 값
|
|
|
|
* @return 현재 Request
|
|
|
|
* @return 현재 Request
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
public Request data(String key, Object value) {
|
|
|
|
public Request data(String key, Object value) {
|
|
|
|
if (data == null)
|
|
|
|
if (keyValues == null)
|
|
|
|
data = new LinkedHashMap<>();
|
|
|
|
keyValues = new LinkedHashMap<>();
|
|
|
|
data.put(key, value);
|
|
|
|
keyValues.put(key, value);
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**요청으로 전달할 데이터를 설정한다.
|
|
|
|
|
|
|
|
* @param data 데이터 값
|
|
|
|
|
|
|
|
* @return 현재 Request
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Request bodyData(Object value) {
|
|
|
|
|
|
|
|
if (keyValues != null)
|
|
|
|
|
|
|
|
keyValues.clear();
|
|
|
|
|
|
|
|
return data("body", value);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**비동기 요청의 텍스트 응답을 처리하는 핸들러를 설정한다.
|
|
|
|
/**비동기 요청의 텍스트 응답을 처리하는 핸들러를 설정한다.
|
|
|
|
* @param handler 비동기 요청의 텍스트 응답을 처리하는 핸들러
|
|
|
|
* @param handler 비동기 요청의 텍스트 응답을 처리하는 핸들러
|
|
|
|
* @return 현재 Request
|
|
|
|
* @return 현재 Request
|
|
|
@ -369,9 +437,10 @@ public class WebClient {
|
|
|
|
|
|
|
|
|
|
|
|
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uri + queryString))
|
|
|
|
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uri + queryString))
|
|
|
|
.GET();
|
|
|
|
.GET();
|
|
|
|
|
|
|
|
/*
|
|
|
|
if (json)
|
|
|
|
if (json)
|
|
|
|
builder.header("Accept", JSON_DATA);
|
|
|
|
builder.header("Accept", JSON_DATA);
|
|
|
|
|
|
|
|
*/
|
|
|
|
if (headers != null)
|
|
|
|
if (headers != null)
|
|
|
|
headers.forEach((k, v) -> builder.header(k, v));
|
|
|
|
headers.forEach((k, v) -> builder.header(k, v));
|
|
|
|
|
|
|
|
|
|
|
@ -379,16 +448,23 @@ public class WebClient {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String getParams() {
|
|
|
|
private String getParams() {
|
|
|
|
if (data == null) return "";
|
|
|
|
if (keyValues == null) return "";
|
|
|
|
|
|
|
|
|
|
|
|
List<String> params = data.entrySet().stream()
|
|
|
|
List<String> params = keyValues.entrySet().stream()
|
|
|
|
.map(entry -> String.format("%s=%s", encode(entry.getKey()), encode((String)entry.getValue())))
|
|
|
|
.map(entry -> String.format("%s=%s", encode(entry.getKey()), encode((String)entry.getValue())))
|
|
|
|
.toList();
|
|
|
|
.toList();
|
|
|
|
return String.join("&", params);
|
|
|
|
return String.join("&", params);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String inJSON() {
|
|
|
|
private String inJSON() {
|
|
|
|
return data != null ? new JSON().stringify(data) : "";
|
|
|
|
Object body = bodyData();
|
|
|
|
|
|
|
|
if (!Assert.isEmpty(body))
|
|
|
|
|
|
|
|
return new JSON().stringify(body);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!Assert.isEmpty(keyValues))
|
|
|
|
|
|
|
|
return new JSON().stringify(keyValues);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String encode(String str) {
|
|
|
|
private String encode(String str) {
|
|
|
@ -396,14 +472,61 @@ public class WebClient {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HttpRequest post() {
|
|
|
|
HttpRequest post() {
|
|
|
|
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uri))
|
|
|
|
try {
|
|
|
|
.header("Content-Type", !json ? FORM_DATA : JSON_DATA)
|
|
|
|
ContentType contentType = contentType();
|
|
|
|
.POST(HttpRequest.BodyPublishers.ofString(!json ? getParams() : inJSON()));
|
|
|
|
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uri));
|
|
|
|
|
|
|
|
if (contentType != null)
|
|
|
|
|
|
|
|
builder.header("Content-Type", contentType.type);
|
|
|
|
|
|
|
|
builder.POST(bodyPublisher(contentType));
|
|
|
|
|
|
|
|
// .header("Content-Type", !json ? FORM_DATA : JSON_DATA)
|
|
|
|
|
|
|
|
// .POST(HttpRequest.BodyPublishers.ofString(!json ? getParams() : inJSON()));
|
|
|
|
|
|
|
|
|
|
|
|
if (headers != null)
|
|
|
|
if (headers != null)
|
|
|
|
headers.forEach((k, v) -> builder.header(k, v));
|
|
|
|
headers.forEach((k, v) -> builder.header(k, v));
|
|
|
|
|
|
|
|
|
|
|
|
return builder.build();
|
|
|
|
return builder.build();
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
throw Assert.runtimeException(e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private BodyPublisher bodyPublisher(ContentType type) throws Exception {
|
|
|
|
|
|
|
|
if (type == null)
|
|
|
|
|
|
|
|
return HttpRequest.BodyPublishers.noBody();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
|
|
case JSON: return HttpRequest.BodyPublishers.ofString(inJSON());
|
|
|
|
|
|
|
|
case XML:
|
|
|
|
|
|
|
|
case PLAIN:
|
|
|
|
|
|
|
|
Object value = bodyData();
|
|
|
|
|
|
|
|
return HttpRequest.BodyPublishers.ofString(value != null ? value.toString() : "");
|
|
|
|
|
|
|
|
case MULTIPART: return multipartPublisher();
|
|
|
|
|
|
|
|
default: return HttpRequest.BodyPublishers.ofString(getParams());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private BodyPublisher multipartPublisher() throws Exception {
|
|
|
|
|
|
|
|
String boundary = new BigInteger(64, new Random()).toString();
|
|
|
|
|
|
|
|
byte[] separator = ("--" + boundary + "\r\nContent-Disposition: form-data; name=").getBytes(charset);
|
|
|
|
|
|
|
|
ArrayList<byte[]> byteList = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (Map.Entry<String, Object> entry: keyValues.entrySet()) {
|
|
|
|
|
|
|
|
byteList.add(separator);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object value = entry.getValue();
|
|
|
|
|
|
|
|
if (value instanceof Path) {
|
|
|
|
|
|
|
|
Path path = (Path)value;
|
|
|
|
|
|
|
|
String mimeType = Files.probeContentType(path);
|
|
|
|
|
|
|
|
byteList.add(("\"" + entry.getKey() + "\"; filename=\"" + path.getFileName() + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
|
|
|
byteList.add(Files.readAllBytes(path));
|
|
|
|
|
|
|
|
byteList.add("\r\n".getBytes(charset));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
byteList.add(("\"" + entry.getKey() + "\"\r\n\r\n" + entry.getValue() + "\r\n").getBytes(charset));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
byteList.add(("--" + boundary + "--\r\n").getBytes(charset));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return BodyPublishers.ofByteArrays(byteList);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|