1 阻塞式
- 代码结构
2. 代码实现
package study.wyy.net.http;
import lombok.extern.slf4j.Slf4j;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author wyaoyao
* @date 2021/5/31 9:43
* 一个简单的http服务器,监听8080端口
*/
@Slf4j
public class SimpleHttpServer {
private final int port = 8080;
private final ServerSocketChannel serverSocketChannel;
private final ExecutorService executorService;
private final int POOL_MULTIPLE = 4;
public SimpleHttpServer() throws IOException {
this.serverSocketChannel = ServerSocketChannel.open();
// 绑定本地端口
this.serverSocketChannel.socket().bind(new InetSocketAddress(this.port));
// 使得在同一个主机上关闭了服务器,紧接着再启动服务器程序时,可以顺利绑定相同的端口
this.serverSocketChannel.socket().setReuseAddress(true);
this.executorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * POOL_MULTIPLE);
log.info("simple http server is started the port is {}", serverSocketChannel.socket().getLocalPort());
}
public void services() {
while (true) {
// 客户端的连接
SocketChannel socketChannel = null;
try {
// 等待客户端连接
socketChannel = serverSocketChannel.accept();
executorService.submit(new RequestHandler(socketChannel));
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 内部类,负责处理Http请求,实现Runnable接口
* 每个http请求都将开辟一个线程去处理
*/
@Slf4j
private static class RequestHandler implements Runnable {
private final SocketChannel socketChannel;
private final Charset charset = Charset.forName("utf-8");
private RequestHandler(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
@Override
public void run() {
handleRequest();
}
private void handleRequest() {
try {
// 返回与当前SocketChannel关联的Socket对象,每个SocketChannel都与一个Socket对象关联
Socket socket = this.socketChannel.socket();
log.info("accept client connection from [{}:{}]", socket.getInetAddress().getHostName() + socket.getPort());
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取http请求,假定其长度不超过1024字节
int read = this.socketChannel.read(buffer);
buffer.flip();
// 解码请求的内容
String request = decode(buffer);
// 打印请求内容
log.info("this request content is [{}]", request);
// 生成http结果
StringBuffer response = new StringBuffer("HTTP/1.1 200 OK \r\n");
// 最后一个请求头的时候要多家一个换行
// 请求头和请求正文之间必须有空行分割
response.append("Content-Type:text/html; charset=UTF-8\r\n\r\n");
// 发送http响应的第一行和响应头
this.socketChannel.write(encode(response.toString()));
FileInputStream fileInputStream;
// 获取第一行请求
String firstLineRequest = request.substring(0, request.indexOf("\r\n"));
// 首页
URL resource = this.getClass().getClassLoader().getResource("index.html");
fileInputStream = new FileInputStream(resource.getFile());
if(firstLineRequest.indexOf("login.html") != -1){
// 登录页面
resource = this.getClass().getClassLoader().getResource("login.html");
fileInputStream = new FileInputStream(resource.getFile());
}
if(firstLineRequest.indexOf("/userLogin") != -1){
// 登录请求
resource = this.getClass().getClassLoader().getResource("success.html");
fileInputStream = new FileInputStream(resource.getFile());
}
FileChannel channel = fileInputStream.getChannel();
// 发送响应正文
channel.transferTo(0,channel.size(),this.socketChannel);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (this.socketChannel != null) {
this.socketChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String decode(ByteBuffer buffer) {
CharBuffer decode = charset.decode(buffer);
return decode.toString();
}
private ByteBuffer encode(String content) {
ByteBuffer encode = charset.encode(content);
return encode;
}
}
}
核心来说就是按照http协议来解析请求,返回请求就好了。
关于http格式就不多介绍。。。。
浏览器访问http://localhost:8080/
,就会返回首页(index.html)
这里提供一下login.html的代码
<html>
<body>
<form method="post" action="/userLogin">
<label>用户名:</label><input name="username" type="text">
<br>
<label>密 码:</label><input name="password" type="password">
<br>
<input type="submit" value="登录">
</form>
</body>
</html>
现在访问http://localhost:8080/login.html
输入用户名密码登录(这里随便输入就好),观察后台输出的请求:
POST /userLogin HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 28
Cache-Control: max-age=0
sec-ch-ua: " Not;A Brand";v="99", "Microsoft Edge";v="91", "Chromium";v="91"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36 Edg/91.0.864.37
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:8080/login.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: Idea-9ea971b1=c85e1a84-f8b7-4885-9555-497f2ed3a5e7; m=2258:Z3Vlc3Q6Z3Vlc3Q%253D
username=name&password=13123 # 请求参数(这里是表单请求,Content-Type: application/x-www-form-urlencoded)
2 非阻塞式
2.1 核心类介绍
- HttpServer是服务器的主程序,负责启动服务器
- AcceptHandler负责接受客户连接
- RequestHandler负责接收Http请求,并解析,然后生成响应
- Request表示Http请求
- Response负责Http响应
- Content表示Http响应正文