手写简易WEB服务器
今天我们来写一个类似于Tomcat的简易服务器。可供大家深入理解一下tomcat的工作原理,本文仅供新手参考,请各位大神指正!
首先我们要准备的知识是:
Socket编程
HTML
HTTP协议
服务器编写
反射
XML解析
有了上面的知识,我们可以开始写我们的代码了~~
1、首先我们要应用Socket编程写一个简单的服务端用来接收服务器端发来的请求:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { Server server = new Server(); //1、创建一个服务器端并开启 server.start(); } public void start(){ try { ServerSocket ss = new ServerSocket(8888); //2、接收来自浏览器的请求 this.recevie(ss); } catch (IOException e) { e.printStackTrace(); } } private void recevie(ServerSocket ss){ try { Socket client = ss.accept(); //3、将来自浏览器的信息打印出来 BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); StringBuilder httpMessage = new StringBuilder(); while(br.read()!=-1){ httpMessage.append(br.readLine()); httpMessage.append("\r\n"); } System.out.println(httpMessage.toString()); } catch (IOException e) { e.printStackTrace(); } } }
以下是浏览器访问上述服务器时产生的请求内容:HTTP知识补充:
以下是浏览器访问上述服务器时产生的请求内容:
POST请求内容:(远不止这些,大家可以通过wireshark来抓包分析请求协议格式)
POST / HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 25
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: null
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
username=ss&password=aaaa
GET请求内容:
GET /?username=aa&password=ssss HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
下面给出一个服务器响应内容:
通用头域包含请求和响应消息都支持的头域,通用头域包含Cache-Control、 Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via。
HTTP/1.1 200 OK
Cache-Control:private, max-age=0, no-cache:
Control说明:
Cache-Control指定请求和响应遵循的缓存机 制。
Public指示响应可被任何缓存区缓存。
Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
no-cache指示请求或响应消息不能缓存
no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
Content-Type:image/gif
Content-Type说明:
Content-Type实体头用于向接收方指示实体的介质类型,指定HEAD方法送到接收方的实体介质类型,或GET方法发送的请求介质类型Content-Range实体头
Date:Sat, 08 Aug 2015 03:23:23 GMT
Date说明:
Date头域表示消息发送的时间,时间的描述格式由rfc822定义。
Pragma:no-cache
Pragma说明:
Pragma头域用来包含实现特定的指令,最常用的是Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。
Server:apache
Server说明:
Server响应头包含处理请求的原始服务器的软件信息。此域能包含多个产品标识和注释,产品标识一般按照重要性排序。
Content-Length:43
重点补充:
Status-Code的第一个数字定义响应的类别,后两个数字没有分类的作用。第一个数字可能取5个不同的值:
1xx:信息响应类,表示接收到请求并且继续处理
2xx:处理成功响应类,表示动作被成功接收、理解和接受
3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理
4xx:客户端错误,客户请求包含语法错误或者是不能正确执行
5xx:服务端错误,服务器不能正确执行一个正确的请求
以上补充有利于项目排错哦~~很重要哦~~
2、我们根据上面的服务器相应内容来写一个针对浏览器客户端的响应:
public class Server { private static final String ENTER = "\r\n"; private static final String SPACE = " "; public static void main(String[] args) { Server server = new Server(); //1、创建一个服务器端并开启 server.start(); } public void start(){ try { ServerSocket ss = new ServerSocket(8888); //2、接收来自浏览器的请求 this.recevie(ss); } catch (IOException e) { e.printStackTrace(); } } private void recevie(ServerSocket ss){ BufferedReader br = null; try { Socket client = ss.accept(); //3、将来自浏览器的信息打印出来 br = new BufferedReader(new InputStreamReader(client.getInputStream())); StringBuilder httpRequest = new StringBuilder(); String meg = null; while(!(meg = br.readLine().trim()).equals("")){ httpRequest.append(meg); httpRequest.append("\r\n"); } System.out.println(httpRequest); this.httpResponse(client); } catch (IOException e) { e.printStackTrace(); }finally{ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } private void httpResponse(Socket client){ BufferedWriter bw = null; try { bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); StringBuilder contextText = new StringBuilder(); contextText.append("<html><head></head><body>This is my page</body></html>"); StringBuilder sb = new StringBuilder(); /*通用头域begin*/ sb.append("HTTP/1.1").append(SPACE).append("200").append(SPACE).append("OK").append(ENTER); sb.append("Server:myServer").append(SPACE).append("0.0.1v").append(ENTER); sb.append("Date:Sat,"+SPACE).append(new Date()).append(ENTER); sb.append("Content-Type:text/html;charset=UTF-8").append(ENTER); sb.append("Content-Length:").append(contextText.toString().getBytes().length).append(ENTER); /*通用头域end*/ sb.append(ENTER);//空一行 sb.append(contextText);//正文部分 System.out.println(sb.toString()); bw.write(sb.toString());//写会 bw.flush(); } catch (IOException e) { e.printStackTrace(); }finally{ try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
3、封装response和request
3.1封装response
步骤:
A:构建报文头
B:构建响应的HTML正文内容
C:将报文头和HTML正文内容发送给客户端(浏览器)
import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.Date; public class Response { private static final String ENTER = "\r\n"; private static final String SPACE = " "; //存储头信息 private StringBuilder headerInfo ; //2、存储正文信息 private StringBuilder textContent; //3、记录正文信息长度 private int contentLength ; //4、构建输出流 private BufferedWriter bw ; public Response() { headerInfo = new StringBuilder(); textContent = new StringBuilder(); contentLength = 0; } public Response(OutputStream os) { this(); bw = new BufferedWriter(new OutputStreamWriter(os)); } /** * 创建头部信息 html报文 * @param code */ private void createHeader(int code){ headerInfo.append("HTTP/1.1").append(SPACE).append(code).append(SPACE); switch (code) { case 200: headerInfo.append("OK").append(ENTER); break; case 404: headerInfo.append("NOT FOUND").append(ENTER); break; case 500: headerInfo.append("SERVER ERROR").append(ENTER); break; default: break; } headerInfo.append("Server:myServer").append(SPACE).append("0.0.1v").append(ENTER); headerInfo.append("Date:Sat,"+SPACE).append(new Date()).append(ENTER); headerInfo.append("Content-Type:text/html;charset=UTF-8").append(ENTER); headerInfo.append("Content-Length:").append(contentLength).append(ENTER); headerInfo.append(ENTER); } /** * 响应给浏览器解析的内容(html正文) * @param content * @return */ public Response htmlContent(String content){ textContent.append(content).append(ENTER); contentLength += (content+ENTER).toString().getBytes().length; return this; } /** * 发送给浏览器端 * @param code */ public void pushToClient(int code){ createHeader(code); try { bw.append(headerInfo.toString()); System.out.println(headerInfo.toString()); bw.append(textContent.toString()); System.out.println(textContent.toString()); bw.flush(); } catch (IOException e) { e.printStackTrace(); }finally{ try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
3.2封装request
步骤:
A:接受浏览器发送的请求
B:解析浏览器发送来的请求
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class Request { private static final String ENTER = "\r\n"; //接收请求 private BufferedReader request ; //储存接受信息 private String requestHeader; //通过解析头信息得到请求方法 private String method ; //通过解析头信息得到请求url private String action ; //通过解析头信息得到传过来的请求参数 ,可能存在一Key多Value的情况所以用list private Map<String, List<String>> parameter; //得到浏览器发过来的头信息 public Request() { requestHeader = ""; method = ""; action = ""; parameter = new HashMap<String, List<String>>(); } public Request(InputStream inputStream) { this(); request = new BufferedReader(new InputStreamReader(inputStream)); //接收到头部信息 try { String temp; while(!(temp=request.readLine()).equals("")){ requestHeader += (temp+ENTER); } System.out.println(requestHeader); } catch (IOException e) { e.printStackTrace(); } //解析头部信息 parseRequestHeader(); } /** * 解析头信息 */ public void parseRequestHeader(){ //声明一个字符串,来存放请求参数 String parameterString = ""; //读取都头信息的第一行 String firstLine = requestHeader.substring(0, requestHeader.indexOf(ENTER)); //开始分离第一行 //splitPoint分割点1 int splitPointOne = firstLine.indexOf("/"); method = firstLine.substring(0, splitPointOne).trim(); //splitPoint分割点2 int splitPointTwo = firstLine.indexOf("HTTP/"); String actionTemp = firstLine.substring(splitPointOne,splitPointTwo).trim(); if(method.equalsIgnoreCase("post")){ //此处代码为得到post请求的参数字符串,哈哈哈哈,读者自己想想该怎么写哦~~ this.action = actionTemp; }else if(method.equalsIgnoreCase("get")){ if(actionTemp.contains("?")){ parameterString = actionTemp.substring((actionTemp.indexOf("?")+1)).trim(); this.action = actionTemp.substring(0, actionTemp.indexOf("?")); }else{ this.action = actionTemp; } //将参数封装到Map中哦 parseParameterString(parameterString); } } /** * 解析参数字符串,将参数封装到Map中 * @param parameterString */ private void parseParameterString(String parameterString) { if("".equals(parameterString)){ return; }else{ String[] parameterKeyValues = parameterString.split("&"); for (int i = 0; i < parameterKeyValues.length; i++) { String[] KeyValues = parameterKeyValues[i].split("="); //可能会出现有key没有value的情况 if(KeyValues.length == 1){ KeyValues = Arrays.copyOf(KeyValues, 2); KeyValues[1] = null; } String key = KeyValues[0].trim(); String values = null == KeyValues[1] ? null : decode(KeyValues[1].trim(),"UTF-8"); //将key和values封装到Map中 if(!parameter.containsKey(key)){//如果不存在key,就创建一个 parameter.put(key, new ArrayList<String>()); } List<String> value = parameter.get(key); value.add(values); } } } /** * 反解码:使用指定的编码机制对 application/x-www-form-urlencoded 字符串解码。 * @param string * @param encoding * @return */ public String decode(String string,String encoding){ try { return URLDecoder.decode(string, encoding); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } /** * 根据名字得到多个值 * @param name * @return */ public String[] getParamterValues(String name){ List<String> values = parameter.get(name); if(values == null){ return null; }else{ return values.toArray(new String[0]); } } /** * 根据名字返回单个值 * @param name * @return */ public String getParamter(String name){ String[] value = getParamterValues(name); if(value == null){ return null; }else{ return value[0]; } } public String getAction() { return action; } public void setAction(String action) { this.action = action; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } }
3.3 此时我们可以将代码优化一下
新建Servlet类来转码处理请求和响应的业务
/** * 专门处理请求和响应 * @author SNOOPY * */ public class Servlet { public void service(Request request, Response response){ String username = request.getParamter("user"); response.htmlContent("<html><head></head><body>This is my page<br><br>"); response.htmlContent("欢迎:"+username+" 来到我的地盘</body></html>"); } }
此时我们的服务器端可以简化为:
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * * @author SNOOPY * @version 02 */ public class Server02 { public void start(){ try { ServerSocket serverSocket = new ServerSocket(8888); //2、接收来自浏览器的请求 this.recevie(serverSocket); } catch (IOException e) { e.printStackTrace(); } } private void recevie(ServerSocket serverSocket){ try { Socket client = serverSocket.accept(); Servlet servlet = new Servlet(); Request request = new Request(client.getInputStream()); Response response = new Response(client.getOutputStream()); servlet.service(request, response); response.pushToClient(200); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Server02 server = new Server02(); //1、创建一个服务器端并开启 server.start(); } }
4、多线程实现
写到这里我们会发现,此时的代码只能处理一个请求,而现实中的情况是往往会有很多很多的客户端发请求,所以这里我们要用多线程来实现
因此我们把与客户端浏览器的通信封装到一个线程当中。
import java.io.IOException; import java.net.Socket; /** * 分发 * @author SNOOPY * */ public class Dispatch implements Runnable{ private Socket client; private Request request; private Response response; private int code = 200; public Dispatch(Socket client) { this.client = client; try { request = new Request(client.getInputStream()); response = new Response(client.getOutputStream()); } catch (IOException e) { //e.printStackTrace(); code = 500; return ; } } @Override public void run() { Servlet servlet = new Servlet(); servlet.service(request, response); response.pushToClient(code); try { client.close(); } catch (IOException e) { e.printStackTrace(); } } }
此时我们的服务器只能应对单一的请求与响应,结束后会自动关闭,而现实中的服务器是一直开启等待客户端的请求。
所以我们的服务器端可以优化为:
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * * @author SNOOPY * @version 03 */ public class Server03 { private boolean isShutDown = false; /** * 启动服务器 */ public void start(){ start(8888); } /** * 指定服务器端口 * @param port */ public void start(int port){ try { ServerSocket serverSocket = new ServerSocket(port); //2、接收来自浏览器的请求 this.recevie(serverSocket); } catch (IOException e) { //e.printStackTrace(); stop(); } } /** * 关闭服务器 */ private void stop() { isShutDown = true; } /** * 接受客户端信息 * @param serverSocket */ private void recevie(ServerSocket serverSocket){ try { while(!isShutDown){ Socket client = serverSocket.accept(); new Thread(new Dispatch(client)).start(); } } catch (IOException e) { e.printStackTrace(); //如果这里面有问题直接关闭服务器 isShutDown = true; } } public static void main(String[] args) { Server03 server = new Server03(); //1、创建一个服务器端并开启 server.start(); } }
到此时我们会发现我们发过来的请求会有很多,也就意味着我们应该会有很多的servlet,例如:RegisterServlet、LoginServlet等等还有很多其他的访问。
那么我们要用到类似于工厂模式的方法处理,来随时产生很多的servlet,来满足不同的功能性的请求。
那么我们要抽象servlet。
在我们抽象servlet之前,我们先来思考一个问题:
我们会写很多的servlet,那么我们怎么将请求与各种的servlet相匹配呢?
接下来我们要先写一个关于servlet的上下文,来封装servlet与请求。说到这里你是不是想到了什么呢??
import java.util.HashMap; import java.util.Map; /** * servlet的上下文 * @author SNOOPY * */ public class servletContext { //通过类名创建servlet对象 private Map<String, Servlet> servlet ; //通过请求名找到对应的servlet类名 private Map<String , String> mapping ; public servletContext() { servlet = new HashMap<String,Servlet>(); mapping = new HashMap<String,String>(); } public Map<String, Servlet> getServlet() { return servlet; } public void setServlet(Map<String, Servlet> servlet) { this.servlet = servlet; } public Map<String, String> getMapping() { return mapping; } public void setMapping(Map<String, String> mapping) { this.mapping = mapping; } }
其实说白了servletContext这个类就是一个容器,用来存放请求与对应的Servlet的。
那么接下来我们模拟一下,往这个容器里面存放请求与对应的Servlet,但是在这之前我们需要有不同的servlet,所以我们接下来要把Servlet进行抽象,
以便于我们可以随意产生不同的servlet。
/** * 专门处理请求和响应 * @author SNOOPY * */ public abstract class Servlet { public void service(Request request, Response response) throws Exception{ String method = request.getMethod(); if(method.equalsIgnoreCase("post")){ this.doPost(request, response); }else if(method.equalsIgnoreCase("get")){ this.doGet(request, response); } } public void doGet(Request request, Response response) throws Exception{ } public void doPost(Request request, Response response) throws Exception{ } }
上面的抽象其实很简单,那么当我们抽象结束后,我们写一个WebApp类来存储一些我们会用到的servlet上下文,并且提供获取他们的方法:
import java.util.HashMap; import java.util.Map; /** * servlet的上下文 * @author SNOOPY * */ public class servletContext { //通过对应的servlet类名创建servlet对象 private Map<String, Servlet> servlet ; //通过请求名(action)找到对应的servlet类名 private Map<String , String> mapping ; public servletContext() { servlet = new HashMap<String,Servlet>(); mapping = new HashMap<String,String>(); } public Map<String, Servlet> getServlet() { return servlet; } public void setServlet(Map<String, Servlet> servlet) { this.servlet = servlet; } public Map<String, String> getMapping() { return mapping; } public void setMapping(Map<String, String> mapping) { this.mapping = mapping; } }
接下来我们要修改一下分发的程序了:
import java.io.IOException; import java.net.Socket; /** * 分发 * @author SNOOPY * */ public class Dispatch implements Runnable{ private Socket client; private Request request; private Response response; private int code = 200; public Dispatch(Socket client) { this.client = client; try { request = new Request(client.getInputStream()); response = new Response(client.getOutputStream()); } catch (IOException e) { //e.printStackTrace(); code = 500; return ; } } @Override public void run() { try{ String action = request.getAction(); Servlet servlet = WebApp.getServlet(action); if(servlet == null){ this.code = 404; response.pushToClient(code); return; } servlet.service(request, response); } catch (Exception e) { e.printStackTrace(); code = 500; } response.pushToClient(code); try { client.close(); } catch (IOException e) { e.printStackTrace(); } } }
这样一个简易的原理过程就出来了,别急,还没有结束呢。
我们现在写的代码用来存储servlet上下文的时候用的是Servlet抽象类吧?就是说我们每次存储的时候都要存储一个类,这样是不是有点消耗内存呢?如果换成字符串呢?
还有一点就是如果我们每次修改这个类是不是都要重启一下服务器?是不是很麻烦??所以。。。你想到了什么?
随你便啦~~其实如果你写过非框架的web应用,那么应该接触过配置文件。那么应该想到的就是web.xml
接下来讲一下配置文件吧,哈哈哈哈哈哈
一说到配置文件,肯定是要解析配置文件的,一解析配置又牵扯到类与对象,少不了的技术就是反射机制哦!!
首先让我们稍微修改一下ServletContext类;
import java.util.HashMap; import java.util.Map; /** * servlet的上下文 * @author SNOOPY * */ public class ServletContext { //通过对应的servlet类名创建servlet对象 //private Map<String, Servlet> servlet ; private Map<String, String> servlet ; //通过请求名(action)找到对应的servlet类名 private Map<String , String> mapping ; public ServletContext() { servlet = new HashMap<String,String>(); mapping = new HashMap<String,String>(); } public Map<String, String> getServlet() { return servlet; } public void setServlet(Map<String, String> servlet) { this.servlet = servlet; } public Map<String, String> getMapping() { return mapping; } public void setMapping(Map<String, String> mapping) { this.mapping = mapping; } }
那么接下来我们要做的就是解析XML文件并且将解析好的值放入servlet上下文中。
解析xml文件之前我们先要用实体类来封装xml文件
/** * * @author SNOOPY * */ public class XmlServlet { private String servlet_name; private String servlet_class; public String getServlet_name() { return servlet_name; } public void setServlet_name(String servlet_name) { this.servlet_name = servlet_name; } public String getServlet_class() { return servlet_class; } public void setServlet_class(String servlet_class) { this.servlet_class = servlet_class; } }
import java.util.ArrayList; import java.util.List; /** * 一个servlet可以对应多个action * @author SNOOPY * */ public class XmlMapping { private String servlet_name; private List<String> url_pattern; public XmlMapping() { url_pattern = new ArrayList<String>(); } public String getServlet_name() { return servlet_name; } public void setServlet_name(String servlet_name) { this.servlet_name = servlet_name; } public List<String> getUrl_pattern() { return url_pattern; } public void setUrl_pattern(List<String> url_pattern) { this.url_pattern = url_pattern; } }
然后再解析XML文件并且将解析好的值放入servlet上下文中。
import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.SAXException; /** * 存储一些我们会用到的servlet上下文,并且提供获取他们的方法 * @author SNOOPY * */ public class WebApp { private static ServletContext context; static { context = new ServletContext(); //创建存放servlet上下文的容器 Map<String, String> mapping = context.getMapping(); Map<String, String> servlet = context.getServlet(); //解析配置文件,将对应的字符串存入里面 /*补充知识: * 解析配置文件的方法有很多,最基本的是SAX解析和DOM解析:SAX解析式基于事件流的解析,DOM解析是基于XML文档树结构的解析 * 另外还有DOM4J和JDOM都可以解析。 * DOM和SAX的区别: * DOM解析适合于对文件进行修改和随机存取的操作,但是不适合于大型文件的操作; * SAX采用部分读取的方式,所以可以处理大型文件,而且只需要从文件中读取特定内容,SAX解析可以由用户自己建立自己的对象模型。 * 所以DOM解析适合于修改,SAX解析适合于读取大型文件,2者结合的话可以用JDOM * * 本次示例为了方便就选择SAX解析,步骤一共分三步: * 1、获得解析工程类。 * 2、工程获取解析器 * 3、加载文档注册处理器 */ //1、获得工厂类 SAXParserFactory factory = SAXParserFactory.newInstance(); try { //2、从解析工程获取解析器 SAXParser parser = factory.newSAXParser(); //3、加载文档并注册处理器(handle)。注意:此处的文档可以用file的形式也可以用流的形式,随便,便于学习,下面提供两种。 //String filePath = ""; //parser.parse(new File(filePath), handler); XMLHandler handler = new XMLHandler(); InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/httpservlet/WEB-INF/web.xml"); parser.parse(is, handler); List<XmlServlet> serv = handler.getServlet(); for (XmlServlet xmlServlet : serv) { servlet.put(xmlServlet.getServlet_name(), xmlServlet.getServlet_class()); } List<XmlMapping> map = handler.getMapping(); for (XmlMapping maps : map) { List<String> actions = maps.getUrl_pattern(); for (String action : actions) { mapping.put(action, maps.getServlet_name()); } } } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static Servlet getServlet(String action){ if("".equals(action) || action == null){ return null; } //通过action找到servlet-name String servlet_name = context.getMapping().get(action); //通过反射,找到相应的类,创建其对象并返回 String classPath = context.getServlet().get(servlet_name);//通过action得到类路径 Servlet servlet = null; if(classPath != null){ Class<?> clazz = null; try { clazz = Class.forName(classPath); servlet = (Servlet)clazz.newInstance();//要确保空构造存在 } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return servlet; } }
这里还需要一个XMLHandler处理器来处理xml文件
import java.util.ArrayList; import java.util.List; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * 存储对象 * @author SNOOPY * */ public class XMLHandler extends DefaultHandler { private List<XmlServlet> servlet ; private List<XmlMapping> mapping ; public List<XmlServlet> getServlet() { return servlet; } public void setServlet(List<XmlServlet> servlet) { this.servlet = servlet; } public List<XmlMapping> getMapping() { return mapping; } public void setMapping(List<XmlMapping> mapping) { this.mapping = mapping; } private XmlServlet serv ; private XmlMapping map ; private String beginTag; private boolean isMap; @Override public void startDocument() throws SAXException { servlet = new ArrayList<XmlServlet>(); mapping = new ArrayList<XmlMapping>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if(null!=qName){ beginTag = qName; if(qName.equals("servlet")){ serv = new XmlServlet(); isMap = false; }else if(qName.equals("servlet-mapping")){ map = new XmlMapping(); isMap = true; } } } @Override public void characters(char[] ch, int start, int length) throws SAXException { if(beginTag != null){ String info = new String(ch, start, length); if(isMap){ if(beginTag.equals("servlet-name")){ map.setServlet_name(info.trim()); }else if(beginTag.equals("url-pattern")){ map.getUrl_pattern().add(info); } }else{ if(beginTag.equals("servlet-name")){ serv.setServlet_name(info); }else if(beginTag.equals("servlet-class")){ serv.setServlet_class(info); } } } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if(null!=qName){ if(qName.equals("servlet")){ servlet.add(serv); }else if(qName.equals("servlet-mapping")){ mapping.add(map); } } beginTag = null; } @Override public void endDocument() throws SAXException { //文档结束 } }
万事具备只欠东风!---->处理一下分发的类:
import java.io.IOException; import java.net.Socket; /** * 分发 * @author SNOOPY * */ public class Dispatch implements Runnable{ private Socket client; private Request request; private Response response; private int code = 200; public Dispatch(Socket client) { this.client = client; try { request = new Request(client.getInputStream()); response = new Response(client.getOutputStream()); } catch (IOException e) { //e.printStackTrace(); code = 500; return ; } } @Override public void run() { try{ String action = request.getAction(); Servlet servlet = WebApp.getServlet(action); if(servlet == null){ this.code = 404; response.pushToClient(code); return; } servlet.service(request, response); /*String method = request.getMethod(); if(method.equalsIgnoreCase("post")){ servlet.doPost(request, response); }else if(method.equalsIgnoreCase("get")){ servlet.doGet(request, response); }*/ } catch (Exception e) { e.printStackTrace(); code = 500; } response.pushToClient(code); try { client.close(); } catch (IOException e) { e.printStackTrace(); } } }
经过以上的分析以及代码的书写,一个简易的web服务器就这样诞生了!
各位看官可以动手尝试一下哦~~
当然代码有很多的地方还很值得去优化和修正,希望有心的大神能指正错误,我会及时修正!!
注意:此文仅适用于刚入门的同学,帮助理解服务器原理。
附:手写服务器包结构(最终整理)