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异常;