Skip to content

feat(examples): add Go and Java sandbox templates#782

Open
shsaihdsaiudh wants to merge 1 commit into
TencentCloud:masterfrom
shsaihdsaiudh:feat/sandbox-templates
Open

feat(examples): add Go and Java sandbox templates#782
shsaihdsaiudh wants to merge 1 commit into
TencentCloud:masterfrom
shsaihdsaiudh:feat/sandbox-templates

Conversation

@shsaihdsaiudh

Copy link
Copy Markdown

Summary

Add pre-compiled sandbox templates for Go and Java runtime environments.

What's included

Examples

  • examples/cubesandbox-base-go/ — Go sandbox template with HTTP server + Python test scripts
  • examples/cubesandbox-base-java/ — Java sandbox template with HelloWorldServer + Python test scripts

Documentation

  • Updated bring-your-own-image.md (EN + ZH) with Go/Java template instructions
  • Updated examples.md (EN + ZH) to include new templates

Each template includes:

  • Dockerfile for building the sandbox image
  • Python test script (e2b SDK compatible)
  • Environment configuration
  • README with usage instructions

server.createContext("/", HelloWorldServer::handleRoot);
server.createContext("/health", HelloWorldServer::handleHealth);

server.setExecutor(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

setExecutor(null) makes the server single-threaded — every request is handled inline in the single dispatcher thread. Under even light concurrent load, requests queue behind each other. Consider using a minimal thread pool so this template is safe to build on:

Suggested change
server.setExecutor(null);
server.setExecutor(Executors.newFixedThreadPool(4));

(or Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory()) on JDK 21+). Remember to add import java.util.concurrent.Executors;.

print(sandbox.files.read("/app/main.go", user="root"))

print("=== GET http://<sandbox>:8080/ ===")
url = f"https://{sandbox.get_host(8080)}/"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The URL uses https:// but the Go server only serves plain HTTP (no TLS). If the E2B sandbox tunnel provides TLS termination, that's fine — but the comment on line 31 says GET http://&lt;sandbox&gt;:8080/, which contradicts the scheme. Please clarify: either add a comment explaining why https:// is correct, or change to http://.

print(sandbox.files.read("/app/HelloWorldServer.java", user="root"))

print("=== GET http://<sandbox>:8080/ ===")
url = f"https://{sandbox.get_host(8080)}/"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same https:// issue as the Go test — the Java server only serves plain HTTP. The comment on line 32 says GET http://&lt;sandbox&gt;:8080/ but the URL uses https://. Please clarify or fix the scheme.

@cubesandboxbot

cubesandboxbot Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR Review: Go and Java sandbox templates

Overall this is a well-structured PR that follows the established patterns from cubesandbox-base-nginx. The Go and Java examples are consistent with each other and the existing documentation. Below are the noteworthy findings organized by severity.


🔴 Critical

1. Go Dockerfile: missing go.mod will cause build failure
examples/cubesandbox-base-go/Dockerfile (line 38)

RUN CGO_ENABLED=0 go test -v . requires a module context under Go ≥1.16 (module mode is the default). No go.mod exists in the template directory and none is generated during the build. The go test . command (package mode) will fail with go: cannot find main module. The standalone go build main.go on line 41 would work (file-argument mode), but the test step will abort the build first.

Fix: Add RUN go mod init cubesandbox-demo-go before the test/build steps, or commit a go.mod file to the repository.


🟠 High

2. SSL_CERT_FILE path is a RHEL path — incompatible with Ubuntu base
examples/cubesandbox-base-go/env.example and examples/cubesandbox-base-java/env.example (line 11)

export SSL_CERT_FILE="/etc/pki/tls/cert.pem" is a Red Hat CA trust-store path. The base image (Dockerfile.cube-base) uses ubuntu:22.04, which stores the CA bundle at /etc/ssl/certs/ca-certificates.crt. This path will not exist on Ubuntu hosts, causing TLS certificate verification failures when running test_sandbox.py. (This also affects examples/cubesandbox-base-nginx/env.example if in scope.)

