从0到1实现一个web服务器
This commit is contained in:
commit
da8cd67432
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
*.log
|
||||
*.class
|
19
README.md
Normal file
19
README.md
Normal file
@ -0,0 +1,19 @@
|
||||
java 知识点学习
|
||||
---
|
||||
|
||||
1. [从0到1实现一个web服务器](demo1/web_server.md)
|
||||
1. 网络请求
|
||||
2. HTTP协议
|
||||
3. 文件读取
|
||||
4. 动静态资源
|
||||
5. 模板渲染
|
||||
2. `@Vaild`为什么能做到校验参数?
|
||||
1. 请求参数处理
|
||||
2. 切面AOP
|
||||
3. 目标寻址(请求URL到目标方法ctrl)
|
||||
4. [GET] URL参数
|
||||
5. [POST] application/x-www-form-urlencoded:表单数据
|
||||
6. [POST] multipart/form-data:文件上传
|
||||
7. [POST] application/json:JSON数据
|
||||
8. [POST] text/xml:XML数据
|
||||
3. spring boot的启动原理
|
11
demo1/pom.xml
Normal file
11
demo1/pom.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>top.yexuejc</groupId>
|
||||
<artifactId>demo1</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>demo1</name>
|
||||
<description>从0到1实现一个web服务器</description>
|
||||
|
||||
</project>
|
36
demo1/src/main/java/top/yexuejc/demo/Request.java
Normal file
36
demo1/src/main/java/top/yexuejc/demo/Request.java
Normal file
@ -0,0 +1,36 @@
|
||||
package top.yexuejc.demo;
|
||||
|
||||
/**
|
||||
* 请求对象类
|
||||
* @author maxiaofeng
|
||||
* @date 2025/6/19 11:37
|
||||
*/
|
||||
public class Request {
|
||||
private final String method;
|
||||
private final String path;
|
||||
private final String body;
|
||||
private final String params;
|
||||
|
||||
public Request(String method, String path, String body, String params) {
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
this.body = body;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public String getParams() {
|
||||
return params;
|
||||
}
|
||||
}
|
219
demo1/src/main/java/top/yexuejc/demo/RequestHandler.java
Normal file
219
demo1/src/main/java/top/yexuejc/demo/RequestHandler.java
Normal file
@ -0,0 +1,219 @@
|
||||
package top.yexuejc.demo;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* 请求处理
|
||||
*
|
||||
* @author maxiaofeng
|
||||
* @date 2025/6/19 11:29
|
||||
*/
|
||||
public class RequestHandler implements Runnable {
|
||||
Logger logger = Logger.getLogger(RequestHandler.class.getName());
|
||||
private final String OS_NAME = System.getProperty("os.name");
|
||||
private final List<String> STATIC_RESOURCES = List.of("css", "js", "img", "fonts", "favicon.ico");
|
||||
|
||||
private final Socket clientSocket;
|
||||
private final String staticRoot;
|
||||
private final String webRoot;
|
||||
|
||||
public RequestHandler(Socket socket, String staticRoot, String webRoot) {
|
||||
this.clientSocket = socket;
|
||||
this.staticRoot = staticRoot;
|
||||
this.webRoot = webRoot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (InputStream input = clientSocket.getInputStream(); OutputStream output = clientSocket.getOutputStream()) {
|
||||
|
||||
// 解析请求
|
||||
Request request = parseRequest(input);
|
||||
|
||||
// 处理请求并生成响应(静态画面)
|
||||
Response response = handleRequest(request);
|
||||
|
||||
// 画面渲染(动态画面)
|
||||
pageLive(request, response);
|
||||
|
||||
// 发送响应
|
||||
sendResponse(output, response);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error handling request: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
clientSocket.close();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error closing client socket: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pageLive(Request request, Response response) {
|
||||
if (!request.getPath().endsWith("html")) {
|
||||
return;
|
||||
}
|
||||
byte[] originalBytes = response.getContent();
|
||||
// 模拟模板引擎
|
||||
// 1. 获取原始的字符串
|
||||
String originalString = new String(originalBytes, StandardCharsets.UTF_8);
|
||||
|
||||
// 2. 替换字符串
|
||||
String replacedString = originalString.replace("${body}", request.getParams());
|
||||
|
||||
// 3. 将替换后的 String 转回 byte[]
|
||||
response.setContent(replacedString.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private Request parseRequest(InputStream input) throws IOException, URISyntaxException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
|
||||
String requestLine = reader.readLine();
|
||||
if (requestLine == null) {
|
||||
throw new IOException("Empty request");
|
||||
}
|
||||
// GET /index.html?a=1 HTTP/1.1
|
||||
// Host: 127.0.0.1:8080
|
||||
// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
|
||||
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;
|
||||
// v=b3;q=0.7
|
||||
|
||||
// POST /getUser HTTP/1.1
|
||||
// Content-Type: application/json
|
||||
// User-Agent: PostmanRuntime/7.44.0
|
||||
// Accept: */*
|
||||
// Host: 127.0.0.1:8080
|
||||
// Accept-Encoding: gzip, deflate, br
|
||||
// Connection: keep-alive
|
||||
// Content-Length: 10
|
||||
|
||||
logger.info("=====================================================");
|
||||
logger.info(requestLine);
|
||||
String[] parts = requestLine.split(" ");
|
||||
if (parts.length != 3) {
|
||||
throw new IOException("Invalid request line: " + requestLine);
|
||||
}
|
||||
|
||||
String method = parts[0]; // GET
|
||||
String path = parts[1]; // /index.html
|
||||
|
||||
URI url = new URI(path);
|
||||
path = url.getPath();
|
||||
String params = url.getQuery();
|
||||
|
||||
|
||||
// 读取请求头
|
||||
String headerLine;
|
||||
int contentLength = 0; // 输入流中字节长度
|
||||
while ((headerLine = reader.readLine()) != null && !headerLine.isEmpty()) {
|
||||
logger.info(headerLine);
|
||||
if (method.equals("POST") && headerLine.startsWith("Content-Length:")) {
|
||||
contentLength = Integer.parseInt(headerLine.split(":")[1].trim());
|
||||
}
|
||||
}
|
||||
|
||||
// 读取POST请求体
|
||||
StringBuilder requestBody = new StringBuilder();
|
||||
if (method.equals("POST") && contentLength > 0) {
|
||||
char[] buffer = new char[contentLength];
|
||||
reader.read(buffer, 0, contentLength);
|
||||
requestBody.append(buffer);
|
||||
logger.info("Body: " + requestBody);
|
||||
}
|
||||
logger.info("Params: " + params);
|
||||
|
||||
logger.info("=====================================================");
|
||||
return new Request(method, path, requestBody.toString(), params);
|
||||
}
|
||||
|
||||
private Response handleRequest(Request request) {
|
||||
String path = request.getPath();
|
||||
|
||||
// 默认主页
|
||||
if (path.equals("/")) {
|
||||
path = "/index.html";
|
||||
}
|
||||
path = getAbsolutePath(path);
|
||||
|
||||
// 获取classPath中的文件路径
|
||||
String classPath = Objects.requireNonNull(getClass().getClassLoader().getResource("")).getPath();
|
||||
if (OS_NAME.startsWith("Windows")) {
|
||||
classPath = classPath.substring(1);
|
||||
}
|
||||
Path filePath = Paths.get(classPath, path);
|
||||
|
||||
// 安全检查,防止目录遍历攻击
|
||||
if (!filePath.startsWith(Paths.get(classPath, webRoot)) && !filePath.startsWith(Paths.get(classPath, staticRoot))) {
|
||||
return new Response(403, "Forbidden", "text/plain", "Access denied".getBytes());
|
||||
}
|
||||
|
||||
// 检查文件是否存在且是普通文件
|
||||
if (Files.exists(filePath) && Files.isRegularFile(filePath)) {
|
||||
try {
|
||||
byte[] content = Files.readString(filePath, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8);
|
||||
String contentType = determineContentType(filePath);
|
||||
return new Response(200, "OK", contentType, content);
|
||||
} catch (IOException e) {
|
||||
return new Response(500, "Internal Server Error", "text/plain", "Error reading file".getBytes());
|
||||
}
|
||||
} else {
|
||||
return new Response(404, "Not Found", "text/plain", "Page not found".getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
private String getAbsolutePath(String path) {
|
||||
if (path.startsWith("/") && STATIC_RESOURCES.stream().anyMatch(path::endsWith)) {
|
||||
return staticRoot + path;
|
||||
}
|
||||
return webRoot + path;
|
||||
}
|
||||
|
||||
private String determineContentType(Path filePath) {
|
||||
String fileName = filePath.getFileName().toString();
|
||||
if (fileName.endsWith(".html")) {
|
||||
return "text/html";
|
||||
} else if (fileName.endsWith(".css")) {
|
||||
return "text/css";
|
||||
} else if (fileName.endsWith(".js")) {
|
||||
return "application/javascript";
|
||||
} else if (fileName.endsWith(".png")) {
|
||||
return "image/png";
|
||||
} else if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
|
||||
return "image/jpeg";
|
||||
} else {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
private void sendResponse(OutputStream output, Response response) throws IOException {
|
||||
String statusLine = STR."""
|
||||
HTTP/1.1 \{response.getStatusCode()} \{response.getStatusMessage()}\r
|
||||
""";
|
||||
String headers = STR."""
|
||||
Content-Type: \{response.getContentType()}\r
|
||||
Content-Length: \{response.getContentLength()}\r
|
||||
\r
|
||||
""";
|
||||
|
||||
output.write(statusLine.getBytes());
|
||||
output.write(headers.getBytes());
|
||||
output.write(response.getContent());
|
||||
output.flush();
|
||||
}
|
||||
}
|
45
demo1/src/main/java/top/yexuejc/demo/Response.java
Normal file
45
demo1/src/main/java/top/yexuejc/demo/Response.java
Normal file
@ -0,0 +1,45 @@
|
||||
package top.yexuejc.demo;
|
||||
|
||||
/**
|
||||
* 响应对象类
|
||||
* @author maxiaofeng
|
||||
* @date 2025/6/19 11:37
|
||||
*/
|
||||
public class Response {
|
||||
private final int statusCode;
|
||||
private final String statusMessage;
|
||||
private final String contentType;
|
||||
private byte[] content;
|
||||
|
||||
public Response(int statusCode, String statusMessage, String contentType, byte[] content) {
|
||||
this.statusCode = statusCode;
|
||||
this.statusMessage = statusMessage;
|
||||
this.contentType = contentType;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public String getStatusMessage() {
|
||||
return statusMessage;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public int getContentLength() {
|
||||
return content.length;
|
||||
}
|
||||
|
||||
public Response setContent(byte[] content) {
|
||||
this.content = content;
|
||||
return this;
|
||||
}
|
||||
}
|
80
demo1/src/main/java/top/yexuejc/demo/WebServer.java
Normal file
80
demo1/src/main/java/top/yexuejc/demo/WebServer.java
Normal file
@ -0,0 +1,80 @@
|
||||
package top.yexuejc.demo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* web服务核心
|
||||
*
|
||||
* @author maxiaofeng
|
||||
* @date 2025/6/19 11:13
|
||||
*/
|
||||
public class WebServer {
|
||||
Logger logger = Logger.getLogger(WebServer.class.getName());
|
||||
/** 端口 */
|
||||
private final int port;
|
||||
/** 页面资源文件 */
|
||||
private final String webRoot;
|
||||
/** 静态资源文件 */
|
||||
private final String staticRoot;
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
private boolean isRunning;
|
||||
|
||||
public WebServer() {
|
||||
this(8080, "static", "web");
|
||||
}
|
||||
|
||||
public WebServer(int port, String staticRoot, String webRoot) {
|
||||
this.port = port;
|
||||
this.staticRoot = staticRoot;
|
||||
this.webRoot = webRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动服务
|
||||
*/
|
||||
public void start() {
|
||||
try {
|
||||
serverSocket = new ServerSocket(port);
|
||||
isRunning = true;
|
||||
logger.info("启动Web服务 : http://127.0.0.1:" + port);
|
||||
|
||||
// 阻塞式监听服务是否正常
|
||||
while (isRunning) {
|
||||
try {
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
// 新建一个线程处理请求
|
||||
new RequestHandler(clientSocket, staticRoot, webRoot).run();
|
||||
} catch (IOException e) {
|
||||
if (isRunning) {
|
||||
logger.severe("创建连接异常。");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable r) {
|
||||
logger.severe("启动Web服务异常。");
|
||||
r.printStackTrace();
|
||||
} finally {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止服务
|
||||
*/
|
||||
public void stop() {
|
||||
isRunning = false;
|
||||
try {
|
||||
if (serverSocket != null) {
|
||||
serverSocket.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.info("停止Web服务异常: " + e.getMessage());
|
||||
}
|
||||
System.out.println("Web服务停止.");
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package top.yexuejc.demo;
|
||||
|
||||
/**
|
||||
* web服务器入口
|
||||
*
|
||||
* @author maxiaofeng
|
||||
* @date 2025/6/19 11:11
|
||||
*/
|
||||
public class WebServerApplication {
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("java.util.logging.SimpleFormatter.format", "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS [%4$s] %2$s - %5$s%6$s%n");
|
||||
new WebServer().start();
|
||||
}
|
||||
}
|
22
demo1/src/main/resources/static/css/index.css
Normal file
22
demo1/src/main/resources/static/css/index.css
Normal file
@ -0,0 +1,22 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #0671dc;
|
||||
/* 让文字左右滚动,并且改变颜色*/
|
||||
text-shadow: 0 0 5px #333;
|
||||
animation: scroll-text 5s linear infinite;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 2s;
|
||||
animation-direction: alternate;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-play-state: running;
|
||||
animation-name: scroll-text;
|
||||
animation-duration: 5s;
|
||||
}
|
13
demo1/src/main/resources/web/index.html
Normal file
13
demo1/src/main/resources/web/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Simple Web Server</title>
|
||||
<link rel="stylesheet" href="/css/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to Simple Web Server</h1>
|
||||
<p>This page is served by our custom Java web server!</p>
|
||||
<p>你的请求参数是:${body}</p>
|
||||
</body>
|
||||
</html>
|
109
demo1/web_server.md
Normal file
109
demo1/web_server.md
Normal file
@ -0,0 +1,109 @@
|
||||
从0到1实现一个web服务器
|
||||
---
|
||||
|
||||
### 一、网络请求
|
||||
|
||||
1. 什么是网络请求?
|
||||
<details>
|
||||
<summary>点击展开</summary>
|
||||
网络请求(Network Request)是指计算机(客户端)向服务器发送数据或从服务器获取数据的过程。它是互联网通信的基础,几乎所有网络应用(如网页、APP、API 调用)都依赖网络请求来交换信息。
|
||||
</details>
|
||||
2. 网络请求的基本概念
|
||||
* 组成:客户端(Client)和服务器(Server)
|
||||
* 常见的网络请求类型
|
||||
|
||||
| 请求方法 | 用途 |
|
||||
|----------|------|
|
||||
| **GET** | 获取数据(如加载网页、查询数据) |
|
||||
| **POST** | 提交数据(如登录、上传文件) |
|
||||
| **PUT** | 更新数据(如修改用户信息) |
|
||||
| **DELETE** | 删除数据(如删除文章) |
|
||||
| **PATCH** | 部分更新数据 |
|
||||
|
||||
* 请求和响应的组成
|
||||
|
||||
| **请求(Request)** | **响应(Response)** |
|
||||
|---------------------|----------------------|
|
||||
| - 请求方法(GET/POST)<br>- URL(目标地址)<br>- 请求头(Headers)<br>- 请求体(Body,可选) | - 状态码(200/404/500)<br>- 响应头(Headers)<br>- 响应体(Body,如 JSON/HTML) |
|
||||
|
||||
3. 网络请求的流程
|
||||
<details>
|
||||
<summary>点击展开</summary>
|
||||
|
||||
1. **客户端发送请求**(如浏览器访问 `https://example.com`)。
|
||||
2. **DNS 解析**(将域名转换为 IP 地址)。
|
||||
3. **建立 TCP 连接**(3 次握手)。
|
||||
4. **发送 HTTP 请求**(如 `GET /index.html`)。
|
||||
5. **服务器处理请求**(读取数据库、计算数据)。
|
||||
6. **服务器返回响应**(如返回 HTML 或 JSON)。
|
||||
7. **客户端解析响应**(如浏览器渲染网页)。
|
||||
8. **关闭连接**(或保持长连接)。
|
||||
</details>
|
||||
|
||||
4. 网络请求的状态码
|
||||
|
||||
| 状态码 | 含义 |
|
||||
|-------------------------------|-------|
|
||||
| **200 OK** | 请求成功 |
|
||||
| **301 Moved Permanently** | 永久重定向 |
|
||||
| **404 Not Found** | 资源不存在 |
|
||||
| **500 Internal Server Error** | 服务器错误 |
|
||||
| **403 Forbidden** | 无权限访问 |
|
||||
|
||||
### 二、WEB服务器
|
||||
|
||||
1. Web服务器的实质是什么?
|
||||
* Web服务器(Web Server)的实质是一个专门处理HTTP/HTTPS请求的软件或计算机系统,它的核心职责是接收客户端(如浏览器、APP)的请求,并返回静态或动态内容(如HTML、JSON、图片等)。
|
||||
2. HTTP请求协议
|
||||
```text
|
||||
GET /index.html HTTP/1.1
|
||||
Host: www.example.com
|
||||
User-Agent: Mozilla/5.0
|
||||
Accept: text/html
|
||||
Connection: keep-alive
|
||||
|
||||
```
|
||||
```text
|
||||
POST /api/users HTTP/1.1
|
||||
Host: api.example.com
|
||||
Content-Type: application/json
|
||||
Content-Length: 45
|
||||
Authorization: Bearer abc123
|
||||
Content-Length: 10
|
||||
|
||||
{"id":"1"}
|
||||
```
|
||||
* 请求行(Request Line)
|
||||
* 请求方法(GET、POST等)
|
||||
* 请求URI(资源路径)
|
||||
* HTTP协议版本
|
||||
* 请求头(Headers)
|
||||
|
||||
|头部字段|描述|
|
||||
|--------|------|
|
||||
|Host|指定服务器域名|
|
||||
|User-Agent|客户端信息|
|
||||
|Accept|可接受的响应内容类型|
|
||||
|Accept-Language|可接受的语言|
|
||||
|Accept-Encoding|可接受的编码方式|
|
||||
|Connection|控制连接(keep-alive/close)|
|
||||
|Content-Type|请求体的MIME类型|
|
||||
|Content-Length|请求体的长度|
|
||||
|Authorization|认证信息|
|
||||
|Cookie|客户端Cookie|
|
||||
* 请求体(Body)
|
||||
* `application/x-www-form-urlencoded`:表单数据
|
||||
* `multipart/form-data`:文件上传
|
||||
* `application/json`:JSON数据
|
||||
* `text/xml`:XML数据
|
||||
* HTTP版本差异
|
||||
* HTTP/1.0 每次请求建立新连接
|
||||
* HTTP/1.1 默认持久连接,支持管道化
|
||||
* HTTP/2 二进制协议,多路复用,头部压缩
|
||||
* HTTP/3 基于QUIC协议,改进传输效率
|
||||
|
||||
### 三、使用JAVA创建一个WEB服务器
|
||||
1. ServerSocket创建服务器(指定端口)
|
||||
2. 等待客户端连接
|
||||
3. 处理请求(解析HTTP协议)
|
||||
4. 响应请求(构建返回内容)
|
Loading…
x
Reference in New Issue
Block a user