Skip to content

Add Java Spring Boot web service sandbox example#755

Open
Sam-Hui-dot wants to merge 2 commits into
TencentCloud:masterfrom
Sam-Hui-dot:codex/java-springboot-web-example
Open

Add Java Spring Boot web service sandbox example#755
Sam-Hui-dot wants to merge 2 commits into
TencentCloud:masterfrom
Sam-Hui-dot:codex/java-springboot-web-example

Conversation

@Sam-Hui-dot

@Sam-Hui-dot Sam-Hui-dot commented Jul 4, 2026

Copy link
Copy Markdown

Summary

This PR adds a Java 21 Spring Boot web service example for CubeSandbox.

It demonstrates:

  • A Maven-based Java/Spring Boot sandbox template image
  • HTTP service endpoints for health, environment info, and task state
  • Persisted runtime state under /tmp/cubesandbox-spring/state/tasks.json
  • A demo script that validates build artifacts, routed HTTP access, snapshot creation, fork inheritance, and result manifest export
  • English and Chinese README/usecase documentation

Changes

  • Add examples/java-springboot-web
  • Add Spring Boot endpoints:
    • GET /health
    • GET /api/info
    • POST /api/tasks
    • GET /api/tasks/{id}
  • Add scripts/run_demo.py for the CubeSandbox demo flow
  • Add English and Chinese usecase docs
  • Register the example in English and Chinese example/usecase indexes

Verification

  • Python syntax check for scripts/run_demo.py
  • POM XML parse
  • git diff --check
  • Java tests: 5 passed
  • Docker image build
  • Offline Maven package inside the built image

Live CubeSandbox demo script is included; local validation covered Docker build, offline Maven package, and Java tests.

Assisted-by: Codex:GPT-5

@cubesandboxbot

cubesandboxbot Bot commented Jul 4, 2026

Copy link
Copy Markdown

PR Review: Java Spring Boot Web Service Sandbox Example

Overall, this is a well-structured example that demonstrates the CubeSandbox snapshot/fork workflow with a realistic Spring Boot service. The code is clean, the tests cover the core behaviors, and the documentation is thorough. Below are the notable findings I'd recommend addressing.

🔴 High Severity

1. Unvalidated user input → resource exhaustion (TaskState.java:60)
The payload field in POST /api/tasks accepts arbitrary JSON with no size or depth limit. Deeply nested objects or multi-megabyte strings can cause stack overflow or memory pressure during Jackson serialization. Apply @Size constraints or configure spring.jackson.deserialization.max-string-length.

2. Filesystem path disclosure in API response (TaskState.java:61)
The absolute server path /tmp/cubesandbox-spring/state/tasks.json is returned in every task response via the stateFile field. This leaks internal filesystem layout.

3. Docker image is 400–600 MB larger than necessary (Dockerfile)

  • Full JDK in final image (~300 MB extra); a JRE or jlink-trimmed runtime suffices.
  • Maven repository (/workspace/.m2/repository, ~150-300 MB) baked into final image but unused at runtime.
  • No .dockerignore — local artifacts can bloat build context.

🟡 Medium Severity

4. JAVA_TOOL_OPTIONS scope (Dockerfile:32)
JAVA_TOOL_OPTIONS affects all JVM processes (including Maven's compiler JVMs). Use JDK_JAVA_OPTIONS on JDK 18+, or pass flags in the startup command. -XX:+UseContainerSupport is also default since JDK 10.

5. Stale temp files on crash (TaskState.java:118)
JVM crash between createTempFile and Files.move leaves orphaned .json.tmp files. Add cleanup-on-init.

6. Hardcoded file list in demo upload (run_demo.py:100)
Every source file is listed explicitly — adding a file silently breaks the demo. Use glob-based discovery.

7. Documentation: incomplete manifest example (README.md)
The README shows only 3/9 manifest fields. Update to match full output or label as abbreviated.

8. Documentation: missing constraints (README.md)
100-task capacity and 12h TTL are undocumented. Users will silently lose tasks.

🔵 Low Severity

  • TaskState.readTasks() has no lock-contract annotation; future callers could miss the lock.
  • Demo Maven command omits -B (batch mode), producing noisier logs.
  • No @ControllerAdvice for consistent error shape — missed educational opportunity.
  • TaskController accepts any string as path variable without UUID format validation.
  • No integration test (@SpringBootTest) or concurrency test for the ReadWriteLock.
  • HealthController and InfoController have no tests.

✅ Positive Highlights

  • Atomic file writes with correct ATOMIC_MOVE fallback.
  • ReadWriteLock appropriately chosen (read lock for get, write lock for create).
  • Temp-file + atomic-replace pattern for manifest download with proper cleanup.
  • Good TTL-based expiry test for TaskState.
  • Cleanup finally block correctly distinguishes expected vs unexpected errors.
  • English and Chinese documentation are consistent and accurate.

this.objectMapper = objectMapper;
}

public synchronized Map<String, Object> createTask(Map<String, Object> request) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All public methods (createTask, getTask) are synchronized on the instance, serializing even read-only lookups. Each call also reads/writes the entire JSON file from disk. This means two concurrent getTask() calls for different IDs still block each other. Consider using a ReentrantReadWriteLock for concurrent reads, or at minimum avoid serializing reads behind the same lock as writes.

