初阶JavaEE(15)(Cookie 和 Session、理解会话机制 (Session)、实现用户登录网页、上传文件网页、常用的代码片段)
接上次博客:初阶JavaEE(14)表白墙程序-CSDN博客
Cookie 和 Session
你还记得我们之前提到的Cookie吗?
Cookie是HTTP请求header中的一个属性,是一种用于在浏览器和服务器之间持久存储数据的机制,允许网站在多次HTTP请求之间保持状态它。
我们先来回忆一些知识点:
1. HTTP 协议的无状态特性
- HTTP协议是一种无状态协议,每个客户端请求都在单独的事务中处理,服务器不会记住前一次请求的信息。
- 无状态特性意味着默认情况下,服务器无法直接识别不同请求之间的关联关系。
2. Cookie 的作用
- Cookie是一种在客户端存储数据的机制,用于在多次HTTP请求之间保持状态信息。
- Cookie通常存储在浏览器端,包含有关用户或应用程序的数据。
- 服务器可以在HTTP响应头中设置Cookie,然后浏览器会将这些Cookie存储在客户端,并在将来的请求中将它们包括在HTTP请求头中。
3. 令牌概念
- 令牌是一种标识或身份验证凭据,通常存储在Cookie中。
- 令牌可以用于标识用户的身份或跟踪用户的会话状态。
- 类比就诊卡:用户登录网站后,服务器颁发一个令牌,就像患者得到就诊卡一样。
4. Cookie 的使用场景
- 用于记录用户的身份认证状态,例如用户登录后,服务器会发送包含身份验证令牌的Cookie。
- 用于跟踪用户的行为,例如购物车功能,保存在Cookie中,以在用户下次访问时还原购物车内容。
总之,网页无法访问主机的文件要想存储数据就得通过其他的方式。Cookie中保存的数据也是键值对的格式(用户自定义)。我们最终还是需要把这个键值对发送回服务器,因为服务器要使用Cookie来完成一些业务逻辑。
其中有一个特殊的情况,我们是会使用Cookie来存储当前用户的信息。
回忆我们之前举过的例子: 去医院挂号的就诊卡就相当于Cookie,里面存储了用户的身份信息。
-
挂号与Cookie设置:
- 患者挂号时提供身份证,就像用户在网站上登录时提供用户名和密码。在挂号后,患者得到了一张就诊卡,就像网站在登录成功后发送一个Cookie到用户的浏览器。
- 这张就诊卡相当于Cookie,其中包含有患者的身份信息,它被存储在患者的钱包中,就像Cookie存储在浏览器中。
-
就诊过程与Cookie的使用:
- 患者在不同科室进行检查、诊断和开药,不需要再次出示身份证,而是凭借就诊卡来识别患者,就像用户在浏览网站的不同页面时,浏览器会将Cookie自动包括在每个HTTP请求中,以识别用户。
- 这种方式使患者(或用户)的身份信息持久存在,而不必在每个步骤中重复验证身份。
-
注销操作与Cookie销毁:
- 如果患者不再需要就诊卡,可以选择注销它,就像用户在网站上注销帐户或清除浏览器中的Cookie。
- 注销就诊卡会销毁与患者的身份关联,类似于注销操作导致Cookie失效。
-
新就诊卡与新Cookie:
- 如果患者再次需要医院服务,可以申请一张新的就诊卡,这将为他们提供一个新的“令牌”,就像用户重新登录网站后会生成一个新的Cookie。
- 这是一种维护隐私和安全性的方式,以及确保用户/患者可以得到新的标识。
那除了身份标识,剩下的信息:基本信息、当前诊断信息、开的药品信息单子、既往病历、账户余额……,每个用户/患者都有一份这样的数据,这些数据在服务器中如何组织呢?
这些信息必然是要存储在数据库的,但是也不仅仅存储在数据库中。
在服务器代码的逻辑展开执行的过程中,这些数据也就会被从数据库中查询出来。
我们先把数据临时的保存到某个内存结构中,后续有什么修改之类的,就会去修改内存、重写写入数据库。
什么内存结构呢?——Session (会话)。
理解会话机制 (Session)
- 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的具体内容和相关使用。
Session是什么?
- Session 是一种在服务器端存储数据的机制,用于跟踪用户在网站上的活动和状态。
- 它通常不涉及持久化存储,而是在用户与服务器的会话期间保持数据。
- Session 可以存储用户的状态信息、身份认证信息以及其他用户特定的数据。
- Session机制通常依赖于Cookie来识别用户,但实际的用户数据存储在服务器上。
- 服务器为每个用户创建一个唯一的会话,并将会话ID存储在Cookie中。该会话ID用于将用户与其数据关联。
Cookie vs. Session:
- Cookie 是一种客户端存储数据的机制,通常是小型文本文件,存储在用户的浏览器中。
- Session 存储在服务器端,通常以键值对的形式存储在服务器内存中。
- 通常,会话(Session)与 Cookie 配合使用,其中 Cookie 中存储用户身份标识或会话 ID,服务器使用会话 ID 来检索或存储与该用户相关的数据。
核心方法
HttpServletRequest 类中的相关方法
1、HttpSession getSession() 方法:
- getSession() 方法用于在服务器中获取会话(Session)对象。能够完成从Cookie中获取到Session ID并且查询出对应的Session的过程。
- 这个方法有两个不同的重载形式:getSession(boolean create) 和 getSession()。
- 如果 create 参数为 true,则在服务器上创建一个新的会话(如果不存在的话),并返回该会话对象;如果 create 为 false,则当不存在会话时返回 null。
- 这个方法通常用于登录等获取与客户端相关联的会话,以便在会话中存储和检索用户特定的数据。会话是一种在用户与服务器之间保持状态的机制。
- Servlet中通过HttpSession这个类表示一个会话,服务器上同时具有多个会话,就会存在一个类似于这样的:HashMap<String,HttpSession>哈希表,里面的String就是Session ID,HttpSession就是Session 对象。如果当前Session ID没有从哈希表中查到,会自动创建出新的键值对(分配新的Session ID以及创建一个新的HttpSession对象)。
2、Cookie[ ] getCookies() 方法:
- getCookies() 方法用于获取客户端发送给服务器的所有的Cookie对象。
- 它返回一个Cookie数组,包含请求中包含的所有Cookie对象。
- 这个方法将自动将Cookie中的格式解析为键值对,以便你可以轻松地访问和操作Cookie的值。
- Cookie是一种客户端存储数据的机制,通常用于跟踪用户会话或存储其他客户端相关的信息。
这两个方法是Servlet开发中非常常用的,它们允许开发者在处理HTTP请求时获取和操作与会话和Cookie相关的数据。getSession() 用于处理与会话相关的操作,而getCookies() 用于获取并解析请求中的Cookie数据。
HttpServletResponse 类中的相关方法
void addCookie(Cookie cookie)
- void addCookie(Cookie cookie) 方法用于向HTTP响应中添加一个Cookie对象。
- 你可以通过创建一个 Cookie 对象并设置其属性,如名称、值、路径、域、过期时间等,然后使用 addCookie 方法将其添加到响应中。
- 一旦Cookie被添加到响应中,它将在响应头中以Set-Cookie标头的形式发送给客户端浏览器。
- 客户端浏览器会接收Cookie,并在后续的HTTP请求中将Cookie信息包含在请求头中,以便服务器可以识别和处理用户的会话或其他相关信息。
HttpSession 类中的相关方法
一个 HttpSession 对象里面包含多个键值对, 我们可以往 HttpSession 中存任何我们需要的信息。
1、Object getAttribute(String name) 方法:
- Object getAttribute(String name) 方法用于返回在该会话中具有指定名称的对象。
- 参数 name 是要查找的对象的名称。
- 返回值:如果在会话中存在具有指定名称的对象,该方法将返回相应的对象;如果不存在,则返回 null。
- 用途:通常用于检索在会话中存储的用户特定的数据。例如,你可以存储用户的用户名、配置选项、购物车内容等,并使用 getAttribute 方法获取这些数据。
2、void setAttribute(String name, Object value) 方法:
- void setAttribute(String name, Object value) 方法用于将一个对象绑定到会话(Session)中,使用指定的名称。
- 参数 name 是要绑定对象的名称,value 是要绑定的对象。
- 返回值:该方法没有返回值,因为它只是将对象存储在会话中。
- 用途:允许你在会话中存储数据,以便在会话期间的不同请求之间共享数据。你可以存储各种类型的数据对象,如用户身份信息、购物车内容、用户首选项等。
3、boolean isNew() 方法:
- boolean isNew() 方法用于判定当前的会话是否是新创建出的会话。
- 返回值:如果会话是新创建的,该方法返回 true;如果是已经存在的会话,则返回 false。
- 用途:通常用于检查会话是否刚刚开始,以便执行特定的操作。例如,你可以使用 isNew 方法来初始化用户状态或执行特定的逻辑仅在新会话期间执行。
这些方法一起提供了方便的方式来管理会话中的数据,并根据需要检查和操作会话状态。
Cookie 类中的相关方法
每个 Cookie 对象就是一个键值对。
1、String getName() 方法:
- String getName() 方法用于返回Cookie的名称。
- 名称是Cookie的唯一标识,一旦创建后就不能再更改。这个名称是通过Set-Cookie字段设置给浏览器,用于在客户端存储和检索数据。
- 返回值:返回Cookie的名称,通常是一个字符串。
- 用途:通常,你可以使用Cookie的名称来唯一标识和获取特定的Cookie对象。
2、String getValue() 方法:
- String getValue() 方法用于获取与Cookie关联的值。
- 每个Cookie都有一个与之关联的值,通常是一些数据,如用户名、会话ID等。
- 返回值:返回与Cookie关联的值,通常是一个字符串。
- 用途:这个方法允许你获取Cookie存储的具体数据,以便在客户端或服务器端使用。
3、setValue(String newValue) 方法:
- void setValue(String newValue) 方法用于设置与Cookie关联的值。
- 通过这个方法,你可以更改Cookie的值,从而更新Cookie中存储的数据。
- 参数 newValue 是你希望设置的新值。
- 返回值:该方法没有返回值,因为它只是设置Cookie的值。
- 用途:这个方法允许你在需要时更改Cookie的内容,以实现动态数据存储。
这些方法使得在Servlet中可以方便地操作Cookie对象的名称和值,允许你根据需要获取、设置和更新Cookie的内容。
总之,
- Cookie是一种键值对的数据结构,通过Cookie类的方法可以获取和设置Cookie的名称和值。
- HttpServletRequest类的getCookies()方法用于获取请求中的多个Cookie键值对。
- HttpServletResponse类的addCookie(Cookie cookie)方法用于将新的Cookie键值对添加到HTTP响应中,以向客户端发送Cookie数据。这些方法使得在Servlet中可以方便地操作Cookie数据。
我们基于上述的API就可以实现用户登录了!
Session 工作原理:
- 当用户第一次访问网站时,服务器为该用户创建一个唯一的 Session ID,并将其存储在 Cookie 中或作为URL参数传递给客户端。
- 服务器会为每个用户维护一个 Session 对象,通常使用哈希表(键值对)来存储。
- 用户在网站上的各种操作和请求会包含该 Session ID,以便服务器可以识别用户的会话。
- 服务器使用 Session ID 从 Session 存储中检索或存储与用户相关的数据。
- Session 数据可以包括用户身份、购物车内容、语言偏好等。
Session 的生命周期:
- Session 数据在用户会话期间保持活动,通常在用户登录到网站并退出登录后终止。
- 它可以具有固定的超时时间,如果用户在一段时间内没有活动,会话可能会被服务器终止。
- Session 也可以在用户关闭浏览器时终止,或者在服务器重启时丢失。
Session 的用途:
- 身份验证和授权:Session 可用于存储用户登录状态和权限信息,以确保用户已登录并有权限执行某些操作。
- 购物车和订单管理:Session 可用于存储用户的购物车内容和订单信息。
- 用户配置和首选项:Session 可以存储用户的语言偏好、主题选择和其他配置信息。
- 临时数据存储:Session 可用于在用户会话期间存储临时数据,以便在不同页面之间共享。
安全性考虑:
- Session 数据存储在服务器端,通常比 Cookie 更安全,因为它不容易被修改或窃取。
- 但要确保在使用 Session 时采取适当的安全措施,如防止会话固定攻击和数据泄露。
总结:
- Session 是一种在服务器端存储数据的机制,用于跟踪用户活动和状态。
- 通常与 Cookie 配合使用,通过存储会话 ID 来维护用户的会话数据。
- Session 可用于身份验证、购物车管理、用户首选项和其他用途。
接下来我们就来写一个“登录”程序:
实现的大致顺序如下:
1、登录页面的构建:
2、通过一个Servlet处理上述的登录请求:通过这个Servlet读取用户名和密码,并且验证是否登陆成功。如果登录成功,就会给当前这个用户创建一个对话,并且把得到的Session ID通过Cookie返回给客户端(客户端就把Cookie保存起来了)。并且还会保存一些用户当前的信息。
3、 网站主页,通过另一个Servlet生成的动态页面,把刚才这里的用户数据给显示到页面上。
实现用户登录
实现简单的用户登录逻辑 。
这个代码中主要是通过 HttpSession 类完成, 并不需要我们手动操作 Cookie 对象。
1、先来写一个登录页面
你懂的,先把该配置的目录还有文件配置好:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录</title>
</head>
<body><!-- 使用 form 表单, 实现登录效果 --><form action="login" method="post"><input type="text" name="username"><input type="text" name="password"><input type="submit" value="登录"></form>
</body>
</html>
配置好Tomcat之后运行起来是这样的:
此处我们可以约定一下:
2、创建 LoginServlet 类,处理登录请求:
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 {@Overrideprotected 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映射:
- 通过@WebServlet注解,这个Servlet被映射到URL路径/login,这意味着当用户访问网站的/login路径时,这个Servlet会被调用。
-
处理POST请求:
- 在doPost方法中,处理POST请求的逻辑被定义。
-
获取请求参数:
- 通过req.getParameter("username")和req.getParameter("password"),从请求中获取用户名和密码。
- 在这个示例中,为了简单起见,用户名和密码都是写死的("zhangsan"和"123"),并且没有与数据库交互。
- 这段代码通过req.setCharacterEncoding("utf8")设置了请求的字符集,以防止中文用户名导致乱码。
-
验证用户名和密码:
- 通过比较用户名和密码是否与预定义的值匹配,来验证登录是否成功。
- 如果用户名和密码不匹配,将返回一个包含错误消息的响应,告知用户登录失败。
-
创建会话(Session):
- 如果用户名和密码验证成功,将为用户创建一个会话。
- 会话对象通过req.getSession(true)来获取,如果会话不存在则会创建一个新的。
- 在会话中,存储了一些用户自定义的数据,如用户名和登录时间。这些数据以键值对的形式存储在会话中。
- 通过session.setAttribute("key", value)方法,将用户名和登录时间存储在会话中。
-
登录成功:
- 登录成功后,通过resp.sendRedirect("index")将用户重定向到网站的首页,即跳转到/index页面。
- 这表示用户已经成功登录,会话被创建,会话中包含用户相关的数据,用户可以访问其他受保护的页面并保持登录状态。
这段代码只是一个非常简单的用户登录处理Servlet示例,它给我们演示了如何从请求中获取参数、验证用户名和密码,如何创建和使用会话对象,以及如何将用户重定向到其他页面。在实际应用中,验证通常会与数据库交互,并会有更复杂的登录逻辑。
3、创建 IndexServlet 类:生成一个主页
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 {@Overrideprotected 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映射:
- 通过@WebServlet注解,这个Servlet被映射到URL路径/index,这意味着当用户访问网站的/index路径时,这个Servlet会被调用。
-
处理GET请求:
- 在doGet方法中,处理GET请求的逻辑被定义。
-
获取当前用户的会话对象:
- 通过req.getSession(false)来获取当前用户的会话对象。
- 如果用户没有会话,即会话不存在,或者SessionID在哈希表中没有对应的HttpSession对象,则会返回null。
- 如果session为null,说明用户尚未登录,会向用户返回一个消息提示,告知用户需要先登录才能访问主页。
-
从会话中获取用户信息:
- 如果会话存在(即用户已登录),则从会话中获取之前存储的用户信息。
- 通过session.getAttribute("key")方法,分别获取用户名("username")和上次登录时间("loginTime")。
- 需要注意,这里进行了类型转换,要确保类型是正确的,以避免运行时异常。这里假设存储在会话中的数据类型是正确的。
-
生成页面响应:
- 构建页面的响应内容,根据用户信息来显示不同的页面。
- 使用resp.getWriter().write()方法将响应内容返回给客户端。
- 在这个示例中,响应内容包括欢迎消息以及上次登录时间。
这段代码实现了一个根据用户登录状态动态生成主页的功能。如果用户已登录,它会显示欢迎消息和上次登录时间;如果用户尚未登录,它会显示一个提示消息。该示例演示了如何使用HttpSession来管理用户会话状态,以及如何在不同Servlet之间共享数据以提供个性化用户体验。内部的哈希表和SessionID的细节由Servlet容器自动处理,开发者只需要关注会话和数据的操作。
全部实现完成以后我们就可以来看看实现效果了:
1、点击登录之后就会触发一个前端的POST请求:
2、到了服务器这边:
3、重定向到主页(index) :
这个东西的应用在于:
-
持久的登录状态: 一旦用户成功登录,通常会话将维持一段时间,使用户在之后的访问中继续保持登录状态。这可以通过Cookie和Session来实现。Cookie通常用于在客户端存储会话标识,而Session在服务器端存储用户数据。这种持久的登录状态使得用户无需在每个页面请求中重新登录。
-
会话过期时间: 会话的持续时间是可以设置的。不同网站可以根据需求设置不同的会话过期时间。一些网站可能会设置长期的会话,以提供长时间的登录状态,而其他敏感性较高的网站可能会设置较短的会话,以增强安全性。
-
Cookie的过期时间: Cookie是在客户端存储的,可以设置Cookie的过期时间。一旦Cookie的过期时间到了,浏览器会自动删除该Cookie。这可以在创建Cookie时通过setMaxAge方法来设置。
-
Session的过期时间: Session是在服务器端管理的,也可以设置会话的过期时间。当会话过期,服务器会自动删除会话及其数据。过期时间可以通过Servlet容器或编程方式来设置。
-
共用计算机的风险: 如果多个人在同一台公共计算机上使用同一个登录状态的帐户,会产生潜在的安全风险。这是因为后续用户无需再次登录,可能会有权限进行一些敏感操作。因此,对于共用计算机,特别是在公共场所,用户应该注销或清除会话和Cookie,以确保不会被其他人滥用。
总之,通过Cookie和Session的合理管理,可以实现用户的登录状态维护,会话过期时间的设置允许网站根据需求进行调整,以平衡用户便利性和安全性。对于共用计算机的风险,用户应当采取适当的措施,如注销或清除登录状态,以保护账户的安全。
上传文件
上传文件也是日常开发中的一类常见需求。 在 Servlet 中也进行了支持。
1、文件上传基础:
- 文件上传是通过HTTP POST请求实现的。
- 在HTML表单中,通常使用<input type="file">标签来创建文件上传字段。
- 当用户选择文件并提交表单时,浏览器将文件数据包含在POST请求中的请求体中。
2、文件上传处理:
- 在Servlet中,你可以通过HttpServletRequest对象的getPart方法或getParts方法来处理上传的文件。
- getPart方法用于获取单个上传的文件,而getParts方法用于获取所有上传的文件。
- 你可以使用这些方法来获取文件的输入流,读取文件内容,并保存文件到服务器的文件系统或进行其他操作。
核心方法
HttpServletRequest 类方法
1、Part getPart(String name):
- 该方法用于获取请求中给定名称(name)的文件。
- 它返回一个Part对象,代表上传的文件。你可以使用Part对象来获取文件名、输入流、文件大小等信息。
Part filePart = request.getPart("file");
2、Collection<Part> getParts():
- 该方法用于获取请求中的所有文件。
- 它返回一个Collection对象,其中包含所有上传的文件(Part对象)。
- 你可以遍历Collection来处理多个上传的文件。
Collection<Part> parts = request.getParts(); for (Part part : parts) {// 处理每个上传的文件(Part) }
这些方法使你能够有效地处理文件上传,并在Servlet中访问上传的文件的内容、文件名、大小等信息。在处理文件上传时,确保验证文件类型、限制文件大小,并处理潜在的安全风险,以保护应用程序的安全性。
Part 类方法
1、String getSubmittedFileName():
- 该方法用于获取提交的文件名。它返回一个字符串,表示上传文件的名称。
- 通常用于获取上传文件的原始文件名。
2、String getContentType():
- 该方法用于获取提交的文件的类型(MIME类型)。它返回一个字符串,表示上传文件的类型。
- 用于识别文件的内容类型,例如"image/jpeg"或"application/pdf"。
3、long getSize():
- 该方法用于获取文件的大小,以字节为单位。它返回一个long值,表示上传文件的大小。
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 目录中。
<form action="upload" enctype="multipart/form-data" method="POST" accept-charset="UTF-8"><meta charset="UTF-8"><input type="file" name="MyImage"><input type="submit" value="提交图片">
</form>
文件上传通常通过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映射:
- 通过@WebServlet("/upload")注解,将Servlet映射到URL路径"/upload",以便处理文件上传请求。
-
doPost
方法:- 当收到POST请求时,doPost方法被调用来处理文件上传。
- 首先,通过request.getPart("MyImage")获取名为"MyImage"的文件上传部分(
Part
对象)。 - 接着,使用自定义方法getSubmittedFileName从Part对象中获取提交的文件名。
-
文件保存:
- 确定了文件名后,指定了服务器上的保存路径,这个路径是基于应用程序的上下文路径。
- 然后,使用InputStream从Part对象中获取文件数据,将数据写入指定的保存路径,完成文件的保存。
-
响应:
- 最后,通过response.getWriter().write("文件 " + fileName + " 上传成功!")向客户端返回成功消息。
-
文件上传功能:
- 该Servlet实现了文件上传功能,客户端可以通过HTML表单选择文件并提交到"/upload"路径。
- 上传的文件将被保存在服务器上的"/uploads/"目录中。
- 上传成功后,Servlet返回一条成功消息。
-
getSubmittedFileName方法:
- 这个方法用于从Part对象的content-disposition头中提取文件名,以确保获取到正确的文件名。
总之,这段代码实现了文件上传的功能,包括接收上传的文件、获取文件名、将文件保存到服务器,并向客户端发送成功消息。这是一个基本的文件上传示例,可以根据需要进行进一步的定制和改进,例如添加文件类型验证、文件大小限制等。
需要注意的是:
- 需要给 UploadServlet 加上 @MultipartConfig 注解, 否则服务器代码无法使用 getPart 方法
- getPart 的参数需要和 form 中 input 标签的 name 属性对应。
- 客户端一次可以提交多个文件, (使用多个 input 标签)。 此时服务器可以通过 getParts 获取所有的 Part 对象。
先在我们的wepapp目录下构建一个新的子目录“upload”,用来存放我们要上传的图片文件(我已经提交了一些,你只需要创建出空的目录即可):
不管是哪个地方的图片,都可以顺利的被提交到我们的upload目录下:
我们也可以抓个包看看:
可以看到 Content-Type 为 multipart/form-data ,这样的请求中带有一个
boundary = ---- WebKitFormBoundaryxt86TAqMzDsINVMA ,
这个 boundary 在 body 这边作为一个 “分隔线”, 分隔线下面是上传的文件的属性和文件内容。
常用代码片段
最后的最后,我把一些常用代码片段罗列在这里, 后续我们写代码的时候可以在这个基础上拷贝过去直接修改:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>Login</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.15.0</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency></dependencies>
</project>
web.xml:
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app><display-name>Archetype Created Web Application</display-name>
</web-app>
hello world:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {resp.getWriter().write("hello");}
}
读取请求报头:
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {String contentType = req.getHeader("Content-Type");// 或者使用String contentType = req.getContentType();}
}
读取 GET 请求的 query string:
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {String userId = req.getParameter("userId");String classId = req.getParameter("classId");}
}
读取 POST 请求的 body:
@WebServlet("/postParameter")
public class PostParameter extends HttpServlet {@Overrideprotected 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 {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {resp.setStatus(200);}
}
设置响应报头:
@WebServlet("/autoRefreshServlet")
public class AutoRefreshServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {resp.setHeader("Refresh", "1");}
}
重定向:
@WebServlet("/redirectServlet")
public class RedirectServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {resp.sendRedirect("http://www.sogou.com");}
}
登录页面:
<form action="login" method="POST"><input type="text" name="username"><input type="password" name="password"><input type="submit" value="提交">
</form>
创建新 Session:
@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {HttpSession session = req.getSession(true);session.setAttribute("username", "admin");session.setAttribute("loginCount", "0");}
}
获取已有 Session:
@WebServlet("/login")
public class LoginServlet extends HttpServlet { HttpSession session = req.getSession(false);if (session == null) {// 用户没有登陆, 重定向到 login.htmlresp.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 {@Overrideprotected 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");}
}
<form action="upload" enctype="multipart/form-data" method="POST"><input type="file" name="MyImage"><input type="submit" value="提交图片">
</form>
相关文章:

初阶JavaEE(15)(Cookie 和 Session、理解会话机制 (Session)、实现用户登录网页、上传文件网页、常用的代码片段)
接上次博客:初阶JavaEE(14)表白墙程序-CSDN博客 Cookie 和 Session 你还记得我们之前提到的Cookie吗? Cookie是HTTP请求header中的一个属性,是一种用于在浏览器和服务器之间持久存储数据的机制,允许网站…...

C++入门学习(1)命名空间和输入输出
前言 在C语言和基本的数据结构学习之后,我们终于迎来了期待已久的C啦!C发明出来的意义就是填补一些C语言的不足,让我们更加方便的写代码,所以今天我们就来讲一下C语言不足的地方和在C中的解决办法! 一、命名空间 在学习…...

AI:58-基于深度学习的猫狗图像识别
🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…...

【原创】java+swing+mysql宠物领养管理系统设计与实现
摘要: 生活中,有很多被人遗弃的宠物,这些宠物的处理成为了一个新的难题。生活中也有许多人喜欢养宠物,为了方便大家进行宠物领养,提高宠物领养管理的效率和便利性。本文针对这一问题,提出设计和实现一个基…...

虚拟机Linux-Centos系统网络配置常用命令+Docker 的常用命令
目录 1、虚拟机Linux-Centos系统网络配置常用命令2、Docker 的常用命令2.1 安装docker步骤命令2.2 在docker容器中安装和运行mysql 2、dockerfile关键字区别(ADD/COPY,CMD/ENTRYPOINT) 1、虚拟机Linux-Centos系统网络配置常用命令 进入网络配置文件目录 cd /etc/sysconfig/ne…...

数据分析相关知识整理_--秋招面试版
一、关于sql语句(常问) 1)sql写过的复杂的运算 聚合函数,case when then end语句进行条件运算,字符串的截取、替换,日期的运算,排名等等;行列转换; eg:行列转换 SELE…...

