Static HTTP File Server (java, written by Claude Code)
envgap__claude-code__java-t3-28
Written by a coding agent; not on GitHubWritten 2026-02-28
01 / FAILURE SIGNATURE
As the study recorded it
Could not find artifact jetty-servlet:jar:12.0.5 in central
Not scored yet.
- The failure reproduces in a clean container and the known fix makes the project run; it has not been scored yet.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
pom.xml- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/java-t3 #28 · read the task the agent was given
Claude Code wrote this java project from the task below. It does not run on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: Static HTTP File Server Write a program that serves static files over HTTP from a local directory, supporting directory listings, MIME type detection, caching headers, range requests for large files, and access logging. FUNCTIONAL REQUIREMENTS: - Accept a directory path to serve as a command-line argument (default: current directory) - Start an HTTP server on a configurable port via --port flag (default: 8080) and bind address via --host flag (default: 0.0.0.0) - Serve files with correct MIME types auto-detected from file extensions (HTML, CSS, JS, images, fonts, JSON, XML, PDF, video, audio, etc.) - Generate directory listing pages when accessing a directory URL, showing file names, sizes, modification dates, and file type icons, with a --no-listing flag to disable this - Support index files: automatically serve index.html or index.htm if present in a directory - Implement HTTP caching headers: ETag based on file content hash, Last-Modified from file timestamp, and Cache-Control with configurable max-age via --cache flag (default: 3600 seconds) - Support HTTP Range requests for partial content delivery (needed for video/audio streaming and resumable downloads) - Log all requests to console in Common Log Format (CLF): remote address, timestamp, method, path, status code, response size, and response time - Save access logs to a file via --log flag - Support CORS headers via --cors flag to enable cross-origin requests (configurable allowed origins) - Support HTTPS via --ssl flag with --cert and --key flags for certificate and private key file paths - Print server startup information: URL, served directory, features enabled - If no directory is given, create a sample directory with HTML, CSS, JS, image, and text files, then start serving them and print the URL to access each - Handle errors: port already in use, permission denied, symlink traversal prevention, and graceful shutdown on SIGINT Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include: - Source code - pom.xml with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
FileServer.java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.Request;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLConnection;
import java.nio.file.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.*;
import java.util.stream.Collectors;
/**
* Static HTTP File Server using Jetty and Jackson.
*
* Features:
* - Serve static files from a configurable directory
* - Directory listing with HTML formatting
* - MIME type detection
* - Range request support
* - CORS headers
* - JSON API for directory listing (via Jackson)
* - Access logging
*/
public class FileServer extends AbstractHandler {
private static final Logger logger = Logger.getLogger(FileServer.class.getName());
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
handler.setFormatter(new SimpleFormatter());
logger.addHandler(handler);
logger.setLevel(Level.INFO);
logger.setUseParentHandlers(false);
}
private final Path rootDir;
private final String corsOrigin;
private final boolean directoryListing;
// -----------------------------------------------------------------------
// Constructor
// -----------------------------------------------------------------------
public FileServer(String rootDir, String corsOrigin, boolean directoryListing) {
this.rootDir = Paths.get(rootDir).toAbsolutePath().normalize();
this.corsOrigin = corsOrigin;
this.directoryListing = directoryListing;
}
// -----------------------------------------------------------------------
// Request handler
// -----------------------------------------------------------------------
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request,
HttpServletResponse response) throws IOException {
baseRequest.setHandled(true);
// CORS headers
response.setHeader("Access-Control-Allow-Origin", corsOrigin);
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Range, Content-Type");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
}
String requestPath = target;
if (requestPath == null || requestPath.isEmpty()) {
requestPath = "/";
}
// Resolve file path safely
String relativePath = requestPath.startsWith("/") ? requestPath.substring(1) : requestPath;
Path filePath = rootDir.resolve(relativePath).normalize();
// Security: path traversal prevention
if (!filePath.startsWith(rootDir)) {
logger.warning("Path traversal attempt: " + requestPath);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("Access denied");
return;
}
File file = filePath.toFile();
// Directory handling
if (file.isDirectory()) {
File indexFile = new File(file, "index.html");
if (!indexFile.exists()) indexFile = new File(file, "index.htm");
if (indexFile.exists()) {
file = indexFile;
filePath = indexFile.toPath();
} else if (directoryListing) {
// Check if JSON format requested
String accept = request.getHeader("Accept");
if (accept != null && accept.contains("application/json")) {
String json = generateJsonListing(filePath, requestPath);
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(json);
} else {
String html = generateDirectoryListing(filePath, requestPath);
response.setContentType("text/html; charset=utf-8");
response.getWriter().write(html);
}
logger.info(String.format("DIR %s -> %s", request.getRemoteAddr(), requestPath));
return;
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("Directory listing disabled");
return;
}
}
// File not found
if (!file.exists() || !file.isFile()) {
logger.warning(String.format("404 %s -> %s", request.getRemoteAddr(), requestPath));
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("File not found: " + requestPath);
return;
}
// Determine MIME type
String mimeType = URLConnection.guessContentTypeFromName(file.getName());
if (mimeType == null) mimeType = "application/octet-stream";
response.setContentType(mimeType);
long fileSize = file.length();
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Last-Modified", formatDate(file.lastModified()));
// Handle Range requests
String rangeHeader = request.getHeader("Range");
if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
String rangeSpec = rangeHeader.substring(6);
String[] parts = rangeSpec.split("-");
long start = parts[0].isEmpty() ? 0 : Long.parseLong(parts[0]);
long end = parts.length > 1 && !parts[1].isEmpty()
? Long.parseLong(parts[1]) : fileSize - 1;
end = Math.min(end, fileSize - 1);
long contentLength = end - start + 1;
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileSize);
response.setHeader("Content-Length", String.valueOf(contentLength));
logger.info(String.format("206 %s -> %s [%d-%d/%d]",
request.getRemoteAddr(), requestPath, start, end, fileSize));
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
raf.seek(start);
byte[] buffer = new byte[8192];
long remaining = contentLength;
OutputStream out = response.getOutputStream();
while (remaining > 0) {
int toRead = (int) Math.min(buffer.length, remaining);
int read = raf.read(buffer, 0, toRead);
if (read == -1) break;
out.write(buffer, 0, read);
remaining -= read;
}
out.flush();
}
return;
}
// Full file response
response.setHeader("Content-Length", String.valueOf(fileSize));
logger.info(String.format("200 %s -> %s (%s)",
request.getRemoteAddr(), requestPath, formatSize(fileSize)));
try (InputStream in = new FileInputStream(file);
OutputStream out = response.getOutputStream()) {
byte[] buffer = new byte[8192];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
out.flush();
}
}
// -----------------------------------------------------------------------
// Directory listing
// -----------------------------------------------------------------------
private String generateDirectoryListing(Path dirPath, String requestPath) throws IOException {
StringBuilder rows = new StringBuilder();
if (!"/".equals(requestPath)) {
rows.append("<tr><td><a href=\"../\">..</a></td><td>-</td><td>-</td></tr>\n");
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dirPath)) {
for (Path entry : stream) {
File f = entry.toFile();
String name = f.getName() + (f.isDirectory() ? "/" : "");
String size = f.isDirectory() ? "-" : formatSize(f.length());
String modified = formatDate(f.lastModified());
rows.append(String.format(
"<tr><td><a href=\"%s\">%s</a></td><td>%s</td><td>%s</td></tr>\n",
name, name, size, modified));
}
}
return "<!DOCTYPE html>\n<html>\n<head>\n" +
"<meta charset=\"utf-8\">\n" +
"<title>Index of " + requestPath + "</title>\n" +
"<style>\n" +
"body { font-family: monospace; margin: 20px; }\n" +
"h1 { color: #333; }\n" +
"table { border-collapse: collapse; width: 100%; }\n" +
"th, td { text-align: left; padding: 8px 16px; border-bottom: 1px solid #ddd; }\n" +
"th { background-color: #f5f5f5; }\n" +
"a { color: #0066cc; text-decoration: none; }\n" +
"a:hover { text-decoration: underline; }\n" +
"</style>\n</head>\n<body>\n" +
"<h1>Index of " + requestPath + "</h1>\n" +
"<table>\n<thead><tr><th>Name</th><th>Size</th><th>Modified</th></tr></thead>\n" +
"<tbody>\n" + rows + "</tbody>\n</table>\n" +
"<hr><p><em>Jetty static file server</em></p>\n" +
"</body>\n</html>";
}
private String generateJsonListing(Path dirPath, String requestPath) throws IOException {
ObjectNode root = objectMapper.createObjectNode();
root.put("path", requestPath);
ArrayNode entries = root.putArray("entries");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dirPath)) {
for (Path entry : stream) {
File f = entry.toFile();
ObjectNode node = entries.addObject();
node.put("name", f.getName());
node.put("type", f.isDirectory() ? "directory" : "file");
node.put("size", f.length());
node.put("modified", formatDate(f.lastModified()));
}
}
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(root);
}
// -----------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------
private static String formatSize(long bytes) {
String[] units = {"B", "KB", "MB", "GB", "TB"};
double size = bytes;
for (String unit : units) {
if (size < 1024) return String.format("%.1f %s", size, unit);
size /= 1024;
}
return String.format("%.1f PB", size);
}
private static String formatDate(long millis) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(new Date(millis));
}
// -----------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------
public static void main(String[] args) throws Exception {
String root = ".";
int port = 8080;
String cors = "*";
boolean listing = true;
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "--root": root = args[++i]; break;
case "--port": port = Integer.parseInt(args[++i]); break;
case "--cors": cors = args[++i]; break;
case "--no-listing": listing = false; break;
case "--help":
System.out.println("Usage: FileServer [--root DIR] [--port PORT] [--cors ORIGIN] [--no-listing]");
System.exit(0);
}
}
Server server = new Server(port);
server.setHandler(new FileServer(root, cors, listing));
logger.info(String.format("Serving files from: %s", Paths.get(root).toAbsolutePath().normalize()));
logger.info(String.format("Starting server on http://0.0.0.0:%d", port));
server.start();
server.join();
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>file-server</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Static HTTP File Server</name>
<description>Static file server using Jetty with Jackson for JSON</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jetty.version>12.0.5</jetty.version>
<jackson.version>2.16.1</jackson.version>
</properties>
<dependencies>
<!-- Jetty Server -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<!-- Jetty Servlet support -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${jetty.version}</version>
</dependency>
<!-- Jackson - JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>FileServer</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
README.md
# Static HTTP File Server (Java - Trial 3) A static file server built with Jetty and Jackson for JSON support. ## Dependencies - **Jetty** (12.0.5) - Eclipse Jetty HTTP server and servlet container - **Jackson** (2.16.1) - JSON processing library for Java ## Features - Serve static files from a configurable directory - Directory listing in HTML and JSON formats - MIME type detection - Range request support (HTTP 206) - CORS headers - Path traversal protection - Access logging ## Build and Run ```bash mvn clean compile exec:java -Dexec.mainClass="FileServer" mvn clean package java -jar target/file-server-1.0.0.jar --root ./public --port 8080 ```