初阶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,里面存储了用户的身份信息。

  1. 挂号与Cookie设置

    • 患者挂号时提供身份证,就像用户在网站上登录时提供用户名和密码。在挂号后,患者得到了一张就诊卡,就像网站在登录成功后发送一个Cookie到用户的浏览器。
    • 这张就诊卡相当于Cookie,其中包含有患者的身份信息,它被存储在患者的钱包中,就像Cookie存储在浏览器中。
  2. 就诊过程与Cookie的使用

    • 患者在不同科室进行检查、诊断和开药,不需要再次出示身份证,而是凭借就诊卡来识别患者,就像用户在浏览网站的不同页面时,浏览器会将Cookie自动包括在每个HTTP请求中,以识别用户。
    • 这种方式使患者(或用户)的身份信息持久存在,而不必在每个步骤中重复验证身份。
  3. 注销操作与Cookie销毁

    • 如果患者不再需要就诊卡,可以选择注销它,就像用户在网站上注销帐户或清除浏览器中的Cookie。
    • 注销就诊卡会销毁与患者的身份关联,类似于注销操作导致Cookie失效。
  4. 新就诊卡与新Cookie

    • 如果患者再次需要医院服务,可以申请一张新的就诊卡,这将为他们提供一个新的“令牌”,就像用户重新登录网站后会生成一个新的Cookie。
    • 这是一种维护隐私和安全性的方式,以及确保用户/患者可以得到新的标识。

那除了身份标识,剩下的信息:基本信息、当前诊断信息、开的药品信息单子、既往病历、账户余额……,每个用户/患者都有一份这样的数据,这些数据在服务器中如何组织呢?

这些信息必然是要存储在数据库的,但是也不仅仅存储在数据库中。

在服务器代码的逻辑展开执行的过程中,这些数据也就会被从数据库中查询出来。

我们先把数据临时的保存到某个内存结构中,后续有什么修改之类的,就会去修改内存、重写写入数据库。

什么内存结构呢?——Session (会话)。

理解会话机制 (Session)

  • Cookie是客户端存储数据的机制;
  • Session是服务器存储的数据的机制(不算持久化存储)。

这两者之间往往会配合使用,在cookie中存储的用户身份标识,也经常会被理解成Session ld。服务器会存储很多的Session,每个用户都有一个自己的Session,也有不同的Session ld。

服务器会通过类似于哈希表这样的键值对来存储Session,Session ld就是key,Session 本身就是value,在Session里面又可以存储各种用户自身的信息(程序猿自定义的)。

 

截止到现在,我们已经学到的大量的概念都是和“键值对”有关的:

