diff --git a/src/main/java/com/eactive/testmaster/client/service/HttpHandler.java b/src/main/java/com/eactive/testmaster/client/service/HttpHandler.java index ab16072..c5be497 100644 --- a/src/main/java/com/eactive/testmaster/client/service/HttpHandler.java +++ b/src/main/java/com/eactive/testmaster/client/service/HttpHandler.java @@ -7,13 +7,31 @@ 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.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; public class HttpHandler extends SimpleChannelInboundHandler { + public static final Pattern CHARSET_PATTERN = Pattern.compile("charset=([^;]+)"); + private static final Charset DEFAULT_CHARSET = determineDefaultCharset(); + + private static Charset determineDefaultCharset() { + try { + String systemEncoding = System.getProperty("file.encoding"); + if (StringUtils.isNotBlank(systemEncoding)) { + return Charset.forName(systemEncoding); + } + } catch (IllegalArgumentException | SecurityException e) { + // Log warning about fallback to UTF-8 + } + return CharsetUtil.UTF_8; + } private final CompletableFuture responseFuture; public HttpHandler(CompletableFuture responseFuture) { @@ -26,7 +44,8 @@ public class HttpHandler extends SimpleChannelInboundHandler { ApiResponse apiResponse = new ApiResponse(); apiResponse.setStatus(response.status().code()); apiResponse.setHeaders(convertHttpHeadersToList(response.headers())); - apiResponse.setBody(response.content().toString(CharsetUtil.UTF_8)); + Charset charset = extractCharset(response.headers()); + apiResponse.setBody(response.content().toString(charset)); int headerSize = calculateHeaderSize(response.headers()); int contentSize = response.content().readableBytes(); @@ -36,6 +55,25 @@ public class HttpHandler extends SimpleChannelInboundHandler { responseFuture.complete(apiResponse); } + private Charset extractCharset(HttpHeaders headers) { + String contentType = headers.get("Content-Type"); + + if (contentType != null) { + Matcher matcher = CHARSET_PATTERN.matcher(contentType.toLowerCase()); + if (matcher.find()) { + try { + return Charset.forName(matcher.group(1).trim()); + } catch (IllegalArgumentException e) { + // Log warning about unsupported charset + return DEFAULT_CHARSET; + } + } + } + + return DEFAULT_CHARSET; + } + + private int calculateHeaderSize(HttpHeaders headers) { int headerSize = 0; for (Map.Entry entry : headers.entries()) { diff --git a/src/main/java/com/eactive/testmaster/client/service/NettyHttpApiClient.java b/src/main/java/com/eactive/testmaster/client/service/NettyHttpApiClient.java index 2fe43ef..9c992af 100644 --- a/src/main/java/com/eactive/testmaster/client/service/NettyHttpApiClient.java +++ b/src/main/java/com/eactive/testmaster/client/service/NettyHttpApiClient.java @@ -29,8 +29,7 @@ import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import io.netty.util.CharsetUtil; -import java.net.URLEncoder; +import java.nio.charset.Charset; import java.util.concurrent.CompletableFuture; import javax.net.ssl.SSLException; import org.apache.commons.lang3.StringUtils; @@ -80,7 +79,8 @@ public class NettyHttpApiClient implements ApiClient { Channel ch = b.connect(host, port).sync().channel(); ByteBuf content; if (StringUtils.isNotEmpty(apiRequest.getRequestBody())) { - content = Unpooled.copiedBuffer(apiRequest.getRequestBody(), CharsetUtil.UTF_8); + Charset charset = RequestCharsetUtils.extractRequestCharset(apiRequest); + content = Unpooled.copiedBuffer(apiRequest.getRequestBody(), charset ); } else { content = Unpooled.EMPTY_BUFFER; } @@ -116,8 +116,6 @@ public class NettyHttpApiClient implements ApiClient { request.headers().add(customHeaders); } -// apiRequest.setSentBytes(request.toString().length()); - ch.writeAndFlush(request); ch.closeFuture().sync(); } catch (Exception e) { diff --git a/src/main/java/com/eactive/testmaster/client/service/RequestCharsetUtils.java b/src/main/java/com/eactive/testmaster/client/service/RequestCharsetUtils.java new file mode 100644 index 0000000..394bde1 --- /dev/null +++ b/src/main/java/com/eactive/testmaster/client/service/RequestCharsetUtils.java @@ -0,0 +1,80 @@ +package com.eactive.testmaster.client.service; + +import com.eactive.testmaster.client.dto.ApiRequestDTO; +import com.eactive.testmaster.client.dto.ApiRequestKeyValueDTO; +import io.netty.util.CharsetUtil; +import java.nio.charset.Charset; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; + +public class RequestCharsetUtils { + private static final Pattern CHARSET_PATTERN = Pattern.compile("charset\\s*=\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE); + private static final String CONTENT_TYPE_HEADER = "Content-Type"; + private static final Charset DEFAULT_CHARSET = determineDefaultCharset(); + + /** + * Determines the default charset based on system properties + * Falls back to UTF-8 if system encoding is not available or invalid + */ + private static Charset determineDefaultCharset() { + try { + String systemEncoding = System.getProperty("file.encoding"); + if (StringUtils.isNotBlank(systemEncoding)) { + return Charset.forName(systemEncoding); + } + } catch (IllegalArgumentException | SecurityException e) { + // Log warning about fallback to UTF-8 + } + return CharsetUtil.UTF_8; + } + + /** + * Extracts charset from request headers with fallback to system default charset + * @param apiRequest The API request containing headers + * @return The detected charset or system default charset + */ + public static Charset extractRequestCharset(ApiRequestDTO apiRequest) { + if (apiRequest == null || apiRequest.getHeaders() == null) { + return DEFAULT_CHARSET; + } + + return apiRequest.getHeaders().stream() + .filter(ApiRequestKeyValueDTO::isEnabled) + .filter(header -> CONTENT_TYPE_HEADER.equalsIgnoreCase(header.getKey())) + .findFirst() + .map(RequestCharsetUtils::parseCharsetFromContentType) + .orElse(DEFAULT_CHARSET); + } + + /** + * Parses charset from Content-Type header value + * @param contentTypeHeader The Content-Type header DTO + * @return The detected charset or system default charset + */ + private static Charset parseCharsetFromContentType(ApiRequestKeyValueDTO contentTypeHeader) { + String contentType = contentTypeHeader.getValue(); + if (StringUtils.isBlank(contentType)) { + return DEFAULT_CHARSET; + } + + Matcher matcher = CHARSET_PATTERN.matcher(contentType); + if (matcher.find()) { + String charsetName = matcher.group(1).trim(); + try { + return Charset.forName(charsetName); + } catch (IllegalArgumentException e) { + // Log warning about unsupported charset + return DEFAULT_CHARSET; + } + } + + // Handle common content types without explicit charset + if (contentType.contains("application/json")) { + return CharsetUtil.UTF_8; // JSON should always use UTF-8 per RFC 8259 + } + + return DEFAULT_CHARSET; + } + +}