浏览器发起HTTP协议请求,服务端通过ServerSocket接收并交给一个Socket对象(管道),然后解析浏览器的请求,并通过Socket管道向浏览器响应。
代码如下:
package com.gjw;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("server is running...");
while (true) {
Socket socket = serverSocket.accept();
System.out.println("connected form" + socket.getRemoteSocketAddress());
new Handler(socket).start();
}
}
}
class Handler extends Thread {
private Socket socket;
public Handler(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try (
// 字节输入流:读入客户端发起的GET请求 字节输出流:写出服务端提供的服务
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream()
)
{
handle(is, os);
} catch (Exception e) {
try {
socket.close();
} catch (IOException ex) {
System.out.println("client disconnected");
}
}
}
private void handle(InputStream is, OutputStream os) throws Exception {
// reader:读取客户端发来的GET请求
BufferedReader reader = new BufferedReader(new InputStreamReader(is,StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os,StandardCharsets.UTF_8));
boolean requestOk = false;
String first = reader.readLine();
if (first.startsWith("GET / HTTP/1.")) {
requestOk = true;
}
String header;
while (!(header = reader.readLine()).isEmpty()) { // 使用isEmpty判断,这样可以值判断到请求头就结束。因为请求头和请求体之间有空行
System.out.println(header);
}
/* String line;
while ((line = reader.readLine()) != null){
不能使用 != null,因为HTTP请求中,请求头和请求体中是一个空行,使用请求!= null判断会使得无法将请求头和请求体分开,导致一直读到
该http请求的末尾,这样的的话可能会导致该循环一直进行,因为客户端可能会不断发送HTTP请求,导致服务器无限等待输入特别是在客户端保持连接
(例如HTTP/1.1中的Connection: keep-alive头部时)。这样,服务器可能不会及时处理和发送响应,从而导致后面的代码无法运行。
System.out.println(line);
}*/
System.out.println(requestOk ? "Response OK" : "Response Error");
if (requestOk) {
InputStream input = Server.class.getClassLoader().getResourceAsStream("html/a.html");
BufferedReader br = new BufferedReader(new InputStreamReader(input));
StringBuffer sb = new StringBuffer();
String content;
while ((content = br.readLine()) != null) {
sb.append(content);
}
br.close();
int length = sb.toString().getBytes(StandardCharsets.UTF_8).length;
writer.write("HTTP/1.1 200 OK\r\n");
writer.write("Connection: keep-alive\r\n");
writer.write("Content-Type: text/html\r\n");
writer.write("Content-Length: " + length + "\r\n");
writer.write("\r\n"); // 空行标识Header和Body的分隔
writer.write(sb.toString());
writer.flush();
} else {
writer.write("HTTP/1.0 404 Not Found\r\n");
writer.write("Content-Length: 0\r\n");
writer.write("\r\n");
writer.flush();
}
}
}
响应的是一个表格数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
table {
margin: 0 auto;
text-align: center;
border: 1px solid black;
border-collapse: collapse;
width: 500px;
}
th,td {
border: 1px solid black;
}
</style>
</head>
<body>
<table>
<tr>
<th>序号</th>
<th>品牌名称</th>
<th>企业名称</th>
</tr>
<tr>
<td>010</td>
<td>三只松鼠</td>
<td>三只松鼠</td>
</tr>
<tr>
<td>009</td>
<td>优衣库</td>
<td>优衣库</td>
</tr>
<tr>
<td>008</td>
<td>小米</td>
<td>小米科技有限公司</td>
</tr>
</table>
</body>
</html>
可以看到这样的过程很繁琐,这还仅仅是针对一个demo程序,如果要手动编写程序去解析http协议,那就太麻烦了。但所有的web项目开发都需要解析http协议,但是http协议是标准的,是统一固定的,因此代码是通用的。所以很多已经将代码写好封装在app中供我们使用,而这个app就是web服务器,比如Tomcat。这样就不用去管解析http协议了,而只需要关注业务逻辑的开发了。