• 手写简易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("
    ");
                    }
                    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 = "
    ";
            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("
    ");
                    }
                    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 = "
    ";
            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 = "
    ";
            //接收请求
            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服务器就这样诞生了!
      各位看官可以动手尝试一下哦~~
      当然代码有很多的地方还很值得去优化和修正,希望有心的大神能指正错误,我会及时修正!!

        注意:此文仅适用于刚入门的同学,帮助理解服务器原理。

    附:手写服务器包结构(最终整理)

     

  • 相关阅读:
    戏说程序猿之荒唐的需求
    戏说程序猿之过年--二叔,我真不会修电脑
    深入理解设计模式(17):迭代器模式
    深入理解设计模式(16):备忘录模式
    我的2018
    Java高级篇(一)——线程
    Java进阶篇(六)——Swing程序设计(下)
    Java进阶篇(六)——Swing程序设计(上)
    Java进阶篇(五)——Java的I/O技术
    Java进阶篇(四)——Java异常处理
  • 原文地址:https://www.cnblogs.com/snoopylovefiona/p/4730242.html
Copyright © 2020-2023  润新知