接上篇,代码已经能处理简单的请求,但走读一下代码,会发现这些代码非常粗糙,可改动的地方非常多,本篇先对Request类进行优化
原代码:
// 解析浏览器发起的请求
public void parseRequest() {
// 暂时忽略文件上传的请求,假设都字符型请求
byte[] buff = new byte[2048];
StringBuffer sb = new StringBuffer(2048);
int len = 0;
//请求内容
try {
len = in.read(buff);
sb.append(new String(buff, 0, len));
} catch (IOException e) {
e.printStackTrace();
}
System.out.print(sb.toString());
//解析请求行中uri信息
uri = this.parseUri(sb.toString());
}
问题1:上面代码是代码是上上篇代码接收客户端发起的请求信息,在极端情况下, 比如请求信息大小超过2kb,in.read(buff) 会读不全, 如果加上while循环读,会发现缓存的buff大小稍微设置不当,请求将被阻塞了,无法响应。
问题2:当前方法仅仅解析请求行中的url, 忽略对于请求行中,请求头中其他数据的解析。
分析:
针对问题1:解决这个问题, 可以采用下面的设计
思路:
1:先创建一个对象,里面设置3个char[]类型数据, 分别存储请求行中 请求方法, 请求uri,请求协议
2:再创一个类SocketInputStream对inputStream做增强。
1> SocketInputStream 继承inputStrean,然后重写其中的read方法
2> 定义一个char数组buff缓存数据,跟2个游标, count 表示一次读流读到的字节个数,pos表示操作缓存buf位置(index)
3:读socket流时,将数据缓存到buf中,记录pos=0, count为读取个数,循环解析,
1>当buf[pos]==‘ ’ 表示解析请求行中的请求方法结束, 使用requestLine.method 保存读取到的方法。
2>当buf[pos]==‘ ’ 表示解析请求行中的请求uri结束, 使用requestLine.uri保存读取到的方法。
2>当buf[pos]==‘\r’ || “\n” 表示解析请求行中的请求协议结束, 使用requestLine.protocal保存读取到的方法。
针对问题2:比较简单,只要问题1解决, 获取到uri 就可以解析uri ? 后面的数据就是查询参数
居于上面的思路,代码如下:
新建类:HttpRequestLine
/**
*
* 请求行中包括3种数据
* 请求方法 请求路径 请求协议
*/
public class HttpRequestLine {
//-----------------------------
//模拟tomcat所有请求数据以char[]类型存在
public char[] method;
public int methodEnd;
public char[] uri;
public int uriEnd;
public char[] protocol;
public int protocolEnd;
//默认长度
public static final int INITIAL_METHOD_SIZE = 8;
public static final int INITIAL_URI_SIZE = 64;
public static final int INITIAL_PROTOCOL_SIZE = 8;
//最长
public static final int MAX_METHOD_SIZE = 1024;
public static final int MAX_URI_SIZE = 32768;
public static final int MAX_PROTOCOL_SIZE = 1024;
//-----------------------------
public HttpRequestLine() {
this(new char[INITIAL_METHOD_SIZE], 0, new char[INITIAL_URI_SIZE], 0, new char[INITIAL_PROTOCOL_SIZE], 0);
}
public HttpRequestLine(char[] method, int methodEnd, char[] uri, int uriEnd, char[] protocol, int protocolEnd) {
this.method = method;
this.methodEnd = methodEnd;
this.uri = uri;
this.uriEnd = uriEnd;
this.protocol = protocol;
this.protocolEnd = protocolEnd;
}
//一次行请求,从头来过, 复位
public void recycle() {
methodEnd = 0;
uriEnd = 0;
protocolEnd = 0;
}
//uri搜索----------------------------------------------------
public int indexOf(String str) {
return indexOf(str.toCharArray(), str.length());
}
public int indexOf(char[] buf, int end) {
char firstChar = buf[0];
int pos = 0;
while (pos < uriEnd) {
pos = indexOf(firstChar, pos);
if (pos == -1)
return -1;
if ((uriEnd - pos) < end)
return -1;
for (int i = 0; i < end; i++) {
if (uri[i + pos] != buf[i])
break;
if (i == (end - 1))
return pos;
}
pos++;
}
return -1;
}
public int indexOf(char c, int start) {
for (int i = start; i < uriEnd; i++) {
if (uri[i] == c)
return i;
}
return -1;
}
//----------------------------------------------------
}
新建类:SocketInuptStream
/**
* 请求输入流处理: 解析Socket中的输入流, 获取请求行信息
*/
public class SocketInputStream extends InputStream {
// 读取缓存信息缓存
protected byte buf[];
// 一次read()读取个数
protected int count;
// 缓存数据数组索引
protected int pos;
protected InputStream is;
// 初始化同时制定缓存区大小
public SocketInputStream(InputStream is, int bufferSize) {
this.is = is;
buf = new byte[bufferSize];
}
// 读取请求数据,封装成功requestLine 对象
public void readRequestLine(HttpRequestLine requestLine) throws IOException {
// 请求方法不为空表示第二次请求,重来
if (requestLine.methodEnd != 0) {
requestLine.recycle();
}
// 读取Socket流数据,遇到回车表明请求行读取结束
int chr = 0;
do {
try {
chr = read();
} catch (Exception e) {
chr = -1;
}
} while (chr == '\r' || chr == '\n');
if (chr == -1) {
throw new RuntimeException("读请求数据出错");
}
pos--;
// 解析请求行方法-------------------------------------------
int maxRead = requestLine.method.length;
int readStart = pos;
int readCount = 0;
boolean space = false; // 结束标志
while (!space) {
// 如果方法数据无法接受数据,按2倍长度拓展
if (readCount >= maxRead) {
if ((2 * maxRead) <= HttpRequestLine.MAX_METHOD_SIZE) {
char[] newBuffer = new char[2 * maxRead];
System.arraycopy(requestLine.method, 0, newBuffer, 0, maxRead);
requestLine.method = newBuffer;
maxRead = requestLine.method.length;
} else {
throw new RuntimeException("读取数据过大");
}
}
// 当缓存数据中数据全部读取完后,重新再读
if (pos >= count) {
int val = read();
if (val == -1) {
throw new RuntimeException("读请求数据出错");
}
// 读取数据游标复位
pos = 0;
readStart = 0;
}
// 当缓存数据中读取到空格, 表示请求行中方法读取完毕
if (buf[pos] == ' ') {
space = true; // 结束循环
}
// 将读取数据保存到预设好的请求行对象的请求方法数组中
requestLine.method[readCount] = (char) buf[pos];
readCount++;
pos++;
}
// 读取请求方法结束后, 标记结束位置
requestLine.methodEnd = readCount - 1;
// 同样道理解析请求行uri-------------------------------------------
maxRead = requestLine.uri.length;
readStart = pos;
readCount = 0;
space = false;
while (!space) {
if (readCount >= maxRead) {
if ((2 * maxRead) <= HttpRequestLine.MAX_URI_SIZE) {
char[] newBuffer = new char[2 * maxRead];
System.arraycopy(requestLine.uri, 0, newBuffer, 0, maxRead);
requestLine.uri = newBuffer;
maxRead = requestLine.uri.length;
} else {
throw new RuntimeException("读取数据过大");
}
}
// 当缓存数据中数据全部读取完后,重新再读
if (pos >= count) {
int val = read();
if (val == -1) {
throw new RuntimeException("读请求数据出错");
}
// 读取数据游标复位
pos = 0;
readStart = 0;
}
// 当缓存数据中读取到空格, 表示请求行中方法读取完毕
if (buf[pos] == ' ') {
space = true; // 结束循环
}
requestLine.uri[readCount] = (char) buf[pos];
readCount++;
pos++;
}
requestLine.uriEnd = readCount - 1;
// 同样道理解析请求行协议-------------------------------------------
maxRead = requestLine.uri.length;
readStart = pos;
readCount = 0;
space = false;
while (!space) {
if (readCount >= maxRead) {
if ((2 * maxRead) <= HttpRequestLine.MAX_PROTOCOL_SIZE) {
char[] newBuffer = new char[2 * maxRead];
System.arraycopy(requestLine.protocol, 0, newBuffer, 0, maxRead);
requestLine.protocol = newBuffer;
maxRead = requestLine.protocol.length;
} else {
throw new RuntimeException("读取数据过大");
}
}
if (pos >= count) {
int val = read();
if (val == -1) {
throw new RuntimeException("读请求数据出错");
}
pos = 0;
readStart = 0;
}
if (buf[pos] == '\r') {
// Skip CR.
} else if (buf[pos] == '\n') {
space = true;
} else {
requestLine.protocol[readCount] = (char) buf[pos];
readCount++;
}
pos++;
}
requestLine.protocolEnd = readCount;
}
// 重写inpustream的read方法
public int read() throws IOException {
// 上一次读取位置
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return buf[pos++] & 0xff;
}
// 一次读取多少数据
protected void fill() throws IOException {
pos = 0;
count = 0;
int nRead = is.read(buf, 0, buf.length);
if (nRead > 0) {
count = nRead;
}
}
}
改动:Request
原先实现ServletRequest 改为实现HttpServletRequest, 满足http请求处理
/**
* 请求信息封装对象
*/
public class Request implements HttpServletRequest{
private String method;
private String protocol;
//请求行信息信息中的uri
private String requestURI;
// 浏览器socket连接的读流
private InputStream in;
//URL上面的参数
private String queryString;
public Request(InputStream in) {
this.in = in;
}
/**省略一堆目前暂时没用到的ServletRequest需要实现的方法*/
}
改动:HttpServer
添加了解析请求行的操作
/**
* 模拟tomcat的核心类
*/
public class HttpServer {
// 模拟tomcat关闭命令
private static final String SHUTDOWN_CMD = "/SHUTDOWN";
private boolean shutdown = false;
private HttpRequestLine requestLine = new HttpRequestLine();
private Request request;
//持续监听端口
@SuppressWarnings("resource")
public void accept() {
ServerSocket serverSocket = null;
try {
// 启动socket服务, 监听8080端口,
serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("启动myTomcat服务器失败:" + e.getMessage(), e);
}
// 没接收到关闭命令前一直监听
while (!shutdown) {
Socket socket = null;
InputStream in = null;
OutputStream out = null;
SocketInputStream input = null;
try {
// 接收请求
socket = serverSocket.accept();
in = socket.getInputStream();
out = socket.getOutputStream();
// 将浏览器发送的请求信息封装成请求对象
input = new SocketInputStream(in, 2048);
request = new Request(input);
parseRequest(input);
// 将相应信息封装相应对象
Response response = new Response(out);
//实现约定:servlet请求路径必须以/servlet开头,以servlet简单类名结束
if(request.getRequestURI().startsWith("/servlet")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
}else {
//此处简单响应一个静态资源文件
StaticSourceProcessor processor = new StaticSourceProcessor();
processor.process(request, response);
}
socket.close();
//如果是使用关闭命令,停止监听退出
shutdown = request.getRequestURI().equals(SHUTDOWN_CMD);
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
private void parseRequest(SocketInputStream input) throws IOException {
//读取请求数据,封装成reauestLine对象
input.readRequestLine(requestLine);
String method =
new String(requestLine.method, 0, requestLine.methodEnd);
String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
if (method.length() < 1 || requestLine.uriEnd < 1) {
throw new RuntimeException("解析请求出错");
}
request.setMethod(method);
request.setProtocol(protocol);
//对uri进行拆分,分uri + 查询参数
String uri = null;
int question = requestLine.indexOf("?");
if (question >= 0) {
request.setQueryString(new String(requestLine.uri, question + 1,
requestLine.uriEnd - question - 1));
uri = new String(requestLine.uri, 0, question);
}
else {
request.setQueryString(null);
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}
request.setRequestURI(uri);
System.out.println("method:" + request.getMethod());
System.out.println("protocol:" + request.getProtocol());
System.out.println("uri:" + request.getRequestURI());
System.out.println("queryString:" + request.getQueryString());
}
public static void main(String[] args) {
new HttpServer().accept();
}
}
到这, 本篇就结束了。