|
1 项目整体结构的设计
为了让新同学深入的理解tomcat的运行原理,动手实现一个简单的tomcat服务器。
文章比较长,而且需要跟着动手练习,才能有所收获。
思路模型图:


编辑
项目结构图:


编辑
web.xml映射文件中的内容,用于定义URL请求映射路径和java类之间的对应关系
<?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?> <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(&#34;myserver/WEB-INF/web.xml&#34;); 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(&#34;servlet&#34;); while(servletElements.hasNext()){ Element servletElement = servletElements.next(); Element servletName = servletElement.element(&#34;servlet-name&#34;); Element servletClass = servletElement.element(&#34;servlet-class&#34;); Entity entity=new Entity(servletName.getText(),servletClass.getText()); entityList.add(entity); } // 获取多个servlet-mapping标签 Iterator<Element> servletMappings = wepApp.elementIterator(&#34;servlet-mapping&#34;); while(servletMappings.hasNext()){ Mapping mapping =new Mapping(); Element servletMapping = servletMappings.next(); //取出servlet-name Element servletName = servletMapping.element(&#34;servlet-name&#34;); mapping.setName(servletName.getText()); Iterator<Element> urlPatterns = servletMapping.elementIterator(&#34;url-pattern&#34;); 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 || &#34;&#34;.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=&#34;http://127.0.0.1:8888/login&#34; method=&#34;post&#34;> 用户名:<input type=&#34;text&#34; name=&#34;username&#34; /><br/> 密码:<input type=&#34;password&#34; name=&#34;password&#34; /><br /> 登录身份: <input type=&#34;checkbox&#34; name=&#34;role&#34; value=&#34;manager&#34; />管理人员 <input type=&#34;checkbox&#34; name=&#34;role&#34; value=&#34;clerk&#34; />普通员工 <input type=&#34;checkbox&#34; name=&#34;role&#34; value=&#34;visiter&#34; />游客 <br /> <input type=&#34;submit&#34; /> </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=&#34;\r\n&#34;; private static final String BLANK=&#34; &#34;; public String getUrl() { return url; } public Request(){ requestInfo=&#34;&#34;; method=&#34;&#34;; url=&#34;&#34;; 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(&#34; &#34;); method=requestLines[0]; String paramaterLine=&#34;&#34;; if(method.equalsIgnoreCase(&#34;get&#34;)){// 请求方式是get if(requestLines[1].contains(&#34;?&#34;)){ url=requestLines[1].substring(0,requestLines[1].indexOf(&#34;?&#34;)); String[] urls=requestLines[1].split(&#34;\\?&#34;); 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(&#34;paramaterLine:&#34;+paramaterLine); if(&#34;&#34;.equalsIgnoreCase(paramaterLine)){ return; } String[] lines = paramaterLine.split(&#34;&&#34;); /* [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(&#34;=&#34;); 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],&#34;UTF-8&#34;); 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=&#34;\r\n&#34;; private static final String BLANK=&#34; &#34;; 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(&#34;HTTP/1.1&#34;).append(BLANK).append(code).append(BLANK); switch (code){ case 200: responseLine.append(&#34;OK&#34;); break; case 404: responseLine.append(&#34;NOTFOUND&#34;); break; case 500: responseLine.append(&#34;SERVEREXCEPTION&#34;); break; default: responseLine.append(&#34;Other&#34;); break; } responseLine.append(CRLF); } // 生成响应头的方法 public void createResponseHeaders(){ responseHeaders.append(&#34;Content-Type: text/html;charset=UTF-8&#34;).append(CRLF); responseHeaders.append(&#34;Content-Length: &#34;).append(length).append(CRLF).append(CRLF); } // 生成响应内容的方法 public void write(String info){ content.append(info); try { length+=info.getBytes(&#34;UTF-8&#34;).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(&#34;服务已经启动,准备接受请求&#34;); 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(&#34;未找到您要访问的资源&#34;); }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(&#34;未找到您要访问的资源&#34;); }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中使用多线程处理
|
|