是的,许多计算机科学和编程中的概念都与键值对(key-value pairs)有关,这种数据结构非常常见,用于存储和组织各种类型的数据。在不同的上下文中,键值对可以被用来处理和表示不同种类的信息,如:

  1. Query String: 在URL中,查询字符串通常是一组键值对,用于向服务器传递参数和数据。

  2. Header: HTTP请求和响应头部是由键值对组成的,它们包含了关于请求或响应的元信息。

  3. Body (Form): 在HTML表单提交中,表单字段通常用键值对的方式传递给服务器,以便处理表单数据。

  4. Body (JSON): JSON(JavaScript Object Notation)是一种数据格式,它以键值对的形式表示数据对象的属性和值。

  5. Cookie: Cookie 是一种客户端存储数据的机制,通常由键值对组成,用于跟踪用户和存储会话信息。

  6. Session: 会话数据通常是通过键值对的方式管理,用于在用户与服务器之间保持状态和信息。

  7. 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 {
    @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容器会确保会话的正确管理。

 

 具体分析一下我们的代码:

  1. Servlet声明和URL映射:

    • 通过@WebServlet注解,这个Servlet被映射到URL路径/login,这意味着当用户访问网站的/login路径时,这个Servlet会被调用。
  2. 处理POST请求:

    • 在doPost方法中,处理POST请求的逻辑被定义。
  3. 获取请求参数:

    • 通过req.getParameter("username")和req.getParameter("password"),从请求中获取用户名和密码。
    • 在这个示例中,为了简单起见,用户名和密码都是写死的("zhangsan"和"123"),并且没有与数据库交互。
    • 这段代码通过req.setCharacterEncoding("utf8")设置了请求的字符集,以防止中文用户名导致乱码。
  4. 验证用户名和密码:

    • 通过比较用户名和密码是否与预定义的值匹配,来验证登录是否成功。
    • 如果用户名和密码不匹配,将返回一个包含错误消息的响应,告知用户登录失败。
  5. 创建会话(Session):

    • 如果用户名和密码验证成功,将为用户创建一个会话。
    • 会话对象通过req.getSession(true)来获取,如果会话不存在则会创建一个新的。
    • 在会话中,存储了一些用户自定义的数据,如用户名和登录时间。这些数据以键值对的形式存储在会话中。
    • 通过session.setAttribute("key", value)方法,将用户名和登录时间存储在会话中。
  6. 登录成功:

    • 登录成功后,通过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 {
    @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);
    }
}

 

 具体分析一下上述代码:

  1. Servlet声明和URL映射:

    • 通过@WebServlet注解,这个Servlet被映射到URL路径/index,这意味着当用户访问网站的/index路径时,这个Servlet会被调用。
  2. 处理GET请求:

    • 在doGet方法中,处理GET请求的逻辑被定义。
  3. 获取当前用户的会话对象:

    • 通过req.getSession(false)来获取当前用户的会话对象。
    • 如果用户没有会话,即会话不存在,或者SessionID在哈希表中没有对应的HttpSession对象,则会返回null。
    • 如果session为null,说明用户尚未登录,会向用户返回一个消息提示,告知用户需要先登录才能访问主页。
  4. 从会话中获取用户信息:

    • 如果会话存在(即用户已登录),则从会话中获取之前存储的用户信息。
    • 通过session.getAttribute("key")方法,分别获取用户名("username")和上次登录时间("loginTime")。
    • 需要注意,这里进行了类型转换,要确保类型是正确的,以避免运行时异常。这里假设存储在会话中的数据类型是正确的。
  5. 生成页面响应:

    • 构建页面的响应内容,根据用户信息来显示不同的页面。
    • 使用resp.getWriter().write()方法将响应内容返回给客户端。
    • 在这个示例中,响应内容包括欢迎消息以及上次登录时间。

这段代码实现了一个根据用户登录状态动态生成主页的功能。如果用户已登录,它会显示欢迎消息和上次登录时间;如果用户尚未登录,它会显示一个提示消息。该示例演示了如何使用HttpSession来管理用户会话状态,以及如何在不同Servlet之间共享数据以提供个性化用户体验。内部的哈希表和SessionID的细节由Servlet容器自动处理,开发者只需要关注会话和数据的操作。

全部实现完成以后我们就可以来看看实现效果了:

 

1、点击登录之后就会触发一个前端的POST请求:

2、到了服务器这边: 

3、重定向到主页(index) :

 

这个东西的应用在于: 

  1. 持久的登录状态: 一旦用户成功登录,通常会话将维持一段时间,使用户在之后的访问中继续保持登录状态。这可以通过Cookie和Session来实现。Cookie通常用于在客户端存储会话标识,而Session在服务器端存储用户数据。这种持久的登录状态使得用户无需在每个页面请求中重新登录。

  2. 会话过期时间: 会话的持续时间是可以设置的。不同网站可以根据需求设置不同的会话过期时间。一些网站可能会设置长期的会话,以提供长时间的登录状态,而其他敏感性较高的网站可能会设置较短的会话,以增强安全性。

  3. Cookie的过期时间: Cookie是在客户端存储的,可以设置Cookie的过期时间。一旦Cookie的过期时间到了,浏览器会自动删除该Cookie。这可以在创建Cookie时通过setMaxAge方法来设置。

  4. Session的过期时间: Session是在服务器端管理的,也可以设置会话的过期时间。当会话过期,服务器会自动删除会话及其数据。过期时间可以通过Servlet容器或编程方式来设置。

  5. 共用计算机的风险: 如果多个人在同一台公共计算机上使用同一个登录状态的帐户,会产生潜在的安全风险。这是因为后续用户无需再次登录,可能会有权限进行一些敏感操作。因此,对于共用计算机,特别是在公共场所,用户应该注销或清除会话和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请求。

  1. Servlet映射:

    • 通过@WebServlet("/upload")注解,将Servlet映射到URL路径"/upload",以便处理文件上传请求。
  2. doPost方法:

    • 当收到POST请求时,doPost方法被调用来处理文件上传。
    • 首先,通过request.getPart("MyImage")获取名为"MyImage"的文件上传部分(Part对象)。
    • 接着,使用自定义方法getSubmittedFileName从Part对象中获取提交的文件名。
  3. 文件保存:

    • 确定了文件名后,指定了服务器上的保存路径,这个路径是基于应用程序的上下文路径。
    • 然后,使用InputStream从Part对象中获取文件数据,将数据写入指定的保存路径,完成文件的保存。
  4. 响应:

    • 最后,通过response.getWriter().write("文件 " + fileName + " 上传成功!")向客户端返回成功消息。
  5. 文件上传功能:

    • 该Servlet实现了文件上传功能,客户端可以通过HTML表单选择文件并提交到"/upload"路径。
    • 上传的文件将被保存在服务器上的"/uploads/"目录中。
    • 上传成功后,Servlet返回一条成功消息。
  6. 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 {
    @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();
   }
}

