IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> httpServer-SUN-WebAppServer桥梁篇手写服务器 -> 正文阅读

[开发测试]httpServer-SUN-WebAppServer桥梁篇手写服务器

目录

B/S架构及C/S架构:

C/S架构:

Client/Server架构,即服务器/客户端架构。是大家熟知的软件系统体系结构,通过将任务合理分配到Client端和Server端,降低了系统的通讯开销,需要安装客户端才可进行管理操作。
常见的C/S架构有:QQ、百度网盘等。

优点:

(1)界面和操作可以很丰富。
(2)大部分数据保存在客户端、相对安全。
(3)大部分功能集成在客户端,只需从服务器下载少量数据,因此访问速度很快。

缺点:

(1)升级维护工作量较大,每一个客户端都需要升级。
(2)用户群固定,由于程序需要安装才可以使用个,因此不适合面向一些不可知的用户。

B/S架构原理剖析

首先何为B/S架构:B/S架构全称为Browser/Server,即浏览器/服务器结构。Browser指的是web浏览器,极少数业务逻辑在前端实现,主要的业务逻辑在服务器端实现。Browser客户端-WebApp服务器端和DB端构成了所谓的三层架构。B/S架构的系统无需特别安装,只有Web浏览器即可。B/S架构中,现实逻辑交给Web浏览器,业务处理逻辑放在了WebApp上,这样就避免了庞大的胖客户端,减少了客户端的压力。因为客户端包含的逻辑很少,因此也被称为瘦客户端。

优点:

(1)客户端无需安装,有Web浏览器即可。
(2)B/S架构可以放在广域网上,通过一定的权限控制实现多客户访问的目的,交互性较强。
(3)B/S架构无需升级多个客户端,升级服务器即可。

缺点:

(1)在跨浏览器上,B/S架构不尽如人意。
(2)表现要达到C/S架构的程度需要花费不少精力。
(3)在速度安全性上需要花费巨大的设计成本,这是B/S架构最大的问题。

接口与协议的作用:

接口和协议可以理解为一种行业的标准或者规范,只有提前制定好规范和标准,将来各个部件或组件才可以做到无缝对接,接口的存在使整个系统变的具有很强的可接插特性,使整个系统各个组件之间达到松耦合,具备很强的高扩展力。Java 语言连接数据库的 API 被称为 JDBC 接口,该接口是 SUN 公司负责制定的,连接数据库的 Java程序员只需要面向 JDBC接口调用相关的方法即可完成底层数据库 CRUD 操作,该规范的出现让操作数据库的 Java 程序和底层具体的数据库产品解耦合,Java程序无须依赖底层具体的数据库产品,可以真正做到“一套 Java程序”连接并操作各种不同类型的数据库系统。再例如“不同版本的浏览器”都可以访问“百度网站”,并且最终在浏览器中显示的百度页面都是相同的,这说明“浏览器软件”和“百度服务器软件”之间是解耦合的,它们之所以可以做到解耦合就是因为这两个软件在互相传递数据的时候必然是遵守某种传送标准的,这种标准就是 W3C 制定的 HTTP 协议(超文本传输协议)。

总的来说,面向接口编程可以解耦合。

B/S架构原理1:

在这里插入图片描述

B/S架构的系统参与者:

(1)Web客户端Browser
(2)Web服务器端Server
(3)WebApp
(4)数据库服务器

B/S架构的系统中参与者之间的接口与协议:

第一:Web 客户端 Browser 和 Web 服务器端 Server 之间是 W3C 制定的 HTTP 协议。
第二:Web 服务器和 Web 应用之间是 SUN 制定的 Servlet 接口。
第三:Web 应用中的小 java 程序和数据库服务器之间是 SUN 制定的 JDBC 接口。

B/S架构原理2:

在这里插入图片描述
我们要做的就是重新制定Servlet接口(虽然SUN已经制定Servlet接口,但是为了更好理解尝试重新制定)、后进行Web服务器开发当我们的服务器研发成功后,可以开发一个web应用对该服务器进行测试。

HTTP超文本传输协议

什么是协议?

