← All tasks
javaclaude-code/java-t3 #28Verified, not scored yet

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
```