mirror of https://github.com/longtai-cn/hippo4j
FIX ISSUE#766,remove OkHttp3 dependency (#800)
* 1. remove the okhttp dependency, use own implementation instead. 2. add logtracing util. * remove unnecessary fields * fix notes * fix post bug * fix url encodingpull/805/head
parent
81f90bf6a6
commit
b74c2db9cc
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.constant;
|
||||
|
||||
/**
|
||||
* header constants.
|
||||
*/
|
||||
public interface HttpHeaderConsts {
|
||||
|
||||
String CLIENT_VERSION_HEADER = "Client-Version";
|
||||
String USER_AGENT_HEADER = "User-Agent";
|
||||
String REQUEST_SOURCE_HEADER = "Request-Source";
|
||||
String CONTENT_TYPE = "Content-Type";
|
||||
String CONTENT_LENGTH = "Content-Length";
|
||||
String ACCEPT_CHARSET = "Accept-Charset";
|
||||
String ACCEPT_ENCODING = "Accept-Encoding";
|
||||
String CONTENT_ENCODING = "Content-Encoding";
|
||||
String CONNECTION = "Requester";
|
||||
String REQUEST_ID = "RequestId";
|
||||
String REQUEST_MODULE = "Request-Module";
|
||||
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.constant;
|
||||
|
||||
import cn.hippo4j.common.toolkit.StringUtil;
|
||||
|
||||
/**
|
||||
* Http Media types.
|
||||
*
|
||||
* @author Rongzhen Yan
|
||||
*/
|
||||
public final class HttpMediaType {
|
||||
|
||||
public static final String APPLICATION_ATOM_XML = "application/atom+xml";
|
||||
|
||||
public static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8";
|
||||
|
||||
public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
|
||||
|
||||
public static final String APPLICATION_SVG_XML = "application/svg+xml";
|
||||
|
||||
public static final String APPLICATION_XHTML_XML = "application/xhtml+xml";
|
||||
|
||||
public static final String APPLICATION_XML = "application/xml;charset=UTF-8";
|
||||
|
||||
public static final String APPLICATION_JSON = "application/json;charset=UTF-8";
|
||||
|
||||
public static final String MULTIPART_FORM_DATA = "multipart/form-data;charset=UTF-8";
|
||||
|
||||
public static final String TEXT_HTML = "text/html;charset=UTF-8";
|
||||
|
||||
public static final String TEXT_PLAIN = "text/plain;charset=UTF-8";
|
||||
|
||||
private HttpMediaType(String type, String charset) {
|
||||
this.type = type;
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* content type.
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
/**
|
||||
* content type charset.
|
||||
*/
|
||||
private final String charset;
|
||||
|
||||
/**
|
||||
* Parse the given String contentType into a {@code MediaType} object.
|
||||
*
|
||||
* @param contentType mediaType
|
||||
* @return MediaType
|
||||
*/
|
||||
public static HttpMediaType valueOf(String contentType) {
|
||||
if (StringUtil.isEmpty(contentType)) {
|
||||
throw new IllegalArgumentException("MediaType must not be empty");
|
||||
}
|
||||
String[] values = contentType.split(";");
|
||||
String charset = Constants.ENCODE;
|
||||
for (String value : values) {
|
||||
if (value.startsWith("charset=")) {
|
||||
charset = value.substring("charset=".length());
|
||||
}
|
||||
}
|
||||
return new HttpMediaType(values[0], charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given contentType and charset to assemble into a {@code MediaType} object.
|
||||
*
|
||||
* @param contentType contentType
|
||||
* @param charset charset
|
||||
* @return MediaType
|
||||
*/
|
||||
public static HttpMediaType valueOf(String contentType, String charset) {
|
||||
if (StringUtil.isEmpty(contentType)) {
|
||||
throw new IllegalArgumentException("MediaType must not be empty");
|
||||
}
|
||||
String[] values = contentType.split(";");
|
||||
return new HttpMediaType(values[0], StringUtil.isEmpty(charset) ? Constants.ENCODE : charset);
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type + ";charset=" + charset;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.constant;
|
||||
|
||||
/**
|
||||
* Http method constants.
|
||||
*
|
||||
* @author Rongzhen Yan
|
||||
*/
|
||||
public class HttpMethod {
|
||||
|
||||
public static final String GET = "GET";
|
||||
|
||||
public static final String HEAD = "HEAD";
|
||||
|
||||
public static final String POST = "POST";
|
||||
|
||||
public static final String PUT = "PUT";
|
||||
|
||||
public static final String PATCH = "PATCH";
|
||||
|
||||
public static final String DELETE = "DELETE";
|
||||
|
||||
public static final String OPTIONS = "OPTIONS";
|
||||
|
||||
public static final String TRACE = "TRACE";
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.constant;
|
||||
|
||||
/**
|
||||
* Http状态码常量
|
||||
* @author Rongzhen Yan
|
||||
*/
|
||||
public interface HttpResponseCode {
|
||||
|
||||
int SC_CONTINUE = 100;
|
||||
int SC_SWITCHING_PROTOCOLS = 101;
|
||||
int SC_OK = 200;
|
||||
int SC_CREATED = 201;
|
||||
int SC_ACCEPTED = 202;
|
||||
int SC_NON_AUTHORITATIVE_INFORMATION = 203;
|
||||
int SC_NO_CONTENT = 204;
|
||||
int SC_RESET_CONTENT = 205;
|
||||
int SC_PARTIAL_CONTENT = 206;
|
||||
int SC_MULTIPLE_CHOICES = 300;
|
||||
int SC_MOVED_PERMANENTLY = 301;
|
||||
int SC_MOVED_TEMPORARILY = 302;
|
||||
int SC_FOUND = 302;
|
||||
int SC_SEE_OTHER = 303;
|
||||
int SC_NOT_MODIFIED = 304;
|
||||
int SC_USE_PROXY = 305;
|
||||
int SC_TEMPORARY_REDIRECT = 307;
|
||||
int SC_BAD_REQUEST = 400;
|
||||
int SC_UNAUTHORIZED = 401;
|
||||
int SC_PAYMENT_REQUIRED = 402;
|
||||
int SC_FORBIDDEN = 403;
|
||||
int SC_NOT_FOUND = 404;
|
||||
int SC_METHOD_NOT_ALLOWED = 405;
|
||||
int SC_NOT_ACCEPTABLE = 406;
|
||||
int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
|
||||
int SC_REQUEST_TIMEOUT = 408;
|
||||
int SC_CONFLICT = 409;
|
||||
int SC_GONE = 410;
|
||||
int SC_LENGTH_REQUIRED = 411;
|
||||
int SC_PRECONDITION_FAILED = 412;
|
||||
int SC_REQUEST_ENTITY_TOO_LARGE = 413;
|
||||
int SC_REQUEST_URI_TOO_LONG = 414;
|
||||
int SC_UNSUPPORTED_MEDIA_TYPE = 415;
|
||||
int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
|
||||
int SC_EXPECTATION_FAILED = 417;
|
||||
int SC_INTERNAL_SERVER_ERROR = 500;
|
||||
int SC_NOT_IMPLEMENTED = 501;
|
||||
int SC_BAD_GATEWAY = 502;
|
||||
int SC_SERVICE_UNAVAILABLE = 503;
|
||||
int SC_GATEWAY_TIMEOUT = 504;
|
||||
int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
|
||||
}
|
@ -1,299 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.toolkit;
|
||||
|
||||
import cn.hippo4j.common.web.exception.ServiceException;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* HttpClient util.
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpClientUtil {
|
||||
|
||||
private OkHttpClient hippo4JOkHttpClient;
|
||||
|
||||
private static AtomicReference<HttpClientUtil> reference = new AtomicReference<>();
|
||||
|
||||
private MediaType jsonMediaType = MediaType.parse("application/json; charset=utf-8");
|
||||
|
||||
private static int HTTP_OK_CODE = 200;
|
||||
|
||||
private HttpClientUtil() {
|
||||
OkHttpClient.Builder build = new OkHttpClient.Builder();
|
||||
build.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
supportHttps(build);
|
||||
this.hippo4JOkHttpClient = build.build();
|
||||
}
|
||||
|
||||
public static HttpClientUtil build() {
|
||||
if (reference.get() == null) {
|
||||
reference.compareAndSet(null, new HttpClientUtil());
|
||||
}
|
||||
return reference.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get.
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
public String get(String url) {
|
||||
try {
|
||||
return new String(doGet(url), "utf-8");
|
||||
} catch (Exception e) {
|
||||
log.error("HttpGet call failed. {}", url, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request, supports adding query string.
|
||||
*
|
||||
* @param url
|
||||
* @param queryString
|
||||
* @return
|
||||
*/
|
||||
public String get(String url, Map<String, String> queryString) {
|
||||
String fullUrl = buildUrl(url, queryString);
|
||||
return get(fullUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize directly after getting Json.
|
||||
*
|
||||
* @param url
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
public <T> T restApiGet(String url, Class<T> clazz) {
|
||||
String resp = get(url);
|
||||
return JSONUtil.parseObject(resp, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call health check.
|
||||
*
|
||||
* @param url
|
||||
* @param clazz
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
public <T> T restApiGetHealth(String url, Class<T> clazz) {
|
||||
String resp = new String(doGet(url), "utf-8");
|
||||
return JSONUtil.parseObject(resp, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request, supports query string.
|
||||
*
|
||||
* @param url
|
||||
* @param queryString
|
||||
* @param clazz
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public <T> T restApiGet(String url, Map<String, String> queryString, Class<T> clazz) {
|
||||
String fullUrl = buildUrl(url, queryString);
|
||||
String resp = get(fullUrl);
|
||||
return JSONUtil.parseObject(resp, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rest interface Post call.
|
||||
*
|
||||
* @param url
|
||||
* @param body
|
||||
* @return
|
||||
*/
|
||||
public String restApiPost(String url, Object body) {
|
||||
try {
|
||||
return doPost(url, body);
|
||||
} catch (Exception e) {
|
||||
log.error("HttpPost call failed. {} message: {}", url, e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rest interface Post call. Deserialize the return value directly.
|
||||
*
|
||||
* @param url
|
||||
* @param body
|
||||
* @return
|
||||
*/
|
||||
public <T> T restApiPost(String url, Object body, Class<T> clazz) {
|
||||
String resp = restApiPost(url, body);
|
||||
return JSONUtil.parseObject(resp, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a complete Url from the query string.
|
||||
*
|
||||
* @param url
|
||||
* @param queryString
|
||||
* @return
|
||||
*/
|
||||
public String buildUrl(String url, Map<String, String> queryString) {
|
||||
if (null == queryString) {
|
||||
return url;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder(url);
|
||||
boolean isFirst = true;
|
||||
for (Map.Entry<String, String> entry : queryString.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (key != null && entry.getValue() != null) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
builder.append("?");
|
||||
} else {
|
||||
builder.append("&");
|
||||
}
|
||||
builder.append(key)
|
||||
.append("=")
|
||||
.append(queryString.get(key));
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private String doPost(String url, Object body) {
|
||||
String bodyContent;
|
||||
if (body instanceof String) {
|
||||
bodyContent = (String) body;
|
||||
} else {
|
||||
bodyContent = JSONUtil.toJSONString(body);
|
||||
}
|
||||
RequestBody requestBody = RequestBody.create(jsonMediaType, bodyContent);
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.post(requestBody)
|
||||
.build();
|
||||
try (Response resp = hippo4JOkHttpClient.newCall(request).execute()) {
|
||||
try (ResponseBody responseBody = resp.body()) {
|
||||
if (resp.code() != HTTP_OK_CODE) {
|
||||
String msg = String.format("HttpPost response code error. [code] %s [url] %s [body] %s", resp.code(), url, bodyContent);
|
||||
throw new ServiceException(msg);
|
||||
}
|
||||
return responseBody.string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private byte[] doGet(String url) {
|
||||
Request request = new Request.Builder().get().url(url).build();
|
||||
try (Response resp = hippo4JOkHttpClient.newCall(request).execute()) {
|
||||
try (ResponseBody responseBody = resp.body()) {
|
||||
if (resp.code() != HTTP_OK_CODE) {
|
||||
String msg = String.format("HttpGet response code error. [code] %s [url] %s", resp.code(), url);
|
||||
throw new ServiceException(msg);
|
||||
}
|
||||
return responseBody.bytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public <T> T restApiGetByThreadPool(String url, Map<String, String> headers, Map<String, String> paramValues, Long readTimeoutMs, Class<T> clazz) {
|
||||
String buildUrl = buildUrl(url, paramValues);
|
||||
Request.Builder builder = new Request.Builder().get();
|
||||
if (!CollectionUtils.isEmpty(headers)) {
|
||||
builder.headers(Headers.of(headers));
|
||||
}
|
||||
Request request = builder.url(buildUrl).build();
|
||||
Call call = hippo4JOkHttpClient.newCall(request);
|
||||
// TODO Plan to optimize the timout api because its version is too high.
|
||||
call.timeout().timeout(readTimeoutMs, TimeUnit.MILLISECONDS);
|
||||
try (Response resp = call.execute()) {
|
||||
try (ResponseBody responseBody = resp.body()) {
|
||||
if (resp.code() != HTTP_OK_CODE) {
|
||||
String msg = String.format("HttpGet response code error. [code] %s [url] %s", resp.code(), url);
|
||||
log.error(msg);
|
||||
throw new ServiceException(msg);
|
||||
}
|
||||
return JSONUtil.parseObject(responseBody.string(), clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public <T> T restApiPostByThreadPool(String url, Map<String, String> headers, Map<String, String> paramValues, Long readTimeoutMs, Class<T> clazz) {
|
||||
String buildUrl = buildUrl(url, paramValues);
|
||||
Request request = new Request.Builder()
|
||||
.url(buildUrl)
|
||||
.headers(Headers.of(headers))
|
||||
.post(RequestBody.create(jsonMediaType, ""))
|
||||
.build();
|
||||
Call call = hippo4JOkHttpClient.newCall(request);
|
||||
// TODO Plan to optimize the timout api because its version is too high.
|
||||
call.timeout().timeout(readTimeoutMs, TimeUnit.MILLISECONDS);
|
||||
try (Response resp = call.execute()) {
|
||||
try (ResponseBody responseBody = resp.body()) {
|
||||
if (resp.code() != HTTP_OK_CODE) {
|
||||
String msg = String.format("HttpPost response code error. [code] %s [url] %s.", resp.code(), url);
|
||||
log.error(msg);
|
||||
throw new ServiceException(msg);
|
||||
}
|
||||
return JSONUtil.parseObject(responseBody.string(), clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void supportHttps(OkHttpClient.Builder builder) {
|
||||
final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||
return new java.security.cert.X509Certificate[]{};
|
||||
}
|
||||
}};
|
||||
|
||||
final SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
||||
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
|
||||
builder.hostnameVerifier((hostname, session) -> true);
|
||||
}
|
||||
}
|
@ -0,0 +1,341 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.toolkit;
|
||||
|
||||
import cn.hippo4j.common.constant.Constants;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* IO related tool methods.
|
||||
*
|
||||
* @author nacos
|
||||
*/
|
||||
public class IoUtils {
|
||||
|
||||
/**
|
||||
* Try decompress by GZIP from stream.
|
||||
*
|
||||
* @param raw compress stream
|
||||
* @return byte array after decompress
|
||||
*/
|
||||
public static byte[] tryDecompress(InputStream raw) throws IOException {
|
||||
try (
|
||||
GZIPInputStream gis = new GZIPInputStream(raw);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
copy(gis, out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try decompress by GZIP from byte array.
|
||||
*
|
||||
* @param raw compressed byte array
|
||||
* @return byte array after decompress
|
||||
* @throws Exception exception
|
||||
*/
|
||||
public static byte[] tryDecompress(byte[] raw) throws Exception {
|
||||
if (!isGzipStream(raw)) {
|
||||
return raw;
|
||||
}
|
||||
try (
|
||||
GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(raw));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
copy(gis, out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try compress by GZIP for string.
|
||||
*
|
||||
* @param str strings to be compressed.
|
||||
* @param encoding encoding.
|
||||
* @return byte[]
|
||||
*/
|
||||
public static byte[] tryCompress(String str, String encoding) {
|
||||
if (str == null || str.length() == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try (GZIPOutputStream gzip = new GZIPOutputStream(out)) {
|
||||
gzip.write(str.getBytes(encoding));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private static BufferedReader toBufferedReader(Reader reader) {
|
||||
return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write string to a file.
|
||||
*
|
||||
* @param file file
|
||||
* @param data string
|
||||
* @param encoding encoding of string
|
||||
* @throws IOException io exception
|
||||
*/
|
||||
public static void writeStringToFile(File file, String data, String encoding) throws IOException {
|
||||
try (OutputStream os = new FileOutputStream(file)) {
|
||||
os.write(data.getBytes(encoding));
|
||||
os.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read lines.
|
||||
*
|
||||
* @param input reader
|
||||
* @return list of line
|
||||
* @throws IOException io exception
|
||||
*/
|
||||
public static List<String> readLines(Reader input) throws IOException {
|
||||
BufferedReader reader = toBufferedReader(input);
|
||||
List<String> list = new ArrayList<>();
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if (null != line) {
|
||||
if (StringUtil.isNotEmpty(line)) {
|
||||
list.add(line.trim());
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* To string from stream.
|
||||
*
|
||||
* @param input stream
|
||||
* @param encoding charset of stream
|
||||
* @return string
|
||||
* @throws IOException io exception
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String toString(InputStream input, String encoding) {
|
||||
if (input == null) {
|
||||
return StringUtil.EMPTY;
|
||||
}
|
||||
return (null == encoding) ? toString(new InputStreamReader(input, Constants.ENCODE))
|
||||
: toString(new InputStreamReader(input, encoding));
|
||||
}
|
||||
|
||||
/**
|
||||
* To string from reader.
|
||||
*
|
||||
* @param reader reader
|
||||
* @return string
|
||||
* @throws IOException io exception
|
||||
*/
|
||||
public static String toString(Reader reader) throws IOException {
|
||||
CharArrayWriter sw = new CharArrayWriter();
|
||||
copy(reader, sw);
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy data.
|
||||
*
|
||||
* @param input source
|
||||
* @param output target
|
||||
* @return copy size
|
||||
* @throws IOException io exception
|
||||
*/
|
||||
public static long copy(Reader input, Writer output) throws IOException {
|
||||
char[] buffer = new char[1 << 12];
|
||||
long count = 0;
|
||||
for (int n = 0; (n = input.read(buffer)) >= 0;) {
|
||||
output.write(buffer, 0, n);
|
||||
count += n;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy data.
|
||||
*
|
||||
* @param input source
|
||||
* @param output target
|
||||
* @return copy size
|
||||
* @throws IOException io exception
|
||||
*/
|
||||
public static long copy(InputStream input, OutputStream output) throws IOException {
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
int totalBytes = 0;
|
||||
while ((bytesRead = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, bytesRead);
|
||||
|
||||
totalBytes += bytesRead;
|
||||
}
|
||||
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file or dir.
|
||||
*
|
||||
* <p>If is dir, clean directory, do not delete dir.
|
||||
*
|
||||
* <p>If is file, delete file.
|
||||
*
|
||||
* @param fileOrDir file or dir
|
||||
* @throws IOException io exception
|
||||
*/
|
||||
public static void delete(File fileOrDir) throws IOException {
|
||||
if (fileOrDir == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileOrDir.isDirectory()) {
|
||||
cleanDirectory(fileOrDir);
|
||||
} else {
|
||||
if (fileOrDir.exists()) {
|
||||
boolean isDeleteOk = fileOrDir.delete();
|
||||
if (!isDeleteOk) {
|
||||
throw new IOException("delete fail");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理目录下的内容. Clean content under directory.
|
||||
*
|
||||
* @param directory directory
|
||||
* @throws IOException io exception
|
||||
*/
|
||||
public static void cleanDirectory(File directory) throws IOException {
|
||||
if (!directory.exists()) {
|
||||
String message = directory + " does not exist";
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
|
||||
if (!directory.isDirectory()) {
|
||||
String message = directory + " is not a directory";
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
|
||||
File[] files = directory.listFiles();
|
||||
// null if security restricted
|
||||
if (files == null) {
|
||||
throw new IOException("Failed to list contents of " + directory);
|
||||
}
|
||||
|
||||
IOException exception = null;
|
||||
for (File file : files) {
|
||||
try {
|
||||
delete(file);
|
||||
} catch (IOException ioe) {
|
||||
exception = ioe;
|
||||
}
|
||||
}
|
||||
|
||||
if (null != exception) {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy File.
|
||||
*
|
||||
* @param source source file path
|
||||
* @param target target file path
|
||||
* @throws IOException io exception
|
||||
*/
|
||||
public static void copyFile(String source, String target) throws IOException {
|
||||
File sf = new File(source);
|
||||
if (!sf.exists()) {
|
||||
throw new IllegalArgumentException("source file does not exist.");
|
||||
}
|
||||
File tf = new File(target);
|
||||
if (!tf.getParentFile().mkdirs()) {
|
||||
throw new RuntimeException("failed to create parent directory.");
|
||||
}
|
||||
if (!tf.exists() && !tf.createNewFile()) {
|
||||
throw new RuntimeException("failed to create target file.");
|
||||
}
|
||||
try (
|
||||
FileChannel sc = new FileInputStream(sf).getChannel();
|
||||
FileChannel tc = new FileOutputStream(tf).getChannel()) {
|
||||
sc.transferTo(0, sc.size(), tc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Judge whether is Gzip stream.
|
||||
*
|
||||
* @param bytes byte array
|
||||
* @return true if is gzip, otherwise false
|
||||
*/
|
||||
public static boolean isGzipStream(byte[] bytes) {
|
||||
|
||||
int minByteArraySize = 2;
|
||||
if (bytes == null || bytes.length < minByteArraySize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return GZIPInputStream.GZIP_MAGIC == ((bytes[1] << 8 | bytes[0]) & 0xFFFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close http connection quietly.
|
||||
*
|
||||
* @param connection http connection
|
||||
*/
|
||||
public static void closeQuietly(HttpURLConnection connection) {
|
||||
if (connection != null) {
|
||||
try {
|
||||
closeQuietly(connection.getInputStream());
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close closable object quietly.
|
||||
*
|
||||
* @param closeable http connection
|
||||
*/
|
||||
public static void closeQuietly(Closeable closeable) {
|
||||
try {
|
||||
if (closeable != null) {
|
||||
closeable.close();
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeQuietly(Closeable... closeable) {
|
||||
Arrays.stream(closeable).forEach(IoUtils::closeQuietly);
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.toolkit;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Dictionary;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Map utils.
|
||||
*/
|
||||
public class MapUtil {
|
||||
|
||||
/**
|
||||
* Null-safe check if the specified Dictionary is empty.
|
||||
*
|
||||
* <p>Null returns true.
|
||||
*
|
||||
* @param map the collection to check, may be null
|
||||
* @return true if empty or null
|
||||
*/
|
||||
public static boolean isEmpty(Map map) {
|
||||
return (map == null || map.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe check if the specified Dictionary is empty.
|
||||
*
|
||||
* <p>Null returns true.
|
||||
*
|
||||
* @param coll the collection to check, may be null
|
||||
* @return true if empty or null
|
||||
*/
|
||||
public static boolean isEmpty(Dictionary coll) {
|
||||
return (coll == null || coll.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe check if the specified Dictionary is not empty.
|
||||
*
|
||||
* <p>Null returns false.
|
||||
*
|
||||
* @param map the collection to check, may be null
|
||||
* @return true if non-null and non-empty
|
||||
*/
|
||||
public static boolean isNotEmpty(Map map) {
|
||||
return !isEmpty(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe check if the specified Dictionary is not empty.
|
||||
*
|
||||
* <p>Null returns false.
|
||||
*
|
||||
* @param coll the collection to check, may be null
|
||||
* @return true if non-null and non-empty
|
||||
*/
|
||||
public static boolean isNotEmpty(Dictionary coll) {
|
||||
return !isEmpty(coll);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put into map if value is not null.
|
||||
*
|
||||
* @param target target map
|
||||
* @param key key
|
||||
* @param value value
|
||||
*/
|
||||
public static void putIfValNoNull(Map target, Object key, Object value) {
|
||||
Objects.requireNonNull(key, "key");
|
||||
if (value != null) {
|
||||
target.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put into map if value is not empty.
|
||||
*
|
||||
* @param target target map
|
||||
* @param key key
|
||||
* @param value value
|
||||
*/
|
||||
public static void putIfValNoEmpty(Map target, Object key, Object value) {
|
||||
Objects.requireNonNull(key, "key");
|
||||
if (value instanceof String) {
|
||||
if (StringUtil.isNotEmpty((String) value)) {
|
||||
target.put(key, value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (value instanceof Collection) {
|
||||
if (CollectionUtil.isNotEmpty((Collection) value)) {
|
||||
target.put(key, value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (value instanceof Map) {
|
||||
if (isNotEmpty((Map) value)) {
|
||||
target.put(key, value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (value instanceof Dictionary) {
|
||||
if (isNotEmpty((Dictionary) value)) {
|
||||
target.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ComputeIfAbsent lazy load.
|
||||
*
|
||||
* @param target target Map data.
|
||||
* @param key map key.
|
||||
* @param mappingFunction function which is need to be executed.
|
||||
* @param param1 function's parameter value1.
|
||||
* @param param2 function's parameter value1.
|
||||
* @return
|
||||
*/
|
||||
public static <K, C, V, T> V computeIfAbsent(Map<K, V> target, K key, BiFunction<C, T, V> mappingFunction, C param1,
|
||||
T param2) {
|
||||
|
||||
Objects.requireNonNull(target, "target");
|
||||
Objects.requireNonNull(key, "key");
|
||||
Objects.requireNonNull(mappingFunction, "mappingFunction");
|
||||
Objects.requireNonNull(param1, "param1");
|
||||
Objects.requireNonNull(param2, "param2");
|
||||
|
||||
V val = target.get(key);
|
||||
if (val == null) {
|
||||
V ret = mappingFunction.apply(param1, param2);
|
||||
target.put(key, ret);
|
||||
return ret;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove value, Thread safety depends on whether the Map is a thread-safe Map.
|
||||
*
|
||||
* @param map map
|
||||
* @param key key
|
||||
* @param removeJudge judge this key can be remove
|
||||
* @param <K> key type
|
||||
* @param <V> value type
|
||||
* @return value
|
||||
*/
|
||||
public static <K, V> V removeKey(Map<K, V> map, K key, Predicate<V> removeJudge) {
|
||||
return map.computeIfPresent(key, (k, v) -> removeJudge.test(v) ? null : v);
|
||||
}
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.toolkit.http;
|
||||
|
||||
import cn.hippo4j.common.constant.Constants;
|
||||
import cn.hippo4j.common.constant.HttpHeaderConsts;
|
||||
import cn.hippo4j.common.constant.HttpMediaType;
|
||||
import cn.hippo4j.common.toolkit.MapUtil;
|
||||
import cn.hippo4j.common.toolkit.StringUtil;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Http header.
|
||||
*
|
||||
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
|
||||
*/
|
||||
public class Header {
|
||||
|
||||
public static final Header EMPTY = Header.newInstance();
|
||||
|
||||
private final Map<String, String> header;
|
||||
|
||||
private final Map<String, List<String>> originalResponseHeader;
|
||||
|
||||
private static final String DEFAULT_CHARSET = "UTF-8";
|
||||
|
||||
private static final String DEFAULT_ENCODING = "gzip";
|
||||
|
||||
private Header() {
|
||||
header = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
originalResponseHeader = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
addParam(HttpHeaderConsts.CONTENT_TYPE, HttpMediaType.APPLICATION_JSON);
|
||||
addParam(HttpHeaderConsts.ACCEPT_CHARSET, DEFAULT_CHARSET);
|
||||
// addParam(HttpHeaderConsts.ACCEPT_ENCODING, DEFAULT_ENCODING);
|
||||
}
|
||||
|
||||
public static Header newInstance() {
|
||||
return new Header();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the key and value to the header.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return header
|
||||
*/
|
||||
public Header addParam(String key, String value) {
|
||||
if (StringUtil.isNotEmpty(key)) {
|
||||
header.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Header setContentType(String contentType) {
|
||||
if (contentType == null) {
|
||||
contentType = HttpMediaType.APPLICATION_JSON;
|
||||
}
|
||||
return addParam(HttpHeaderConsts.CONTENT_TYPE, contentType);
|
||||
}
|
||||
|
||||
public Header build() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getValue(String key) {
|
||||
return header.get(key);
|
||||
}
|
||||
|
||||
public Map<String, String> getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public Iterator<Map.Entry<String, String>> iterator() {
|
||||
return header.entrySet().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer to KV part list. The odd index is key and the even index is value.
|
||||
*
|
||||
* @return KV string list
|
||||
*/
|
||||
public List<String> toList() {
|
||||
List<String> list = new ArrayList<>(header.size() * 2);
|
||||
Iterator<Map.Entry<String, String>> iterator = iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, String> entry = iterator.next();
|
||||
list.add(entry.getKey());
|
||||
list.add(entry.getValue());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all KV list to header. The odd index is key and the even index is value.
|
||||
*
|
||||
* @param list KV list
|
||||
* @return header
|
||||
*/
|
||||
public Header addAll(List<String> list) {
|
||||
if ((list.size() & 1) != 0) {
|
||||
throw new IllegalArgumentException("list size must be a multiple of 2");
|
||||
}
|
||||
for (int i = 0; i < list.size();) {
|
||||
String key = list.get(i++);
|
||||
if (StringUtil.isNotEmpty(key)) {
|
||||
header.put(key, list.get(i++));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all parameters to header.
|
||||
*
|
||||
* @param params parameters
|
||||
*/
|
||||
public void addAll(Map<String, String> params) {
|
||||
if (MapUtil.isNotEmpty(params)) {
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
addParam(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set original format response header.
|
||||
*
|
||||
* <p>Currently only corresponds to the response header of JDK.
|
||||
*
|
||||
* @param key original response header key
|
||||
* @param values original response header values
|
||||
*/
|
||||
public void addOriginalResponseHeader(String key, List<String> values) {
|
||||
if (StringUtil.isNotEmpty(key)) {
|
||||
this.originalResponseHeader.put(key, values);
|
||||
addParam(key, values.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get original format response header.
|
||||
*
|
||||
* <p>Currently only corresponds to the response header of JDK.
|
||||
*
|
||||
* @return Map original response header
|
||||
*/
|
||||
public Map<String, List<String>> getOriginalResponseHeader() {
|
||||
return this.originalResponseHeader;
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
String acceptCharset = getValue(HttpHeaderConsts.ACCEPT_CHARSET);
|
||||
if (acceptCharset == null) {
|
||||
String contentType = getValue(HttpHeaderConsts.CONTENT_TYPE);
|
||||
acceptCharset = StringUtil.isNotBlank(contentType) ? analysisCharset(contentType) : Constants.ENCODE;
|
||||
}
|
||||
return acceptCharset;
|
||||
}
|
||||
|
||||
private String analysisCharset(String contentType) {
|
||||
String[] values = contentType.split(";");
|
||||
String charset = Constants.ENCODE;
|
||||
if (values.length == 0) {
|
||||
return charset;
|
||||
}
|
||||
for (String value : values) {
|
||||
if (value.startsWith("charset=")) {
|
||||
charset = value.substring("charset=".length());
|
||||
}
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
header.clear();
|
||||
originalResponseHeader.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Header{" + "headerToMap=" + header + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.toolkit.http;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Represents a client-side HTTP response.
|
||||
*
|
||||
* @author mai.jh
|
||||
*/
|
||||
public interface HttpClientResponse extends Closeable {
|
||||
|
||||
/**
|
||||
* Return the headers of this message.
|
||||
*
|
||||
* @return a corresponding HttpHeaders object (never {@code null})
|
||||
*/
|
||||
Header getHeaders();
|
||||
|
||||
/**
|
||||
* Return the body of the message as an input stream.
|
||||
*
|
||||
* @return String response body
|
||||
*/
|
||||
InputStream getBody();
|
||||
|
||||
/**
|
||||
* Return the HTTP status code.
|
||||
*
|
||||
* @return the HTTP status as an integer
|
||||
*/
|
||||
int getStatusCode();
|
||||
|
||||
/**
|
||||
* Return the HTTP status text of the response.
|
||||
*
|
||||
* @return the HTTP status text
|
||||
*/
|
||||
String getStatusText();
|
||||
|
||||
/**
|
||||
* Return the body As string.
|
||||
* @return
|
||||
*/
|
||||
String getBodyString();
|
||||
|
||||
/**
|
||||
* close response InputStream.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.toolkit.http;
|
||||
|
||||
import cn.hippo4j.common.constant.Constants;
|
||||
import cn.hippo4j.common.constant.HttpMediaType;
|
||||
import cn.hippo4j.common.constant.HttpMethod;
|
||||
import cn.hippo4j.common.constant.HttpResponseCode;
|
||||
import cn.hippo4j.common.toolkit.IoUtils;
|
||||
import cn.hippo4j.common.toolkit.JSONUtil;
|
||||
import cn.hippo4j.common.toolkit.StringUtil;
|
||||
import cn.hippo4j.common.toolkit.logtracing.LogMessage;
|
||||
import cn.hippo4j.common.web.exception.ServiceException;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.hippo4j.common.constant.HttpHeaderConsts.CONTENT_LENGTH;
|
||||
|
||||
/**
|
||||
* Http request utilities.
|
||||
* @author Rongzhen Yan
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpUtils {
|
||||
|
||||
private static final int CONNECT_TIMEOUT = 10000;
|
||||
|
||||
private static final int READ_TIMEOUT = 300000;
|
||||
|
||||
private HttpUtils() {
|
||||
}
|
||||
|
||||
public static <T> T post(String url, Object body, Class<T> clazz) {
|
||||
String result = post(url, body);
|
||||
return JSONUtil.parseObject(result, clazz);
|
||||
}
|
||||
|
||||
public static <T> T post(String url, Object body, long timeoutMillis, Class<T> clazz) {
|
||||
String result = post(url, body, timeoutMillis);
|
||||
return JSONUtil.parseObject(result, clazz);
|
||||
}
|
||||
|
||||
public static <T> T post(String url, Map<String, String> headers, Map<String, String> params, long timeoutMillis, Class<T> clazz) {
|
||||
String result = execute(buildUrl(url, params), HttpMethod.POST, null, headers, timeoutMillis).getBodyString();
|
||||
return JSONUtil.parseObject(result, clazz);
|
||||
}
|
||||
|
||||
public static <T> T post(String url, Map<String, String> headers, Object body, long timeoutMillis, Class<T> clazz) {
|
||||
String result = execute(url, HttpMethod.POST, body, headers, timeoutMillis).getBodyString();
|
||||
return JSONUtil.parseObject(result, clazz);
|
||||
}
|
||||
|
||||
public static String post(String url, Object body) {
|
||||
return execute(url, HttpMethod.POST, body, null).getBodyString();
|
||||
}
|
||||
|
||||
public static String post(String url, Object body, long timeoutMillis) {
|
||||
return execute(url, HttpMethod.POST, body, null, timeoutMillis).getBodyString();
|
||||
}
|
||||
|
||||
public static String postJson(String url, String json) {
|
||||
return executeJson(url, HttpMethod.POST, json, null).getBodyString();
|
||||
}
|
||||
|
||||
public static String put(String url, Object body) {
|
||||
return execute(url, HttpMethod.PUT, body, null).getBodyString();
|
||||
}
|
||||
|
||||
public static String put(String url, Object body, Map<String, String> headers) {
|
||||
return execute(url, HttpMethod.PUT, body, headers).getBodyString();
|
||||
}
|
||||
|
||||
public static <T> T get(String url, Map<String, String> headers, Map<String, String> params, long readTimeoutMillis, Class<T> clazz) {
|
||||
String result = execute(buildUrl(url, params), HttpMethod.GET, null, headers, readTimeoutMillis).getBodyString();
|
||||
return JSONUtil.parseObject(result, clazz);
|
||||
}
|
||||
|
||||
public static String get(String url, Map<String, String> params) {
|
||||
return execute(buildUrl(url, params), HttpMethod.GET, null, null).getBodyString();
|
||||
}
|
||||
|
||||
public static String get(String url) {
|
||||
return execute(url, HttpMethod.GET, null, null).getBodyString();
|
||||
}
|
||||
|
||||
public static <T> T get(String url, Class<T> clazz) {
|
||||
return JSONUtil.parseObject(get(url), clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a complete Url from the query string.
|
||||
* @param url
|
||||
* @param queryString
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String buildUrl(String url, Map<String, String> queryString) {
|
||||
if (null == queryString) {
|
||||
return url;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder(url);
|
||||
boolean isFirst = true;
|
||||
for (Map.Entry<String, String> entry : queryString.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (key != null && entry.getValue() != null) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
builder.append("?");
|
||||
} else {
|
||||
builder.append("&");
|
||||
}
|
||||
String value = URLEncoder.encode(queryString.get(key), Constants.ENCODE)
|
||||
.replaceAll("\\+", "%20");
|
||||
builder.append(key)
|
||||
.append("=")
|
||||
.append(value);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static HttpClientResponse execute(String url, String method, Object param, Map<String, String> headers) {
|
||||
HttpURLConnection connection = createConnection(url, method);
|
||||
return doExecute(connection, param, headers);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static HttpClientResponse doExecute(HttpURLConnection connection, Object body, Map<String, String> headers) {
|
||||
try {
|
||||
if (headers != null) {
|
||||
for (String key : headers.keySet()) {
|
||||
connection.setRequestProperty(key, headers.get(key));
|
||||
}
|
||||
}
|
||||
String bodyString;
|
||||
if (body instanceof String) {
|
||||
bodyString = (String) body;
|
||||
} else {
|
||||
bodyString = JSONUtil.toJSONString(body);
|
||||
}
|
||||
if (!StringUtil.isEmpty(bodyString)) {
|
||||
connection.setDoOutput(true);
|
||||
byte[] b = bodyString.getBytes();
|
||||
connection.setRequestProperty(CONTENT_LENGTH, String.valueOf(b.length));
|
||||
OutputStream outputStream = connection.getOutputStream();
|
||||
outputStream.write(b, 0, b.length);
|
||||
outputStream.flush();
|
||||
IoUtils.closeQuietly(outputStream);
|
||||
}
|
||||
connection.connect();
|
||||
JdkHttpClientResponse response = new JdkHttpClientResponse(connection);
|
||||
if (HttpResponseCode.SC_OK != response.getStatusCode()) {
|
||||
String msg = String.format("HttpPost response code error. [code] %s [url] %s [body] %s", response.getStatusCode(), connection.getURL(), response.getBodyString());
|
||||
throw new ServiceException(msg);
|
||||
}
|
||||
return response;
|
||||
} catch (Throwable e) {
|
||||
log.error(LogMessage.getInstance().setMsg("Http Call error.")
|
||||
.kv("url", connection.getURL())
|
||||
.kv("method", connection.getRequestMethod())
|
||||
.kv("body", JSONUtil.toJSONString(body))
|
||||
.kv2String("headers", JSONUtil.toJSONString(headers)), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpClientResponse execute(String url, String method, Object body, Map<String, String> headers, long timeout) {
|
||||
HttpURLConnection connection = createConnection(url, method, timeout);
|
||||
return doExecute(connection, body, headers);
|
||||
}
|
||||
|
||||
public static HttpClientResponse executeJson(String url, String method, String json, Map<String, String> headers) {
|
||||
if (!JSONUtil.isJson(json)) {
|
||||
log.error(LogMessage.getInstance().setMsg("Http Call error.")
|
||||
.kv("url", url)
|
||||
.kv("method", method)
|
||||
.kv("json", json)
|
||||
.kv2String("headers", JSONUtil.toJSONString(headers)));
|
||||
throw new ServiceException("invalid http json body, please check it again.");
|
||||
}
|
||||
return execute(url, method, json, headers);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static HttpURLConnection createConnection(String url, String method) {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(CONNECT_TIMEOUT);
|
||||
connection.setReadTimeout(READ_TIMEOUT);
|
||||
connection.setRequestMethod(method);
|
||||
connection.setRequestProperty(Constants.CONTENT_TYPE, HttpMediaType.APPLICATION_JSON);
|
||||
return connection;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static HttpURLConnection createConnection(String url, String method, long timeout) {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
|
||||
connection.setDoInput(true);
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(Integer.parseInt(String.valueOf(timeout)));
|
||||
connection.setReadTimeout(Integer.parseInt(String.valueOf(timeout)));
|
||||
connection.setRequestMethod(method);
|
||||
connection.setRequestProperty(Constants.CONTENT_TYPE, HttpMediaType.APPLICATION_JSON);
|
||||
return connection;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.toolkit.http;
|
||||
|
||||
import cn.hippo4j.common.constant.Constants;
|
||||
import cn.hippo4j.common.constant.HttpHeaderConsts;
|
||||
import cn.hippo4j.common.toolkit.IoUtils;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a client-side HTTP response with JDK implementation
|
||||
*
|
||||
* @author Rongzhen Yan
|
||||
*/
|
||||
public class JdkHttpClientResponse implements HttpClientResponse {
|
||||
|
||||
private final HttpURLConnection conn;
|
||||
|
||||
private InputStream responseStream;
|
||||
|
||||
private Header responseHeader;
|
||||
|
||||
private static final String CONTENT_ENCODING = "gzip";
|
||||
|
||||
public JdkHttpClientResponse(HttpURLConnection conn) {
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header getHeaders() {
|
||||
if (this.responseHeader == null) {
|
||||
this.responseHeader = Header.newInstance();
|
||||
}
|
||||
for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
|
||||
this.responseHeader.addOriginalResponseHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return this.responseHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public InputStream getBody() {
|
||||
Header headers = getHeaders();
|
||||
InputStream errorStream = this.conn.getErrorStream();
|
||||
this.responseStream = (errorStream != null ? errorStream : this.conn.getInputStream());
|
||||
String contentEncoding = headers.getValue(HttpHeaderConsts.CONTENT_ENCODING);
|
||||
// Used to process http content_encoding, when content_encoding is GZIP, use GZIPInputStream
|
||||
if (CONTENT_ENCODING.equals(contentEncoding)) {
|
||||
byte[] bytes = IoUtils.tryDecompress(this.responseStream);
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
return this.responseStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getStatusCode() {
|
||||
return this.conn.getResponseCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String getStatusText() {
|
||||
return this.conn.getResponseMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBodyString() {
|
||||
return IoUtils.toString(this.getBody(), Constants.ENCODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
IoUtils.closeQuietly(this.responseStream);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hippo4j.common.toolkit.logtracing;
|
||||
|
||||
import org.slf4j.helpers.FormattingTuple;
|
||||
import org.slf4j.helpers.MessageFormatter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 日志KV打印框架
|
||||
* @author Rongzhen Yan
|
||||
*/
|
||||
public class LogMessage {
|
||||
|
||||
private Map<String, Object> kvs = new ConcurrentHashMap<>();
|
||||
private String msg = "";
|
||||
|
||||
private LogMessage() {
|
||||
}
|
||||
|
||||
public static LogMessage getInstance() {
|
||||
return new LogMessage();
|
||||
}
|
||||
|
||||
public LogMessage setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String msg(String msg, Object... args) {
|
||||
LogMessage l = new LogMessage();
|
||||
l.kvs = this.kvs;
|
||||
return l.setMsgString(msg, args);
|
||||
}
|
||||
|
||||
public LogMessage setMsg(String msg, Object... args) {
|
||||
FormattingTuple ft = MessageFormatter.arrayFormat(msg, args);
|
||||
this.msg = ft.getThrowable() == null ? ft.getMessage() : ft.getMessage() + "||_fmt_throw=" + ft.getThrowable();
|
||||
return this;
|
||||
}
|
||||
|
||||
public String setMsgString(String msg, Object... args) {
|
||||
FormattingTuple ft = MessageFormatter.arrayFormat(msg, args);
|
||||
this.msg = ft.getThrowable() == null ? ft.getMessage() : ft.getMessage() + "||_fmt_throw=" + ft.getThrowable();
|
||||
return toString();
|
||||
}
|
||||
|
||||
public LogMessage kv(String k, Object v) {
|
||||
this.kvs.put(k, v == null ? "" : v);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String kv2String(String k, Object v) {
|
||||
this.kvs.put(k, v == null ? "" : v);
|
||||
return toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (msg != null && !msg.isEmpty()) {
|
||||
sb.append("||_msg=").append(msg);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> kv : kvs.entrySet()) {
|
||||
sb.append("||" + kv.getKey() + "=").append(kv.getValue());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in new issue