读取 GET 请求的 query string:

@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");
   }
}

读取 POST 请求的 body:

@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");
   }
}

登录页面:

<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 {
    @Override
    protected 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.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");
   }
}
<form action="upload" enctype="multipart/form-data" method="POST">
    <input type="file" name="MyImage">
    <input type="submit" value="提交图片">
</form>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/121080.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】类和对象(一):什么是面向对象,访问限定符有哪些,类定义细节,结构体和类的关系。

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

机组 指令系统

机器指令 机器指令&#xff1a;每一条机器语言的语句 指令系统&#xff1a;全部机器指令的集合 指令的一般格式 指令由操作码和地址码两部分组成 操作码 作用&#xff1a;指明该指令要完成的操作 位数&#xff1a;反映机器的操作种类&#xff0c;即机器允许的指令条数 …

瞅瞅 Opencv:扫描图像

扫描图像查询表 一、概述二、图像矩阵如何存储在内存中?三、高效的方式四、迭代器(安全)方法五、带引用返回的动态地址计算六、核心功能七、性能差异 一、概述 让我们考虑一种简单的色彩还原方法。通过使用unsigned char C和c类型进行矩阵项存储&#xff0c;一个像素通道可以…

音视频技术开发周刊 | 318

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 日程揭晓&#xff01;速览深圳站大会专题议程详解 LiveVideoStackCon 2023 音视频技术大会深圳站&#xff0c;保持着往届强大的讲师阵容以及高水准的演讲质量。两天的参会…

git commit规范提交

Git每次提交代码时&#xff0c;都要写Commit Message&#xff08;提交说明&#xff09;&#xff0c;通常情况下&#xff0c;Commit Message应该清晰明了&#xff0c;说明本次提交的目的和具体操作等。然而笔者工作多年来发现&#xff0c;有些公司对Commit Message没有明确的要求…

wpf Grid布局详解 `Auto` 和 `*` 是两种常见的设置方式 行或列占多个单元格,有点像excel里的合并单元格。使其余的列平均分配剩余的空间

比如只有行的界面 <Window x:Class"GenerateTokenApp.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/exp…

SpringCloudAlibaba——Sentinel

Sentinel也就是我们之前的Hystrix&#xff0c;而且比Hystrix功能更加的强大。Sentinel是分布式系统的流量防卫兵&#xff0c;以流量为切入点&#xff0c;从流量控制、流量路由、熔断降级等多个维度保护服务的稳定性。 Sentinel采用的是懒加载&#xff0c;这个接口被访问一次&a…

企业级低代码开发,科技赋能让企业具备“驾驭软件的能力”

科技作为第一生产力&#xff0c;其强大的影响力在各个领域中都有所体现。数字技术&#xff0c;作为科技领域中的一股重要力量&#xff0c;正在对传统的商业模式进行深度的变革&#xff0c;为各行业注入新的生命力。随着数字技术的不断发展和应用&#xff0c;企业数字化转型的趋…

SpringBoot自动装配 Spring相关 常用设计模式 双亲委派 MongoDB Redis 适配器模式与策略模式

