feat(接口测试): 接口调试导入curl
This commit is contained in:
parent
3164f98e8b
commit
0b7826c0f9
|
@ -453,3 +453,7 @@ api_definition.status.continuous=Continuous
|
|||
|
||||
api_test_case.clear.api_change=Ignore the differences in this change
|
||||
api_test_case.ignore.api_change=Ignore all change differences
|
||||
|
||||
curl_script_is_empty=Curl script cannot be empty
|
||||
curl_script_is_invalid=Curl script is invalid
|
||||
curl_raw_content_is_invalid=Raw content is invalid
|
|
@ -421,3 +421,7 @@ api_definition.status.continuous=连调中
|
|||
|
||||
api_test_case.clear.api_change=忽略本次变更差异
|
||||
api_test_case.ignore.api_change=忽略全部变更差异
|
||||
|
||||
curl_script_is_empty=cURL脚本不能为空
|
||||
curl_script_is_invalid=cURL脚本格式不正确
|
||||
curl_raw_content_is_invalid=raw内容格式不正确
|
|
@ -421,3 +421,7 @@ api_definition.status.continuous=持續中
|
|||
|
||||
api_test_case.clear.api_change=忽略本次變更差異
|
||||
api_test_case.ignore.api_change=忽略全部變更差異
|
||||
|
||||
curl_script_is_empty=curl脚本不能爲空
|
||||
curl_script_is_invalid=curl脚本格式不正確
|
||||
curl_raw_content_is_invalid=raw内容格式不正確
|
|
@ -1,8 +1,11 @@
|
|||
package io.metersphere.api.controller.debug;
|
||||
|
||||
import io.metersphere.api.curl.domain.CurlEntity;
|
||||
import io.metersphere.api.curl.util.CurlParserUtil;
|
||||
import io.metersphere.api.domain.ApiDebug;
|
||||
import io.metersphere.api.dto.debug.*;
|
||||
import io.metersphere.api.dto.request.ApiEditPosRequest;
|
||||
import io.metersphere.api.dto.request.ApiImportCurlRequest;
|
||||
import io.metersphere.api.dto.request.ApiTransferRequest;
|
||||
import io.metersphere.api.service.ApiFileResourceService;
|
||||
import io.metersphere.api.service.debug.ApiDebugLogService;
|
||||
|
@ -120,4 +123,14 @@ public class ApiDebugController {
|
|||
public List<BaseTreeNode> options(@PathVariable String projectId) {
|
||||
return fileModuleService.getTree(projectId);
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/import-curl")
|
||||
@Operation(summary = "接口测试-接口调试-导入curl")
|
||||
@RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_IMPORT)
|
||||
public CurlEntity importCurl(@RequestBody ApiImportCurlRequest request) {
|
||||
CurlEntity parse = CurlParserUtil.parse(request.getCurl());
|
||||
return parse;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package io.metersphere.api.curl.constants;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
public interface CurlPatternConstants {
|
||||
|
||||
/**
|
||||
* CURL结构校验
|
||||
*/
|
||||
Pattern CURL_STRUCTURE_PATTERN = Pattern.compile("^curl");
|
||||
|
||||
/**
|
||||
* URL路径
|
||||
*/
|
||||
Pattern URL_PATH_PATTERN = Pattern.compile("(?:\\s|^)(?:'|\")?(https?://[^\\s'\"]*(?:\\?[^\\s'\"]*)?)(?:'|\")?(?:\\s|$)");
|
||||
|
||||
/**
|
||||
* URL_PARAMS请求参数
|
||||
*/
|
||||
Pattern URL_PARAMS_PATTERN = Pattern.compile("(?:\\s|^)(?:'|\")?(https?://[^\\s'\"]+)(?:'|\")?(?:\\s|$)");
|
||||
|
||||
/**
|
||||
* HTTP请求方法
|
||||
*/
|
||||
Pattern HTTP_METHOD_PATTERN = Pattern.compile("curl\\s+[^\\s]*\\s+(?:-X|--request)\\s+'?(GET|POST)'?");
|
||||
|
||||
/**
|
||||
* 默认HTTP请求方法
|
||||
*/
|
||||
Pattern DEFAULT_HTTP_METHOD_PATTERN = Pattern.compile(".*\\s(-d|--data|--data-binary)\\s.*");
|
||||
|
||||
/**
|
||||
* 请求头
|
||||
*/
|
||||
Pattern CURL_HEADERS_PATTERN = Pattern.compile("(?:-H|--header)\\s+(?:\"([^\"]*)\"|'([^']*)')");
|
||||
|
||||
/**
|
||||
* -u/--user 请求头
|
||||
*/
|
||||
Pattern CURL_USER_HEAD_PATTERN = Pattern.compile("-(u|user)\\s+(\\S+:\\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+'(.*?)'");
|
||||
|
||||
|
||||
/**
|
||||
* -x/--proxy 代理配置
|
||||
*/
|
||||
Pattern PROXY_PATTERN = Pattern.compile("(-x|--proxy)\\s+[\\S]+");
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package io.metersphere.api.curl.domain;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class CurlEntity {
|
||||
/**
|
||||
* URL路径
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 请求方法类型
|
||||
*/
|
||||
private Method method;
|
||||
|
||||
/**
|
||||
* URL参数
|
||||
*/
|
||||
private Map<String, String> queryParams;
|
||||
|
||||
/**
|
||||
* header参数
|
||||
*/
|
||||
private Map<String, String> headers;
|
||||
|
||||
/**
|
||||
* 请求体
|
||||
*/
|
||||
private JSONObject body;
|
||||
|
||||
public enum Method {
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
PATCH,
|
||||
OPTIONS,
|
||||
HEAD,
|
||||
CONNECT
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package io.metersphere.api.curl.handler;
|
||||
|
||||
import io.metersphere.api.curl.constants.CurlPatternConstants;
|
||||
import io.metersphere.api.curl.domain.CurlEntity;
|
||||
import io.metersphere.sdk.exception.MSException;
|
||||
import io.metersphere.sdk.util.Translator;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
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);
|
||||
|
||||
|
||||
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 MSException(Translator.get("curl_script_is_empty"));
|
||||
}
|
||||
|
||||
Matcher matcher = CurlPatternConstants.CURL_STRUCTURE_PATTERN.matcher(curl);
|
||||
if (!matcher.find()) {
|
||||
throw new MSException(Translator.get("curl_script_is_invalid"));
|
||||
}
|
||||
}
|
||||
|
||||
public static CurlHandlerChain init() {
|
||||
return new CurlHandlerChain() {
|
||||
@Override
|
||||
public void handle(CurlEntity entity, String curl) {
|
||||
this.validate(curl);
|
||||
|
||||
// 替换掉可能存在的转译(字符串中的空白字符,包括空格、换行符和制表符...)
|
||||
curl = curl.replace("\\", "")
|
||||
.replace("\n", "")
|
||||
.replace("\t", "");
|
||||
|
||||
Matcher matcher = CurlPatternConstants.PROXY_PATTERN.matcher(curl);
|
||||
if (matcher.find()) {
|
||||
curl = matcher.replaceAll("").trim();
|
||||
}
|
||||
|
||||
int compressedIndex = curl.indexOf("--compressed");
|
||||
if (compressedIndex != -1) {
|
||||
String beforeCompressed = curl.substring(4, compressedIndex);
|
||||
String afterCompressed = curl.substring(compressedIndex + "--compressed".length());
|
||||
curl = "curl" + afterCompressed + beforeCompressed;
|
||||
}
|
||||
|
||||
|
||||
if (next != null) {
|
||||
next.handle(entity, curl);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package io.metersphere.api.curl.handler;
|
||||
|
||||
import io.metersphere.api.curl.constants.CurlPatternConstants;
|
||||
import io.metersphere.api.curl.domain.CurlEntity;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
public class HeaderHandler extends CurlHandlerChain {
|
||||
|
||||
@Override
|
||||
public void handle(CurlEntity entity, String curl) {
|
||||
Map<String, String> headers = parseHeaders(curl);
|
||||
entity.setHeaders(headers);
|
||||
super.nextHandle(entity, curl);
|
||||
}
|
||||
|
||||
/**
|
||||
* header解析
|
||||
*
|
||||
* @param curl
|
||||
* @return
|
||||
*/
|
||||
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 = "";
|
||||
if (matcher.group(1) != null) {
|
||||
header = matcher.group(1);
|
||||
} else {
|
||||
header = matcher.group(2);
|
||||
}
|
||||
String[] headerKeyValue = header.split(":", 2);
|
||||
if (headerKeyValue.length == 2) {
|
||||
// 去除键和值的首尾空白字符
|
||||
headers.put(headerKeyValue[0].trim(), headerKeyValue[1].trim());
|
||||
}
|
||||
}
|
||||
|
||||
Matcher userMatcher = CurlPatternConstants.CURL_USER_HEAD_PATTERN.matcher(curl);
|
||||
if (userMatcher.find()) {
|
||||
String user = userMatcher.group(2);
|
||||
headers.put("Authorization", "Basic " + Base64.getEncoder().encodeToString(user.getBytes()));
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package io.metersphere.api.curl.handler;
|
||||
|
||||
import io.metersphere.api.curl.constants.CurlPatternConstants;
|
||||
import io.metersphere.api.curl.domain.CurlEntity;
|
||||
import io.metersphere.api.utils.JSONUtil;
|
||||
import io.metersphere.sdk.exception.MSException;
|
||||
import io.metersphere.sdk.util.Translator;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.json.XML;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.io.StringReader;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
public class HttpBodyHandler extends CurlHandlerChain {
|
||||
@Override
|
||||
public void handle(CurlEntity entity, String curl) {
|
||||
JSONObject body = parseBody(curl);
|
||||
entity.setBody(body);
|
||||
super.nextHandle(entity, curl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求体解析
|
||||
*
|
||||
* @param curl
|
||||
* @return
|
||||
*/
|
||||
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) {
|
||||
//单引号数据
|
||||
bodyStr = defaultMatcher.group(1);
|
||||
} else if (defaultMatcher.group(2) != null) {
|
||||
//双引号数据
|
||||
bodyStr = defaultMatcher.group(2);
|
||||
} else {
|
||||
//无引号数据
|
||||
bodyStr = defaultMatcher.group(3);
|
||||
}
|
||||
|
||||
if (isJSON(bodyStr)) {
|
||||
return JSONUtil.parseObject(bodyStr);
|
||||
}
|
||||
|
||||
//其他格式 a=b&c=d
|
||||
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()) {
|
||||
//提取表单
|
||||
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];
|
||||
|
||||
//文件属性
|
||||
if (value.startsWith("@")) {
|
||||
//获取文件名
|
||||
formData.put(key, value.substring(1));
|
||||
} else {
|
||||
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];
|
||||
String decodedValue = URLDecoder.decode(value, StandardCharsets.UTF_8);
|
||||
urlEncodeData.put(key, decodedValue);
|
||||
}
|
||||
}
|
||||
return urlEncodeData;
|
||||
}
|
||||
|
||||
private JSONObject parseRowBody(Matcher rowMatcher) {
|
||||
String rawData = rowMatcher.group(1);
|
||||
|
||||
if (isXML(rawData)) {
|
||||
return xml2json(rawData);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSONUtil.parseObject(rawData);
|
||||
} catch (Exception e) {
|
||||
throw new MSException(Translator.get("curl_raw_content_is_invalid"), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isJSON(String jsonStr) {
|
||||
try {
|
||||
JSONUtil.parseObject(jsonStr);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isXML(String xmlStr) {
|
||||
try {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setFeature("disallow-doctype-decl", false);
|
||||
factory.setFeature("external-general-entities", false);
|
||||
factory.setFeature("external-parameter-entities", 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 {
|
||||
JSONObject orgJsonObj = XML.toJSONObject(xmlStr);
|
||||
String jsonString = orgJsonObj.toString();
|
||||
return JSONUtil.parseObject(jsonString);
|
||||
} catch (JSONException e) {
|
||||
throw new MSException(Translator.get("curl_raw_content_is_invalid"), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package io.metersphere.api.curl.handler;
|
||||
|
||||
import io.metersphere.api.curl.constants.CurlPatternConstants;
|
||||
import io.metersphere.api.curl.domain.CurlEntity;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
public class HttpMethodHandler extends CurlHandlerChain {
|
||||
|
||||
@Override
|
||||
public void handle(CurlEntity entity, String curl) {
|
||||
CurlEntity.Method method = parseMethod(curl);
|
||||
entity.setMethod(method);
|
||||
super.nextHandle(entity, curl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求方法解析
|
||||
*
|
||||
* @param curl
|
||||
* @return
|
||||
*/
|
||||
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,没有明确请求方法,默认为 POST
|
||||
return CurlEntity.Method.POST;
|
||||
} else {
|
||||
//没有明确指定请求方法,默认为 GET
|
||||
return CurlEntity.Method.GET;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.api.curl.handler;
|
||||
|
||||
import io.metersphere.api.curl.domain.CurlEntity;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
public interface ICurlHandler<R, S> {
|
||||
|
||||
ICurlHandler<CurlEntity, String> next(ICurlHandler<CurlEntity, String> handler);
|
||||
|
||||
void handle(CurlEntity entity, String curl);
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package io.metersphere.api.curl.handler;
|
||||
|
||||
import io.metersphere.api.curl.constants.CurlPatternConstants;
|
||||
import io.metersphere.api.curl.domain.CurlEntity;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
public class QueryParamsHandler extends CurlHandlerChain {
|
||||
|
||||
@Override
|
||||
public void handle(CurlEntity entity, String curl) {
|
||||
String url = extractUrl(curl);
|
||||
Map<String, String> queryParams = parseQueryParams(url);
|
||||
entity.setQueryParams(queryParams);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* query参数解析
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
private Map<String, String> parseQueryParams(String url) {
|
||||
if (StringUtils.isBlank(url)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
String[] urlParts = url.split("\\?");
|
||||
if (urlParts.length > 1) {
|
||||
String query = urlParts[1];
|
||||
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);
|
||||
queryParams.put(key, value);
|
||||
} else {
|
||||
queryParams.put(pair, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return queryParams;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package io.metersphere.api.curl.handler;
|
||||
|
||||
import io.metersphere.api.curl.constants.CurlPatternConstants;
|
||||
import io.metersphere.api.curl.domain.CurlEntity;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
public class UrlPathHandler extends CurlHandlerChain {
|
||||
|
||||
@Override
|
||||
public void handle(CurlEntity entity, String curl) {
|
||||
String url = parseUrlPath(curl);
|
||||
entity.setUrl(url);
|
||||
super.nextHandle(entity, curl);
|
||||
}
|
||||
|
||||
/**
|
||||
* url路径解析
|
||||
*
|
||||
* @param curl
|
||||
* @return
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package io.metersphere.api.curl.util;
|
||||
|
||||
import io.metersphere.api.curl.domain.CurlEntity;
|
||||
import io.metersphere.api.curl.handler.*;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
public class CurlParserUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 解析crul 工具类
|
||||
* @param curl
|
||||
* @return
|
||||
*/
|
||||
public static CurlEntity parse(String curl) {
|
||||
CurlEntity entity = CurlEntity.builder().build();
|
||||
ICurlHandler<CurlEntity, String> handlerChain = CurlHandlerChain.init();
|
||||
|
||||
handlerChain.next(new UrlPathHandler())
|
||||
.next(new QueryParamsHandler())
|
||||
.next(new HttpMethodHandler())
|
||||
.next(new HeaderHandler())
|
||||
.next(new HttpBodyHandler());
|
||||
|
||||
handlerChain.handle(entity, curl);
|
||||
return entity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package io.metersphere.api.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Data
|
||||
public class ApiImportCurlRequest implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "curl字符串")
|
||||
private String curl;
|
||||
}
|
|
@ -12,6 +12,7 @@ import io.metersphere.api.dto.debug.*;
|
|||
import io.metersphere.api.dto.definition.ResponseBinaryBody;
|
||||
import io.metersphere.api.dto.definition.ResponseBody;
|
||||
import io.metersphere.api.dto.request.ApiEditPosRequest;
|
||||
import io.metersphere.api.dto.request.ApiImportCurlRequest;
|
||||
import io.metersphere.api.dto.request.ApiTransferRequest;
|
||||
import io.metersphere.api.dto.request.MsCommonElement;
|
||||
import io.metersphere.api.dto.request.http.MsHTTPElement;
|
||||
|
@ -94,6 +95,8 @@ public class ApiDebugControllerTests extends BaseTest {
|
|||
public static final String TRANSFER_OPTION = "transfer/options";
|
||||
public static final String TRANSFER = "transfer";
|
||||
|
||||
public static final String IMPORT_CURL = "import-curl";
|
||||
|
||||
@Resource
|
||||
private ApiDebugMapper apiDebugMapper;
|
||||
@Resource
|
||||
|
@ -782,4 +785,164 @@ public class ApiDebugControllerTests extends BaseTest {
|
|||
requestGetPermissionTest(PermissionConstants.PROJECT_API_DEBUG_DELETE, DEFAULT_DELETE, addApiDebug.getId());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Order(18)
|
||||
public void testImportCurl() throws Exception {
|
||||
ApiImportCurlRequest request = new ApiImportCurlRequest();
|
||||
//浏览器 curl 测试
|
||||
String curl = "curl 'https://127.0.0.1:8081/api/definition/page' \\\n" +
|
||||
" -H 'Accept: application/json, text/plain, */*' \\\n" +
|
||||
" -H 'Accept-Language: zh-CN' \\\n" +
|
||||
" -H 'CSRF-TOKEN: 1234454351313131431' \\\n" +
|
||||
" -H 'Connection: keep-alive' \\\n" +
|
||||
" -H 'Content-Type: application/json;charset=UTF-8' \\\n" +
|
||||
" -H 'ORGANIZATION: 100001' \\\n" +
|
||||
" -H 'Origin: http://127.0.0.1:8081' \\\n" +
|
||||
" -H 'PROJECT: 100001100001' \\\n" +
|
||||
" -H 'Referer: http://127.0.0.1:8081/' \\\n" +
|
||||
" -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36' \\\n" +
|
||||
" -H 'X-AUTH-TOKEN: 45fdgsrgdsg-2baf-40bc-98ba-5dsg15s1fg' \\\n" +
|
||||
" --data-raw '{\"current\":1,\"pageSize\":10,\"sort\":{},\"keyword\":\"\",\"combine\":{},\"searchMode\":\"AND\",\"projectId\":\"100001100001\",\"moduleIds\":[],\"protocols\":[\"HTTP\"],\"filter\":{\"status\":[],\"method\":[],\"priority\":[]},\"excludeIds\":[\"123456783242123\",\"\",\"\"]}' \\\n" +
|
||||
" --insecure";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
curl = "curl 'http://127.0.0.1:8081/project/get-member/option/100001100001?_t=1724293604633' \\\n" +
|
||||
" -H 'Accept: application/json, text/plain, */*' \\\n" +
|
||||
" -H 'Accept-Language: zh-CN' \\\n" +
|
||||
" -H 'CSRF-TOKEN: Q+DnK2GzMwG07cIVmaaeqSHZFeOk6RnorsyXL9eSCASECASDFJzHzwj60q9uW43o/yESDFSCESASDSFASDH3xXTiCXRxPXT6spuFIHjmYQ+AYbw=' \\\n" +
|
||||
" -H 'ORGANIZATION: 100001' \\\n" +
|
||||
" -H 'PROJECT: 1202136548611' \\\n" +
|
||||
" -H 'Proxy-Connection: keep-alive' \\\n" +
|
||||
" -H 'Referer: http://127.0.0.1:8081/' \\\n" +
|
||||
" -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36' \\\n" +
|
||||
" -H 'X-AUTH-TOKEN: 85de962d-2baf-40bc-98ba-9af2e6564d0b' \\\n" +
|
||||
" --insecure";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
//抓包格式测试
|
||||
//Charles工具 curl 测试
|
||||
curl = "curl \n" +
|
||||
"-H 'Host: 127.0.0.1:8081' \n" +
|
||||
"-H 'Accept: application/json, text/plain, */*' \n" +
|
||||
"-H 'CSRF-TOKEN: Q+DnK2GzMwG07cIVmaaeqSHZFeOk6RnorsyXL9eD9VxP3FEJzHzwj60q9uW43o/y0Exoa6kQub0sN0H3xXTiCXRxPXT6spuFIHjmYQ+AYbw=' \n" +
|
||||
"-H 'X-AUTH-TOKEN: 512dsfsfds255d-2baf-40bc-98ba-5dsg15s1fg' \n" +
|
||||
"-H 'PROJECT: 124548721548' \n" +
|
||||
"-H 'Accept-Language: zh-CN' \n" +
|
||||
"-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36' \n" +
|
||||
"-H 'ORGANIZATION: 100001' -H 'Referer: http://127.0.0.1:8081/' \n" +
|
||||
"--compressed 'http://127.0.0.1:8081/project/get/100001100001?_t=1724294013069'";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
//Fiddler工具 curl 测试
|
||||
curl = "curl -X POST 'http://example.com/api' -H 'Accept: application/json' -H 'User-Agent: Fiddler' -H 'Authorization: Bearer token_here'";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
//GET 请求 测试
|
||||
curl = "curl -X GET https://example.com";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
//POST 请求带数据 测试
|
||||
curl = "curl -X POST -d 'key1=value1&key2=value2' https://example.com/post";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
//json数据 测试
|
||||
curl = "curl -X POST -H 'Content-Type: application/json' -d '{\"key\":\"value\"}' https://example.com/post";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
//文件 测试
|
||||
curl = "curl -F 'file=@path/to/file' https://example.com/upload";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
//自定义头部和认证 测试
|
||||
curl = "curl -H 'Authorization: Bearer token' -H 'Accept: application/json' -u username:password https://example.com";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
//组合 测试
|
||||
curl = "curl -X POST \\\n" +
|
||||
"-u username:password \\\n" +
|
||||
"-H 'Content-Type: multipart/form-data' \\\n" +
|
||||
"-H 'Custom-Header: Value' \\\n" +
|
||||
"-F 'file=@/path/to/file' \\\n" +
|
||||
"-F 'param1=value1' \\\n" +
|
||||
"https://example.com/upload";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
curl = "curl -X GET \\\n" +
|
||||
"-H 'Authorization: Bearer YOUR_TOKEN' \\\n" +
|
||||
"-L \\\n" +
|
||||
"-v \\\n" +
|
||||
"https://example.com/resource";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
curl = "curl -X GET \\\n" +
|
||||
"-H 'Accept: application/json' \\\n" +
|
||||
"-x http://proxyserver:port \\\n" +
|
||||
"-i \\\n" +
|
||||
"https://example.com/api?param1=value1¶m2=value2";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
curl = "curl -X POST \\\n" +
|
||||
"-H 'Content-Type: application/json' \\\n" +
|
||||
"-d '{\"key1\": \"value1\", \"key2\": \"value2\"}' \\\n" +
|
||||
"--max-time 30 \\\n" +
|
||||
"--retry 3 \\\n" +
|
||||
"https://example.com/api";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
curl = "curl 'https://www.tapd.cn/2335412151/prong/iterations/card_view/123465456789431534?q=fsadfasjhkahkrhfdsasccasfsdf' \\\n" +
|
||||
" -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \\\n" +
|
||||
" -H 'Accept-Language: zh-CN,zh;q=0.9' \\\n" +
|
||||
" -H 'Cache-Control: max-age=0' \\\n" +
|
||||
" -H 'Connection: keep-alive' \\\n" +
|
||||
" -H 'Cookie: iter_card_status=; 66565258464512_55234234_iterations_card_view_close_status=0; 232564132154_55023423_/prong/iterations/index_remember_view=134247554678224278546; iter_card_status=; 412045464_5123433_iterations_card_view_close_status=0; left_iteration_list_token=20225424528746eda6e21g1dfgd51867891ef7cbb3a9; tui_filter_fields=%5B%13d1gd51r1gf23d1r%2C%22owner%22%2C%22dg13dr51ation_id2C%dsf22priority%22%5D; 112315sc39_5501533_/prong/tasks/index_remember_view=115501223315036973; 5dfsse933_11324fsd3001000025_story_create_template=1155041233424001000009; tapdsession=17174887199dc61sdfscaeerg229dcd5beb5c16d6062b32d6cesc68a61f51fb; __root_domain_v=.tapd.cn; _qddaz=QD.147917cscse0554; t_u=7ab057cd1f0c6casedfcasfr61d09eb29f94c12a82c73007d3e505f68411bdfgdrg156f1b984a7b98566b7bdsafs2937ccb1974809f3fsef3f828%7C1; iteration_view_type_cookie=card_view; fsefdcdcbug_create_template=1155049f12055242000010; new_worktable=search_filter; dsc-token=V0FahgEQeO8hNyzI; 5532133_11550434242340025_story_create_template=115504234234000009; iteration_card_tab_33242490=list; iteration_card_current_iteration_334235590=--; cloud_current_workspaceId=53429234; iteration_card_tab_324234dd=list; _t_uid=19732439; _t_crop=6049432436; tapd_div=101_2885; locale=zh_CN; iteration_card_current_iteration_5234234=1155042344512542342863' \\\n" +
|
||||
" -H 'Referer: https://www.tapd.cn/5324120234165/bugtrace/bugs/view?bug_id=1445248132543744315' \\\n" +
|
||||
" -H 'Sec-Fetch-Dest: document' \\\n" +
|
||||
" -H 'Sec-Fetch-Mode: navigate' \\\n" +
|
||||
" -H 'Sec-Fetch-Site: same-origin' \\\n" +
|
||||
" -H 'Sec-Fetch-User: ?1' \\\n" +
|
||||
" -H 'Upgrade-Insecure-Requests: 1' \\\n" +
|
||||
" -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36' \\\n" +
|
||||
" -H 'sec-ch-ua: \"Not)A;Brand\";v=\"99\", \"Google Chrome\";v=\"127\", \"Chromium\";v=\"127\"' \\\n" +
|
||||
" -H 'sec-ch-ua-mobile: ?0' \\\n" +
|
||||
" -H 'sec-ch-ua-platform: \"macOS\"'";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
|
||||
curl = "curl 'https://127.0.0.1:8081/api/definition/page' \\\n" +
|
||||
" -H 'Accept: application/json, text/plain, */*' \\\n" +
|
||||
" -H 'Accept-Language: zh-CN' \\\n" +
|
||||
" -H 'CSRF-TOKEN: 1234454351313131431' \\\n" +
|
||||
" -H 'Connection: keep-alive' \\\n" +
|
||||
" -H 'Content-Type: application/json;charset=UTF-8' \\\n" +
|
||||
" -H 'ORGANIZATION: 100001' \\\n" +
|
||||
" -H 'Origin: http://127.0.0.1:8081' \\\n" +
|
||||
" -H 'PROJECT: 100001100001' \\\n" +
|
||||
" -H 'Referer: http://127.0.0.1:8081/' \\\n" +
|
||||
" -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36' \\\n" +
|
||||
" -H 'X-AUTH-TOKEN: 45fdgsrgdsg-2baf-40bc-98ba-5dsg15s1fg' \\\n" +
|
||||
" --data-raw '{current:1,\"pageSize\":10,\"sort\":{},\"keyword\":\"\",\"combine\":{},\"searchMode\":\"AND\",\"projectId\":\"100001100001\",\"moduleIds\":[],\"protocols\":[\"HTTP\"],\"filter\":{\"status\":[],\"method\":[],\"priority\":[]},\"excludeIds\":[\"123456783242123\",\"\",\"\"]}' \\\n" +
|
||||
" --insecure";
|
||||
request.setCurl(curl);
|
||||
this.requestPost(IMPORT_CURL, request);
|
||||
|
||||
curl = "curl -X POST -H 'Content-Type: application/json' --data-urlencode '{\"key\":\"value\"}' https://example.com/post";
|
||||
request.setCurl(curl);
|
||||
this.requestPostWithOk(IMPORT_CURL, request);
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue