[Servlet]HttpServletResponse的二进制输出(获取应用程序中的资源再输出)、重定向、错误发送

时间:2022-07-11 21:03:39

1. 使用getOutputStream输出二进制字节流:

    1) 有时相对浏览器输出的直接是一个文件资源而不是HTML等字符串文档,此时就需要使用HttpServletResponse的getOutputStream进行字节流输出;

    2) 输出过程:

         i. ServletOutputStream HttpServletResponse.getOutputStream(); // 获取一个ServletOutputStream对象,可以用来输出

         ii. ServletOutputStream有一系列方法输出二进制字节流,总体来说有两类,一类是print,另一类是println,都分别重载输出boolean、char、int、double、float、long、String这些基本类型的方法,只不过它们都是以二进制的格式输出的,想要读取它们也必须以二进制的方式读取,否则得到的只会是乱码;

         iii. 由于ServletOutputStream继承自OutputStream,因此可以用OutputStream对象引用来接受getOutputStream返回的对象,可以直接使用OutputStream的write方法大批量写数据:

              a. void OutputStream.write(byte[] b); // 写b.length个字节

              b. void OutputStream.write(byte[] b, int off, int len); // 从b[off]开始写,写len个字节

              c. 当然write也提供了只写一个int的版本:void OutputStream.write(int b);

!!OutputStream属于Java SE的类;


!!!输出内容要与MIME类型匹配:由于使用getOutputStream输出的是二进制数据,因此可以输出任何类型的数据,可以是PDF,也可以是视频、音频,但是如果你想让浏览器正确接受/打开接受到的二进制资源就必须得设置相应的MIME类型;


2. 获取Web应用程序中的资源:

    1) 有时想从Web应用程序目录中读取资源并发送给客户端,此时就可以使用GenericServlet的getServletContext获得应用程序环境根目录资源,然后使用getResourceAsStream获取具体的资源,该函数的参数是资源相对于环境根目录的路径;

    2) getServletContext:

         i. ServletContext GenericServlet.getServletContext();

         ii. 该函数继承自GenericServlet,而HttpServlet和Servlet都继承自该类,因此要调用该函数一定要通过this调用,或者直接调用也行,但还是使用this更清晰明了;

         iii. 返回的是Servlet应用的环境根目录资源,可以通过该资源句柄进一步获取目录中的其它资源;

    3) getResourceAsStream:

         i. InputStream ServletContext.getResourceAsStream(String path);

         ii. 参数是目标资源相对于环境根目录的路径;

         iii. 返回的是InputStream对象,它将资源以二进制形式保存在InputStream对象的缓冲区中;

         iv. 然后在用InputStream的read方法将缓冲区中的内容读取到字节数组中:

             a. int InputStream.read(byte[] b);  // 缓冲区写入到b中,能写多少写多少,顶多写满

             b. int InputStream.read(byte[] b, int off, int len); // 将缓冲区中的内容从b的off处开始写,指定写len个字节

             c. 返回值表示实际写入到b中的字节数,只能小于等于b.length或len;

!!!可以通过该方法使客户端访问到WEB-INF中的资源:

            a. 通常Tomcat等Web容器默认都不允许用户直接访问WEB-INF目录中的资源;

            b. 但是Servlet可以通过在getResourceAsStream中填写"/WEB-INF/XXX"的路径使用户顺利获取WEB-INF中的资源;


!!!InputStream和OutputStream用完之后千万别忘了用close方法关闭;


3. 一个简单下载器的例子:用户需要提供密码来下载WEB-INF目录下的PDF文件:

doGet:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		//response.getWriter().append("Served at: ").append(request.getContextPath());
		
		String passwd = request.getParameter("passwd");
		if ("123456".equals(passwd)) {
			response.setContentType("application/pdf"); // 由于是二进制数据,所以不用设置编码
			InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/abc.pdf");
			OutputStream out = response.getOutputStream();
			
			byte[] buff = new byte[1024];
			int len = -1;
			while ((len = in.read(buff)) != -1) out.write(buff);
			
			in.close();
			out.close();
		}
	}

!测试URL:http://localhost:8080/Download/download.do?passwd=123456

!!这里只是一个演示,所以即使有密码用使用GET方法请求了;


4. 响应重定向:

    1) 响应重定向:用HttpServletResponse向浏览器发送消息,让浏览器重新请求一个新的URL(Response会把这个新的URL发给浏览器来重新请求);

    2) 响应重定向和请求调派的不同:

         i. 请求调派是在服务器端的Servlet之间派来派去,和浏览器无关,因此再怎么调派浏览器地址栏中的内容都不会变化;

         ii. 响应重定向由于是给浏览器发送了新的URL让其重新请求,因此浏览器地址栏中的内容就变成了新的URL;

         iii. 请求调派使用的是Request,而响应重定向使用的是Response;

    3) HttpServletResponse重定向的方法:

         i. void HttpServletResponse.sendRedirect(String location);

         ii. location是重定向的新的URL;

         iii. 使用该方法后会设置响应标头:

              a. 首先是状态码被设置成了301,即通知浏览器本次响应的内容是重定向;

              b. 其次设置Location标头的内容为新的URL,浏览器会读取该标头然后用该URL重新请求;

         iv. 注意!一定要在响应确认前调用重定向方法,否则会跑出IllegalStateException异常;


5. 向客户端发送错误页面:

    1) 使用sendError方法:

         i. void HttpServletResponse.sendError(int sc);   // 指定状态码sc(Status Code)的缩写,并使用默认的错误信息文本

         ii. void HttpServletResponse.sendError(int sc, String msg);   // 自定义错误信息文本

         iii. 该方法会返回一个错误页面,页面中有错误状态码和错误信息,返回的同时也清空输出缓冲区;

    2) 常用的状态码:都以SC_打头,即Status Code的缩写,而SC_并不都是错误码,它包含了所有类型的状态,当然错误状态也在其中,这里罗列一些常见的错误状态码

SC_BAD_REQUEST:400,用户的请求内容语法有误

SC_BAD_GATEWAY:502,错误网关/代理,一般是指上游服务器直接协议不协调导致无法返回正确的响应

SC_INTERNAL_SERVER_ERROR:500,HTTP服务器内部错误

SC_METHOD_NOT_ALLOWED:405,请求方法在服务器端没有实现,因此该中请求被拒绝

SC_NOT_FOUND:404,最常见的,即请求的资源找不到

SC_REQUEST_TIME_OUT:408,请求超时

    3) HttpServlet的doGet中默认调用了sendError来识别两种错误,一种是SC_BAD_REQUEST,另一种是SC_METHOD_NOT_ALLOWED;

    4) sendError同样需要在响应确认前调用,否则也会跑出IllegalStateException异常;