feat(core): ServletUtils 新增 isMultipart、isForm、isStream 方法

This commit is contained in:
2025-12-30 23:07:18 +08:00
parent 25fb9e0a27
commit 580aa00af9
4 changed files with 46 additions and 49 deletions

View File

@@ -18,6 +18,7 @@ package top.continew.starter.core.util;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
@@ -189,5 +190,38 @@ public class ServletUtils extends JakartaServletUtil {
write(response, data, MediaType.APPLICATION_JSON_VALUE);
}
/**
* 检查请求是否为 {@code multipart/form-data} 格式(常用于文件上传)
*
* @param request 请求对象
* @return true: 是; false: 否
* @since 2.15.1
*/
public static boolean isMultipart(HttpServletRequest request) {
return StrUtil.startWithIgnoreCase(request.getContentType(), "multipart/");
}
/**
* 检查 HTTP 请求是否为 {@code application/x-www-form-urlencoded} 格式(标准表单提交)
*
* @param request 请求对象
* @return true: 是; false: 否
* @see MediaType#APPLICATION_FORM_URLENCODED_VALUE
* @since 2.15.1
*/
public static boolean isForm(HttpServletRequest request) {
return StrUtil.contains(request.getContentType(), MediaType.APPLICATION_FORM_URLENCODED_VALUE);
}
/**
* 检查 HTTP 响应是否为 {@code Server-Sent Events (SSE)} 流格式
*
* @param response 响应对象
* @return true: 是; false: 否
* @see MediaType#TEXT_EVENT_STREAM_VALUE
* @since 2.15.1
*/
public static boolean isStream(HttpServletResponse response) {
return StrUtil.contains(response.getContentType(), MediaType.TEXT_EVENT_STREAM_VALUE);
}
}

View File

@@ -18,13 +18,12 @@ package top.continew.starter.core.wrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.http.MediaType;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.util.StreamUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ServletUtils;
import java.io.*;
import java.net.URLEncoder;
@@ -77,8 +76,8 @@ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
? new FastByteArrayOutputStream(contentLength)
: new FastByteArrayOutputStream();
// 判断是否为文件上传请求
if (!isMultipartContent(request)) {
if (isFormRequest()) {
if (!ServletUtils.isMultipart(request)) {
if (ServletUtils.isForm(request)) {
writeRequestParametersToCachedContent();
} else {
StreamUtils.copy(request.getInputStream(), cachedContent);
@@ -90,7 +89,7 @@ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
@Override
public ServletInputStream getInputStream() throws IOException {
// 如果是文件上传,直接返回原始输入流
if (isMultipartContent(super.getRequest())) {
if (ServletUtils.isMultipart((HttpServletRequest)super.getRequest())) {
return super.getRequest().getInputStream();
}
synchronized (this) {
@@ -102,7 +101,7 @@ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
@Override
public BufferedReader getReader() throws IOException {
// 如果是文件上传直接返回原始Reader
if (isMultipartContent(super.getRequest())) {
if (ServletUtils.isMultipart((HttpServletRequest)super.getRequest())) {
return super.getRequest().getReader();
}
@@ -153,22 +152,6 @@ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
return cachedContent;
}
/**
* 判断当前请求是否为 multipart/form-data 类型的文件上传请求。 该类型一般用于表单上传文件的场景,例如 enctype="multipart/form-data"。
*
* @param request 当前 HTTP 请求对象
* @return true 表示为 multipart 文件上传请求;否则为 false
*/
public boolean isMultipartContent(ServletRequest request) {
String contentType = request.getContentType();
return contentType != null && contentType.toLowerCase().startsWith("multipart/");
}
private boolean isFormRequest() {
String contentType = getContentType();
return (contentType != null && contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE));
}
/**
* Body 缓存的ServletInputStream实现 DefaultServerRequestBuilder.BodyInputStream
*/

View File

@@ -20,7 +20,7 @@ import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import org.springframework.http.MediaType;
import top.continew.starter.core.util.ServletUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -46,30 +46,11 @@ public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
public RepeatReadResponseWrapper(HttpServletResponse response) {
super(response);
checkStreamingResponse();
}
@Override
public void setContentType(String type) {
super.setContentType(type);
// 根据 Content-Type 判断是否为流式响应
if (type != null) {
String lowerType = type.toLowerCase();
isStreamingResponse = lowerType.contains(MediaType.TEXT_EVENT_STREAM_VALUE);
}
}
private void checkStreamingResponse() {
String contentType = getContentType();
if (contentType != null) {
String lowerType = contentType.toLowerCase();
isStreamingResponse = lowerType.contains(MediaType.TEXT_EVENT_STREAM_VALUE);
}
isStreamingResponse = ServletUtils.isStream(response);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
checkStreamingResponse();
// 对于 SSE 流式响应,直接返回原始响应流,不做额外处理
if (isStreamingResponse) {
return super.getOutputStream();
@@ -103,7 +84,6 @@ public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
@Override
public PrintWriter getWriter() throws IOException {
checkStreamingResponse();
if (isStreamingResponse) {
// 对于 SSE 流式响应,直接返回原始响应写入器,不做额外处理
return super.getWriter();

View File

@@ -17,12 +17,12 @@
package top.continew.starter.log.http.servlet;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.WebUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ServletUtils;
import top.continew.starter.core.wrapper.RepeatReadRequestWrapper;
import top.continew.starter.log.http.RecordableHttpRequest;
@@ -75,7 +75,7 @@ public final class RecordableServletHttpRequest implements RecordableHttpRequest
@Override
public Map<String, String> getHeaders() {
return JakartaServletUtil.getHeaderMap(request);
return ServletUtils.getHeaderMap(request);
}
@Override
@@ -83,7 +83,7 @@ public final class RecordableServletHttpRequest implements RecordableHttpRequest
try {
RepeatReadRequestWrapper wrappedRequest = WebUtils
.getNativeRequest(request, RepeatReadRequestWrapper.class);
if (wrappedRequest == null || wrappedRequest.isMultipartContent(request)) {
if (wrappedRequest == null || ServletUtils.isMultipart(request)) {
return null;
}
String body = wrappedRequest.getContentAsString();
@@ -96,12 +96,12 @@ public final class RecordableServletHttpRequest implements RecordableHttpRequest
@Override
public String getParams() {
String body = this.getBody();
return CharSequenceUtil.isNotBlank(body) ? body : JSONUtil.toJsonStr(JakartaServletUtil.getParamMap(request));
return CharSequenceUtil.isNotBlank(body) ? body : JSONUtil.toJsonStr(ServletUtils.getParamMap(request));
}
@Override
public String getIp() {
return JakartaServletUtil.getClientIP(request);
return ServletUtils.getClientIP(request);
}
/**