HMM与LTP词性标注之命名实体识别与HMM
文章目录 知识图谱介绍NLP应用场景知识图谱(Neo4j演示)命名实体识别模型架构讲解HMM与CRFHMM五大要素(两大状态与三大概率)HMM案例分享HMM实体识别应用场景代码实现 知识图谱介绍 NLP应用场景 图谱的本质,就是把自然…...

Sui发布RPC2.0 Beta,拥抱GraphQL并计划弃用JSON-RPC
为了解决现有RPC存在的许多已知问题,Sui正在准备推出一个基于GraphQL的新RPC服务,名为Sui RPC 2.0。GraphQL是一种开源数据查询和操作语言,旨在简化需要复杂数据查询的API和服务。 用户目前可以访问Sui主网和测试网网络的Beta版本的只读快照…...

设计模式—结构型模式之桥接模式
设计模式—结构型模式之桥接模式 将抽象与实现解耦,使两者都可以独立变化。 在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不…...

【RabbitMQ】RabbitMQ 消息的堆积问题 —— 使用惰性队列解决消息的堆积问题
文章目录 一、消息的堆积问题1.1 什么是消息的堆积问题1.2 消息堆积的解决思路 二、惰性队列解决消息堆积问题2.1 惰性队列和普通队列的区别2.2 惰性队列的声明方式2.3 演示惰性队列接收大量消息2.4 惰性队列的优缺点 一、消息的堆积问题 1.1 什么是消息的堆积问题 消息的堆积…...

