Part 1: Tự tạo lại một Servlet Container đơn giản để serve HTTP request bằng ServerSocket

0 0 0

Người đăng: Sang Le

Theo Viblo Asia

Chào những người anh em,

Hôm nay tự nhiên hứng chí nhớ lại môn mạng máy tính được học thời đại học, nhớ thầy từng dạy tạo một ứng dụng chat bằng ServerSocket (một interface nằm trong gói network của Java), tự nhiên tui lại liên tưởng sao không thử tạo một servlet container đơn giản để serve các HTTP request giống Tomcat, Jetty các thứ xem như thế nào (ý tưởng giống thôi nhé, chứ không giống lắm, vì cái của tui là siêu đơn giản 😂).

Trong bài thì như bài cook ra cái Servlet container đơn giản thôi, chi tiết thì tui update từ từ nhé những người anh em. Code tui cũng để trong repo kèm theo bài viết cuối bài nhé!

Chuẩn bị nguyên liệu nấu ăn

1. SimpleServletRequest

Đây là một interface đơn giản tương tự HttpServletRequest bao gồm các method liên quan đến việc xử lý HTTP request:

  • String getMethod();
  • String getPath();
  • String getHeader(String name);
  • String getBody();

2. SimpleServletResponse

Này tương tự HttpServletResponse bao gồm các method liên quan đến việc xử lý HTTP response, ví dụ gồm các method đơn giản sau:

  • void setStatus(int statusCode);
  • void setHeader(String name, String value);
  • void writeBody(String body);
  • void sendResponse() throws Exception;

3. CabinServletContainer

Đâu ra chữ Cabin hả những người anh em, vì tui thích đặt tên vậy thôi, câu chuyện liên quan đến Cabin dài lắm, anh em sẽ bắt gặp Cabin nhiều hơn trong các bài viết của tui 😁

Đây là một cái ServletContainer đơn giản thôi, bao gồm một số method đơn giản phục vụ cho việc start, đăng ký các Handler, xử lý các request:

  • void registerServlet
  • void start(int port)
  • void handleRequest(Socket clientSocket)

Nấu ăn

Do anh em mình nấu món đơn giản nên thôi, bỏ hết mớ nguyên liệu sơ chế sẳn vào nồi luộc là lụm luôn 😁, quá đơn giản do hôm nay anh em mình ăn món luộc thôi, hôm sau thì làm các món cầu kì hơn.

1. SimpleServletRequestImpl

Chúng ta sẽ implements cái interface SimpleServletRequest nhé nên anh em chế biến mấy cái method định nghĩa trong interface đó nhe:


