表白墙的实现【前后端交互】

目录

一、Servlet API 详解

1. HttpServletRequest

1.1 HttpServletRequest 方法

1.2 getParameter

2.HttpServletResponse

2.1  HttpServletResponse 方法 

2.2 代码示例: 设置状态码

2.3 代码示例: 重定向

二、表白墙

1.准备工作

 2.约定前后端交互接口

2.1 接口一:页面获取当前所有的留言消息

2.2 接口二:提交新消息给服务器

3.调整前端页面代码

4.数据持久化

4.1 数据存入数据库


上节 Servlet 我们学习了 Tomcat 提供的 API;并且编写一个 Hello World 基本步骤:1️⃣创建项目(maven)2️⃣引入依赖(servler)3️⃣创建目录4️⃣编写代码(webapp/WEB-INF/web.xml)5️⃣打包程序(waven package)6️⃣部署程序(把 war 拷贝到 webapps 目录中)7️⃣验证代码;最后我们学习了 Servlet 中的三个核心类

一、Servlet API 详解

1. HttpServletRequest

HttpServletRequest 是一个 HTTP 请求:Tomcat 通过 Socket API 读取 HTTP 请求(字符串),就会按照 HTTP 协议的格式把字符串解析成 HttpServletRequest

1.1 HttpServletRequest 方法

String getProtocol()
返回请求协议的名称和版本
String getMethod()
返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT
String getRequestURI()
从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分
String getContextPath()
返回指示请求上下文的请求 URI 部分
String getQueryString()
返回包含在路径后的请求 URL 中的查询字符串
Enumeration getParameterNames()
返回一个 String 对象的枚举,包含在该请求中包含的参数的名称
String getParameter(String name)
以字符串形式返回请求参数的值,或者如果参数不存在则返回 null
String[] getParameterValues(String name)
返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null
Enumeration getHeaderNames()
返回一个枚举,包含在该请求中包含的所有的头名
String getHeader(String name)
以字符串形式返回指定的请求头的值
String getCharacterEncoding()
返回请求主体中使用的字符编码的名称
String getContentType()
返回请求主体的 MIME 类型,如果不知道类型则返回 null
int getContentLength()
以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1
InputStream getInputStream()
用于读取请求的 body 内容. 返回一个 InputStream 对象

1️⃣ 其中 getRequestURI 不是 URL:URL 唯一资源定位符;URI 唯一资源标识符

2️⃣ query string 查询字符串:例如 http://餐厅:18/熏肉大饼?葱=少放,其中 葱=少放 就是查询字符串

3️⃣Enumeration getParameterNames() 和 String getParameter(String name) 是获取请求中的字符串:上述中 query string 中的键值对

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

//继承 HttpServlet
@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StringBuilder result = new StringBuilder();
        result.append(req.getProtocol());
        result.append("
"); result.append(req.getMethod()); result.append("
"); result.append(req.getRequestURI()); result.append("
"); result.append(req.getQueryString()); result.append("
"); result.append(req.getContextPath()); result.append("
"); //在响应中设置上 body 的类型,方便浏览器进行解析 resp.setContentType("text/html;charset=utf8"); resp.getWriter().write(result.toString()); } }

表白墙的实现【前后端交互】_第1张图片

1.2 getParameter

getParameter 是最常用的 API 之一,是前端和后端传递数据非常常见的需求

1️⃣通过 query string 传递

约定:前端通过 query string 传递 username 和 password

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //前端通过 url 的 query string 传递 username 和 password 两个属性
        String username = req.getParameter("username");
        if (username == null) {
            System.out.println("username 这个 key 在 query string 中不存在!");
        }
        String password = req.getParameter("password");
        if (password == null) {
            System.out.println("password 这个 key 在 query string 中不存在!");
        }
        System.out.println("username" + username + ", password" + password);

        resp.getWriter().write("ok");
    }
}

表白墙的实现【前后端交互】_第2张图片

