Browse Source

新增测试时显示`Response Header`

mxd 3 years ago
parent
commit
1fc2e3bd38

+ 22 - 15
magic-api/src/main/java/org/ssssssss/magicapi/controller/RequestHandler.java

@@ -3,7 +3,6 @@ package org.ssssssss.magicapi.controller;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.http.converter.HttpMessageConverter;
@@ -47,6 +46,7 @@ import java.math.BigDecimal;
 import java.util.*;
 import java.util.stream.Collectors;
 
+import static org.springframework.http.HttpHeaders.*;
 import static org.ssssssss.magicapi.config.MessageType.BREAKPOINT;
 import static org.ssssssss.magicapi.config.MessageType.EXCEPTION;
 import static org.ssssssss.magicapi.model.Constants.*;
@@ -61,6 +61,9 @@ public class RequestHandler extends MagicController {
 	private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class);
 	private static final Map<String, Object> EMPTY_MAP = new HashMap<>();
 	private final ResultProvider resultProvider;
+	private static final List<String> DEFAULT_ALLOW_READ_RESPONSE_HEADERS = Arrays.asList(
+			ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_METHODS,
+			CONTENT_TYPE, DATE, SERVER, SET_COOKIE, CONNECTION, CONTENT_LENGTH, CONTENT_ENCODING, TRANSFER_ENCODING, VARY);
 
 	public RequestHandler(MagicConfiguration configuration) {
 		super(configuration);
@@ -130,12 +133,12 @@ public class RequestHandler extends MagicController {
 			// 设置 path 变量
 			context.set(VAR_NAME_PATH_VARIABLE, requestEntity.getPathVariables());
 			// 设置 body 变量
-			if(bodyValue != null){
+			if (bodyValue != null) {
 				context.set(VAR_NAME_REQUEST_BODY, bodyValue);
 			}
 			BaseDefinition requestBody = info.getRequestBodyDefinition();
 			if (requestBody != null) {
-				if(!CollectionUtils.isEmpty(requestBody.getChildren())){
+				if (!CollectionUtils.isEmpty(requestBody.getChildren())) {
 					requestBody.setName(StringUtils.defaultIfBlank(requestBody.getName(), "root"));
 					doValidate(scriptName, VAR_NAME_REQUEST_BODY, Collections.singletonList(requestBody), new HashMap<String, Object>() {{
 						put(requestBody.getName(), bodyValue);
@@ -204,16 +207,16 @@ public class RequestHandler extends MagicController {
 				List<Object> list = (List) parameters.get(parameter.getName());
 				if (list != null) {
 					List<Map<String, Object>> newList = list.stream().map(it -> doValidate(scriptName, VAR_NAME_REQUEST_BODY, parameter.getChildren(), new HashMap<String, Object>() {{
-						put(EMPTY, it);
+						put(Constants.EMPTY, it);
 					}}, jsonCode)).collect(Collectors.toList());
 					for (int i = 0, size = newList.size(); i < size; i++) {
-						list.set(i, newList.get(i).get(EMPTY));
+						list.set(i, newList.get(i).get(Constants.EMPTY));
 					}
 				}
 
 			} else if (StringUtils.isNotBlank(parameter.getName()) || parameters.containsKey(parameter.getName())) {
 				boolean isFile = parameter.getDataType() == DataType.MultipartFile || parameter.getDataType() == DataType.MultipartFiles;
-				String requestValue = StringUtils.defaultIfBlank(Objects.toString(parameters.get(parameter.getName()), EMPTY), Objects.toString(parameter.getDefaultValue(), EMPTY));
+				String requestValue = StringUtils.defaultIfBlank(Objects.toString(parameters.get(parameter.getName()), Constants.EMPTY), Objects.toString(parameter.getDefaultValue(), Constants.EMPTY));
 				if (StringUtils.isBlank(requestValue) && !isFile) {
 					if (!parameter.isRequired()) {
 						continue;
@@ -230,7 +233,7 @@ public class RequestHandler extends MagicController {
 					// 正则验证
 					if (VALIDATE_TYPE_PATTERN.equals(parameter.getValidateType())) {
 						String expression = parameter.getExpression();
-						if (StringUtils.isNotBlank(expression) && !PatternUtils.match(Objects.toString(value, EMPTY), expression)) {
+						if (StringUtils.isNotBlank(expression) && !PatternUtils.match(Objects.toString(value, Constants.EMPTY), expression)) {
 							throw new ValidateException(jsonCode, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]不满足正则表达式", comment, parameter.getName())));
 						}
 					}
@@ -394,14 +397,6 @@ public class RequestHandler extends MagicController {
 	 */
 	private Object response(RequestEntity requestEntity, Object value) {
 		if (value instanceof ResponseEntity) {
-			if (requestEntity.isRequestedFromTest()) {
-				ResponseEntity<?> entity = (ResponseEntity<?>) value;
-				Set<String> headerKeys = entity.getHeaders().keySet();
-				if (!headerKeys.isEmpty()) {
-					// 允许前端读取自定义的header(跨域情况)。
-					requestEntity.getResponse().setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, String.join(",", headerKeys));
-				}
-			}
 			return value;
 		} else if (value instanceof ResponseModule.NullValue) {
 			return null;
@@ -434,6 +429,18 @@ public class RequestHandler extends MagicController {
 				logger.warn("执行afterCompletion出现出错", e);
 			}
 		}
+		Set<String> exposeHeaders = new HashSet<>(16);
+		if (returnValue instanceof ResponseEntity) {
+			exposeHeaders.addAll(((ResponseEntity<?>) returnValue).getHeaders().keySet());
+		}
+		if (requestEntity.isRequestedFromTest()) {
+			HttpServletResponse response = requestEntity.getResponse();
+			exposeHeaders.addAll(response.getHeaderNames());
+			exposeHeaders.addAll(DEFAULT_ALLOW_READ_RESPONSE_HEADERS);
+		}
+		if (!exposeHeaders.isEmpty()) {
+			requestEntity.getResponse().setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, String.join(",", exposeHeaders));
+		}
 		return returnValue;
 	}
 

+ 3 - 5
magic-editor/src/console/src/components/common/magic-json.vue

@@ -1,12 +1,12 @@
 <template>
   <div class="ma-json-container">
-    <div class="json-view f_c" :style="'height:'+ height">
+    <div class="json-view f_c">
       <!-- 解决子组件不强制刷新 -->
       <div v-show="forceUpdate"></div>
       <div class="header">视图</div>
       <magic-json-tree :jsonData="jsonData" :forceUpdate="forceUpdate" class="view-box" v-on:jsonClick="handleJsonClick"></magic-json-tree>
     </div>
-    <div class="json-panel f_c" :style="'height:'+ height">
+    <div class="json-panel f_c">
       <div class="header">属性</div>
       <div class="panel-box f_c" v-if="fieldObj.dataType && fieldObj.dataType !== 'Object' && fieldObj.dataType !== 'Array'">
         <div class="box-item">
@@ -104,7 +104,6 @@
       },
       // 解决子组件不强制刷新
       forceUpdate: Boolean,
-      height: String,
       type: String
     },
     data() {
@@ -192,8 +191,7 @@
   }
 
   .json-view {
-    width: 30vw;
-    overflow: scroll;
+    flex: 1;
     margin: 0px 10px;
     border: 1px solid var(--border-color);
     border-top: none;

+ 2 - 2
magic-editor/src/console/src/components/editor/magic-script-editor.vue

@@ -732,7 +732,7 @@ export default {
                 index++;
               }
               dataLen = dataLen.toFixed(2);
-              bus.$emit('status', `「${fullName}」测试完毕,状态:<em>${res.status}</em> 大小:<em>${dataLen}${unit[index]}</em> 耗时:<em>${new Date().getTime() - start}ms</em>`)
+              bus.$emit('status', `「${fullName}」测试完毕,状态:<em>${res.status}</em> 大小:<em>${dataLen} ${unit[index]}</em> 耗时:<em>${new Date().getTime() - start} ms</em>`)
               const contentType = res.headers['content-type']
               target.ext.debugDecorations && this.editor.deltaDecorations(target.ext.debugDecorations, [])
               target.ext.debugDecorations = target.ext.debugDecoration = null
@@ -741,7 +741,7 @@ export default {
                 target.responseBody = utils.formatJson(data)
                 bus.$emit('switch-tab', 'result')
                 bus.$emit('update-response-body-definition', target.responseBodyDefinition);
-                bus.$emit('update-response-body', target.responseBody)
+                bus.$emit('update-response-body', target.responseBody, res.headers)
               } else {
                 // 执行完毕
                 target.running = false

+ 1 - 1
magic-editor/src/console/src/components/layout/magic-function.vue

@@ -9,7 +9,7 @@
       <magic-input :value.sync="info.path" placeholder="请输入函数路径" width="500px"/>
     </div>
     <div class="ma-request-parameters">
-      <ul class="not-select">
+      <ul class="not-select ma-nav-tab">
         <li v-for="(item, key) in navs" :key="'request_item_' + key" :class="{ selected: showIndex === key }"
             @click="showIndex = key;">{{ item }}
         </li>

+ 1 - 1
magic-editor/src/console/src/components/layout/magic-group.vue

@@ -8,7 +8,7 @@
       <button class="ma-button" @click="doSave">保存</button>
     </div>
     <div class="ma-request-parameters">
-      <ul class="not-select">
+      <ul class="not-select ma-nav-tab">
         <li v-for="(item, key) in navs" :key="key" :class="{ selected: showIndex === key }" @click="showIndex = key;">{{ item }}
         </li>
       </ul>

+ 4 - 4
magic-editor/src/console/src/components/layout/magic-options.vue

@@ -279,7 +279,7 @@ export default {
   background: var(--hover-background);
 }
 
-.ma-request-parameters > ul li {
+.ma-nav-tab li {
   display: inline-block;
   height: 24px;
   line-height: 24px;
@@ -287,15 +287,15 @@ export default {
   cursor: pointer;
 }
 
-.ma-request-parameters > ul li.selected {
+.ma-nav-tab li.selected {
   background: var(--selected-background);
 }
 
-.ma-request-parameters > ul li:hover {
+.ma-nav-tab li:hover {
   background: var(--hover-background);
 }
 
-.ma-request-parameters > ul {
+.ma-nav-tab {
   border-bottom: 1px solid var(--tab-bar-border-color);
   height: 24px;
 }

+ 1 - 1
magic-editor/src/console/src/components/layout/magic-request.vue

@@ -9,7 +9,7 @@
       <magic-input :value.sync="info.path" placeholder="请输入接口路径" style="flex: 2"/>
     </div>
     <div class="ma-request-parameters">
-      <ul class="not-select">
+      <ul class="not-select ma-nav-tab">
         <li v-for="(item, key) in navs" :key="'request_item_' + key" :class="{ selected: showIndex === key }"
             @click="showIndex = key;initRequestBodyDom();">{{ item }}
         </li>

+ 46 - 12
magic-editor/src/console/src/components/layout/magic-run.vue

@@ -1,13 +1,26 @@
 <template>
-  <div class="ma-run" style="display: flex; flex-direction: row;">
-    <div style="width: 40%" v-show="!contentType">
-      <div ref="resultEditor" class="ma-body-editor"></div>
+  <div class="ma-run">
+    <ul class="not-select ma-nav-tab">
+      <li v-for="(item, key) in navs" :key="'response_item_' + key" :class="{ selected: showIndex === key }" @click="showIndex = key;">{{ item }}
+      </li>
+    </ul>
+    <div ref="resultEditor" class="ma-body-editor" v-show="showIndex === 0 && !contentType"></div>
+    <iframe v-if="contentType && showIndex === 0" class="ma-response-body-container" style="padding:5px;" :src="this.objectUrl"></iframe>
+    <div class="ma-layout" v-if="showIndex === 1">
+      <div class="ma-layout-container">
+        <div class="ma-header ma-table-row">
+          <div>Key</div>
+          <div>Value</div>
+        </div>
+        <div class="ma-content">
+          <div v-for="(value, key) in responseHeaders" :key="'response_header_' + key" class="ma-table-row content-bg">
+            <div>{{ key }}</div>
+            <div>{{ value }}</div>
+          </div>
+        </div>
+      </div>
     </div>
-    <div style="flex: 1;" v-show="!contentType">
-      <magic-json :jsonData="responseBody || []" :forceUpdate="forceUpdate" :height="layoutHeight" type="response"></magic-json>
-    </div>
-    <iframe v-if="contentType" class="ma-response-body-container" style="padding:5px;" :src="this.objectUrl">
-    </iframe>
+    <magic-json v-if="!contentType && showIndex === 2" :jsonData="responseBody || []" :forceUpdate="forceUpdate" type="response"></magic-json>
   </div>
 </template>
 
@@ -33,10 +46,12 @@ export default {
     return {
       resultEditor: null,
       responseBody: [],
+      responseHeaders: {},
       forceUpdate: false,
-      layoutHeight: '255px',
       contentType: '',
-      objectUrl: null
+      navs: ['Body','响应Header', '响应结构'],
+      objectUrl: null,
+      showIndex: 0
     }
   },
   watch: {
@@ -52,7 +67,7 @@ export default {
   },
   mounted() {
     this.createEditor()
-    bus.$on('update-response-body', (responseBody) => {
+    bus.$on('update-response-body', (responseBody, headers) => {
       this.contentType = null
       if(this.objectUrl){
         URL.revokeObjectURL(this.objectUrl)
@@ -60,12 +75,14 @@ export default {
       }
       this.resultEditor && this.resultEditor.setValue(responseBody || '')
       this.updateResponseBody(responseBody)
+      this.responseHeaders = headers
     })
     bus.$on('update-response-body-definition', (responseBodyDefinition) => {
       this.responseBody = responseBodyDefinition ? [responseBodyDefinition] : []
     })
     bus.$on('update-response-blob',(contentType, blob, headers) => {
       this.contentType = contentType
+      this.responseHeaders = headers
       let disposition = headers['content-disposition'];
       if(disposition){
         try {
@@ -169,11 +186,15 @@ div.ma-run{
   height: 100%;
   width: 100%;
   position: relative;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
 }
 
-div.ma-run > div {
+div.ma-run > *:not(ul) {
   width: 100%;
   height: 100%;
+  flex: 1;
 }
 .ma-body-editor {
   width: 100%;
@@ -184,4 +205,17 @@ div.ma-run > div {
   height: 100%;
   border: none;
 }
+.ma-layout-container .ma-header div,
+.ma-layout-container .ma-content .ma-table-row div{
+  width: 50%;
+  padding-left: 5px;
+  background: none;
+}
+.ma-run .ma-layout .ma-content .content-bg{
+  cursor: pointer;
+}
+.ma-run .ma-layout .ma-content .content-bg:hover{
+  background: var(--toolbox-list-hover-background);
+}
+
 </style>