Fix: Use /etc/ssl/certs/ca-certificates.crt, or omit the variable and let certifi/system defaults apply.

3. Go server: no HTTP timeouts configured
examples/cubesandbox-base-go/main.go (line 28)

http.ListenAndServe uses a default http.Server with zero-valued ReadTimeout, WriteTimeout, and IdleTimeout. Slow clients can hold connections open indefinitely, exhausting goroutines and file descriptors. For a clean demo, even modest timeouts (e.g., 10s read, 10s write, 30s idle) would be better than unbounded.


🟡 Medium

4. APP_PORT read from env on every request — both Go and Java
examples/cubesandbox-base-go/main.go (line 37), examples/cubesandbox-base-java/HelloWorldServer.java (line 50)

Both servers read the APP_PORT environment variable on every HTTP request (via os.Getenv / System.getenv()). This makes a kernel call per request for a value known at startup. Capture the port once during initialization and close over it.

5. Java handleHealth closes TCP connection unconditionally
examples/cubesandbox-base-java/HelloWorldServer.java (line 56)

sendResponseHeaders(200, -1) followed by exchange.close() disables HTTP keep-alive. Every health check probe opens a new TCP connection. Since health probes may be frequent (load balancers, Cube readiness), this is unnecessary overhead. Consider sending a zero-length body with proper stream closure.

6. Java thread pool: hardcoded size, never shut down
examples/cubesandbox-base-java/HelloWorldServer.java (line 43)

Executors.newFixedThreadPool(4) is fixed regardless of available vCPUs and is never shut down. In a MicroVM with 1–2 vCPUs, 4 threads add context-switching overhead. Mentioned as a known limitation in the README, which is good, but the executor leak still deserves noting.

7. Java APP_PORT not validated (Integer.parseInt crash)
examples/cubesandbox-base-java/HelloWorldServer.java (line 22)

Non-numeric values (e.g. empty string, spaces) cause an uncaught NumberFormatException at startup. A simple trim + try/catch fallback would make it more robust.

8. Python test scripts have no assertions
examples/cubesandbox-base-go/test_sandbox.py, examples/cubesandbox-base-java/test_sandbox.py

Both scripts print output but never assert or check exit codes. They are debugging helpers rather than automated tests. If a sandbox command fails, the script continues silently. Consider adding assert statements or a non-zero exit on unexpected values.

9. Java test: no coverage for APP_PORT env var
examples/cubesandbox-base-java/HelloWorldServerTest.java

The Java tests always bind to port 0 (OS-assigned), so System.getenv("APP_PORT") is never exercised. The Go tests cover this path explicitly.


🔵 Low / Informational

  • Maven installed but unnecessary for the templateHelloWorldServer compiles with plain javac. Including Maven increases image size and attack surface. If it's intended as a tool for downstream users, consider documenting that.
  • / route catches all unknown paths — In both Go and Java servers, unmatched paths (e.g. /api/foo) hit the root handler and return 200 with the demo HTML. Not blocking for a demo, but worth a comment.
  • Java concurrent test: thread pool not in finally blockHelloWorldServerTest.testConcurrentRequestsAllSucceed leaks the executor and pending futures if the latch times out.
  • Go go test -v flag in Docker build — The -v flag adds build-log noise; no benefit in a docker build context.

✅ What worked well

  • Consistent structure and naming across Go, Java, and the existing nginx example
  • Both READMEs clearly document known limitations and use cases
  • Good test coverage patterns in Go (httptest-based) and Java (zero-dependency test harness)
  • Each template includes proper E2B SDK smoke tests, Dockerfiles with CI-friendly test-before-build steps, and environment configuration
  • Documentation updates reference both new templates correctly in EN and ZH versions

Add pre-compiled sandbox templates with tests:

cubesandbox-base-go/
- Go HTTP server with test_sandbox.py integration test
- main_test.go unit tests

cubesandbox-base-java/
- Java HTTP server (HelloWorldServer) with test_sandbox.py
- HelloWorldServerTest.java unit tests

