diff --git a/src/main/resources/static/js/APIClient.js b/src/main/resources/static/js/APIClient.js
new file mode 100644
index 0000000..52b5433
--- /dev/null
+++ b/src/main/resources/static/js/APIClient.js
@@ -0,0 +1,188 @@
+/**
+ * 처리 순서
+ * 1. preRequestScript 실행 -> executePreRequestScript
+ * 2. 변수 치환 -> replacePlaceholdersWithVariables
+ * 3. API 호출 -> sendAPIRequest
+ * 4. postRequestScript 실행 -> executePostRequestScript
+ */
+
+class APIClient {
+ constructor() {
+ window.globals = {};
+ this.globalVariables = window.globals;
+ this.variables = {};
+ this.currentAjaxRequest = null;
+ this.abortController = null;
+ }
+
+ async sendAPIRequest(model) {
+ try {
+ const updatedModel = await this.executePreRequestScript(model);
+ let processedRequest = this.replacePlaceholdersWithVariables(updatedModel);
+ return this.makeAjaxRequest(processedRequest, model);
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ abort() {
+ if (this.abortController) {
+ this.abortController.abort(); // abort the fetch
+ this.abortController = null; // reset the controller
+ }
+ }
+
+ makeAjaxRequest(processedRequest, model) {
+ this.abortController = new AbortController();
+ const {signal} = this.abortController;
+
+ return fetch('/mgmt/api/test.do', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-XSRF-TOKEN': $('meta[name="_csrf"]').attr('content')
+ },
+ body: JSON.stringify(processedRequest),
+ signal: signal // pass the signal to the fetch
+ })
+ .then(response => response.json())
+ .then(data => {
+ try {
+ 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;
+ });
+ return this.executePostRequestScript(model.postRequestScript, model.responseModel);
+ } catch (error) {
+ console.log('error1')
+ $('.toast-body').text(error);
+ $('.toast').toast('show');
+ }
+ })
+ .catch(error => {
+ console.log('error2')
+ console.log(error.name)
+ if (error.name === 'AbortError') {
+ console.log('Fetch aborted');
+ } else {
+ // $('.toast-body').text(response.responseJSON.error);
+ // $('.toast').toast('show');
+ }
+ });
+
+ }
+
+
+ replacePlaceholdersWithVariables(original) {
+ let request = JSON.parse(JSON.stringify(original));
+
+ let mergedVariables = {};
+ _.forEach(this.globalVariables, (value, key) => {
+ mergedVariables[key] = value;
+ });
+
+ if (this.variables) {
+ _.forEach(this.variables, (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) {
+ let replaced = JSON.stringify(request.headers).replace(new RegExp(placeholder, 'g'), value);
+ request.headers = JSON.parse(replaced);
+ }
+
+ // Replace in queryParams
+ if (request.queryParams) {
+ let replaced = JSON.stringify(request.queryParams).replace(new RegExp(placeholder, 'g'), value);
+ request.queryParams = JSON.parse(replaced);
+ }
+ });
+
+ return request;
+ }
+
+ executePreRequestScript(model) {
+ return new Promise((resolve, reject) => {
+ let script = model.preRequestScript;
+ let resultFrame = document.getElementById('preRequest');
+
+ this.variables = {};
+ model.variables.forEach((item) => {
+ if (item.enabled) {
+ this.variables[item.key] = item.value;
+ }
+ });
+
+ window.preRequestComplete = (updatedModel, error) => {
+ if (error) {
+ reject('PreRequestScript error:', error);
+ } else {
+ resolve(updatedModel);
+ }
+ };
+
+ window.temp_variable = this.variables;
+ window.currentRequest = model;
+ resultFrame.srcdoc = ``;
+ });
+ }
+
+ executePostRequestScript(script, response) {
+ return new Promise((resolve, reject) => {
+ let resultFrame = document.getElementById('postRequest');
+ window.response = response;
+ window.temp_variable = this.variables;
+ window.postRequestComplete = (updatedModel, error) => {
+ if (error) {
+ reject('PreRequestScript error:', error);
+ } else {
+ resolve(updatedModel);
+ }
+ };
+
+ resultFrame.srcdoc = ``;
+ });
+ }
+}
\ No newline at end of file