由 Java Socket API 实现的简单 HTTP Client 与 Http Server
开发日志: DevLog.md
测试用数据集: Data.zip
本项目基于 JDK 17.0 编写,由 Maven 构造。
Scope | GroupId | ArtifactId | Version |
---|---|---|---|
Provided | org.projectlombok |
lombok |
1.18.22 |
Compile | org.json |
json |
20220320 |
Test | org.junit.jupiter |
junit-jupiter-engine |
5.8.2 |
- HttpClient:
mvn package -Pclient
- HttpServer:
mvn package -Pserver
最终产品为 jar 文件,通过 java -jar [NAME].jar
运行。
Data 及其子目录会在 HttpClient 或 HttpServer 启动时被自动创建于 jar 文件所在目录,结构如下:
Data
├── Client
│ └── Cache // Client的缓存目录
└── Server
├── Cache // Server的缓存目录(未使用)
└── Static // Server的静态文件存放目录
java -jar /path/to/HttpServer.jar [OPTIONS]...
SYNOPSIS
~ [-a <ADDRESS>] [-p <PORT>] [--keep-alive]
[-t <TIMEOUT>] [--help]
OPTIONS
--help Show this help information.
-a <ADDRESS> Bind the server to the specified IPv4 address.
The default value is 127.0.0.1
-p <PORT> Bind the server to the specified port number.
The default value is 8080
--keep-alive Enable keep-alive.
-t <TIMEOUT> Socket timeout.
The default value is 10000
若运行正常,则会显示启动信息,包含:
- Server 运行日志
- 预设 URL Mapping 信息
- 静态文件读取目录
如下所示:
SERVER: Preset mappings:
/register, methods: [POST]
/status, methods: [GET]
/login, methods: [GET]
/logout, methods: [GET]
/test, methods: [GET]
/missing, methods: [GET,POST]
/moved, methods: [GET]
SERVER: Server bound to /127.0.0.1:8080
SERVER: Reading static files from: [file:///.../Data/Server/Static]
SERVER: The server is now running
java -jar /path/to/HttpClient.jar <URL> [OPTIONS]...
SYNOPSIS
~ <URL>
[-m <METHOD>] [--keep-alive] [-b <text>]
[-h <headers>...] [--help]
URL
Using the generic URI syntax of:
http://<HOSTNAME>[:PORT][/PATH][?QUERY]
e.g.: http://jyywiki.cn/OS/2020/, http://127.0.0.1:8080/help/, http://www.google.com/search?q=uri
The default value of the port number is 80.
Only support HTTP protocol (not HTTPS).
OPTIONS
--help Show this help information.
-m <METHOD> Send with the specified web method.
Only supports GET and POST.
The default value is GET.
--keep-alive Enable keep-alive.
-b <text> Plain text body.
-h <header>... Send with the specified headers.
Syntax: <key>:<value>
e.g.: User-Agent:AbaAba/0.1
若执行正常,则会显示
- Client 执行日志
- HTTP Request Message 原信息
- HTTP Response Message 原信息
其中原信息包含:
- Startline
- Headers
- Body
- 若为
text/plain
则直接显示 - 否则存储在缓存目录中,显示文件路径
- 若为
如下所示:
/* 直接显示 Body 内容 */
CLIENT: Client has connect to the host
SOCKET[localhost127.0.0.1:8080]: Message sent 0.097656 KB
>> ==================== HTTP Request Message ==================== <<
>> GET /test HTTP/1.1
>> Accept: */*
>> User-Agent: Wget/1.21.3
>> Host: localhost
>> Accept-Encoding: gzip
>>
CLIENT: Status code received: 200
CLIENT: Handle returned directly...
CLIENT: Request complete
<< ==================== HTTP Response Message ==================== >>
<< HTTP/1.1 200 OK
<< date: Tue, 24 May 2022 03:54:16 GMT
<< server: nju_ic
<< content-encoding: gzip
<< connection: keep-alive
<< content-type: text/plain; charset=UTF-8
<<
<< You got the place!!!
/* 显示 Body 存储路径 */
>> ==================== HTTP Request Message ==================== <<
>> GET /OS/2022/ HTTP/1.1
>> Accept: */*
>> User-Agent: Wget/1.21.3
>> If-Modified-Since: Sun, 22 May 2022 11:13:51 GMT
>> Host: jyywiki.cn
>> Accept-Encoding: gzip
>>
CLIENT: Status code received: 200
CLIENT: Handle returned directly...
CLIENT: Request complete
<< ==================== HTTP Response Message ==================== >>
<< HTTP/1.1 200 OK
<< date: Tue, 24 May 2022 03:56:37 GMT
<< server: nginx/1.18.0 (Ubuntu)
<< content-encoding: gzip
<< content-type: text/html; charset=utf-8
<< connection: keep-alive
<< Content-Length: 16876
<<
<< Body saved at:
<< file:///.../Data/Client/Cache/jyywiki.cn/OS/2022/index/cache.html
HttpServer 与 HttpClient 均采用 Java NIO.2 模型进行开发,使用了 java.nio.channels.AsynchronousServerSocketChannel
、java.nio.channels.AsynchronousSocketChannel
等类。
相关测试: LoginSystemTests
- 200 OK
- 301 Moved Permanently
- 302 Found
- 304 Not Modified
- 404 Not Found
- 405 Method Not Allowed
- 500 Internal Server Error
- 505 HTTP Version Not Supported
相关测试:KeepAliveTest
已测试过的全部类型: mime.json
理论支持任意文件传输
- HTML:
text/html
- png:
image/png
- mp4:
video/mp4
- svg:
image/svg+xml
相关测试: LoginSystemTests
- 用户注册
- 检查登录状态
- 登出
- 检查登录状态
- 登录
相关测试:WANTest
相关测试:RequestTest
- 301 Moved
- 302 Found
- 304 Not Modified
- 仅支持 HTTP/1.1
- 收/发支持的 Content-Encoding: gzip
- 收/发支持的 Transfer-Encoding: default(Content-Length), chunked
- HttpServer 可以自动根据负载大小调整选用的 Content-Encoding 与 Transfer-Encoding
- HttpServer 可以用类似 SpringBoot 的方式编写 URL Mapping
main
├── java
│ └── edu.nju.http
│ ├── client /* Client Package */
│ │ ├── ClientDriver.java // main 函数入口
│ │ ├── HttpClient.java // 程序主轴
│ │ └── StatusHandler.java // Status code 处理类
│ ├── exception /* Exception Package */
│ │ ├── InvalidCommandException.java
│ │ │ // 用于 CLI,提示指令不合法
│ │ └── InvalidMessageException.java
│ │ // 用于 Message Parser,提示解析错误
│ ├── message /* Message Package */
│ │ ├── HttpMessage.java // 抽象类
│ │ ├── HttpRequestMessage.java // 继承 HttpMessage
│ │ ├── HttpResponseMessage.java // 继承 HttpMessage
│ │ ├── MessageHelper.java // 工具类
│ │ ├── ResponseMessageFactory.java
│ │ │ // Http Response Message 制造工厂
│ │ ├── consts /* Constant Package */
│ │ │ ├── Headers.java // Header 常量类
│ │ │ └── WebMethods.java // Method 常量类
│ │ ├── packer /* Packer Package */
│ │ │ ├── MessagePacker.java
│ │ │ │ // 包装并发送 Http Message
│ │ │ └── encode
│ │ │ │ /* Content-Encoding */
│ │ │ │ /* & Transfer-Encoding 策略类 */
│ │ │ ├── ContentGzipEncodeStrategy.java
│ │ │ ├── EncodeStrategy.java
│ │ │ ├── SourceEncodeStrategy.java
│ │ │ ├── TransChunkedEncodeStrategy.java
│ │ │ └── TransContentLengthEncodeStrategy.java
│ │ └── parser /* Parser Package */
│ │ ├── CustomizedReader.java // 自制字节流读取器
│ │ ├── MessageParser.java // 解析并接收 Http Message
│ │ ├── contentdecode
│ │ │ │ /* Content-Encoding 策略类 */
│ │ │ ├── ContentDecodeStrategy.java
│ │ │ └── ContentGzipDecodeStrategy.java
│ │ └── transdecode
│ │ │ /* Transfer-Encoding 策略类 */
│ │ ├── TransChunkedDecodeStrategy.java
│ │ ├── TransContentLengthDecodeStrategy.java
│ │ └── TransDecodeStrategy.java
│ ├── server /* Server Package */
│ │ ├── HttpServer.java // 程序主循环
│ │ ├── ServerDriver.java // main 函数入口
│ │ ├── TargetHandler.java // Target 处理类
│ │ └── target /* Target Package */
│ │ │ /* 包含各 Mapping 方法 */
│ │ ├──TargetSet.java
│ │ │ // 抽象类,其他类应继承此类
│ │ ├── Common.java
│ │ ├── Html.java
│ │ ├── LoginSystem.java
│ │ └── Mapping.java
│ └── util /* Utility Package */
│ ├── ArgIterator.java // 参数遍历器,用于 CLI
│ ├── Config.java // 包含各参数信息
│ └── Log.java // 调试方法类
└── resources
├── default_status_text.json // 默认 Status Code 对应文字
├── global_headers.json // 默认 Server Headers
├── mime.json // Suffix -> Content-Type 映射
└── target_path.json // Target 类注册表
//( 用于在 Target Handler 初始化时用
// Reflection 自动装配 )