接上次博客:初阶JavaEE(14)表白墙程序-CSDN博客
你还记得我们之前提到的Cookie吗?
Cookie是HTTP请求header中的一个属性,是一种用于在浏览器和服务器之间持久存储数据的机制,允许网站在多次HTTP请求之间保持状态它。
我们先来回忆一些知识点:
总之,网页无法访问主机的文件要想存储数据就得通过其他的方式。Cookie中保存的数据也是键值对的格式(用户自定义)。我们最终还是需要把这个键值对发送回服务器,因为服务器要使用Cookie来完成一些业务逻辑。
其中有一个特殊的情况,我们是会使用Cookie来存储当前用户的信息。
回忆我们之前举过的例子: 去医院挂号的就诊卡就相当于Cookie,里面存储了用户的身份信息。
挂号与Cookie设置:
就诊过程与Cookie的使用:
注销操作与Cookie销毁:
新就诊卡与新Cookie:
那除了身份标识,剩下的信息:基本信息、当前诊断信息、开的药品信息单子、既往病历、账户余额……,每个用户/患者都有一份这样的数据,这些数据在服务器中如何组织呢?
这些信息必然是要存储在数据库的,但是也不仅仅存储在数据库中。
在服务器代码的逻辑展开执行的过程中,这些数据也就会被从数据库中查询出来。
我们先把数据临时的保存到某个内存结构中,后续有什么修改之类的,就会去修改内存、重写写入数据库。
什么内存结构呢?——Session (会话)。
这两者之间往往会配合使用,在cookie中存储的用户身份标识,也经常会被理解成Session ld。服务器会存储很多的Session,每个用户都有一个自己的Session,也有不同的Session ld。
服务器会通过类似于哈希表这样的键值对来存储Session,Session ld就是key,Session 本身就是value,在Session里面又可以存储各种用户自身的信息(程序猿自定义的)。
截止到现在,我们已经学到的大量的概念都是和“键值对”有关的:
是的,许多计算机科学和编程中的概念都与键值对(key-value pairs)有关,这种数据结构非常常见,用于存储和组织各种类型的数据。在不同的上下文中,键值对可以被用来处理和表示不同种类的信息,如:
Query String: 在URL中,查询字符串通常是一组键值对,用于向服务器传递参数和数据。
Header: HTTP请求和响应头部是由键值对组成的,它们包含了关于请求或响应的元信息。
Body (Form): 在HTML表单提交中,表单字段通常用键值对的方式传递给服务器,以便处理表单数据。
Body (JSON): JSON(JavaScript Object Notation)是一种数据格式,它以键值对的形式表示数据对象的属性和值。
Cookie: Cookie 是一种客户端存储数据的机制,通常由键值对组成,用于跟踪用户和存储会话信息。
Session: 会话数据通常是通过键值对的方式管理,用于在用户与服务器之间保持状态和信息。
Session 数据内容: Session 数据内容通常以键值对的形式组织,其中键表示属性或字段名称,值表示相应的数据值,用于存储用户状态和相关信息。
键值对是一种灵活且通用的数据结构,可用于处理各种不同的数据需求,从简单的查询参数到复杂的数据对象。它们在编程和数据存储中起到关键作用,使数据的组织和访问更加方便和有效。
综上,可以说,键值对贯穿整个编程生涯,是一个非常重要的概念。
我们可以通过Servlet API 来操作上述结构:
Cookie是浏览器的机制,Servlet提供了API来获取Cookie;
Session是服务器的机制,Servlet内部已经实现好了,也提供了API供我们使用。
我们马上来看看Session的具体内容和相关使用。
1、HttpSession getSession() 方法:
2、Cookie[ ] getCookies() 方法:
这两个方法是Servlet开发中非常常用的,它们允许开发者在处理HTTP请求时获取和操作与会话和Cookie相关的数据。getSession() 用于处理与会话相关的操作,而getCookies() 用于获取并解析请求中的Cookie数据。
void addCookie(Cookie cookie)
一个 HttpSession 对象里面包含多个键值对, 我们可以往 HttpSession 中存任何我们需要的信息。
1、Object getAttribute(String name) 方法:
2、void setAttribute(String name, Object value) 方法:
3、boolean isNew() 方法:
这些方法一起提供了方便的方式来管理会话中的数据,并根据需要检查和操作会话状态。
每个 Cookie 对象就是一个键值对。
1、String getName() 方法:
2、String getValue() 方法:
3、setValue(String newValue) 方法:
这些方法使得在Servlet中可以方便地操作Cookie对象的名称和值,允许你根据需要获取、设置和更新Cookie的内容。
总之,
我们基于上述的API就可以实现用户登录了!
接下来我们就来写一个“登录”程序:
实现的大致顺序如下:
1、登录页面的构建:
2、通过一个Servlet处理上述的登录请求:通过这个Servlet读取用户名和密码,并且验证是否登陆成功。如果登录成功,就会给当前这个用户创建一个对话,并且把得到的Session ID通过Cookie返回给客户端(客户端就把Cookie保存起来了)。并且还会保存一些用户当前的信息。
3、 网站主页,通过另一个Servlet生成的动态页面,把刚才这里的用户数据给显示到页面上。
实现简单的用户登录逻辑 。
这个代码中主要是通过 HttpSession 类完成, 并不需要我们手动操作 Cookie 对象。
登录
配置好Tomcat之后运行起来是这样的:
此处我们可以约定一下:
package org.example;
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.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 读取请求传来的参数 (用户名和密码)
// 最好先给请求设置一下字符集. 否则如果 username 是中文, 此处的 getParameter 可能会乱码.
req.setCharacterEncoding("utf8");
String username = req.getParameter("username");
String password = req.getParameter("password");
// 2. 验证用户名密码, 是否是正确的. 一般来说, 验证用户名密码, 是要通过数据库的.
// 此处为了简单一点, 先把用户名和密码, 写死. 比如此处假设正确的用户名是 zhangsan, 正确的密码是 123
// 此处还要注意, 上述 getParameter 可能会拿到 null . 为了避免空指针异常, 下面这种比较方式是更合适的写法.
// equqls内部能够针对参数为null做好处理
if (!"zhangsan".equals(username) || !"123".equals(password)) {
// 登录失败
// 给用户返回一个提示.
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前的用户名或者密码错误!");
return;
}
// 3. 登录成功了, 给这个用户创建一个会话出来.
// 可以给会话中保存一些自定义的数据, 通过 Attribute 的方式来保存.
//
HttpSession session = req.getSession(true);
// 此处 Attribute 也是键值对. 这里的内容存储什么都可以. 程序员自定义的.
// 这样的数据存储好了之后, 后续跳转到其他的页面, 也随时可以把这个数据从会话中取出来.
session.setAttribute("username", username);
session.setAttribute("loginTime", System.currentTimeMillis());
// 4. 此时相当于登录成功!! 让页面跳转到网站首页.
resp.sendRedirect("index");
}
}
服务器中可能有多个 Servlet 类,这些 Servlet 要处理不同的请求,但是现在这些 Servlet 都可以获取到刚刚这个 session 对象,进一步的就能拿到这里存储的 Attribute,也就实现了不同Servlet之间共享数据的效果。
当用户成功登录并创建了一个会话(HttpSession)后,会话中的属性(Attribute)可以在应用程序的不同Servlet中共享。每个Servlet都可以通过会话对象访问和修改这些属性,以提供一致的用户体验。
这对于在不同页面之间传递用户数据和状态信息非常有用,例如用户身份认证信息、购物车内容、用户首选项等。而且,这种数据的存储和访问是线程安全的,因为Servlet容器会确保会话的正确管理。
具体分析一下我们的代码:
Servlet声明和URL映射:
处理POST请求:
获取请求参数:
验证用户名和密码:
创建会话(Session):
登录成功:
这段代码只是一个非常简单的用户登录处理Servlet示例,它给我们演示了如何从请求中获取参数、验证用户名和密码,如何创建和使用会话对象,以及如何将用户重定向到其他页面。在实际应用中,验证通常会与数据库交互,并会有更复杂的登录逻辑。
package org.example;
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.servlet.http.HttpSession;
import java.io.IOException;
// 通过这个 Servlet 生成一个 主页
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 先获取到当前用户对应的会话对象. 生成的页面要根据当前用户信息来构造.
HttpSession session = req.getSession(false);
if (session == null) {
// sessionId 不存在, 或者 sessionId 没有在 hash 表中查到.
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("您当前尚未登录!");
return;
}
// 2. 从会话中拿到之前存储的用户信息
// 此处的强转, 需要程序员自行保证, 类型是靠谱的.
String username = (String) session.getAttribute("username");
Long loginTime = (Long) session.getAttribute("loginTime");
// 3. 生成一个页面, 把上述数据显示到页面上.
resp.setContentType("text/html; charset=utf8");
String respBody = "欢迎你 " + username + "! 上次登录时间为: " + loginTime;
resp.getWriter().write(respBody);
}
}
Servlet声明和URL映射:
处理GET请求:
获取当前用户的会话对象:
从会话中获取用户信息:
生成页面响应:
这段代码实现了一个根据用户登录状态动态生成主页的功能。如果用户已登录,它会显示欢迎消息和上次登录时间;如果用户尚未登录,它会显示一个提示消息。该示例演示了如何使用HttpSession来管理用户会话状态,以及如何在不同Servlet之间共享数据以提供个性化用户体验。内部的哈希表和SessionID的细节由Servlet容器自动处理,开发者只需要关注会话和数据的操作。
全部实现完成以后我们就可以来看看实现效果了:
2、到了服务器这边:
3、重定向到主页(index) :
这个东西的应用在于:
持久的登录状态: 一旦用户成功登录,通常会话将维持一段时间,使用户在之后的访问中继续保持登录状态。这可以通过Cookie和Session来实现。Cookie通常用于在客户端存储会话标识,而Session在服务器端存储用户数据。这种持久的登录状态使得用户无需在每个页面请求中重新登录。
会话过期时间: 会话的持续时间是可以设置的。不同网站可以根据需求设置不同的会话过期时间。一些网站可能会设置长期的会话,以提供长时间的登录状态,而其他敏感性较高的网站可能会设置较短的会话,以增强安全性。
Cookie的过期时间: Cookie是在客户端存储的,可以设置Cookie的过期时间。一旦Cookie的过期时间到了,浏览器会自动删除该Cookie。这可以在创建Cookie时通过setMaxAge方法来设置。
Session的过期时间: Session是在服务器端管理的,也可以设置会话的过期时间。当会话过期,服务器会自动删除会话及其数据。过期时间可以通过Servlet容器或编程方式来设置。
共用计算机的风险: 如果多个人在同一台公共计算机上使用同一个登录状态的帐户,会产生潜在的安全风险。这是因为后续用户无需再次登录,可能会有权限进行一些敏感操作。因此,对于共用计算机,特别是在公共场所,用户应该注销或清除会话和Cookie,以确保不会被其他人滥用。
总之,通过Cookie和Session的合理管理,可以实现用户的登录状态维护,会话过期时间的设置允许网站根据需求进行调整,以平衡用户便利性和安全性。对于共用计算机的风险,用户应当采取适当的措施,如注销或清除登录状态,以保护账户的安全。
上传文件也是日常开发中的一类常见需求。 在 Servlet 中也进行了支持。
1、Part getPart(String name):
Part filePart = request.getPart("file");
2、Collection
Collection parts = request.getParts();
for (Part part : parts) {
// 处理每个上传的文件(Part)
}
这些方法使你能够有效地处理文件上传,并在Servlet中访问上传的文件的内容、文件名、大小等信息。在处理文件上传时,确保验证文件类型、限制文件大小,并处理潜在的安全风险,以保护应用程序的安全性。
1、String getSubmittedFileName():
2、String getContentType():
3、long getSize():
4、void write(String path):
Part filePart = request.getPart("file");
String fileName = filePart.getSubmittedFileName();
String contentType = filePart.getContentType();
long fileSize = filePart.getSize();
// 将文件保存到服务器上
String savePath = "path/to/save/uploads/" + fileName;
filePart.write(savePath);
这些Part类的方法允许你访问上传文件的信息和内容,从而进行文件上传处理,包括获取文件名、文件类型、文件大小,以及将文件数据保存到服务器文件系统中。
我们可以简单运用一下这些API,实现程序,通过网页提交一个图片到服务器上。
1. 创建 upload.html, 放到 webapp 目录中。
文件上传通常通过POST请求的表单来实现,同时需要确保表单使用multipart/form-data编码类型以支持文件上传。
这是因为multipart/form-data允许在HTTP请求中传输二进制数据,包括文件。
2. 创建 UploadServlet 类 。
package org.example;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Part;
import java.io.*;
import java.net.URLDecoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@MultipartConfig
@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
// 获取上传的文件
Part filePart = request.getPart("MyImage");
// 获取文件名
String fileName = getSubmittedFileName(filePart);
// 在获取文件名之前,对文件名进行解码
String decodedFileName = URLDecoder.decode(fileName, "UTF-8");
// 指定服务器上的保存路径
String savePath = getServletContext().getRealPath("/uploads/") + File.separator + fileName;
// 将文件保存到服务器
try (InputStream input = filePart.getInputStream();
OutputStream output = new FileOutputStream(savePath)) {
int bytesRead;
byte[] buffer = new byte[4096];
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
}
response.setContentType("text/html; charset=utf8");
response.getWriter().write("文件 " + fileName + " 上传成功!");
}
private String getSubmittedFileName(Part part) {
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String fileName = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
return fileName;
}
}
return null;
}
}
这段代码是一个Servlet,用于处理文件上传的POST请求。
Servlet映射:
doPost
方法:
Part
对象)。文件保存:
响应:
文件上传功能:
getSubmittedFileName方法:
总之,这段代码实现了文件上传的功能,包括接收上传的文件、获取文件名、将文件保存到服务器,并向客户端发送成功消息。这是一个基本的文件上传示例,可以根据需要进行进一步的定制和改进,例如添加文件类型验证、文件大小限制等。
需要注意的是:
先在我们的wepapp目录下构建一个新的子目录“upload”,用来存放我们要上传的图片文件(我已经提交了一些,你只需要创建出空的目录即可):
不管是哪个地方的图片,都可以顺利的被提交到我们的upload目录下:
我们也可以抓个包看看:
可以看到 Content-Type 为 multipart/form-data ,这样的请求中带有一个
boundary = ---- WebKitFormBoundaryxt86TAqMzDsINVMA ,
这个 boundary 在 body 这边作为一个 “分隔线”, 分隔线下面是上传的文件的属性和文件内容。
最后的最后,我把一些常用代码片段罗列在这里, 后续我们写代码的时候可以在这个基础上拷贝过去直接修改:
4.0.0
org.example
Login
1.0-SNAPSHOT
8
8
UTF-8
javax.servlet
javax.servlet-api
4.0.1
provided
com.fasterxml.jackson.core
jackson-databind
2.15.0
mysql
mysql-connector-java
5.1.47
Archetype Created Web Application
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().write("hello");
}
}
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String contentType = req.getHeader("Content-Type");
// 或者使用
String contentType = req.getContentType();
}
}
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String userId = req.getParameter("userId");
String classId = req.getParameter("classId");
}
}
@WebServlet("/postParameter")
public class PostParameter extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
req.setCharacterEncoding("utf-8");
String userId = req.getParameter("userId");
String classId = req.getParameter("classId");
resp.getWriter().write("userId: " + userId + ", " + "classId: " +
classId);
}
}
@WebServlet("/statusServlet")
public class StatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setStatus(200);
}
}
@WebServlet("/autoRefreshServlet")
public class AutoRefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setHeader("Refresh", "1");
}
}
@WebServlet("/redirectServlet")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.sendRedirect("http://www.sogou.com");
}
}
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession(true);
session.setAttribute("username", "admin");
session.setAttribute("loginCount", "0");
}
}
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
HttpSession session = req.getSession(false);
if (session == null) {
// 用户没有登陆, 重定向到 login.html
resp.sendRedirect("login.html");
return;
}
// 如果已经登陆, 则从 Session 中取出数据
String userName = (String)session.getAttribute("username");
String countString = (String)session.getAttribute("loginCount");
}
@MultipartConfig
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Part part = req.getPart("MyImage");
System.out.println(part.getSubmittedFileName());
System.out.println(part.getContentType());
System.out.println(part.getSize());
part.write("d:/MyImage.jpg");
resp.getWriter().write("upload ok");
}
}