深度优先遍历与连通分量
深度优先遍历(Depth First Search)的主要思想是首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点。当没有未访问过的顶点时,则回到上一个顶点,继续试探别的顶点,直至所有的顶点都被访问过。 下图示例的…...

Python学习笔记--类的继承
七、类的继承 1、定义类的继承 说到继承,你一定会联想到继承你老爸的家产之类的。 类的继承也是一样。 比如有一个旧类,是可以算平均数的。然后这时候有一个新类,也要用到算平均数,那么这时候我们就可以使用继承的方式。新类继…...

全自动批量AI改写文章发布软件【软件脚本+技术教程】
项目原理: 利用AI工具将爆款文章改写发布到平台上流量变现,通过播放量赚取收益 软件功能: 1.可以根据你选的文章领域,识别你在网站上抓取的文章链接进来自动洗稿生成过原创的文章,自动配图 2.同时还可以将管理的账号导入进脚本软…...

strongswan:configure: error: OpenSSL Crypto library not found
引子 在配置strongswan时,有时会遇到以下错误(其实所有需要openssl的软件configure时都有可能遇到该问题): configure: error: OpenSSL Crypto library not found 解决方法 crypto是什么呢? 是OpenSSL 加密库(lib), 这个库需要op…...

Xcode 常见错误
1. Xcode 15 编译出现以下错误 clang: error: SDK does not contain libarclite at the path /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphonesimulator.a; try increasing the minimum deployment target 从…...

