HttpServletResponse Response对象

* 功能: 设置响应消息
		1. 设置响应行
				1. 格式: HTTP/1.1 200 OK
				2. 设置状态码: setStatus(int sc)
		2. 设置响应头: setHeader(String name, String value)
		3. 设置响应体:
				* 使用步骤:
						1. 获取输出流
								* 字符输出流: PrintWriter getWriter()
			                	* 字节输出流: ServletOutputStream getOutputStream()
			                  
			            2. 使用输出流, 将数据输出到客户端浏览器
            
* 案例:
	 1. 完成重定向
	 			* 重定向: 资源跳转的方式
	 			* 代码实现:
						1. 第一种方式:
							  // 1. 设置状态码为302
		                      response.setStatus(302);
		
		                      // 2. 设置响应头location, 重定向的路径
		                      response.setHeader("location", "/responseDemo2");

						2. 第二种方式(重点掌握):
							  // 简单的重定向方法
                  				  response.sendRedirect("/responseDemo2");
						
				* 重定向的特点: redirect
							1. 地址栏发生变化
							2. 重定向可以访问其他站点(服务器)的资源
							3. 重定向是两次请求, 不能使用request对象来共享数据
							
				* 转发的特点: forward
							1. 转发地址栏路径不变
							2. 转发只能访问当前服务器下的资源
							3. 转发是一次请求, 可以使用request对象来共享数据
				
				* 路径写法:
						1. 路径分类:
									1. 相对路径: 通过相对路径不可以确定唯一资源
											* 如: ./index.html
											* 不以/开头, 以.开头路径
											
											* 规则: 找到当前资源和目标资源之间的相对位置关系
													* ./: 当前目录
													* ../: 后退一级目录
													
									2. 绝对路径: 通过绝对路径可以确定唯一资源
				                      		* 如: http://localhost:8080/responseDemo2		/responseDemo2
				                      		* 以/开头的路径
                     		
				                      		* 规则: 判断定义的路径是给谁用的? 判断请求从哪儿发出
				                      			* 给客户端浏览器使用: 需要加虚拟目录(项目的想问路径)
				                                	* 建议虚拟目录动态获取: request.getContextPath()
				                              	* 给服务器使用: 不需要加虚拟目录
				                              		* 转发路径
                             
				                    3. 动态获取虚拟目录: 以后不管服务器的虚拟目录怎么改, 你之前定义的转发, 或者重定向都不用再手动改.(注意: 虚拟目录千万不要写死)
																	String contextPath = request.getContextPath();


     2. 服务器输出字符数据到浏览器
     			* 步骤:
					1. 获取字符输出流
					2. 输出数据
					3. 代码:
							/*
			                      // 获取流对象之前, 设置流的默认编码: ISO-8859-1 设置为:utf-8,
			                      response.setCharacterEncoding("utf-8");
			
			                      // 告诉浏览器, 服务器发送的消息体数据的编码. 要浏览器使用该编码解码
			                      response.setHeader("content-type", "text/html;charset=utf-8");
			
			                      这两句可以省略成一句
			                      response.setHeader("content-type", "text/html;charset=utf-8");
			
			                      这一句又很麻烦, 所有重点记住这行(简化模式)
			                      response.setContentType("text/html;charset=utf-8");
			                  */
			
			                  // (写在最前边)设置响应格式, 不设置响应格式, 浏览器无法识别编码, 会出现中文乱码
			                  response.setContentType("text/html;charset=utf-8");
			
			                  // 1. 获取字符输出流
			                  PrintWriter pw = response.getWriter();
			                  // 2. 输出数据, write也行 print也行
			                  pw.write("<h1>哈哈哈哈</h1>");
			
			                  pw.println("<h2>嘻嘻<h2>");
							
				* 注意:
					* 乱码问题:
						  1. PrintWriter pw = response.getWriter();获取的流的默认编码是ISO-8859-1
		                  2. 设置该流的默认编码
		                  3. 告诉浏览器响应体使用的编码
		                  		* 注意: 是在获取流之前设置
		                  		response.setContentType("text/html;charset=utf-8"); 一行搞定2、3问题
                  		
                  		
     3. 服务器输出字节数据到浏览器
     			* 步骤:
					1. 获取字节输出流
					2. 输出数据
					3. 代码:	
							/*
			                    // 获取流对象之前, 设置流的默认编码: ISO-8859-1 设置为:utf-8,
			                    response.setCharacterEncoding("utf-8");
			
			                    // 告诉浏览器, 服务器发送的消息体数据的编码. 要浏览器使用该编码解码
			                    response.setHeader("content-type", "text/html;charset=utf-8");
			
			                    这两句可以省略成一句
			                    response.setHeader("content-type", "text/html;charset=utf-8");
			
			                    这一句又很麻烦, 所有重点记住这行(简化模式)
			                    response.setContentType("text/html;charset=utf-8");
			                */
			
			                // (写在最前边)设置响应格式, 不设置响应格式, 浏览器无法识别编码, 会出现中文乱码
			                response.setContentType("text/html;charset=utf-8");
			
			                // 1. 获取字节输出流
			                ServletOutputStream sos = response.getOutputStream();
			                // 2. 输出数据, write
			                /*
			                    getBytes把字符串改成字节数组, 不传编码默认是得到一个操作系统默认的
			                    编码格式的字节数组。这个表示在不同OS下,返回的东西不一样
			                */
			                sos.write("<h1>哈哈哈<h1>".getBytes("utf-8"));


     4. 验证码:
			1. 本质: 图片
			2. 目的: 防止恶意表单注册
			3. 代码:
				int width = 100;
                int height = 50;

                // 1. 创建一对象, 在内存中画图(验证码图片对象)
                /*
                     第一个参数: 宽度
                     第二个参数: 高度
                     第三个参数: 常量值, 都在BufferedImage这个类下
                 */
                BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);


                // 2. 美化图片
                // 2.1 填充背景色
                Graphics graphics = image.getGraphics(); // 获取画笔对象
                // 设置画笔的颜色-->粉色
                graphics.setColor(Color.PINK);
                // 填充矩形
                graphics.fillRect(0, 0, width, height);

                // 2.2 画边框
                graphics.setColor(Color.blue); // 设置画笔的颜色为蓝色
                // 画边框
                graphics.drawRect(0, 0, width-1, height-1);

                // 定义字符集
                String str = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890";

                // 生成随机角标
                Random random = new Random();

                // 2.3 写验证码
                for (int i = 1; i <= 4; i++){
                    // 生成随机下标
                    int index = random.nextInt(str.length());
                    // 获取随机字符
                    char ch = str.charAt(index);
                    graphics.drawString(ch+"", width/5*i, height/2);
                }

                // 2.4 画干扰线
                graphics.setColor(Color.GREEN);

                // 随机生成坐标点
                for (int i = 0; i < 10; i++) {
                    int x1 = random.nextInt(width);
                    int x2 = random.nextInt(width);
                    int y1 = random.nextInt(height);
                    int y2 = random.nextInt(height);
                    graphics.drawLine(x1, y1, x2, y2);
                }


                // 3. 将图片输出到页面展示
                /*
                     第一个参数: BufferedImage对象
                     第二个参数: 图片的后缀
                     第三个参数: 字节流对象
                 */
                ImageIO.write(image, "jpg", response.getOutputStream());