计算机 A 和计算机 B 之间在传送数据的时候,提前制定好的一种数据传送格式,计算机 A 向计算机B发送一个数据包,计算机 B 必须提前知道该数据包的数据格式,才可以成功的将该数据包中有价值的数据解析出来。
例如:小张和小王聊天,小张说:“你吃饭了吗?”,小王说:“吃了”。可见小张和小王可以正常
沟通交流,他们为什么可以正常沟通交流呢?因为小张和小王都遵守同一套协议,该协议就是中国普
通话协议,其实我们从幼儿园就开始学习这套协议了。

什么是HTTP协议?

HTTP 协议是一种超文本传输协议,超文本表示不仅可以传送普通的文本,还可以传送一些二进制数据,例如:图片、声音、视频、流媒体等数据。
HTTP 协议是 W3C(万维网联盟组织)制定的,该协议规定了浏览器软件和 web 服务器软件之间在传送数据的时候采用什么样的格式。这样就可以做到不同类型的浏览器访问不同类型的 Web 服务
器。浏览器和 Web 服务器之间解耦合。
浏览器 Browser 向 Web 服务器发送数据,称为请求 request,Web 服务器向浏览器发送数据,称为响应 response

HTTP协议初步

请求协议包括的主要部分
A、请求行
B、消息报头
C、空白行
D、请求体

请求协议的详细内容:
在这里插入图片描述
响应协议包括的主要部分
A、状态行
B、 响应报头
C、空白行
D、响应体

响应协议详细内容
在这里插入图片描述

Socket编程

什么是Socket编程?

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个 Socket
Socket 的英文原义是“孔”或“插座”。通常也称作"套接字",用于描述 IP 地址和端口,可以用来实现不同计算机之间的通信。在 Internet 上的主机一般运行了多个服务软件,同时提供几种服务。
每种服务都打开一个 Socket并绑定到一个端口上,不同的端口对应于不同的服务。Socket 正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供 220 伏交流电, 有的提供 110 伏交流电,有的则提供有线电视节目。客户软件将插头插到不同编号的插座,就可以得到不同的服务


连接过程

根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:
服务器监听,客户端请求,连接确认

第一步:服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状
态,实时监控网络状态。
第二步:客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。
为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址端口
号,然后就向服务器端套接字提出连接请求
第三步:连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就
响应客户端套接字的请求,建立一个新的线程把服务器端套接字的描述发给客户端,一旦客户端确
认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态继续接收其他客户端套接字的
连接请求


Java中如何实现Socket编程:

JavaSE 中提供了实现 Socket 编程的 API,让网络编程变的更简单,更加面向对象。要实现两台计算
机(两个服务)之间的通讯,至少编写以下的代码:

服务器端:

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