【JavaEE】实现简单博客系统-前端部分
文件目录: 展示: blog_list.html: <!DOCTYPE html> <html lang"cn"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><t…...

首发scitb包,一个为制作统计表格而生的R包
目前,本人写的第3个R包scitb包已经正式在R语言官方CRAN上线,scitb包是一个为生成专业化统计表格而生的R包。 可以使用以下代码安装 install.packages("scitb")scitb包对我而言是个很重要的R包,我的很多想法需要靠它做平台来实现&a…...

2023-11-06 LeetCode每日一题(最大单词长度乘积)
2023-11-06每日一题 一、题目编号 318. 最大单词长度乘积二、题目链接 点击跳转到题目位置 三、题目描述 给你一个字符串数组 words ,找出并返回 length(words[i]) * length(words[j]) 的最大值,并且这两个单词不含有公共字母。如果不存在这样的两个…...

numpy机器学习深度学习 常用函数
Python numpy(np)创建空的字符串数组、矩阵。解决数组中每个元素仅保留单个字符,无法完整填入字符串。 matrix1np.zeros(shape(31,22)).astype(np.str_) matrix1[matrix1 0.0] 1.reshape()方法 作用是将数据按照指定的维度重新组织并返回。也就是reshape&#x…...

连接器切断机维修
目录 起因 机器出现的问题排查 问题 检查 维修方法 今天也开始了设备的维修记录,今天出问题的是连接器切断器的维护! 起因 “连接器切断机坏了,有没有维修的,机器不动了,没有报警,没有断电和气管的泄漏&…...

