简单服务器开发(九)Web服务器和WebApp解耦合

之前的开发步骤:简单服务器开发

  分析之前的代码,LoginServlet 类是 JavaWeb 程序员开发的,而 HandlerRequest 类是服务器开发人员开发的,在服务器中的代码关心了具体的 Servlet 类,显然服务器的程序和 JavaWeb 程序产生了依赖,具有高强度的耦合,实际上对于 Web 服务器来说,根本就不知道 Web 应用中有一个 LoginServlet 类,上面的程序中还使用了“new LoginServlet();”,这显然是错误的。

  另外在之前的 Web 服务器程序中编写了具体的请求路径/oa/login,这显然是不合理的,对于 Web 服务器来说浏览器客户端发送的请求是未知的。但是我们知道浏览器发送的请求路径/oa/login 是和底层 WebApp 中的 LoginServlet 存在映射关系/绑定关系的。而这种关系的指定必须由 WebApp 的开发人员来指定,我相信大家此时应该想到了“配置文件”,在配置文件中指定请求 URI 和对应要执行的 Servlet。该配置文件的编写由 WebApp程序员来完成,但是该文件由 Web 服务器开发人员读取并解析,所以该文件的名字、该文件的存放位置、该文件中所编写的配置元素都不能随意编写,必须提前制定好一个规范,那么这个规范由谁来制定呢?当然是由 SUN 公司来负责制定。

SUN 制定的配置文件规范
A、在配置文件 web.xml 中描述请求 URI 和对应的 Servlet 类之间的关系
B、web.xml 文件统一存放到 WebAppRoot/WEB-INF/ 目录下(WebAppRoot是app的根目录)
C、web.xml 文件中采用如下的标签描述请求 URI 和对应 Servlet 类之间的关系

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <servlet>
        <servlet-name>ServletName</servlet-name>
        <servlet-class>Servlet完整类名</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ServletName</servlet-name>
        <url-pattern>URL</url-pattern>
    </servlet-mapping>
</web-app> 

servlet-mapping:
如果url-pattern定义的是路径,那么以后所有对这个路径下资源的请求都会由servlet-name中定义的servlet处理;
如果url-pattern定义的是资源格式例如*.do等,那么对于所有符合这种格式的资源的请求都由指定的servlet处理。

(1)WebApp开发人员

A、在OA应用下创建WEB-INF文件夹,并且在该文件夹下创建web.xml
B、在org.zda.oa.servlet下创建LoginServlet类,处理登录信息

 

注意:一个web.xml中可能有多个servlet。本例中的web.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <servlet>
        <servlet-name>loginServlet</servlet-name>
        <servlet-class>org.zda.oa.servlet.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>loginServlet</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
    
    <servlet>
        <servlet-name>userSaveServlet</servlet-name>
        <servlet-class>org.bjpowernode.oa.servlet.UserSaveServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>userSaveServlet</servlet-name>
        <url-pattern>/user/save</url-pattern>
    </servlet-mapping>
</web-app>

 

 (2)Web服务器开发人员

A、服务器开发者负责解析web.xml文件
  a 、通过 Dom4j+xpath 解析 web.xml
B、在Web 服务器启动阶段解析web.xml文件

  web.xml 文件中主要配置了请求 URI 和对应的 Servlet 完整类名,请求 URI 更像一个 Map 集合的 key,对应的 Servlet 完整类名更像一个 Map 集合的 value,所以我们决定采用一个Map集合存储解析出来的数据。在浏览器发送请求的时候再去解析web.xml文件时有点晚了,所以我们决定在 Web 服务器启动阶段解析 web.xml 文件。

 

package com.zda.httpserver.core;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * 解析服务器中的web.xml配置文件
 * @author zda
 * @version    1.0
 * @since    1.0
 *
 */
public class WebParser {
    public static Map<String,Map<String,String>> servletMaps = new HashMap<String,Map<String,String>>();
    
    /**
     * 解析服务器所有应用的web.xml
     * @param webAppNames 服务器中所有应用的名称
     * @throws DocumentException 
     */
    public static void parser(String[] webAppNames) throws DocumentException{
        for(String webAppName:webAppNames){
            Map<String,String> servletMap = parser(webAppName);
            servletMaps.put(webAppName, servletMap);
        }
    }
    
