TCP 서버 옵션 추가
This commit is contained in:
@@ -13,11 +13,21 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import MonacoEditor from '@/components/shared/MonacoEditor';
|
||||
|
||||
const defaultScript = `function processJson(message) {
|
||||
//process message;
|
||||
const defaultScript = `function processMessage(message, serviceId) {
|
||||
// Process the message and serviceId
|
||||
console.log("Received message:", message);
|
||||
console.log("Service ID:", serviceId);
|
||||
|
||||
// Return response
|
||||
return "response_message";
|
||||
}`;
|
||||
|
||||
@@ -27,7 +37,11 @@ const TCPServerEditDialog = ({ open, onOpenChange, item }) => {
|
||||
name: '',
|
||||
port: 1024,
|
||||
active: true,
|
||||
script: defaultScript
|
||||
script: defaultScript,
|
||||
prefixSize: 4,
|
||||
includePrefixLength: true,
|
||||
serviceIdLength: 0,
|
||||
serviceIdPosition: 'NONE'
|
||||
});
|
||||
|
||||
// Initialize form when item changes
|
||||
@@ -41,7 +55,11 @@ const TCPServerEditDialog = ({ open, onOpenChange, item }) => {
|
||||
name: '',
|
||||
port: 1024,
|
||||
active: true,
|
||||
script: defaultScript
|
||||
script: defaultScript,
|
||||
prefixSize: 4,
|
||||
includePrefixLength: true,
|
||||
serviceIdLength: 0,
|
||||
serviceIdPosition: 'NONE'
|
||||
});
|
||||
}
|
||||
}, [item]);
|
||||
@@ -93,44 +111,108 @@ const TCPServerEditDialog = ({ open, onOpenChange, item }) => {
|
||||
|
||||
<ScrollArea className="max-h-[80vh]">
|
||||
<form id="serverForm" onSubmit={handleSubmit} className="space-y-4 p-1">
|
||||
{/* Server Name */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name" className="must">
|
||||
서버 이름
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
required
|
||||
value={server.name}
|
||||
onChange={(e) => handleChange('name', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{/* Basic Server Configuration */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>기본 설정</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Server Name */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name" className="must">서버 이름</Label>
|
||||
<Input
|
||||
id="name"
|
||||
required
|
||||
value={server.name}
|
||||
onChange={(e) => handleChange('name', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Port Number */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="port" className="must">
|
||||
포트
|
||||
</Label>
|
||||
<Input
|
||||
id="port"
|
||||
type="number"
|
||||
required
|
||||
min="1024"
|
||||
max="65535"
|
||||
value={server.port}
|
||||
onChange={(e) => handleChange('port', parseInt(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
{/* Port Number */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="port" className="must">포트</Label>
|
||||
<Input
|
||||
id="port"
|
||||
type="number"
|
||||
required
|
||||
min="1024"
|
||||
max="65535"
|
||||
value={server.port}
|
||||
onChange={(e) => handleChange('port', parseInt(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Active Status */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="active"
|
||||
checked={server.active}
|
||||
onCheckedChange={(checked) => handleChange('active', checked)}
|
||||
/>
|
||||
<Label htmlFor="active">Active</Label>
|
||||
</div>
|
||||
{/* Active Status */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="active"
|
||||
checked={server.active}
|
||||
onCheckedChange={(checked) => handleChange('active', checked)}
|
||||
/>
|
||||
<Label htmlFor="active">Active</Label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Message Configuration */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>메시지 설정</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Prefix Size */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="prefixSize">Prefix Size</Label>
|
||||
<Input
|
||||
id="prefixSize"
|
||||
type="number"
|
||||
min="0"
|
||||
value={server.prefixSize}
|
||||
onChange={(e) => handleChange('prefixSize', parseInt(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Include Prefix Length */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="includePrefixLength"
|
||||
checked={server.includePrefixLength}
|
||||
onCheckedChange={(checked) => handleChange('includePrefixLength', checked)}
|
||||
/>
|
||||
<Label htmlFor="includePrefixLength">Include Prefix Length</Label>
|
||||
</div>
|
||||
|
||||
{/* Service ID Length */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="serviceIdLength">Service ID Length</Label>
|
||||
<Input
|
||||
id="serviceIdLength"
|
||||
type="number"
|
||||
min="0"
|
||||
value={server.serviceIdLength}
|
||||
onChange={(e) => handleChange('serviceIdLength', parseInt(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Service ID Position */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="serviceIdPosition">Service ID Position</Label>
|
||||
<Select
|
||||
value={server.serviceIdPosition}
|
||||
onValueChange={(value) => handleChange('serviceIdPosition', value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select position" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="NONE">None</SelectItem>
|
||||
<SelectItem value="START">Start</SelectItem>
|
||||
<SelectItem value="END">End</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Script Editor */}
|
||||
<Card>
|
||||
|
||||
@@ -10,17 +10,17 @@ import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
|
||||
import com.eactive.testmaster.socket.entity.NettyServerEntity;
|
||||
|
||||
public class NettyServer {
|
||||
private final int port;
|
||||
private final NettyServerEntity config;
|
||||
private EventLoopGroup bossGroup;
|
||||
private EventLoopGroup workerGroup;
|
||||
private Channel serverChannel;
|
||||
private volatile boolean isInitialized = false;
|
||||
private final String script;
|
||||
|
||||
public NettyServer(int port, String script) {
|
||||
this.port = port;
|
||||
this.script = script;
|
||||
public NettyServer(NettyServerEntity config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public synchronized void start() throws InterruptedException {
|
||||
@@ -29,7 +29,6 @@ public class NettyServer {
|
||||
}
|
||||
|
||||
try {
|
||||
// Use custom thread factories to avoid naming conflicts
|
||||
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("netty-boss"));
|
||||
workerGroup = new NioEventLoopGroup(
|
||||
Runtime.getRuntime().availableProcessors() * 2,
|
||||
@@ -45,20 +44,20 @@ public class NettyServer {
|
||||
.childHandler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) {
|
||||
ch.pipeline().addLast(new NettySocketHandler(script));
|
||||
ch.pipeline().addLast(new NettySocketHandler(config.getScript(), config));
|
||||
}
|
||||
});
|
||||
|
||||
// Bind and wait for the server socket
|
||||
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
|
||||
ChannelFuture channelFuture = serverBootstrap.bind(config.getPort()).sync();
|
||||
if (!channelFuture.isSuccess()) {
|
||||
throw new RuntimeException("Failed to bind to port " + port);
|
||||
throw new RuntimeException("Failed to bind to port " + config.getPort());
|
||||
}
|
||||
|
||||
serverChannel = channelFuture.channel();
|
||||
isInitialized = true;
|
||||
|
||||
System.out.println("Netty server started on port " + port);
|
||||
System.out.println("Netty server started on port " + config.getPort());
|
||||
} catch (Exception e) {
|
||||
// Clean up resources if initialization fails
|
||||
stop();
|
||||
@@ -86,7 +85,11 @@ public class NettyServer {
|
||||
}
|
||||
}
|
||||
isInitialized = false;
|
||||
System.out.println("Netty server stopped on port " + port);
|
||||
System.out.println("Netty server stopped on port " + config.getPort());
|
||||
}
|
||||
}
|
||||
|
||||
public NettyServerEntity getConfig() {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.eactive.testmaster.socket;
|
||||
|
||||
import com.eactive.testmaster.socket.entity.NettyServerEntity;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
@@ -15,8 +16,10 @@ public class NettySocketHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(NettySocketHandler.class);
|
||||
private final ScriptEngine engine;
|
||||
private final NettyServerEntity config;
|
||||
|
||||
public NettySocketHandler(String scriptContent) {
|
||||
public NettySocketHandler(String scriptContent, NettyServerEntity config) {
|
||||
this.config = config;
|
||||
ScriptEngineManager manager = new ScriptEngineManager();
|
||||
this.engine = manager.getEngineByName("nashorn");
|
||||
try {
|
||||
@@ -44,7 +47,6 @@ public class NettySocketHandler extends ChannelInboundHandlerAdapter {
|
||||
logger.debug("Received new message from client: {}", ctx.channel().remoteAddress());
|
||||
logger.debug("Total readable bytes: {}", in.readableBytes());
|
||||
|
||||
// Read the entire message as it comes
|
||||
byte[] messageBytes = new byte[in.readableBytes()];
|
||||
in.readBytes(messageBytes);
|
||||
String message = new String(messageBytes);
|
||||
@@ -53,15 +55,18 @@ public class NettySocketHandler extends ChannelInboundHandlerAdapter {
|
||||
logger.debug("- Raw message: {}", message);
|
||||
logger.debug("- Hex dump: {}", bytesToHex(messageBytes));
|
||||
|
||||
// Process the message, removing the length prefix
|
||||
String messageContent = message.substring(4);
|
||||
logger.debug("- Message content (without length): {}", messageContent);
|
||||
// Extract message content based on configuration
|
||||
String messageContent = extractMessageContent(message);
|
||||
String serviceId = extractServiceId(message);
|
||||
|
||||
logger.debug("- Message content: {}", messageContent);
|
||||
logger.debug("- Service ID: {}", serviceId);
|
||||
|
||||
String result;
|
||||
try {
|
||||
logger.debug("Executing script processor for message");
|
||||
Invocable invocable = (Invocable) engine;
|
||||
Object jsResult = invocable.invokeFunction("processMessage", messageContent);
|
||||
Object jsResult = invocable.invokeFunction("processMessage", messageContent, serviceId);
|
||||
result = String.valueOf(jsResult);
|
||||
logger.debug("Script processing successful. Result: {}", result);
|
||||
} catch (ScriptException | NoSuchMethodException e) {
|
||||
@@ -70,20 +75,17 @@ public class NettySocketHandler extends ChannelInboundHandlerAdapter {
|
||||
logger.error("Script execution failed. Using error message as result: {}", result);
|
||||
}
|
||||
|
||||
// Format response with length prefix
|
||||
byte[] responseBytes = result.getBytes();
|
||||
String lengthPrefix = String.format("%04d", responseBytes.length + 4);
|
||||
// Format response with configured prefix
|
||||
byte[] responseBytes = formatResponse(result, serviceId);
|
||||
logger.debug("Preparing response:");
|
||||
logger.debug("- Response content: {}", result);
|
||||
logger.debug("- Response length: {}", responseBytes.length);
|
||||
logger.debug("- Length prefix: {}", lengthPrefix);
|
||||
|
||||
ByteBuf response = ctx.alloc().buffer(4 + responseBytes.length);
|
||||
response.writeBytes(lengthPrefix.getBytes());
|
||||
ByteBuf response = ctx.alloc().buffer(responseBytes.length);
|
||||
response.writeBytes(responseBytes);
|
||||
|
||||
logger.debug("Sending response to client: {}", ctx.channel().remoteAddress());
|
||||
logger.debug("- Total response size: {} bytes", 4 + responseBytes.length);
|
||||
logger.debug("- Total response size: {} bytes", responseBytes.length);
|
||||
ctx.writeAndFlush(response);
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -95,9 +97,71 @@ public class NettySocketHandler extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to convert byte array to hexadecimal string for logging
|
||||
*/
|
||||
private String extractMessageContent(String message) {
|
||||
int prefixSize = config.getPrefixSize().intValue();
|
||||
int serviceIdLength = config.getServiceIdLength().intValue();
|
||||
|
||||
String content = message.substring(prefixSize);
|
||||
|
||||
if (serviceIdLength > 0) {
|
||||
if (config.getServiceIdPosition() == NettyServerEntity.ServiceIdPosition.START) {
|
||||
content = content.substring(serviceIdLength);
|
||||
} else if (config.getServiceIdPosition() == NettyServerEntity.ServiceIdPosition.END) {
|
||||
content = content.substring(0, content.length() - serviceIdLength);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private String extractServiceId(String message) {
|
||||
int prefixSize = config.getPrefixSize().intValue();
|
||||
int serviceIdLength = config.getServiceIdLength().intValue();
|
||||
|
||||
if (serviceIdLength == 0 || config.getServiceIdPosition() == NettyServerEntity.ServiceIdPosition.NONE) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (config.getServiceIdPosition() == NettyServerEntity.ServiceIdPosition.START) {
|
||||
return message.substring(prefixSize, prefixSize + serviceIdLength);
|
||||
} else {
|
||||
return message.substring(message.length() - serviceIdLength);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] formatResponse(String result, String serviceId) {
|
||||
byte[] resultBytes = result.getBytes();
|
||||
byte[] serviceIdBytes = serviceId.getBytes();
|
||||
|
||||
int contentLength = resultBytes.length +
|
||||
(config.getServiceIdLength().intValue() > 0 ? serviceIdBytes.length : 0);
|
||||
|
||||
int totalLength = config.getIncludePrefixLength() ?
|
||||
contentLength + config.getPrefixSize().intValue() :
|
||||
contentLength;
|
||||
|
||||
String lengthPrefix = String.format("%0" + config.getPrefixSize() + "d", totalLength);
|
||||
|
||||
ByteBuf buffer = io.netty.buffer.Unpooled.buffer();
|
||||
buffer.writeBytes(lengthPrefix.getBytes());
|
||||
|
||||
if (config.getServiceIdPosition() == NettyServerEntity.ServiceIdPosition.START) {
|
||||
buffer.writeBytes(serviceIdBytes);
|
||||
buffer.writeBytes(resultBytes);
|
||||
} else {
|
||||
buffer.writeBytes(resultBytes);
|
||||
if (config.getServiceIdPosition() == NettyServerEntity.ServiceIdPosition.END) {
|
||||
buffer.writeBytes(serviceIdBytes);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] responseBytes = new byte[buffer.readableBytes()];
|
||||
buffer.readBytes(responseBytes);
|
||||
buffer.release();
|
||||
|
||||
return responseBytes;
|
||||
}
|
||||
|
||||
private String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
@@ -30,12 +32,31 @@ public class NettyServerEntity {
|
||||
@Lob
|
||||
private String script;
|
||||
|
||||
@Column(nullable = false, columnDefinition = "BIGINT DEFAULT 4")
|
||||
private Long prefixSize = 4L;
|
||||
|
||||
@Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT TRUE")
|
||||
private Boolean includePrefixLength = true;
|
||||
|
||||
@Column(nullable = false, columnDefinition = "BIGINT DEFAULT 0")
|
||||
private Long serviceIdLength = 0L;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, columnDefinition = "VARCHAR(10) DEFAULT 'NONE'")
|
||||
private ServiceIdPosition serviceIdPosition = ServiceIdPosition.NONE;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||
private LocalDateTime createdAt = LocalDateTime.now();
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||
private LocalDateTime lastModified = LocalDateTime.now();
|
||||
|
||||
public enum ServiceIdPosition {
|
||||
NONE,
|
||||
START,
|
||||
END
|
||||
}
|
||||
|
||||
// Standard getters and setters
|
||||
public Long getId() {
|
||||
return id;
|
||||
@@ -97,4 +118,36 @@ public class NettyServerEntity {
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getPrefixSize() {
|
||||
return prefixSize;
|
||||
}
|
||||
|
||||
public void setPrefixSize(Long prefixSize) {
|
||||
this.prefixSize = prefixSize;
|
||||
}
|
||||
|
||||
public Boolean getIncludePrefixLength() {
|
||||
return includePrefixLength;
|
||||
}
|
||||
|
||||
public void setIncludePrefixLength(Boolean includePrefixLength) {
|
||||
this.includePrefixLength = includePrefixLength;
|
||||
}
|
||||
|
||||
public Long getServiceIdLength() {
|
||||
return serviceIdLength;
|
||||
}
|
||||
|
||||
public void setServiceIdLength(Long serviceIdLength) {
|
||||
this.serviceIdLength = serviceIdLength;
|
||||
}
|
||||
|
||||
public ServiceIdPosition getServiceIdPosition() {
|
||||
return serviceIdPosition;
|
||||
}
|
||||
|
||||
public void setServiceIdPosition(ServiceIdPosition serviceIdPosition) {
|
||||
this.serviceIdPosition = serviceIdPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class NettyServerService {
|
||||
}
|
||||
|
||||
if (!activeServers.containsKey(entity.getPort())) {
|
||||
NettyServer server = new NettyServer(entity.getPort(), entity.getScript());
|
||||
NettyServer server = new NettyServer(entity);
|
||||
server.start();
|
||||
activeServers.put(entity.getPort(), server);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user