使用Java實(shí)現(xiàn)一個(gè)解析CURL腳本小工具
版本 | 時(shí)間 | 修改內(nèi)容 |
V1 | 2024.06.13 | 新建 |
V2 | 2024.06.28 | 更新body和請(qǐng)求類型篩選的正則表達(dá)式內(nèi)容,特殊換符和轉(zhuǎn)移符剔除 |
該工具可以將CURL腳本中的Header解析為KV Map結(jié)構(gòu);獲取URL路徑、請(qǐng)求類型;解析URL參數(shù)列表;解析Body請(qǐng)求體: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)原理很簡(jiǎ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
- 請(qǐng)求方法類型: 例如 POST、GET、DELETE、PUT...... 需要正則匹配
-X
--request
等標(biāo)識(shí)符 - Header請(qǐng)求頭:例如 Cookie、Token、Content-Type...... 需要正則匹配
-H
--header
等標(biāo)識(shí)符 - Body請(qǐng)求體:可以分為
form-data/-form
、data-raw
、data-urlencode
、-d
、--data
、kvbody
等。格式可能包含JSON、XML、文本、KV鍵值對(duì),二進(jìn)制流(暫不支持解析)等等。
具體實(shí)現(xiàn)
流程簡(jiǎn)圖:
類關(guān)系圖:
CurlParserUtil
Curl解析工具類:
public class CurlParserUtil { /** * 該方法是用來解析CURL的入口。 * * @param curl 輸入的CURL文本字符串 * @return 返回解析后生成的CURL實(shí)體對(duì)象 */ 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; /** * 請(qǐng)求方法類型 */ private Method method; /** * URL參數(shù) */ private Map<String, String> urlParams; /** * header參數(shù) */ private Map<String, String> headers; /** * 請(qǐng)求體 */ 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
請(qǐng)求類型解析:
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,沒有明確請(qǐng)求方法,默認(rèn)為 POST return CurlEntity.Method.POST; } else { // 沒有明確指定請(qǐng)求方法,默認(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請(qǐng)求體解析:
- form-data/-form
- data-urlencode
- data-raw
- default/-d/--data
格式可能包含JSON、XML、文本、KV鍵值對(duì),二進(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) { // 單引號(hào)包裹的數(shù)據(jù) bodyStr = defaultMatcher.group(1); } else if (defaultMatcher.group(2) != null) { // 雙引號(hào)包裹的數(shù)據(jù) bodyStr = defaultMatcher.group(2); } else { // 無引號(hào)的數(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]; // 檢測(cè)文件字段標(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()) { // 提取鍵值對(duì)字符串 String keyValueEncoded = urlencodeMatcher.group(1); // 分隔鍵和值 String[] keyValue = keyValueEncoded.split("=", 2); if (keyValue.length == 2) { String key = keyValue[0]; String value = keyValue[1]; // 對(duì)值進(jìn)行URL解碼 String decodedValue = URLDecoder.decode(value, StandardCharsets.UTF_8); // 存入數(shù)據(jù)到JSON對(duì)象 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|$)"); /** * 請(qǐng)求參數(shù)列表匹配 */ Pattern URL_PARAMS_PATTERN = Pattern.compile("(?:\\s|^)(?:'|\")?(https?://[^\\s'\"]+)(?:'|\")?(?:\\s|$)"); /** * HTTP請(qǐng)求方法匹配 */ //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請(qǐng)求方法匹配 */ Pattern DEFAULT_HTTP_METHOD_PATTERN = Pattern.compile(".*\\s(-d|--data|--data-binary)\\s.*"); /** * 請(qǐng)求頭匹配 */ Pattern CURL_HEADERS_PATTERN = Pattern.compile("(?:-H|--header)\\s+'(.*?:.*?)'"); /** * -d/--data 請(qǐng)求體匹配 */ 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 請(qǐng)求體匹配 */ Pattern HTTP_ROW_BODY_PATTERN = Pattern.compile("--data-raw '(.+?)'(?s)", Pattern.DOTALL); /** * --form 請(qǐng)求體匹配 */ Pattern HTTP_FROM_BODY_PATTERN = Pattern.compile("--form\\s+'(.*?)'|-F\\s+'(.*?)'"); /** * --data-urlencode 請(qǐng)求體匹配 */ 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工具的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot中swagger快速啟動(dòng)流程
這篇文章主要介紹了springboot中的swagger快速啟動(dòng)流程,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09Spring?boot?Jpa添加對(duì)象字段使用數(shù)據(jù)庫默認(rèn)值操作
這篇文章主要介紹了Spring?boot?Jpa添加對(duì)象字段使用數(shù)據(jù)庫默認(rèn)值操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Java 8中Collectors.toMap空指針異常源碼解析
這篇文章主要為大家介紹了Java 8中Collectors.toMap空指針異常源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08