前言
上一个版本中已经测试完成了读取客户端发送过来一行字符串的操作,因此,本版本开始完成解析请求。
由于一个请求包含信息比较多,因此我们设计一个类:HttpRequest并用该类的每一个实例表示客户端
发送过来的一个Http请求内容。
实现:
一、com.webserver.http
新建一个包com.webserver.http
注意:将来有关http协议的类都放在这个包里
二、HttpRequest
在http包中新建一个类:HttpRequest 即:请求对象 这个类的实例用于表示一个具体的Http(如上图)
三、解析请求
在HttpRequest中定义构造方法,用来在实例化请求对象的同时完成请求的解析工作。代码如下(纯手打):
public class HttpRequest {
private Socket socket;
private InputStream in;
//请求行相关信息
private String method; //请求方式
private String uri; //抽象路径
private String protocol; //协议版本
//消息头相关信息
Map<String, String> headers = new HashMap<>();
//消息正文相关信息
public HttpRequest(Socket socket) {
try {
this.socket = socket;
this.in = socket.getInputStream();
//顺序进行解析
//1.解析请求行
parseRequestLine();
//2.解析消息头
parseHeaders();
//3.解析消息正文
parseContent();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 解析请求行
*/
private void parseRequestLine() {
System.out.println("HttpRequest:开始解析请求行...");
try {
String line = readLine();
System.out.println("请求行:"+line);
/*
将请求行按照"空格"拆分成三块
分别赋给method,uri,protocol三个属性
*/
String[] arr = line.split("\\s");
method = arr[0];
/*
uri = arr[1]
这里后期会不定期出现下标越界异常,
空请求导致的,后面会解决
*/
uri = arr[1];
protocol = arr[2];
System.out.println("method:"+method);
System.out.println("uri:"+uri);
System.out.println("protocol:"+protocol);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("HttpRequest:请求行解析完毕!");
}
/**
* 解析消息头
*/
private void parseHeaders() {
System.out.println("Headers:开始解析消息头...");
try {
while (true) {
String line = readLine();
//是否单独读取到了CRLF,是则会读取到空字符串
if (line.isEmpty()) {
break; //如果单独读取CRLF就停止读取
}
System.out.println("消息头:"+line);
String[] data = line.split(":\\s");
headers.put(data[0],data[1]);
}
System.out.println("headers:"+headers);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Headers:消息头解析完毕!");
}
/**
* 解析消息正文
*/
private void parseContent() {
System.out.println("Content:开始解析消息正文...");
System.out.println("Content:消息正文解析完毕!");
}
/**
* 读取一行字符串,一行字符串结尾的标志是CRLF
* 返回的字符串中不含有最后的CRLF。
* @return
*/
private String readLine() throws IOException {
int d; //用来接收单次读取到的字符
//pre用来表示上一次读取到的字符,cur表示本次读取到的字符
char pre = 'a',cur = 'a';
//保存读取到的所有字符
StringBuilder builder = new StringBuilder();
while ((d = in.read()) != -1) {
cur = (char)d; //本次读取到的字符
//如果上次读取的是回车符,本次读取的是换行符就停止
if(pre == 13 && cur == 10) {
break;
}
//将本次读取到的字符拼到builder中
builder.append(cur);
//将本次读取到的字符赋给pre并进行下一次读取
pre = cur;
}
//将拼好的builder转换为字符串并输出
return builder.toString().trim();
}
public Socket getSocket() {
return socket;
}
public String getUri() {
return uri;
}
public String getProtocol() {
return protocol;
}
/**
* 获取消息头信息
*
* @param name 给定的消息头的名字
* @return 对应该消息头的值
*/
public String getHeader(String name) {
return headers.get(name);
}
}
在ClientHandler第一步解析请求的环节中通过实例化请求对象完成解析工作。(如下图)
此版本到此结束,总结一下,就是把上个版本ClientHandler的run方法中的读取字符串代码移入HttpRequest,在HttpRequest中就称为了解析请求行和消息头的关键。
下个版本将测试一下给浏览器响应一个页面回去。