feat(examples): add Go and Java sandbox templates#782
Conversation
| server.createContext("/", HelloWorldServer::handleRoot); | ||
| server.createContext("/health", HelloWorldServer::handleHealth); | ||
|
|
||
| server.setExecutor(null); |
There was a problem hiding this comment.
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:
| 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)}/" |
There was a problem hiding this comment.
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://<sandbox>: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)}/" |
There was a problem hiding this comment.
Same https:// issue as the Go test — the Java server only serves plain HTTP. The comment on line 32 says GET http://<sandbox>:8080/ but the URL uses https://. Please clarify or fix the scheme.
PR Review: Go and Java sandbox templatesOverall this is a well-structured PR that follows the established patterns from 🔴 Critical1. Go Dockerfile: missing
Fix: Add 🟠 High2.
Fix: Use 3. Go server: no HTTP timeouts configured
🟡 Medium4. Both servers read the 5. Java
6. Java thread pool: hardcoded size, never shut down
7. Java Non-numeric values (e.g. empty string, spaces) cause an uncaught 8. Python test scripts have no assertions 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 9. Java test: no coverage for The Java tests always bind to port 0 (OS-assigned), so 🔵 Low / Informational
✅ What worked well
|
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.
59d0f9e to
dcfc411
Compare
| # 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 |
There was a problem hiding this comment.
Critical: missing go.mod — go 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-gostep before the test command, or - A
go.modfile 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 { |
There was a problem hiding this comment.
Missing HTTP timeouts — http.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 == "" { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
/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")) | ||
|
|
There was a problem hiding this comment.
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.)
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 scriptsexamples/cubesandbox-base-java/— Java sandbox template with HelloWorldServer + Python test scriptsDocumentation
bring-your-own-image.md(EN + ZH) with Go/Java template instructionsexamples.md(EN + ZH) to include new templatesEach template includes: