1 项目整体结构的设计
为了让新同学深入的理解tomcat的运行原理,动手实现一个简单的tomcat服务器。
文章比较长,而且需要跟着动手练习,才能有所收获。
思路模型图:
项目结构图:
web.xml映射文件中的内容,用于定义URL请求映射路径和java类之间的对应关系
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<!--配置别名和对应的servletclass-->
<servlet>
<servlet-name>login</servlet-name>
<sevrlet-class>com.bjsxt.servlet.LoginServlet</sevrlet-class>
</servlet>
<!--配置请求的映射路径对应的servletname-->
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
<url-pattern>/login2</url-pattern>
</servlet-mapping>
</web-app>
2 Entity实体类的处理
/**
* 用于处理别名和对应servlet的实体类
* <sevlet>
* <sevlet-name>login</sevlet-name>
* <sevlet-class>com.bjsxt.servlet.LoginServlet</sevlet-class>
* </sevlet>
*/
public class Entity {
private String name;// servlet-name
private String clazz;// servlet-class
3 Mapping映射类的处理
/**
* 用于处理servlet别名和对应映射路径的实体类
* <sevlet-mapping>
* <sevlet-name>login</sevlet-name>
* <url-pattern>/login</url-pattern>
* <url-pattern>/login2</url-pattern>
* </sevlet-mapping>
*/
public class Mapping {
private String name;//servlet-name
private List<String> urlPattern;//多个url-pattern
public Mapping() {
this.urlPattern =new ArrayList<String>();
}
public Mapping(String name, List<String> urlPattern) {
this();
this.name = name;
this.urlPattern = urlPattern;
}
4 DOM4j解析xml映射文件
导入DOM4J jar包,帮助我们解析web.xml文件
/*
* 读取web.xml配置文件
* 配置文件中的信息封装进多个 entity对象和多个mapping对象中
*
* */
public class WebDom4j {
private List<Entity> entityList;
private List<Mapping> mappingList;
public WebDom4j() {
entityList=new ArrayList<Entity>();
mappingList=new ArrayList<Mapping>();
}
public WebDom4j(List<Entity> entityList, List<Mapping> mappingList) {
this();
this.entityList = entityList;
this.mappingList = mappingList;
}
public List<Entity> getEntityList() {
return entityList;
}
public void setEntityList(List<Entity> entityList) {
this.entityList = entityList;
}
public List<Mapping> getMappingList() {
return mappingList;
}
public void setMappingList(List<Mapping> mappingList) {
this.mappingList = mappingList;
}
// 读取配置文件,生成一个Document对象的方法
public Document getDocument() {
SAXReader reader =new SAXReader();
File webXml=new File("myserver/WEB-INF/web.xml");
try {
return reader.read(webXml);
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
// 将Document对象解析成Entity对象和Mapping对象 并放入 entityList 和 mappingList中
public void parseDocument(Document document){
// 获取根标签
Element wepApp = document.getRootElement();
// 获取多个servlet标签
Iterator<Element> servletElements = wepApp.elementIterator("servlet");
while(servletElements.hasNext()){
Element servletElement = servletElements.next();
Element servletName = servletElement.element("servlet-name");
Element servletClass = servletElement.element("servlet-class");
Entity entity=new Entity(servletName.getText(),servletClass.getText());
entityList.add(entity);
}
// 获取多个servlet-mapping标签
Iterator<Element> servletMappings = wepApp.elementIterator("servlet-mapping");
while(servletMappings.hasNext()){
Mapping mapping =new Mapping();
Element servletMapping = servletMappings.next();
//取出servlet-name
Element servletName = servletMapping.element("servlet-name");
mapping.setName(servletName.getText());
Iterator<Element> urlPatterns = servletMapping.elementIterator("url-pattern");
List<String> patterns=new ArrayList<String>();
while(urlPatterns.hasNext()){
Element urlPattern = urlPatterns.next();
patterns.add(urlPattern.getText());
}
mapping.setUrlPattern(patterns);
mappingList.add(mapping);
}
}
}
5处理ServletContext类
全局容器,目前用于存储映射关系
import java.util.HashMap;
import java.util.Map;
/*
* 全局容器类 存储整个项目的信息
* 暂时我们 需要他存储 全局映射关系信息
* Map mapping 存储映射路径和别名 Map servlet 别名和servlet类的全路径名
* url路径 >>>>> servlet别名 servlet别名 >>>> servlet类的全路径名
* /login login login com.bjsxt.servlet.LoginServlet
* /login2 login
* */
public class ServletContext {
/**
* List<Entity> entityList;
* servlet-name作为键
* servlet-class作为值
*/
private Map<String,String> servlet;
/*
List<Mapping> mappingList;
* url-pattern 作为键
* servlet-name作为值
* */
private Map<String,String> mapping;
public ServletContext() {
servlet=new HashMap<String,String>();
mapping=new HashMap<String,String>();
}
public ServletContext(Map<String, String> servlet, Map<String, String> mapping) {
this();
this.servlet = servlet;
this.mapping = mapping;
}
6 编写WebApp类
运行时初始化程序
根据不同的url使用反射创建不同的servlet对象
package com.bjsxt.server;
import com.bjsxt.servlet.Servlet;
import org.dom4j.Document;
import java.util.List;
import java.util.Map;
public class WebApp {
// 将WebDom4j中的信息转化到ServletContext类中
private static ServletContext context;
static{
context=new ServletContext();
Map<String, String> servlet = context.getServlet();
Map<String, String> mapping = context.getMapping();
WebDom4j dom4j=new WebDom4j();
Document document = dom4j.getDocument();
dom4j.parseDocument(document);
List<Entity> entityList = dom4j.getEntityList();
for(Entity entity:entityList){
String name = entity.getName();
String clazz = entity.getClazz();
servlet.put(name,clazz);
}
List<Mapping> mappingList = dom4j.getMappingList();
for(Mapping mappingx:mappingList){
String name = mappingx.getName();
List<String> urlPatterns = mappingx.getUrlPattern();
for(String urlPattern:urlPatterns){
mapping.put(urlPattern,name);
}
}
}
// 根据映射路径返回对应的servlet对象
public static Servlet getServlet(String urlPattern){// /login >>> com.bjsxt.servlet.LoginServlet
if(null == urlPattern || "".equals(urlPattern)){
return null;
}
Map<String, String> mappging = context.getMapping();
String servletName = mappging.get(urlPattern);
if(null == servletName){
return null;
}
Map<String, String> servlet = context.getServlet();
String servletClassName = servlet.get(servletName);
if(null == servletClassName){
return null;
}
try {
Class servletClazz = Class.forName(servletClassName);
return (Servlet) servletClazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
7 准备提交信息的HTML页面
<form action="http://127.0.0.1:8888/login" method="post">
用户名:<input type="text" name="username" /><br/>
密码:<input type="password" name="password" /><br />
登录身份:
<input type="checkbox" name="role" value="manager" />管理人员
<input type="checkbox" name="role" value="clerk" />普通员工
<input type="checkbox" name="role" value="visiter" />游客
<br />
<input type="submit" />
</form>
8 封装请求对象
将请求中的信息放入一个Request对象中,便于我们直接从Request对象上获取信息
package com.bjsxt.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
public class Request {
private InputStream is;
private String requestInfo;
private String method;// post get
private String url;//
//用于存储参数的Map集合
//username=zhangsan&password=qwer&role=manager&role=clerk
/*
* key value
* username [zhangsan]
* password [qwer]
* role [manager,clerk]
*
* */
private Map<String, List<String>> paramaterValues;
private static final String CRLF="rn";
private static final String BLANK=" ";
public String getUrl() {
return url;
}
public Request(){
requestInfo="";
method="";
url="";
paramaterValues=new HashMap<String,List<String>>();
}
public Request(InputStream is){
this();
this.is=is;
paraseRequeseInfo();
}
public void paraseRequeseInfo(){
// 获取请求信息
byte[] buf=new byte[10240];
int index = 0;
try {
index = is.read(buf);
} catch (IOException e) {
e.printStackTrace();
}
requestInfo=new String (buf,0,index);
// 取出请求行 请求方式 url地址 请求的协议
String requestLine=requestInfo.substring(0,requestInfo.indexOf(CRLF)).trim();
String[] requestLines = requestLine.split(" ");
method=requestLines[0];
String paramaterLine="";
if(method.equalsIgnoreCase("get")){// 请求方式是get
if(requestLines[1].contains("?")){
url=requestLines[1].substring(0,requestLines[1].indexOf("?"));
String[] urls=requestLines[1].split("?");
paramaterLine=urls[1].trim();
}else{
url=requestLines[1];
}
}else{//请求方式是Post
url=requestLines[1];
paramaterLine=requestInfo.substring(requestInfo.lastIndexOf(CRLF)+1).trim();
}
parseParamater(paramaterLine);
}
public void parseParamater(String paramaterLine){
//username=asdf&password=qwer&role=manager&role=clerk
System.out.println("paramaterLine:"+paramaterLine);
if("".equalsIgnoreCase(paramaterLine)){
return;
}
String[] lines = paramaterLine.split("&");
/*
[username,asdf]
[password,qwer]
[role,manager]
[role,clerk]
*/
/*
* key value
* username [zhangsan]
* password [qwer]
* role [manager,clerk]
*
* */
for(String line:lines){
String[] keyValue = line.split("=");
if(keyValue.length==1){// key=
keyValue=Arrays.copyOf(keyValue,2);
keyValue[1]=null;
}
String key=keyValue[0];
String value=keyValue[1]==null?null:decode(keyValue[1],"UTF-8");
if(paramaterValues.containsKey(key)){//
List<String> values = paramaterValues.get(key);
values.add(value);
}else{
List<String> values=new ArrayList<String>();
values.add(value);
paramaterValues.put(key,values);
}
}
System.out.println(paramaterValues);
}
public String decode(String value,String charset){
// %E5%BC%A0%E4%B8%89 UTF-8
try {
return URLDecoder.decode(value,charset);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
// 根据键向外界返回参数的方法
public String getParamater(String name){
if(paramaterValues.containsKey(name)){
List<String> list = paramaterValues.get(name);
if(null == list || list.size()==0){
return null;
}else{
return list.get(0);
}
}
return null;
}
public String[] getParamaterValues(String name){
if(paramaterValues.containsKey(name)){
List<String> list = paramaterValues.get(name);
if(null == list || list.size()==0){
return null;
}else{
String[] values = list.toArray(new String[0]);
return values;
}
}
return null;
}
}
9封装响应对象
package com.bjsxt.server;
import java.io.*;
public class Response {
// 字符输出流
private BufferedWriter bw;
// 响应行
private StringBuilder responseLine;
// 响应头
private StringBuilder responseHeaders;
// 响应数据实体
private StringBuilder content;
// 响应数据的长度
private int length;
private static final String CRLF="rn";
private static final String BLANK=" ";
public Response() {
responseLine=new StringBuilder();
responseHeaders=new StringBuilder();
content=new StringBuilder();
length=0;
}
public Response(OutputStream os){
this();
bw =new BufferedWriter(new OutputStreamWriter(os));
}
// 处理响应行的方法
public void createResponseLine(int code){
//HTTP/1.1 200 OK
responseLine.append("HTTP/1.1").append(BLANK).append(code).append(BLANK);
switch (code){
case 200:
responseLine.append("OK");
break;
case 404:
responseLine.append("NOTFOUND");
break;
case 500:
responseLine.append("SERVEREXCEPTION");
break;
default:
responseLine.append("Other");
break;
}
responseLine.append(CRLF);
}
// 生成响应头的方法
public void createResponseHeaders(){
responseHeaders.append("Content-Type: text/html;charset=UTF-8").append(CRLF);
responseHeaders.append("Content-Length: ").append(length).append(CRLF).append(CRLF);
}
// 生成响应内容的方法
public void write(String info){
content.append(info);
try {
length+=info.getBytes("UTF-8").length;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
// 将内容响应给浏览器的方法
public void responseToBrowser(int code){
createResponseLine(code);
createResponseHeaders();
StringBuilder info=new StringBuilder();
info.append(responseLine).append(responseHeaders).append(content);
try {
bw.write(info.toString());
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
创建server类,用于启动服务,接受请求
package com.bjsxt.server;
import com.bjsxt.servlet.Servlet;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
new Server().start();
}
private ServerSocket serverSocket;
// 启动服务,接受请求
public void start(){
try {
serverSocket=new ServerSocket(8888);
System.out.println("服务已经启动,准备接受请求");
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// 将请求信息封装进一个Request对象
Request request =new Request(inputStream);
// 封装一个Response响应对象
Response response=new Response(outputStream);
// 根据请求的url 实例化一个Servlet对象
String url = request.getUrl();
Servlet servlet = WebApp.getServlet(url);
int code =200;
if(null == servlet){
code=404;
response.write("未找到您要访问的资源");
}else {
// 调用servlet的service方法去处理请求
try {
servlet.service(request,response);
} catch (Exception e) {
code=500;
response.write(e.getMessage());
}
}
response.responseToBrowser(code);
} catch (IOException e) {
e.printStackTrace();
}
}
}
10多线程处理
定义请求分发线程任务
import com.bjsxt.servlet.Servlet;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class RequestDispacher implements Runnable{
private Socket socket;
public RequestDispacher(Socket socket){
this.socket=socket;
}
@Override
public void run() {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
// 将请求信息封装进一个Request对象
Request request =new Request(inputStream);
// 封装一个Response响应对象
Response response=new Response(outputStream);
// 根据请求的url 实例化一个Servlet对象
String url = request.getUrl();
Servlet servlet = WebApp.getServlet(url);
int code =200;
if(null == servlet){
code=404;
response.write("未找到您要访问的资源");
}else {
// 调用servlet的service方法去处理请求
try {
servlet.service(request,response);
} catch (Exception e) {
code=500;
response.write(e.getMessage());
}
}
response.responseToBrowser(code);
// 释放资源
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在Server中使用多线程处理
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
new Server().start();
}
private ServerSocket serverSocket;
// 启动服务,接受请求
public void start(){
try {
serverSocket=new ServerSocket(8888);
System.out.println("服务已经启动,准备接受请求");
while(true){
Socket socket = serverSocket.accept();
new Thread(new RequestDispacher(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}