作者:叩丁狼教育王一飞,高级讲师。转载请注明出处。
接上一篇,通过HttpServer,Request,Response个类的配合勉强可以处理浏览器发起的请求跟响应请求。但功能有点寒酸,只可以处理静态网页和404,本篇加入对servlet的支持,注意,仅仅是对servlet的简单支持。
servlet回顾:
servlet是java web一个组件,是java动态网页的基石,使用相对简单,想深入学习的朋友可以腾讯课堂看任小龙老师传送门:java大神之路java大神之路,这里不累赘了,就回顾下servlet用法:
public class MyServlet implements Servlet{
public MyServlet() {
System.out.println("创建....");
}
public void init(ServletConfig config) throws ServletException {
System.out.println("初始化....");
}
public void service(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
System.out.println("服务....");
}
public void destroy() {
System.out.println("销毁....");
}
public ServletConfig getServletConfig() {
return null;
}
public String getServletInfo() {
return null;
}
}
tomcat启动后, 发起第一请求时,servlet执行顺序
创建(构造器)----初始化(init)---[服务(service)] 循环----销毁(destroy)
非第一次发起请求,直接调用serivce方法重复执行。
好,回顾到这,下面进入主题。
代码结构:
UML类图(借用书中类图):
相对上篇代码做改进:
0:创建一个常量类Consts, 持有项目中所有的静态常量
/**
* 常量类
*/
public class Consts {
// tomcat项目绝对路径, 所有web项目都丢在webapps目录下
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webapps";
//请求404响应内容
public static final String RESPONSE_404_CONTENT = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
//请求响应成功响应头
public static final String RESPONSE_200_HEADER = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: #{count}\r\n" +
"\r\n";
}
1:Request遵循serlvet规范,实现ServletRequest接口
/**
* 请求信息封装对象
*/
public class Request implements ServletRequest{
// 浏览器socket连接的读流
private InputStream in;
//请求行信息信息中的uri
private String uri;
public Request(InputStream in) {
this.in = in;
}
// 解析浏览器发起的请求
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());
}
public String parseUri(String httpContent) {
//传入的内容解析第一行的请求行即可:
//请求行格式: 请求方式 请求uri 协议版本 3个内容以空格隔开
int beginIndex = httpContent.indexOf(" ");
int endIndex;
if(beginIndex > -1) {
endIndex = httpContent.indexOf(" ", beginIndex + 1);
if(endIndex > beginIndex) {
return httpContent.substring(beginIndex, endIndex).trim();
}
}
return null;
}
public String getUri() {
return uri;
}
/**省略一堆目前暂时没用到的ServletRequest需要实现的方法*/
public Object getAttribute(String name) {
return null;
}
//-------------------------------------------------
实现ServletRequest接口,需要重写的方法一概不动,空实现。
2:Response遵循servlet规范,实现ServletResponse接口
/**
* 处理响应请求对象
*/
public class Response implements ServletResponse{
// 浏览器socket连接的写流
private OutputStream out;
public Response(OutputStream out) {
this.out = out;
}
//跳转
public void sendRedirect(String uri) {
File webPage = new File(Consts.WEB_ROOT, uri);
FileInputStream fis = null;
StringBuffer sb = new StringBuffer();
try {
//找得到页面是
if(webPage.exists()&& webPage.isFile()) {
fis = new FileInputStream(webPage);
byte[] buff = new byte[2048];
int len = 0;
while( (len = fis.read(buff))!= -1) {
sb.append(new String(buff, 0, len));
}
String respHeader=Consts.RESPONSE_200_HEADER.replace("#{count}", sb.length()+"");
System.out.println(respHeader + sb);
out.write((respHeader + sb).getBytes());
}else {
//页面找不到时
out.write(Consts.RESPONSE_404_CONTENT.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//重写getWriter方法
public PrintWriter getWriter() throws IOException {
PrintWriter writer = new PrintWriter(out, true);
//设置响应头,后续还会修改
writer.println(Consts.RESPONSE_200_HEADER);
return writer;
}
//--------------------------
仅仅重写getWriter方法,其他方法空实现
3:响应处理分离:处理静态请求使用staticSourceProcessor类,处理servlet使用ServletProcessor类,判断依据uri中使用servlet字样
/**
* 用于响应静态文件请求
*/
public class StaticSourceProcessor {
public void process(Request request, Response response) {
response.sendRedircet(request.getUri());
}
}
servlet类的处理有点麻烦,原因:servlet摆放在约定好的webapp目录下,项目使用时,需要额外加载自定义的servlet的字节码到内存。
public class ServletProcessor {
public void process(Request request, Response response) {
String uri = request.getUri();
//从uri中获取serlvet名称
String servletName = uri.substring(uri.lastIndexOf("/")+1);
try {
//加载classpath路径,默认使用webapps
//此处设一个限制,约束自定义的serlvet必须没有包名,没有为什么,demo就不要那么多要求
File classPath = new File(Consts.WEB_ROOT);
//转换成url能识别的路径, 简单讲加上file协议
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
URL url = new URL(repository);
//创建一个url类加载器,用于加载上面指定serlvetName的servlet类
URLClassLoader loader = new URLClassLoader(new URL[] {url});
//通过反射创建servlet类对象
Class myClass = loader.loadClass(servletName);
Servlet servlet= (Servlet) myClass.newInstance();
//使用servlet调用service方法,servlet处理完成
servlet.service(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:这里做2个约定, 1:自定义的serlvet必须没有包名,直接放在src目录里。 2:将编译后的自定义字节码拷贝到webapps中等价于部署。
4:HttpServer类改造,实现响应分离
/**
* 模拟tomcat的核心类
*/
public class HttpServer {
// 模拟tomcat关闭命令
private static final String SHUTDOWN_CMD = "/SHUTDOWN";
private boolean shutdown = false;
//持续监听端口
@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;
try {
// 接收请求
socket = serverSocket.accept();
in = socket.getInputStream();
out = socket.getOutputStream();
// 将浏览器发送的请求信息封装成请求对象
Request request = new Request(in);
request.parseRequest();
// 将相应信息封装相应对象
Response response = new Response(out);
//实现约定:servlet请求路径必须以/servlet开头,以servlet简单类名结束
if(request.getUri().startsWith("/servlet")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
}else {
//此处简单响应一个静态资源文件
StaticSourceProcessor processor = new StaticSourceProcessor();
processor.process(request, response);
}
socket.close();
//如果是使用关闭命令,停止监听退出
shutdown = request.getUri().equals(SHUTDOWN_CMD);
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
public static void main(String[] args) {
new HttpServer().accept();
}
}
5:添加自定义的servlet类
HelloServlet:
在MyTomcat项目中加入servlet-api.jar依赖包,自定义HelloServlet实现Servlet接口,同时实现service方法,用于响应浏览器发送的请求。
public class HelloServlet implements Servlet {
public void init(ServletConfig arg0) throws ServletException {}
//重写service方法,响应请求
public void service(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.println("hello, servlet....");
}
public void destroy() {}
public ServletConfig getServletConfig() {return null; }
public String getServletInfo() {return null;}
}
再次强调, 这个类没有包(package),编译之后,应该将字节码放到webapps目录下,否则报类找不到异常。
6:测试
运行HttpServer类,
在浏览器中输入:http://localhost:8080/hello/index.html 进入静态资源处理
在浏览器中输入:http://localhost:8080/servlet/HelloServlet 进入静态资源处理