* 文件下载:
	* 文件下载需求:
		1. 页面显示超链接
		2. 点击超链接后弹出下载提示框
		3. 完成图片文件下载


	* 分析:
		1. 超链接指向的资源如果能够被浏览器解析,则在浏览器中展示,如果不能解析,则弹出下载提示框。不满足需求
		2. 任何资源都必须弹出下载提示框
		3. 使用响应头设置资源的打开方式:
			* content-disposition:attachment;filename=xxx


	* 步骤:
		1. 定义页面,编辑超链接href属性,指向Servlet,传递资源名称filename
		2. 定义Servlet
			1. 获取文件名称
			2. 使用字节输入流加载文件进内存
			3. 指定response的响应头: content-disposition:attachment;filename=xxx
			4. 将数据写出到response输出流


	* 问题:
		* 中文文件问题
			* 解决思路:
				1. 获取客户端使用的浏览器版本信息
				2. 根据不同的版本信息,设置filename的编码方式不同


	* 代码实现:
		// 1. 获取ServletContext对象
        ServletContext servletContext = this.getServletContext();

        // 2. 接受参数
        String fileName = request.getParameter("filename");

        // 3. 校验参数
        if (fileName == null) {
            response.setContentType("text/html;charset=utf-8");
            response.setStatus(404);
            response.getWriter().write("Not Found...");
            return; // 结束该方法
        }

        // 4. 设置响应头类型
        String mimeType = servletContext.getMimeType(fileName);
        response.setContentType(mimeType);

        // 5. 获取文件路径
        String realPath = servletContext.getRealPath("/img/" + fileName);

        // 6. 把文件加载进内存
        FileInputStream fis = new FileInputStream(realPath);

        /*
            重点:
                把文件加载进内存

                    一定要放在编码文件名之前, 因为先编码文件名, 那么还怎么把文件加载进内存

                使用工具类方法编码文件名
         */

        // 7. 解决中文名文件
        // 7.1 获取user-agent请求头, 获取浏览器版本信息
        String agent = request.getHeader("user-agent");
        // 7.2 使用工具类方法编码文件名即可
        fileName = DownLoadUtils.getFileName(agent, fileName);

        // 7.3 设置响应头打开方式
        response.setHeader("content-disposition", "attachment;filename=" + fileName);

        // 8. 将输入流的数据写出到输出流中
        ServletOutputStream sos = response.getOutputStream();
        // 8.1 使用数组缓冲读取多个字节, 写入多个字节
        byte[] buff = new byte[1024 * 8];
        // 8.2 每次读取的有效字节数
        int len = 0;
        // 8.3 把read读取到的数据存到buff里, 每次读取的长度赋值给len 在跟-1对比
        while((len = fis.read(buff)) != -1){
            // 8.4 使用response字节输出流中的方法write, 把读取到的字节回写到浏览器客户端
            sos.write(buff, 0, len); // 写入从bytes数组 0位置开始写入的长度为len
        }
        // 8.5 关闭文件字节输入流
        fis.close();


	* 解决文件中文名->工具类网上很多:
		package cn.xiaoge.web.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;
		    }
		}