api client body 처리 수정

This commit is contained in:
현성필
2023-05-09 15:35:05 +09:00
parent aea3c9dc7a
commit 60b8d7b19c
3 changed files with 204 additions and 163 deletions
@@ -10,7 +10,7 @@ public class ApiRequestDTO {
private Long server; private Long server;
private List<ApiHeaderDTO> headers; private List<ApiHeaderDTO> headers;
private String body; private String requestBody;
public String getPath() { public String getPath() {
return path; return path;
@@ -44,11 +44,11 @@ public class ApiRequestDTO {
this.headers = headers; this.headers = headers;
} }
public String getBody() { public String getRequestBody() {
return body; return requestBody;
} }
public void setBody(String body) { public void setRequestBody(String requestBody) {
this.body = body; this.requestBody = requestBody;
} }
} }
@@ -5,6 +5,7 @@ import com.eactive.httpmockserver.client.dto.ApiResponse;
import com.eactive.httpmockserver.client.entity.Server; import com.eactive.httpmockserver.client.entity.Server;
import com.eactive.httpmockserver.client.repository.ServerRepository; import com.eactive.httpmockserver.client.repository.ServerRepository;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
@@ -16,6 +17,8 @@ import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.util.CharsetUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -44,7 +47,7 @@ public class NettyApiClient {
.handler(new ChannelInitializer<Channel>() { .handler(new ChannelInitializer<Channel>() {
@Override @Override
public void initChannel(Channel ch) { public void initChannel(Channel ch) {
if (server.getScheme().equalsIgnoreCase("https")){ if (server.getScheme().equalsIgnoreCase("https")) {
try { try {
final SslContext sslCtx = SslContextBuilder.forClient() final SslContext sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build(); .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
@@ -62,13 +65,31 @@ public class NettyApiClient {
}); });
Channel ch = b.connect(host, port).sync().channel(); Channel ch = b.connect(host, port).sync().channel();
ByteBuf content = null;
if (StringUtils.isNotEmpty(apiRequest.getRequestBody())) {
content = Unpooled.copiedBuffer(apiRequest.getRequestBody(), CharsetUtil.UTF_8);
} else {
content = Unpooled.EMPTY_BUFFER;
}
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(apiRequest.getMethod()), HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(apiRequest.getMethod()),
apiRequest.getPath(), Unpooled.EMPTY_BUFFER); apiRequest.getPath(), content);
request.headers().set("Host", host); request.headers().set("Host", host);
request.headers().set("Connection", "close"); request.headers().set("Connection", HttpHeaderValues.CLOSE);
request.headers().set("Accept-Encoding", "gzip"); request.headers().set("Accept-Encoding", HttpHeaderValues.GZIP);
if (StringUtils.isNotEmpty(apiRequest.getRequestBody())) {
request.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
}
if (apiRequest.getHeaders().size() > 0) {
HttpHeaders customHeaders = new DefaultHttpHeaders();
apiRequest.getHeaders().forEach((header) -> {
customHeaders.set(header.getKey(), header.getValue());
});
request.headers().add(customHeaders);
}
ch.writeAndFlush(request); ch.writeAndFlush(request);
ch.closeFuture().sync(); ch.closeFuture().sync();
@@ -78,4 +99,6 @@ public class NettyApiClient {
return responseFuture; return responseFuture;
} }
} }
+171 -153
View File
@@ -5,7 +5,8 @@
<section layout:fragment="contentFragment"> <section layout:fragment="contentFragment">
<div id="loading-overlay" style="display: none;"> <div id="loading-overlay" style="display: none;">
<div class="d-flex justify-content-center align-items-center" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9999;"> <div class="d-flex justify-content-center align-items-center"
style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9999;">
<div class="text-center"> <div class="text-center">
<span class="sr-only">Loading...</span> <span class="sr-only">Loading...</span>
<br/> <br/>
@@ -15,135 +16,138 @@
</div> </div>
<div class="container"> <div class="container">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<div class="d-flex align-items-center"> <div class="api_request">
<div class="form-floating col-md-2 me-1"> <div class="d-flex align-items-center">
<select name="method" class="form-select" id="method"> <div class="form-floating col-md-2 me-1">
<option value="POST">POST</option> <select name="method" class="form-select method">
<option value="GET">GET</option> <option value="POST">POST</option>
<option value="PUT">PUT</option> <option value="GET">GET</option>
<option value="DELETE">DELETE</option> <option value="PUT">PUT</option>
<option value="CONNECT">CONNECT</option> <option value="DELETE">DELETE</option>
<option value="HEAD">HEAD</option> <option value="CONNECT">CONNECT</option>
<option value="OPTIONS">OPTIONS</option> <option value="HEAD">HEAD</option>
<option value="TRACE">TRACE</option> <option value="OPTIONS">OPTIONS</option>
<option value="PATCH">PATCH</option> <option value="TRACE">TRACE</option>
</select> <option value="PATCH">PATCH</option>
<label for="method" class="form-label must">Method</label> </select>
</div> <label for="method" class="form-label must">Method</label>
<div class="form-floating col-md-3 me-1"> </div>
<select name="server" class="form-select" id="server"> <div class="form-floating col-md-3 me-1">
<option th:each="server : ${servers}" th:value="${server.id}" <select name="server" class="form-select server">
th:text="${server.name}"></option> <option th:each="server : ${servers}" th:value="${server.id}"
</select> th:text="${server.name}"></option>
<label for="method" class="form-label must">Server</label> </select>
</div> <label for="method" class="form-label must">Server</label>
<div class="input-group col-md"> </div>
<div class="form-floating col-md"> <div class="input-group col-md">
<input type="text" name="path" class="form-control" id="path" placeholder="호출할 경로 (/부터 입력)" required> <div class="form-floating col-md">
<label for="path" class="must">Path</label> <input type="text" name="path" class="form-control path" placeholder="호출할 경로 (/부터 입력)"
required>
<label for="path" class="must">Path</label>
</div>
<button class="btn btn-primary send">Send</button>
</div> </div>
<button class="btn btn-primary" id="send">Send</button>
</div> </div>
</div> <ul class="nav nav-tabs mt-1" role="tablist">
<ul class="nav nav-tabs mt-1" role="tablist"> <li class="nav-item" role="presentation">
<li class="nav-item" role="presentation"> <button class="nav-link active" data-bs-toggle="tab"
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#requestParamTab" type="button" role="tab" aria-controls="paramTab"
data-bs-target="#requestParamTab" type="button" role="tab" aria-controls="paramTab" aria-selected="true">Param
aria-selected="true">Param </button>
</button> </li>
</li> <li class="nav-item" role="presentation">
<li class="nav-item" role="presentation"> <button class="nav-link" data-bs-toggle="tab"
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#requestHeaderTab" type="button" role="tab"
data-bs-target="#requestHeaderTab" type="button" role="tab" aria-controls="requestHeader"
aria-controls="requestHeader" aria-selected="false">Header
aria-selected="false">Header </button>
</button> </li>
</li> <li class="nav-item" role="presentation">
<li class="nav-item" role="presentation"> <button class="nav-link" data-bs-toggle="tab"
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#requestBodyTab" type="button" role="tab" aria-controls="requestBody"
data-bs-target="#requestBodyTab" type="button" role="tab" aria-controls="requestBody" aria-selected="false">Body
aria-selected="false">Body </button>
</button> </li>
</li> </ul>
</ul> <div class="tab-content border-bottom" style="min-height: 300px;">
<div class="tab-content border-bottom" style="min-height: 300px;"> <div class="tab-pane fade show active" id="requestParamTab" role="tabpanel"
<div class="tab-pane fade show active" id="requestParamTab" role="tabpanel" aria-labelledby="requestParams-tab">
aria-labelledby="requestParams-tab"> <table class="table" id="requestMatcherParamTable">
<table class="table" id="requestMatcherParamTable"> <thead>
<thead> <tr>
<tr> <th scope="col">이름</th>
<th scope="col">이름</th> <th></th>
<th></th> <th>
<th> <button type="button" class="btn btn-secondary btn-sm btn_add_query">추가
<button type="button" class="btn btn-secondary btn-sm btn_add_query">추가 </button>
</button> </th>
</th> </tr>
</tr> </thead>
</thead> <tbody class="query_params">
<tbody id="queryParams"> </tbody>
</tbody> </table>
</table> </div>
<div class="tab-pane fade" id="requestHeaderTab" role="tabpanel"
aria-labelledby="requestHeader-tab">
<table class="table">
<thead>
<tr>
<th scope="col">이름</th>
<th scope="col"></th>
<th>
<button type="button" class="btn btn-secondary btn-sm btn_add_header">추가
</button>
</th>
</tr>
</thead>
<tbody class="request_headers">
</tbody>
</table>
</div>
<div class="tab-pane fade" id="requestBodyTab" role="tabpanel"
aria-labelledby="requestBody-tab">
<textarea class="form-control request_body" style="height: 200px;"></textarea>
</div>
</div> </div>
<div class="tab-pane fade" id="requestHeaderTab" role="tabpanel" <div class="mt-1">
aria-labelledby="requestHeader-tab"> <span>Response</span>
<table class="table"> <span class="response_status"></span>
<thead> <span class="response_time"></span>
<tr> <span class="response_size"></span>
<th scope="col">이름</th>
<th scope="col"></th>
<th>
<button type="button" class="btn btn-secondary btn-sm btn_add_header">추가
</button>
</th>
</tr>
</thead>
<tbody id="headers">
</tbody>
</table>
</div> </div>
<div class="tab-pane fade" id="requestBodyTab" role="tabpanel" <ul class="nav nav-tabs mt-1" role="tablist">
aria-labelledby="requestBody-tab"> <li class="nav-item" role="presentation">
<textarea class="form-control" style="height: 200px;" id="requestBody"></textarea> <button class="nav-link active" data-bs-toggle="tab"
</div> data-bs-target="#responseBodyTab" type="button" role="tab" aria-controls="responseBody"
</div> aria-selected="false">Body
<div class="mt-1"> </button>
<span>Response</span> </li>
<span class="response_status"></span> <li class="nav-item" role="presentation">
<span class="response_time"></span> <button class="nav-link" data-bs-toggle="tab"
<span class="response_size"></span> data-bs-target="#responseHeaderTab" type="button" role="tab"
</div> aria-controls="responseHeader"
<ul class="nav nav-tabs mt-1" role="tablist"> aria-selected="false">Header
<li class="nav-item" role="presentation"> </button>
<button class="nav-link active" data-bs-toggle="tab" </li>
data-bs-target="#responseBodyTab" type="button" role="tab" aria-controls="responseBody" </ul>
aria-selected="false">Body <div class="tab-content border-bottom" style="min-height: 250px;">
</button> <div class="tab-pane fade show active" id="responseBodyTab" role="tabpanel"
</li> aria-labelledby="requestParams-tab">
<li class="nav-item" role="presentation"> <textarea style="height: 200px;" id="responseBody"></textarea>
<button class="nav-link" data-bs-toggle="tab" </div>
data-bs-target="#responseHeaderTab" type="button" role="tab" <div class="tab-pane fade" id="responseHeaderTab" role="tabpanel"
aria-controls="responseHeader" aria-labelledby="requestParams-tab">
aria-selected="false">Header <table class="table">
</button> <thead>
</li> <tr>
</ul> <th scope="col">이름</th>
<div class="tab-content border-bottom" style="min-height: 250px;"> <th scope="col"></th>
<div class="tab-pane fade show active" id="responseBodyTab" role="tabpanel" </tr>
aria-labelledby="requestParams-tab"> </thead>
<textarea style="height: 200px;" id="responseBody"></textarea> <tbody class="response_headers">
</div> </tbody>
<div class="tab-pane fade" id="responseHeaderTab" role="tabpanel" </table>
aria-labelledby="requestParams-tab"> </div>
<table class="table">
<thead>
<tr>
<th scope="col">이름</th>
<th scope="col"></th>
</tr>
</thead>
<tbody id="responseHeaders">
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
@@ -218,22 +222,19 @@
} }
}); });
$('#requestBody').on('input', function () { model.server = $('.api_request').find('.server').val();
model.requestBody = $(this).val(); model.method = $('.api_request').find('.method').val();
})
model.server = $('#server').val();
model.method = $('#method').val();
$('#server').on('change', function () { $('.api_request').find('.server').on('change', function () {
model.server = $(this).val(); model.server = $(this).val();
}); });
$('#method').on('change', function () { $('.api_request').find('.method').on('change', function () {
model.method = $(this).val(); model.method = $(this).val();
}); });
$('#path').on('input', function () { $('.api_request').find('.path').on('input', function () {
model.path = $(this).val(); model.path = $(this).val();
model.queryParams = []; model.queryParams = [];
let queryString = model.path.split('?')[1]; let queryString = model.path.split('?')[1];
@@ -246,23 +247,23 @@
} }
}); });
$('#headers').on('click', 'button', function () { $('.api_request').find('.request_headers').on('click', 'button', function () {
if ($(this).hasClass('btn_remove')) { if ($(this).hasClass('btn_remove')) {
controller.removeHeader($(this).attr("data-id")); controller.removeHeader($(this).attr("data-id"));
} }
}); });
$('#queryParams').on('click', 'button', function () { $('.api_request').find('.query_params').on('click', 'button', function () {
if ($(this).hasClass('btn_remove')) { if ($(this).hasClass('btn_remove')) {
controller.removeQueryParam($(this).attr("data-id")); controller.removeQueryParam($(this).attr("data-id"));
} }
}); });
$('#send').click(function () { $('.api_request').find('.send').click(function () {
controller.send(); controller.send();
}); });
this.requestBody = CodeMirror.fromTextArea(document.getElementById('requestBody'), { this.requestBody = CodeMirror.fromTextArea($('.api_request').find(".request_body")[0], {
lineNumbers: true, lineNumbers: true,
mode: "application/json", mode: "application/json",
extraKeys: { extraKeys: {
@@ -275,6 +276,10 @@
} }
}); });
this.requestBody.on('change', function(cm, change) {
model.requestBody = cm.getValue();
})
this.responseBody = CodeMirror.fromTextArea(document.getElementById('responseBody'), { this.responseBody = CodeMirror.fromTextArea(document.getElementById('responseBody'), {
lineNumbers: true, lineNumbers: true,
mode: "application/json", mode: "application/json",
@@ -293,10 +298,10 @@
this.render(); this.render();
}, },
render: function () { render: function () {
$('#headers').empty(); $('.api_request').find('.request_headers').empty();
$('#queryParams').empty(); $('.api_request').find('.query_params').empty();
$('#path').val(model.path); $('.api_request').find('.path').val(model.path);
for (let i = 0; i < model.headers.length; i++) { for (let i = 0; i < model.headers.length; i++) {
let template = $('#inputTemplate').children().find('tr').clone(); let template = $('#inputTemplate').children().find('tr').clone();
@@ -311,7 +316,7 @@
model.headers[i].value = $(this).val(); model.headers[i].value = $(this).val();
}); });
$('#headers').append(template); $('.api_request').find('.request_headers').append(template);
} }
for (let i = 0; i < model.queryParams.length; i++) { for (let i = 0; i < model.queryParams.length; i++) {
@@ -329,13 +334,13 @@
controller.buildPath(); controller.buildPath();
}); });
$('#queryParams').append(template); $('.api_request').find('.query_params').append(template);
} }
}, },
renderResponse: function(){ renderResponse: function () {
$('#responseHeaders').empty(); $('.api_request').find('.response_headers').empty();
$(".response_status").empty(); $('.api_request').find(".response_status").empty();
view.responseBody.setValue(responseModel.body); view.responseBody.setValue(responseModel.body);
@@ -343,13 +348,13 @@
let template = $('#responseHeaderTemplate').children().find('tr').clone(); let template = $('#responseHeaderTemplate').children().find('tr').clone();
template.find('input[name="key"]').val(responseModel.headers[i].key); template.find('input[name="key"]').val(responseModel.headers[i].key);
template.find('input[name="value"]').val(responseModel.headers[i].value); template.find('input[name="value"]').val(responseModel.headers[i].value);
$('#responseHeaders').append(template); $('.api_request').find('.response_headers').append(template);
} }
//show response status //show response status
$(".response_status").text("status: " + responseModel.status); $('.api_request').find(".response_status").text("status: " + responseModel.status);
$(".response_time").text("time: " + responseModel.time + "ms"); $('.api_request').find(".response_time").text("time: " + responseModel.time + "ms");
$(".response_size").text("size: " + responseModel.size + "bytes"); $('.api_request').find(".response_size").text("size: " + responseModel.size + "bytes");
} }
} }
@@ -395,14 +400,15 @@
this.buildPath(); this.buildPath();
view.render(); view.render();
}, },
showLoadingOverlay: function() { showLoadingOverlay: function () {
$("#loading-overlay").show(); $("#loading-overlay").show();
}, },
hideLoadingOverlay: function(){ hideLoadingOverlay: function () {
$("#loading-overlay").hide(); $("#loading-overlay").hide();
}, },
send: function () { send: function () {
$('#responseHeaders').empty(); console.log(model);
$('.api_request').find('.response_headers').empty();
view.responseBody.setValue(''); view.responseBody.setValue('');
let startTime = new Date().getTime(); let startTime = new Date().getTime();
if (model.path !== '') { if (model.path !== '') {
@@ -430,6 +436,18 @@
} }
}); });
} }
},
addCollection: function () {
},
removeCollection: function () {
},
addAPIRequest: function () {
},
removeAPIRequest: function () {
} }
} }