Additionally, tasks are only ever appended — there's no eviction or TTL, so /tmp/cubesandbox-spring/state/tasks.json grows unboundedly. In a tmpfs-backed /tmp, this can silently exhaust memory over longer sessions.

Comment thread examples/java-springboot-web/Dockerfile Outdated
RUN mvn -B -DskipTests dependency:go-offline

COPY src ./src
RUN chown -R user:user /workspace && chmod -R 0777 /workspace

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Container runs as root with chmod -R 0777 /workspace (world-writable). No USER directive is set. While sandbox isolation at the platform layer is the primary defense, running as root with 0777 eliminates defense-in-depth filesystem isolation inside the sandbox. Consider adding a USER user:user directive and using more restrictive permissions (e.g., 0755 on directories, 0644 on files).

@RequestMapping("/api")
public class InfoController {
@GetMapping("/info")
public Map<String, Object> info() throws Exception {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

throws Exception is a blanket checked-exception clause that propagates all checked exceptions to Spring's error handler. The only checked path here is InetAddress.getLocalHost() which throws UnknownHostException. Catching that specific exception and handling it gracefully (e.g., returning "hostname": "unknown") would be more idiomatic and make exception handling explicit.

primary_deployment = correlated[-1] if correlated else None

incident_minutes = int((last_breach - first_breach).total_seconds() / 60) + METRIC_GRANULARITY_MINUTES
latency_delta = baseline["incident_peak_p95_latency_ms"] / baseline["baseline_p95_latency_ms"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

latency_delta and error_delta divide incident_peak_p95_latency_ms and incident_peak_error_rate by their baseline counterparts (baseline_p95_latency_ms, baseline_error_rate). If the pre-breach metrics window contains all-zero rows, these baselines could be zero, causing a ZeroDivisionError crash. Consider a safe-divide helper that guards against zero (e.g., lambda a, b: round(a / b, 2) if b else 0).

this.objectMapper = objectMapper;
}

public synchronized Map<String, Object> createTask(Map<String, Object> request) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All public methods (createTask, getTask) are synchronized on the instance, serializing even read-only lookups. Each call also reads/writes the entire JSON file from disk. This means two concurrent getTask() calls for different IDs still block each other. Consider using a ReentrantReadWriteLock for concurrent reads, or at minimum avoid serializing reads behind the same lock as writes.

Additionally, tasks are only ever appended — there's no eviction or TTL, so /tmp/cubesandbox-spring/state/tasks.json grows unboundedly. In a tmpfs-backed /tmp, this can silently exhaust memory over longer sessions.

Comment thread examples/java-springboot-web/Dockerfile Outdated
RUN mvn -B -DskipTests dependency:go-offline

COPY src ./src
RUN chown -R user:user /workspace && chmod -R 0777 /workspace

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Container runs as root with chmod -R 0777 /workspace (world-writable). No USER directive is set. While sandbox isolation at the platform layer is the primary defense, running as root with 0777 eliminates defense-in-depth filesystem isolation inside the sandbox. Consider adding a USER user:user directive and using more restrictive permissions (e.g., 0755 on directories, 0644 on files).

@RequestMapping("/api")
public class InfoController {
@GetMapping("/info")
public Map<String, Object> info() throws Exception {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

throws Exception is a blanket checked-exception clause that propagates all checked exceptions to Spring's error handler. The only checked path here is InetAddress.getLocalHost() which throws UnknownHostException. Catching that specific exception and handling it gracefully (e.g., returning "hostname": "unknown") would be more idiomatic and make exception handling explicit.

primary_deployment = correlated[-1] if correlated else None

incident_minutes = int((last_breach - first_breach).total_seconds() / 60) + METRIC_GRANULARITY_MINUTES
latency_delta = baseline["incident_peak_p95_latency_ms"] / baseline["baseline_p95_latency_ms"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

latency_delta and error_delta divide incident_peak_p95_latency_ms and incident_peak_error_rate by their baseline counterparts (baseline_p95_latency_ms, baseline_error_rate). If the pre-breach metrics window contains all-zero rows, these baselines could be zero, causing a ZeroDivisionError crash. Consider a safe-divide helper that guards against zero (e.g., lambda a, b: round(a / b, 2) if b else 0).

Add a Java 21 Spring Boot web service template example with health, info, task APIs, persisted sandbox state, offline Maven demo flow, and bilingual docs/index entries.

Assisted-by: Codex:GPT-5
Signed-off-by: Sam-Hui-dot <19303092837@163.com>
Add Spring Boot tests, run the template as a non-root user, make task state bounded and read/write locked, and handle hostname lookup failures explicitly.

Assisted-by: Codex:GPT-5
Signed-off-by: Sam-Hui-dot <19303092837@163.com>
@Sam-Hui-dot Sam-Hui-dot force-pushed the codex/java-springboot-web-example branch from f10e891 to c65a5ad Compare July 5, 2026 08:56
@Sam-Hui-dot

Copy link
Copy Markdown
Author

Related to #645

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant