模拟tomcat webserver

1. com.webserver.core

/**
 * WebServer是模拟Tomcat的一个web容器
 * web容器可以同时管理多个网络应用,并且提供了与客户端(通常是浏览器)的网络连接以及传输数据
 * 和与客户端的应用层交互(涉及到TCP协议以及HTTP协议)上面的支持。有了web容器,使得程序员更多
 * 的精力是放在具体Web应用的业务上。
 * 
 * webapp(网络应用):它包含的内容大致有网页,图片,其他静态素材以及java程序代码,就是
 * 我们上网时俗称的一个"网站"的全部内容。
 * 
 * TCP协议,处于传输层,负责两台计算机之间通过网络传输数据的协议。
 * 
 * HTTP协议,处于应用层,规定了双方发送数据的格式,以及交互规则。
 */
public class WebServer {
    private ServerSocket server;
    private ExecutorService threadPool;
    /**
     * 初始化服务端
     */
    public WebServer() {
        try {
            System.out.println("正在启动服务端...");
            server = new ServerSocket(8088);
            threadPool = Executors.newFixedThreadPool(50);
            System.out.println("服务端启动完毕!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 服务端开始工作的方法
     */
    public void start() {
        try {
            while(true) {
                System.out.println("等待客户端连接...");
                Socket socket = server.accept();
                System.out.println("一个客户端连接了!");            
                //启动一个线程处理该客户端交互
                ClientHandler handler = new ClientHandler(socket);                
                threadPool.execute(handler);            
            }            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }    
    public static void main(String[] args) {
        WebServer server = new WebServer();
        server.start();
    }
}
/**
 * 该线程负责处理与指定客户端的交互工作
 * 处理过程分为三步:
 * 1:准备工作
 * 2:处理请求
 * 3:发送响应
 */
public class ClientHandler implements Runnable{
    private Socket socket;
    public ClientHandler(Socket socket) {
        this.socket = socket;
    }
    public void run() {
        try {
            //1 准备工作
            HttpRequest request = new HttpRequest(socket);
            HttpResponse response = new HttpResponse(socket);
            /*
             * 2 处理请求
             * 2.1:通过request获取客户端请求的资源对应的抽象路径requestURI的值
             * 2.2:从webapps目录中通过对应的抽象路径寻找该资源    
             */
            String path = request.getRequestURI();
            System.out.println("path:"+path);    
            //首先判断该请求是否请求为业务
            HttpServlet servlet = ServerContext.getServlet(path);
            if(servlet!=null) {
                //处理业务
                servlet.service(request,response);                        
            }else {            
                File file = new File("./webapps"+path);
                if(file.exists()) {
                    System.out.println("该资源已找到!");                    
                    response.setEntity(file);        
                    System.out.println("响应客户端完毕!");
                }else {
                    System.out.println("该资源不存在!");                    
                    File notFound = new File("./webapps/root/404.html");
                    //设置状态代码与描述
                    response.setStatusCode(404);
                    response.setStatusReason("NOT FOUND");                    
                    //设置正文文件为404页面
                    response.setEntity(notFound);                                    
                }
            }        
            //3响应客户端
            response.flush();
        } catch(EmptyRequestException e) {
            //单独捕获空请求,不做任何处理。
            System.out.println("空请求...");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            /*
             * 本次响应完毕后与客户端断开连接这个操作是HTTP1.0的方式。
             * 1.1允许建立连接后进行多次请求响应,但是需要额外的消息头和响应头的处理
             */
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }    
        }
    }
}
/**
 * 这里保存所有服务端相关的配置信息
 */
public class ServerContext {
    private static Map<String,HttpServlet> servletMapping = new HashMap<>();    
    static {
        initServletMapping();
    }
    /**
     * 初始化请求路径与对应Servlet的关系
     */
    private static void initServletMapping(){
//        servletMapping.put("/myweb/reg", new RegServlet());
//        servletMapping.put("/myweb/login", new LoginServlet());
        /*
         * 解析conf/servlets.xml文件
         * 将根标签下所有的<servlet>标签获取到并且将每个<servlet>标签中的属性:
         * path的值作为key
         * className的值利用反射加载对应的类并实例化,将实例化的对象造型为
         * HttpServlet并作为value存入到servletMapping完成初始化。
         */
        try {
            SAXReader reader = new SAXReader();
            Document doc = reader.read(new File("./conf/servlets.xml"));
            Element root = doc.getRootElement();
            List<Element> list = root.elements();
            for(Element servletEle : list) {
                String path = servletEle.attributeValue("path");                
                String className = servletEle.attributeValue("className");
                Class cls = Class.forName(className);
                HttpServlet servlet = (HttpServlet)cls.newInstance();                
                servletMapping.put(path, servlet);
            }        
        } catch (Exception e) {
            e.printStackTrace();
        }        
        System.out.println(servletMapping.size());
    } 
    /**
     * 根据给定的请求路径获取对应的Servlet
     * @param path
     * @return
     */
    public static HttpServlet getServlet(String path) {
        return servletMapping.get(path);
    }
}
/**
 * 将user.dat文件中的用户信息显示到控制台
 */
public class ShowAllUserDemo {
    public static void main(String[] args) throws IOException {
        RandomAccessFile raf = new RandomAccessFile("user.dat","r");        
        for(int i=0;i<raf.length()/100;i++) {
            byte[] data = new byte[32];
            raf.read(data);
            String username = new String(data,"UTF-8").trim();            
            raf.read(data);
            String password = new String(data,"UTF-8").trim();            
            raf.read(data);
            String nickname = new String(data,"UTF-8").trim();            
            int age = raf.readInt();            
            System.out.println(username+","+password+","+nickname+","+age);
            System.out.println("pos:"+raf.getFilePointer());
        }        
        raf.close();
    }
}

2.com.webserver.exception

/**
 * 空请求异常
 * 当HttpRequest解析请求时发现此请求为空请求时抛出该异常。
 */
public class EmptyRequestException extends Exception{
    private static final long serialVersionUID = 1L;
    public EmptyRequestException() {
        super();
    }
    public EmptyRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
    public EmptyRequestException(String message, Throwable cause) {
        super(message, cause);
    }
    public EmptyRequestException(String message) {
        super(message);
    }
    public EmptyRequestException(Throwable cause) {
        super(cause);
    }
}

3.com.webserver.http

/**
 * HTTP协议规定之内容
 */
public class HttpContext {
    private static Map<String,String> mimeMapping = new HashMap<>();
    static {
        //初始化所有静态属性
        initMimeMapping();
    }
    /**
     * 初始化资源后缀与Content-Type对应值
     */
    private static void initMimeMapping() {
//        mimeMapping.put("html", "text/html");
//        mimeMapping.put("css", "text/css");
//        mimeMapping.put("js", "application/javascript");
//        mimeMapping.put("png", "image/png");
//        mimeMapping.put("jpg", "image/jpeg");
//        mimeMapping.put("gif", "image/gif");
        /*
         * 解析conf/web.xml文件
         * 将根标签下所有名为<mime-mapping>的子标签获取出来,并将它下面的:
         * <extension>标签中的文本作为key
         * <mime-type>标签中的文本作为value
         * 初始化mimeMapping这个Map。 
         * 初始化完毕后,mimeMapping这个Map中应当有1000多个元素
         */
        try {
            SAXReader reader = new SAXReader();
            Document doc = reader.read(new File("conf/web.xml"));
            Element root = doc.getRootElement();
            List<Element> list = root.elements("mime-mapping");
            for(Element e : list) {
                String key = e.elementTextTrim("extension");
                String value = e.elementTextTrim("mime-type");
                mimeMapping.put(key, value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }        
        System.out.println(mimeMapping.size());
    }    
    /**
     * 根据给定的资源后缀名获取对应的Content-Type的值
     */
    public static String getMimeType(String ext) {
        return mimeMapping.get(ext);
    }
    public static void main(String[] args) {
        String type = getMimeType("js");
        System.out.println(type);
    }
}
/**
 * 请求对象
 * 该类的每一个实例用于表示客户端发送过来的一个具体的请求内容
 * 一个请求由三部分组成:请求行,消息头,消息正文
 */
public class HttpRequest {
    //请求行相关信息
    //请求的方式
    private String method;
    //抽象路径
    private String uri;
    //协议版本
    private String protocol;
    //uri当中请求部分(?左侧内容)
    private String requestURI;
    //uri当中参数部分(?右侧内容)
    private String queryString;
    //记录参数部分中的具体每一个参数
    private Map<String,String> parameters = new HashMap<>();
    //消息头相关信息
    /*
     * key:消息头的名字
     * value:消息头对应的值
     */
    private Map<String,String> headers = new HashMap<>();
    //消息正文相关信息
        
    //和连接相关的属性
    private Socket socket;
    private InputStream in;
    /**
     * 构造方法,用于初始化请求对象
     * @throws EmptyRequestException 
     */
    public HttpRequest(Socket socket) throws EmptyRequestException {
        try {
            this.socket = socket;
            this.in = socket.getInputStream();
            /*
             * 解析请求分为三步:
             * 1:解析请求行
             * 2:解析消息头
             * 3:解析消息正文
             */
            System.out.println("HttpRequest:开始解析请求...");
            parseRequestLine();
            parseHeaders();
            parseContent();
            System.out.println("HttpRequest:解析请求完毕!");    
        } catch(EmptyRequestException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 解析请求行
     * @throws EmptyRequestException 
     */
    private void parseRequestLine() throws EmptyRequestException {
        System.out.println("HttpRequest:开始解析请求行");
        try {
            /*
             * 通过输入流读取第一行字符串。
             * 一个请求中的第一行字符串就是请求行的内容。
             * 读取到以后,按照" "(空格)拆分为三部分然后将这三部分内容分别设置到请求行对应
             * 的属性method,uri,protocol上
             * http://localhost:8088/index.html
             */
            String line = readLine();
            //判断该请求是否为空请求
            if("".equals(line)) {
                throw new EmptyRequestException();
            }        
            String[] data = line.split("\\s");
            method = data[0];
            uri = data[1];
            protocol = data[2];            
            parseURI();//进一步解析uri            
            System.out.println("method:"+method);// GET
            System.out.println("uri:"+uri);// /index.html
            System.out.println("protocol:"+protocol);// HTTP/1.1        
        } catch(EmptyRequestException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("HttpRequest:解析请求行完毕");
    }
    /**
     * 进一步解析uri
     */
    private void parseURI() {
        System.out.println("HttpRequest:进一步解析uri...");
        /*
         * 首先在解析之前,要先判别当前uri是否含有参数,判断的依据是看uri当中
         * 是否含有"?",有则说明含有参数,否则就是没有参数。
         * 如果没有参数,那么直接将uri的值赋值给requestURI即可。
         * 
         * 若有参数,则应当先按照"?"将uri拆分然后将"?"左侧内容赋值给requestURI,
         * 将"?"右侧内容赋值给queryString,接着将参数部分再按照"&"拆分为每一组参
         * 数,每组参数再按照"="拆分为参数名与参数值,并将名字作为key,值作为value
         * 保存到parameters这个Map中完成解析 
         * /myweb/reg?username=%E8%8C%8CXXXXXX
         */
        if(uri.indexOf("?")!=-1) {
            String[] data = uri.split("\\?");
            requestURI = data[0];
            if(data.length>1) {
                queryString = data[1];
                //对queryString中包含的%XX进行转码
                try {
                    queryString = URLDecoder.decode(queryString, "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }            
                parseParameters(queryString);            
            }
        }else {
            requestURI = uri;
        }
        System.out.println("requestURI:"+requestURI);
        System.out.println("queryString:"+queryString);
        System.out.println("parameters:"+parameters);
        System.out.println("HttpRequest:解析uri完毕!");
    }
    /**
     * 解析参数,参数是一个字符串.
     * 格式为:name=value&name=value...
     * 解析出来的参数会放在parameters这个Map中
     */
    private void parseParameters(String line) {
        String[] data = line.split("&");
        for(String str : data) {
            String[] para = str.split("=");
            if(para.length>1) {
                parameters.put(para[0], para[1]);
            }else {
                parameters.put(para[0], null);
            }
        }
    }    
    /**
     * 解析消息头
     */
    private void parseHeaders() {
        System.out.println("HttpRequest:开始解析消息头");
        try {
            /*
             * 循环调用readLine方法读取每一行字符串,每一行就是一个消息头,
             * 如果readLine方法返回的字符串是一个空字符串时就应当停止循环读取
             * 操作了(因为单独读取到了CRLF)
             * 
             * 读取到每一个消息头后,我们可以按照": "(即:冒号空格)来进行拆分,将
             * 消息头的名字作为key,消息头的值作为value保存到headers这个Map中完成消息
             * 头的解析工作
             */
            while(true) {
                String line = readLine();
                if("".equals(line)) {
                    break;
                }
                String[] data = line.split(":\\s");
                headers.put(data[0], data[1]);
            }        
            System.out.println("headers:"+headers);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("HttpRequest:解析消息头完毕");
    }
    /**
     * 解析消息正文
     */
    private void parseContent() {
        System.out.println("HttpRequest:开始解析消息正文");
        /*
         * 根据消息头中的Content-Length来判定
         * 当前请求是否含有消息正文
         */
        if(headers.containsKey("Content-Length")) {
            //首先得到正文的长度(字节量)
            int length = Integer.parseInt(
                headers.get("Content-Length")    
            );
            try {
                //读取正文中所有的字节
                byte[] data = new byte[length];
                in.read(data);                
                //根据Content-Type指定的正文类型来处理
                String type = headers.get("Content-Type");            
                //是否为页面form表单提交的用户输入的信息
                if("application/x-www-form-urlencoded".equals(type)) {
                    /*
                     * 正文内容就是一个字符串,原GET请求中
                     * url里"?"右侧内容
                     */
                    String line = new String(data,"ISO8859-1");                    
                    line = URLDecoder.decode(line,"UTF-8");                    
                    parseParameters(line);                
                //将来可以再添加分支判断其他类型    
                }    
            } catch (Exception e) {
                e.printStackTrace();
            }            
        }    
        System.out.println("HttpRequest:解析消息正文完毕");
    }    
    private String readLine() throws IOException {
        int d = -1;
        char c1='a',c2='a';
        StringBuilder builder = new StringBuilder();
        while((d = in.read())!=-1) {
            c2 = (char)d;
            if(c1==13&&c2==10) {
                break;
            }
            builder.append(c2);
            c1 = c2;
        }
        return builder.toString().trim();
    }
    public String getMethod() {
        return method;
    }
    public String getUri() {
        return uri;
    }
    public String getProtocol() {
        return protocol;
    }
    public String getHeader(String name) {
        return headers.get(name);
    }
    public String getRequestURI() {
        return requestURI;
    }
    public String getQueryString() {
        return queryString;
    }
    /**
     * 根据参数名获取参数值
     * @param name
     * @return
     */
    public String getParameter(String name) {
        return parameters.get(name);
    }
}
/**
 * 响应对象
 * 该类的每一个实例用于表示发送给客户端的一个具体的响应内容。
 * 每个响应包含三部分内容:
 * 状态行,响应头,响应正文
 */
public class HttpResponse {
    //状态行相关信息
    private int statusCode = 200;
    private String statusReason = "OK";    
    //响应头相关信息
    private Map<String,String> headers = new HashMap<>();    
    //响应正文相关信息
    //响应正文对应的实体文件
    private File entity;    
    //与连接相关的信息
    private Socket socket;
    private OutputStream out;
    public HttpResponse(Socket socket) {
        try {
            this.socket = socket;
            this.out = socket.getOutputStream();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }    
    /**
     * 将当前响应对象内容以一个标准的响应格式发送给客户端
     */
    public void flush() {
        /*
         * 1发送状态行
         * 2发送响应头
         * 3发送响应正文
         */
        sendStatusLine();
        sendHeaders();
        sendContent();
    }
    /**
     * 发送状态行
     */
    private void sendStatusLine() {
        System.out.println("HttpResponse:开始发送状态行...");
        try {
            //发送状态行
            String line = "HTTP/1.1"+" "+statusCode+" "+statusReason;
            System.out.println("状态行:"+line);
            out.write(line.getBytes("ISO8859-1"));
            out.write(13);//written CR
            out.write(10);//written LF
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("HttpResponse:状态行发送完毕!");
    }
    /**
     * 发送响应头
     */
    private void sendHeaders() {
        System.out.println("HttpResponse:开始发送响应头...");
        try {
            /*
             * 遍历headers,将所有响应头发送
             */
            Set<Entry<String,String>> entrySet 
                                = headers.entrySet();
            for(Entry<String,String> header : entrySet) {
                String name = header.getKey();
                String value = header.getValue();
                String line = name+": "+value;
                System.out.println("响应头:"+line);
                out.write(line.getBytes("ISO8859-1"));
                out.write(13);
                out.write(10);
            }        
            //单独发送CRLF表示响应头发送完毕
            out.write(13);//written CR
            out.write(10);//written LF
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("HttpResponse:响应头发送完毕!");
    }
    /**
     * 发送响应正文
     */
    private void sendContent() {
        System.out.println("HttpResponse:开始发送响应正文...");
        if(entity!=null) {
            try(
                FileInputStream fis 
                    = new FileInputStream(entity);
            ){
                //发送响应正文
                int len = -1;
                byte[] data = new byte[1024*10];
                while((len = fis.read(data))!=-1) {
                    out.write(data,0,len);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("HttpResponse:响应正文发送完毕!");
    }

    public File getEntity() {
        return entity;
    }
    /**
     * 设置响应正文实体文件
     * 设置的同时会自动添加两个响应头:
     * Content-Type与Content-Length
     */
    public void setEntity(File entity) {
        this.entity = entity;
        String fileName = entity.getName();
        System.out.println("资源名:"+fileName);
        //获取该资源后缀名
        String ext = fileName.substring(
            fileName.lastIndexOf(".")+1
        ).toLowerCase();
        System.out.println("资源后缀名:"+ext);    
        String type = HttpContext.getMimeType(ext);        
        putHeader("Content-Type", type);
        putHeader("Content-Length", entity.length()+"");        
    }
    public int getStatusCode() {
        return statusCode;
    }
    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }
    public String getStatusReason() {
        return statusReason;
    }
    public void setStatusReason(String statusReason) {
        this.statusReason = statusReason;
    }
    /**
     * 向当前响应对象中添加新的响应头
     * @param name 响应头的名字
     * @param value 响应头的值
     */
    public void putHeader(String name,String value) {
        this.headers.put(name, value);
    }
}

4 com.webserver.servlet

/**
 * 所有Servlet的超类
 */
public abstract class HttpServlet {
    public abstract void service(HttpRequest request,HttpResponse response);
    /**
     * 设置response跳转指定的页面
     * @param path
     * @param request
     * @param response
     */
    public void forward(String path,HttpRequest request,HttpResponse response) {
        File file = new File("./webapps"+path);
        response.setEntity(file);
    }
}
/**
 * 登录业务
 */
public class LoginServlet extends HttpServlet{
    public void service(HttpRequest request,HttpResponse response) {
        //1获取登录信息
        String username = request.getParameter("username");
        String password = request.getParameter("password");    
        //2验证登录
        try (
            RandomAccessFile raf = new RandomAccessFile("user.dat","r");
        ){
            for(int i=0;i<raf.length()/100;i++) {
                raf.seek(i*100);
                //读取用户名
                byte[] data = new byte[32];
                raf.read(data);
                String name = new String(data,"UTF-8").trim();
                if(name.equals(username)) {
                    //比密码
                    raf.read(data);
                    String pwd = new String(data,"UTF-8").trim();
                    if(pwd.equals(password)) {
                        //登录成功
                        forward("/myweb/login_success.html", request, response);
                        return;
                    }
                    break;
                }
            }//for循环结束            
            //如果走到这里,统一设置登录失败
            forward("/myweb/login_fail.html",request,response);            
        } catch (Exception e) {
            e.printStackTrace();
        }    
    }
}
/**
 * 用于处理注册业务
 */
public class RegServlet extends HttpServlet{
    public void service(HttpRequest request,HttpResponse response) {
        System.out.println("RegServlet:开始处理注册...");
        /*
         * 1:通过request获取用户提交的注册信息
         * 2:将信息写入user.dat文件保存
         * 3:设置response响应注册结果页面
         */
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String nickname = request.getParameter("nickname");
        int age = Integer.parseInt(request.getParameter("age"));
        System.out.println("username:"+username);
        System.out.println("password:"+password);
        System.out.println("nickname:"+nickname);
        System.out.println("age:"+age);    
        /*
         * 每个用户占用100字节
         * 其中用户名,密码,昵称为字符串各32字节
         * 年龄是int值固定的4字节。
         */
        try(
            RandomAccessFile raf = new RandomAccessFile("user.dat","rw");    
        ) {
            //要先检查该用户是否已经存在了
            for(int i=0;i<raf.length()/100;i++) {
                raf.seek(i*100);
                byte[] data = new byte[32];
                raf.read(data);
                String name = new String(data,"UTF-8").trim();
                if(name.equals(username)) {
                    //重复用户
                    forward("/myweb/have_user.html", request, response);
                    return;
                }
            }        
            raf.seek(raf.length());        
            //写入用户名
            byte[] data = username.getBytes("UTF-8");
            //扩容数组到32字节
            data = Arrays.copyOf(data, 32);
            raf.write(data);            
            //写密码
            data = password.getBytes("UTF-8");
            data = Arrays.copyOf(data, 32);
            raf.write(data);            
            //写昵称
            data = nickname.getBytes("UTF-8");
            data = Arrays.copyOf(data, 32);
            raf.write(data);            
            //写年龄
            raf.writeInt(age);    
            //响应注册成功的页面给客户端
            forward("/myweb/reg_success.html", request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }    
        System.out.println("RegServlet:处理注册完毕!");
    }
}

sevlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<servlets>
    <servlet path="/myweb/reg" className="com.webserver.servlet.RegServlet" />
    <servlet path="/myweb/login" className="com.webserver.servlet.LoginServlet" />
</servlets>

 

猜你喜欢

转载自www.cnblogs.com/hello4world/p/12169332.html