solon-net-httputils
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-net-httputils</artifactId>
</dependency>
1、描述
网络扩展插件。提供基础的 http 调用。默认基于 HttpURLConnection 适配,当有 okhttp 引入时自动切换为 okhttp 适配。
- HttpURLConnection 适配时,为 40Kb 左右
- OkHttp 适配时,为 3.2 Mb 左右(项目里有 OkHttp 依赖时)
v3.0 后,同时做为 solon-test 和 nami-channel-http 的内部工具。
2、基本操作
- HEAD 请求
int code = HttpUtils.http("http://localhost:8080/hello").head();
- GET 请求
String body = HttpUtils.http("http://localhost:8080/hello").get();
//for Bean
Book book = HttpUtils.http("http://localhost:8080/book?bookId=1")
.getAs(Book.class);
- POST 请求
//x-www-form-urlencoded
String body = HttpUtils.http("http://localhost:8080/hello")
.data("name","world")
.post();
//form-data
String body = HttpUtils.http("http://localhost:8080/hello")
.data("name","world")
.post(true); // useMultipart
//form-data :: upload-file
String body = HttpUtils.http("http://localhost:8080/hello")
.data("name", new File("/data/demo.jpg"))
.post(true); // useMultipart
//body-json
String body = HttpUtils.http("http://localhost:8080/hello")
.bodyOfJson("{\"name\":\"world\"}")
.post();
//for Bean
Result body = HttpUtils.http("http://localhost:8080/book")
.bodyOfBean(book)
.postAs(Result.class);
//for Bean generic type
Result<User> body = HttpUtils.http("http://localhost:8080/book")
.bodyOfBean(book)
.postAs(new Result<User>(){}.getClass()); //通过临时类构建泛型(或别的方式)
- PUT 请求
String body = HttpUtils.http("http://localhost:8080/hello")
.data("name","world")
.put();
//for Bean
Result body = HttpUtils.http("http://localhost:8080/book")
.bodyOfBean(book)
.putAs(Result.class);
- PATCH 请求
String body = HttpUtils.http("http://localhost:8080/hello")
.data("name","world")
.patch();
//for Bean
Result body = HttpUtils.http("http://localhost:8080/book")
.bodyOfBean(book)
.patchAs(Result.class);
- DELETE 请求
String body = HttpUtils.http("http://localhost:8080/hello")
.data("name","world")
.delete();
//for Bean
Result body = HttpUtils.http("http://localhost:8080/book")
.bodyOfBean(book)
.deleteAs(Result.class);
3、高级操作
获取响应(用完要关闭)
try(HttpResponse resp = HttpUtils.http("http://localhost:8080/hello").data("name","world").exec("POST")) {
int code = resp.code();
String head = resp.header("Demo-Header");
String body = resp.bodyAsString();
}
配置序列化器。默认为 json,改为 fury;或者自己定义。
FuryBytesSerializer serializer = new FuryBytesSerializer();
Result body = HttpUtils.http("http://localhost:8080/book")
.serializer(serializer)
.bodyOfBean(book)
.postAs(Result.class);
定制扩展(统一添加头信息等...)
//注解模式
@Component
public class HttpExtensionImpl implements HttpExtension {
@Override
public void onInit(HttpUtils httpUtils, String url) {
httpUtils.header("TOKEN","xxxx");
}
}
//手动模式
HttpConfiguration.addExtension((httpUtils, url)->{
httpUtils.header("TOKEN","xxxx");
});
4、其它操作
基于服务名的调用示例:(内部是基于注册与发布服务)
public class App {
public static void maing(String[] args) {
Solon.start(App.class, args);
//通过服务名进行http请求
HttpUtils.http("HelloService","/hello").get();
HttpUtils.http("HelloService","/hello").data("name", "world").put();
HttpUtils.http("HelloService","/hello").bodyOfJson("{\"name\":\"world\"}").post();
}
}
顺带放了一个预热工具,让自己可以请求自己。从而达到简单预热效果:
public class App {
public static void maing(String[] args) {
Solon.start(App.class, args);
//用http请求自己进行预热
PreheatUtils.preheat("/healthz");
//用bean预热
HelloService service = Solon.context().getBean(HelloService.class);
service.hello();
}
}
5、Http 流式(文本流)获取操作(基于 solon-rx 接口返回)
- 获取以“行”为单位的文本流(比如 ndjson )
HttpUtils.http("http://localhost:8080/ndjson")
.execAsTextStream("GET")
.subscribe(new SimpleSubscriber<String>().doOnNext(line -> {
System.out.println(line);
}));
也可用于逐行读取网页
@Test
public void case1() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
HttpUtils.http("https://solon.noear.org/")
.execAsTextStream("GET")
.subscribe(new SimpleSubscriber<String>().doOnNext(line -> {
System.out.println(line);
}).doOnComplete(() -> {
latch.countDown();
}));
latch.await();
}
- 获取以“SSE”(Server Sent Event)为单位的文本流
Publisher<ServerSentEvent> publisher = HttpUtils.http("http://localhost:8080/sse")
.execAsEventStream("GET");
6、接口说明
public interface HttpUtils {
static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
/**
* 创建
*/
static HttpUtils http(String service, String path) {
String url = LoadBalanceUtils.getServer(null, service) + path;
return http(url);
}
/**
* 创建
*/
static HttpUtils http(String group, String service, String path) {
String url = LoadBalanceUtils.getServer(group, service) + path;
return http(url);
}
/**
* 创建
*/
static HttpUtils http(String url) {
return HttpConfiguration.getFactory().http(url);
}
/**
* 配置序列化器
*/
HttpUtils serializer(Serializer serializer);
/**
* 获取序列化器
*/
Serializer serializer();
/**
* 启用打印(专为 tester 服务)
*/
HttpUtils enablePrintln(boolean enable);
/**
* 代理配置
*/
HttpUtils proxy(Proxy proxy);
/**
* ssl
*/
HttpUtils ssl(HttpSslSupplier sslProvider);
/**
* 代理配置
*/
default HttpUtils proxy(String host, int port) {
return proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port)));
}
/**
* 超时配置
*/
default HttpUtils timeout(int timeoutSeconds) {
return timeout(HttpTimeout.of(timeoutSeconds));
}
/**
* 超时配置
*/
default HttpUtils timeout(int connectTimeoutSeconds, int writeTimeoutSeconds, int readTimeoutSeconds) {
return timeout(HttpTimeout.of(connectTimeoutSeconds, writeTimeoutSeconds, readTimeoutSeconds));
}
/**
* 超时配置
*/
HttpUtils timeout(HttpTimeout timeout);
/**
* 是否多部分配置
*/
HttpUtils multipart(boolean multipart);
/**
* 用户代理配置
*/
HttpUtils userAgent(String ua);
/**
* 编码配置
*/
HttpUtils charset(String charset);
/**
* 头配置
*/
HttpUtils headers(Map headers);
/**
* 头配置
*/
HttpUtils headers(Iterable<KeyValues<String>> headers);
/**
* 头配置(替换)
*/
HttpUtils header(String name, String value);
/**
* 头配置(添加)
*/
HttpUtils headerAdd(String name, String value);
/**
* Content-Type 头配置
*/
default HttpUtils contentType(String contentType) {
return header("Content-Type", contentType);
}
/**
* Accept 头配置
*/
default HttpUtils accept(String accept) {
return header("Accept", accept);
}
/**
* 小饼配置
*/
HttpUtils cookies(Map cookies);
/**
* 小饼配置
*/
HttpUtils cookies(Iterable<KeyValues<String>> cookies);
/**
* 小饼配置(替换)
*/
HttpUtils cookie(String name, String value);
/**
* 小饼配置(添加)
*/
HttpUtils cookieAdd(String name, String value);
/**
* 参数配置
*/
HttpUtils data(Map data);
/**
* 参数配置
*/
HttpUtils data(Iterable<KeyValues<String>> data);
/**
* 参数配置(替换)
*/
HttpUtils data(String name, String value);
/**
* 参数配置
*/
HttpUtils data(String name, String filename, InputStream inputStream, String contentType);
/**
* 参数配置
*/
HttpUtils data(String name, String filename, File file);
/**
* 参数配置
*/
default HttpUtils data(String name, File file) {
return data(name, file.getName(), file);
}
/**
* 主体配置
*/
default HttpUtils bodyOfTxt(String txt) {
return body(txt, MimeType.TEXT_PLAIN_VALUE);
}
/**
* 主体配置
*/
default HttpUtils bodyOfJson(String txt) {
return body(txt, MimeType.APPLICATION_JSON_VALUE);
}
/**
* 主体配置(由序列化器决定内容类型)
*/
HttpUtils bodyOfBean(Object obj) throws HttpException;
/**
* 主体配置
*/
HttpUtils body(String txt, String contentType);
/**
* 主体配置
*/
HttpUtils body(byte[] bytes, String contentType);
/**
* 主体配置
*/
default HttpUtils body(byte[] bytes) {
return body(bytes, null);
}
/**
* 主体配置
*/
HttpUtils body(InputStream raw, String contentType);
/**
* 主体配置
*/
default HttpUtils body(InputStream raw) {
return body(raw, null);
}
/**
* get 请求并返回 body
*/
String get() throws HttpException;
/**
* get 请求并返回 body
*/
<T> T getAs(Type type) throws HttpException;
/**
* post 请求并返回 body
*/
String post() throws HttpException;
/**
* post 请求并返回 body
*/
<T> T postAs(Type type) throws HttpException;
/**
* post 请求并返回 body
*/
default String post(boolean useMultipart) throws HttpException {
if (useMultipart) {
multipart(true);
}
return post();
}
/**
* post 请求并返回 body
*/
default <T> T postAs(Type type, boolean useMultipart) throws HttpException {
if (useMultipart) {
multipart(true);
}
return postAs(type);
}
/**
* put 请求并返回 body
*/
String put() throws HttpException;
/**
* put 请求并返回 body
*/
<T> T putAs(Type type) throws HttpException;
/**
* patch 请求并返回 body
*/
String patch() throws HttpException;
/**
* patch 请求并返回 body
*/
<T> T patchAs(Type type) throws HttpException;
/**
* delete 请求并返回 body
*/
String delete() throws HttpException;
/**
* delete 请求并返回 body
*/
<T> T deleteAs(Type type) throws HttpException;
/**
* options 请求并返回 body
*/
String options() throws HttpException;
/**
* head 请求并返回 code
*/
int head() throws HttpException;
//////
/**
* 执行请求并返回响应主体
*/
String execAsBody(String method) throws HttpException;
/**
* 执行请求并返回响应主体
*/
<T> T execAsBody(String method, Type type) throws HttpException;
/**
* 执行请求并返回代码
*/
int execAsCode(String method) throws HttpException;
/**
* 执行请求并返回文本行流
*/
Publisher<String> execAsLineStream(String method);
/**
* 执行请求并返回服务端推送事件流
*/
Publisher<ServerSentEvent> execAsSseStream(String method);
/**
* 执行请求并返回响应
*/
HttpResponse exec(String method) throws HttpException;
/**
* 异步执行请求
*/
CompletableFuture<HttpResponse> execAsync(String method);
/**
* 填充自己
*/
default HttpUtils fill(Consumer<HttpUtils> consumer) {
consumer.accept(this);
return this;
}
/////////////
/**
* url 编码
*/
static String urlEncode(String s) throws IOException {
return urlEncode(s, null);
}
/**
* url 编码
*/
static String urlEncode(String s, String charset) throws UnsupportedEncodingException {
if (charset == null) {
return URLEncoder.encode(s, Solon.encoding());
} else {
return URLEncoder.encode(s, charset);
}
}
/**
* map 转为 queryString
*/
static CharSequence toQueryString(Map<?, ?> map) throws IOException {
return toQueryString(map, null);
}
/**
* map 转为 queryString
*/
static CharSequence toQueryString(Map<?, ?> map, String charset) throws IOException {
StringBuilder buf = new StringBuilder();
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (buf.length() > 0) {
buf.append('&');
}
buf.append(urlEncode(entry.getKey().toString(), charset))
.append('=')
.append(urlEncode(entry.getValue().toString(), charset));
}
return buf.toString();
}
}