跳转到内容

JQuick-Curl

JQuick-Curl 是一个轻量级 Java HTTP 客户端,它把 curl 的用法原样搬进了 Java:你用一条普通的 curl 命令描述请求,再把它当作一个普通的 Java 方法调用。命令由 ANTLR 语法解析,最终运行在带连接池的高性能传输层之上,因此不再需要手写请求构建代码。

⭐ 本项目已被 Awesome Java 收录。

  • 原生 curl 风格 API:用 curl 命令描述请求,用 Java 方法调用。
  • 注解与 XML 双配置:@JCurlCommand 直接写在接口上,或集中维护在 apis.xml。
  • 动态代理客户端:JCurlInvoker.createProxy(UserApi.class) 把接口变成可调用的客户端。
  • 变量替换:运行时解析 ${name} / #{name} 占位符。
  • 条件渲染:XML <if test="..."> 表达式为真时才渲染对应的 Header 或选项。
  • Cookie 支持:手动 Cookie、Netscape Cookie 文件(Cookie Jar)与 Set-Cookie 持久化。
  • HTTP 方法:GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS、TRACE。
  • 文件传输:-F 多部分上传,-o / --output 下载。
  • 批量执行:一次调用执行类中所有 @JCurlCommand 方法。
  • 超时 / 重试 / 重定向 / 连接池:通过 JQuickCurlConfig 统一配置。
  • 拦截器:全局追加认证、日志或请求预处理。
  • 代理与 SSL:支持 HTTP / SOCKS5 代理,-k / --insecure 跳过证书校验。
<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-curl</artifactId>
<version>2.6.0</version>
</dependency>

在接口方法上声明 curl 命令,创建代理后即可调用。

import com.github.paohaijiao.anno.JCurlCommand;
import com.github.paohaijiao.domain.req.JQuickCurlReq;
import com.github.paohaijiao.executor.JCurlInvoker;
public interface UserApi {
// GET 请求:返回响应体字符串 / GET request: returns the response body as a String
@JCurlCommand("curl -X GET https://httpbin.org/get")
String list(JQuickCurlReq request);
}
class GetDemo {
public static void main(String[] args) throws Exception {
UserApi api = JCurlInvoker.createProxy(UserApi.class);
String body = api.list(new JQuickCurlReq());
System.out.println(body);
}
}
import com.github.paohaijiao.anno.JCurlCommand;
import com.github.paohaijiao.domain.req.JQuickCurlReq;
import com.github.paohaijiao.executor.JCurlInvoker;
public interface OrderApi {
// POST 请求,携带 JSON 请求体 / POST with a JSON body
@JCurlCommand("curl -X POST https://httpbin.org/post " +
"-H 'Content-Type: application/json' " +
"-d '{\"sku\":\"A-1001\",\"count\":2}'")
String create(JQuickCurlReq request);
}
class PostDemo {
public static void main(String[] args) throws Exception {
OrderApi api = JCurlInvoker.createProxy(OrderApi.class);
System.out.println(api.create(new JQuickCurlReq()));
}
}

${...} 占位符在调用时从 JQuickCurlReq 中解析,凭据请放在变量里,不要硬编码。

public interface AuthApi {
@JCurlCommand("curl -X GET https://api.example.com/me -u ${user}:${password}")
String currentUser(JQuickCurlReq request);
}
JQuickCurlReq request = new JQuickCurlReq();
request.put("user", "ada");
request.put("password", System.getenv("API_PASSWORD"));
String me = JCurlInvoker.createProxy(AuthApi.class).currentUser(request);

-b 发送 Cookie,-c / --cookie-jar 把响应中的 Set-Cookie 按 Netscape 格式落盘,-b @file 在后续请求中复用。

import com.github.paohaijiao.anno.JCurlCommand;
import com.github.paohaijiao.domain.req.JQuickCurlReq;
import com.github.paohaijiao.executor.JCurlInvoker;
import com.github.paohaijiao.responseBody.JQuickCurlResponseBody;
public interface SessionApi {
// 第一步:登录并把所有 Set-Cookie 写入 cookies.txt
@JCurlCommand("curl -X POST https://httpbin.org/post " +
"-H 'Content-Type: application/json' " +
"-d '{\"user\":\"ada\",\"password\":\"secret\"}' " +
"-c ./cookies.txt")
JQuickCurlResponseBody login(JQuickCurlReq request);
// 第二步:后续请求自动带上持久化的 Cookie
@JCurlCommand("curl -X GET https://httpbin.org/cookies -b @./cookies.txt")
String profile(JQuickCurlReq request);
}