SpringBoot自动装配 阿里云登录 - 欢迎登录阿里云&#xff0c;安全稳定的云计算服务平台 Spring相关 阿里云登录 - 欢迎登录阿里云&#xff0c;安全稳定的云计算服务平台 常用设计模式 双亲委派 Java虚拟机定义了三个主要的类加载器: 1、启动类加载器 2、扩展类加载器 …

《网络协议》02. 物理层 · 数据链路层 · 网络层

title: 《网络协议》02. 物理层 数据链路层 网络层 date: 2022-08-31 22:26:48 updated: 2023-11-08 06:58:52 categories: 学习记录&#xff1a;网络协议 excerpt: 物理层&#xff08;数据通信模型&#xff0c;信道&#xff09;、数据链路层&#xff08;封装成帧&#xff0c…

CSDN中: Markdown编辑器使用说明

Markdown编辑器使用说明 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一…

Android UI 开发·界面布局开发·案例分析

目录 ​编辑 1. 线性布局&#xff08;LinearLayout&#xff09; 2. 相对布局&#xff08;RelativeLayout&#xff09; 3. 表格布局&#xff08;TableLayout&#xff09; 4. 帧布局&#xff08;FrameLayout&#xff09; 5. 网格布局&#xff08;GridLayout&#xff0…

05【保姆级】-GO语言的标识符

之前我学过C、Java、Python语言时总结的经验&#xff1a; 先建立整体框架&#xff0c;然后再去抠细节。先Know how&#xff0c;然后know why。先做出来&#xff0c;然后再去一点点研究&#xff0c;才会事半功倍。适当的囫囵吞枣。因为死抠某个知识点很浪费时间的。对于GO语言&a…

Android 10.0 系统默认打开OEM解锁开关功能实现

1.前言 在10.0的系统定制中,在9.0系统以后为了设备的安装,系统开始启用oem机制,所以在adb push文件就需要先oem解锁,然后才可以 进行相关操作,所以就需要默认打开oem解锁的开关,来方便oem解锁功能的实现 如图: 2.系统默认打开OEM解锁开关功能实现的核心类 packages\ap…

初步了解 RabbitMQ

目录 ​编辑一、MQ 概述 1、MQ 的简介 2、MQ 的用途 &#xff08;1&#xff09;限流削峰 &#xff08;2&#xff09;异步解耦 (3)数据收集 二、RabbitMQ 概述 1、RabbitMQ 简介 2、四大核心概念 3、RabbitMQ 的核心部分 ​编辑 4、名词解释&#xff1a; 三、Hello …

ESP32 C3 smartconfig一键配网报错

AP配网 在调试我的esp32c3的智能配网过程中&#xff0c;发现ap配网使用云智能App是可以正常配置的。 切记用户如果在menu菜单里使能AP配网&#xff0c;默认SSID名字为adh_PK值_MAC后6位。用户可以修改这个apssid的键值&#xff0c;但是要使用云智能app则这个名字的开头必须为ad…

香港金融科技周VERTU CSO Sophie谈Web3.0的下一个风口 手机虚拟货币移动支付

10月31日&#xff0c;香港金融科技周正式拉开帷幕。这项香港金融科技界地年度盛事今年已经踏入了第八届&#xff0c;本届活动吸引超过数百位金融科技专业人士、创业者和行业领袖现场参与&#xff0c;线上参与观众超过10万人次。 在金融科技周的圆桌会议上&#xff0c;VERTU首席…

Java-继承

1 继承 1.1 为什么需要继承 Java中使用类对现实世界中实体来进行描述&#xff0c;类经过实例化之后的产物对象&#xff0c;则可以用来表示现实中的实体&#xff0c;但是现实世界错综复杂&#xff0c;事物之间可能会存在一些关联&#xff0c;那在设计程序是就需要考虑。 以下…

Vulnhub靶场之Funbox

正如该靶场的描述所说&#xff0c;它对初学者来说非常简单。 项目地址&#xff1a;Funbox: Scriptkiddie ~ VulnHub 所需工具&#xff1a; KaliLinux即可。 0x00 信息收集 打开虚拟机后使用nmap扫描一下网段存活&#xff0c;这里我给的虚拟机的范围是100-253,其中kali的IP是10…

Git 安全警告修复手册:解决 `fatal: detected dubious ownership in repository at ` 问题 ️

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…