使用Java實(shí)現(xiàn)一個(gè)解析CURL腳本小工具
版本 | 時(shí)間 | 修改內(nèi)容 |
V1 | 2024.06.13 | 新建 |
V2 | 2024.06.28 | 更新body和請求類型篩選的正則表達(dá)式內(nèi)容,特殊換符和轉(zhuǎn)移符剔除 |
該工具可以將CURL腳本中的Header解析為KV Map結(jié)構(gòu);獲取URL路徑、請求類型;解析URL參數(shù)列表;解析Body請求體:Form表單、Raw Body、KV Body、XML/JSON/TEXT結(jié)構(gòu)體等。
使用示例
獲取一個(gè)http curl腳本:
curl --location --request POST 'https://cainiao-inc.com?param_1=value_1¶m_2=value_2' \
--header 'Cookie: USER_COOKIE' \
--header 'Content-Type: application/json' \
--data-raw '{
"appName": "link",
"apiId": "TEST_API",
"content": {
"address": "Cainiao Home",
"city": "Hangzhou"
}
}'執(zhí)行解析例子:

實(shí)現(xiàn)原理
實(shí)現(xiàn)原理很簡單:基于Java正則 + 責(zé)任鏈設(shè)計(jì)模式,按照Curl腳本的常見語法去匹配、解析即可~
按照Curl語法結(jié)構(gòu),可以將其拆分為 5 個(gè)部分:
- URL路徑:http://cainiao.com
- URL參數(shù)列表:?param_1=valie_1¶m_2=valie_2
- 請求方法類型: 例如 POST、GET、DELETE、PUT...... 需要正則匹配
-X--request等標(biāo)識符 - Header請求頭:例如 Cookie、Token、Content-Type...... 需要正則匹配
-H--header等標(biāo)識符 - Body請求體:可以分為
form-data/-form、data-raw、data-urlencode、-d、--data、kvbody等。格式可能包含JSON、XML、文本、KV鍵值對,二進(jìn)制流(暫不支持解析)等等。
具體實(shí)現(xiàn)
流程簡圖:

類關(guān)系圖:

