Skip to content

Instantly share code, notes, and snippets.

@decagondev
Created August 16, 2025 23:38
Show Gist options
  • Select an option

  • Save decagondev/33aa52e03e0db3775233631260f2f2de to your computer and use it in GitHub Desktop.

Select an option

Save decagondev/33aa52e03e0db3775233631260f2f2de to your computer and use it in GitHub Desktop.

Building a Simple HTTP Server in Java: Problem Set

Introduction

This problem set guides you through building a basic HTTP server from scratch using Java's standard library, specifically the java.net.ServerSocket and java.net.Socket classes. The goal is to create a server that handles simple GET requests, parses incoming HTTP requests, and sends valid HTTP responses. We'll break it down into "tickets" – small, incremental tasks that build upon each other, simulating a real-world project workflow.

Prerequisites:

  • Basic Java knowledge (sockets, I/O streams, file handling, string manipulation).
  • No external libraries are allowed; use only Java's standard library (e.g., java.net, java.io, java.nio.file).
  • Test your server using tools like curl (e.g., curl http://localhost:8080/) or a web browser.

Project Structure:

  • Create a single Java file, e.g., SimpleHTTPServer.java.
  • The server should run on localhost port 8080 by default.
  • Handle only GET requests; respond with 405 Method Not Allowed for other methods.
  • Serve static files from a www directory in the same folder as your code (create it and add some HTML files for testing).

Running the Server:

  • Compile and run: javac SimpleHTTPServer.java && java SimpleHTTPServer.
  • It should listen indefinitely until interrupted (e.g., Ctrl+C).

Complete the tickets in order, testing each step before proceeding.

Ticket 1: Set Up the Basic Socket Server

Description: Create the foundation for your HTTP server by setting up a TCP server socket that listens for incoming connections.

Tasks:

  • Create a ServerSocket on localhost:8080.
  • In a loop, accept incoming Socket connections.
  • For each connection, send a simple HTTP response: HTTP/1.1 200 OK\r\n\r\nHello, World!.
  • Close the connection after responding.
  • Print a message like "Server is listening on port 8080..." when starting.
  • Handle interrupts gracefully (e.g., Ctrl+C).

Testing:

  • Run the server and use curl http://localhost:8080 or a browser to see "Hello, World!".
  • Ensure the server handles multiple connections without crashing.

Hints:

  • Use try-with-resources for socket and stream management.
  • Handle IOException for robust error handling.

Ticket 2: Read and Log Incoming Requests

Description: Extend the server to read incoming request data and log it to the console.

Tasks:

  • Read the request from the socket's input stream (up to 1024 bytes is sufficient for now).
  • Decode the data as UTF-8 and print it to the console.
  • Continue sending the "Hello, World!" response.
  • Handle cases where the request is empty or malformed with a 400 Bad Request response.

Testing:

  • Use curl http://localhost:8080 and check if the server logs the request (e.g., "GET / HTTP/1.1...").
  • Test with a browser to confirm "Hello, World!" is returned.
  • Send invalid requests via telnet localhost 8080 to verify error handling.

Hints:

  • Use BufferedReader to read lines from the socket.
  • Ensure responses use \r\n line endings.

Ticket 3: Parse the Request Line

Description: Parse the HTTP request line to extract method, path, and HTTP version, and validate that the method is GET.

Tasks:

  • Split the first line of the request to get method, path, and version (e.g., GET / HTTP/1.1).
  • If the method is not "GET", respond with 405 Method Not Allowed.
  • If the request line is malformed (e.g., fewer than 3 parts), respond with 400 Bad Request.
  • Log the method and path.
  • Continue with "Hello, World!" for valid GET requests.

Testing:

  • curl -X GET http://localhost:8080/ → "Hello, World!".
  • curl -X POST http://localhost:8080/ → 405 response.
  • Send invalid requests via telnet (e.g., "INVALID / HTTP/1.1") → 400 response.

Hints:

  • Split the request line by spaces.
  • Validate the number of components.

Ticket 4: Parse Request Headers

Description: Parse HTTP headers into a map and log them for debugging.

Tasks:

  • Read lines after the request line until an empty line.
  • Parse each header line as key: value (split by first :).
  • Store headers in a Map<String, String> (keys in lowercase for consistency).
  • Log the headers map.
  • If headers are malformed (e.g., no colon), respond with 400 Bad Request.
  • Continue with "Hello, World!" for valid GET requests.

Testing:

  • curl -v -H "X-Test: value" http://localhost:8080/ → Headers logged (e.g., {"x-test": "value"}).
  • Test invalid headers via telnet to verify 400 response.

Hints:

  • Use HashMap for headers.
  • Stop parsing at an empty line.

Ticket 5: Handle Simple GET Requests with Proper Responses

Description: Implement routing for GET requests with proper HTTP response headers.

Tasks:

  • For path /, respond with 200 OK, Content-Type: text/plain, Content-Length, and body "Hello, World!".
  • For path /echo, respond with the raw request (method, path, headers) as the body.
  • For other paths, respond with 404 Not Found and body "Not Found".
  • Include Content-Length in all responses.

Testing:

  • curl http://localhost:8080/ → "Hello, World!".
  • curl http://localhost:8080/echo → Request details.
  • curl http://localhost:8080/invalid → 404 with "Not Found".

Hints:

  • Calculate Content-Length based on the response body.
  • Use \r\n for header line endings.

Ticket 6: Serve Static Files

Description: Serve files from a www directory for GET requests.

Tasks:

  • Create a www folder with an index.html containing <h1>Welcome!</h1>.
  • Map / to www/index.html and other paths (e.g., /style.css) to www/style.css.
  • Use java.nio.file to read files.
  • Set Content-Type based on file extension (.htmltext/html, .csstext/css, .txttext/plain, default application/octet-stream).
  • Prevent directory traversal (e.g., /../) by normalizing paths.
  • Respond with 404 if the file doesn't exist.

Testing:

  • curl http://localhost:8080/ → HTML content.
  • curl http://localhost:8080/test.txt → Text content.
  • curl http://localhost:8080/missing → 404.
  • curl http://localhost:8080/../secret → 400 or 404.

Hints:

  • Use Files.readAllBytes for file reading.
  • Use Path.normalize() for path safety.

Ticket 7: Add Error Handling and Logging

Description: Enhance the server with robust error handling and logging.

Tasks:

  • Handle socket and file I/O exceptions gracefully.
  • Log each request with method, path, and status code (e.g., GET / - 200).
  • Add a Server: SimpleJavaServer header to all responses.
  • Ensure the server doesn't crash on errors.

Testing:

  • Simulate errors (e.g., remove www/index.html, send invalid requests).
  • Check console logs for request details and errors.

Hints:

  • Use try-catch for all I/O operations.
  • Log errors with System.err.

Bonus Ticket: Support Query Parameters

Description: (Optional) Parse query parameters and include them in the /echo route.

Tasks:

  • Parse the query string from the path (after ?).
  • Store parameters in a Map<String, String> (simplified, single values).
  • For /echo, include query parameters in the response body.
  • Ensure file serving uses the base path (ignoring query parameters).

Testing:

  • curl http://localhost:8080/echo?name=test → Response includes name=test.
  • curl http://localhost:8080/?param=value → Serves index.html.

Hints:

  • Split the path by ? and parse the query string manually.
  • Use URLDecoder for query values.

Conclusion

Completing all tickets will result in a functional HTTP server in Java! This project teaches low-level networking, request parsing, and file serving. Extend it further if desired (e.g., support HEAD requests or multi-threading with ExecutorService). Submit your code and test outputs for review. Great job!

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