在 URL 中 query string 如果是包含 中文/特殊字符,务必要使用 urlencode 的方式转码;如果是直接写 中文/特殊字符,就会存在很大风险;如果不转码,在有些浏览器/http服务器下对中文支持不好的花,会出现问题

表白墙的实现【前后端交互】_第3张图片

2️⃣通过 body (from) 传递

相当于 body 里存的数据的格式,和 query string 一样,但是 Content-Type 是 application/x-www-form-urlencoded,此时也是通过 getParameter 来获取到键值对

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //给请求设置编码方式
        req.setCharacterEncoding("utf8");        
        //前端通过 body,以 form 表单的格式,把 username 和 password 传给服务器
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null) {
            System.out.println("username 这个 key 在 body 中不存在!");
        }
        if (password == null) {
            System.out.println("password 这个 key 在 body 中不存在!");
        }
        System.out.println("username" + username + ", password" + password);

        resp.getWriter().write("ok");
    }
}

 由于是 post 请求,在网页中不好表现,我们打开 postman 

表白墙的实现【前后端交互】_第4张图片

3️⃣通过 body (json) 传递(最常用的传递方式)

 json 也是键值对格式的数据,但是 Servlet 自身没有内置 json 解析功能,因此就需要借助其他第三方库;用来处理 json 的第三方库有很多,常见的如 fastjson、gson、jackson...,我们使用 jackson 来解析

 引入依赖:



    com.fasterxml.jackson.core
    jackson-databind
    2.15.0

import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

class User {
    public String username;
    public String password;
}

@WebServlet("/json")
public class JsonServlet extends HttpServlet {
    // 使用 jackson, 最核心的对象就是 ObjectMapper
    // 通过这个对象, 就可以把 json 字符串解析成 java 对象; 也可以把一个 java 对象转成一个 json 格式字符串.
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过 post 请求的 body 传递过来一个 json 格式的字符串.
        User user = objectMapper.readValue(req.getInputStream(), User.class);
        System.out.println("username=" + user.username + ", password=" + user.password);

        resp.getWriter().write("ok");
    }
}

表白墙的实现【前后端交互】_第5张图片

c4f81e1bb0dd4c2ab2202a7d74c9e07b.png

 readValue 里面做的事情:

  1. 解析 json 字符串,转换成若干个键值对
  2. 根据第二个参数 User.class(.class 是类对象,就是这个类的图纸),去找到 User 里的所有的 public 的属性(或者有 public getter setter 的属性),依次遍历......
  3. 遍历属性,根据属性的名字取上述准备好的键值对里查询,看看这个属性名字是否存在对应的 value ,如果存在就把 value 复制到该属性中

58970f15f5ac4a1c9318d75533f74558.png

2.HttpServletResponse

HttpServletResponse 表示一个 HTTP 响应

Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应, 然后把响应的数据设置到 HttpServletResponse 对象中;然后 Tomcat 就会把这个HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过 Socket 写回给浏览器.

2.1  HttpServletResponse 方法 

void setStatus(int sc)
为该响应设置状态码
void setHeader(String name, String value)
设置一个带有给定的名称和值的 header. 如果 name 已经存在, 则覆盖旧的值
void addHeader(String name, String value)
添加一个带有给定的名称和值的 header. 如果 name 已经存在, 不覆盖旧的值, 并列添加新的键值对
void setContentType(String type)
设置被发送到客户端的响应的内容类型
void setCharacterEncoding(String charset)
设置被发送到客户端的响应的字符编码(MIME 字符集)例如, UTF-8
void sendRedirect(String location)
使用指定的重定向位置 URL 发送临时重定向响应到客户端
PrintWriter getWriter()
用于往 body 中写入文本格式数据
OutputStream getOutputStream()
用于往 body 中写入二进制格式数据

2.2 代码示例: 设置状态码

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(404);
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("返回 404 响应!");
    }
}

表白墙的实现【前后端交互】_第6张图片

 抓包结果

表白墙的实现【前后端交互】_第7张图片

 2.3 代码示例: 自动刷新

通过 header 实现自动刷新效果:给 HTTP 响应中,设置 Refresh——时间

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

//通过 header 实现自动刷新效果:给 HTTP 响应中,设置 Refresh——时间

@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
        // 每隔 1s 自动刷新一次.
        resp.setHeader("Refresh", "1");
        resp.getWriter().write("time=" + System.currentTimeMillis());
    }
}

表白墙的实现【前后端交互】_第8张图片

2.3 代码示例: 重定向

 代码案例:用户访问这个供暖这个路径的时候,自动重定向到搜狗主页

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 用户访问这个路径的时候, 自动重定向到 搜狗主页 .
        //resp.setStatus(302);
        //resp.setHeader("Location", "https://www.sogou.com");
        resp.sendRedirect("https://www.sogou.com");
    }
}

部署程序, 通过 URL http://127.0.0.1:8080/hello_servlet/redirectServlet 访问, 可以看到, 页面自动跳转到 搜狗主页 了.

抓包结果:

表白墙的实现【前后端交互】_第9张图片

二、表白墙

表白墙前端页面代码:message.html




    
    
    
    表白墙
    
    
    


    

表白墙

输入内容后点击提交, 信息会显示到下方表格中

谁:
对谁:
说:

1.准备工作

1️⃣创建一个 maven 项目

2️⃣引入依赖:servlet、jackson



    com.fasterxml.jackson.core
    jackson-databind
    2.15.0



    javax.servlet
    javax.servlet-api
    3.1.0
    provided


3️⃣创建必要的目录 webapp、WEB-INF、web.xml

web.xml 中的代码:



        Archetype Created Web Application

4️⃣把之前实现的表白墙前端页面拷贝到 webapp 目录中

表白墙的实现【前后端交互】_第10张图片

表白墙的实现【前后端交互】_第11张图片

 5️⃣配置 Smart Tomcat

表白墙的实现【前后端交互】_第12张图片

 6️⃣运行程序就可以访问前端页面

表白墙的实现【前后端交互】_第13张图片

 2.约定前后端交互接口

 写代码之前需要明确前后端交互端口

表白墙的实现【前后端交互】_第14张图片

 什么时候发送请求❓❓


1️⃣页面加载完毕之后,需要给服务器发送请求,获取当前的留言数据都有什么

2️⃣用户点击提交的时候,就需要告诉服务器,当前用户发送了的消息是啥

在交互的过程中,有涉及到关键的问题:请求具体是什么样子❓❓响应具体是什么样子❓❓这些都需要程序猿来设计,这就叫“约定前后端交互端口

此处给出一份典型的约定方式(并不是唯一的方式)

2.1 接口一:页面获取当前所有的留言消息

约定

1️⃣请求:GET/message

2️⃣响应:HTTP/1.1 200 OK

    Content-Type:application/json

[
   {
        from: "从哪里来",
        to: "到哪里去",
        message: "消息是啥"
   },
   {
        from: "从哪里来",
        to: "到哪里去",
        message: "消息是啥"
   },
    ......
]

json 中使用[ ] 表示数组,[ ] 中的每个元素是一个 { } json 对象;每个对象里又有三个属性 from、to、message

2.2 接口二:提交新消息给服务器

1️⃣请求:POST/message

    Content-Type:application/json

[
   {
        from: "从哪里来",
        to: "到哪里去",
        message: "消息是啥"
   },
   {
        from: "从哪里来",
        to: "到哪里去",
        message: "消息是啥"
   },
    ......
]

2️⃣响应:HTTP/1.1 200 OK

处理 "获取所有留言消息":创建 Message 类、创建 MessageServlet 类

import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

class Message {
    // 这几个属性必须设置 public !!!!
    // 如果设置 private, 必须生成 public 的 getter 和 setter !!!
    public String from;
    public String to;
    public String message;

