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
localhostport 8080 by default. - Handle only GET requests; respond with 405 Method Not Allowed for other methods.
- Serve static files from a
wwwdirectory 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.
Description: Create the foundation for your HTTP server by setting up a TCP server socket that listens for incoming connections.
Tasks:
- Create a
ServerSocketonlocalhost:8080. - In a loop, accept incoming
Socketconnections. - 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:8080or a browser to see "Hello, World!". - Ensure the server handles multiple connections without crashing.
Hints:
- Use
try-with-resourcesfor socket and stream management. - Handle
IOExceptionfor robust error handling.
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:8080and 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 8080to verify error handling.
Hints:
- Use
BufferedReaderto read lines from the socket. - Ensure responses use
\r\nline endings.
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.
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
HashMapfor headers. - Stop parsing at an empty line.
Description: Implement routing for GET requests with proper HTTP response headers.
Tasks:
- For path
/, respond with200 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 Foundand body"Not Found". - Include
Content-Lengthin 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-Lengthbased on the response body. - Use
\r\nfor header line endings.
Description: Serve files from a www directory for GET requests.
Tasks:
- Create a
wwwfolder with anindex.htmlcontaining<h1>Welcome!</h1>. - Map
/towww/index.htmland other paths (e.g.,/style.css) towww/style.css. - Use
java.nio.fileto read files. - Set
Content-Typebased on file extension (.html→text/html,.css→text/css,.txt→text/plain, defaultapplication/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.readAllBytesfor file reading. - Use
Path.normalize()for path safety.
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: SimpleJavaServerheader 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.
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 includesname=test.curl http://localhost:8080/?param=value→ Servesindex.html.
Hints:
- Split the path by
?and parse the query string manually. - Use
URLDecoderfor query values.
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!