-
Notifications
You must be signed in to change notification settings - Fork 0
HTTP Server
HttpServer is a complete HTTP/1.1 server implementation built on top of ConnectionServer. It provides annotation-based routing, request parsing, response building, and static file serving.
┌─────────────────────────────────────┐
│ HttpServer │ ← YOU ARE HERE
│ (HTTP protocol implementation) │
├─────────────────────────────────────┤
│ ConnectionServer │ ← TCP transport
│ (Socket handling) │
├─────────────────────────────────────┤
│ Server (abstract base) │ ← Foundation
└─────────────────────────────────────┘
Files:
-
connection/http/HttpServer.java- Main server class -
connection/http/Route.java- Routing annotation -
connection/http/HttpRequest.java- Request model -
connection/http/HttpResponse.java- Response model
import jsi.connection.http.*;
import jsi.connection.http.response.HttpResponseType;
public class MyWebServer extends HttpServer {
public MyWebServer(int port) {
super(port);
}
@Route(path = "/")
public HttpResponse home(HttpRequest request) {
return createHtmlResponse(HttpResponseType.OK,
"<h1>Welcome!</h1>");
}
@Route(path = "/about")
public HttpResponse about(HttpRequest request) {
return createHtmlResponse(HttpResponseType.OK,
"<h1>About Page</h1>");
}
public static void main(String[] args) {
new MyWebServer(8080).start();
}
}Access at:
-
http://localhost:8080/- Home page -
http://localhost:8080/about- About page
The @Route annotation maps URL paths to handler methods:
@Route(path = "/users")
public HttpResponse getUsers(HttpRequest request) {
// Handle /users route
}
@Route(path = "/products")
public HttpResponse getProducts(HttpRequest request) {
// Handle /products route
}How it works:
- On server startup,
HttpServerscans all methods in your class - Methods annotated with
@Routeare registered as route handlers - Incoming requests are matched against registered paths
- Matching method is invoked with the
HttpRequestobject
Serve HTML files directly without writing handler logic:
@Route(path = "/", staticResource = "static/index.html")
public HttpResponse home(HttpRequest request) {
return null; // File served automatically
}
@Route(path = "/contact", staticResource = "static/contact.html")
public HttpResponse contact(HttpRequest request) {
return null; // File served automatically
}File structure:
project/
├── static/
│ ├── index.html
│ ├── contact.html
│ └── about.html
├──
└── MyWebServer.java
Important: Paths are relative to the working directory (where you run java).
Extract query parameters from URLs:
@Route(path = "/greet")
public HttpResponse greet(HttpRequest request) {
String name = (String) request.getParameter("name");
String age = (String) request.getParameter("age");
if (name == null) name = "Guest";
String html = String.format(
"<h1>Hello, %s!</h1><p>Age: %s</p>",
name, age != null ? age : "unknown"
);
return createHtmlResponse(HttpResponseType.OK, html);
}Usage:
-
http://localhost:8080/greet→ "Hello, Guest!" -
http://localhost:8080/greet?name=Alice→ "Hello, Alice!" -
http://localhost:8080/greet?name=Bob&age=25→ "Hello, Bob! Age: 25"
Build responses with custom headers and content types:
@Route(path = "/api/users")
public HttpResponse getUsersApi(HttpRequest request) {
String json = "[{\"id\": 1, \"name\": \"Alice\"}, " +
"{\"id\": 2, \"name\": \"Bob\"}]";
return new HttpResponse(
HttpResponseType.OK,
new ResponseHeader[] {
new HttpResponseHeader("Content-Type", "application/json"),
new HttpResponseHeader("Cache-Control", "no-cache"),
new HttpResponseHeader("Content-Length",
String.valueOf(json.getBytes(StandardCharsets.UTF_8).length))
},
new HttpResponseBody(json)
);
}Convenience method:
protected Response createHtmlResponse(HttpResponseType type, String content) {
// Automatically sets Content-Type and Content-Length
}File: connection/http/HttpRequest.java
public class HttpRequest implements Request {
private String path; // "/users"
private HttpRequestType requestType; // GET, POST, PUT, DELETE
private HttpRequestParameter[] parameters; // Query params
private HttpRequestHeader[] headers; // HTTP headers
public String getPath() { return path; }
public Object getParameter(String name) {
// Returns parameter value or null
}
public HttpRequestType getHttpRequestType() {
return requestType;
}
}Request line:
GET /users?id=123&name=Alice HTTP/1.1
│ │ │
└─ Method └─ Protocol version
└─ Path + query string
Parsing example:
// Raw HTTP request
String raw = "GET /users?id=123 HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"User-Agent: Mozilla/5.0\r\n" +
"\r\n";
HttpRequest request = new HttpRequest(raw);
String path = request.getPath(); // "/users"
String id = request.getParameter("id"); // "123"
HttpRequestType type = request.getHttpRequestType(); // GETFile: connection/http/request/HttpRequestType.java
public enum HttpRequestType {
GET,
POST,
PUT,
DELETE,
HEAD,
OPTIONS,
PATCH
}Usage:
@Route(path = "/users")
public HttpResponse handleUsers(HttpRequest request) {
switch (request.getHttpRequestType()) {
case GET:
return listUsers();
case POST:
return createUser(request);
case DELETE:
return deleteUser(request);
default:
return createHtmlResponse(
HttpResponseType.METHOD_NOT_ALLOWED,
"<h1>405 - Method Not Allowed</h1>"
);
}
}File: connection/http/HttpResponse.java
public class HttpResponse implements Response {
private ResponseType responseType; // 200 OK, 404 NOT FOUND, etc.
private ResponseHeader[] headers; // HTTP headers
private ResponseBody body; // Response content
@Override
public String serialize() {
// Converts to HTTP format:
// HTTP/1.1 200 OK
// Content-Type: text/html
// Content-Length: 123
//
// <html>...</html>
}
}File: connection/http/response/HttpResponseType.java
public enum HttpResponseType implements ResponseType {
OK("200 OK"),
CREATED("201 Created"),
NO_CONTENT("204 No Content"),
BAD_REQUEST("400 Bad Request"),
UNAUTHORIZED("401 Unauthorized"),
FORBIDDEN("403 Forbidden"),
NOT_FOUND("404 Not Found"),
METHOD_NOT_ALLOWED("405 Method Not Allowed"),
INTERNAL_SERVER_ERROR("500 Internal Server Error"),
SERVICE_UNAVAILABLE("503 Service Unavailable");
private String name;
HttpResponseType(String name) { this.name = name; }
@Override
public String getName() { return name; }
}Simple HTML response:
return createHtmlResponse(HttpResponseType.OK, "<h1>Success</h1>");JSON response:
String json = "{\"status\": \"ok\"}";
return new HttpResponse(
HttpResponseType.OK,
new ResponseHeader[] {
new HttpResponseHeader("Content-Type", "application/json")
},
new HttpResponseBody(json)
);Redirect response:
return new HttpResponse(
HttpResponseType.MOVED_PERMANENTLY,
new ResponseHeader[] {
new HttpResponseHeader("Location", "/new-path")
},
new HttpResponseBody("")
);Server starts
↓
onServerStarted() called
↓
registerRoutes() scans class
↓
Finds methods with @Route annotation
↓
Creates RoutedMethod objects
↓
Stores in routes list
↓
Ready to handle requests
File: connection/http/HttpServer.java
public class HttpServer extends ConnectionServer {
private final List<RoutedMethod> routes = new ArrayList<>();
protected void registerRoutes() {
for (Method method : this.getClass().getDeclaredMethods()) {
if (method.isAnnotationPresent(Route.class)) {
Route routeAnnotation = method.getAnnotation(Route.class);
String path = routeAnnotation.path();
String staticResource = routeAnnotation.staticResource();
routes.add(new RoutedMethod(path, method, staticResource));
}
}
}
@Override
public HttpResponse handleRequest(Request request) {
String path = extractPath(request);
for (RoutedMethod handler : routes) {
if (handler.getPath().equals(path)) {
try {
return handler.handleRequest(request);
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
return handleNotFound(request);
}
}public class RoutedMethod {
String path;
Method method;
String staticResource;
public HttpResponse handleRequest(Request request) throws Exception {
// If static file, serve it
if (staticResource != null && !staticResource.isEmpty()) {
return serveHtmlFile(staticResource);
}
// Otherwise, invoke handler method
method.setAccessible(true);
return (HttpResponse) method.invoke(HttpServer.this, request);
}
}Override handleNotFound() to customize error pages:
@Override
protected HttpResponse handleNotFound(Request request) {
String html = "<html><head><title>404</title></head><body>" +
"<h1>Page Not Found</h1>" +
"<p>The requested resource doesn't exist.</p>" +
"<a href='/'>Go Home</a>" +
"</body></html>";
return createHtmlResponse(HttpResponseType.NOT_FOUND, html);
}@Route(path = "/", staticResource = "static/index.html")
public HttpResponse home(HttpRequest request) {
return null; // Return value ignored
}Behind the scenes:
-
RoutedMethodchecks ifstaticResourceis set - Calls
serveHtmlFile(staticResource) - Uses
ConnectionServer.readFile()to read file - Wraps content in
HttpResponsewith appropriate headers
@Route(path = "/download")
public HttpResponse download(HttpRequest request) {
String filename = (String) request.getParameter("file");
try {
String content = readFile("files/" + filename);
return new HttpResponse(
HttpResponseType.OK,
new ResponseHeader[] {
new HttpResponseHeader("Content-Type", "application/octet-stream"),
new HttpResponseHeader("Content-Disposition",
"attachment; filename=\"" + filename + "\"")
},
new HttpResponseBody(content)
);
} catch (IOException e) {
return handleFileReadError("files/" + filename, e);
}
}By default, serveHtmlFile() sets Content-Type: text/html. For other types, use serveFile():
protected HttpResponse serveFile(String filePath, String contentType) {
// Serves file with custom Content-Type
}Example:
@Route(path = "/styles.css")
public HttpResponse css(HttpRequest request) {
try {
return serveFile("static/styles.css", "text/css");
} catch (IOException e) {
return handleFileReadError("static/styles.css", e);
}
}public class RestApiServer extends HttpServer {
private List<User> users = new ArrayList<>();
public RestApiServer(int port) {
super(port);
// Seed data
users.add(new User(1, "Alice", "[email protected]"));
users.add(new User(2, "Bob", "[email protected]"));
}
@Route(path = "/api/users")
public HttpResponse getUsers(HttpRequest request) {
String json = convertUsersToJson(users);
return new HttpResponse(
HttpResponseType.OK,
new ResponseHeader[] {
new HttpResponseHeader("Content-Type", "application/json")
},
new HttpResponseBody(json)
);
}
@Route(path = "/api/user")
public HttpResponse getUser(HttpRequest request) {
String idParam = (String) request.getParameter("id");
if (idParam == null) {
return jsonError("Missing 'id' parameter");
}
int id = Integer.parseInt(idParam);
User user = findUserById(id);
if (user == null) {
return jsonError("User not found");
}
return jsonResponse(HttpResponseType.OK, convertUserToJson(user));
}
private HttpResponse jsonError(String message) {
String json = "{\"error\": \"" + message + "\"}";
return jsonResponse(HttpResponseType.BAD_REQUEST, json);
}
private HttpResponse jsonResponse(HttpResponseType type, String json) {
return new HttpResponse(
type,
new ResponseHeader[] {
new HttpResponseHeader("Content-Type", "application/json")
},
new HttpResponseBody(json)
);
}
}public class SessionServer extends HttpServer {
private Map<String, Session> sessions = new ConcurrentHashMap<>();
@Route(path = "/login")
public HttpResponse login(HttpRequest request) {
String username = (String) request.getParameter("username");
// Create session
String sessionId = UUID.randomUUID().toString();
sessions.put(sessionId, new Session(username));
return new HttpResponse(
HttpResponseType.OK,
new ResponseHeader[] {
new HttpResponseHeader("Set-Cookie",
"sessionId=" + sessionId + "; Path=/; HttpOnly")
},
new HttpResponseBody("<h1>Logged in as " + username + "</h1>")
);
}
@Route(path = "/profile")
public HttpResponse profile(HttpRequest request) {
String sessionId = extractSessionIdFromCookie(request);
if (sessionId == null || !sessions.containsKey(sessionId)) {
return createHtmlResponse(HttpResponseType.UNAUTHORIZED,
"<h1>Please log in</h1>");
}
Session session = sessions.get(sessionId);
return createHtmlResponse(HttpResponseType.OK,
"<h1>Welcome, " + session.getUsername() + "</h1>");
}
}@Override
protected void onBeforeStart() {
System.out.println("Loading configuration...");
loadDatabase();
initializeCache();
}
@Override
protected void onServerStarted() {
System.out.println("HTTP Server running on port " + getPort());
logToFile("Server started at " + LocalDateTime.now());
}Override parseRequest() to customize HTTP parsing:
@Override
protected HttpRequest parseRequest(String input) {
HttpRequest request = super.parseRequest(input);
// Add custom processing
logRequest(request);
validateRequest(request);
return request;
}public abstract class MiddlewareHttpServer extends HttpServer {
@Override
public HttpResponse handleRequest(Request request) {
// Pre-processing
HttpRequest httpReq = (HttpRequest) request;
// Logging
System.out.println(httpReq.getHttpRequestType() + " " +
httpReq.getPath());
// CORS headers
HttpResponse response = super.handleRequest(request);
addCorsHeaders(response);
return response;
}
private void addCorsHeaders(HttpResponse response) {
// Add CORS headers to response
}
}Each HTTP request is handled in a separate thread (inherited from ConnectionServer):
- Pros: Simple, isolated request handling
- Cons: Limited scalability (< 10,000 concurrent connections)
See ConnectionServer for details.
Current implementation reads files on every request. For production, add caching:
private Map<String, String> fileCache = new ConcurrentHashMap<>();
@Override
protected String readFile(String filePath) throws IOException {
return fileCache.computeIfAbsent(filePath, path -> {
try {
return super.readFile(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}Add gzip compression for large responses:
protected HttpResponse compressResponse(HttpResponse response) {
// Implement gzip compression
// Add Content-Encoding: gzip header
}GET /users?id=123 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0
Accept: text/html
Connection: keep-alive
Components:
- Request line: Method, path, protocol version
- Headers: Key-value pairs
- Empty line: Separates headers from body
- Body: (optional, for POST/PUT requests)
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 123
Connection: close
<html><body><h1>Hello</h1></body></html>
Components:
- Status line: Protocol, status code, status text
- Headers: Key-value pairs
- Empty line: Separates headers from body
- Body: Response content
- Architecture Overview - Three-layer architectural model
-
Core Abstractions -
Request,Responseinterfaces - ConnectionServer - TCP transport layer
- Getting Started - Quick start examples
- Database Server - Alternative protocol implementation
Next: Explore Database Server to see a completely different protocol implementation, or dive into Extensibility Guide to build your own protocols.
JSI - Java Server Interface | Educational Server Framework | Zero Dependencies
Home • Getting Started • Architecture • Source Code
Made for learning | Report Issues • Discussions
Last updated: December 2025 | JSI v1.0
HTTP Development
Database Development
Custom Protocols