目录
前言
前面我们实现了多线程处理请求,接下来我们去实现Sevelet框架。但是我们不去实现那么多的接口,我们只写一种情况。
实现
创建Servlet框架
创建接口MyServlet
public interface MyServlet {
}
创建抽象类MyHttpServlet实现MyServlet接口
public abstract class MyHttpServlet implements MyServlet {
}
创建request与response的封装类
创建好Servlet框架后,还需要创建两个对象分别对响应和请求的封装,在TomCat中原类名叫做HttpServletRequest与HttpServletResponse。我们自己创建这两个类与其接口。
创建request的接口和实现类
public interface MyRequest {
}
public class MyHttpRequest implements MyRequest{
}
创建response的接口和实现类
public interface MyResponse {
}
public class MyHttpResponse implements MyResponse {
}
现在我们只是提供了必要的类,还没有具体的方法。接下来先对request的封装类添加一些必要的方法。
- getParameter():获取指定参数的值
- getMethod():获取请求方式
- getUri():获取Uri
public interface MyRequest {
String getParameter(String name);
String getMethod();
String getUri();
}
public class MyHttpRequest implements MyRequest{
//用来存储每个请求的信息
private String uri;
private String method;
private Map<String,String> parameters = new HashMap<>();
//通过输入流来获取信息
private InputStream inputStream;
public MyHttpRequest(InputStream inputStream) {
this.inputStream = inputStream;
//未定义,待会实现
init();
}
@Override
public String getParameter(String name) {
if (parameters.containsKey(name)){
return parameters.get(name);
}
throw new RuntimeException("没有该参数名为:"+name);
}
@Override
public String getMethod() {
return method;
}
@Override
public String getUri() {
return uri;
}
}
提供好方法后,需要通过输入流获取请求头信息并解析对成员属性赋值。这里我使用了init()方法。
private void init() {
byte[] bytes = new byte[1024];
int len;
StringBuilder stringBuilder = new StringBuilder();
//从输入流读取信息到bytes
try {
//循环读取到读取结束
while ((len = inputStream.read(bytes)) != -1) {
stringBuilder.append(new String(bytes, 0, len));
//我么使用的时Socket通信,如果不关闭socket通道的话,是无法返回-1的。这时read方法阻塞,只有关闭后才会返回-1
if (len != bytes.length) break;
}
String requestHead = URLDecoder.decode(stringBuilder.substring(0, stringBuilder.indexOf("\r\n")), "utf-8");
// GET /people?username=123456 HTTP/1.1 获取第一个空格的下标位置
int index = requestHead.indexOf(" ");
this.method = requestHead.substring(0, index);
//接下来区分是Get请求还是Post请求
if ("GET".equals(method)) {
setGetUriAndParameters(requestHead);
} else if (("POST").equals(method)) {
setPostUriAndParameters(requestHead, stringBuilder);
}
} catch (IOException e) {
e.printStackTrace();
}
}
上面代码中使用了两个方法分别来区分Get请求与Post请求的Uri、Parameter的设置。这两个方法分别为
/**
* 通过请求地址获取uri与参数
*
* @param requestHead
*/
private void setGetUriAndParameters(String requestHead) {
//最后一个空格的下标
int index = requestHead.lastIndexOf(" ");
// /favicon.ico?k=v
String uriAndParameter = requestHead.substring(requestHead.indexOf(" ") + 1, index);
if (uriAndParameter.contains("?")) {
//说明包含参数
//问号的下标
int flagIndex = uriAndParameter.indexOf("?");
String params = uriAndParameter.substring(flagIndex + 1);
String uri = uriAndParameter.substring(0, flagIndex);
this.uri = uri;
setParameter(params);
} else {
this.uri = uriAndParameter;
}
}
private void setPostUriAndParameters(String requestHead, StringBuilder sb) {
try {
//得到uri
uri = requestHead.substring(requestHead.indexOf(" ") + 1, requestHead.lastIndexOf(" "));
//URLDecoder是对游览器的请求进行解码
//传入的sb就是请求头+请求体
//我们通过寻找最后一个换行符就能得到请求体
String params = URLDecoder.decode(sb.substring(sb.lastIndexOf("\n") + 1), "utf-8");
//和get一样传入参数字符串,然后对参数进行初始化
setParameter(params);
} catch (IOException e) {
e.printStackTrace();
}
}
这两个方法都使用了setParameter方法来设置参数。该方法代码如下
private void setParameter(String params) {
//是否包含&符号
if (params.contains("&")) {
String[] kvs = params.split("&");
for (String s : kvs) {
String[] kv = s.split("=");
if (kv.length == 2) {
parameters.put(kv[0], kv[1]);
}
}
} else {
String[] kv = params.split("=");
if (kv.length == 2) {
parameters.put(kv[0], kv[1]);
}
}
}
测试
Get请求
首先测试Get方法,我们添加一些输出语句来观察运行结果,修改线程池的执行代码为下
public void execute(Socket socket) {
executor.execute(()->{
System.out.println(Thread.currentThread().getName()+"开始处理请求");
InputStream inputStream = null;
try {
inputStream = socket.getInputStream();
MyHttpRequest myHttpRequest = new MyHttpRequest(inputStream);
System.out.println("请求方式为:"+myHttpRequest.getMethod());
System.out.println("请求参数username为:"+myHttpRequest.getParameter("username"));
System.out.println("请求Uri为:"+myHttpRequest.getUri());
//读取完毕后,进行一个简单响应
OutputStream outputStream = socket.getOutputStream();
String responseHead = "HTTP/1.1 200\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
String responseBody = "这是一个简单的展示页面";
String response = responseHead + responseBody;
//将数据展示给浏览器
outputStream.write(response.getBytes());
outputStream.close();
inputStream.close();
socket.close();
System.out.println("断开连接...");
} catch (IOException e) {
e.printStackTrace();
}
});
}
在浏览器输入地址localhost:8080/people?username=123456观察控制台输出结果
可以看到正常输出。
Post请求
接下来测试Post请求,添加一个webapp文件夹,在里面编写一个简单的form表单
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="http://localhost:8080/login" method="post">
用户名:<input type="text" name="username"></br>
密码:<input type="password" name="password"></br>
<input type="submit">
</form>
</body>
</html>
然后打开该页面后输入值点击提交。
可以看到,可以正常输出结果。