美文网首页
服务器对象Servlet

服务器对象Servlet

作者: 不会写诗的苏轼 | 来源:发表于2023-01-03 15:16 被阅读0次

    Servlet : server applet

    • 概念︰运行在服务器端的小程序
      servlet就是一个接口,定义了Java类被浏览器访问到(tomcat识别)的规则。*将来我们自定义一个类,实现servlet接口,复写方法。
    • 快速入门:
      1.创建JavaEE项目
      2.定义一个类,实现servlet接口
      public class servletDemo1 implements servlet
      3.实现接口中的抽象方法
      4.配置servlet
      在web.xml中配置︰<servlet>
     <servlet>
            <servlet-name>demo1</servlet-name>
            <servlet-class>com.xjbt.servlet.ServletGo</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>demo1</servlet-name>
            <url-pattern>/demo1</url-pattern>
        </servlet-mapping>
    
    • 原理:

      1.当服务器接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的servlet的资源路径
      2.查找web.xml文件,是否有对应的<url-pattern>标签体内容。
      3.如果有,则在找到对应的<servlet-class>全类名
      4.tomcat会将字节码文件加载进内存,并且创建其对象
      5.调用其方法

    • servlet中的生命周期方法︰
    1. init(ServletConfig servletConfig)被创建:
      init方法,只执行一次

    说明一个servlet在内存中只存在一个对象,servlet是单例的
    多个用户同时访问时,可能存在线程安全问题。
    解决∶尽量不要在servlet中定义成员变量。即使定义了成员变量,也不要修改值

    servlet什么时候被创建?
    在配置文件web.xml=》<Servlet></Servlet>标签体中配置

    指定Servlet创建时机
                指定servelet第一次访问时创建 默认
                <load-on-startup>-1</load-on-startup>
                指定servelet在服务器启动时创建
                <load-on-startup>1</load-on-startup>
    
    1. service(ServletRequest servletRequest, ServletResponse servletResponse)提供服务:
      执行service方法,执行多次
      每次访问servlet时,service方法都会被调用一次。
    2. destroy()被销毁:
      执行destroy方法,只执行一次
      只有服务器正常关闭时,才会执行destroy方法。
      destroy方法在servlet被销毁之前执行,一般用于释放资源
    • 使用注解
      servlet3.0 :支持注解配置。可以不需要web.xml了。

    步骤

    1. 创建]avaEE项目,选择Servlet的版本3.0以上,可以不创建web.xml
    2. 定义一个类,实现servlet接口
    3. 复写方法
    4. 在类上使用@webservlet注解,进行配置@webservlet(“Servlet资源路径")
      告诉计算机(Tomcat服务器)程序的资源路径(“Servlet资源路径),程序全类名称不用写,注释在哪儿就是哪个类
    • Servlet体系结构
      • Servlet --接口
        |
        • Genericservlet(Servlet子) --抽象类
          GenericServlet :将servlet接口中其他的方法做了默认空实现,只将service()方法作为抽象
          将来定义servlet类时,可以继承Genericservlet,实现service()方法即问 如果想要其他方法复写即可
          |
          • Httpservlet(servlet孙) --抽象类
            定义类继承Httpservlet
            复写doGet(get接收数据方式)/doPost(post接收数据方式)方法
    • Servlet相关配置
      urlpartten:servlet 访问路径
      一个servlet可以定义多个访问路径︰ @webServlet({" /d4" , "/dd4" , " /ddd4"})
      路径定义规则∶
      1. /xxx
      2. /xxx/xxx:多层路径,目录结构
      3. *.do

    HTTP

    • 概念:Hyper Text Transfer Protocol超文本传输协议
    • 传输协议:定义了,客户端和服务器端通信时,发送数据的格式
    • 特点∶
      1. 基于TCP/IP的高级协议
      2. 默认端口号:80
      3. 基于请求/响应模型的:一次请求对应一次响应
      4. 无状态的:每次请求之间相互独立,不能交互数据
    • 历史版本:
      1.0:每一次请求响应都会建立新的连接
      1.1:复用连接

    • 请求消息数据格式
    字符串格式∶
    POST /login.html HTTP/1.1     -请求行
    Host:localhost
    User-Agent: Mozilla/5.0 (windows NT 6.1; win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0Accept: text/html,application/xhtml+xml, application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-Tw;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflate
    connection: keep-alive
    upgrade-Insecure-Requests: 1  -请求头 
                                  -请求空行
    username=zhangsanl            -请求体 POST方式才有请求体
    
    1. 请求行
      请求方式【GET/POST】 请求url【/login.html】 请求协议/版本【HTTP/1.1】

      请求方式:HTTP协议有7中请求方式,常用的有2种
      GET:
      1. 请求参数在请求行中,在url后。
      2. 请求的url长度有限制的
      3. 不太安全
      POST :
      1.请求参数在请求体中
      2.请求的url长度没有限制的
      3.相对安全

    2. 请求头:客户端浏览器告诉服务器一些信息
      请求头名称:请求头值

      常见的请求头:
      User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息
      可以在服务器端获取该头的信息,解决浏览器的兼容性问题
      Referer : http://localhost/login.html
      *告诉服务器,我(当前请求)从哪里来?
      作用:
      1. 防盗链:防止其他网站的地址指向当前地址的资源页面
      2. 统计工作:

    3. 请求空行
      空行

    4. 请求体(正文)


    • 响应消息数据格式
      响应消息∶服务器端发送给客户端的数据
    HTTP/1.0 200 oK                     =>响应行
    content-Type: text/html;            =>响应头
    charset=UTF-8content-Length: 101        
    Date: wed,06 Jun 2018 07:08:42GMT
                                        =>响应空行
    <html>                              =>响应体
    <head>
    <title>$Title$</title></ head>
    <body>
    hello , response< / body>
    </html>
    
    1. 响应行
      协议/版本【HTTP/1.0】 响应状态码【200】 状态码描述【oK】

      状态码都是3位数字
      分类:
      1xx:服务器接收客户端消息,但没有接收完成,等待一段时间后,发送1xx多状态码
      2xx:成功。代表:200
      3xx:重定向。代表:302(重定向),304(访问缓存,浏览器访问服务器图片,浏览器如果有该图片的缓存,服务器发送304,表示不用在请求这张图片)
      4xx:客户端错误。
      404:(请求路径没有对应的资源)
      405:请求方式没有对应的doXxx方法
      5xx:服务器端错误。代表:500(服务器内部出现异常)

    2. 响应头

      content-Type∶服务器告诉客尸端本次响应体数据格式以及编码格式
      content-disposition:服务器告诉客户端以什么格式打开响应体数据
      值:
      in-line:默认值,在当前页面内打开
      attachment;filename=xxx∶以附件形式打开响应体。文件下载

    3. 响应空行

    4. 响应体:传输的数据


    • Request对象和Response对象的原理

    • Request继承结构体系

      • servletRequest --接口
        l继承
        • HttpservletRequest --接
          l实现
          org.apache.catalina.connector.RequestFacade类(tomcat)
    • request功能∶

    1. 获取请求数据:
      1. 获取请求行数据
        请求行:GET /xjbt/demo1?name=zhangsan HTTP/1.1
        方法∶
        string getMethod()获取请求方式:GET
        *string getcontextPath()获取虚拟目录:/day14
        string getservletPath()获取servlet路径:/demo1
        string getQuerystring()获取get方式请求参数: name=zhangsan
        /day14/demo1获取请求URI
        *string getRequestURI() /day14/demo1
        *stringBuffer getRequestURL() http://localhost/day14/demo16
        string getProtocol()获取协议及版本:HTTP/1.1
        string getRemoteAddr() 获取客户机的IP地址

      2. 获取请求头数据
        方法∶
        *string getHeader(string name):通过请求头的名称获取请求头的值
        Enumeration<string> getHeaderNames():获取所有的请求头名称
        示例:

        @WebServlet("/ServletText02")
        public class ServletText02 extends HttpServlet {
         protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {
          1,获取请求头 user-agent
          String agent = req.getHeader("user-agent");
          if(agent.contains("Chrome")){
                  System.out.println("谷歌浏览器");
          }else if(agent.contains("Firefox")){
                  System.out.println("火狐浏览器");
          }
          System.out.println("----------------------------------------------------");
          2,所有请求头名子
          Enumeration<String> headerNames = req.getHeaderNames();
          while (headerNames.hasMoreElements()){
                  String headerName = headerNames.nextElement();
                  //根据名称获取请求头值
                  String header = req.getHeader(headerName);
                  System.out.println(headerName+"---"+header);
          }
          System.out.println("=====================================================");
          3,获取请求头 referer  是从哪里访问
          String referer = req.getHeader("referer");
          System.out.println(referer);
          System.out.println("-----------");
        3,防盗链
          if(referer != null){
              if( referer.contains ("newServlet")){
              //正常访问
              response.setContentType( "text/htm1;charset=utf-8" );
              response.getWriter().write("播放电影...." );
          }else{
              //盗链
              response.setContentType( "text/html;charset=utf-8");
              response.getWriter().write("想看电影吗?来优酷吧...");
          }
        }
        }
        }
        
      3. 获取请求体数据:
        请求体∶只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数
        步骤∶

        1. 获取流对象
          BufferedReader getReader():获取字符输入流,只能操作字符数据
          *servletInputstream getInputstream()∶获取字节输入流,可以操作所有类型数据【文件上传】
        2. 再从流对象中拿数据
        @WebServlet("/RequestText02")
        public class RequestText02 extends HttpServlet {
           protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        1获取请求体
           1)获取字符输入流
           BufferedReader reader = req.getReader();
           String line=null;
           2)读取字符
           while((line=reader.readLine())!=null){     =>按行读取数据
               System.out.println(line);
           }
          }
        }
        
    2. 其他功能︰
      1. 获取请求参数通用方式
        string getParameter(String name) :根据参数名称获取参数值username=zs&password=123
        String[] getParametervalues(string name) :根据参数名称获取参数值的数组hobby=xx&hobby-game
        Enumeration<string> getParameterNames():获取所有请求的参数名称
        Map<String,string[]> getParameterMap():获取所有参数的map集合

        @WebServlet("/RequestText03")
        public class RequestText03 extends HttpServlet {
        protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        获取请求参数通用方式
            1)通过参数名称获取参数值对应到页面的name值 适用于单个键值对
            String username = req.getParameter("username");
            //System.out.println(username);
            2)通过参数名获取参数值数据,复选框 数组
            String[] hobbies = req.getParameterValues("hobby");
                for (String hobby: hobbies) {
                    //System.out.println(hobby);
            }
            3)获取所有的参数名
            Enumeration<String> parameterNames = req.getParameterNames();
            while (parameterNames.hasMoreElements()){
            System.out.println(req.getParameter(parameterNames.nextElement()));
            }
            4)获取所有的参数名 map集合
            Map<String, String[]> parameterMap = req.getParameterMap();
            Set<String> keySet = parameterMap.keySet();    =>获取name【map的key[]】
            for (String name:keySet){
                System.out.println(name);
                String[] values = parameterMap.get(name);  =>获取每个name对应的值【通过map的key获取map的值values[]】
               for (String value:values){
                   System.out.println(value);
               }
                System.out.println("--------");
            }
        
        }
        
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request,response);    =>复用用dopost方法
        }
        }
        

        中文乱码问题:
        get方式:,tomcat 8 已经将get方式乱码问题解决了
        post方式会乱码
        解决∶在获取参数前,设置request的编码request.setcharacterEncoding("utf-8");

      2. 请求转发︰—种在服务器内部的资源跳转方式
        步骤︰
        1.通过request对象获取请求转发器对象︰RequestDispatcher getRequestDispatcher(String path)
        2.使用RequestDispatcher对象来进行转发∶forward(ServletRequest request,ServletResponse response)
        特点∶
        1.浏览器地址栏路径不发生变化。
        2.只能转发到当前服务器内部资源中。
        3.转发是一次请求。

      3. 共享数据
        域对象:一个有作用范围的对象,可以在范围内共享数据
        reauest域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
        方法:
        void setAttribute(string name,object obj):存储数据
        object getAttitude(string name):通过键获取值
        void removeAttribute(string name):通过键移除键值对

    需求:
    1.编写login.html登录页面
    2.username & password 两个输入框
    2.使用Druid数据库连接池技术,操作mysql, day14数据库中user表
    4.使用JdbcTemplate技术封装JDBC
    5.登录成功跳转到successservlet展示:登录成功!用户名,欢迎您
    6.登录失败跳转到Failservlet展示:登录失败,用户名或密码错误

    • 开发步骤:
      1. 创建项目,导入html页面,配置文件,jar包
      2. 创建数据库环境
      CREATE DATABASE if not exists jdbc;
      USE jdbc;
      CREATE TABLE USER(
      id INT PRIMARY KEY AUTO_INCREMENT,
      username VARCHAR(32) UNIQUE NOT NULL,
      PASSWORD VARCHAR(32) NOT NULL
      );
      
      3.创建User实体类,属性是user表的字段
      1. 编写基于druid数据库池的jdbcUtils
      public class JDBCUtilsTest {
        private static DataSource ds;//数据库连接池对象
        static{
            try {
                Properties pro = new Properties();
                //查找jdbc.properties数据库配置文件
                InputStream is = JDBCUtilsTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
                pro.load(is);
                ds= DruidDataSourceFactory.createDataSource(pro);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
      
        /**
         * 获取数据库连接池
         * @return
         */
        public static DataSource getDataSource(){
            return ds;
        }
      
        /**
         * 获取数据库连接
         * @return
         * @throws SQLException
         */
        public static Connection getConnection() throws SQLException {
            return ds.getConnection();
        }
      }
      
      1. 创建dao提供login方法
      public class UserDao {
        //声明template公用里面的方法
        private JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDatasource());
        /**
         * 用户登陆方法 填写表单
         * @param loginUser 用户名密码
         * @return 包含用户全部数据的user对象
         */
        public User login(User loginUser){
            try {
                String sql = "select * from user where username=? and password=?";
                User user = template.queryForObject(sql,
                        new BeanPropertyRowMapper<User>(User.class),
                        loginUser.getUsername(),
                        loginUser.getPassword());
                        return user;
            } catch (DataAccessException e) {
                e.printStackTrace();
                return null;
            }
        }
      }
      
      1. 测试下dao是否成功
      public class UeserDaoTest {
        @Test
        public void testLogin(){
            User user=new User();
            user.setUsername("zhangsan");
            user.setPassword("123");
            UserDao userdao=new UserDao();
            User loginuser = userdao.login(user);
            System.out.println(loginuser);
        }
      }
      
      1. 写ServletLogin类
      @WebServlet("/LoginServlet")
      public class LoginServlet extends HttpServlet {
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          //设置编码
          req.setCharacterEncoding("utf-8");
      /*       //获取传过来的值
          String username=req.getParameter("username");
          String password=req.getParameter("password");
          //创建User对象
          User loginUser=new User();
          loginUser.setUsername(username);
          loginUser.setPassword(password);*/
      
          Map<String, String[]> map = req.getParameterMap();
          User loginUser=new User();
          try {
              //使用beanutils
              BeanUtils.populate(loginUser,map);
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          } catch (InvocationTargetException e) {
              e.printStackTrace();
          }
          //创建dao方法
          UserDao userdao=new UserDao();
          User user = userdao.login(loginUser);
          if(user==null){
              //登录失败
              req.getRequestDispatcher("/Failservlet").forward(req,resp);
          }else{
              //登录成功
              req.setAttribute("username",user.getUsername());
              req.setAttribute("password",user.getPassword());
              req.getRequestDispatcher("/successServlet").forward(req,resp);
          }
      }
      protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      
        }
      }
      
      1. 编写successServlet
      @WebServlet("/successServlet")
      public class successServlet extends HttpServlet {
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //设置编码
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().write("欢迎"+request.getAttribute("username"));
        }
      
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      
        }
      }
      
      1. 编写FailServlet
      @WebServlet("/Failservlet")
      public class Failservlet extends HttpServlet {
       protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
           //设置编码
           response.setContentType("text/html;charset=utf-8");
           response.getWriter().write("登录失败");
       }
       protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      
       }
      }
      
      1. login.html中form表单的action路径的写法
        虚拟目录+Servlet的资源路径
      2. BeanUtils工具类,简化数据封装
        用于封装JavaBean【标准的Java类】
        • 要求:
          1.类必须被public修饰
          2.必须提供空参的构造器
          3.成员变量必须使用private修饰
          4.提供公共setter和getter方法
        • 功能:封装数据
        • 概念:
          成员变量:
          属性:setter和getter方法截取后的产物
          例如:getUsername() --> Username--> username
        • 方法:
          setProperty()
          getProperty()
          populate(Object obj , Map map):将map集合的键值对信息,封装到对应的JavaBean对象中

    • Response对象
      1. 设置响应行【:HTTP/1.1 200 ok】
        设置状态码: setstatus(int sc)

      2. 设置响应头: setHeader(string name,string value)

      3. 设置响应体:
        使用步骤︰

        1. 获取输出流
          字符输出流:Printwriter getwriter()
          字节输出流:servletoutputstream getoutputstream()
        2. 使用输出流,将数据输出到客户端浏览器
      4. 案例

        1. 重定向
        //重定向
        response.setstatus(302);//设置重定向状态码
        response.setHeader ("location" , " /newServlet/ServletResponseTest02");//设置响应头*/
        //动态获取虚拟目录
        string contextPath = request.getcontextPath();
        //重定向2只有重定向的资源路径发生了改变
        response.sendRedirect(s: contextPath+" /servletResponseTesto2"");
        
        
        • 重定向的特点
          1.地址栏发生变化
          2.重定向可以访问其他站点(服务器)的资源
          3.重定向是两次请求
        • 转发的特点︰
          1.转发地址栏路径不变
          2.转发只能访问当前服务器下的资源
          3.转发是一次请求
        • 路径:
          1. 相对路径∶通过相对路径不可以确定唯一资源
            规则:找到当前资源和目标资源之间的相对位置关系

          2. 绝对路径∶通过绝对路径可以确定唯一资源
            给客户端浏览器使用:需要加虚拟目录(项目的访问路径)
            建议虚拟目录动态获取: request.getcontextPath()
            <a> , <form>重定向【sendRedirect(s: contextPath+"/servletResponseTesto2");】...
            给服务器使用:不需要加虚拟目录
            转发路径【getRequestDispatcher("/successServlet").forward(req,resp)

        2.服务器输出字符数据到浏览器
        步骡︰
        1.获取字符输出流
        2.输出数据

        乱码问题∶
        获取流对象之前,设置流的默认编码: ISO-8859-1设置为:GBKresponse.setCharacterEncoding( "utf-8");
        告诉浏览器,服务器发送的消息体数据的编码。建议浏览器使用该编码解码
        response.setHeader( "content-type","text/htm1;charset=utf-8");
        简单的形式,设置编码,是在获取流之前设置
        response.setcontentType("text/html;charset=utf-8");
        1.Printwriter pw = response.getwriter();获取的流的默认编码是ISO-8859-1
        2.设置该流的默认编码

      3. 验证码:
      ```
       int width=100;
        int height=50;
        //1.创建—对象,在内存中图片(验证码图片对象)
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
    
        //2.美化图片
        Graphics graphics = image.getGraphics();//画笔对象
        //填充粉红色
        graphics.setColor(Color.pink);//设置画笔颜色
        graphics.fillRect(0,0,width,height);//填充
    
        //蓝色边框
        graphics.setColor(Color.blue);
        graphics.drawRect(0,0,width-1,height-1);
    
        //写数字
        String str="ABCDEFGHIJKLMNOPQISCUVWSYZabcdefghijklmnopqiscuvwsyz0123456789";
        //生成随机角标
        Random rand = new Random();
        for (int i = 1; i <=4 ; i++) {
            int index = rand.nextInt(str.length());
            //获取随机字符
            char c = str.charAt(index);//随机字符
            graphics.drawString(c+"",width/5*i,height/2);
        }
    
        //4画干扰线
        graphics.setColor(Color.green);
        for (int i = 1; i <=10; i++) {
            int x1=rand.nextInt(width);
            int y1=rand.nextInt(height);
            int x2=rand.nextInt(width);
            int y2=rand.nextInt(height);
            graphics.drawLine(x1,y1,x2,y2);
        }
    
    
        //3.将图片输出到页面展示
        ImageIO.write(image,"jpg",response.getOutputStream());
      ```
      ```
      var randimg = document.getElementById("randImg");
      randimg.onclick=function(){
        var date=new Date().getTime();//时间戳,永远不会重复
    
        randimg.src="/newServlet/ServletResponseTest03?"+date
      }
      ```
    

    • ServletContext对象:
      1. 概念:代表整个web应用,可以和程序的容器(服务器)来通信
      2. 获取:
        1. 通过request对象获取:request.getservletcontext();
        2. 通过Httpservlet获取:this.getservletcontext();
      3. 功能:
        1. 获取MIME类型
          MIME类型:在互联网通信过程中定义的一种文件数据类型
          格式:大类型/小类型【text/ html】【image/jpeg】
          获取:string getMimeType( string file)

        2. 域对象:共享数据
          setAttribute(string name,object value)
          getAttribute(string name)
          removeAttribute(string name)
          ServletContext对象范围:所有用户所有请求的数据

        3. 获取文件的真实(服务器)路径
          String realPath = context.getRealPath("/b.txt" ) ; //web目录下资源访问
          String c = context.getRealPath( s: "/WEB-INF/c.txt"); //WEB-INF目录下的资源访问
          String a = context.getRealPath( s: "/WEB-INF/c1asses/a.txt");//src目录

      4. 下载:
    //1.获取请求参数
                String filename = request.getParameter("filename");
    
                //2.使用字节输入流加载文件进内存
                    //找到文件服务器路径
                    ServletContext servletContext = this.getServletContext();
                    //获取文件真实路径
                    String realPath = servletContext.getRealPath("/img/" + filename);
                    //使用字节流关联
                    FileInputStream fils = new FileInputStream(realPath);
    
                //3.设置response的响应头
                    //3.1设置响应头类型:content-type
                    String mimeType = servletContext.getMimeType(filename);//获取文件的mime类型
                    response.setHeader("content-type" ,mimeType);
    
    
            //解决中文文件名问题
            //1.获取user-agent请求头、
            String agent = request.getHeader( "user-agent");
            //2.使用工具类方法编码文件名即可
            filename = DownLoadUtils.getFileName(agent,filename);
    
            //3.2设置响应头打开方式: content-disposition
            response.setHeader("content-disposition","attachment;filename="+filename);
    
                //4.将输入流的数据写出到输出流中
                ServletOutputStream sos = response.getOutputStream();
                byte[] buff = new byte[ 1024* 8];//缓冲区
                int len = 0;//读到的个数
                while( ( len = fils.read(buff))!=-1){
                            sos.write( buff,0,len);
                 }
                 fils.close();
    

    相关文章

      网友评论

          本文标题:服务器对象Servlet

          本文链接:https://www.haomeiwen.com/subject/rnohqdtx.html