    @Override
    public String toString() {
        return "Message{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    private List messageList = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过这个方法来处理 "获取所有留言消息"
        // 需要返回一个 json 字符串数组. jackson 直接帮我们处理好了格式.
        String respString = objectMapper.writeValueAsString(messageList);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(respString);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过这个方法来处理 "提交新消息"
        Message message = objectMapper.readValue(req.getInputStream(), Message.class);
        messageList.add(message);
        System.out.println("消息提交成功! message=" + message);

        // 响应只是返回 200 报文. body 为空. 此时不需要额外处理. 默认就是返回 200 的.
    }
}

3.调整前端页面代码




    
    
    
    表白墙
    
    
    


    

表白墙

输入内容后点击提交, 信息会显示到下方表格中

谁:
对谁:
说:

此时通过浏览器的URL:127.0.0.1:8080/message_wall/messageWall.htm 访问服务器即可看到

表白墙的实现【前后端交互】_第15张图片

 此时我们每次提交的数据都会发送给服务器. 每次打开页面的时候页面都会从服务器加载数据. 因此及时关闭页面, 数据也不会丢失.

但是数据此时是存储在服务器的内存中 ( private List messages = new ArrayList(); ), 一旦服务器重启, 数据仍然会丢失

4.数据持久化

把数据存储到银盘上,才是让数据更让好的持久化的方法:1️⃣数据存储到文件里2️⃣数据存入数据可中

4.1 数据存入数据库

1️⃣引入 JDBC 依赖:



    mysql
    mysql-connector-java
    5.1.49

2️⃣建库建表

create table messages (`from` varchar(255), `to` varchar(255), `message` varchar(2048));

3️⃣修改代码

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import static java.lang.System.load;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Lenovo
 * Date: 2023-06-02
 * Time: 19:56
 */
class Message {
    // 这几个属性必须设置 public !!!!
    // 如果设置 private, 必须生成 public 的 getter 和 setter !!!
    public String from;
    public String to;
    public String message;

    @Override
    public String toString() {
        return "Message{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    private List messageList = new ArrayList<>();
    //private List messageList = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过这个方法来处理 "获取所有留言消息"
        // 需要返回一个 json 字符串数组. jackson 直接帮我们处理好了格式.
        List messageList = load();
        String respString = objectMapper.writeValueAsString(messageList);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(respString);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过这个方法来处理 "提交新消息"
        Message message = objectMapper.readValue(req.getInputStream(), Message.class);
        messageList.add(message);
        save(message);
        System.out.println("消息提交成功! message=" + message);

        // 响应只是返回 200 报文. body 为空. 此时不需要额外处理. 默认就是返回 200 的.
    }

    // 这个方法用来往数据库中存一条记录
    private void save(Message message) {
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java107?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("2222");

        try {
            Connection connection = dataSource.getConnection();
            String sql = "insert into message values(?, ?, ?)";
            PreparedStatement statement = connection.prepareStatement(sql);
            statement.setString(1, message.from);
            statement.setString(2, message.to);
            statement.setString(3, message.message);
            statement.executeUpdate();

            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 这个方法用来从数据库查询所有记录
    private List load() {
        List messageList = new ArrayList<>();

        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java107?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("2222");

        try {
            Connection connection = dataSource.getConnection();
            String sql = "select * from message";
            PreparedStatement statement = connection.prepareStatement(sql);
            ResultSet resultSet = statement.executeQuery();

            while (resultSet.next()) {
                Message message = new Message();
                message.from = resultSet.getString("from");
                message.to = resultSet.getString("to");
                message.message = resultSet.getString("message");
                messageList.add(message);
            }

            resultSet.close();
            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return messageList;
    }
}

通过上述代码就已经写出一个很简单的网站;未来写的复杂网站都是这一套逻辑

1️⃣约定前后端交互接口

2️⃣实现服务器代码(通常回操作数据库)

3️⃣实现客户端代码(通常会使用 ajax 构造请求,并使用一些 js 的 webapi 操作页面内容)

 

你可能感兴趣的:(JavaSE,servlet,java,tomcat,交互,表白墙)