Servlet 笔记

news2024/11/13 10:12:27

1. HTTP 协议

1.1 HTTP协议简介

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。

HTTP的发展是由蒂姆·伯纳斯-李于1989年在欧洲核子研究组织(CERN)所发起。HTTP的标准制定由万维网协会(World Wide Web Consortium,W3C)和互联网工程任务组(Internet Engineering Task Force,IETF)进行协调,最终发布了一系列的RFC,其中最著名的是1999年6月公布的 RFC 2616,定义了HTTP协议中现今广泛使用的一个版本——HTTP 1.1

2014年12月,互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis(httpbis)工作小组将HTTP/2标准提议递交至IESG进行讨论,于2015年2月17日被批准。 HTTP/2标准于2015年5月以RFC 7540正式发表,取代HTTP 1.1成为HTTP的实现标准。

1.2 HTTP协议概述

HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。我们称这个客户端为用户代理程序(user agent)。应答的服务器上存储着一些资源,比如HTML文件和图像。我们称这个应答服务器为源服务器(origin server)。在用户代理和源服务器中间可能存在多个“中间层”,比如代理服务器、网关或者隧道(tunnel)。

尽管TCP/IP协议是互联网上最流行的应用,HTTP协议中,并没有规定必须使用它或它支持的层。事实上,HTTP可以在任何互联网协议上,或其他网络上实现。HTTP假定其下层协议提供可靠的传输。因此,任何能够提供这种保证的协议都可以被其使用。因此也就是其在TCP/IP协议族使用TCP作为其传输层。

通常,由HTTP客户端发起一个请求,创建一个到服务器指定端口(默认是80端口)的TCP连接。HTTP服务器则在那个端口监听客户端的请求。一旦收到请求,服务器会向客户端返回一个状态,比如"HTTP/1.1 200 OK",以及返回的内容,如请求的文件、错误消息、或者其它信息。

1.3 HTTP工作原理

HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。

在浏览器中 F12 可查看:

以下是 HTTP 请求/响应的步骤:

1. 客户端连接到Web服务器
一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.baidu.com。

2. 发送HTTP请求
通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。

3. 服务器接受请求并返回HTTP响应
Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。

4. 释放连接TCP连接
若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;

5. 客户端浏览器解析HTML内容
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。

例如:在浏览器地址栏键入URL,按下回车之后会经历以下流程:

  1. 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
  2. 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
  3. 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
  4. 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
  5. 释放 TCP连接;
  6. 浏览器将该 html 文本并显示内容;

1.4 HTTP 协议的特点

  1. 支持客户/服务器模式
  2. 简单快速:客户向服务器请求服务时,只需要传送请求的方法和路径。请求方法常用的有GET、POST。每种方法规定了客户与服务器连续的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
  3. 灵活:HTTP允许传输任意类型数据对象。传输类型有Context-Type加以标记。
  4. 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间,并且可以提高并发性能,不能和每个用户建立长久的连接,请求一次相应一次,服务端和客户端就中断了。但是无连接有两种方式,早期的http协议是一个请求一个响应之后,直接就断开了,但是现在的http协议1.1版本不是直接就断开了,而是等几秒钟,这几秒钟是等什么呢,等着用户有后续的操作,如果用户在这几秒钟之内有新的请求,那么还是通过之前的连接通道来收发消息,如果过了这几秒钟用户没有发送新的请求,那么就会断开连接,这样可以提高效率,减少短时间内建立连接的次数,因为建立连接也是耗时的,默认的好像是3秒中现在,但是这个时间是可以通过咱们后端的代码来调整的,自己网站根据自己网站用户的行为来分析统计出一个最优的等待时间。
  5. 无状态:HTTP是一种不保存状态,即无状态(stateless)协议。HTTP协议 自身不对请求和响应之间的通信状态进行保存。也就是说在HTTP这个 级别,协议对于发送过的请求或响应都不做持久化处理。

1.5 HTTP请求方法

HTTP/1.1协议中共定义了八种方法(也叫“动作”)来以不同方式操作指定的资源

GET

向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,例如在Web Application中。其中一个原因是GET可能会被网络蜘蛛等随意访问。

HEAD

与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。

POST

向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。

PUT

向指定资源位置上传其最新内容。

DELETE

请求服务器删除Request-URI所标识的资源。

TRACE

回显服务器收到的请求,主要用于测试或诊断。

OPTIONS

这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用'*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作。

CONNECT

HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。

注意事项:

  1. 方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed),当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)。
  2. HTTP服务器至少应该实现GET和HEAD方法,其他方法都是可选的。当然,所有的方法支持的实现都应当匹配下述的方法各自的语义定义。此外,除了上述方法,特定的HTTP服务器还能够扩展自定义的方法。例如PATCH(由 RFC 5789 指定的方法)用于将局部修改应用到资源

请求方式: get与post请求(通过form表单我们自己写写看)

  • GET提交的数据会放在URL之后,也就是请求行里面,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456.(请求头里面那个content-type做的这种参数形式,后面讲) POST方法是把提交的数据放在HTTP包的请求体中.
  • GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
  • GET与POST请求在服务端获取请求数据方式不同,就是我们自己在服务端取请求数据的时候的方式不同了。

1.6 HTTP状态码

所有HTTP响应的第一行都是状态行,依次是当前HTTP版本号,3位数字组成的状态代码,以及描述状态的短语,彼此由空格分隔。

状态代码的第一个数字代表当前响应的类型:

  • 1xx消息——请求已被服务器接收,继续处理
  • 2xx成功——请求已成功被服务器接收、理解、并接受
  • 3xx重定向——需要后续操作才能完成这一请求
  • 4xx请求错误——请求含有词法错误或者无法被执行
  • 5xx服务器错误——服务器在处理某个正确请求时发生错误

虽然 RFC 2616 中已经推荐了描述状态的短语,例如"200 OK","404 Not Found",但是WEB开发者仍然能够自行决定采用何种短语,用以显示本地化的状态描述或者自定义信息。

1.7 URL 简介