public class MyServerSocket {
    public static void main(String[] args) {
        ServerSocket serverSocket=null;
        Socket clientSocket=null;
        BufferedReader br=null;
        //为了结束时关闭资源,放在try-catch外边
        try {//创建服务器套接字,并绑定端口号:8080
            serverSocket=new ServerSocket(8080);
            //开始监听网络,此时程序处理等待状态,接受客户端消息。
            clientSocket=serverSocket.accept();
            //接受客户端套接字
            br=new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            String temp=null;
            //打印消息
            while((temp=br.readLine())!=null){
                System.out.println(temp);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(br!=null){
                try{
                    br.close();
                }catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(clientSocket!=null){
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

客户端:

import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class MyClientSocket {
    public static void main(String[] args) {
        Socket clientSocket=null;
        PrintWriter out=null;
        try {
            //创建客户端套接字
            clientSocket = new Socket("localhost", 8080);
            //创建输入流对象
            out=new PrintWriter(clientSocket.getOutputStream());
            //消息
            String msg="Hello world!";
            //传送消息
            out.print(msg);
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            //关闭资源
            if(out!=null){
                out.close();
            }
            if(clientSocket!=null){
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

运行时先启动服务器端程序,在启动客户端程序,可将客户端内容输出至服务器端。

在这里插入图片描述
在自己的浏览器中输入:http://localhost:8080,
服务器端会输出自己浏览器的request:
在这里插入图片描述

软件测试

软件测试技术是软件开发过程中的一个重要组成部分,是贯穿整个软件开发生命周期、对软件产品(包括阶段性产品)进行验证和确认的活动过程,其目的是尽快尽早地发现在软件产品中所存在的
各种问题-与用户需求、预先定义的不一致性。检查软件产品的 bug。写成测试报告,交于开发人员修

软件测试技术包括哪些:

A、黑盒测试

黑盒测试也称功能测试,它是通过测试来检测每个功能是否都能正常使用。在测试中,把程序看
作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序接口进行测试,
它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生
正确的输出信息。黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和软件功
能进行测试。黑盒测试是以用户的角度,从输入数据与输出数据的对应关系出发进行测试的。

B、 白盒测试

软件的白盒测试是对软件的过程性细节做细致的检查。这种方法是把测试对象看做一个打开的盒
子,它允许测试人员利用程序内部的逻辑结构及有关信息,设计或选择测试用例,对程序所有逻辑路
径进行测试。通过在不同点检查程序状态,确定实际状态是否与预期的状态一致。因此白盒测试又称
为结构测试或逻辑驱动测试。白盒测试一般有专门的白盒测试人员测试。

C、 单元测试

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元
的含义,一般来说,要根据实际情况去判定其具体含义,如 Java 里单元指一个类。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。单元测试一般由程序员自己完成。并且测试完成之后需要提交单元测试报告。

D、压力测试

压力测试属于性能测试的一部分,测试系统的最大负载情况。 E、 其它测试
易用性测试
安全性测试
……

如何做单元测试?

手动单元测试

Java 的单元测试有专门的开源项目,例如:JUnit,
首先需要到 JUnit 官网下载 JUnit 相关 jar 包,我们这里使用 JUnit4-4.12,JUnit 官网地址:http://junit.org/junit4/
JUnit-4.12 下载完成后存放到本地硬盘中
在这里插入图片描述
打开IDEA,并点击
在这里插入图片描述
在这里插入图片描述
将文件添加进来即可。

使用IDEA做单元测试,可专门为测试代码创建测试文件夹,具体方法为Project Structure->Project Settings->Modules->Mark as ->Tests即可。
在这里插入图片描述
在这里插入图片描述
进行单元测试时,具体方法如下:
源代码:

package com.sh.junit;

public class MyMath {
    public static int sum(int a,int b){
        return a+b;
    }
    public static int divide(int a,int b){
        return a/b;
    }
}

测试代码:

import com.sh.junit.MyMath;
import org.junit.Test;
import static org.junit.Assert.*;
public class MyMathTest {
    @Test
    public void testSum(){
        int actual=MyMath.sum(10,10);//实际值
        int expected=20;//期望值
        assertEquals(expected,actual);//断言

    }
}

测试结果:
测试正确:
在这里插入图片描述
若错误:
在这里插入图片描述
以上方法为手动创建测试,下面我们自动生成测试类。

自动单元测试

还是老一套:导入junit,写好源代码待测试、创建好测试文件夹test
打开要测试的类:按住ctrl+shift+t
在这里插入图片描述
点击Creat New Test…
出现下面页面,然后勾选要测试的方法。
在这里插入图片描述
会自动生成单元测试类,并需要将JUnit4 add to path…
在这里插入图片描述
编写方法,测试完成:
在这里插入图片描述
可以用此方法测试后面编写的程序。

代码实现

基本方法讲完之后,
分析一下我们的需求:
下图中的类使我们将会用到的
在这里插入图片描述
逐个解释:
conf:用于存放服务器端口号的配置文件
oa:webapp的配置文件其中包括index静态界面、login登录静态界面(后续待完善,准备添加)、userSave静态界面

以及可能将要扮演的角色:Web服务器开发人员、WebApp开发人员、SUN公司制定标准人员。

作为Web服务器开发人员

Servlet.xml

<?xml version="1.0" encoding="UTF-8" ?>
<server>
    <service>
        <connector port="8080"></connector>
    </service>
</server>

web.xml 用于构成<servlet-name,<urlpattern,servletclass>>

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <servlet>
        <servlet-name>loginServlet</servlet-name>
        <servlet-class>com.sh.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>com.sh.oa.servlet.UserSaveServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>userSaveServlet</servlet-name>
        <url-pattern>/user/save</url-pattern>
    </servlet-mapping>
</web-app>

OA办公系统首页

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>OA办公系统首页</title>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
</head>
<body>
<center><font size="35xp" color="blue">OA办公系统首页</font></center>
</body>
</html>

在这里插入图片描述localhost:8080/oa/userSave.html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <title>oa办公系统-用户信息保存</title>
</head>
<body>
    <form action="/oa/user/save" method="get">
        用户名
            <input type="text" name="username">
            <br/>
        性别
            <input type="radio" name="sex" value="m"/><input type="radio" name="sex" value="f"/><br/>
        兴趣
            运动<input type="checkbox" name="interest" value="sport"/>
            音乐<input type="checkbox" name="interest" value="music"/>
            旅游<input type="checkbox" name="interest" value="travel"/>
            美食<input type="checkbox" name="interest" value="food"/>
            <br/>
        <input type="submit" value="保存">
    </form>
</body>
</html>

在这里插入图片描述src/httpserver表示我们作为服务器开发人员需要完成的工作:
core为核心类包,util为工具类包

core包中:
BootStrap为程序主入口:

package com.sh.httpserver.core;

import com.sh.httpserver.util.Logger;
import org.dom4j.DocumentException;

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

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

    /**
     * 主程序入口
     */
    public static void start() {
        ServerSocket serverSocket=null;
        Socket clientSocket =null;
        BufferedReader br = null;
        try {
            //解析服务器中包含的web.xml文件
            String[] webAppNames={"oa"};
            WebParser.parser(webAppNames);

            Logger.log("httpserver start!");
            //获取系统端口号
            int port=ServerParser.getPort();
            Logger.log("httpserver-port:"+port);
            //创建服务器套接字,并绑定端口号:8080
            serverSocket=new ServerSocket(port);
            //8080端口号可能被占用,我们可以将此端口号放在xml文件中便于更改,也便于分配。
            //用dom4j+xPath解析XML
            //开始监听网络,此时程序处于等待状态,等待接收客户端的消息。
            Logger.log("httpserver started");
            while(true) {//实现循环监听,但目前为止,只能单线程,一次只能实现一个客户端请求
                clientSocket = serverSocket.accept();
                /*//接收客户端消息    多线程需要,放在HandlerRequest中
                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 (IOException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        } finally{
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            /*if(br!=null){  转到HandlerRequest中关闭资源
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(clientSocket!=null){
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }*/
        }
    }
}

HandlerRequest为请求处理入口:

package com.sh.httpserver.core;

import com.sh.httpserver.util.Logger;
import com.sh.oa.servlet.LoginServlet;
import com.sh.oa.servlet.ServletCache;

import javax.servlet.Servlet;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.util.Map;

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

        BufferedReader br=null;
        Logger.log("httpserver thread:"+Thread.currentThread().getName());//获取线程名称
        PrintWriter out=null;
        try {
            //接收客户端消息
            br=new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            //获取响应流对象
            out=new PrintWriter(clientSocket.getOutputStream());
            /*//打印客户端消息
            String temp=null;
            while((temp=br.readLine())!=null){
                System.out.println(temp);
            }*/
            //获取请求协议的请求行
            String requestLine=br.readLine();//GET / HTTP/1.1
            //获取URI -> 请求行(requestsLine)  -> 请求方式URI请求协议版本号 -> 三者之间是通过一个空格进行连接
            String requestsURI=requestLine.split(" ")[1];
//            System.out.println(requestsURI);
            //判断用户请求是否为静态页面:以.html或.htm结尾的文件叫做html页面
            if(requestsURI.endsWith(".html")||requestsURI.endsWith(".htm")){
                //处理静态页面的方法
                requestStaticPage(requestsURI,out);
            }else{//动态资源:java程序,业务处理类
                //requestURI: /oa/login?username=zhangsan&password=111   表单如果有参数,肯定会有问号
                //requestURI: /oa/login

                String servletPath=requestsURI;
                //判断servletPath是否包含参数
                if(servletPath.contains("?")){
                    servletPath=servletPath.split("[?]")[0];
                }
                /*if("/oa/login".equals(servletPath)){
                    LoginServlet loginServlet=new LoginServlet();
                    loginServlet.service();
                }

                分析以上代码,LoginServlet 类是 JavaWeb 程序员开发的,而 HandlerRequest 类是服务器开发人员
                开发的,在服务器中的代码关心了具体的 Servlet 类,显然服务器的程序和 JavaWeb 程序产生了依赖,
                具有高强度的耦合,实际上对于 Web 服务器来说,根本就不知道 Web 应用中有一个 LoginServlet 类,
                上面的程序中还使用了“new LoginServlet();”,这显然是错误的。另外在上面的 Web 服务器程序中编
                写了具体的请求路径/oa/login,这显然是不合理的,对于 Web 服务器来说浏览器客户端发送的请求是
                未知的。但是我们知道浏览器发送的请求路径/oa/login 是和底层 WebApp 中的 LoginServlet 存在映射
                关系/绑定关系的。而这种关系的指定必须由 WebApp 的开发人员来指定,我相信大家此时应该想到
                了“配置文件”,在配置文件中指定请求 URI 和对应要执行的 Servlet。该配置文件的编写由 WebApp
                程序员来完成,但是该文件由 Web 服务器开发人员读取并解析,所以该文件的名字、该文件的存放
                位置、该文件中所编写的配置元素都不能随意编写,必须提前制定好一个规范,那么这个规范由谁来
                制定呢?当然是由 SUN 公司来负责制定。*/

                //获取应用的名称; oa  在uri:/oa/login 里
                String webAppName= servletPath.split("[/]")[1];
                //获取servletMaps集合中的values值  -> servletMap  -> key:urlPatttern value:servletClassName
                Map<String,String> servletMap=WebParser.servletMaps.get(webAppName);
                //获取servletMap集合中的key值  ->  存在于uri中 /oa/login   /oa/user/xx/xxx  -> oa/user/....
                String urlPattern =servletPath.substring(1+webAppName.length());
                //获取servletClassName

                String servletClassName= servletMap.get(urlPattern);
                //判断该业务处理的Servlet类是否存在
                if(servletClassName!=null) {
                    out.print("HTTP/1.1 200 OK\n");
                    out.print("Content-Type:text/html;charset=utf-8\n\n");
                    //作为web服务器开发人员,我们要提取URI中的信息比如账号密码,传递给WebApp开发人员
                    RequestObject requestObj=new RequestObject(requestsURI);
                   /* //通过反射机制创建该业务处理类
                    Class c = Class.forName(servletClassName);
                    Object obj = c.newInstance();
                    //这个时候,服务开发人员不知道如何调用servlet业务处理类里的方法?制定servlet接口,

                    从上面可以看出,每一次浏览器客户端发送请求的时候,WEB 服务器都会通过反射机制创建一个
                    新的 Servlet 对象,当多线程同时并发访问的时候,在 WEB 服务器的 JVM 中会有大量的 Servlet 对象,
                    使 JVM 的堆内存压力增大,耗费占用大量的堆内存空间。并且每一次都创建 Servlet 对象,显然效率
                    是比较低的,怎么做既可以节省堆内存的开销又可以提高程序的执行效率呢?当然,此时你肯定想到
                    了单实例的方式,也就是说当浏览器客户端第一次发送请求的时候创建 Servlet 对象,将 Servlet 对象
                    和对应的请求 URI 存放到缓存当中,当浏览器客户端第 2+次发送请求的时候直接从缓存当中获取请求
                    URI 对应的 Servlet 实例,直接调用 Servlet 对象的 service 方法。这样做我们就可以达到既节省了内存
                    的开销,又提高了程序的执行速度。
                    */
                    Servlet servlet = ServletCache.get(urlPattern);
                    if(servlet==null){
                        Class c =Class.forName(servletClassName);
                        servlet=(Servlet)c.getDeclaredConstructor().newInstance();
                        ServletCache.put(urlPattern,servlet);
                    }
                    Logger.log("获取到Servlet对象"+servlet);
                    ResponseObject responseObj=new ResponseObject();
                    responseObj.setWriter(out);
                    //  Servlet servlet = (Servlet) obj;
                    //同时传过去RequestObj,ReponseObj
                    servlet.service(requestObj,responseObj);
                }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 (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } finally{
            //关闭资源
            if(br!=null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(clientSocket!=null){
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 处理静态页面
     * @param requestsURI 请求URI
     * @param out 响应流对象
     */
    public void requestStaticPage(String requestsURI, PrintWriter out) {
        //requestURI:/oa/index.html
        //静态页面的路径:oa/index.html
        String htmlPath=requestsURI.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) {
            e.printStackTrace();
        }


    }
}

RequestObject和ResponseObject:

package com.sh.httpserver.core;

import javax.servlet.ServletRequest;
import java.util.HashMap;
import java.util.Map;

public class RequestObject implements ServletRequest {
    //存储用户信息
    private Map<String,String[]> parameterMap=new HashMap<>();

    public RequestObject(String requestURIAndData){
        //requestURIAndData可能是:
        // /oa/save
        // /oa/save?username=zhangsan&password=123
        // /oa/save?username=zhangsan&password=123&interest=sport&interest=music
        // /oa/save?username=&password=interest=sport&interest=music
        if(requestURIAndData.contains("?")){
            //将请求分为URI和参数两部分
            String[] uriAndData=requestURIAndData.split("\\?");
            if(uriAndData.length>=2){
                //获取参数部分
                //username=
                //username=zhangsan&interest=sport&interest=music
                //username=&interest=sport&interest=music
                String data=uriAndData[1];
                //判断是否有多个参数
                if(data.contains("&")){//多个参数,包含&则说明有多个参数
                    String[] nameAndValues=data.split("&");
                    for(String nameAndValue:nameAndValues){
                        //通过 “=” 号将name和value分离
                        String[] nameAndValueArr=nameAndValue.split("=");//该数组0表示key,1表示value
                        //判断请求是否为多选框类型
                        if(parameterMap.containsKey(nameAndValueArr[0])){//多选框类型
                            String[] values=parameterMap.get(nameAndValueArr[0]);
                            String[] newValues=new String[values.length+1];//扩容,加一个值

                            System.arraycopy(values,0,newValues,0,values.length);
                            if(nameAndValueArr.length>1){
                                newValues[newValues.length-1]=nameAndValueArr[1];//将新扩容数组的最后一个位置填为当前key对应的value
                            }else{
                                newValues[newValues.length-1]="";
                            }
                            parameterMap.put(nameAndValueArr[0],newValues);
                        }else{//正常类型
                            if(nameAndValueArr.length>1){
                                //判断参数是否有值
                                parameterMap.put(nameAndValueArr[0],new String[]{nameAndValueArr[1]});
                            }else{//参数没有值
                                parameterMap.put(nameAndValueArr[0],new String[]{""});
                            }
                        }
                    }
                }else{//一个参数
                    String[] atrs=data.split("=");
                    if(atrs.length>1){
                        //key有value
                        parameterMap.put(atrs[0],new String[]{atrs[1]});
                    }else{//key无value
                        parameterMap.put(atrs[0],new String[]{""});
                    }
                }
            }
        }
    }
    public String getParameter(String attributeName){
	//获取单属性元素的值
        String[] parameterValues=parameterMap.get(attributeName);//
        if(parameterValues!=null&&parameterValues.length!=0){
            return parameterValues[0];
        }
        return null;
    }

    public String[] getParameterValues(String attributeName){
    //获取多属性元素的值
        return parameterMap.get(attributeName);
    }
}

package com.sh.httpserver.core;

import javax.servlet.ServletResponse;
import java.io.PrintWriter;

public class ResponseObject implements ServletResponse {
    /**
     * 以后虽有负责相应的参数都会被封装到ResponseObject中
     * @author web服务器开发人员
     */
    private PrintWriter out;
    public void setWriter(PrintWriter out){
        this.out=out;
    }
    public PrintWriter getWriter(){
        return out;
    }
}

ServerParser用于解析server.xml文件得到端口号(解耦合,所以用配置文件存储信息而非直接写入)

package com.sh.httpserver.core;

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

/**
 * 解析server.xml配置文件
 */
public class ServerParser {
    /**
     * 获取服务器的端口号
     * @return int port
     */
    public static int getPort(){
        //设置服务器默认端口号:8080,一定要设置在外面,可供return
        int port=8080;
        try {
            //创建解析器
            SAXReader saxReader=new SAXReader();
            //通过解析器的read方法讲配置文件读取到内存中,生成一个Document[org.dom4j]对象树
            Document doc=saxReader.read("conf/server.xml");
            //获取connector结点元素的路径:server->service->connector
            //三种方式:
            //获取connector结点元素的xpath路径:/server/service/connector
            //server//connector
            // connector
            Element connectorElt=(Element) doc.selectSingleNode("//connector");
            port=Integer.parseInt(connectorElt.attributeValue("port"));
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return port;
    }
}

WebParser用于解析应用信息,使每个用户名对应相应的类名等

package com.sh.httpserver.core;

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

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

/**
 * 解析服务器中的web.xml配置文件
 */
public class WebParser {
    public static Map<String,Map<String,String>> servletMaps=new HashMap<>();

    /**
     * 解析服务器中所有应用的web.xml
     * @param webAppNames 服务器中所有应用的名称
     */
    public static void parser(String[] webAppNames) throws DocumentException {
        for(String wepAppName:webAppNames){
            Map<String,String> servletMap=parser(wepAppName);
            servletMaps.put(wepAppName,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结点元素: webApp -> 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");
            //获取servlet-class结点元素对象的值
            String servletClassName=servletClassElt.getStringValue();
            //放到Map集中
            servletInfoMap.put(servletName,servletClassName);
        }
        //获取servlet-mapping结点元素对象结点元素对象:web-app  -> servlet-mapping
        List<Element> servletMappingNodes= document.selectNodes("/web-app/servlet-mapping");
        //创建一个servletMappingInfoMap集合:将servlet-name 和url-pattern结点元素对象的值键值对存入Map中
        Map<String,String> servletMappingInfoMap =new HashMap<>();
        //开始遍历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");
            //获取url-pattern结点元素对象的值
            String urlPattern =urlPatternElt.getStringValue();

            //将servletName和urlPattern分别存入map
            servletMappingInfoMap.put(servletName,urlPattern);
        }
        //获取servletName集:从servletMappingInfoMap或 servletInfoMap的keySet()中
        Set<String>  servletNames= servletInfoMap.keySet();
        //创建一个servletMap用于存放
        //serletMappingInfoMap中的value作key
        //servletInfoMap中的value作value
        Map<String,String> servletMap= new HashMap<>();
        //遍历获取并存储至servletMap中
        for(String servletName: servletNames){
            String servletClassName=servletInfoMap.get(servletName);
            String urlPattern=servletMappingInfoMap.get(servletName);
            servletMap.put(urlPattern,servletClassName);
        }
        return servletMap;
    }
}

DateUtil 日期工具类,单独写出,供所有其他类可用

package com.sh.httpserver.util;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 日期工具类
 */
public class DateUtil {
    private static SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
    private DateUtil(){}

    /**
     * 获取系统当前时间
     * @return String [yyyy-MM-dd HH:]
     */
    public static String getCurrentTime(){
        return dateFormat.format(new Date());
    }
}

Logger日志记录类

package com.sh.httpserver.util;

/**
 * 日志记录器
 *
 */
public class Logger {
    //工具类的方法往往是静态的,直接通过类名调用,不需要去创建对象
    //工具类的构造方法往往也是私有,但不是必须的。
    private Logger(){

    }

    /**
     * 普通日志记录器
     * @param msg
     */
    public static void log(String msg){
        System.out.println(" [INFO] "+ DateUtil.getCurrentTime()+" "+msg);
    }
}

作为WebApp开发人员

下面我们转换角色,看看webapp开发人员要完成的工作:

登录业务服务器,目前较为不满意,等到学会相应技术回来修改。

package com.sh.oa.servlet;

import com.sh.httpserver.core.RequestObject;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import javax.servlet.Servlet;
import java.io.PrintWriter;

/**
 * 处理登录业务的java程序,改程序有webApp开发人员开发,由web服务器开发人员调用
 * @author webApp开发人员
 */
public class LoginServlet implements Servlet {
    /**
     * 处理业务的核心类,该方法有web服务器负责调用
     * @param
     * @param requestObj
     */
    //PrintWriter 参数属于负责响应的参数,将来可能会有更多参数
    //参数过多时,会让Service方法边的特别臃肿
    //我们在这里做一个小的设计,将所有负责相应的参数统一都封装到负责响应的对象中。
    //那么我们这个负责相应的对象应该由谁来实现?  Web服务器开发人员,
    public void service(ServletRequest requestObj, ServletResponse response){
        System.out.println("正在验证身份,请稍等.....");//但这样太单一了,我们想复杂些,于是传入参数out
        PrintWriter out=response.getWriter();
        out.print("<html>");
        out.print("<head>");
        out.print("<title>正在验证</title>");
        out.print("<meta content='text/html;charset=utf-8'/>");
        out.print("</head>");
        out.print("<body>");
        out.print("<center><font size='35px' color='blue'>正在验证身份,请稍等</center>");
        out.print("</body>");
        out.print("</html>");


    }
}

伪单例,将已有服务器写入缓存,不必一个一开。

package com.sh.oa.servlet;

import javax.servlet.Servlet;
import java.util.HashMap;
import java.util.Map;

/**
 * Servlet对象缓存类
 * @author WEB服务器开发人员
 */
public class ServletCache {
    private static Map<String, Servlet> servletMap=new HashMap<>();
    public static Servlet get(String urlPattern){
        return servletMap.get(urlPattern);
    }

    public static void put(String urlPattern,Servlet servlet){
        servletMap.put(urlPattern,servlet);
    }

}

UserSaveServlet处理用户信息保存业务

package com.sh.oa.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.PrintWriter;

public class UserSaveServlet implements Servlet {
    @Override
    public void service(ServletRequest request, ServletResponse response) {
        //获取用户表单中提交的信息
        String username=request.getParameter("username");
        String sex=request.getParameter("sex");
        String []interests =request.getParameterValues("interest");
        StringBuilder interest=new StringBuilder();
        for(String n:interests){
            interest.append(n);
            interest.append(" ");
        }
        //连接数据库执行保存操作,将结果响应到浏览器,这里数据库就不再做了。
        // 可以等着后续自己再试试。
        PrintWriter out=response.getWriter();
        out.print("用户名:"+username+"<br>");
        out.print("性别:"+sex+"<br>");
        out.print("兴趣:"+interest+"<br>");
    }
}

SUN公司需要做的是什么?
当然是制定接口!

作为SUN公司接口制定人员

Servlet接口

package javax.servlet;

import com.sh.httpserver.core.RequestObject;

/**
 * 由SUN公司指定的Servlet接口规范,该接口由web服务器开发人员来调用,由WebApp开发人来实现
 */
public interface Servlet {
    //处理业务的核心方法
    void service(ServletRequest request, ServletResponse response);
    //那么问题来了,Servlet是SUN公司的规范(参数只有ServletResponse)
    // 但是为什么规范中出现了RequestObject类型,RequestObject类型是sh开发的WEb服务器程序
    //我们有需要解耦合,怎么解?
    //和我们之前的思路一样,Servlet规范中service方法的第一个参数应该是一个接口,
    //那我们现在以SUN公司的角色制定接口ServletResponse。最终的结果:
    // RequestObject -> ServletRequest  成功解耦合。
}

ServletRequest接口

package javax.servlet;

/**
 * ServletRequest负责封装请求协议
 */
public interface ServletRequest {
    /**
     * 通过参数的名字获取参数的值
     */
    String getParameter(String attributeName);
    /**
     * 通过参数的名字获取参数的值
     */
    String[] getParameterValues(String attributeName);
}

ServletResponse接口

package javax.servlet;

import java.io.PrintWriter;

/**
 * 负责响应的接口类型
 */
public interface ServletResponse {
    /**
     * 获取响应流
     */
    PrintWriter getWriter();
}

Well,that’s all.
29710-words

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-08-27 12:10:10  更:2021-08-27 12:11:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/13 2:57:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码