public class SimpleServletResponseImpl implements SimpleServletResponse { private OutputStream outputStream; private int statusCode = 200; private Map<String, String> headers = new HashMap<>(); private StringBuilder body = new StringBuilder(); public SimpleServletResponseImpl(OutputStream outputStream) { this.outputStream = outputStream; } @Override public void setStatus(int statusCode) { this.statusCode = statusCode; } @Override public void setHeader(String name, String value) { headers.put(name, value); } @Override public void writeBody(String body) { this.body.append(body); } @Override public void sendResponse() { try { System.err.println("Sending response"); // Map status codes to standard HTTP messages String statusMessage = switch (statusCode) { case 200 -> "OK"; case 404 -> "Not Found"; case 500 -> "Internal Server Error"; default -> "Unknown"; }; // Build the response StringBuilder response = new StringBuilder(); response.append("HTTP/1.1 ").append(statusCode).append(" ").append(statusMessage).append("\r\n"); headers.forEach((key, value) -> response.append(key).append(": ").append(value).append("\r\n")); byte[] bodyBytes = body.toString().getBytes("UTF-8"); response.append("Content-Length: ").append(bodyBytes.length).append("\r\n"); response.append("Connection: close\r\n"); response.append("\r\n"); // Write headers outputStream.write(response.toString().getBytes("UTF-8")); // Write body outputStream.write(bodyBytes); outputStream.flush(); } catch (java.net.SocketException e) { System.err.println("Client disconnected before response was fully sent: " + e.getMessage()); } catch (Exception e) { e.printStackTrace(); } }
}

2. SimpleServletResponseImpl

Nấu tiếp thằng SimpleServletResponse thôi:

public class SimpleServletResponseImpl implements SimpleServletResponse { private OutputStream outputStream; private int statusCode = 200; private Map<String, String> headers = new HashMap<>(); private StringBuilder body = new StringBuilder(); public SimpleServletResponseImpl(OutputStream outputStream) { this.outputStream = outputStream; } @Override public void setStatus(int statusCode) { this.statusCode = statusCode; } @Override public void setHeader(String name, String value) { headers.put(name, value); } @Override public void writeBody(String body) { this.body.append(body); } @Override public void sendResponse() { try { System.err.println("Sending response"); // Map status codes to standard HTTP messages String statusMessage = switch (statusCode) { case 200 -> "OK"; case 404 -> "Not Found"; case 500 -> "Internal Server Error"; default -> "Unknown"; }; // Build the response StringBuilder response = new StringBuilder(); response.append("HTTP/1.1 ").append(statusCode).append(" ").append(statusMessage).append("\r\n"); headers.forEach((key, value) -> response.append(key).append(": ").append(value).append("\r\n")); byte[] bodyBytes = body.toString().getBytes("UTF-8"); response.append("Content-Length: ").append(bodyBytes.length).append("\r\n"); response.append("Connection: close\r\n"); response.append("\r\n"); // Write headers outputStream.write(response.toString().getBytes("UTF-8")); // Write body outputStream.write(bodyBytes); outputStream.flush(); } catch (java.net.SocketException e) { System.err.println("Client disconnected before response was fully sent: " + e.getMessage()); } catch (Exception e) { e.printStackTrace(); } }
}

3. CabinServletServer

Trong phần này anh em để ý cái quan trọng nhất là chỗ hàm start

public void start(int port) throws Exception { try (ServerSocket serverSocket = new ServerSocket(port)) { System.out.println("Server started on port " + port); while (true) { Socket clientSocket = serverSocket.accept(); handleRequest(clientSocket); } }
}

1. Khởi tạo ServerSocket

ServerSocket serverSocket = new ServerSocket(port);

Khởi tạo một đối tượng ServerSocket, giúp server bắt đầu lắng nghe các kết nối từ client tại cổng được chỉ định.

2. Vòng lặp while (true) lắng nghe kết nối

while (true) { Socket clientSocket = serverSocket.accept(); handleRequest(clientSocket);
}

Đây là vòng lặp chính để server hoạt động liên tục. Nó lắng nghe kết nối từ các client và xử lý từng kết nối một. Khi một client kết nối thành công, nó trả về một đối tượng Socket. Đối tượng này đại diện cho kết nối giữa server và client.

handleRequest(clientSocket);

Xử lý các kết nối và response đến client. Đơn giản thế thôi, sao đoạn này làm tui nhớ tới học môn mạng máy tính quá, concept code y chang luôn 😁

Đây code đầy đủ đây:

public class CabinServletContainer { private Map<String, SimpleServlet> servlets = new HashMap<>(); public void registerServlet(String path, SimpleServlet servlet) { servlets.put(path, servlet); servlet.init(); } public void start(int port) throws Exception { try (ServerSocket serverSocket = new ServerSocket(port)) { System.out.println("Server started on port " + port); while (true) { Socket clientSocket = serverSocket.accept(); handleRequest(clientSocket); } } } private void handleRequest(Socket clientSocket) { try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) { SimpleServletRequest request = new SimpleServletRequestImpl(inputStream); SimpleServletResponse response = new SimpleServletResponseImpl(outputStream); SimpleServlet servlet = servlets.get(request.getPath()); if (servlet != null) { servlet.service(request, response); } else { response.setStatus(404); response.writeBody("Not Found"); } response.sendResponse(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws Exception { CabinServletContainer container = new CabinServletContainer(); // Register a simple servlet container.registerServlet("/hello", new SimpleServlet() { @Override public void init() { System.out.println("HelloServlet initialized"); } @Override public void service(SimpleServletRequest request, SimpleServletResponse response) { response.writeBody("Hello, World!"); } @Override public void destroy() { System.out.println("HelloServlet destroyed"); } }); container.start(8080); }
}

Ăn thử và cảm nhận

Testing

curl http://localhost:8080/hello
Hello, World!
curl http://localhost:8080/increment
Not Found

Cảm nhận về cái Server vừa cook ra

Xong thì tui đã demo cho anh em hiểu cách hoạt động của một web server cơ bản hoạt động, cũng như cách mà các framework và container như Tomcat hoặc Jetty xử lý các Http request.

Tuy nhiên có có một số nhược điểm rõ ràng khi so sánh với cá servlet container kia (những món ăn ngon trong nhà hàng 🙃). Một số cái chưa hỗ trợ trong món ăn đơn giản này:

  1. Không hỗ trợ đa luồng
  2. Chưa thuân thủ chuẩn Java Servlet API (vì món luộc nên cũng không cầu kì màu mè)
  3. Không xử lý lỗi đầy đủ (vì chưa sơ chế nguyên vật liệu kỹ càng)
  4. Không quản lý lifecycle của servlet (không đảm bảo an toàn thực phẩm)
  5. Không hỗ trợ session và cookie (Món luộc nên không cũng không có món ăn kèm, hay topping,...)
  6. Hiệu suất kém (luộc thì chắc vị nó dở, khó ăn)
  7. .... Thôi nhiều quá không kể hết

Nấu xong mà nếm phát xong vứt luôn, thôi những bài sau thì mình sẽ nấu lại món này ngon hơn nhé. Thôi hẹn gặp lại những người anh em trong bài viết tiếp theo. Bài này không kêu gọi anh em vote đâu, nấu dở quá mà 😄

À code đầy đủ ở đây nhé những người anh em code nè

Cảm ơn những người anh em đã đọc đến đây 😊

Bình luận

Bài viết tương tự

- vừa được xem lúc

Chia sẻ đồ án Xây dựng website chia sẻ địa điểm sử dụng Java Servlet

Xây dựng website chia sẻ địa điểm hấp dẫn - Java Servlet. . Mục đích Chia sẻ. .

0 0 35

- vừa được xem lúc

Giới thiệu về Listener trong Java Servlet

21, Giới thiệu về Listener. Phần này là trích đoạn một phần tài liệu nhật ký học web với Java Servlet của tôi.

0 0 4