当一个 Servlet 首次被 Web 服务器创建时,会传递一个 Response 和 Request 对象过去。
Web 服务器收到客户端的 HTTP 请求时,会针对每一次请求分别创建一个用于代表请求的 Resquest 对象和代表响应的 Response 对象。
一、HttpServletResponse
1、常用方法:
setStatus(int sc) --- 设置向客户机响应的状态码
setHeader(String name,String value) --- 设置一个响应头信息
getWriter() --- 得到向客户机输出的字符流
getOutputStream() --- 得到向客户机输出的字节流
二、OutputStream 输出中文乱码问题
当服务器向 response 中写如中文的时候,response 会按照本地的字符编码方式对中文进行编码,比如本地计算机的编码方式为 gb2312,则服务器会按照 gb2312 的方式进行编码。然后将数据传递给远方客户端,由于客户端收到的是数据而不是中文,则客户端浏览器会按照浏览器的编码方式对数据进行解码,如果客户浏览器的编码方式与服务器的不一致,则导致乱码问题的出现。解决方案:
(1) 向 response 对象中写入一个消息头信息,通知客户浏览器用指定的编码方式解码
public void test1(HttpServletResponse response) throws IOException{ response.setHeader("content-type", "text/html;charset=utf-8"); response.getOutputStream().write(("中国").getBytes("utf-8")); }
(2) 利用 <meta> 标签模拟 HTTP 响应消息头,此方法不是用过 HTTP 协议发送消息头,而是直接在源文件中加如 <meta> 声明
public void test2(HttpServletResponse response) throws IOException{ response.getOutputStream().write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'>".getBytes()); response.getOutputStream().write(("中国").getBytes("utf-8")); }
(3) 当这样向浏览器输出:response.getOutputStream().write(1); 时,浏览器依然会显示乱码,因为浏览器是一个字符解析的工具,它会在它当前的编码方式内查找 1 对应的字符,显然会出现乱码。应修改为:response.getOutputStream().write((1 + "").getBytes()); 将 1 转为字符串 1,在所有的码表中,字符 1 就是都是一样的编码方式。
三、PrintWriter 输出中文乱码问题
与 OutputStream 不同,因为无论是 PrintWriter 还是 OutputStream 都是向 response 中写入数据,但是 response 在缓存数据的时候是采用字节数组的形式,由于 OutputStream 得到的就是二进制数据,会直接保存在缓冲区中,所以先前指定好的字符编码方式可以正常使用。而 PrintWriter 是保存的字符数据,在保存到 response 的时候,会采用 ISO8859-1 的编码方式对字符进行编码,所以无论先前怎么指定编码方式,对最后的改动都不会起到作用,所以我们在使用 PrintWriter 写数据的时候要先指定 response 的编码方式。解决方案:
1、先设置 response 中的编码方式,再向客户机写入消息头,指定编码方式
public void test1(HttpServletResponse response) throws IOException{
response.setCharacterEncoding("utf-8");
response.getWriter().write("中国");
response.setHeader("content-type", "text/html;charset=utf-8");
}
2、直接调用 response 中的 setContentType 方法,该方法除了设置客户机的编码方式,它还修改了 response 中的编码方式
public void test2(HttpServletResponse response) throws IOException{ response.setContentType("text/html;charset=utf-8"); response.getWriter().write("中国"); }
四、文件下载
下载步骤:1、获得文件的绝对路径。2、通过路径创建出一个文件对象。3、获得这个文件对象的输入流。4、增加头信息的 <content-disposition> 标签,告诉客户机是下载文件的方式,不是打开文件的方式。(setHeader("content-disposition","attachment;filename" + name))。5、获取服务器的输出流,向客户机写出数据,关闭文件输入流。
public void download(HttpServletResponse response) throws FileNotFoundException, IOException{ response.setHeader("content-type", "text/html;charset=utf-8"); String realpath = this.getServletContext().getRealPath("/download/1.gif"); File file = new File(realpath); OutputStream out = response.getOutputStream(); if(!file.exists()){ out.write("对不起,该文件已被删除".getBytes("utf-8")); return; } String filename = realpath.substring(realpath.lastIndexOf("\\") + 1); response.setHeader("content-disposition", "attachment;filename=" + filename); FileInputStream in = new FileInputStream(file); int len = 0; byte[] b = new byte[1024]; while((len=in.read(b))!=-1){ out.write(b, 0, len); } in.close(); }
这种方式对于中文的文件名并不适用。如果是中文文件名要经过 URL 编码,如果文件名中包含空格,那么 URL 编码后会用 + 号替换掉,所以在编码后的名字中要用 %20 来替换
public void download1(HttpServletResponse response) throws FileNotFoundException, IOException{ response.setHeader("content-type", "text/html;charset=utf-8"); String realpath = this.getServletContext().getRealPath("/download/超级玛 丽.gif"); File file = new File(realpath); OutputStream out = response.getOutputStream(); if(!file.exists()){ out.write("对不起,该文件已被删除".getBytes("utf-8")); return; } String filename = realpath.substring(realpath.lastIndexOf("\\") + 1); filename = URLEncoder.encode(filename, "utf-8"); filename = filename.replaceAll("\\+", "%20"); response.setHeader("content-disposition", "attachment;filename=" + filename); FileInputStream in = new FileInputStream(file); int len = 0; byte[] b = new byte[1024]; while((len=in.read(b))!=-1){ out.write(b, 0, len); } in.close(); }
五、编写验证码
步骤:1、在内存中创建一个缓冲图片
2、得到这个图片的图形 Graphics
3、在这个图形上填充矩形
4、在这个图形上画边框
5、在这个图形上画干扰线
6、在这个图形上画字符串
7、利用图像流写入到客户端浏览器上
public class ValidatePic extends HttpServlet { private static final int WIDTH = 130; private static final int HEIGHT = 30; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 在内存中创建一个图片 BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); // 得到这个内存图形 Graphics g = image.getGraphics(); // 填充矩形背景 setbgcolor(g); // 画矩形边框 drawRect(g); // 画干扰线 drawLine(g); // 画文字 drawString((Graphics2D)g); // 用图片io流写入浏览器 response.setContentType("image/gif"); response.setDateHeader("Expries", -1); response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); ImageIO.write(image, "gif", response.getOutputStream()); g.dispose(); } public void drawRect(Graphics g) { g.setColor(Color.RED); g.drawRect(1, 1, WIDTH - 2, HEIGHT - 2); } public void setbgcolor(Graphics g) { g.setColor(Color.YELLOW); g.fillRect(0, 0, WIDTH, HEIGHT); } public void drawLine(Graphics g) { for (int i = 0; i < 7; i++) { int x = new Random().nextInt(WIDTH); int y = new Random().nextInt(HEIGHT); int x1 = new Random().nextInt(WIDTH); int y1 = new Random().nextInt(HEIGHT); g.drawLine(x, y, x1, y1); } } public void drawString(Graphics2D g) { String words = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6"; int x = 10; for (int i = 0; i < 4; i++) { char word = words.charAt(new Random().nextInt(words.length())); g.setFont(new Font("隶书", Font.BOLD, 20)); g.setColor(Color.BLUE); int degree = new Random().nextInt()%30; g.rotate(degree*Math.PI/180,x,20); g.drawString(word+"",x, 20); g.rotate(-degree*Math.PI*180,x,20); x = x + 30; } } }
六、定时刷新(浏览器登录跳转、论坛帖子刷新)
HTTP 定时刷新的消息头:"Refresh","3"
public class Refresh extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setHeader("Refresh", "3"); int num = new Random().nextInt(); response.getWriter().write(num+""); } }
private void director(HttpServletResponse response){ response.setHeader("Refresh", "2;url=/day05/index.jsp"); }
通常我们不这样跳转页面,我们会将消息放在域对象里面,带给显示层的 jsp 然后在 jsp 的 <meta> 标签内加入
<meta equiv="refresh" content="3";/project/index.jsp>
七、让浏览器缓存数据
我们可以在响应消息头中增加 "expires",System.currentTimeMillis() + 3600*1000 注意是当前的系统毫秒数加上缓存的时间
八、response 的请求重定向
请求重定向是指:一个 Web 资源收到客户请求后,通知客户端应去访问另外一个 Web 资源,这就是请求重定向。一次请求重定向需要像服务器发送两次请求,并且地址栏有变化,应用场景多为用户登录。
1、response.sendRedirect() 实现请求重定向
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.sendRedirect("/day05/index.jsp"); }
2、302 状态码和 location 头实现请求重定向
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setStatus(302); response.setHeader("location", "/day05/index.jsp"); }
请求重定向过程:
1、浏览器向服务器发请求
2、服务器收到请求后,如果是首次访问将创建一个新的 Servlet
3、调用 Servlet 的 service() 方法(调用之前准备好 request 和 response 对象)
4、在 service() 运行时向 response 中写数据,向 response 中写如 sendRedirect() 命令
5、service() 方法执行完后返回给服务器
6、服务器发现 response 中有 302 和 location ,于是就将 302 和 location 写给浏览器
7、浏览器收到 302 和 location 后,就又去找资源,再次发送请求
8、服务器就又找第二个 Servlet
9、服务器又准备一个 request 和 response 对象
10、服务器再调用第二个 service() 方法
11、service() 方法返回给服务器信息
12、服务器再将信息返回给浏览器
九、response 中的细节
1、getOutputStream 和 PrintWiter 两个方法相排斥,调用了一个方法后就不能再调用另一个
2、getOutputStream 和 PrintWiter 写的数据是写在 response 里面去,在 response 里面有一个字节数组的缓冲区,然后服务器再从 response 里面获取数据返回给浏览器
3、我们一般不要手动关闭 getOutputStream 和 PrintWiter ,因为在服务器调用完 service() 后,会检查这两个流有没有被关闭,如果没有,则会自动关闭。
十、HttpServletRequest对象
1、HttpServletRequest 对象代表客户端的请求,当客户端通过 HTTP 协议访问服务器时, HTTP 请求中的所有信息都封装在这个对象中(用 HashMap 进行保存)。
HTTP 请求由:请求行、请求头、请求数据 。三部分组成
(1)、请求行包括: 请求方式、请求资源、采用的协议 (GET /aa/1.html Http/1.1)
request.getMethod() --- 获取请求方式
request.getURI() --- 获取请求的资源 (URI:定位资源,URL:定位互联网上的资源)
(2)、有多个方法可以得到请求头
(3)、有多个方法可以得到请求数据 ,其中 getParamatersMap() 中得到的 Map 值是一个 String 数组,getInpuStream 可以以流的形式返回给服务器,用作文件上传
2、HTTP 中常见的请求头
Host、Accept、Cookie、Connection、Pragma、Accept-Encoding、Content-Length、Refere ……
十一、防盗链
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String from = request.getParameter("referer");
if(from != null || !from.startsWith("/project/index.jsp")){
response.sendRedirect("/project/index.jps");
return;
}
System.out.println("…………");
}
十二、request 提交数据的乱码问题
1、当用 post 方式提交时,首先浏览器将中文字符转为浏览器当前的字符编码,然后提交给服务器并存在 request 的字节缓冲区中,当我们使用 request.getParamater() 时,request 会根据 ISO8859-1 的编码方式对字节进行解码,所以会出现乱码。究其愿意,是因为 request 用错了码表,导致无法正常解析字符,所以我们只需要 request.setCharacterEncoding("..."); 即可。也就是说 request 在进行解码的时候必须与客户机浏览器的编码方式相一致。
2、当使用 get 方式提交时,由于 get 提交数据,其数据是在地址栏可显示的,所以 get 提交的数据会进行 URL 编码,URL会采用 ISO8859-1 的方式进行编码,当传给服务器的时候存在了 request 的对象里面,然后在 getParamater() 的时候等于是用 ISO8859-1 对中文进行编码,即便是指定了 request 的编码方式,也是没有用的。因为用其他编码方式对 ISO8859-1 进行解码,依然找不到对应的中文字符,所以我们应该先将字符数据以 ISO8859-1 的方式进行编码,就得到了客户机传递过来的原始数据,然后再以其他方式进行解码。正确方式应是:
String username = new String((request.getParamater("username").getBytes("ISO8859-1")),"utf-8");
十三、Web 工程中的地址访问写发
如果这个地址是给浏览器用的,那么 / 就代表你的主机,就是代表网站;如果地址是给服务器用的,那么 / 就代表当前的 Web 应用。就是以谁为使用目标的原则为基准。代表主机的话,就可以通过 /index.jsp 直接访问,如果代表浏览器则 /project/index.jsp