超文本传输协议(HTTP)的统一资源定位符将从因特网获取信息的五个基本元素包括在一个简单的地址中:

  • 传送协议。
  • 层级URL标记符号(为[//],固定不变)
  • 访问资源需要的凭证信息(可省略)
  • 服务器。(通常为域名,有时为IP地址)
  • 端口号。(以数字方式表示,若为HTTP的默认值“:80”可省略)
  • 路径。(以“/”字符区别路径中的每一个目录名称)
  • 查询。(GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题)
  • 片段。以“#”字符为起点

以http://www.luffycity.com:80/news/index.html?id=250&page=1 为例, 其中:

http,是协议;
www.luffycity.com,是服务器;
80,是服务器上的默认网络端口号,默认不显示;
/news/index.html,是路径(URI:直接定位到对应的资源);
?id=250&page=1,是查询。
大多数网页浏览器不要求用户输入网页中“http://”的部分,因为绝大多数网页内容是超文本传输协议文件。同样,“80”是超文本传输协议文件的常用端口号,因此一般也不必写明。一般来说用户只要键入统一资源定位符的一部分(www.luffycity.com:80/news/index.html?id=250&page=1)就可以了。

由于超文本传输协议允许服务器将浏览器重定向到另一个网页地址,因此许多服务器允许用户省略网页地址中的部分,比如 www。从技术上来说这样省略后的网页地址实际上是一个不同的网页地址,浏览器本身无法决定这个新地址是否通,服务器必须完成重定向的任务。

1.8 HTTP请求格式(请求协议)

URL包含:/index/index2?a=1&b=2;路径和参数都在这里。

1.9 HTTP响应格式(响应协议)

2. Tomcat 服务器

2.1 什么是Tomcat

Tomcat 是一个符合JavaEE WEB 标准的最小的WEB 容器,所有JSP程序一定要有WEB 容器的支持才能运行,而且在给定的 WEB 容器里面都会支持事务处理操作。

Tomcat 是由Apache 提供的(www.apache.org) 提供的可以用安装版和解压版,安装版可以在服务中出现一个Tomcat服务,免安装没有,开发中使用免安装版。Tomcat 是一个运行Java 的网络服务器,底层是Socket 的一个程序,它也是JSP 和 Servlet 的一个容器。

Tomcat 服务器是一个免费的开发源代码的 Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。性能稳定,而且免费。

2.2 安装Tomcat

官网下载: Apache Tomcat® - Welcome!

下载解压:

启动方式:

方式一:直接在bin 目录下双击“startup.bat”启动服务

方式二:cmd 进入 bin 目录下,运行“startup.bat”命令启动服务

可能问题:启动Tomcat时日志出现中文乱码,如下

解决办法:

解决方法一:

查看cmd窗口属性,发现编码格式不是UTF-8,需要更改系统编码:

打开控制面板进入区域,更改系统区域设置,把下图的Beta 版勾上。

更改后重启电脑生效,检查发现cmd属性中显示时UTF-8。

再次重启Tomcat,发现日志中文正常了:

浏览器访问:http://localhost:8080/

服务访问成功。

注:Tomcat默认占用端口8080,如果启动着oracle 等占用80 端口的服务,需要先关掉后再启动Tomcat

解决方法二:

找到这一行,将UTF-8 改成GBK,重启Tomcat即可:

2.3 配置环境变量

配置成功后,不需要进入bin 命令可以直接执行命令启动:

2.4 Tomcat 自带项目

Tomcat 自带了一些项目可以自行了解,如:

这里随便进入“WebSocket Examples”--》 “Chat example”,这是个简单聊天工具,如下:

还有其它一些不错的自带项目。

2.5 IDEA 配置 Tomcat

IDEA中选择菜单“File”--> “Settings”,然后如下:

2.6 IDEA 中没有Application Servers问题

问题:有些版本的IDEA 可能没有Application Servers 配置页面,所以无法配置Tomcat,如下:

解决:安装Tomcat插件

3. Servlet 的实现

3.1 Servlet 简介

Servlet 是 Server 与 Applet 的缩写,是服务端小程序的意思。使用Java语言编写的服务端程序,可以生成动态的 WEB 页, Servlet 主要运行再服务器端,并由服务器调用执行,是一种安装Servlet 标准来开发的类。是SUN 公司提供的一门用于开发动态 Web 资源的技术。(言外之意:要实现web开发,需要实现Servlet标准

Servlet 本质上也是Java 类,但要遵循 Servlet 规范进行编写,没有 main() 方法,它的创建、使用、销毁都由Servlet 容器进行管理(如:Tomcat)。(言外之意:写自己的类,不用写main方法,别人自动调用

Servlet是和HTTP 协议是紧密联系的,其可以处理 HTTP 协议相关的所有内容。这也是Servlet应用广泛的原因之一。

提供了 Servlet 功能的服务器,叫做 Servlet 容器,其常见容器有很多,如Tomcat,Jetty, WebLogic Server, WebShere, JBoss 等等。

3.2 创建Web项目

因为新版的IDEA 新建项目时没有“Web Application“,如下:

所有可以先新建一个普通Java项目先:

然后选择项目,右键选择"Add Framework Support ...":

然后生成了Web 项目结构如下:

3.3 给项目配置Tomcat

配置好后,点击Fix按钮:

把下面划线处的项目名称后面的尾巴去掉,改成项目名称“/servlet01”:

然后点击“Apply”,然后检查右上角已经出现了Tomcat:

3.4 导入Tomcat 依赖包

检查发现已经导入Tomcat依赖包:

3.5 实现 Servlet 规范

(1)实现 Servlet 规范,即继承 HttpServlet 类,并导入相应的包,该类中已经完成了通信的规则,我们只需要进行业务的实现即可。

(2)在规范中有一个叫做 service 的方法,专门用来做请求处理的操作,业务代码则可以写在该方法中。

Alt + Insert 快捷键选择service方法复写:

(3)设置注解

在完成好了一切代码的编写后,还需要向服务器说明,特定请求对应特定资源。

开发 servlet 项目,使用 @WebServlet 将一个继承于 javax.servlet.http.httpServlet 的类定义为 Servlet 组件。在 Servlet3.0中,可以使用 @WebServlet 注解将一个继承于 javax.servlet.http.httpServlet 的类标注为可以处理用户请求的 Servlet。

(4)启动项目

在弹出的浏览器中,完善路径:localhost:8080/servlet01/servlet01, 然后回车访问:

注:路径中的第一个“servlet01”是前面配置的项目的上下文路径:

后面的那个“servlet01”是注解中的资源路径:(这里恰好用了同样的名字)

查看后台服务器输入:

(5)F12 并刷新页面可以看到,具体的请求响应信息:

3.6 Servlet 的生命周期

Servlet 没有 main() 方法,不能独立运行,它的运行完全由 Servlet 引擎来控制和调度。所以生命周期,指的是 servlet 容器何时创建 servlet 实例、何时调用其方法进行请求的处理、何时销毁其实例的过程。

(1)实例和初始化时机

当请求到达容器时,容器查找该 servlet 对象是否存在,如果不存在,则会创建实例并进行初始化。

(2)就绪/调用/服务阶段

有请求到达容器,容器调用 servlet 对象的 service() 方法, 处理请求的方法在整个生命周期中可以被多次调用;HttpServlet 的 service() 方法,会依据请求方法来调用doGet() 或者 doPost() 方法。但是,这两个do 方法默认情况下,会抛出异常,需要子类去override

(3)销毁时机

当容器关闭时(应用程序停止时),会将程序中的 Servlet 实例进行销毁。

上述的生命周期可以通过 Servlet 中的生命周期方法来观察。在 Servlet 中有三个生命周期方法,不由用户手动调用,而是在特定的时机有容器自动调用

Servlet 中的三个生命周期方法:

(1)init 方法,在 Servlet 实例创建之后执行。

(2)service 方法, 每次有请求到达某个 Servlet方法时执行,用来处理请求。

(3)destroy 方法, Servlet 实例销毁时执行。

上面存在问题:控制台打印中文乱码。

解决方法一:设置IDEA的字体编码成UTF-8,如下:

设置完无效,继续设置如下:

(-Dfile.encoding=UTF-8)

重启Tomcat测试中文乱码解决了。如下:

浏览器访问服务三次后,后台打印:

然后关闭Tomcat 服务,后台打印:

Servlet 的生命周期,简单概括就分为四步: Servlet 类加载 --> 实例化 --> 服务 --> 销毁

Servlet 接收、处理和响应请求的流程:

(1)Web Client 向 Servlet 容器 (Tomcat)发出Http 请求

(2)Servlet 容器接收 Web Client 的请求

(3)Servlet 容器创建一个 HttpServletRequest 对象,将 Web Client 请求的信息封装到这个对象中

(4)Servlet 容器创建一个 HttpServletResponse 对象

(5)Servlet 容器调用 HttpServlet 对象 的service 方法,把 HttpServletRequest 与 HttpServletResponse 作为参数,传递给HttpServlet

(6)HttpServlet 调用 HttpServletRequest 对象的有关方法,获取 Http 请求信息

(7)HttpServlet 调用 HttpServletResponse 对象的有关方法,生成响应数据

(8)Servlet 容器把 HttpServlet 的响应结果传给 Web Client

4. HttpServletRequest 对象

HttpServletRequest 对象:主要作用时用来接收客户端发送过来的请求信息,例如:请求的参数,发送的头信息等都属于客户端发来的信息,service() 方法中形参接收的是 HttpServletRequest 接口的实例化对象,表示该对象主要应用在Http协议上,该对象由Tomcat 封装好传递过来。

HttpServletRequest 是 ServletRequest 的子接口, ServletRequest 只有 HttpServletRequest 一个子接口,主要是考虑到以后可能支持更多新的协议而设计的,但目前主要是HTTP协议。

HttpServletRequest 接口中很多方法,但不需要处理,因为都是由容器传递过来,我们需要做的就是取出该对象中的数据,进行分析和处理。

4.1 接收请求

4.1.1 常用方法

启动服务,浏览器访问:http://localhost:8080/servlet01/servlet02?username=zs&age=18

4.1.2 获取请求参数

(1)getParameter(name) : 获取指定名称的参数,不管传递什么,返回的都是字符串

(2)getParameterValues(String name) : 获取指定名称的所有值

浏览器访问:http://localhost:8080/servlet01/servlet02?name=zs&hobby=dance&hobby=readerBook

4.2 请求乱码问题

由于现在的request 属于接收客户端的参数,所有必然由其默认的语言编码,主要是由于在解析过程中默认使用的编码方式为 ISO-8859-1 (此编码不支持中文),所以解析时一定会出现乱码。要想解决这种乱码问题,需要设置 request 中的编码方式,告诉浏览器以何种方式来解析数据。或者在接收到乱码数据以后,再通过相应的编码格式还原。

方式一:

request.setCharacterEncoding("UTF-8");

这种方式只针对 POST 有效(必须在接收所有的数据之前设定)

方式二:

//解决Tomcat 7 及以下版本的 GET 请求乱码,Tomcat 8 以上的 GET 请求不要用,否则又转错,又乱码
String name = new String(request.getParameter("name").getBytes("ISO-8859-1"),"UTF-8");

借助了String 对象的方法,该中方法对任何请求有效,是通用的

注:Tomcat8 起,以后的GET方式请求是不会出现乱码的,8版本以下都会乱码。

POST方式请求,无论什么版本的服务器都会出现中文乱码问题。

例子一:GET 方式请求

GET 方式下,浏览器发送请求:http://localhost:8080/servlet01/servlet03?name=张三&pwd=123456

控制台输入(Tomcat 8 以上版本,GET 方式不出现乱码):

例子二:POST 方式请求(展示出现乱码)

Servlet 服务端同上,下面添加一个JSP页面发 post 请求:

启动服务,浏览器访问: http://localhost:8080/servlet01/login.jsp

提交表单,查看控制台:

成功出现乱码。

乱码解决:获取参数前设置编码格式为UTF-8

重启服务,再次访问,控制台输入不乱码:

例子三:Tomcat 8 以上版本,GET 请求不需要转换编码,却又转换,所以出现中文乱码

浏览器访问:http://localhost:8080/servlet01/servlet03?name=张三&pwd=123456

所以,Tomcat8及以上版本,get 请求不需要转码。

4.3 请求转发

请求转发,是一种服务器的行为,当客户端请求到达后,服务器进行转发,此时会将请求对象进行保存,地址栏中的URL地址不会改变,得到响应后,服务器端再将响应发送给客户端,从从始至终只有一个请求发出。

实现方式如下,达到多个资源协同响应的效果。

request.getRequestDispatcher("servlet05").forward(request,response);

请求转发特点:

* 1. 服务器行为

* 2. 地址栏不发生改变

* 3. 从始至终只有一个请求

* 4. request 数据可以共享

例子一:请求转发到另一个Servlet

浏览器访问:http://localhost:8080/servlet01/servlet04?name=zhangsan

控制台输出:

注:全程浏览器中的访问地址没有变。

例子二:请求转发到JSP 页面

重启服务,浏览器访问:http://localhost:8080/servlet01/servlet04?name=zhangsan

页面跳转,但地址栏没有变:

注:请求转发到 html 页面也可以。

4.4 request 作用域

通过该对象可以再一个请求中传递数据

作用范围:在一次请求中有效,即服务器跳转有效。

        //设置域对象内容
        request.setAttribute(String name, Object value);
        //获取域对象内容
        request.getAttribute(String name);
        //删除域对象内容
        request.removeAttribute(String name);

request 域对象中的数据在一次请求中有效,则经过请求转发, request 域中的数据依然存在,所以在请求转发的过程中可以通过request 来传输/共享数据。

例子一:

浏览器访问:servlet06

控制台输出:

注:请求转发到JSP和html页面也可以传输域对象。

5. HttpServletResponse 对象

Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的 request 对象和代表响应的 response 对象。

request 和 response 对象代表请求和响应:获取客户端数据,需要通过request 对象;向客户端输出数据,需要通过response 对象

service() 方法中形参接收的是HttpServletResponse 接口的实例化对象这个对象中封装了向客户端发送数据、发送响应头、发送响应状态码的方法

5.1 响应数据

接收到客户端请求后,可以通过 HttpServletResponse 对象直接进行响应,响应时需要获取输出流。

有两种形式(两个不能同时使用):

getWriter() : 获取字符流(只响应回字符)

getOutputStream() : 获取字节流(能响应一切数据

响应回的数据到客户端被浏览器解析。

注: 两个不能同时使用,否则会报错。

例子一:getWriter() : 获取字符流(只响应回字符)

启动服务,浏览器访问:

例子二:getOutputStream() : 获取字节流(能响应一切数据

5.2 响应乱码问题

在响应中,如果我们响应的内容中包含有中文,则有可能出现乱码。这是因为服务器响应的数据也会通过网络传输,服务器有一种编码方式,客户端也有一种编码方式,当两端使用的编码方式不同时出现乱码。

5.2.1 getWriter() 的字符乱码

对于 getWriter() 获取到的字符流,响应中文必定处乱码,由于服务器端在进行编码时默认使用 ISO-8859-1 格式的编码,该编码方式并不支持中文。

要解决这种乱码只能在服务器端告知服务器使用一种能够支持中文的编码格式,比如我们通常用的 UTF-8.

//设置服务器端的编码格式
response.setCharacterEncoding("UTF-8");

此时还只是完成了一半的工作,要保证数据正常显示,还需要确定客户端的编码方式。

//设置客户端的编码格式和响应的MIME类型
response.setHeader("content-type", "text/html;charset=UTF-8");

两端指定编码后,乱码就解决了。一句话:保证发送端和接收端的编码一致

        //设置服务器端的编码格式
        response.setCharacterEncoding("UTF-8");
        //设置客户端的编码格式和响应的MIME类型
        response.setHeader("content-type", "text/html;charset=UTF-8");
        //获取字符输出流
        PrintWriter writer = response.getWriter();
        writer.write("你好");

以上两端编码的指定也可以使用一句替代,同时特定服务器和客户端:

        //同时设置客户端和服务器端的编码格式(相当于前面两个设置合并)
        response.setContentType("text/html;charset=UTF-8");

例子一:解决 getWriter() 的字符乱码

然后指定服务器端编码:

还是不同的乱码,接下来同时指定客户端编码:

乱码问题成功解决。

下面用setContentType() 方法同时设置客户端和服务端编码格式,代替前面的两个设置。

5.2.2 getOutputStream() 字节乱码

对于 getOutputStream() 方式获取到的字节流,响应中文时,由于本身就是传输的字节,所以此时可能出现乱码,也可能正常显示。当服务器端个的字节恰好和客户端使用的编码方式一致时则文本正确显示,否则出现乱码。无论如何我们都应该准确掌握服务器和客户端使用的是哪种编码格式,以确保数据正确显示。

指定客户端和服务器端使用编码方式一致:

response.setContentType("text/html;charset=UTF-8");
        //同时设置客户端和服务器端的编码格式(相当于前面两个设置合并)
        response.setContentType("text/html;charset=UTF-8");
        //获取字节输出流
        ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write("大家好".getBytes());

例子一:解决 getOutputStream() 的字符乱码

5.3 重定向

重定向是一种服务器指导,客户端的行为。客户端发出第一个请求,被服务器接收处理后,服务器进行响应,在响应的同时,服务器会给客户端一个新的地址(下次请求的地址 response.sendRedirect(url);), 当客户端接收到响应后,会立即、马上、自动根据服务器给的新地址发起第二个请求,服务器接收请求并作出响应,重定向完成。

从描述中可以看出重定向当中有两个请求存在,并且属于客户端行为

        //重定向跳转到index.jsp
        response.sendRedirect("index.jsp");

通过观察浏览器我们发现第一次请求获得的响应码为302,并且含有一个location头信息。并且地址栏最终看到的地址是和第一次请求地址不同的,地址栏已经发生变化。

注:重定向的两次请求中,request 对象数据不共享。

实例一:重定向到零另一个Servlet

浏览器F12, 然后访问:http://localhost:8080/servlet02/servlet04

访问完,发现浏览器接收到两次状态码,浏览器地址改变为:http://localhost:8080/servlet02/servlet05

控制台输出:

5.4 请求转发与重定向的区别

请求转发(req.getRequestDispatcher().forward())

重定向(resp.sendRedirect())

一次请求,数据在 request 域中共享

两次请求,request域中数据不共享

服务器端行为

客户端行为

地址栏不发生改变

地址栏发生变化

绝对地址定位到站点后(地址只能在当前项目下找资源)

绝对地址可写到http://(地址可以是任何地址)

两者都可以进行跳转,根据实际需求选取即可。

6. Cookie 对象

Cookie 是浏览器提供的一种技术,通过服务器的程序能够将一些只须保存在客户端,或者在客户端进行处理的数据,放在客户端所在的本地计算机上,不需要通过网络传输,因而提高网页处理的效率,并且能够减少服务器的负载,但由于Cookie 是服务器端保存在客户端的信息,所以其安全性也是很差的。例如常见的记住密码则可以通过Cookie 来实现。

有一个专门操作Cookie 的类 javax.servlet.http.Cookie。随着服务器端的响应发送给客户端,保存在浏览器,当下次再次访问服务器时把Cookie 再带回服务器

Cookie 的格式:键值对用“=”链接,多个键值对键通过“;”隔开。

例如:F2查看百度首页的Cookie 信息:

6.1 Cookie 的创建与发送

通过 new Cookie("key","value"); 来创建一个Cookie 对象,要想将 Cookie 随响应发送到客户端,需要添加到 response 对象中,response.addCookie(cookie); 此时该 cookie 对象则随着响应发送到了客户端。在浏览器上可以看见。

        // Cookie 的创建
        Cookie cookie = new Cookie("name", "admin");
        //发送(响应)Cookie 对象
        response.addCookie(cookie);

F12 查看:

实例:Cookie 的创建与发送

访问: http://localhost:8080/servlet03/cook01

F12 查看浏览器返回信息:

6.2 Cookie 的获取

在服务器端只提供了一个 getCookie() 的方法用来获取客户端回传的所有 cookie 组成的一个数组,如果需要获取单个 cookie 则需要通过遍历。getName() 获取 Cookie 的名称, getValue() 获取 Cookie 的值。

例子:获取Cookie

在8.1中我们在浏览器中创建与发送了一个name=“name”,value=“admin” 的cookie,下面另一个Servlet 中获取存在浏览器中的该 Cookie。

控制台输出:

6.3 Cookie 设置到期时间

除了 Cookie 的名称和内容外,我们还需要关心Cookie 的到期时间,到期时间用来指定该 Cookie 何时失效。默认为当前浏览器关闭即失效。可以手动设定cookie的有效时间(通过到期时间计算),通过setMaxAge(int time); 方法设定 cookie 的最大有效时间,以秒为单位

到期时间的取值:

(1)负整数: 表示不存储该 cookie

cookie 的 maxAge属性的默认值就是 -1, 表示只在浏览器内存中存活,一旦关闭浏览器窗口,那么 cookie 就会消失

(2)正整数:表示存储的秒数

表示 cookie 对象可存活指定的秒数,当值大于0时,浏览器会把 cookie 保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie 也会存活相应的时间。(即 同一浏览器,同一电脑既可以)

(3)零:0表示删除该 cookie

cookie 生命等于0是一个特殊的值,它表示 cookie 被作废!如果原来浏览器已经保存了这个cookie,那么可以通过Cookie 的 setMaxAge(0) 来删除这个 Cookie,无论是在浏览器内存中,还是在客户端硬盘上都会删除这个cookie.

例子:设置Cookie到期时间

访问: http://localhost:8080/servlet03/cook03

30秒后访问:http://localhost:8080/servlet03/cook02 查看当前浏览器Cookie,发现name2 Cookie已经超过30秒失效了。

只剩下name2 Cookie 了,然后关掉浏览器再访问:http://localhost:8080/servlet03/cook02

结果name1 Cookie也因为浏览器关闭后失效了。

6.4 Cookie 的注意点

(1)Cookie 保存再当前浏览器中, cookie不能跨浏览器。

(2)Cookie 存中文问题

Cookie 中不能出现中文,否则会报错。如果有中文则通过URLEncoder.encode() 来进行编码,获取时通过URLDecoder.decode() 来进行解码。

(3)同名 Cookie 问题:会覆盖原有的同名 Cookie

(4)浏览器存放Cookie 的数量:对大小的限制一般在4kb左右

不同浏览器对 Cookie 也有限制, Cookie的存放是有上限的。Cookie 是存储再客户端浏览器的,而且一般是有服务器端创建和设定,后期结合 Sesssion来实现会话跟踪。

例子:Cookie 存中文问题

浏览器访问:http://localhost:8080/servlet03/cook04

因为中文,直接报错。下面加入编码解码:

访问:http://localhost:8080/servlet03/cook04

控制台输出:

6.5 Cookie 的路径

Cookie 的setPath 设置 cookie 的路径,这个路径直接决定服务器的请求是否会从浏览器中加载某些 Cookie。

场景一:当前服务器下任何项目的任意资源都可以获取 Cookie 对象

        /*当前服务器下任何项目的任意资源都可以获取 Cookie 对象*/
        Cookie cookie01 = new Cookie("cookie01", "cookie01");
        //设置路径”/“, 表示在当前服务器路径下的任何项目都可以访问到这个Cookie 对象
        cookie01.setPath("/");
        response.addCookie(cookie01);

场景二:当前项目下的资源可以获取 Cookie对象(默认不设置 Cookie 的path)

        /*当前项目下的资源可以获取 Cookie对象(默认不设置 Cookie 的path)*/
        Cookie cookie02 = new Cookie("cookie02", "cookie02");
        //设置路径”/Servlet03“, 表示在当前项目下的任何项目都可以访问到这个Cookie 对象
        cookie02.setPath("/Servlet03"); // 默认情况,可以不设置path的值
        response.addCookie(cookie02);

场景三:指定项目下的资源可以获取 Cookie对象

        /*指定项目下的资源可以获取 Cookie对象*/
        Cookie cookie03 = new Cookie("cookie03", "cookie03");
        //设置指定站点名
        cookie03.setPath("/Servlet02"); //只能在Servlet02项目下获取Cookie,就算 Cookie 是 Servlet03产生的,Servlet03也不能获取它
        response.addCookie(cookie03);

场景四:指定目录下的资源可以获取 Cookie 对象

        /*指定目录下的资源可以获取 Cookie 对象*/
        Cookie cookie04 = new Cookie("cookie04", "cookie04");
        //设置指定目录
        cookie04.setPath("/cookie04/cook05");
        response.addCookie(cookie04);

总结:只有访问的路径中包含 Cookie 对象的path的值,才可以获取到该 Cookie 对象。

7. HttpSession 对象

7.1 重新创建一个 Servlet 项目

为了熟悉项目创建过程,这里完整演示一遍项目创建过程,并作为HttpSession 对象的实例:

右键项目:

导入Tomcat 相关jar包:

新建Servlet类:

右上角配置项目到Tomcat服务器:

点击”Fix“ 或者上面的Deployment标签页,更改上下文名称

新建完成。

7.2 HttpSession 对象简介

HttpSession 对象是 javax.servlet.http.HttpSession 的实例,该接口并不像 HttpServletRequest 或 HttpServletResponse 还存在一个父接口,该接口只是一个纯粹的接口。这因为 session 本身就属于HTTP 协议的范畴

对于服务器而言,每一个连接到它的客户端都是一个session,servlet 容器使用此接口创建HTTP客户端和HTTP服务器之间的会话。会话将保留指定的时间段,跨多个连接或来自用户的页面请求。一个会话通常对应于一个用户,该用户可能多次访问一个站点。可以通过此接口查看和操作有关某个会话的信息,比如会话标识符、创建时间和最后一次访问时间。在整个session中,最重要的就是属性的操作。

session 无论客户端还是服务器端都可以感知到,若重新打开一个新的浏览器,则无法取得之前设置的session,因为每一个session 只保存在当前的浏览器中,并在相关的页面取得。

Session 的作用就是为了标识一次会话,或者说确认一个用户;并在一次会话(一个用户的多次请求)期间共享数据。可以通过request.getSession() 方法,来获取当前会话的 session 对象。

        //获取 Session 对象
        HttpSession session = request.getSession();

        //获取session 的会话标识符
        String id = session.getId();
        System.out.println(id);
        //获取session的创建时间
        System.out.println(session.getCreationTime());
        //获取最后一次访问时间
        System.out.println(session.getLastAccessedTime());
        //判断是否新的session对象
        System.out.println(session.isNew());

例子:session常用方法

访问:http://localhost:8080/servlet04/ss01

F2 查看浏览器:

控制台输出:

注:当获取Session对象时,会先判断Session对象是否存在,如果存在,则获取session 对象,如果不存在,则创建新的session对象。服务端和浏览器的SessionId是一样的。

7.3 标识符 JSESSIONID

Session 既然是为了标识一次会话,那么此次会话就应该有一个唯一的标志,这个标志就是sessionId。

每当一次请求到达服务器,如果开启了会话(访问了session),服务器第一步会查看是否从客户端回传一个名为JSESSIONID 的 cookie, 如果没有则认为这是一次新的会话,会创建一个新的session对象,并用唯一的sessionId 为此次会话做一个标志。如果有JSESSIONID 这个 cookie回传,服务器则会根据JSESSIONID 这个值去查看是否服务器端含有id为JSESSIONID 值的 session 对象,如果没有则认为是一个新的会话,重新创建一个新的session对象,并标志此次会话;如果服务器端找到相应的 session 对象,则认为之前标志过的一次会话,返回该 session 对象,数据达到共享。

JSESSIONID 是一个比较特殊的 Cookie,Session 的底层依赖 Cookie 来实现。

7.4 session 域对象

session 用来表示一次会话,在一次会话中数据可以共享的,这时 session 作为域对象存在,可以通过 setAttribute(name, value) 方法向域对象中添加数据,通过 getAttribute(name) 从域对象中获取数据,通过 removeAttribute(name) 从域对象中移除数据。

        //获取 Session 对象
        HttpSession session = request.getSession();

        //设置session 域对象
        session.setAttribute("uname", "admin");
        session.setAttribute("upwd", "123456");
        //移除session 域对象
        session.removeAttribute("upwd");

数据存储在 session 域对象中,当 session 对象不存在了,或者是两个不同的session 对象时,数据也就不能共享了。这就不得不谈到 session 的生命周期。

例子一:请求转发(一次请求,session 和 request 作用域都有效

访问:http://localhost:8080/servlet04/ss02

例子二:重定向(两次请求,session 作用域有效, 但request 作用域无效

访问:http://localhost:8080/servlet04/ss02

注:session 可以拿到,request 拿不到值。

7.5 Session 对象的销毁

(1)默认到期时间

当客户端第一次请求 Servlet 并且操作 session 时,session 对象生成, Tomcat 中 session 默认的存活时间为 30 mins, 即你不操作界面的时间,一旦有操作,session 会重新计时。

可以在Tomcat 中的conf 目录下的 web.xml 文件中进行修改。

(2)自定义设定到期时间

当然除了以上修改方式外,我们也可以在程序中自己设定 session 的生命周期,通过 session.setMaxInactiveInterval(int) 来设定 session 的最大不活动时间,单位为秒。

当然也可以通过 session.getMaxInactiveInterval() 方法来查看当前 Session 对象的最大不活动时间。

        //获取 session 的最大存活时间
        int sessionTime = session.getMaxInactiveInterval();
        System.out.println("session 的最大存活时间:" + sessionTime);
        //设置 session 的最大存活时间
        session.setMaxInactiveInterval(15);

例子:

间隔15秒以上后刷新浏览器后,session 到期,生成一个新的 session:

(3) session 立即失效

通过以下方法让 session 立即生效:

        //销毁session 对象
        session.invalidate();

(4)关闭浏览器

从前面的 JSESSIONID 可知道, session 的底层依赖 cookie 实现,并且该cookie 的有效时间为关闭浏览器,从而 session 在浏览器关闭时也相当于失效了(因为没有JSESSIONID再与之对应)。

(5)关闭服务器

当关闭服务器时,session 销毁。

Session 失效意味着此次会话结束,数据共享结束。

8. ServletContext 对象

每一个 web 应用都有且仅有一个ServletContext 对象, 又称 Application 对象,从名称可知,该对象是与应用程序相关的。在 WEB 容器启动的时候,会为每一个 WEB 应用程序创建一个对应的 ServletContext 对象。

该对象有两大作用:第一,作为域对象用来共享数据,此时数据在整个应用程序中共享;第二,该对象中保存了当前应用程序相关信息。例如可以通过getServerInfo() 方法获取当前服务器信息,通过getRealPath(String path) 获取资源的真实路径等。

8.1 ServletContext 对象的获取

获取 ServletContext 对象的途径有很多,比如:

  1. 通过 request 对象获取
ServletContext servletContext1 = request.getServletContext();
  1. 通过 session 对象获取
ServletContext servletContext2 = request.getSession().getServletContext();
  1. 通过 ServletConfig 对象获取
ServletContext servletContext3 = getServletConfig().getServletContext();
  1. 直接获取
ServletContext servletContext4 = getServletContext();

  1. 常用方法:
        //  常用方法
        String serverInfo = request.getServletContext().getServerInfo();
        System.out.println("获取当前服务器的版本信息:" + serverInfo);
        String realPath = request.getServletContext().getRealPath("/");
        System.out.println("获取项目的真实路径:" + realPath);

8.2 ServletContext 域对象

ServletContext 也可以当作域对象来使用,通过向 ServletContext 中存取数据,可以使得整个应用程序共享某些数据。当然不建议存放过多数据,因为 ServletContext 中的数据一旦存储进去没有手动移除会一直保存。因为一般服务器不经常重启。

        // 获取ServletContext 对象
        ServletContext servletContext = request.getServletContext();
        // 设置域对象
        servletContext.setAttribute("name","zhangsan");
        // 获取域对象
        String name = (String) servletContext.getAttribute("name");
        // 移除域对象
        servletContext.removeAttribute("name");

8.3 Servlet 的三大域对象

  1. request 域对象

在一次请求中有效。请求转发有效,重定向无效。

  1. session 域对象

在一次会话中有效。 请求转发和重定向都有效, session 销毁后失效。

  1. servletContext 域对象

在整个应用程序中有效。服务器关闭后失效。

9. 文件的上传和下载

9.1 文件上传

文件上传涉及到前台页面的编写和后台服务器端代码的编写,前台发送文件,后台接收并保存文件,这才是一次完整的文件上传。

(1)前台页面实现

在做文件上传的时候,会有一个上传文件的界面,首先我们需要一个表单,并且表单的请求方式为POST;其次我们的 form 表单 的enctype 必须设为“multipart/form-data”,即 enctype="multipart/form-data",意思是设置表单的类型为文件上传表单。默认情况下这个表单类型是"application/x-www-form-urlencoded",这个不能用于文件上传。只有使用了 “multipart/form-data” 才能完整地传递文件数据。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
    <!--
        文件上传
            1. 准备表单
            2. 设置表单地提交类型为POST请求 method="post"
            3. 设置表单类型为文件上传表单 enctype="multipart/form-data"
            4. 设置文件提交的地址
            5. 准备表单元素
                1. 普通的表单项   type="text"
                2. 文件项  type="file"
            6. 设置表单元素的name属性值(表单提交一定要设置表单元素的name属性值,否则后台无法接收数据!)
    -->
    <form action="uploadServlet" method="post" enctype="multipart/form-data">
        姓名:<input type="text" name="uname"><br>
        文件:<input type="file" name="myFile"><br>
        <!--button 默认的类型是提交类型 type="submit"-->
        <button>提交</button>
    </form>

</body>
</html>

(2) 后台实现

使用注解@MultipartConfig 将一个Servlet 标识为支持文件上传。Servlet 将 multipart/form-data 的POST请求封装成Part 对象,通过Part 对手上传的文件进行操作。

package com.menergy.servlet;

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

/**
 * 文件上传
 */
@MultipartConfig    //如果是文件上传表单,一定要加这个注解
@WebServlet("/uploadServlet")
public class UploadServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置请求的编码格式
        request.setCharacterEncoding("UTF-8");
        //获取普通表单项(获取参数)
        String uname = request.getParameter("uname");//"uname"代表文本框的name 属性值
        System.out.println("uname: " + uname);
        //通过 getPart(name) 方法获取 Part 对象(name代表的是页面中 file 文件域的 name 属性值)
        Part part = request.getPart("myFile");
        //通过 Part 对象,获取上传的文件名
        String submittedFileName = part.getSubmittedFileName();
        System.out.println("fileName: " + submittedFileName);
        //获取上传文件需要存放的路径(得到项目存放的真实路径)
        String realPath = request.getServletContext().getRealPath("/");
        System.out.println("realPath: " + realPath);
        //将文件上传到指定位置
        part.write(realPath + submittedFileName);
    }
}

访问并提交表单:

注:路径有问题是因为创建项目是建到了上一个项目的文件夹内了,这里忽略它。

查看文件是否上传成功:

9.2 文件下载

文件下载,即是将服务器上的资源下载(拷贝)到本地,我们可以通过两种方式下载。第一种是通过超链接本身的特性来下载;第二种是通过代码下载。

9.2.1 超链接下载

当我们在HTML 或 JSP 页面中使用 a 标签时,愿意是希望能够进行跳转,但当超链接遇到浏览器不识别的资源时会自动下载;当遇到浏览器能够直接显示的资源,浏览器就会默认显示出来,比如 txt、png、jpg 等。当然我们也可以通过download 属性规定浏览器进行下载,但有些浏览器并不支持

(1)默认下载:

    <!--浏览器能够识别的资源-->
  <a href="download/Hello.txt">文本文件</a>
  <a href="download/瀑布.jpg">图片文件</a>

    <!--浏览器不能识别的资源-->
  <a href="download/大纲:鸦片战争.zip">压缩文件</a>

例子一:通过默认属性,超链接下载

在web目录下新建"download" 文件夹,从放三种类型的文件。

由于在IDEA中,tomcat启动时会自动将这些资源文件放到输出目录中,如果要让这些资源文件依然在当前项目下,(如果想要资源被我们的项目或者能被浏览器访问到)需要单独设置该目录,设置如下:

编写html页面:

访问:http://localhost:8080/servlet05/download.html

点击前面两个超链接:

点击第三个超链接:

(2)通过 download 属性下载

    <!--浏览器能够识别的资源-->
    <a href="download/Hello.txt" download>文本文件</a>
    <a href="download/瀑布.jpg" download>图片文件</a>

    <!--浏览器不能识别的资源-->
    <a href="download/大纲:鸦片战争.zip" download>压缩文件</a>

例子二:通过 download 属性实现浏览器下载

其它文件同例子一, 不同如下:

访问浏览器:

依次点击三个链接,三个文件都可以下载:

注:download 属性可以设置属性值,属性值即为下载时的文件名,如果不设置属性值则为原来的文件名。

9.2.2 后台实现下载

实现步骤:

  1. 需要通过 response.setContentType 方法设置 Content-type 头字段的值,为浏览器无法使用某种方式或激活某个程序来处理的MIME 类型,例如:"application/octet-stream" 或 "application/x-msdownload" 等。
  2. 需要通过 response.setHeader 方法设置Content-Disposition 头的值为 “attachment;filename=文件名”。
  3. 读取下载文件,调用 response.getOutputStream 方法向客户端写入附件内容。

例子:

(1)下载页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>超链接文件下载</title>
</head>
<body>
    <hr>
    <form action="downloadServlet" method="post">
        文件名:<input type="text" name="fileName" placeholder="请输入要下载的文件名">
        <button>下载</button>
    </form>
</body>
</html>

(2) 后台 Servlet

package com.menergy.servlet;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("文件下载 ...");
        //设置请求的编码格式
        request.setCharacterEncoding("UTF-8");
        //设置响应编码格式
        response.setContentType("text/html;charset=UTF-8");
        //获取参数(得到下载的文件名)
        String fileName = request.getParameter("fileName");
        if(fileName == null ||"".equals(fileName.trim())){
            //输入到页面
            response.getWriter().write("请输入要下载的文件名!");
            //关闭流
            response.getWriter().close();
            return;
        }
        //先得到图片存放的路径
        String realPath = request.getServletContext().getRealPath("/download/");
        //通过路径得到 file 对象
        File file = new File(realPath + fileName);
        //判断文件对象是否存在并且时一个标准文件
        if (file.exists() && file.isFile()){
            //设置响应类型(浏览器无法使用某种方式或激活某个程序来处理的MIME 类型)
            response.setContentType("application/x-msdownload");
            //设置响应头信息
            response.setHeader("Content-Disposition","attachment;filename=" + fileName);
            //得到输入流
            InputStream in = new FileInputStream(file);
            //得到输出流
            ServletOutputStream out = response.getOutputStream();
            //定义byte数组
            byte[] bytes = new byte[1024];
            //定义长度
            int len = 0;
            //循环输出
            while((len = in.read(bytes)) != -1){
                //输出
                out.write(bytes,0,len);
            }
            //关闭资源
            out.close();
            in.close();
        }else{
            response.getWriter().write("文件不存在,请重试!");
            response.getWriter().close();
        }

    }
}

访问:http://localhost:8080/servlet05/download.html

提交表单后,发现界面没有下载,而是直接显示图片,看后台控制台,发现有编码异常:

异常原因:界面输入的中文文件名,后台编码异常。

解决办法:后台将接收到的文件名进行编码处理:

更改前:

//设置响应头信息
response.setHeader("Content-Disposition","attachment;filename=" + fileName);

更改后:

//设置响应头信息
response.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(fileName));

重启服务再次下载成功:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

etcd原理剖析一

为什么Kubernetes使用etcd&#xff1f; 首先我们来看服务高可用以及数据一致性。单副本存在单点故障&#xff0c;而多副本又引入数据一致性问题。 为了解决数据一致性问题&#xff0c;需要引入一个共识算法。例如Raft等。etcd选择了Raft&#xff0c;它将复杂的一致性问题分解…

Maven 笔记

1. Maven 的简介 1.1 简介 Maven 这个词可以翻译为"专家","内行"。作为Apache 组织中的一个开源项目&#xff0c;主要服务于基于java平台的项目构建&#xff0c;依赖管理和项目信息管理。 无论是小型的开源类库项目&#xff0c;还是大型的企业级应用&am…

Spring 5 笔记 - 入门与IOC

1. Spring 入门简介 Spring&#xff1a;轻量级、开源的JavaEE框架&#xff0c; 解决企业应用的复杂性。包括IOC和AOP两个核心部分。 IOC&#xff1a; 控制反转&#xff0c;把创建对象和对象之间的调用的过程都交给Spring 进行管理&#xff0c;使耦合度降低。 AOP&#xff1a…

Winform从入门到精通(38)—StatusStrip(史上最全)更新中

一、属性 1、Name 获取StatusStrip控件对象 2、AllowDrop 允许用户拖拽数据到控件上 3、AllowItemReorder 当用于按下alt键时,是否允许对项进行排列,如下图: 4、AllowMerge 5、Anchor 6、AutoSize 7、BackColor 设置StatusStrip的背景色 8、BackgroundImage 设置背…

R语言方差分析

R中的方差分析 介绍用于比较独立组的不同类型的方差分析&#xff0c;包括&#xff1a; 单因素方差分析&#xff1a;独立样本 t 检验的扩展&#xff0c;用于在存在两个以上组的情况下比较均值。这是方差分析检验的最简单情况&#xff0c;其中数据仅根据一个分组变量&#xff0…

垃圾回收器ZGC应用分析总结

目录 一、基本概述 二、基本关键技术知识总结 &#xff08;一&#xff09;三色标记法&#xff08;着色指针&#xff09; &#xff08;二&#xff09;读屏障 &#xff08;三&#xff09;多图映射 &#xff08;四&#xff09;简单场景说明ZGC并发 三、基本回收原理介绍 四…

PowerJob基本概念

本文来说下PowerJob的一些基本概念 文章目录 PowerJob概述PowerJob官网为什么选择PowerJob同类产品对比适用场景 PowerJob概述 PowerJob是新一代分布式任务调度与计算框架&#xff0c;支持CRON、API、固定频率、固定延迟等调度策略&#xff0c;提供工作流来编排任务解决依赖关系…

React框架第七课 语法基础课《第一课React你好世界》

React框架第七课 语法基础课《第一课React你好世界》 从这一课开始真正进入到React框架的基础语法学习&#xff0c;之前的前五课做个了解即可。 1 React框架的基本项目结构 ├── README.md 使用方法的文档 ├── node_modules 所有的依赖安装的目录 ├── package-lock.j…

[架构之路-181]-《软考-系统分析师》-19- 系统可靠性分析与设计 - 2-容错性: 软件容错技术

目录 前言&#xff1a; 1 9 . 4 软件容错技术 19.4.1 N 版本程序设计 1 . 与 通 常 软 件 开 发 过 程 的 区 别 2 . 其 他 需 要 注 意 的 问 题 19.4.2 恢复块方法 19.4.3 防卫式程序设计&#xff08;预防性设计&#xff09;》广泛使用 1 . 错误检测 2 . 破坏估计 …

【C++初阶】类与对象:6个默认成员函数-----构造函数和析构函数

我们在写代码的时候经常会忘记初始化和销毁&#xff0c;C的构造函数和析构函数就能避免这个问题。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成的成员函数称为默认成员函数。 一.构造函数 A.概念 1.构造函数是一个特殊的成员函数&#xff1b; 2.名字与…

React框架的第八课 语法基础课《第二课React框架中的事件》

React框架的第八课 语法基础课《第二课React框架中的事件》 React中的事件是指通过React建立的应用程序中处理用户交互的响应。React事件处理程序只是在组件上调用的JavaScript函数&#xff0c;以响应某些类型的操作或事件&#xff0c;例如点击、触摸、滚动等。 React组件可以使…

【P3】HTTP 接口设计

一、简答 HTTP 接口设计 HTTP请求默认值&#xff1a; 配置 http 请求的默认值&#xff0c;比如协议、主机、端口 HTTP信息头管理器&#xff1a; 配置 http 请求的头部参数 HTTP请求&#xff1a; 用于和业务交互 查看结果树&#xff1a; 用于结果展示 二、准备工作 慕慕生…

希尔排序详解(Shell Sort)

本文已收录于专栏 《算法合集》 一、简单释义 1、算法概念 希尔排序是插入排序的一种又称“缩小增量排序”&#xff0c;是直接插入排序算法的一种更高效的改进版本。希尔排序是把记录按下标的一定增量分组&#xff0c;对每组使用直接插入排序算法排序&#xff1b;随着增量逐渐…

【复杂网络建模】——Pytmnet进行多层网络分析与可视化

目录 一、Pymnet介绍 二、安装步骤 三、多层网络的构建 1、单层网络的构建 2、双层随机网络的构建和可视化 3、多路复用网络图的可视化 四、总结 一、Pymnet介绍 官网&#xff1a; Pymnet是一个用于网络分析和建模的Python库。它提供了各种网络分析工具&#xff0c;例…

shell的基础学习一

文章目录 一、shell的简介二、 Shell 变量三、Shell 传递参数总结 一、shell的简介 Shell 是一个用 C 语言编写的程序&#xff0c;它是用户使用 Linux 的桥梁。Shell 既是一种命令语言&#xff0c;又是一种程序设计语言。 Shell 是指一种应用程序&#xff0c;这个应用程序提供…

百胜中国:未来将实现强劲增长

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 收入分析与未来展望 在过去的三年里&#xff0c;百胜中国&#xff08;YUMC&#xff09;的收入一直受到疫情导致的旅行限制和封锁的影响。为了应对疫情造成的业务中断&#xff0c;该公司开始专注于外卖业务&#xff0c;并将…

中通快递财报预测:中通快递2023年收入和利润将大幅下降

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 市场对中通快递2023年的预测 卖方虽然预测中通快递&#xff08;ZTO&#xff09;在2023年的表现会很不错&#xff0c;但他们也预计中通快递今年的财务业绩将不会像去年那样好。 根据S&P Capital IQ的数据&#xff0c;卖…

【软考备战·希赛网每日一练】2023年5月1日

文章目录 一、今日成绩二、错题总结第一题第二题 三、知识查缺 题目及解析来源&#xff1a;2023年05月01日软件设计师每日一练 一、今日成绩 二、错题总结 第一题 解析&#xff1a; 了解即可。 第二题 解析&#xff1a; 在序列基本有序时&#xff0c;快速排序基准元素起不到分…

第16章 变更管理

文章目录 16.1.2 项目变更的分类 50416.1.3 项目变更产生的原因 50516.2 变更管理的基本原则 50516.3 变更管理角色职责与工作程序 50616.3.1 角色职责 50716.3.2 工作程序 50716.4.1 变更管理操作要点 511 16.1 项目变更的基本概念 504 项目变更是指在信息系统项目的实施过程中…

【蓝桥杯】Python基础:经济基础决定上层建筑!

前言&#xff1a;今年4月第一次参加蓝桥杯比赛&#xff0c;选择的Python 研究生组赛道。在备赛过程中&#xff0c;发现经常会用到一些编程小技巧&#xff0c;因此笔者整理了一些蓝桥杯Python组编程基础常用的内容&#xff0c;以便日后备用。如果有小伙伴也觉得实用&#xff0c;…