Request & Response
Request
Request基础
# Request对象 和 Response对象的原理
1)request 和 response 对象是由服务器创建的
2)request对象是来获取请求消息的,response对象是来设置响应消息的
# Request对象继承体系结构:
ServletRequest -- 接口
|
HttpServletRequest -- 接口
|
org.apache.catalina.connector.RequestFacade -- 类(Tomcat提供)
Request的功能
# 获取请求消息数据
* 获取请求行的数据: GET /day14/demo1?name=zhangsan HTTP/1.1
1)获取请求方式 GET:String getMethod()
2)获取虚拟路径 /day14:String getContextPath()
3)获取资源路径 /demo1:String getServletPath()
4)获取GET方式请求参数 name=zhangsan:String getQueryString()
5)获取请求URI /day14/demo1:
* String getRequestURI():/day14/demo1
* StringBuffer getRequestURL():http://localhost/day14/demo1
* URL:统一资源定位符
* URI:统一资源标识符
6)获取协议及版本 HTTP/1.1:String getProtocol()
7)获取客户机的 IP 地址:String getRemoteAddr()
package cn.itcast.web.requests; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 演示 Request 对象获取请求行数据 */ @WebServlet("/RequestDemo1") public class RequestDemo1 extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求方式:GET String method = req.getMethod(); System.out.println(method); // 获取虚拟目录:/day14 String contextPath = req.getContextPath(); System.out.println(contextPath); // 获取Servlet路径:/demo1 String servletPath = req.getServletPath(); System.out.println(servletPath); // 获取get方式请求参数:name=zhangsan String queryString = req.getQueryString(); System.out.println(queryString); // 获取请求URI:/day14/demo1 String requestURI = req.getRequestURI(); System.out.println(requestURI); // 获取请求URL:http://localhost:80/day14/RequestDemo1 StringBuffer requestURL = req.getRequestURL(); System.out.println(requestURL); // 获取协议及版本:HTTP/1.1 String protocol = req.getProtocol(); System.out.println(protocol); // 获取客户机的IP地址 String remoteAddr = req.getRemoteAddr(); System.out.println(remoteAddr); } }
* 获取请求头数据
1)通过请求头的名称获取请求头的值(不区分大小写):String getHeader(String name)
2)获取所有的请求头名称:Enumeration<String> getHeaderNames()
package cn.itcast.web.requests; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; /** * 演示 Request 对象获取请求行数据 */ @WebServlet("/RequestDemo2") public class RequestDemo2 extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取所有请求体的名称 Enumeration<String> headerNames = req.getHeaderNames(); // 遍历 while (headerNames.hasMoreElements()) { String s = headerNames.nextElement(); // 根据名称获取请求头的值 String header = req.getHeader(s); System.out.println(s + "---" + header); } } }
* 获取请求体数据
* 只有 POST 请求才有请求体,在请求体中封装了 POST 请求的参数
* 步骤:
1)获取流对象
* 获取字符输入流,只能操作字符数据:BufferReader getReader()
* 获取字节输入流,可以操作所有类型的数据:ServletInputStream getInputStream()
2)再从流对象中获取数据
其他功能
# 获取请求参数的通用方式:不论 GET 还是 POST 请求方式都可以使用下列方法来获取请求参数
* 根据参数名称获取参数值:String getParameter(String name)
* 根据参数名称获取参数值的数组(可接收checkbox的值):String[ ] getParameterValues(String name)
* 获取所有请求的参数名称:Enumeration<String> getParameterNames()
* 获取所有参数的 Map 集合:Map<String, String[ ]> getParameterMap()
package cn.itcast.web.requests; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; import java.util.Map; import java.util.Set; @WebServlet("/RequestDemo6") public class RequestDemo5 extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取请求参数 String username = request.getParameter("username"); System.out.println(username); String[] hobbies = request.getParameterValues("hobby"); for (String hobby : hobbies) { System.out.println(hobby); } Enumeration<String> parameterNames = request.getParameterNames(); // 遍历枚举 while (parameterNames.hasMoreElements()) { String s = parameterNames.nextElement(); System.out.println(s + "..." + request.getParameter(s)); } Map<String, String[]> map = request.getParameterMap(); Set<String> keys = map.keySet(); for (String key : keys) { // 根据键获取值 System.out.println(key + "..." + map.get(key)); } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
* 中文乱码问题:
* GET 方式:Tomcat 8 已经将 GET 方式乱码问题解决了
* POST 方式:会乱码
* 解决:在获取参数前,设置 Request的编码:request.setCharacterEncoding("utf-8")
# 请求转发:一种在服务器内部的资源跳转方式
* 步骤:
1)通过 Request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
2)使用 RequestDispatcher对象进行转发:forword(ServletRequest request, ServletResponse response)
* 特点:
1)浏览器地址栏路径不发生变化
2)只能转发到当前服务器内部资源中
3)转发也被视为一次请求
# 共享数据:
* 域对象:一个有作用范围的对象,可以在范围内共享数据
* Request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
* 方法:
1)存储数据:void setAttribute(String name, Object obj)
2)通过键获取值:Object getAttribute(String name)
3)通过键移除键值对:void removeAttribute(String name)
package cn.itcast.web.requests; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/RequestDemo3") public class RequestDemo3 extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo3 被访问了"); // 存储数据到 request域中 request.setAttribute("msg","hello"); request.getRequestDispatcher("/RequestDemo4").forward(request, response); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
# 获取 ServletContext:
* ServletContext getServletContext()
案例:用户登录
# 用户登录案例需求:
1)编写 login.html 登录页面:username & password 两个输入框
2)使用 Druid数据库连接池技术,操作Mysql,day14数据库中的 user表
3)使用 JDBCTemplate技术封装 JDBC
4)登陆成功,跳转到 SuccessServlet展示:登录成功!${用户名}$,欢迎您
5)登录失败,跳转到 FailServlet展示:登陆失败,用户名或密码错误
# 分析
# 开发步骤
1)创建项目,导入 html页面,配置文件,jar包
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///day14
username=root
password=123
initialSize=5
maxActive=10
maxWait=3000
2)创建数据库环境
CREATE DATABASE day14;
USE day14;
CREATE TABLE USER( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) UNIQUE NOT NULL, PASSWORD VARCHAR(32) NOT NULL );
3)创建包 cn.itcast.domain,创建类 User
package cn.itcast.domain; /** * 用户的实体类 */ public class User { private int id; private String username; private String password; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { final StringBuffer sb = new StringBuffer("User{"); sb.append("id=").append(id); sb.append(", username='").append(username).append('\''); sb.append(", password='").append(password).append('\''); sb.append('}'); return sb.toString(); } }
4)创建包 cn.itcast.util,编写工具类 JDBCUtils
package cn.itcast.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; import java.sql.Statement; /** * JDBCUtils,使用 Druid连接池 */ public class JDBCUtils { // 1、定义成员变量 DataSource private static DataSource ds; static { try { Properties pro = new Properties(); pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties")); ds = DruidDataSourceFactory.createDataSource(pro); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } /** * 获取连接的方法 */ public static Connection getConnection() throws SQLException { return ds.getConnection(); } /** * 释放资源的方法 */ public static void close(Statement stmt, Connection conn) { close(null, stmt, conn); } public static void close(ResultSet rs, Statement stmt, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 获取连接池的方法 */ public static DataSource getDataSource() { return ds; } }
5)创建包 cn.itcast.dao,创建类 UserDao,提供 login() 方法
package cn.itcast.dao; import cn.itcast.domain.User; import cn.itcast.util.JDBCUtils; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; /** * 提供 login的方法 */ public class UserDao { // 声明JDBCTemplate对象来共用 private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource()); /** * 登录方法 */ public User login(User loginUser) { try { // 编写 sql String sql = "select * from user where username = ? and password = ?"; // 调用 query方法 User user = template.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), loginUser.getUsername(), loginUser.getPassword()); return user; } catch (DataAccessException e) { e.printStackTrace(); return null; } } }
6)创建包 cn.itcast.web.servlet包,创建类 LoginServlet()
package cn.itcast.servlet; import cn.itcast.dao.UserDao; import cn.itcast.domain.User; import org.apache.commons.beanutils.BeanUtils; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Map; @WebServlet("/loginServlet") public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置编码 req.setCharacterEncoding("utf-8"); /*// 获取请求参数 String username = req.getParameter("username"); String password = req.getParameter("password"); // 封装user对象 User loginUser = new User(); loginUser.setUsername(username); loginUser.setPassword(password);*/ // 获取所有请求参数 Map<String, String[]> map = req.getParameterMap(); // 创建 User对象 User loginUser = new User(); try { BeanUtils.populate(loginUser,map); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } // 调用 UserDao的login方法 UserDao dao = new UserDao(); User user = dao.login(loginUser); if (user == null) { // 登录失败 req.getRequestDispatcher("/failServlet").forward(req,resp); } else { // 登录成功 // 存储数据 req.setAttribute("user",user); // 转发 req.getRequestDispatcher("/successServlet").forward(req,resp); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doGet(req, resp); } }
7)在 servlet包下,编写 FailServlet类和SuccessServlet类
package cn.itcast.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/failServlet") public class FailServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 给页面写一句话 // 设置页面的编码 response.setContentType("text/html;charset=utf-8"); // 输出 response.getWriter().write("登录失败,用户名或密码错误"); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
package cn.itcast.servlet; import cn.itcast.domain.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/successServlet") public class SuccessServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置页面的编码 response.setContentType("text/html;charset=utf-8"); // 获取request域中共享的user对象 User user = (User) request.getAttribute("user"); // 输出 response.getWriter().write("登录成功," + user.getUsername() + "欢迎您"); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
8)login.html 中 form表单的action路径写法:
* 虚拟目录 + 资源名称
9)BeanUtils工具类,简化数据封装,(org.apache.commons.beanutils.BeanUtils)
* 用于封装 JavaBean的
①:JavaBean:标准的Java类
* 要求:
1、类必须被 public 修饰
2、必须提供空参的构造器
3、成员变量必须使用 private 修饰
4、提供公共的 setter 和 getter 方法
* 功能:封装数据
②:属性的概念
* setter 和 getter 方法名截取后的产物
* 例如:getUsername() --> Username --> username
③:方法:
1、setProperty(Object obj, String name, Object value):设置属性值
2、getProperty(Object obj, String name):获取属性值
3、populate(Object obj, Map map):将 Map 集合的键值对信息封装到对应的 JavaBean对象中
package cn.itcast.test; import cn.itcast.domain.User; import org.apache.commons.beanutils.BeanUtils; import org.junit.Test; import java.lang.reflect.InvocationTargetException; public class BeanUtilsTest { @Test public void test() { User user = new User(); try { BeanUtils.setProperty(user, "username", "zhangsan"); System.out.println(user); String password = BeanUtils.getProperty(user, "password"); System.out.println(password); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } }
Response
设置响应消息
# 设置响应行
* 格式:HTTP/1.1 200 ok
* 设置状态码:setStatus(int sc)
# 设置响应头:setHeader(String name, String value)
# 设置响应体:
1)获取输出流:
* 字符输出流:PrintWriter getWriter()
* 字节输出流:ServletOutputStream getOutputStream()
2)使用输出流,将数据输出到客户端浏览器
案例
# 完成重定向
* 重定向:资源跳转的方式
* 重定向的特点:redirect
1)地址栏发生变化
2)重定向可以访问其他站点(服务器)的资源
3)重定向是两次请求,不能使用 Request对象来共享数据
* 转发的特点:forward
1)转发地址栏路径不变
2)转发只能访问当前服务器下的资源
3)转发只有一次请求,可以使用 Request对象来共享数据
package cn.itcast.web.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/responseDemo1") public class ResponseDemo1 extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("demo1..."); // 访问 /responseDemo1,会自动跳转到 /responseDemo2资源 /*// 设置状态码为302 resp.setStatus(302); // 设置响应头location resp.setHeader("location", "/day15/responseDemo2");*/ // 简单的重定向方法 resp.sendRedirect("/day15/responseDemo2"); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
* 路径写法:
1)路径分类:
* 相对路径:通过相对路径不可以缺点唯一资源
* 如:./index.html
*确定当前资源和目标资源之间的相对位置关系
* ./ :当前目录
* ../ :后退一级目录
* 绝对路径:用过路径可以确定唯一的资源
* 如:http://localhost/day15/responseDemo2
2) 资源访问路径:判断定义的路径是给谁用的,判断请求将来从哪里发出
* 给客户端浏览器使用:需要加虚拟目录(项目的访问路径)
* 建议虚拟目录动态获取:requset.getContextPath()
* <a>,<form>,重定向
* 给服务器使用:不需要加虚拟目录
* 转发
# 服务器输出字符数据到浏览器
* 步骤:
1)获取字符输出流
2)输出数据
* 乱码问题:
1)PrintWriter response.getWriter(),从 Tomcat 获取的流的默认编码是 ISO-8859-1
2)设置该流的编码
* response.setCharacterEncoding("utf-8")
3)告诉浏览器响应体使用的编码
* response.setContentType("text/html;charset=utf-8")
package cn.itcast.web.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/responseDemo3") public class ResponseDemo3 extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 在获取流对象之前,设置编码方式,原来是 ISO-8859-1 resp.setCharacterEncoding("utf-8"); // 告诉浏览器,服务器发送的消息的编码方式 // resp.setHeader("content-type","text/html;charset=utf-8"); resp.setContentType("text/html;charset=utf-8"); // 获取字符输出流 PrintWriter writer = resp.getWriter(); // 输出数据 writer.write("<h1>hello response</h1>"); writer.write("<h1>你好啊啊啊 response</h1>"); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
# 验证码
* 本质:图片
* 目的:防止恶意表单注册
package cn.itcast.web.servlet; import javax.imageio.ImageIO; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Random; @WebServlet("/checkCodeServlet") public class CheckCodeServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 创建对象,在内存中代表图片(验证码图片对象) int width = 100; int height = 50; BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 美化图片 // 填充背景色 Graphics g = image.getGraphics(); // 设置画笔对象 g.setColor(Color.cyan); // 设置画笔颜色 g.fillRect(0, 0, width, height); // 画边框 g.setColor(Color.BLACK); g.drawRect(0, 0, width - 1, height - 1); // 生成验证码 String str = "ABCEEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyz0123456789"; // 生成随机角标 Random ran = new Random(); for (int i = 1; i <= 4; i++) { int index = ran.nextInt(str.length()); //获取字符 char ch = str.charAt(index); // 写验证码 g.drawString(ch + "", width / 5 * i, height / 2); } // 画干扰线 g.setColor(Color.green); // 随机生成坐标点 for (int i = 0; i < 10; i++) { int x1 = ran.nextInt(width); int x2 = ran.nextInt(width); int y1 = ran.nextInt(height); int y2 = ran.nextInt(height); g.drawLine(x1, y1, x2, y2); } // 将图片输出到页面展示 ImageIO.write(image, "jpg", resp.getOutputStream()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <img id="checkCode" src="/day15/checkCodeServlet"> <a id="change" href="javascript:void(0)">看不清,换一张?</a> <script> /* 分析: 点击超链接或者图片需要换一张图片 1、给超链接和图片绑定单机事件 2、重新设置图片的src属性值 */ window.onload = function (ev) { var img = document.getElementById("checkCode"); img.onclick = function (ev1) { var date = new Date().getTime(); img.src = "/day15/checkCodeServlet?" + date; }; var link = document.getElementById("change"); link.onclick = function (ev1) { var date = new Date().getTime(); img.src = "/day15/checkCodeServlet?" + date; }; } </script> </body> </html>
ServletContext对象
# 概念:代表整个 web 应用,可以和程序的容器(服务器)来通信
# 获取(不同的方法获取的是同一个对象):
* 用过 Request对象来获取:request.getServletContext()
* 通过 HttpServlet获取:this.getServletContext()
# 功能:
* 获取 MIME类型
* MIME类型:在互联网通信过程中定义的一种文件数据类型
* 格式:大类型/小类型 如:text/html image/jpeg
* 获取一个文件的MIME类型:String getMimeType(String file)
package cn.itcast.web.servletContext; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/ServletContextDemo2") public class ServletContextDemo2 extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取ServletContext ServletContext context = this.getServletContext(); // 定义文件名称 String filename = "a.jpg"; // 获取MIME类型 String mimeType = context.getMimeType(filename); System.out.println(mimeType); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
* 域对象:共享数据
* setAttribute(String name, Object value)
* getAttribute(String name)
* removeAttribute(String name)
* ServletContext对象的范围:所有用户和所有请求的数据,因此使用需谨慎
* 获取文件的真实路径(服务器路径)
* 方法:String getRealPath(String path)
案例
# 文件下载需求:
1)页面显示超链接
2)点击超链接后弹出下载提示框
3)完成图片文件下载
# 分析:
1)超链接标签指向的资源如果能够被浏览器解析,则在浏览器中展示,不满足需求
2)任何资源都必须弹出下载提示框
3)使用响应头设置资源的打开方式:content-disposition:attachment;filename=xxx
# 步骤:
1)定义页面,编辑超链接 href 属性,指向 Servlet,传递资源名称 filename
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>download</title> </head> <body> <a href="/day15/DownloadServlet?filename=1.jpg">图片1</a> </body> </html>
2)定义Servlet
① 获取文件名称
② 使用字节输入流加载文件进内存
③ 指定 response 的响应头:content-disposition:attachment;filename=xxx
④ 将数据写出到 response输出流中
package cn.itcast.web.download; import javax.servlet.ServletContext; 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.FileInputStream; import java.io.IOException; @WebServlet("/DownloadServlet") public class DownloadServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求参数 req.setCharacterEncoding("utf-8"); String filename = req.getParameter("filename"); // 找到文件的服务器路径 ServletContext context = this.getServletContext(); String realPath = context.getRealPath("/img/" + filename); System.out.println(realPath); // 设置response的响应头 String mimeType = context.getMimeType(filename); resp.setContentType(mimeType); resp.setHeader("content-disposition", "attachment;filename=" + filename); // 将输入流的数据写出到输出流中 FileInputStream fis = new FileInputStream(realPath); ServletOutputStream sos = resp.getOutputStream(); byte[] buff = new byte[1024 * 8]; int len = 0; while ((len = fis.read(buff)) != -1) { sos.write(buff, 0, len); } fis.close(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doGet(req, resp); } }
# 问题:
* 中文文件名乱码
* 解决:
① 获取客户端使用的浏览器版本信息
② 根据不同的版本信息,设置 filename 的编码方式
package cn.itcast.utils; import sun.misc.BASE64Encoder; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; public class DownLoadUtils { public static String getFileName(String agent, String filename) throws UnsupportedEncodingException { if (agent.contains("MSIE")) { // IE浏览器 filename = URLEncoder.encode(filename, "utf-8"); filename = filename.replace("+", " "); } else if (agent.contains("Firefox")) { // 火狐浏览器 BASE64Encoder base64Encoder = new BASE64Encoder(); filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?="; } else { // 其它浏览器 filename = URLEncoder.encode(filename, "utf-8"); } return filename; } }