通信开发--接收浏览器网络连接

一、前言

此项目用纯java手写了一个HTTP服务器(没有用Spring/Tomcat等框架),配合一个前端登录页面,实现了完整的前后端交互:

**后端:**监听9999端口,接收浏览器请求,返回HTML静态文件或JSON数据

**前端:**一个美观的登录界面,通过fetch API向后端发送登录请求

核心价值:从零理解HTTP协议和Web服务器的工作原理

二、后端Server.java核心知识点

2.1 HTTP 协议基础

浏览器和服务器之间通信使用 HTTP 协议,本质就是按特定格式发送的纯文本。

一个 HTTP 请求长这样:

复制代码
POST /login HTTP/1.1                    ← 请求行(方法 + 路径 + 协议版本)
Host: localhost:9999                     ← 请求头(多个,每行一个 key: value)
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
                                        ← 空行(分隔头和体)
username=admin&password=123456          ← 请求体(POST 才有)

一个 HTTP 响应长这样:

复制代码
HTTP/1.1 200 OK                          ← 状态行(协议 + 状态码 + 状态文本)
Content-Type: application/json           ← 响应头
Content-Length: 52
                                        ← 空行
{"success":true,"message":"登录成功"}   ← 响应体

关键规则 :每行末尾是 \r\n(回车+换行),头和体之间有一个空行 (就是 \r\n\r\n)。

2.2 服务器启动流程
java 复制代码
serverSocket = new ServerSocket(9999);       // 1. 绑定 9999 端口
while (true) {
    Socket socket = serverSocket.accept();    // 2. 阻塞等待浏览器连接
    new Thread(new RequestHandler(socket)).start();  // 3. 每个连接开一个线程处理
}

知识点:

ServerSocket 是服务端的 Socket,accept() 会阻塞(停住等待),直到有客户端连接

while(true) 让服务器持续运行,不断接收新连接

new Thread(...).start() --- 每来一个请求就开一个新线程,这样多个浏览器可以同时访问(并 发)

2.3 请求解析(RequestHandler.run())

这是最值得学习的部分------手动解析 HTTP 请求的三个阶段:

第一阶段:解析请求行

java 复制代码
String requestLine = reader.readLine();   // 读第一行: "POST /login HTTP/1.1"
String[] parts = requestLine.split(" ");
String method = parts[0];     // "POST"
String path = parts[1];       // "/login"

第二阶段:解析请求头

java 复制代码
while ((line = reader.readLine()) != null && !line.isEmpty()) {
    int colon = line.indexOf(":");
    String key = line.substring(0, colon).trim();   // "Content-Length"
    String val = line.substring(colon + 1).trim();  // "29"
    headers.put(key, val);
}

关键 :请求头是按行读取的,遇到空行 (line.isEmpty())说明头结束了。

第三阶段:读取请求体

java 复制代码
char[] body = new char[contentLength];
reader.read(body, 0, contentLength);   // 按 Content-Length 读指定长度
String bodyStr = new String(body);     // "username=admin&password=123456"

为什么不能直接 readLine() 读体? 因为请求体可能没有换行符,必须按 Content-Length 头指定的字节数来读。

2.4 路由分发
java 复制代码
if ("POST".equals(method) && "/login".equals(path)) {
    handleLogin(reader, out, contentLength);     // POST /login → 登录验证
} else if ("GET".equals(method)) {
    handleStaticFile(out, path);                  // GET → 返回静态文件
} else {
    sendError(out, 405, "Method Not Allowed");    // 其他 → 405 错误
}

这就是一个极简的路由系统 :根据 HTTP 方法和路径,分发到不同的处理方法。Spring 的 @RequestMapping 底层也是这个原理,只是封装得更优雅。

2.5 GET --- 静态文件处理(handleStaticFile)
java 复制代码
File file = new File(Server.rootDir, path).getCanonicalFile();
String rootCanonical = new File(Server.rootDir).getCanonicalPath();

// 防止路径穿越攻击
if (!file.getPath().startsWith(rootCanonical)) {
    sendError(out, 403, "Forbidden");
    return;
}

重点学习 --- 路径穿越攻击防护:

如果用户请求 GET /../../etc/passwd,不做防护的话可以读到服务器上的任意文件。getCanonicalPath() 会解析 .. 得到真实路径,然后检查它是否还在网站根目录内。

读取文件后,根据扩展名设置 Content-Type(MIME 类型):

java 复制代码
private String getMimeType(String fileName) {
    if (fileName.endsWith(".html")) return "text/html; charset=UTF-8";
    if (fileName.endsWith(".css"))   return "text/css; charset=UTF-8";
    if (fileName.endsWith(".js"))    return "application/javascript; charset=UTF-8";
    if (fileName.endsWith(".png"))   return "image/png";
    // ...
    return "application/octet-stream";  // 默认:二进制流
}

三、前端部分

前端部分主要由workBuddy完成,这里不作介绍

相关推荐
酣大智2 小时前
TightVNC Server 配置窗口
网络·vnc
隐擎fox2 小时前
解密 BGP Anycast 边缘路由与跨国链路质量检测:多租户网络出口的低时延与防丢包架构实战
网络·python·网络协议·tcp/ip·网络安全·架构·tcpdump
金銀銅鐵2 小时前
[Java] 用 GUI 展示 ArrayList 扩容的过程
java·后端·ai编程
磊 子2 小时前
什么是虚假唤醒(spurious wakeup)?从 POSIX 标准讲到 futex 内核
java·开发语言
Allen-Steven2 小时前
SQL查询专项:从完整书写到综合判断
运维·服务器·数据库
guo_wen_qiang2 小时前
云服务器安装MongoDB
运维·服务器
Data analyse4562 小时前
App埋点版本兼容的字段迁移校验
大数据·运维·服务器·前端·数据库·数据分析
飘然生2 小时前
traceId链路 skywalking java agent
java·开发语言·skywalking
anew___2 小时前
《从零手写操作系统 (46):设备模型与devtmpfs——硬件资源的文件系统化》
linux·运维·服务器
Escalating_xu2 小时前
【Linux 网络编程】Windows 客户端访问 Linux:用 Winsock 跑通 UDP 回显
linux·网络·windows