From 5c938336b39ada326a89c60f4fb4d7fe098a1412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=98=84=EC=84=B1=ED=95=84?= Date: Wed, 27 Dec 2023 21:09:01 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/dto/ApiRequestDTO.java | 20 ++ .../client/entity/ApiRequestInfo.java | 25 +- .../client/service/ApiRequestMgmtService.java | 3 + .../client/service/NettyApiClient.java | 35 +-- src/main/resources/static/js/APITester.js | 267 +++++++++++++++--- src/main/resources/static/js/LayoutUtil.js | 116 ++++++++ .../templates/page/tester/apiTester.html | 7 + 7 files changed, 396 insertions(+), 77 deletions(-) create mode 100644 src/main/resources/static/js/LayoutUtil.js diff --git a/src/main/java/com/eactive/httpmockserver/client/dto/ApiRequestDTO.java b/src/main/java/com/eactive/httpmockserver/client/dto/ApiRequestDTO.java index 95dd734..77c6b96 100644 --- a/src/main/java/com/eactive/httpmockserver/client/dto/ApiRequestDTO.java +++ b/src/main/java/com/eactive/httpmockserver/client/dto/ApiRequestDTO.java @@ -22,6 +22,10 @@ public class ApiRequestDTO { private String requestBody; + private String preRequestScript; + + private String postRequestScript; + public String getId() { return id; } @@ -93,4 +97,20 @@ public class ApiRequestDTO { public void setRequestBody(String requestBody) { this.requestBody = requestBody; } + + public String getPreRequestScript() { + return preRequestScript; + } + + public void setPreRequestScript(String preRequestScript) { + this.preRequestScript = preRequestScript; + } + + public String getPostRequestScript() { + return postRequestScript; + } + + public void setPostRequestScript(String postRequestScript) { + this.postRequestScript = postRequestScript; + } } diff --git a/src/main/java/com/eactive/httpmockserver/client/entity/ApiRequestInfo.java b/src/main/java/com/eactive/httpmockserver/client/entity/ApiRequestInfo.java index eeca761..1e2339a 100644 --- a/src/main/java/com/eactive/httpmockserver/client/entity/ApiRequestInfo.java +++ b/src/main/java/com/eactive/httpmockserver/client/entity/ApiRequestInfo.java @@ -40,6 +40,13 @@ public class ApiRequestInfo { @Lob private String requestBody; + @Lob + private String preRequestScript; + + @Lob + private String postRequestScript; + + public String getName() { return name; } @@ -120,6 +127,22 @@ public class ApiRequestInfo { this.requestBody = requestBody; } + public String getPreRequestScript() { + return preRequestScript; + } + + public void setPreRequestScript(String preRequestScript) { + this.preRequestScript = preRequestScript; + } + + public String getPostRequestScript() { + return postRequestScript; + } + + public void setPostRequestScript(String postRequestScript) { + this.postRequestScript = postRequestScript; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -130,7 +153,7 @@ public class ApiRequestInfo { @Override public int hashCode() { - return Objects.hash(id, server, owner, method, path, headers, queryParams, requestBody); + return Objects.hash(id, server, owner, method, path, headers, queryParams, requestBody, preRequestScript, postRequestScript); } } diff --git a/src/main/java/com/eactive/httpmockserver/client/service/ApiRequestMgmtService.java b/src/main/java/com/eactive/httpmockserver/client/service/ApiRequestMgmtService.java index 45da84a..10e7d49 100644 --- a/src/main/java/com/eactive/httpmockserver/client/service/ApiRequestMgmtService.java +++ b/src/main/java/com/eactive/httpmockserver/client/service/ApiRequestMgmtService.java @@ -70,6 +70,9 @@ public class ApiRequestMgmtService { existing.setVariables(api.getVariables()); existing.setServer(api.getServer()); existing.setRequestBody(api.getRequestBody()); + existing.setPreRequestScript(api.getPreRequestScript()); + existing.setPostRequestScript(api.getPostRequestScript()); + apiRequestRepository.save(existing); api = existing; } diff --git a/src/main/java/com/eactive/httpmockserver/client/service/NettyApiClient.java b/src/main/java/com/eactive/httpmockserver/client/service/NettyApiClient.java index 6fd3579..d102905 100644 --- a/src/main/java/com/eactive/httpmockserver/client/service/NettyApiClient.java +++ b/src/main/java/com/eactive/httpmockserver/client/service/NettyApiClient.java @@ -37,7 +37,7 @@ public class NettyApiClient { ServerRepository serverRepository; public CompletableFuture handleRequest(ApiRequestDTO originalRequest) throws InterruptedException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException { - ApiRequestDTO apiRequest = replacePlaceholdersWithVariables(originalRequest); + ApiRequestDTO apiRequest = originalRequest; Server server = serverRepository.findById(apiRequest.getServer()).orElseThrow(() -> new ServerNotFoundException("Server not found")); String host = server.getHostname(); int port = server.getPort(); @@ -108,37 +108,4 @@ public class NettyApiClient { return responseFuture; } - private ApiRequestDTO replacePlaceholdersWithVariables(ApiRequestDTO original) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException { - ApiRequestDTO request = (ApiRequestDTO) BeanUtils.cloneBean(original); - if (request.getVariables() != null) { - for (ApiRequestKeyValueDTO variable : request.getVariables()) { - if (variable.isEnabled()) { - String placeholder = "{{" + variable.getKey() + "}}"; - // Replace in path - request.setPath(request.getPath().replace(placeholder, variable.getValue())); - // Replace in requestBody - if (request.getRequestBody() != null) { - request.setRequestBody(request.getRequestBody().replace(placeholder, variable.getValue())); - } - - // Replace in headers - if (request.getHeaders() != null) { - for (ApiRequestKeyValueDTO header : request.getHeaders()) { - header.setKey(header.getKey().replace(placeholder, variable.getValue())); - header.setValue(header.getValue().replace(placeholder, variable.getValue())); - } - } - // Replace in queryParams - if (request.getQueryParams() != null) { - for (ApiRequestKeyValueDTO queryParam : request.getQueryParams()) { - queryParam.setKey(queryParam.getKey().replace(placeholder, variable.getValue())); - queryParam.setValue(queryParam.getValue().replace(placeholder, variable.getValue())); - } - } - } - } - } - return request; - } - } diff --git a/src/main/resources/static/js/APITester.js b/src/main/resources/static/js/APITester.js index dbe2b5b..6dc77b6 100644 --- a/src/main/resources/static/js/APITester.js +++ b/src/main/resources/static/js/APITester.js @@ -14,7 +14,11 @@ class APITester { this.collections = []; //API 컬렉션 this.requestEditors = []; //API request body editor this.responseEditors = []; //API response body editor - + this.preRequestEditors = []; + this.postRequestEditors = []; + window.globals = {}; + this.globalVariables = window.globals; + this.variables = {}; } renderServers(selectedServer) { @@ -36,7 +40,7 @@ class APITester { apiRequestTemplate(apiRequest, index) { return ` -
+
${apiRequest.collectionName} > ${apiRequest.name}
@@ -60,13 +64,12 @@ class APITester {
- +
- +
@@ -98,6 +101,19 @@ class APITester { aria-selected="false">Variables + + +
+
+
+
+
+
+
Response @@ -467,8 +491,7 @@ class APITester { //console.log(fieldName); const newValue = this.getFieldValue(element); //console.log(newValue); - - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); let model = this.apiRequests[tabIndex]; if (fieldName !== '') { @@ -479,7 +502,7 @@ class APITester { } handleUpdateServer(event) { - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); let model = this.apiRequests[tabIndex]; let target = $('#api_request_' + model.id); //console.log('model.server: ' + model.server); @@ -505,7 +528,7 @@ class APITester { for (let idx = 0; idx < this.apiRequests.length; idx++) { let li = ``; @@ -524,7 +547,7 @@ class APITester { this.openTab(model.id); let target = $('#api_request_' + model.id); - require(['vs/editor/editor.main'], function () { + require(['vs/editor/editor.main'], () => { let requestBodyEditor = monaco.editor.create(target.find(".request_body")[0], { value: model.requestBody, language: 'json', @@ -532,15 +555,15 @@ class APITester { automaticLayout: true }); me.requestEditors.push(requestBodyEditor); - target.find(".request_body")[0].addEventListener('shown.bs.tab', function () { + target.find(".request_body")[0].addEventListener('shown.bs.tab', () => { me.requestEditors[index].layout(); }); - me.requestEditors[index].onDidChangeModelContent(function (e) { + me.requestEditors[index].onDidChangeModelContent( (e) => { model.requestBody = requestBodyEditor.getValue(); }); - target.find('.request_body_type').on('change', function () { + target.find('.request_body_type').on('change', () => { let model = requestBodyEditor.getModel(); monaco.editor.setModelLanguage(model, $(this).val()); }); @@ -552,6 +575,40 @@ class APITester { automaticLayout: true }); me.responseEditors.push(responseBodyEditor); + + let preRequestEditor = monaco.editor.create(target.find(".pre-request")[0], { + value: model.preRequestScript, + language: 'javascript', + theme: 'vs-light', + automaticLayout: true + }); + me.preRequestEditors.push(preRequestEditor); + target.find(".pre-request")[0].addEventListener('shown.bs.tab', () => { + me.preRequestEditors[index].layout(); + }); + + me.preRequestEditors[index].onDidChangeModelContent( (e) => { + model.preRequestScript = preRequestEditor.getValue(); + }); + + let postRequestEditor = monaco.editor.create(target.find(".post-request")[0], { + value: model.postRequestScript, + language: 'javascript', + theme: 'vs-light', + automaticLayout: true + }); + me.postRequestEditors.push(postRequestEditor); + target.find(".post-request")[0].addEventListener('shown.bs.tab', () => { + me.postRequestEditors[index].layout(); + }); + + me.postRequestEditors[index].onDidChangeModelContent( (e) => { + model.postRequestScript = postRequestEditor.getValue(); + }); + + + + }); } @@ -621,7 +678,7 @@ class APITester { } addHeader(event) { - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); let model = this.apiRequests[tabIndex]; model.headers.push({ enabled: true, @@ -632,7 +689,7 @@ class APITester { } removeHeader(event) { - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); const index = $(event.currentTarget).closest('tr').data('index'); let model = this.apiRequests[tabIndex]; model.headers.splice(index, 1); @@ -649,7 +706,7 @@ class APITester { addQueryParam(event) { - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); //console.log('addQueryParam'); let model = this.apiRequests[tabIndex]; model.queryParams.push({ @@ -663,7 +720,7 @@ class APITester { removeQueryParam(event) { //console.log('removeQueryParam'); - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); const index = $(event.currentTarget).closest('tr').data('index'); let model = this.apiRequests[tabIndex]; model.queryParams.splice(index, 1); @@ -680,7 +737,7 @@ class APITester { addVariable(event) { //console.log('addVariable'); - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); let model = this.apiRequests[tabIndex]; model.variables.push({ enabled: true, @@ -692,7 +749,7 @@ class APITester { removeVariable(event) { //console.log('removeVariable'); - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); const index = $(event.currentTarget).closest('tr').data('index'); let model = this.apiRequests[tabIndex]; model.variables.splice(index, 1); @@ -707,7 +764,7 @@ class APITester { target.find('.variables').append(this.renderVariables(model.variables)); } parseParam(event) { - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); let model = this.apiRequests[tabIndex]; model.path = $(event.currentTarget).val(); //console.log(model.path); @@ -729,7 +786,7 @@ class APITester { } } buildPath(event) { - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); let model = this.apiRequests[tabIndex]; let queryString = model.path.split('?')[0]; if (model.queryParams.length > 0) { @@ -757,41 +814,65 @@ class APITester { } sendApiRequest(event) { - const tabIndex = $(event.currentTarget).closest('.api_request').data('index'); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); + console.log(tabIndex); const me = this; let model = this.apiRequests[tabIndex]; + + if (!model.server){ + $('.toast-body').text('서버를 등록해주세요.'); + $('.toast').toast('show'); + return; + } + let target = $('#api_request_' + model.id); target.find('.response_headers').empty(); this.responseEditors[tabIndex].setValue(''); let startTime = new Date().getTime(); - if (model.path !== '') { - this.showLoadingOverlay(); + this.showLoadingOverlay(); + model.responseModel = {}; + + let processedRequest = this.executePreRequestScript(model, tabIndex,() => { + processedRequest = this.replacePlaceholdersWithVariables(processedRequest, tabIndex); this.currentAjaxRequest = $.ajax({ url: '/mgmt/api/test.do', type: 'POST', contentType: 'application/json', - data: JSON.stringify(model), - success: function (data) { + data: JSON.stringify(processedRequest), + success: (data) => { model.responseModel.status = data.status; model.responseModel.body = data.body; model.responseModel.headers = data.headers; model.responseModel.size = data.size; + + let response = {}; + response.body = data.body; + response.status = data.status; + response.headers = {}; + response.size = data.size; + _.forEach(data.headers, (header) => { + response.headers[header.key] = header.value; + }); + + + this.executePostRequestScript(model.postRequestScript, tabIndex, response, ()=> { + let endTime = new Date().getTime(); + model.responseModel.time = endTime - startTime; + me.renderResponse(tabIndex); + me.hideLoadingOverlay(); + }); }, - error: function (response, textStatus, errorThrown) { + error: (response, textStatus, errorThrown) => { me.hideLoadingOverlay(); $('.toast-body').text(response.responseJSON.error); $('.toast').toast('show'); - }, - complete: function () { - let endTime = new Date().getTime(); - model.responseModel.time = endTime - startTime; - me.renderResponse(tabIndex); - me.hideLoadingOverlay(); } }); - } + }); + + } @@ -917,7 +998,7 @@ class APITester { saveAPIRequest(event) { const me = this; - const index = $(event.currentTarget).data('index'); + const index = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); let model = this.apiRequests[index]; me.showLoadingOverlay(); @@ -946,6 +1027,11 @@ class APITester { addAPIRequest(event) { const collectionId = $('#modal_collection').val(); + let newApiName = $('#new_api_name').val(); + if (newApiName === '') { + newApiName = '새로운 API'; + } + const collection = this.collections.filter(function (collection) { return collection.id === collectionId; })[0]; @@ -954,12 +1040,14 @@ class APITester { id: '', server: null, method: 'GET', - name: 'New Request', + name: newApiName, path: '', headers: [], queryParams: [], variables: [], requestBody: '', + preRequestScript :'', + postRequestScript :'', collectionId: collectionId, responseModel: { status: 0, @@ -991,7 +1079,7 @@ class APITester { me.renderTabHeader(); me.renderTabContent(me.currentIndex); - + $('#new_api_name').val(''); me.hideLoadingOverlay(); $('#new_api_request_modal').modal('hide'); } @@ -1078,28 +1166,123 @@ class APITester { me.hideLoadingOverlay(); me.loadCollections(); $('#confirm_delete_api_modal').modal('hide'); - // $('#confirm_delete_api_modal').find('.confirm_delete_api').removeAttr('data-id'); - // $('#confirm_delete_api_modal').find('.confirm_delete_api').removeAttr('data-collection'); } }); } closeTabWithApiIndex(index) { + console.log(index); if (index !== undefined){ //console.log('closeTabWithApiIndex: ' + index); const model = this.apiRequests[index]; + console.log(model); this.apiRequests.splice(index, 1); let apiRequestTabTitle = $(this.apiTabTitleTarget); apiRequestTabTitle.empty(); this.renderTabHeader(); $(`#api_request_${model.id}`).remove(); - $(`#apiRequestTabTitle li:eq(${this.apiRequests.length - 1}) button`).tab('show'); + // $(`#apiRequestTabTitle li:eq(${this.apiRequests.length - 1}) button`).tab('show'); + $(`#apiRequestTabTitle li:eq(0) button`).tab('show'); } } closeTab(event) { - let index = $(event.currentTarget).closest('button').data('index'); - //console.log('closeTab: ' + index); + const index = $('#apiRequestTabTitle').children().index($(event.currentTarget).closest('.nav-item')); this.closeTabWithApiIndex(index); } + + replacePlaceholdersWithVariables(original, index) { + let request = JSON.parse(JSON.stringify(original)); + + let mergedVariables = {}; + _.forEach(this.globalVariables, (value, key) => { + mergedVariables[key] = value; + }); + + if (this.variables[index]) { + _.forEach(this.variables[index], (value, key) => { + mergedVariables[key] = value; + }); + } + + _.forEach(mergedVariables, (value, key) => { + const placeholder = `{{${key}}}`; + // Replace in path + request.path = request.path.replace(new RegExp(placeholder, 'g'), value); + + // Replace in requestBody + if (request.requestBody) { + request.requestBody = request.requestBody.replace(new RegExp(placeholder, 'g'), value); + } + + // Replace in headers + if (request.headers) { + request.headers = request.headers.map(header => { + return { + ...header, + key: header.key.replace(new RegExp(placeholder, 'g'), value), + value: header.value.replace(new RegExp(placeholder, 'g'), value) + }; + }); + } + + // Replace in queryParams + if (request.queryParams) { + request.queryParams = request.queryParams.map(queryParam => { + return { + ...queryParam, + key: queryParam.key.replace(new RegExp(placeholder, 'g'), value), + value: queryParam.value.replace(new RegExp(placeholder, 'g'), value) + }; + }); + } + }); + + return request; + } + + executePreRequestScript(model, index, callback) { + let script = model.preRequestScript; + let resultFrame = document.getElementById('preRequest'); + let {headers, method, path, queryParams, requestBody} = model; + window.request = {headers, method, path, queryParams, requestBody}; + this.variables[index] = {}; + model.variables.forEach((item) =>{ + if (item.enabled) { + this.variables[index][item.key] = item.value; + } + }); + + window.preRequestComplete = function(updatedModel) { + callback(updatedModel); + }; + window.temp_variable = this.variables[index]; + + resultFrame.srcdoc = ``; + + return model; + } + + executePostRequestScript(script, index, response, callback) { + let resultFrame = document.getElementById('postRequest'); + window.response = response; + window.parent.temp_variable = this.variables[index]; + window.postRequestComplete = function(updatedModel) { + callback(updatedModel); + }; + window.temp_variable = this.variables[index]; + resultFrame.srcdoc = ``; + } } \ No newline at end of file diff --git a/src/main/resources/static/js/LayoutUtil.js b/src/main/resources/static/js/LayoutUtil.js new file mode 100644 index 0000000..bd21bd4 --- /dev/null +++ b/src/main/resources/static/js/LayoutUtil.js @@ -0,0 +1,116 @@ +class LayoutUtil { + static flattenObject(obj, prefix = '') { + return _.reduce(obj, (acc, value, key) => { + const fullKey = prefix ? `${prefix}.${key}` : key; + if (_.isObject(value) && !_.isArray(value)) { + _.assign(acc, this.flattenObject(value, fullKey)); + } else if (_.isArray(value)) { + // Process each item in the array + value.forEach((item, index) => { + // If the item is an object, recurse, otherwise assign directly + if (_.isObject(item)) { + _.assign(acc, this.flattenObject(item, `${fullKey}[${index}]`)); + } else { + acc[`${fullKey}[${index}]`] = item; + } + }); + } else { + acc[fullKey] = value; + } + return acc; + }, {}); + } + + static createGroup(groupModel) { + let groupHtml = `
${groupModel.groupTitle}
`; + + groupModel.fields.forEach(item => { + groupHtml += this.createFormElement(item); + }); + + groupHtml += '
'; + return groupHtml; + } + + static createTextInput(elementModel) { + return ` +
+ + +
+ `; + } + + static createBooleanInput(elementModel) { + return ` +
+
+ + +
+
`; + + } + + + static createSelectInput(elementModel) { + const optionsHtml = elementModel.options.map(optionValue => ``).join(''); + + return ` +
+ + +
+ `; + } + + static createTextAreaInput(elementModel) { + return ` +
+ + +
`; + } + + static createFormElement(elementModel) { + switch (elementModel.type) { + case "boolean": + return this.createBooleanInput(elementModel); + case "text": + return this.createTextInput(elementModel); + case "select": + return this.createSelectInput(elementModel); + case "textarea": + return this.createTextAreaInput(elementModel); + default: + console.error("Unsupported element type: " + elementModel.type); + return ''; + } + } + + static createForm(model) { + let formHtml = ''; + model.forEach(group => { + formHtml += this.createGroup(group); + }); + return formHtml; + } + + static getFieldValue(element) { + if ($(element).is('select') || $(element).is('input[type="text"]') || $(element).is('textarea')) { + return $(element).val(); + } + if ($(element).is('input[type="checkbox"]')) { + return $(element).is(':checked'); + } + } + + static echartReplacer(key, value) { + if (key === 'echart' || key === 'series') { + return undefined; + } + return value; + } +} \ No newline at end of file diff --git a/src/main/resources/templates/page/tester/apiTester.html b/src/main/resources/templates/page/tester/apiTester.html index 914db0d..65cf59b 100644 --- a/src/main/resources/templates/page/tester/apiTester.html +++ b/src/main/resources/templates/page/tester/apiTester.html @@ -48,6 +48,10 @@
+ + +