From 3bd7a1d049faa96e0f41ef388a7e804910640101 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Thu, 3 Dec 2020 16:28:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95):?= =?UTF-8?q?=20swagger=20=E5=AF=BC=E5=85=A5=E9=87=8D=E6=9E=84=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../request/sampler/MsHTTPSamplerProxy.java | 62 +--- .../io/metersphere/api/dto/scenario/Body.java | 75 ++++- .../api/parse/ApiImportAbstractParser.java | 100 ++++++ .../api/parse/ApiImportParser.java | 13 + .../api/parse/ApiImportParserFactory.java | 17 + .../api/parse/JmeterDocumentParser.java | 227 +++++++++++++ .../io/metersphere/api/parse/MsParser.java | 101 ++++++ .../metersphere/api/parse/PostmanParser.java | 184 +++++++++++ .../metersphere/api/parse/Swagger2Parser.java | 309 ++++++++++++++++++ .../api/service/APITestService.java | 6 +- .../api/definition/components/Run.vue | 30 +- .../definition/components/body/ApiBody.vue | 12 +- .../request/http/ApiHttpRequestForm.vue | 32 +- .../common/components/MsJsonCodeEdit.vue | 2 +- 14 files changed, 1075 insertions(+), 95 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java create mode 100644 backend/src/main/java/io/metersphere/api/parse/ApiImportParser.java create mode 100644 backend/src/main/java/io/metersphere/api/parse/ApiImportParserFactory.java create mode 100644 backend/src/main/java/io/metersphere/api/parse/JmeterDocumentParser.java create mode 100644 backend/src/main/java/io/metersphere/api/parse/MsParser.java create mode 100644 backend/src/main/java/io/metersphere/api/parse/PostmanParser.java create mode 100644 backend/src/main/java/io/metersphere/api/parse/Swagger2Parser.java diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java index 06706fcdd7..2bf62c351b 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java @@ -1,6 +1,5 @@ package io.metersphere.api.dto.definition.request.sampler; -import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONType; @@ -9,7 +8,6 @@ import io.metersphere.api.dto.definition.request.dns.MsDNSCacheManager; import io.metersphere.api.dto.scenario.Body; import io.metersphere.api.dto.scenario.KeyValue; import io.metersphere.api.dto.scenario.environment.EnvironmentConfig; -import io.metersphere.api.dto.scenario.request.BodyFile; import io.metersphere.api.service.ApiTestEnvironmentService; import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs; import io.metersphere.commons.utils.CommonBeanFactory; @@ -23,16 +21,13 @@ import org.apache.jmeter.protocol.http.control.Header; import org.apache.jmeter.protocol.http.control.HeaderManager; import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; import org.apache.jmeter.protocol.http.util.HTTPArgument; -import org.apache.jmeter.protocol.http.util.HTTPFileArg; import org.apache.jmeter.save.SaveService; import org.apache.jmeter.testelement.TestElement; import org.apache.jorphan.collections.HashTree; import java.net.URL; import java.net.URLDecoder; -import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; @Data @EqualsAndHashCode(callSuper = true) @@ -142,39 +137,11 @@ public class MsHTTPSamplerProxy extends MsTestElement { if (CollectionUtils.isNotEmpty(this.getArguments())) { sampler.setArguments(httpArguments(this.getArguments())); } + // 请求体 if (!StringUtils.equals(this.getMethod(), "GET")) { - List body = new ArrayList<>(); - if (this.getBody().isKV() || this.getBody().isBinary()) { - body = this.getBody().getKvs().stream().filter(KeyValue::isValid).collect(Collectors.toList()); - HTTPFileArg[] httpFileArgs = httpFileArgs(); - // 文件上传 - if (httpFileArgs.length > 0) { - sampler.setHTTPFiles(httpFileArgs()); - sampler.setDoMultipart(true); - } - } else if (this.getBody().isJson()) { - KeyValue keyValue = new KeyValue("", JSON.toJSONString(this.getBody().getJson())); - keyValue.setEnable(true); - keyValue.setEncode(false); - body.add(keyValue); - } else { - if (StringUtils.isNotBlank(this.getBody().getRaw())) { - sampler.setPostBodyRaw(true); - KeyValue keyValue = new KeyValue("", this.getBody().getRaw()); - keyValue.setEnable(true); - keyValue.setEncode(false); - body.add(keyValue); - } - if (StringUtils.isNotBlank(this.getBody().getXml())) { - sampler.setPostBodyRaw(true); - KeyValue keyValue = new KeyValue("", this.getBody().getXml()); - keyValue.setEnable(true); - keyValue.setEncode(false); - body.add(keyValue); - } - } - sampler.setArguments(httpArguments(body)); + List bodyParams = this.body.getBodyParams(sampler, this.getId()); + sampler.setArguments(httpArguments(bodyParams)); } final HashTree httpSamplerTree = tree.add(sampler); @@ -225,29 +192,6 @@ public class MsHTTPSamplerProxy extends MsTestElement { return arguments; } - private void setFileArg(List list, List files, KeyValue keyValue) { - final String BODY_FILE_DIR = "/opt/metersphere/data/body"; - if (files != null) { - files.forEach(file -> { - String paramName = keyValue.getName() == null ? this.getId() : keyValue.getName(); - String path = BODY_FILE_DIR + '/' + file.getId() + '_' + file.getName(); - String mimetype = keyValue.getContentType(); - list.add(new HTTPFileArg(path, paramName, mimetype)); - }); - } - } - - private HTTPFileArg[] httpFileArgs() { - List list = new ArrayList<>(); - this.getBody().getKvs().stream().filter(KeyValue::isFile).filter(KeyValue::isEnable).forEach(keyValue -> { - setFileArg(list, keyValue.getFiles(), keyValue); - }); - this.getBody().getBinary().stream().filter(KeyValue::isFile).filter(KeyValue::isEnable).forEach(keyValue -> { - setFileArg(list, keyValue.getFiles(), keyValue); - }); - return list.toArray(new HTTPFileArg[0]); - } - public void setHeader(HashTree tree) { HeaderManager headerManager = new HeaderManager(); headerManager.setEnabled(true); diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java b/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java index cbf9a73ea3..bd03c0d8cc 100644 --- a/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java @@ -1,27 +1,32 @@ package io.metersphere.api.dto.scenario; +import io.metersphere.api.dto.scenario.request.BodyFile; import lombok.Data; import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Data public class Body { private String type; private String raw; private String format; - private List fromUrlencoded; private List kvs; private List binary; private Object json; private String xml; - private final static String KV = "KeyValue"; - private final static String FORM_DATA = "Form Data"; - private final static String RAW = "Raw"; - private final static String BINARY = "BINARY"; - private final static String JSON = "JSON"; - private final static String XML = "XML"; + public final static String KV = "KeyValue"; + public final static String FORM_DATA = "Form Data"; + public final static String WWW_FROM = "WWW_FORM"; + public final static String RAW = "Raw"; + public final static String BINARY = "BINARY"; + public final static String JSON = "JSON"; + public final static String XML = "XML"; public boolean isValid() { if (this.isKV()) { @@ -32,7 +37,54 @@ public class Body { } public boolean isKV() { - return StringUtils.equals(type, KV); + if (StringUtils.equals(type, FORM_DATA) || StringUtils.equals(type, WWW_FROM)) { + return true; + } else return false; + } + + public List getBodyParams(HTTPSamplerProxy sampler, String requestId) { + List body = new ArrayList<>(); + if (this.isKV() || this.isBinary()) { + body = this.getKvs().stream().filter(KeyValue::isValid).collect(Collectors.toList()); + HTTPFileArg[] httpFileArgs = httpFileArgs(requestId); + // 文件上传 + if (httpFileArgs.length > 0) { + sampler.setHTTPFiles(httpFileArgs(requestId)); + sampler.setDoMultipart(true); + } + } else { + if (!this.isJson()) { + sampler.setPostBodyRaw(true); + } + KeyValue keyValue = new KeyValue("", this.getRaw()); + keyValue.setEnable(true); + keyValue.setEncode(false); + body.add(keyValue); + } + return body; + } + + private HTTPFileArg[] httpFileArgs(String requestId) { + List list = new ArrayList<>(); + this.getKvs().stream().filter(KeyValue::isFile).filter(KeyValue::isEnable).forEach(keyValue -> { + setFileArg(list, keyValue.getFiles(), keyValue, requestId); + }); + this.getBinary().stream().filter(KeyValue::isFile).filter(KeyValue::isEnable).forEach(keyValue -> { + setFileArg(list, keyValue.getFiles(), keyValue, requestId); + }); + return list.toArray(new HTTPFileArg[0]); + } + + private void setFileArg(List list, List files, KeyValue keyValue, String requestId) { + final String BODY_FILE_DIR = "/opt/metersphere/data/body"; + if (files != null) { + files.forEach(file -> { + String paramName = keyValue.getName() == null ? requestId : keyValue.getName(); + String path = BODY_FILE_DIR + '/' + file.getId() + '_' + file.getName(); + String mimetype = keyValue.getContentType(); + list.add(new HTTPFileArg(path, paramName, mimetype)); + }); + } } public boolean isBinary() { @@ -47,4 +99,11 @@ public class Body { return StringUtils.equals(type, XML); } + public boolean isWwwFROM() { + return StringUtils.equals(type, WWW_FROM); + } + + public boolean isFromData() { + return StringUtils.equals(type, FORM_DATA); + } } diff --git a/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java b/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java new file mode 100644 index 0000000000..f8f19bc121 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java @@ -0,0 +1,100 @@ +package io.metersphere.api.parse; + +import io.metersphere.api.dto.ApiTestImportRequest; +import io.metersphere.api.dto.scenario.KeyValue; +import io.metersphere.api.dto.scenario.Scenario; +import io.metersphere.api.dto.scenario.request.HttpRequest; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.LogUtil; +import org.apache.commons.lang3.StringUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public abstract class ApiImportAbstractParser implements ApiImportParser { + + protected String getApiTestStr(InputStream source) { + StringBuilder testStr = null; + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(source, StandardCharsets.UTF_8))) { + testStr = new StringBuilder(); + String inputStr; + while ((inputStr = bufferedReader.readLine()) != null) { + testStr.append(inputStr); + } + } catch (Exception e) { + MSException.throwException(e.getMessage()); + LogUtil.error(e.getMessage(), e); + } finally { + try { + source.close(); + } catch (IOException e) { + MSException.throwException(e.getMessage()); + LogUtil.error(e.getMessage(), e); + } + } + return testStr.toString(); + } + + protected void setScenarioByRequest(Scenario scenario, ApiTestImportRequest request) { + if (request.getUseEnvironment()) { + scenario.setEnvironmentId(request.getEnvironmentId()); + } + } + + protected void addContentType(HttpRequest request, String contentType) { +// addHeader(request, "Content-Type", contentType); + } + + protected void addCookie(List headers, String key, String value) { + addCookie(headers, key, value, ""); + } + + protected void addCookie(List headers, String key, String value, String description) { + boolean hasCookie = false; + for (KeyValue header : headers) { + if (StringUtils.equalsIgnoreCase("Cookie", header.getName())) { + hasCookie = true; + String cookies = Optional.ofNullable(header.getValue()).orElse(""); + header.setValue(cookies + key + "=" + value + ";"); + } + } + if (!hasCookie) { + addHeader(headers, "Cookie", key + "=" + value + ";", description); + } + } + + protected void addHeader(List headers, String key, String value) { + addHeader(headers, key, value, ""); + } + + protected void addHeader(List headers, String key, String value, String description) { + boolean hasContentType = false; + for (KeyValue header : headers) { + if (StringUtils.equalsIgnoreCase(header.getName(), key)) { + hasContentType = true; + } + } + if (!hasContentType) { + headers.add(new KeyValue(key, value, description)); + } + } +// protected void addHeader(HttpRequest request, String key, String value) { +// List headers = Optional.ofNullable(request.getHeaders()).orElse(new ArrayList<>()); +// boolean hasContentType = false; +// for (KeyValue header : headers) { +// if (StringUtils.equalsIgnoreCase(header.getName(), key)) { +// hasContentType = true; +// } +// } +// if (!hasContentType) { +// headers.add(new KeyValue(key, value)); +// } +// request.setHeaders(headers); +// } +} diff --git a/backend/src/main/java/io/metersphere/api/parse/ApiImportParser.java b/backend/src/main/java/io/metersphere/api/parse/ApiImportParser.java new file mode 100644 index 0000000000..6173f71c77 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/parse/ApiImportParser.java @@ -0,0 +1,13 @@ +package io.metersphere.api.parse; + +import io.metersphere.api.dto.ApiTestImportRequest; +import io.metersphere.api.dto.definition.parse.ApiDefinitionImport; +import io.metersphere.api.dto.parse.ApiImport; + +import java.io.InputStream; + +public interface ApiImportParser { + ApiImport parse(InputStream source, ApiTestImportRequest request); + ApiDefinitionImport parseApi(InputStream source, ApiTestImportRequest request); + +} diff --git a/backend/src/main/java/io/metersphere/api/parse/ApiImportParserFactory.java b/backend/src/main/java/io/metersphere/api/parse/ApiImportParserFactory.java new file mode 100644 index 0000000000..ee41450068 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/parse/ApiImportParserFactory.java @@ -0,0 +1,17 @@ +package io.metersphere.api.parse; + +import io.metersphere.commons.constants.ApiImportPlatform; +import org.apache.commons.lang3.StringUtils; + +public class ApiImportParserFactory { + public static ApiImportParser getApiImportParser(String platform) { + if (StringUtils.equals(ApiImportPlatform.Metersphere.name(), platform)) { + return new MsParser(); + } else if (StringUtils.equals(ApiImportPlatform.Postman.name(), platform)) { + return new PostmanParser(); + } else if (StringUtils.equals(ApiImportPlatform.Swagger2.name(), platform)) { + return new Swagger2Parser(); + } + return null; + } +} diff --git a/backend/src/main/java/io/metersphere/api/parse/JmeterDocumentParser.java b/backend/src/main/java/io/metersphere/api/parse/JmeterDocumentParser.java new file mode 100644 index 0000000000..4acf7f5690 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/parse/JmeterDocumentParser.java @@ -0,0 +1,227 @@ +package io.metersphere.api.parse; + +import io.metersphere.commons.utils.LogUtil; +import io.metersphere.commons.utils.ScriptEngineUtils; +import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayInputStream; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +public class JmeterDocumentParser { + private final static String HASH_TREE_ELEMENT = "hashTree"; + private final static String STRING_PROP = "stringProp"; + private final static String ARGUMENTS = "Arguments"; + private final static String COLLECTION_PROP = "collectionProp"; + private final static String HTTP_SAMPLER_PROXY = "MsHTTPSamplerProxy"; + private final static String ELEMENT_PROP = "elementProp"; + + public static byte[] parse(byte[] source) { + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try ( + ByteArrayInputStream byteStream = new ByteArrayInputStream(source) + ) { + InputSource inputSource = new InputSource(byteStream); + DocumentBuilder docBuilder = factory.newDocumentBuilder(); + final Document document = docBuilder.parse(inputSource); + final Element jmeterTestPlan = document.getDocumentElement(); + + NodeList childNodes = jmeterTestPlan.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node instanceof Element) { + Element ele = (Element) node; + parseHashTree(ele); + } + } + return documentToBytes(document); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + return source; + } + } + + private static byte[] documentToBytes(Document document) throws TransformerException { + DOMSource domSource = new DOMSource(document); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.transform(domSource, result); + return writer.toString().getBytes(); + } + + private static void parseHashTree(Element hashTree) { + if (invalid(hashTree)) { + return; + } + + if (hashTree.getChildNodes().getLength() > 0) { + final NodeList childNodes = hashTree.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node instanceof Element) { + Element ele = (Element) node; + if (invalid(ele)) { + continue; + } + + if (nodeNameEquals(ele, HASH_TREE_ELEMENT)) { + parseHashTree(ele); + } else if (nodeNameEquals(ele, ARGUMENTS)) { + processArguments(ele); + } else if (nodeNameEquals(ele, HTTP_SAMPLER_PROXY)) { + processHttpSamplerProxy(ele); + } + } + } + } + } + + private static void processHttpSamplerProxy(Element ele) { + if (invalid(ele)) { + return; + } + NodeList childNodes = ele.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.item(i); + if (!(item instanceof Element)) { + continue; + } + Element element = (Element) item; + if (nodeNameEquals(element, ELEMENT_PROP) && "HTTPsampler.Arguments".equals(element.getAttribute("name"))) { + processArguments(element); + } else if ("HTTPSampler.path".equals(element.getAttribute("name"))) { + processStringProp(element); + } + } + } + + private static void processArguments(Element ele) { + if (invalid(ele)) { + return; + } + NodeList childNodes = ele.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.item(i); + if (!(item instanceof Element)) { + continue; + } + Element element = (Element) item; + if (nodeNameEquals(item, COLLECTION_PROP) && "Arguments.arguments".equals(element.getAttribute("name"))) { + NodeList elementProps = item.getChildNodes(); + for (int j = 0; j < elementProps.getLength(); j++) { + Node elementProp = elementProps.item(j); + if (!(elementProp instanceof Element)) { + continue; + } + NodeList stringProps = elementProp.getChildNodes(); + for (int k = 0; k < stringProps.getLength(); k++) { + Node stringProp = stringProps.item(k); + if (!(stringProp instanceof Element)) { + continue; + } + processStringProp((Element) stringProp); + } + } + } + } + } + + private static void processStringProp(Element ele) { + String name = ele.getAttribute("name"); + switch (name) { + case "HTTPSampler.path": + String path = ele.getTextContent(); + Map parser = parserUrl(path); + String url = parser.get("URL"); + String params = parser.keySet().stream().filter(k -> !"URL".equals(k)).reduce("?", (u, k) -> { + String v = parser.get(k); + if (!StringUtils.equals("?", u)) { + u += "&"; + } + u += k + "=" + ScriptEngineUtils.calculate(v); + return u; + }); + ele.setTextContent(url + ((params != null && !params.equals("?")) ? params : "")); + break; + case "Argument.value": + String textContent = ele.getTextContent(); + if (StringUtils.startsWith(textContent, "@")) { + ele.setTextContent(ScriptEngineUtils.calculate(textContent)); + } + break; + default: + break; + } + } + + private static boolean nodeNameEquals(Node node, String desiredName) { + return desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName()); + } + + private static boolean invalid(Element ele) { + return !StringUtils.isBlank(ele.getAttribute("enabled")) && !Boolean.parseBoolean(ele.getAttribute("enabled")); + } + + private static Map parserUrl(String url) { +// 传递的URL参数 + Map strUrlParas = new HashMap<>(); + + String strUrl; + String strUrlParams; + + +// 解析访问地址 + if (url.contains("?")) { + String[] strUrlPatten = url.split("\\?"); + strUrl = strUrlPatten[0]; + strUrlParams = strUrlPatten[1]; + + } else { + strUrl = url; + strUrlParams = url; + } + + strUrlParas.put("URL", strUrl); +// 解析参数 + String[] params = null; + + if (strUrlParams.contains("&")) { + params = strUrlParams.split("&"); + } else { + params = new String[]{strUrlParams}; + } + +// 保存参数到参数容器 + for (String p : params) { + if (p.contains("=")) { + String[] param = p.split("="); + if (param.length == 1) { + strUrlParas.put(param[0], ""); + } else { + + String key = param[0]; + String value = param[1]; + + strUrlParas.put(key, value); + } + } + } + return strUrlParas; + } +} diff --git a/backend/src/main/java/io/metersphere/api/parse/MsParser.java b/backend/src/main/java/io/metersphere/api/parse/MsParser.java new file mode 100644 index 0000000000..c9d165e247 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/parse/MsParser.java @@ -0,0 +1,101 @@ +package io.metersphere.api.parse; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.parser.Feature; +import io.metersphere.api.dto.ApiTestImportRequest; +import io.metersphere.api.dto.definition.parse.ApiDefinitionImport; +import io.metersphere.api.dto.parse.ApiImport; +import io.metersphere.api.dto.scenario.request.RequestType; +import io.metersphere.commons.constants.MsRequestBodyType; +import org.apache.commons.lang3.StringUtils; + +import java.io.InputStream; + +public class MsParser extends ApiImportAbstractParser { + + @Override + public ApiImport parse(InputStream source, ApiTestImportRequest request) { + String testStr = getApiTestStr(source); + ApiImport apiImport = JSON.parseObject(parsePluginFormat(testStr), ApiImport.class); + apiImport.getScenarios().forEach(scenario -> setScenarioByRequest(scenario, request)); + return apiImport; + } + + @Override + public ApiDefinitionImport parseApi(InputStream source, ApiTestImportRequest request) { + String testStr = getApiTestStr(source); + ApiDefinitionImport apiImport = JSON.parseObject(testStr, ApiDefinitionImport.class); + return apiImport; + } + + private String parsePluginFormat(String testStr) { + JSONObject testObject = JSONObject.parseObject(testStr, Feature.OrderedField); + if (testObject.get("scenarios") != null) { + return testStr; + } else { + //插件格式 + JSONArray scenarios = new JSONArray(); + testObject.keySet().forEach(scenarioName -> { + JSONObject scenario = new JSONObject(); + scenario.put("name", scenarioName); + JSONArray requestsObjects = new JSONArray(); + JSONObject requestsObject = testObject.getJSONObject(scenarioName); + requestsObject.keySet().forEach(requestName -> { + JSONObject requestObject = new JSONObject(true); + JSONObject requestTmpObject = requestsObject.getJSONObject(requestName); + //排序,确保type在第一个,否则转换失败 + if (StringUtils.isBlank(requestTmpObject.getString("type"))) { + requestObject.put("type", RequestType.HTTP); + } + + requestTmpObject.keySet().forEach(key -> requestObject.put(key, requestTmpObject.get(key))); + requestObject.put("name", requestName); + parseBody(requestObject); + requestsObjects.add(requestObject); + }); + scenario.put("requests", requestsObjects); + scenarios.add(scenario); + }); + JSONObject result = new JSONObject(); + result.put("scenarios", scenarios); + return result.toJSONString(); + } + } + + private void parseBody(JSONObject requestObject) { + if (requestObject.containsKey("body")) { + Object body = requestObject.get("body"); + if (body instanceof JSONArray) { + JSONArray bodies = requestObject.getJSONArray("body"); + if (bodies != null) { + StringBuilder bodyStr = new StringBuilder(); + for (int i = 0; i < bodies.size(); i++) { + String tmp = bodies.getString(i); + bodyStr.append(tmp); + } + JSONObject bodyObject = new JSONObject(); + bodyObject.put("raw", bodyStr); + bodyObject.put("type", MsRequestBodyType.RAW.value()); + requestObject.put("body", bodyObject); + } + } else if (body instanceof JSONObject) { + JSONObject bodyObj = requestObject.getJSONObject("body"); + if (bodyObj != null) { + JSONArray kvs = new JSONArray(); + bodyObj.keySet().forEach(key -> { + JSONObject kv = new JSONObject(); + kv.put("name", key); + kv.put("value", bodyObj.getString(key)); + kvs.add(kv); + }); + JSONObject bodyRes = new JSONObject(); + bodyRes.put("kvs", kvs); + bodyRes.put("type", MsRequestBodyType.KV.value()); + requestObject.put("body", bodyRes); + } + } + } + } +} diff --git a/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java b/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java new file mode 100644 index 0000000000..e8eed52279 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java @@ -0,0 +1,184 @@ +package io.metersphere.api.parse; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import io.metersphere.api.dto.ApiTestImportRequest; +import io.metersphere.api.dto.definition.ApiDefinitionResult; +import io.metersphere.api.dto.definition.parse.ApiDefinitionImport; +import io.metersphere.api.dto.definition.request.MsTestElement; +import io.metersphere.api.dto.definition.request.configurations.MsHeaderManager; +import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; +import io.metersphere.api.dto.parse.ApiImport; +import io.metersphere.api.dto.parse.postman.*; +import io.metersphere.api.dto.scenario.Body; +import io.metersphere.api.dto.scenario.KeyValue; +import io.metersphere.api.dto.scenario.Scenario; +import io.metersphere.api.dto.scenario.request.HttpRequest; +import io.metersphere.api.dto.scenario.request.Request; +import io.metersphere.api.dto.scenario.request.RequestType; +import io.metersphere.commons.constants.MsRequestBodyType; +import io.metersphere.commons.constants.PostmanRequestBodyMode; +import org.apache.commons.lang3.StringUtils; +import org.apache.jorphan.collections.HashTree; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +public class PostmanParser extends ApiImportAbstractParser { + + @Override + public ApiImport parse(InputStream source, ApiTestImportRequest request) { + + String testStr = getApiTestStr(source); + PostmanCollection postmanCollection = JSON.parseObject(testStr, PostmanCollection.class); + PostmanCollectionInfo info = postmanCollection.getInfo(); + List variables = postmanCollection.getVariable(); + ApiImport apiImport = new ApiImport(); + List scenarios = new ArrayList<>(); + + Scenario scenario = new Scenario(); + scenario.setName(info.getName()); + setScenarioByRequest(scenario, request); + parseItem(postmanCollection.getItem(), scenario, variables, scenarios); + apiImport.setScenarios(scenarios); + + return apiImport; + } + + @Override + public ApiDefinitionImport parseApi(InputStream source, ApiTestImportRequest request) { + String testStr = getApiTestStr(source); + PostmanCollection postmanCollection = JSON.parseObject(testStr, PostmanCollection.class); + List variables = postmanCollection.getVariable(); + ApiDefinitionImport apiImport = new ApiDefinitionImport(); + List requests = new ArrayList<>(); + + parseItem(postmanCollection.getItem(), variables, requests); + apiImport.setData(requests); + return apiImport; + } + + private void parseItem(List items, List variables, List scenarios) { + for (PostmanItem item : items) { + List childItems = item.getItem(); + if (childItems != null) { + parseItem(childItems, variables, scenarios); + } else { + ApiDefinitionResult request = parsePostman(item); + if (request != null) { + scenarios.add(request); + } + } + } + } + + private ApiDefinitionResult parsePostman(PostmanItem requestItem) { + PostmanRequest requestDesc = requestItem.getRequest(); + if (requestDesc == null) { + return null; + } + PostmanUrl url = requestDesc.getUrl(); + ApiDefinitionResult request = new ApiDefinitionResult(); + request.setName(requestItem.getName()); + request.setPath(url.getRaw()); + request.setMethod(requestDesc.getMethod()); + request.setProtocol(RequestType.HTTP); + MsHTTPSamplerProxy requestElement = new MsHTTPSamplerProxy(); + requestElement.setName(requestItem.getName() + "Postman MHTTPSamplerProxy"); + requestElement.setBody(parseBody(requestDesc)); + requestElement.setArguments(parseKeyValue(url.getQuery())); + requestElement.setProtocol(RequestType.HTTP); + requestElement.setPath(url.getRaw()); + requestElement.setMethod(requestDesc.getMethod()); + requestElement.setId(UUID.randomUUID().toString()); + requestElement.setRest(new ArrayList()); + MsHeaderManager headerManager = new MsHeaderManager(); + headerManager.setId(UUID.randomUUID().toString()); + headerManager.setName(requestItem.getName() + "Postman MsHeaderManager"); + headerManager.setHeaders(parseKeyValue(requestDesc.getHeader())); + HashTree tree = new HashTree(); + tree.add(headerManager); + LinkedList list = new LinkedList<>(); + list.add(headerManager); + requestElement.setHashTree(list); + request.setRequest(JSON.toJSONString(requestElement)); + return request; + } + + + private List parseKeyValue(List postmanKeyValues) { + if (postmanKeyValues == null) { + return null; + } + List keyValues = new ArrayList<>(); + postmanKeyValues.forEach(item -> keyValues.add(new KeyValue(item.getKey(), item.getValue()))); + return keyValues; + } + + private void parseItem(List items, Scenario scenario, List variables, List scenarios) { + List requests = new ArrayList<>(); + for (PostmanItem item : items) { + List childItems = item.getItem(); + if (childItems != null) { + Scenario subScenario = new Scenario(); + subScenario.setName(item.getName()); + subScenario.setEnvironmentId(scenario.getEnvironmentId()); + parseItem(childItems, subScenario, variables, scenarios); + } else { + Request request = parseRequest(item); + if (request != null) { + requests.add(request); + } + } + } + scenario.setVariables(parseKeyValue(variables)); + scenario.setRequests(requests); + scenarios.add(scenario); + } + + private Request parseRequest(PostmanItem requestItem) { + HttpRequest request = new HttpRequest(); + PostmanRequest requestDesc = requestItem.getRequest(); + if (requestDesc == null) { + return null; + } + PostmanUrl url = requestDesc.getUrl(); + request.setName(requestItem.getName()); + request.setUrl(url.getRaw()); + request.setUseEnvironment(false); + request.setMethod(requestDesc.getMethod()); + request.setHeaders(parseKeyValue(requestDesc.getHeader())); + request.setParameters(parseKeyValue(url.getQuery())); + request.setBody(parseBody(requestDesc)); + return request; + } + + private Body parseBody(PostmanRequest requestDesc) { + Body body = new Body(); + JSONObject postmanBody = requestDesc.getBody(); + if (postmanBody == null) { + return null; + } + String bodyMode = postmanBody.getString("mode"); + if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.RAW.value())) { + body.setRaw(postmanBody.getString(bodyMode)); + body.setType(MsRequestBodyType.RAW.value()); + JSONObject options = postmanBody.getJSONObject("options"); + if (options != null) { + JSONObject raw = options.getJSONObject(PostmanRequestBodyMode.RAW.value()); + if (raw != null) { + body.setFormat(raw.getString("language")); + } + } + } else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FORM_DATA.value()) || StringUtils.equals(bodyMode, PostmanRequestBodyMode.URLENCODED.value())) { + List postmanKeyValues = JSON.parseArray(postmanBody.getString(bodyMode), PostmanKeyValue.class); + body.setType(MsRequestBodyType.KV.value()); + body.setKvs(parseKeyValue(postmanKeyValues)); + } + return body; + } + +} diff --git a/backend/src/main/java/io/metersphere/api/parse/Swagger2Parser.java b/backend/src/main/java/io/metersphere/api/parse/Swagger2Parser.java new file mode 100644 index 0000000000..687ab38786 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/parse/Swagger2Parser.java @@ -0,0 +1,309 @@ +package io.metersphere.api.parse; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import io.metersphere.api.dto.ApiTestImportRequest; +import io.metersphere.api.dto.definition.ApiDefinitionResult; +import io.metersphere.api.dto.definition.parse.ApiDefinitionImport; +import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; +import io.metersphere.api.dto.parse.ApiImport; +import io.metersphere.api.dto.scenario.Body; +import io.metersphere.api.dto.scenario.KeyValue; +import io.metersphere.api.dto.scenario.request.RequestType; +import io.metersphere.commons.constants.MsRequestBodyType; +import io.metersphere.commons.constants.SwaggerParameterType; +import io.swagger.models.*; +import io.swagger.models.parameters.*; +import io.swagger.models.properties.*; +import io.swagger.parser.SwaggerParser; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.InputStream; +import java.sql.Connection; +import java.util.*; + +public class Swagger2Parser extends ApiImportAbstractParser { + + private Map definitions = null; + + @Override + public ApiDefinitionImport parseApi(InputStream source, ApiTestImportRequest request) { + Swagger swagger; + if (StringUtils.isNotBlank(request.getSwaggerUrl())) { + swagger = new SwaggerParser().read(request.getSwaggerUrl()); + } else { + swagger = new SwaggerParser().readWithInfo(getApiTestStr(source)).getSwagger(); + } + ApiDefinitionImport definitionImport = new ApiDefinitionImport(); + definitionImport.setData(parseRequests(swagger)); + return definitionImport; + } + + @Override + public ApiImport parse(InputStream source, ApiTestImportRequest request) { + return null; + } + + private List parseRequests(Swagger swagger) { + List results = new LinkedList<>(); + Map paths = swagger.getPaths(); + Set pathNames = paths.keySet(); + + this.definitions = swagger.getDefinitions(); + + for (String pathName : pathNames) { + Path path = paths.get(pathName); + Map operationMap = path.getOperationMap(); + Set httpMethods = operationMap.keySet(); + for (HttpMethod method : httpMethods) { + Operation operation = operationMap.get(method); + + ApiDefinitionResult apiDefinition = buildApiDefinition(operation, pathName, method.name()); + MsHTTPSamplerProxy request = buildRequest(operation, pathName, method.name()); + parseParameters(operation, request); + apiDefinition.setRequest(JSON.toJSONString(request)); + results.add(apiDefinition); + + +// List tags = operation.getTags(); +// if (tags != null) { +// tags.forEach(tag -> { +// Scenario scenario = Optional.ofNullable(scenarioMap.get(tag)).orElse(new Scenario()); +// List requests = Optional.ofNullable(scenario.getRequests()).orElse(new ArrayList<>()); +// requests.add(request); +// scenario.setRequests(requests); +// scenario.setName(tag); +// scenarioMap.put(tag, scenario); +// }); +// } else { +// Scenario scenario = Optional.ofNullable(scenarioMap.get("default")).orElse(new Scenario()); +// List requests = Optional.ofNullable(scenario.getRequests()).orElse(new ArrayList<>()); +// requests.add(request); +// scenario.setRequests(requests); +// scenarioMap.put("default", scenario); +// } + + } + } + + this.definitions = null; + + return results; + } + + private ApiDefinitionResult buildApiDefinition(Operation operation, String path, String method) { + ApiDefinitionResult apiDefinition = new ApiDefinitionResult(); + if (StringUtils.isNotBlank(operation.getSummary())) { + apiDefinition.setName(operation.getSummary()); + } else { + apiDefinition.setName(operation.getOperationId()); + } + apiDefinition.setPath(path); + apiDefinition.setProtocol(RequestType.HTTP); + apiDefinition.setMethod(method); + return apiDefinition; + } + private MsHTTPSamplerProxy buildRequest(Operation operation, String path, String method) { + MsHTTPSamplerProxy request = new MsHTTPSamplerProxy(); + if (StringUtils.isNotBlank(operation.getSummary())) { + request.setName(operation.getSummary()); + } else { + request.setName(operation.getOperationId()); + } + request.setPath(path); + request.setMethod(method); + request.setProtocol(RequestType.HTTP); + return request; + } + + private void parseParameters(Operation operation, MsHTTPSamplerProxy request) { + + List parameters = operation.getParameters(); + request.setId(UUID.randomUUID().toString()); + request.setHeaders(new ArrayList<>()); + request.setArguments(new ArrayList<>()); + request.setRest(new ArrayList<>()); + request.setBody(new Body()); + request.getBody().setType(getBodyType(operation)); + + // todo 路径变量 {xxx} 是否要转换 + + for (Parameter parameter : parameters) { + switch (parameter.getIn()) { + case SwaggerParameterType.PATH: + parsePathParameters(parameter, request.getRest()); + break; + case SwaggerParameterType.QUERY: + parseQueryParameters(parameter, request.getArguments()); + break; + case SwaggerParameterType.FORM_DATA: + parseFormDataParameters((FormParameter) parameter, request.getBody()); + break; + case SwaggerParameterType.BODY: + parseBodyParameters(parameter, request.getBody()); + break; + case SwaggerParameterType.HEADER: + parseHeaderParameters(parameter, request.getHeaders()); + break; + case SwaggerParameterType.COOKIE: + parseCookieParameters(parameter, request.getHeaders()); + break; +// case SwaggerParameterType.FILE: +// parsePathParameters(parameter, request); +// break; + } + } + + +// List responseContentTypes = operation.getProduces(); + } + + private String getBodyType(Operation operation) { + if (CollectionUtils.isEmpty(operation.getConsumes())) { + return Body.RAW; + } + String contentType = operation.getConsumes().get(0); + String bodyType = ""; + switch (contentType) { + case "application/x-www-form-urlencoded": + bodyType = Body.WWW_FROM; + break; + case "multipart/form-data": + bodyType = Body.FORM_DATA; + break; + case "application/json": + bodyType = Body.JSON; + break; + case "application/xml": + bodyType = Body.XML; + break; +// case "": //todo binary 啥类型 +// bodyType = Body.BINARY; +// break; + default: + bodyType = Body.RAW; + } + return bodyType; + } + + private void parsePathParameters(Parameter parameter, List rests) { + PathParameter pathParameter = (PathParameter) parameter; + rests.add(new KeyValue(pathParameter.getName(), "", getDefaultStringValue(parameter.getDescription()))); + } + + private String getDefaultStringValue(String val) { + return StringUtils.isBlank(val) ? "" : val; + } + + private void parseCookieParameters(Parameter parameter, List headers) { + CookieParameter cookieParameter = (CookieParameter) parameter; + addCookie(headers, cookieParameter.getName(), "", getDefaultStringValue(cookieParameter.getDescription())); + } + + private void parseHeaderParameters(Parameter parameter, List headers) { + HeaderParameter headerParameter = (HeaderParameter) parameter; + addHeader(headers, headerParameter.getName(), "", getDefaultStringValue(headerParameter.getDescription())); + } + + private void parseBodyParameters(Parameter parameter, Body body) { + BodyParameter bodyParameter = (BodyParameter) parameter; + Model schema = bodyParameter.getSchema(); + + // 引用模型 + if (schema instanceof RefModel) { + String simpleRef = ""; + RefModel refModel = (RefModel) bodyParameter.getSchema(); + String originalRef = refModel.getOriginalRef(); + if (refModel.getOriginalRef().split("/").length > 3) { + simpleRef = originalRef.replace("#/definitions/", ""); + } else { + simpleRef = refModel.getSimpleRef(); + } + Model model = this.definitions.get(simpleRef); + HashSet refSet = new HashSet<>(); + refSet.add(simpleRef); + if (model != null) { + JSONObject bodyParameters = getBodyParameters(model.getProperties(), refSet); + body.setRaw(bodyParameters.toJSONString()); + } + } else if (schema instanceof ArrayModel) { + //模型数组 + ArrayModel arrayModel = (ArrayModel) bodyParameter.getSchema(); + Property items = arrayModel.getItems(); + if (items instanceof RefProperty) { + RefProperty refProperty = (RefProperty) items; + String simpleRef = refProperty.getSimpleRef(); + HashSet refSet = new HashSet<>(); + refSet.add(simpleRef); + Model model = definitions.get(simpleRef); + JSONArray propertyList = new JSONArray(); + propertyList.add(getBodyParameters(model.getProperties(), refSet)); + body.setRaw(propertyList.toString()); + } + } + body.setFormat("json"); + } + + private JSONObject getBodyParameters(Map properties, HashSet refSet) { + JSONObject jsonObject = new JSONObject(); + if (properties != null) { + properties.forEach((key, value) -> { + if (value instanceof ObjectProperty) { + ObjectProperty objectProperty = (ObjectProperty) value; + jsonObject.put(key, getBodyParameters(objectProperty.getProperties(), refSet)); + } else if (value instanceof ArrayProperty) { + ArrayProperty arrayProperty = (ArrayProperty) value; + Property items = arrayProperty.getItems(); + if (items instanceof RefProperty) { + RefProperty refProperty = (RefProperty) items; + String simpleRef = refProperty.getSimpleRef(); + if (refSet.contains(simpleRef)) { + //避免嵌套死循环 + jsonObject.put(key, new JSONArray()); + return; + } + refSet.add(simpleRef); + Model model = this.definitions.get(simpleRef); + JSONArray propertyList = new JSONArray(); + propertyList.add(getBodyParameters(model.getProperties(), refSet)); + jsonObject.put(key, propertyList); + } else { + jsonObject.put(key, new ArrayList<>()); + } + } else { + jsonObject.put(key, getDefaultValueByPropertyType(value)); + } + }); + } + return jsonObject; + } + + private Object getDefaultValueByPropertyType(Property value) { + if (value instanceof LongProperty || value instanceof IntegerProperty + || value instanceof BaseIntegerProperty) { + return 0; + } else if (value instanceof FloatProperty || value instanceof DoubleProperty + || value instanceof DecimalProperty) { + return 0.0; + } else {// todo 其他类型? + return getDefaultStringValue(value.getDescription()); + } + } + + private void parseFormDataParameters(FormParameter parameter, Body body) { + List keyValues = Optional.ofNullable(body.getKvs()).orElse(new ArrayList<>()); + KeyValue kv = new KeyValue(parameter.getName(), "", getDefaultStringValue(parameter.getDescription())); + if (StringUtils.equals(parameter.getType(), "file") ) { + kv.setType("file"); + } + keyValues.add(kv); + body.setKvs(keyValues); + } + + private void parseQueryParameters(Parameter parameter, List arguments) { + QueryParameter queryParameter = (QueryParameter) parameter; + arguments.add(new KeyValue(queryParameter.getName(), "", getDefaultStringValue(queryParameter.getDescription()))); + } +} diff --git a/backend/src/main/java/io/metersphere/api/service/APITestService.java b/backend/src/main/java/io/metersphere/api/service/APITestService.java index 9dcac1c29f..a3a6dfb917 100644 --- a/backend/src/main/java/io/metersphere/api/service/APITestService.java +++ b/backend/src/main/java/io/metersphere/api/service/APITestService.java @@ -6,9 +6,9 @@ import io.metersphere.api.dto.*; import io.metersphere.api.dto.parse.ApiImport; import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter; import io.metersphere.api.jmeter.JMeterService; -import io.metersphere.api.parse.ApiImportParser; -import io.metersphere.api.parse.ApiImportParserFactory; -import io.metersphere.api.parse.JmeterDocumentParser; +import io.metersphere.api.parse.old.ApiImportParser; +import io.metersphere.api.parse.old.ApiImportParserFactory; +import io.metersphere.api.parse.old.JmeterDocumentParser; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.ApiTestFileMapper; import io.metersphere.base.mapper.ApiTestMapper; diff --git a/frontend/src/business/components/api/definition/components/Run.vue b/frontend/src/business/components/api/definition/components/Run.vue index 69da69a55e..3d9e370d69 100644 --- a/frontend/src/business/components/api/definition/components/Run.vue +++ b/frontend/src/business/components/api/definition/components/Run.vue @@ -75,21 +75,23 @@ }); } }); - request.body.binary.forEach(param => { - if (param.files) { - param.files.forEach(item => { - if (item.file) { - if (!item.id) { - let fileId = getUUID().substring(0, 12); - item.name = item.file.name; - item.id = fileId; + if (request.body.binary) { + request.body.binary.forEach(param => { + if (param.files) { + param.files.forEach(item => { + if (item.file) { + if (!item.id) { + let fileId = getUUID().substring(0, 12); + item.name = item.file.name; + item.id = fileId; + } + obj.bodyUploadIds.push(item.id); + bodyUploadFiles.push(item.file); } - obj.bodyUploadIds.push(item.id); - bodyUploadFiles.push(item.file); - } - }); - } - }); + }); + } + }); + } } }); return bodyUploadFiles; diff --git a/frontend/src/business/components/api/definition/components/body/ApiBody.vue b/frontend/src/business/components/api/definition/components/body/ApiBody.vue index ce8d599ae8..aaa10515b9 100644 --- a/frontend/src/business/components/api/definition/components/body/ApiBody.vue +++ b/frontend/src/business/components/api/definition/components/body/ApiBody.vue @@ -1,7 +1,7 @@