From f2f235d2eebb8e1ec2185d3d4b5f3fd33503ad9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=98=84=EC=84=B1=ED=95=84?= Date: Tue, 9 May 2023 14:36:33 +0900 Subject: [PATCH] =?UTF-8?q?APITester=20=EA=B8=B0=EB=B3=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ApiClientController.java | 38 +- .../client/dto/ApiHeaderDTO.java | 23 + .../httpmockserver/client/dto/ApiRequest.java | 9 - .../client/dto/ApiRequestDTO.java | 54 +++ .../client/dto/ApiResponse.java | 42 ++ .../client/service/HttptHandler.java | 69 +++ .../client/service/NettyApiClient.java | 67 +++ src/main/resources/static/css/custom.css | 6 +- .../resources/templates/fragment/header.html | 5 +- .../templates/layout/api_tester_layout.html | 35 ++ .../templates/page/apiCollection.html | 61 +++ .../resources/templates/page/apiTester.html | 418 +++++++++++------- 12 files changed, 639 insertions(+), 188 deletions(-) create mode 100644 src/main/java/com/eactive/httpmockserver/client/dto/ApiHeaderDTO.java delete mode 100644 src/main/java/com/eactive/httpmockserver/client/dto/ApiRequest.java create mode 100644 src/main/java/com/eactive/httpmockserver/client/dto/ApiRequestDTO.java create mode 100644 src/main/java/com/eactive/httpmockserver/client/service/HttptHandler.java create mode 100644 src/main/java/com/eactive/httpmockserver/client/service/NettyApiClient.java create mode 100644 src/main/resources/templates/layout/api_tester_layout.html create mode 100644 src/main/resources/templates/page/apiCollection.html diff --git a/src/main/java/com/eactive/httpmockserver/client/controller/ApiClientController.java b/src/main/java/com/eactive/httpmockserver/client/controller/ApiClientController.java index 54e3162..721cd1f 100644 --- a/src/main/java/com/eactive/httpmockserver/client/controller/ApiClientController.java +++ b/src/main/java/com/eactive/httpmockserver/client/controller/ApiClientController.java @@ -1,28 +1,56 @@ package com.eactive.httpmockserver.client.controller; +import com.eactive.httpmockserver.client.dto.ApiRequestDTO; +import com.eactive.httpmockserver.client.dto.ApiResponse; +import com.eactive.httpmockserver.client.entity.ApiCollection; import com.eactive.httpmockserver.client.entity.Server; import com.eactive.httpmockserver.client.repository.ServerRepository; +import com.eactive.httpmockserver.client.service.ApiTesterService; +import com.eactive.httpmockserver.client.service.NettyApiClient; +import com.eactive.httpmockserver.common.security.CurrentUser; +import com.eactive.httpmockserver.user.entity.StaffUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; import java.util.List; @Controller -@RequestMapping("/mgmt") public class ApiClientController { @Autowired - ServerRepository serverRepository; + private ServerRepository serverRepository; - @GetMapping("/api_tester.do") - public String testView(ModelMap model){ + @Autowired + private ApiTesterService apiTesterService; + + @Autowired + private NettyApiClient nettyApiClient; + + @PostMapping("/api/test.do") + @ResponseBody + public ApiResponse handleApiRequest(@RequestBody ApiRequestDTO request) { + + try { + return nettyApiClient.handleRequest(request).get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @GetMapping("/mgmt/api_tester.do") + public String testView(ModelMap model, + @CurrentUser StaffUser user) { List servers = serverRepository.findAll(); + List collections = apiTesterService.findMyCollections(user.getEsntlId()); model.addAttribute("servers", servers); + model.addAttribute("collections", collections); return "page/apiTester"; } diff --git a/src/main/java/com/eactive/httpmockserver/client/dto/ApiHeaderDTO.java b/src/main/java/com/eactive/httpmockserver/client/dto/ApiHeaderDTO.java new file mode 100644 index 0000000..82fdba3 --- /dev/null +++ b/src/main/java/com/eactive/httpmockserver/client/dto/ApiHeaderDTO.java @@ -0,0 +1,23 @@ +package com.eactive.httpmockserver.client.dto; + +public class ApiHeaderDTO { + + private String key; + private String value; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/eactive/httpmockserver/client/dto/ApiRequest.java b/src/main/java/com/eactive/httpmockserver/client/dto/ApiRequest.java deleted file mode 100644 index e1fe891..0000000 --- a/src/main/java/com/eactive/httpmockserver/client/dto/ApiRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.eactive.httpmockserver.client.dto; - -public class ApiRequest { - - private String uri; - private String method; - - private String body; -} diff --git a/src/main/java/com/eactive/httpmockserver/client/dto/ApiRequestDTO.java b/src/main/java/com/eactive/httpmockserver/client/dto/ApiRequestDTO.java new file mode 100644 index 0000000..410414f --- /dev/null +++ b/src/main/java/com/eactive/httpmockserver/client/dto/ApiRequestDTO.java @@ -0,0 +1,54 @@ +package com.eactive.httpmockserver.client.dto; + +import java.util.List; + +public class ApiRequestDTO { + + private String path; + private String method; + + private Long server; + + private List headers; + private String body; + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Long getServer() { + return server; + } + + public void setServer(Long server) { + this.server = server; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public List getHeaders() { + return headers; + } + + public void setHeaders(List headers) { + this.headers = headers; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } +} diff --git a/src/main/java/com/eactive/httpmockserver/client/dto/ApiResponse.java b/src/main/java/com/eactive/httpmockserver/client/dto/ApiResponse.java index 5e44792..54fb547 100644 --- a/src/main/java/com/eactive/httpmockserver/client/dto/ApiResponse.java +++ b/src/main/java/com/eactive/httpmockserver/client/dto/ApiResponse.java @@ -1,4 +1,46 @@ package com.eactive.httpmockserver.client.dto; +import java.util.List; + public class ApiResponse { + + private int status; + + private List headers; + + private String body; + + private int size; + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public List getHeaders() { + return headers; + } + + public void setHeaders(List headers) { + this.headers = headers; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } } diff --git a/src/main/java/com/eactive/httpmockserver/client/service/HttptHandler.java b/src/main/java/com/eactive/httpmockserver/client/service/HttptHandler.java new file mode 100644 index 0000000..2f63099 --- /dev/null +++ b/src/main/java/com/eactive/httpmockserver/client/service/HttptHandler.java @@ -0,0 +1,69 @@ +package com.eactive.httpmockserver.client.service; + +import com.eactive.httpmockserver.client.dto.ApiHeaderDTO; +import com.eactive.httpmockserver.client.dto.ApiResponse; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.util.CharsetUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class HttptHandler extends SimpleChannelInboundHandler { + + private final CompletableFuture responseFuture; + + public HttptHandler(CompletableFuture responseFuture) { + this.responseFuture = responseFuture; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse response) { + + ApiResponse apiResponse = new ApiResponse(); + apiResponse.setStatus(response.status().code()); + apiResponse.setHeaders(convertHttpHeadersToList(response.headers())); + apiResponse.setBody(response.content().toString(CharsetUtil.UTF_8)); + + int headerSize = calculateHeaderSize(response.headers()); + int contentSize = response.content().readableBytes(); + int totalSize = headerSize + contentSize; + apiResponse.setSize(totalSize); + + responseFuture.complete(apiResponse); + } + + private int calculateHeaderSize(HttpHeaders headers) { + int headerSize = 0; + for (Map.Entry entry : headers.entries()) { + headerSize += entry.getKey().length() + entry.getValue().length() + 4; // Add 2 for ': ' and 2 for '\r\n' + } + + // Add 2 for the final '\r\n' after the headers + headerSize += 2; + + return headerSize; + } + + public List convertHttpHeadersToList(HttpHeaders headers) { + List headerList = new ArrayList<>(); + for (Map.Entry entry : headers.entries()) { + ApiHeaderDTO header = new ApiHeaderDTO(); + header.setKey(entry.getKey()); + header.setValue(entry.getValue()); + headerList.add(header); + } + + return headerList; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + responseFuture.completeExceptionally(cause); + ctx.close(); + } +} diff --git a/src/main/java/com/eactive/httpmockserver/client/service/NettyApiClient.java b/src/main/java/com/eactive/httpmockserver/client/service/NettyApiClient.java new file mode 100644 index 0000000..ca76ee9 --- /dev/null +++ b/src/main/java/com/eactive/httpmockserver/client/service/NettyApiClient.java @@ -0,0 +1,67 @@ +package com.eactive.httpmockserver.client.service; + +import com.eactive.httpmockserver.client.dto.ApiRequestDTO; +import com.eactive.httpmockserver.client.dto.ApiResponse; +import com.eactive.httpmockserver.client.entity.Server; +import com.eactive.httpmockserver.client.repository.ServerRepository; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.concurrent.CompletableFuture; + +@Service +public class NettyApiClient { + + @Autowired + ServerRepository serverRepository; + + public CompletableFuture handleRequest(ApiRequestDTO apiRequest) throws Exception { + + Server server = serverRepository.findById(apiRequest.getServer()).orElseThrow(() -> new Exception("Server not found")); + String host = server.getHostname(); + int port = server.getPort(); + + CompletableFuture responseFuture = new CompletableFuture<>(); + EventLoopGroup group = new NioEventLoopGroup(); + try { + Bootstrap b = new Bootstrap() + .group(group) + .channel(NioSocketChannel.class) + .option(ChannelOption.TCP_NODELAY, true) + .handler(new ChannelInitializer() { + @Override + public void initChannel(Channel ch) { + ch.pipeline().addLast(new HttpClientCodec()); + ch.pipeline().addLast(new HttpObjectAggregator(65536)); + ch.pipeline().addLast(new HttpContentDecompressor()); + ch.pipeline().addLast(new HttptHandler(responseFuture)); + } + }); + + Channel ch = b.connect(host, port).sync().channel(); + + DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(apiRequest.getMethod()), + apiRequest.getPath(), Unpooled.EMPTY_BUFFER); + + request.headers().set("Host", host); + request.headers().set("Connection", "close"); + request.headers().set("Accept-Encoding", "gzip"); + + ch.writeAndFlush(request); + ch.closeFuture().sync(); + } finally { + group.shutdownGracefully(); + } + + return responseFuture; + } +} diff --git a/src/main/resources/static/css/custom.css b/src/main/resources/static/css/custom.css index ca6c8fa..74d0e51 100644 --- a/src/main/resources/static/css/custom.css +++ b/src/main/resources/static/css/custom.css @@ -1507,9 +1507,9 @@ figure div span { } -.CodeMirror { - margin-bottom: 0.1rem !important; -} +/*.CodeMirror {*/ +/* margin-bottom: 0.1rem !important;*/ +/*}*/ .CodeMirror-fullscreen { position: fixed !important; diff --git a/src/main/resources/templates/fragment/header.html b/src/main/resources/templates/fragment/header.html index 77de41d..245cde1 100644 --- a/src/main/resources/templates/fragment/header.html +++ b/src/main/resources/templates/fragment/header.html @@ -15,10 +15,10 @@