目录
[1. 解析请求信息](#1. 解析请求信息)
[创建解析请求类 HttpRequest](#创建解析请求类 HttpRequest)
[2. 创建静态资源目录 webs](#2. 创建静态资源目录 webs)
[3. 封装响应信息](#3. 封装响应信息)
[创建静态资源处理器 StaticResourceHandler](#创建静态资源处理器 StaticResourceHandler)
[创建响应类 HttpResponse](#创建响应类 HttpResponse)
[1. 绘制 请求解析类 HttpRequest 和响应类 HttpResponse 的封装流程图](#1. 绘制 请求解析类 HttpRequest 和响应类 HttpResponse 的封装流程图)
[2. 优化客户端的连接](#2. 优化客户端的连接)
响应静态资源
在昨天的基础上,再进一步优化,能够响应HTML文件,图片等静态资源
在响应静态资源之前,我们要先确定客户端请求的是哪个静态资源,是HTML页面还是图片呢?
我们先打印一下客户端的请求信息
HTTP协议请求格式
可以看到,第一行也就是 请求行 ,它有请求方法,请求URL和请求协议
请求URL就是需要请求的资源
1. 解析请求信息
那如何拿到 请求的URL呢?然后根据请求URL响应对应的资源
我们看一下 HTTP协议的请求格式,不难发现,都是以回车符和换行符结尾
以 回车符和换行符进行分割,然后再根据 空格 分割就可以拿到请求URL了
创建解析请求类 HttpRequest
java
package com.shao.net;
import java.util.HashMap;
public class HttpRequest {
/**
* 请求信息
*/
private String msg;
/**
* 请求行
*/
private String requestLine;
/**
* 请求行的请求方法
*/
private String requestMethod;
/**
* 请求行的请求URI,请求路径
*/
private String requestURI;
/**
* 请求行的请求模块,例如:/index.html?a=1 请求模块为:/index.html
*/
private String requestModule;
/**
* 请求行的请求协议
*/
private String requestProtocol;
/**
* 存储请求头参数
*/
private HashMap<String, String> requestHeaderParams = new HashMap<>();
/**
* 存储请求体参数
*/
private HashMap<String, String> requestBodyParams = new HashMap<>();
/**
* 构造函数
*/
public HttpRequest(String msg) {
this.msg = msg;
// 根据HTTP协议格式 分割请求信息
String[] requestArr = msg.split("\r\n");
// 把数组中的第一个元素赋值给请求行
requestLine = requestArr[0];
// 1. 解析请求行
parseRequestLine();
// 2. 解析请求头
parseRequestHeader(requestArr);
// 3. 解析请求体
parseRequestBody();
}
// 1. 解析请求行
private void parseRequestLine() {
// 把请求行按空格分割
String[] requestParts = requestLine.split(" ");
// 请求方法
requestMethod = requestParts[0];
// 请求资源路径
requestURI = requestParts[1];
// 请求协议
requestProtocol = requestParts[2];
// 如果请求方法是GET,则根据 ? 号分割,获取请求模块,例如:/index.html?a=1 请求模块为:/index.html
String[] split = requestURI.split("\\?");
requestModule = split[0];
System.out.println("请求方法:" + requestMethod);
System.out.println("请求uri:" + requestURI);
System.out.println("请求协议:" + requestProtocol);
System.out.println("请求模块:" + requestModule);
}
// 2. 解析请求头
private void parseRequestHeader(String[] requestArr) {
// 检查请求数组,判断是否为空,判断有没有请求头
if (requestArr == null || requestArr.length <= 1) {
return;
}
// 遍历请求数组,从第二个元素开始,因为第一个元素是请求行
for (int i = 1; i < requestArr.length; i++) {
// 判断是否为空,如果为空,表示请求头结束,因为 HTTP 协议中,请求头和请求体之间有2个回车符 换行符
String headerLine = requestArr[i];
if (headerLine.length() == 0) {
break;
}
// 把请求头按 : 分割,获取请求头参数
// 注意:这里需要使用 ": ",而不是 ":"
String[] headerParts = headerLine.split(": ");
// 判断请求头的格式是否正确
if (headerParts.length >= 2) {
requestHeaderParams.put(headerParts[0], headerParts[1]);
}
}
System.out.println("请求头参数:" + requestHeaderParams);
}
// 3. 解析请求体
private void parseRequestBody() {
// POST 方法的请求参数是在请求体,GET 方法的请求参数在请求行
if (this.requestMethod.equalsIgnoreCase("POST")) {
// 分割请求信息
String[] split = msg.split("\r\n\r\n");
// 如果分割后的长度 >= 2,表示有请求体
if (split.length >= 2) {
// 分割请求体
splitRequestBody(split[1]);
}
} else if (this.requestMethod.equalsIgnoreCase("GET")) {
// 把请求行按空格分割,获取请求参数
String[] requestLineParts = this.requestLine.split(" ");
String[] split = requestLineParts[1].split("\\?");
if (split.length >= 2) {
// 分割请求体
splitRequestBody(split[1]);
}
}
System.out.println("请求体参数:" + requestBodyParams);
}
private void splitRequestBody(String requestBody) {
// 分割请求参数,例如:a=1&b=2&c=3
String[] requestBodyParts = requestBody.split("&");
// 遍历请求体
for (int i = 0; i < requestBodyParts.length; i++) {
String part = requestBodyParts[i];
// 把请求参数按 = 分割,获取键值对,最多分割2部分,如果有多个 = ,只保留第一个 = 之前和之后的部分
String[] keyValue = part.split("=", 2);
if (keyValue.length == 2) {
requestBodyParams.put(keyValue[0], keyValue[1]);
} else {
System.out.println("警告:非法格式的请求体:" + part);
}
}
}
/**
* 获取
* @return msg
*/
public String getMsg() {
return msg;
}
/**
* 设置
* @param msg
*/
public void setMsg(String msg) {
this.msg = msg;
}
/**
* 获取
* @return requestLine
*/
public String getRequestLine() {
return requestLine;
}
/**
* 设置
* @param requestLine
*/
public void setRequestLine(String requestLine) {
this.requestLine = requestLine;
}
/**
* 获取
* @return requestMethod
*/
public String getRequestMethod() {
return requestMethod;
}
/**
* 设置
* @param requestMethod
*/
public void setRequestMethod(String requestMethod) {
this.requestMethod = requestMethod;
}
/**
* 获取
* @return requestURI
*/
public String getRequestURI() {
return requestURI;
}
/**
* 设置
* @param requestURI
*/
public void setRequestURI(String requestURI) {
this.requestURI = requestURI;
}
/**
* 获取
* @return requestModule
*/
public String getRequestModule() {
return requestModule;
}
/**
* 设置
* @param requestModule
*/
public void setRequestModule(String requestModule) {
this.requestModule = requestModule;
}
/**
* 获取
* @return requestProtocol
*/
public String getRequestProtocol() {
return requestProtocol;
}
/**
* 设置
* @param requestProtocol
*/
public void setRequestProtocol(String requestProtocol) {
this.requestProtocol = requestProtocol;
}
/**
* 获取
* @return requestHeaderParams
*/
public HashMap<String, String> getRequestHeaderParams() {
return requestHeaderParams;
}
/**
* 设置
* @param requestHeaderParams
*/
public void setRequestHeaderParams(HashMap<String, String> requestHeaderParams) {
this.requestHeaderParams = requestHeaderParams;
}
/**
* 获取
* @return requestBodyParams
*/
public HashMap<String, String> getRequestBodyParams() {
return requestBodyParams;
}
/**
* 设置
* @param requestBodyParams
*/
public void setRequestBodyParams(HashMap<String, String> requestBodyParams) {
this.requestBodyParams = requestBodyParams;
}
public String toString() {
return "HttpRequest{msg = " + msg + ", requestLine = " + requestLine + ", requestMethod = " + requestMethod + ", requestURI = " + requestURI + ", requestModule = " + requestModule + ", requestProtocol = " + requestProtocol + ", requestHeaderParams = " + requestHeaderParams + ", requestBodyParams = " + requestBodyParams + "}";
}
}
调用 HttpRequest 解析请求信息
游览器发送请求
打印解析后的请求信息,可以看到请求的静态资源是 /index.html
拿到了请求uri,我们就可以根据 请求uri 响应静态资源了
那这个静态资源放在哪儿呢?
2. 创建静态资源目录 webs
创建一个 HTML 文件,内容先写英文,写中文需要在响应头加上 UTF-8 字符编码
那现在 index.html 有了,怎么响应给客户端呢?
读取 index.html 文件,将读取到的内容发送到游览器,游览器会自动渲染页面
3. 封装响应信息
把响应信息的代码拿出来,单独放到一个类里,这个类就只需要响应数据
这样可以降低代码的耦合度
在创建响应类之前有两个问题
**1. 怎么确定请求的是静态资源?**动态资源一般需要操作数据库
2. 响应的媒体类型怎么写?
如果响应的是图片,那么就需要修改为 Content-Type:image/jpeg 或 image/png 等
所以,就需要动态的判断响应的静态资源是什么媒体类型。
创建静态资源处理器 StaticResourceHandler
java
package com.shao.net;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class StaticResourceHandler {
// 常见静态资源扩展名
private static final String[] staticExtensions = new String[]{".html", ".css", ".js", ".jpg", ".png", ".gif", ".ico", ".svg", ".pdf", ".txt"};
/**
* 判断是否静态资源
* 如果是静态资源则返回 true ,否则返回 false
*/
public static boolean isLikelyStaticResource(String fileName) {
// 检查文件的扩展名是否存在于静态资源扩展名数组中
for (String ext : staticExtensions) {
if (fileName.endsWith(ext)) {
return true;
}
}
return false;
}
/**
* 根据文件路径获取文件内容
*/
public static byte[] getFileContents(String filePath) {
// 1. 获取文件对象
File file = new File(filePath);
// 2. 判断文件是否存在
if (!file.exists()) {
return null;
}
// 3. 获取文件内容
// 定义一个字节数组,数组大小根据文件大小确定
byte[] fileContents = new byte[0];
try {
FileInputStream fis = new FileInputStream(file);
// fis.available() 是读取的文件的字节数
fileContents = new byte[fis.available()];
// 读取文件存放到 fileContents 数组中
fis.read(fileContents);
// 关闭文件输入流
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
return fileContents;
}
/**
* 获取文件的媒体类型
*/
public static String getFileMimeType(String filePath) {
// 1 获取文件对象
File file = new File(filePath);
// 2 判断文件是否存在
if (!file.exists()) {
return "text/html";
}
// 3 获取文件的媒体类型
String fileType = null;
try {
fileType = Files.probeContentType(Paths.get(filePath));
} catch (IOException e) {
e.printStackTrace();
}
return fileType;
}
}
创建响应类 HttpResponse
java
package com.shao.net;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
public class HttpResponse {
/**
* 输出流
*/
private OutputStream os;
/**
* 解析请求信息的对象
*/
private HttpRequest httpRequest;
public HttpResponse(OutputStream os, HttpRequest httpRequest) {
this.os = os;
this.httpRequest = httpRequest;
}
public void response(String filePath) {
//判断请求的是否为静态文件
if (StaticResourceHandler.isLikelyStaticResource(httpRequest.getRequestModule())) {
// 获取静态资源一般是 GET 请求方法
if (httpRequest.getRequestMethod().equals("GET")) {
// 响应静态资源
responseStaticResource(filePath);
}
} else {
// 处理动态请求
System.out.println("请求动态资源");
}
}
/**
* 响应静态资源
*/
private void responseStaticResource(String filePath) {
// 读取文件
byte[] fileContents = StaticResourceHandler.getFileContents(filePath);
// 判断文件是否存在,不存在则返回 404 的页面
if (fileContents == null) {
try {
FileInputStream fis = new FileInputStream("webs/pages/not_Found404.html");
fileContents = new byte[fis.available()];
fis.read(fileContents);
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 响应协议
String protocol = httpRequest.getRequestProtocol();
// 文件媒体类型
String fileMimeType = StaticResourceHandler.getFileMimeType(filePath);
try {
os.write((protocol + " 200 OK\r\n").getBytes());
os.write(("Content-Type: " + fileMimeType + "\r\n").getBytes());
os.write(("Content-Length: " + fileContents.length + "\r\n").getBytes());
os.write("\r\n".getBytes());
os.write(fileContents);
os.flush();
System.out.println("响应成功");
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后就可以调用响应类了
测试
静态资源的路径说明
可以直接复制文件的相对路径
作业
1. 绘制 请求解析类 HttpRequest 和响应类 HttpResponse 的封装流程图
2. 优化客户端的连接
现在只有主线程处理客户端连接,如果同一时刻有多个客户端发起连接,同一时间只能处理一个客户端的连接,这样效率不高,怎么处理?
解决方案是来一个客户端连接就创建一条线程去处理