JavaSE 手写 Web 服务器(一)

原文地址:JavaSE 手写 Web 服务器(一)
博客地址:

一、背景

某日,在 Java 技术群中看到网友讨论 tomcat 容器相关内容,然后想到自己能不能实现一个简单的 web 容器。于是翻阅资料和思考,最终通过 JavaSE 原生 API 编写出一个简单 web 容器(模拟 tomcat)。在此只想分享编写简单 web 容器时的思路和技巧。

二、涉及知识

Socket 编程:服务端通过监听端口,提供客户端连接进行通信。

Http 协议:分析和响应客户端请求。

多线程:处理多个客户端请求。

用到的都是 JavaSE 的基础知识。

三、初步模型 3.1 通过 Socket API 编写服务端

服务端的功能:接收客户端发送的的数据和响应数据回客户端。

package com.light.server; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Date; public class Server { private static final String BLANK = " "; private static final String RN = "\r\n"; private ServerSocket server; public static void main(String[] args) { Server server = new Server(); server.start(); } /** * 启动服务器 */ public void start() { try { server = new ServerSocket(8080); // 接收数据 this.receiveData(); } catch (IOException e) { e.printStackTrace(); } } /** * 接收数据 */ private void receiveData() { try { Socket client = this.server.accept(); // 读取客户端发送的数据 byte[] data = new byte[10240]; int len = client.getInputStream().read(data); String requestInfo = new String(data,0,len); // 打印客户端数据 System.out.println(requestInfo); // 响应正文 String responseContent = "<!DOCTYPE html>" + "<html lang=\"zh\">" + " <head> " + " <meta charset=\"UTF-8\">"+ " <title>测试</title>"+ " </head> "+ " <body> "+ " <h3>Hello World</h3>"+ " </body> "+ "</html>"; StringBuilder response = new StringBuilder(); // 响应头信息 response.append("HTTP/1.1").append(BLANK).append("200").append(BLANK).append("OK").append(RN); response.append("Content-Length:").append(responseContent.length()).append(RN); response.append("Content-Type:text/html").append(RN); response.append("Date:").append(new Date()).append(RN); response.append("Server:nginx/1.12.1").append(RN); response.append(RN); // 添加正文 response.append(responseContent); // 输出到浏览器 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); bw.write(response.toString()); bw.flush(); bw.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 关闭服务器 */ public void stop() { } }

启动程序,通过浏览器访问 :8080/login?username=aaa&password=bbb,结果如下图:

image

响应信息与代码中设置的一致。

3.2 分析客户端数据 3.2.1 获取 get 方式的请求数据

打开浏览器,通过 get 方式请求 :8080/login?username=aaa&password=bbb 服务端打印内容如下:

GET /login?username=aaa&password=bbb HTTP/1.1 Host: localhost:8080 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.8 Cookie: SESSION=2b5369d6-9d94-4b54-9ef3-05e47fe63025; JSESSIONID=3B48C7BF26937058A433A29EB2F978BC 3.2.2 获取 post 方式的请求数据

编写一个简单的 html 页面,发送 post 请求,

<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>测试</title> </head> <body> <form action="http://localhost:8080/login" method="post"> <table> <tr> <td>用户名</td> <td><input type="text"></td> </tr> <tr> <td>密码</td> <td><input type="password"></td> </tr> <tr> <td>爱好</td> <td> <input type="checkbox" value="1">篮球&nbsp; <input type="checkbox" value="2">足球&nbsp; <input type="checkbox" value="3">棒球 </td> </tr> <tr> <td colspan="2"> <input type="submit" value="提交">&nbsp;&nbsp; <input type="reset" value="重置"> </td> </tr> </table> </form> </body> </html>

服务端打印内容如下:

POST /login HTTP/1.1 Host: localhost:8080 Connection: keep-alive Content-Length: 41 Cache-Control: max-age=0 Origin: null Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.8 Cookie: SESSION=2b5369d6-9d94-4b54-9ef3-05e47fe63025; JSESSIONID=3B48C7BF26937058A433A29EB2F978BC username=aaa&password=bbb&likes=1&likes=2

通过分析和对比两种请求方式的数据,我们可以得到以下结论:

共同点:请求方式、请求 URL 和请求协议都是放在第一行。

不同点:get 请求的请求参数与 URL 拼接在一起,而 post 请求参数放在数据的最后一行。

四、封装请求和响应

Java 作为面向对象的程序开发语言,封装是其三大特性之一。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwgxgd.html