Mysql数据库 8.SQL语言 外键约束
一、外键约束 外键约束——将一个列添加外键约束与另一张表的主键(唯一列)进行关联之后,这个外键约束的列添加的数据必须要在关联的主键字段中存在 案例 创建原则:先创建不含外键的表也就是班级表 添加外键的方式 一般使用第一…...

ERROR in static/js/xxx.js from UglifyJs Unexpected token name «currentVersion»
添加链接描述 ERROR in static/js/xxx.js from UglifyJs Unexpected token name currentVersion, expected punc 遇到这种异常, 需要运行下面脚本运行npm i -D uglifyjs-webpack-pluginbeta修改webpack.prod.conf.jsjs中引入参数const UglifyJsPlugin require(uglifyjs-webpa…...

反序列化 [网鼎杯 2020 青龙组]AreUSerialz 1
打开题目 <?phpinclude("flag.php");highlight_file(__FILE__);class FileHandler {protected $op;protected $filename;protected $content;function __construct() {$op "1";$filename "/tmp/tmpfile";$content "Hello World!&qu…...

JWT登录校验
工作原理 下面来详细看看 UTF-8 是如何工作的,以及为什么它会根据被编码的字符具有不同的长度。 一、JWT是什么? 在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程: 1、客户端使用用户名和密码请求登录 2、服务端…...

python发送企业微信群webhook消息(文本、文件)
import datetime import os import time from copy import copyimport requests from loguru import logger from urllib3 import encode_multipart_formdataclass WeiXin_Robot:def __init__(self,url: str ""):# 测试cartest_url "https://qyapi.weixin.qq.…...

高数笔记06:无穷级数
图源:文心一言 时间比较紧张,仅导图~~🥝🥝 第1版:查资料、画导图~🧩🧩 参考资料:《高等数学 基础篇》武忠祥 🐳目录 🐳常数项级数 🐋概要 &…...

Android工具栏ToolBar
主流APP除了底部有一排标签栏外,通常顶部还有一排导航栏。在Android5.0之前,这个顶部导航栏以ActionBar控件的形式出现,但AcionBar存在不灵活、难以扩展等毛病,所以Android5.0之后推出了ToolBar工具栏控件,意在取代Aci…...

2.3 - 网络协议 - ICMP协议工作原理,报文格式,抓包实战
「作者主页」:士别三日wyx 「作者简介」:CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」:对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 ICMP协议 1、ICMP协议工作原理2、ICMP协议报文格式…...

北京陪诊小程序|陪诊系统开发|陪诊小程序未来发展不可小觑
近几年随着互联网快速发展,各行业领域都比较注重线上服务系统,通过陪诊小程序开发可以满足更多用户使用需求,同时还能提高用户使用体验。现在陪诊类的软件应用得到全面推广,在医疗行业当中陪诊小程序更贴近用户生活,可…...

前端面试题总结(一)
1. vue性能优化 v-if和v-show使用:频繁切换使用v-show(display样式),反之使用v-if(删除与新值DOM)v-for必须加key,不能使用index当作key(使用index,如果数组发生变化&am…...