CurlParserUtil
Curl解析工具類:
public class CurlParserUtil {
/**
* 該方法是用來解析CURL的入口。
*
* @param curl 輸入的CURL文本字符串
* @return 返回解析后生成的CURL實(shí)體對象
*/
public static CurlEntity parse(String curl) {
CurlEntity entity = CurlEntity.builder().build();
ICurlHandler<CurlEntity, String> handlerChain = CurlHandlerChain.init();
// 如需擴(kuò)展其他解析器,繼續(xù)往鏈表中add即可
handlerChain.next(new UrlPathHandler())
.next(new UrlParamsHandler())
.next(new HttpMethodHandler())
.next(new HeaderHandler())
.next(new HttpBodyHandler());
handlerChain.handle(entity, curl);
return entity;
}
}CurlEntity
解析后得到的Curl實(shí)體類(這里分了5個(gè)部分)
@Data
@Builder
public class CurlEntity {
/**
* URL路徑
*/
private String url;
/**
* 請求方法類型
*/
private Method method;
/**
* URL參數(shù)
*/
private Map<String, String> urlParams;
/**
* header參數(shù)
*/
private Map<String, String> headers;
/**
* 請求體
*/
private JSONObject body;
public enum Method {
GET,
POST,
PUT,
DELETE
}
}ICurlHandler
責(zé)任鏈鏈表結(jié)構(gòu)定義:
public interface ICurlHandler<R, S> {
ICurlHandler<CurlEntity, String> next(ICurlHandler<CurlEntity, String> handler);
void handle(CurlEntity entity, String curl);
}CurlHandlerChain
責(zé)任鏈載體:
public abstract class CurlHandlerChain implements ICurlHandler<CurlEntity, String> {
ICurlHandler<CurlEntity, String> next;
@Override
public ICurlHandler<CurlEntity, String> next(ICurlHandler<CurlEntity, String> handler) {
this.next = handler;
return this.next;
}
@Override
public abstract void handle(CurlEntity entity, String curl);
/**
* for subclass call
*/
protected void nextHandle(CurlEntity curlEntity, String curl) {
if (next != null) {
next.handle(curlEntity, curl);
}
}
protected void validate(String curl) {
if (StringUtils.isBlank(curl)) {
throw new IllegalArgumentException("Curl script is empty");
}
Matcher matcher = CURL_BASIC_STRUCTURE_PATTERN.matcher(curl);
if (!matcher.find()) {
throw new IllegalArgumentException("Curl script is invalid");
}
}
public static CurlHandlerChain init() {
return new CurlHandlerChain() {
@Override
public void handle(CurlEntity entity, String curl) {
this.validate(curl);
// 替換掉可能存在的轉(zhuǎn)譯(字符串中的空白字符,包括空格、換行符和制表符...)
curl = curl.replace("\\", "")
.replace("\n", "")
.replace("\t", "");
if (next != null) {
next.handle(entity, curl);
}
}
};
}
public void log(Object... logParams) {
// Write log for subclass extensions
}
}UrlPathHandler
URL路徑解析:
public class UrlPathHandler extends CurlHandlerChain {
@Override
public void handle(CurlEntity entity, String curl) {
String url = parseUrlPath(curl);
entity.setUrl(url);
this.log(url);
super.nextHandle(entity, curl);
}
/**
* 該方法用于解析URL路徑。
*
* @param curl 需要解析的URL,以字符串形式給出
* @return URL中的路徑部分。如果找不到,將返回null
*/
private String parseUrlPath(String curl) {
Matcher matcher = CurlPatternConstants.URL_PATH_PATTERN.matcher(curl);
if (matcher.find()) {
return matcher.group(1) != null ? matcher.group(1) : matcher.group(3);
}
return null;
}
@Override
public void log(Object... logParams) {
LogPrinter.info("UrlPathHandler execute: url={}", logParams);
}
}HttpMethodHandler
請求類型解析:
public class HttpMethodHandler extends CurlHandlerChain {
@Override
public void handle(CurlEntity entity, String curl) {
CurlEntity.Method method = parseMethod(curl);
entity.setMethod(method);
this.log(method);
super.nextHandle(entity, curl);
}
private CurlEntity.Method parseMethod(String curl) {
Matcher matcher = CurlPatternConstants.HTTP_METHOD_PATTERN.matcher(curl);
Matcher defaultMatcher = CurlPatternConstants.DEFAULT_HTTP_METHOD_PATTERN.matcher(curl);
if (matcher.find()) {
String method = matcher.group(1);
return CurlEntity.Method.valueOf(method.toUpperCase());
} else if (defaultMatcher.find()) {
// 如果命令中包含 -d 或 --data,沒有明確請求方法,默認(rèn)為 POST
return CurlEntity.Method.POST;
} else {
// 沒有明確指定請求方法,默認(rèn)為 GET
return CurlEntity.Method.GET;
}
}
@Override
public void log(Object... logParams) {
LogPrinter.info("HttpMethodHandler execute: method={}", logParams);
}
}UrlParamsHandler
URL參數(shù)列表解析:
public class UrlParamsHandler extends CurlHandlerChain {
@Override
public void handle(CurlEntity entity, String curl) {
String url = extractUrl(curl);
Map<String, String> urlParams = parseUrlParams(url);
entity.setUrlParams(urlParams);
this.log(urlParams);
super.nextHandle(entity, curl);
}
private String extractUrl(String curl) {
Matcher matcher = CurlPatternConstants.URL_PARAMS_PATTERN.matcher(curl);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
private Map<String, String> parseUrlParams(String url) {
if (StringUtils.isBlank(url)) {
return Collections.emptyMap();
}
Map<String, String> urlParams = new HashMap<>();
// 提取URL的查詢參數(shù)部分
String[] urlParts = url.split("\\?");
if (urlParts.length > 1) {
// 只處理存在查詢參數(shù)的情況
String query = urlParts[1];
// 解析查詢參數(shù)到Map
String[] pairs = query.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
if (idx != -1 && idx < pair.length() - 1) {
String key = pair.substring(0, idx);
String value = pair.substring(idx + 1);
urlParams.put(key, value);
} else {
// 存在無值的參數(shù)時(shí)
urlParams.put(pair, null);
}
}
}
return urlParams;
}
@Override
public void log(Object... logParams) {
LogPrinter.info("UrlParamsHandler execute: urlParams={}", logParams);
}
}HeaderHandler
Http Header解析:
public class HeaderHandler extends CurlHandlerChain{
@Override
public void handle(CurlEntity entity, String curl) {
Map<String, String> headers = parseHeaders(curl);
entity.setHeaders(headers);
this.log(headers);
super.nextHandle(entity, curl);
}
private Map<String, String> parseHeaders(String curl) {
if (StringUtils.isBlank(curl)) {
return Collections.emptyMap();
}
Matcher matcher = CurlPatternConstants.CURL_HEADERS_PATTERN.matcher(curl);
Map<String, String> headers = new HashMap<>();
while (matcher.find()) {
String header = matcher.group(1);
String[] headerKeyValue = header.split(":", 2);
if (headerKeyValue.length == 2) {
// 去除鍵和值的首尾空白字符
headers.put(headerKeyValue[0].trim(), headerKeyValue[1].trim());
}
}
return headers;
}
@Override
public void log(Object... logParams) {
LogPrinter.info("HeaderHandler execute: headers={}", logParams);
}
}HttpBodyHandler
Request Body請求體解析:
- form-data/-form
- data-urlencode
- data-raw
- default/-d/--data
格式可能包含JSON、XML、文本、KV鍵值對,二進(jìn)制流(暫不支持解析)等等。
public class HttpBodyHandler extends CurlHandlerChain {
@Override
public void handle(CurlEntity entity, String curl) {
JSONObject body = parseBody(curl);
entity.setBody(body);
this.log(body);
super.nextHandle(entity, curl);
}
private JSONObject parseBody(String curl) {
Matcher formMatcher = CurlPatternConstants.HTTP_FROM_BODY_PATTERN.matcher(curl);
if (formMatcher.find()) {
return parseFormBody(formMatcher);
}
Matcher urlencodeMatcher = CurlPatternConstants.HTTP_URLENCODE_BODY_PATTERN.matcher(curl);
if (urlencodeMatcher.find()) {
return parseUrlEncodeBody(urlencodeMatcher);
}
Matcher rawMatcher = CurlPatternConstants.HTTP_ROW_BODY_PATTERN.matcher(curl);
if (rawMatcher.find()) {
return parseRowBody(rawMatcher);
}
Matcher defaultMatcher = CurlPatternConstants.DEFAULT_HTTP_BODY_PATTERN.matcher(curl);
if (defaultMatcher.find()) {
return parseDefaultBody(defaultMatcher);
}
return new JSONObject();
}
private JSONObject parseDefaultBody(Matcher defaultMatcher) {
String bodyStr = "";
if (defaultMatcher.group(1) != null) {
// 單引號包裹的數(shù)據(jù)
bodyStr = defaultMatcher.group(1);
} else if (defaultMatcher.group(2) != null) {
// 雙引號包裹的數(shù)據(jù)
bodyStr = defaultMatcher.group(2);
} else {
// 無引號的數(shù)據(jù)
bodyStr = defaultMatcher.group(3);
}
// 判斷是否是json結(jié)構(gòu)
if (isJSON(bodyStr)) {
return JSONObject.parseObject(bodyStr);
}
// 特殊Case: username=test&password=secret
Matcher kvMatcher = CurlPatternConstants.DEFAULT_HTTP_BODY_PATTERN_KV.matcher(bodyStr);
return kvMatcher.matches() ? parseKVBody(bodyStr) : new JSONObject();
}
private JSONObject parseKVBody(String kvBodyStr) {
JSONObject json = new JSONObject();
String[] pairs = kvBodyStr.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
String key = URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8);
String value = URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8);
json.put(key, value);
}
return json;
}
private JSONObject parseFormBody(Matcher formMatcher) {
JSONObject formData = new JSONObject();
// 重置指針匹配的位置
formMatcher.reset();
while (formMatcher.find()) {
// 提取表單項(xiàng)
String formItem = formMatcher.group(1) != null ? formMatcher.group(1) : formMatcher.group(2);
// 分割鍵和值
String[] keyValue = formItem.split("=", 2);
if (keyValue.length == 2) {
String key = keyValue[0];
String value = keyValue[1];
// 檢測文件字段標(biāo)記
// PS: 理論上文件標(biāo)記字段不需要支持
if (value.startsWith("@")) {
// 只提取文件名,不讀取文件內(nèi)容
formData.put(key, value.substring(1));
} else {
// 放入表單數(shù)據(jù)
formData.put(key, value);
}
}
}
return formData;
}
private JSONObject parseUrlEncodeBody(Matcher urlencodeMatcher) {
JSONObject urlEncodeData = new JSONObject();
// 重置指針匹配的位置
urlencodeMatcher.reset();
while (urlencodeMatcher.find()) {
// 提取鍵值對字符串
String keyValueEncoded = urlencodeMatcher.group(1);
// 分隔鍵和值
String[] keyValue = keyValueEncoded.split("=", 2);
if (keyValue.length == 2) {
String key = keyValue[0];
String value = keyValue[1];
// 對值進(jìn)行URL解碼
String decodedValue = URLDecoder.decode(value, StandardCharsets.UTF_8);
// 存入數(shù)據(jù)到JSON對象
urlEncodeData.put(key, decodedValue);
}
}
return urlEncodeData;
}
private JSONObject parseRowBody(Matcher rowMatcher) {
String rawData = rowMatcher.group(1);
if (isXML(rawData)) {
// throw new IllegalArgumentException("Curl --data-raw content cant' be XML");
return xml2json(rawData);
}
try {
return JSON.parseObject(rawData);
} catch (Exception e) {
throw new IllegalArgumentException("Curl --data-raw content is not a valid JSON");
}
}
private boolean isJSON(String jsonStr) {
try {
JSONObject.parseObject(jsonStr);
return true;
} catch (Exception e) {
return false;
}
}
public static boolean isXML(String xmlStr) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(SecurityConstants.DDD, true);
factory.setFeature(SecurityConstants.EGE, false);
factory.setFeature(SecurityConstants.EPE, false);
DocumentBuilder builder = factory.newDocumentBuilder();
InputSource is = new InputSource(new StringReader(xmlStr));
builder.parse(is);
return true;
} catch (Exception e) {
return false;
}
}
private JSONObject xml2json(String xmlStr) {
try {
org.json.JSONObject orgJsonObj = XML.toJSONObject(xmlStr);
String jsonString = orgJsonObj.toString();
return JSON.parseObject(jsonString);
} catch (JSONException e) {
throw new LinkConsoleException("Curl --data-raw content xml2json error", e);
}
}
@Override
public void log(Object... logParams) {
LogPrinter.info("HttpBodyHandler execute: body={}", logParams);
}
}CurlPatternConstants
正則匹配常量定義:
public interface CurlPatternConstants {
/**
* CURL基本結(jié)構(gòu)校驗(yàn)
*/
Pattern CURL_BASIC_STRUCTURE_PATTERN = Pattern.compile("^curl (\\S+)");
/**
* URL路徑匹配
*/
Pattern URL_PATH_PATTERN =
Pattern.compile("(?:\\s|^)(?:'|\")?(https?://[^?\\s'\"]*)(?:\\?[^\\s'\"]*)?(?:'|\")?(?:\\s|$)");
/**
* 請求參數(shù)列表匹配
*/
Pattern URL_PARAMS_PATTERN = Pattern.compile("(?:\\s|^)(?:'|\")?(https?://[^\\s'\"]+)(?:'|\")?(?:\\s|$)");
/**
* HTTP請求方法匹配
*/
//Pattern HTTP_METHOD_PATTERN = Pattern.compile("(?:-X|--request)\\s+(\\S+)");
Pattern HTTP_METHOD_PATTERN = Pattern.compile("curl\\s+[^\\s]*\\s+(?:-X|--request)\\s+'?(GET|POST)'?");
/**
* 默認(rèn)HTTP請求方法匹配
*/
Pattern DEFAULT_HTTP_METHOD_PATTERN = Pattern.compile(".*\\s(-d|--data|--data-binary)\\s.*");
/**
* 請求頭匹配
*/
Pattern CURL_HEADERS_PATTERN = Pattern.compile("(?:-H|--header)\\s+'(.*?:.*?)'");
/**
* -d/--data 請求體匹配
*/
Pattern DEFAULT_HTTP_BODY_PATTERN = Pattern.compile("(?:--data|-d)\\s+(?:'([^']*)'|\"([^\"]*)\"|(\\S+))", Pattern.DOTALL);
Pattern DEFAULT_HTTP_BODY_PATTERN_KV = Pattern.compile("^([^=&]+=[^=&]+)(?:&[^=&]+=[^=&]+)*$", Pattern.DOTALL);
/**
* --data-raw 請求體匹配
*/
Pattern HTTP_ROW_BODY_PATTERN = Pattern.compile("--data-raw '(.+?)'(?s)", Pattern.DOTALL);
/**
* --form 請求體匹配
*/
Pattern HTTP_FROM_BODY_PATTERN = Pattern.compile("--form\\s+'(.*?)'|-F\\s+'(.*?)'");
/**
* --data-urlencode 請求體匹配
*/
Pattern HTTP_URLENCODE_BODY_PATTERN = Pattern.compile("--data-urlencode\\s+'(.*?)'");
}其他代碼
public class SecurityConstants {
public static String DDD = "http://apache.org/xml/features/disallow-doctype-decl";
public static String EGE = "http://xml.org/sax/features/external-general-entities";
public static String EPE = "http://xml.org/sax/features/external-parameter-entities";
public static String LED = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
}以上就是使用Java實(shí)現(xiàn)一個(gè)解析CURL腳本小工具的詳細(xì)內(nèi)容,更多關(guān)于Java實(shí)現(xiàn)解析CURL工具的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring?boot?Jpa添加對象字段使用數(shù)據(jù)庫默認(rèn)值操作
這篇文章主要介紹了Spring?boot?Jpa添加對象字段使用數(shù)據(jù)庫默認(rèn)值操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Java 8中Collectors.toMap空指針異常源碼解析
這篇文章主要為大家介紹了Java 8中Collectors.toMap空指針異常源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08