Each template includes Dockerfile, env config, and e2b SDK test scripts.
@shsaihdsaiudh shsaihdsaiudh force-pushed the feat/sandbox-templates branch from 59d0f9e to dcfc411 Compare July 6, 2026 14:22
# Build a static-ish binary; CGO_ENABLED=0 avoids libc linkage surprises.
RUN CGO_ENABLED=0 go build -o /app/helloserver main.go

EXPOSE 8080 49983

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Critical: missing go.modgo test -v . will fail without a go.mod file because Go ≥1.16 defaults to module mode (GO111MODULE=on). Running go test . (package mode) requires a module context; only go build main.go (file-argument mode) works without one.

Add either:

  • A RUN go mod init cubesandbox-demo-go step before the test command, or
  • A go.mod file committed to the repository.

(The standalone go build -o /app/helloserver main.go on line 41 will work because it specifies a file argument, but the test will fail first.)

mux.HandleFunc("/health", handleHealth)

fmt.Printf("helloserver listening on :%s (Go %s)\n", port, runtime.Version())
if err := http.ListenAndServe(":"+port, mux); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing HTTP timeoutshttp.ListenAndServe uses the default http.Server with zero-valued ReadTimeout, WriteTimeout, and IdleTimeout. Slow or malicious clients can hold connections open indefinitely, exhausting goroutines and file descriptors. Consider setting explicit timeouts:

srv := &http.Server{
    Addr:         ":" + port,
    Handler:      mux,
    ReadTimeout:  10 * time.Second,
    WriteTimeout: 10 * time.Second,
    IdleTimeout:  30 * time.Second,
}
if err := srv.ListenAndServe(); err != nil { ... }

(This is a demo, so even 30s defaults are better than 0 = no timeout.)

func handleRoot(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
port := os.Getenv("APP_PORT")
if port == "" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

os.Getenv("APP_PORT") is read on every request — this makes a syscall into the OS for each HTTP request. The port value is already captured in main() as the local variable port. Consider closing over it or storing it at the package level to avoid redundant lookups. Same pattern in the Java server.


public static void main(String[] args) throws IOException {
int port = Integer.parseInt(System.getenv().getOrDefault("APP_PORT", "8080"));
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Integer.parseInt will crash on non-numeric APP_PORT — if the env var is accidentally set to "8080 " (trailing space), "eight-thousand", or an empty string, the JVM throws an uncaught NumberFormatException at startup. Consider wrapping with a try/catch that falls back to the default, or at minimum trimming the value: System.getenv("APP_PORT") != null ? Integer.parseInt(System.getenv("APP_PORT").trim()) : 8080.

byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set("Content-Type", "text/html; charset=utf-8");
exchange.sendResponseHeaders(200, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hardcoded thread-pool size of 4 — In a MicroVM the vCPU count may be 1–2, so 4 threads could cause unnecessary context switching. Consider Runtime.getRuntime().availableProcessors() or the default dispatcher (single-threaded null executor). Also, the ExecutorService is never shut down — if the server were restarted within the same JVM, threads from the old pool would leak.

export CUBE_TEMPLATE_ID="<your-template-id>"


export SSL_CERT_FILE="/etc/pki/tls/cert.pem"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

/etc/pki/tls/cert.pem is a RHEL path — the base image is ubuntu:22.04, which stores the CA certificate bundle at /etc/ssl/certs/ca-certificates.crt. The path /etc/pki/tls/cert.pem will not exist on an Ubuntu host, causing TLS certificate verification failures when test_sandbox.py makes HTTPS requests. (This also applies to the Java env.example.)


print("=== read /app/main.go ===")
print(sandbox.files.read("/app/main.go", user="root"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No assertions — this is a debugging script, not an automated test — stdout is printed but nothing is asserted. If sandbox.commands.run(...) returns a non-zero exit code, the script prints it and continues. Consider adding explicit assertions (or raise SystemExit(1) on unexpected values) so the script fails meaningfully when a SDK integration issue exists. (Same issue in the Java test_sandbox.py.)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants