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

一、前言

此项目用纯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完成,这里不作介绍

相关推荐
蔬菜_3 小时前
前端转全栈-day6(构造函数与继承)
java
程序员雷欧3 小时前
Java 反射深度解析:从原理到源码的全面剖析
java·开发语言·python
登登登__4 小时前
春秋招笔试题总结
java·开发语言
rannn_1114 小时前
【力扣hot100】链表专题|21、2、19、24、92、25
java·数据结构·算法·leetcode·链表·开发
秋田君4 小时前
QT_绘图原理双缓冲机制
服务器·数据库·qt
GlobalInfo4 小时前
AI与另类数据融合,市场研究正在从“经验驱动”走向“数据智能”
大数据·网络·人工智能·ai
Rabitebla4 小时前
C++11 新特性详解(一):列表初始化、initializer_list 与右值引用
java·开发语言·数据结构·c++·算法·leetcode·list
天吾cc5 小时前
RTT-MQTT
网络·单片机·嵌入式硬件·算法
洋不写bug5 小时前
JavaComparable、Comparator、Cloneable接口解析,深拷贝浅拷贝详细解析
android·java·开发语言
千视kiloview5 小时前
“轻”不等于降级:IP时代广播制作系统的轻量化与Hybrid架构实践
网络·tcp/ip·架构