返回值声明为 JQuickCurlResponseBody 即可访问响应头与原始报文。

JQuickCurlResponseBody response = api.setCookie(new JQuickCurlReq());
String first = response.header("Set-Cookie"); // 第一个 Set-Cookie
List<String> all = response.headers("Set-Cookie"); // 全部 Set-Cookie(重定向可能返回多个)
String body = response.asString(); // 响应报文
public interface FileApi {
// 多部分上传:-F 'file=@/path/to/file'
@JCurlCommand("curl -X POST https://api.example.com/files -F 'file=@./report.pdf'")
String upload(JQuickCurlReq request);
// 表单字段与文件混合上传
@JCurlCommand("curl -X POST https://api.example.com/import " +
"-F 'userId=1001' -F 'file=@./report.pdf'")
String uploadWithForm(JQuickCurlReq request);
// 下载:-o / --output 把响应字节写入本地文件
@JCurlCommand("curl -X GET https://api.example.com/files/report.pdf --output './download/report.pdf'")
byte[] download(JQuickCurlReq request);
}

JQuickCurlBatchRunner 会扫描类中所有带 @JCurlCommand 的 public 方法并顺序执行。

import com.github.paohaijiao.anno.JCurlCommand;
import com.github.paohaijiao.responseBody.JQuickCurlResponseBody;
import com.github.paohaijiao.support.JQuickCurlBatchRunner;
import java.util.List;
public class BatchCommands {
@JCurlCommand("curl -X GET https://httpbin.org/get")
public String first() {
return null;
}
@JCurlCommand("curl -X GET https://httpbin.org/uuid")
public String second() {
return null;
}
}
class BatchDemo {
public static void main(String[] args) throws Exception {
JQuickCurlBatchRunner runner = new JQuickCurlBatchRunner();
List<JQuickCurlResponseBody> results =
runner.runCurlCommands(new BatchCommands(), JQuickCurlResponseBody.class);
results.forEach(r -> System.out.println(r.asString()));
}
}
import com.github.paohaijiao.config.JQuickCurlConfig;
import okhttp3.Interceptor;
import java.util.concurrent.TimeUnit;
JQuickCurlConfig.getInstance()
.connectTimeout(3, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.connectionPool(50, 5, TimeUnit.MINUTES)
.maxRetryCount(2)
.followRedirects(true);
// 全局拦截器:统一追加 Bearer Token
Interceptor auth = chain -> chain.proceed(
chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + System.getenv("API_TOKEN"))
.build());
JQuickCurlConfig.getInstance().addInterceptor(auth);

请求模板可以放在 XML 中,与 Java 代码彻底解耦。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE curls PUBLIC "-//PAOHAIJIAO//DTD API CURL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-curl.dtd">
<curls namespace="com.example.UserApi">
<curl name="getUser" returnClass="java.lang.String">
curl -X GET https://api.example.com/users/#{id} -b @./cookies.txt
</curl>
</curls>
import com.github.paohaijiao.xml.JQuickCurlXmlParseFactory;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
JQuickParseHandler parser = new JQuickCurlXmlParseFactory();
JQuickFactory factory = new JQuickXmlFactory(parser, "apis.xml");
UserApi api = factory.createApi(UserApi.class);
分类 支持的写法 用途
请求方法 -X <METHOD>、--request <METHOD> 指定 HTTP 方法
请求头 -H 'Name: value'、--header 'Name: value' 追加请求头
Cookie -b 'name=value'、--cookie、-b @file 发送 Cookie 或加载 Cookie 文件
Cookie Jar -c <file>、--cookie-jar <file> 把 Set-Cookie 持久化到文件
请求数据 -d、--data、--data-ascii、--data-binary、--data-raw 发送请求体
表单编码 --data-urlencode 'key=value' 发送 URL 编码表单
Basic 认证 -u 'user:password'、--user 生成 Basic Authorization 头
重定向 -L、--location、--max-redirs <N> 跟随重定向并限制次数
文件上传 -F 'file=@/path/to/file'、--form 'key=value' 多部分上传或表单字段
文件下载 -o './file'、--output './file' 把响应字节写入本地文件
代理 -x 'host:port'、--proxy、--socks5-hostname 使用 HTTP / SOCKS5 代理
协议与日志 --http2、-k、--insecure、-v、--verbose、-s、--silent HTTP/2、跳过证书校验、详细或静默输出