    /**
     * 解析单个应用的web.xml配置文件
     * @param webAppName 应用名称
     * @return servletMap<String,String> 
     * @throws DocumentException 
     */
    private static Map<String,String> parser(String webAppName) throws DocumentException{
        //获取web.xml的路径
        String webPath = webAppName + "/WEB-INF/web.xml";
        //创建解析器
        SAXReader saxReader = new SAXReader();
        //通过解析器的read方法将配置文件读取到内存中,生成一个Document【org.dom4j】对象树
        Document document = saxReader.read(new File(webPath));
        
        //获取servlet节点元素: web-app -> servlet
        List<Element> servletNodes = document.selectNodes("/web-app/servlet");
        //创建一个servletInfoMap集合,将servlet-name和servlet-class的值分别当作key和value存放到该集合中
        Map<String,String> servletInfoMap = new HashMap<String,String>();
        //开始遍历servletNodes
        for(Element servletNode:servletNodes){
            //获取servlet-name节点元素对象
            Element servletNameElt = (Element) servletNode.selectSingleNode("servlet-name");
            //获取servletNameElt节点元素对象的值
            String servletName = servletNameElt.getStringValue();
            
            //获取servlet-class节点元素对象
            Element servletClassElt = (Element) servletNode.selectSingleNode("servlet-class");
            //获取servletClassElt节点元素对象的值
            String servletClassName = servletClassElt.getStringValue();
            
            //将servletName和servletClassName分别当作key和value存放到servletInfoMap集合中
            servletInfoMap.put(servletName, servletClassName);
        }
        
        //获取servlet-mapping节点元素对象: web-app -> servlet-mapping
        List<Element> servletMappingNodes = document.selectNodes("/web-app/servlet-mapping");
        //创建一个servletMappingInfoMap集合,将servlet-name和url-pattern节点元素对象的值分别当作key和value存放到该map集合中
        Map<String,String> servletMappingInfoMap = new HashMap<String,String>();
        //开始遍历servletMappingNodes
        for(Element servletMappingNode:servletMappingNodes){
            //获取servlet-name节点元素对象
            Element servletNameElt = (Element) servletMappingNode.selectSingleNode("servlet-name");
            //获取servletNameElt节点元素对象的值
            String servletName = servletNameElt.getStringValue();
            
            //获取url-pattern节点元素对象
            Element urlPatternElt = (Element) servletMappingNode.selectSingleNode("url-pattern");
            //获取urlPatternElt节点元素对象的值
            String urlPattern = urlPatternElt.getStringValue();
            
            //将servletName和urlPattern分别当作key和value存放到servletMappingInfoMap集合中
            servletMappingInfoMap.put(servletName, urlPattern);
        }
        
        //获取servletInfoMap或者servletMappingInfoMap任何一个key值的集合
        Set<String> servletNames = servletInfoMap.keySet();
        //创建一个servletMap集合,将servletMappingInfoMap的value和servletInfoMap的value分别当作key和value存放到该map集合中
        Map<String,String> servletMap = new HashMap<String,String>();
        //开始遍历servletNames
        for(String servletName:servletNames){
            //获取servletMappingInfoMap集合中的value:urlPattern
            String urlPattern = servletMappingInfoMap.get(servletName);
            //获取servletInfoMap集合中的value:servletClass
            String servletClassName = servletInfoMap.get(servletName);
            
            //将urlPattern和servletClassName分别当作key和value存放到servletMap集合中
            servletMap.put(urlPattern, servletClassName);
        }
        
        return servletMap;
    }
}

解析出来servletClassName,在HandlerRequest里反射出该业务处理类。但是,怎么调用里面的方法?

SUN公司制定了制订了Servlet接口,所有的WebApp的业务处理类必须实现此接口,由服务器开发人员调用

package javax.servlet;
/**
 * 由SUN公司制定的Servlet接口规范,该接口由web服务器开发人员来调用,由webApp开发人来实现
 * @author SUN公司
 * @version    1.0
 * @since    1.0
 *
 */
public interface Servlet {
    //处理业务的核心方法
    void service();
}

处理请求:

package com.zda.httpserver.core;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Map;

import javax.servlet.Servlet;

import com.zda.httpserver.util.Logger;

/**
 * 处理客户端请求
 * @author zda
 * @version 1.0
 * @since    1.0
 */
