Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 62 additions & 8 deletions testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import groovy.transform.AutoClone
import org.codehaus.groovy.runtime.InvokerHelper
import org.springframework.http.*
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
import org.springframework.web.multipart.MultipartHttpServletRequest
import org.springframework.web.client.RestTemplate
import org.zstack.compute.vm.VmGlobalConfig
import org.zstack.configuration.SqlForeignKeyGenerator
Expand Down Expand Up @@ -987,21 +988,74 @@ class EnvSpec extends ApiHelper implements Node {
}

HttpEntity<String> getEntityFromRequest(HttpServletRequest req) {
StringBuilder sb = new StringBuilder()
String line
while ((line = req.getReader().readLine()) != null) {
sb.append(line)
}
req.getReader().close()

HttpHeaders header = new HttpHeaders()
for (Enumeration e = req.getHeaderNames() ; e.hasMoreElements() ;) {
String name = e.nextElement().toString()
header.add(name, req.getHeader(name))
}

StringBuilder sb = new StringBuilder()
if (req.getContentType()?.toLowerCase()?.startsWith("multipart/")) {
sb.append(readMultipartBody(req))
} else {
String line
while ((line = req.getReader().readLine()) != null) {
sb.append(line)
}
req.getReader().close()
}
Comment on lines +1001 to +1006
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 只读验证:定位该方法内对 getReader() 的重复调用点
rg -n -C2 'getEntityFromRequest|req\.getReader\(\)\.readLine\(\)|req\.getReader\(\)\.close\(\)' testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy

Repository: MatheMatrix/zstack

Length of output: 768


🏁 Script executed:

#!/bin/bash
# Verification only: locate repeated getReader() calls within this method
rg -n -C2 'getEntityFromRequest|req\.getReader\(\)\.readLine\(\)|req\.getReader\(\)\.close\(\)' testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy

Repository: MatheMatrix/zstack

Length of output: 768


避免重复调用 req.getReader(),改为单次获取并统一关闭。

第 1002 行和第 1005 行重复调用 getReader(),在不同 Servlet 实现下可能带来读取不一致或异常风险。建议单次获取 reader 后复用并在 finally 中关闭。

建议修复
-            String line
-            while ((line = req.getReader().readLine()) != null) {
-                sb.append(line)
-            }
-            req.getReader().close()
+            def reader = req.getReader()
+            try {
+                String line
+                while ((line = reader.readLine()) != null) {
+                    sb.append(line)
+                }
+            } finally {
+                reader.close()
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String line
while ((line = req.getReader().readLine()) != null) {
sb.append(line)
}
req.getReader().close()
}
def reader = req.getReader()
try {
String line
while ((line = reader.readLine()) != null) {
sb.append(line)
}
} finally {
reader.close()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy` around lines 1001 -
1006, The code repeatedly calls req.getReader() causing potential inconsistent
reads; change to obtain a single BufferedReader reader = req.getReader() (or def
reader) before the loop, use that reader in the while loop that appends lines
(instead of calling req.getReader().readLine()), and ensure the reader is closed
in a finally block (or try-with-resources) to guarantee a single, safe close;
update the block around the loop in EnvSpec.groovy where String line is used so
all read/close operations reference this single reader variable.


return new HttpEntity<String>(sb.toString(), header)
}

protected String readMultipartBody(HttpServletRequest req) {
byte[] raw = req.inputStream.bytes
if (raw.length > 0) {
return new String(raw, "UTF-8")
}

if (req instanceof MultipartHttpServletRequest) {
return readSpringMultipartBody(req as MultipartHttpServletRequest)
}

StringBuilder sb = new StringBuilder()
try {
req.getParts().each { part ->
appendMultipartPart(sb, part.name, part.submittedFileName, part.contentType, part.inputStream.bytes)
}
} catch (Throwable t) {
logger.debug("failed to read multipart parts for ${req.requestURI}", t)
}
return sb.toString()
}

protected String readSpringMultipartBody(MultipartHttpServletRequest req) {
StringBuilder sb = new StringBuilder()
req.parameterMap.each { String name, String[] values ->
values.each { value ->
appendMultipartPart(sb, name, null, "text/plain", value == null ? new byte[0] : value.getBytes("UTF-8"))
}
}
req.fileMap.each { String name, file ->
appendMultipartPart(sb, name, file.originalFilename, file.contentType, file.bytes)
}
return sb.toString()
}

protected void appendMultipartPart(StringBuilder sb, String name, String filename, String contentType, byte[] content) {
sb.append("Content-Disposition: form-data; name=\"").append(name).append("\"")
if (filename != null) {
sb.append("; filename=\"").append(filename).append("\"")
}
sb.append("\n")
if (contentType != null) {
sb.append("Content-Type: ").append(contentType).append("\n")
}
sb.append("\n")
sb.append(new String(content == null ? new byte[0] : content, "UTF-8"))
sb.append("\n")
}

void handleConditionSimulatorHttpRequests(HttpServletRequest req, HttpEntity entity, HttpServletResponse rsp) {
def url = req.getRequestURI()
if (httpConditionHandlers[url] == null || httpConditionHandlers[url].isEmpty()) {
Expand Down Expand Up @@ -1222,4 +1276,4 @@ class EnvSpec extends ApiHelper implements Node {
void resetAllMessageSize() {
messageHandlerCounters.clear()
}
}
}
12 changes: 11 additions & 1 deletion testlib/src/main/java/org/zstack/testlib/TestLibController.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class TestLibController {
private static final ExecutorService pool = Executors.newFixedThreadPool(32);

@RequestMapping(
value = "/**",
value = {"/**", "/v1/sites/**", "/v1/quota/**"},
method = {
RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE, RequestMethod.GET,
RequestMethod.HEAD, RequestMethod.OPTIONS, RequestMethod.PATCH, RequestMethod.TRACE
Expand All @@ -39,6 +39,11 @@ public void handle(HttpServletRequest request, HttpServletResponse response) thr
return;
}

if (isMultipartRequest(request)) {
Test.handleHttp(request, response);
return;
}

final AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(TestConfigUtils.getMessageTimeoutMillisConfig());

Expand All @@ -59,6 +64,11 @@ public void handle(HttpServletRequest request, HttpServletResponse response) thr
});
}

private boolean isMultipartRequest(HttpServletRequest request) {
String contentType = request.getContentType();
return contentType != null && contentType.toLowerCase().startsWith("multipart/");
}

@PreDestroy
public void shutdownPool() {
logger.info("Shutting down TestLibController pool");
Expand Down