public class HandlerRequest implements Runnable {
    public Socket clientSocket;
    public HandlerRequest(Socket clientSocket){
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        //处理客户端请求
        BufferedReader br = null;
        PrintWriter out = null;
        Logger.log("httpserver thread: " + Thread.currentThread().getName());
        try {
            //接收客户端消息
            br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            //获取响应流对象
            out = new PrintWriter(clientSocket.getOutputStream());
            //获取请求协议的请求行
            String requestLine = br.readLine();// GET /oa/index.html HTTP/1.1
            //获取URI -> 请求行(requestLine) -> 请求方式  URI 请求协议版本号 -> 三者之间是通过一个空格进行连接
            String requestURI = requestLine.split(" ")[1];//{"GET","/oa/index.html","HTTP/1.1"}
            System.out.println(requestURI);
            //判断用户请求是否为一个静态页面:以.html或.htm结尾的文件叫作html页面
            if(requestURI.endsWith(".html") || requestURI.endsWith(".htm")){
                //处理静态页面的方法
                responseStaticPage(requestURI,out);
            }else{//动态资源:java程序,业务处理类
                //requestURI: /oa/login?username=zhangsan&password=111
                //requestURI: /oa/login
                
                String servletPath = requestURI;
                //判断servletPath是否包含参数
                if(servletPath.contains("?")){
                    servletPath = servletPath.split("[?]")[0];// /oa/login
                }
                    
                /*if("/oa/login".equals(servletPath)){
                    LoginServlet loginServlet = new LoginServlet();
                    loginServlet.service();
                }*/
                
                //获取应用的名称:oa 在 uri里:/oa/login
                String webAppName = servletPath.split("[/]")[1];
                //获取servletMaps集合中的value值->servletMap -> key:urlPattern value:servletClassName
                Map<String,String> servletMap = WebParser.servletMaps.get(webAppName);
                //获取servletMap集合中的key值 -> 存在于uri中/oa/login /oa/use/xxx/xxx/xxx/xxx
                String urlPattern = servletPath.substring(1 + webAppName.length());
                //获取servletClassName
                String servletClassName = servletMap.get(urlPattern);
                //判断该业务处理的Servlet类是否存在 
                if(servletClassName != null){
                    //通过反射机制创建该业务处理类
                    Class c = Class.forName(servletClassName);
                    Object obj = c.newInstance();
                    //这个时候,服务器开发人员不知道如何调用servlet业务处理类里的方法了?
                    //SUN站了出来,制订了Servlet接口,所有的WebApp的业务处理类必须实现此接口,由服务器开发人员调用
                    Servlet servlet = (Servlet)obj;
                    servlet.service();
                }else{//找不到该业务处理类:404
                    //404找不到资源
                    StringBuilder html = new StringBuilder();
                    html.append("HTTP/1.1 404 NotFound\n");
                    html.append("Content-Type:text/html;charset=utf-8\n\n");
                    html.append("<html>");
                    html.append("<head>");
                    html.append("<title>404-错误</title>");
                    html.append("<meta content='text/html;charset=utf-8'/>");
                    html.append("</head>");
                    html.append("<body>");
                    html.append("<center><font size='35px' color='red'>404-Not Found</font></center>");
                    html.append("</body>");
                    html.append("</html>");
                    out.print(html);
                }
                
            }
            //强制刷新
            out.flush();            
            
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            //关闭资源
            if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(clientSocket != null){
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        
    }

    /**
     * 处理静态页面
     * @param requestURI 请求URI
     * @param out    响应流对象
     */
    public void responseStaticPage(String requestURI, PrintWriter out) {
        //requestURI:/oa/index.html
        //静态页面的路径:oa/index.html
        String htmlPath = requestURI.substring(1);
        BufferedReader br = null;
        try {
            //读取页面
            br = new BufferedReader(new FileReader(htmlPath));
            StringBuilder html = new StringBuilder();
            //拼接响应信息
            html.append("HTTP/1.1 200 OK\n");
            html.append("Content-Type:text/html;charset=utf-8\n\n");
            String temp = null;
            while((temp = br.readLine()) != null){
                html.append(temp);
            }
            //输出html
            out.print(html);
        } catch (FileNotFoundException e) {
            //404找不到资源
            StringBuilder html = new StringBuilder();
            html.append("HTTP/1.1 404 NotFound\n");
            html.append("Content-Type:text/html;charset=utf-8\n\n");
            html.append("<html>");
            html.append("<head>");
            html.append("<title>404-错误</title>");
            html.append("<meta content='text/html;charset=utf-8'/>");
            html.append("</head>");
            html.append("<body>");
            html.append("<center><font size='35px' color='red'>404-Not Found</font></center>");
            html.append("</body>");
            html.append("</html>");
            out.print(html);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

在服务器启动时解析服务器中包含的web.xml文件

package com.zda.httpserver.core;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import com.zda.httpserver.util.Logger;

/**
 * httpserver程序主入口
 * @author zda
 * @version    1.0
 * @since    1.0
 *
 */
public class BootStrap {

    /**
     * 主程序
     * @param args
     */
    public static void main(String[] args) {
        //程序入口
        start();
    }

    /**
     * 主程序入口
     */
    public static void start() {
        ServerSocket serverSocket = null;
        Socket clientSocket = null;
        Thread.currentThread().setName("MAIN");
        try {
            Logger.log("httpserver start");
            //获取当前时间
            long start = System.currentTimeMillis();
            //解析服务中包含的web.xml配置文件
            WebParser.parser(new String[] {"oa"});
            //获取系统端口号
            int port = ServerParser.getPort();
            Logger.log("httpserver-port: " + port);
            //创建服务器套接字,并且绑定端口号:8080
            serverSocket = new ServerSocket(port);
            //获取结束时间
            long end = System.currentTimeMillis();
            Logger.log("httpserver started: " + (end-start) + " ms");
            while(true){
                //开始监听网络,此时程序处于等待状态,等待接收客户端的消息
                clientSocket = serverSocket.accept();
                /*//接收客户端消息
                br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                String temp = null;
                while((temp = br.readLine()) != null){
                    System.out.println(temp);
                }*/
                new Thread(new HandlerRequest(clientSocket)).start();
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            //关闭资源
            /*if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(clientSocket != null){
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }*/
            if(serverSocket != null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

}

 

posted @ 2020-02-15 13:03  摘珰  阅读(423)  评论(0编辑  收藏  举报