从容器到HttpServlet
有关HTTP请求的相关信息,是如何变成相对应的Java对象的呢?
当请求来到HTTP服务器,服务器将请求转交给Web容器的时候,Web容器会创建一个代表当次请求的HttpServletRequest对象,并给这个对象设置请求的相关信息。同时,容器也会创建一个作为稍后对客户端进行响应的HttpServletResponse对象。
接着,容器会根据读取的@WebServlet标注或web.xml的设置,找出处理该请求的Servlet,调用它的service()方法,将创建的HttpServletRequest对象和HttpServletResponse对象传入作为参数,service()方法中会根据HTTP请求的方式,调用对应的doxxx()方法。
例如,若为GET,调用doGet()方法,那么在该方法中就可以使用两个传入的对象了。可以使用getParameter()取得请求参数,使用getWriter()取得输出用的PrintWriter()对象,并进行各项响应处理。对PrintWriter做的输出操作,最后由容器转换为HTTP响应,再由HTTP服务器对浏览器进行响应。之后容器将做对象销毁回收,该次请求响应结束。
在GET与POST都需要相同处理的情境下,通常可以在继承HttpServlet之后,在doGet()、doPost()中都调用一个自定义的processRequest()。
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
processRequest(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
processRequest(req, resp);
}
protected void processRequest(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//处理请求...
}
关于HttpServletRequest
处理请求参数与标头
HttpServletRequest定义了取得一些通用请求信息的方法:
1. getParameter():指定请求参数名称来取得对应的值。
String username = request.getParameter("name");
2. getParameterValues():可复选,多值。
String[] values = request.getParameterValues("param");
取得所有请求参数名称:
Enumeration<String> e = req.getParameterNames();
while(e.hasMoreElements()) {
String param = e.nextElement();
...
}
3.getParameterMap():键是请求参数名称,值是请求参数值。
对于HTTP的标头(描述客户端或者服务器的属性、被传输的资源以及应该实现的连接 ),可以用以下方法取得:
getHeader() getHeaders() getHeaderName() *转换:getIntHeader()或getDateHeader()
另外,getContextPath()可以取得Web应用程序环境路径。
请求参数编码处理
- POST
如果浏览器以UTF-8来发送请求,那么接收时也要使用UTF-8编码字符串,所以在取得任何参数前调用执行该语句:
req.setCharacterEncoding("UTF-8");
- GET
Tomcat在GET时,使用setCharacterEncoding()方法设置编码就不会有作用。所以是通过String的getBytes()指定编码来取得该字符串的字节数组,然后再重新构造为正确编码的字符串:
name = new String(name.getBytes("ISO-8859-1"),"UTF-8");
getReader()读取Body内容
HttpServletRequest上定义有getReader()方法,可以取得一个BufferedReader对象,通过该对象取得请求的Body数据。
...
String body = readBody(request);
...
}
private String readBody(HttpServletRequest request) throws IOException {
BufferedReader reader = request.getReader();
String input = null;
String requestBody = "";
while((input = reader.readLine()) != null) {
requestBody = requestBody = input + "<BR>";
}
return requestBody;
}
}
在同一个请求期间,getReader()与getIntputStream()只能择一调用,若两者都有调用,会抛出IllegalStatExcption异常。
getPart()、getParts()取得上传文件
编写一个Servlet来进行文件上传的处理,使用getPart()来处理上传的文件:
package cc.openhome;
import com.sun.xml.internal.fastinfoset.tools.FI_DOM_Or_XML_DOM_SAX_SAXEvent;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@MultipartConfig //Tomcat中必须设置此标注才能使用getPart()相关API
@WebServlet("/upload.do")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Part part = req.getPart("photo"); //使用getPart()取得Part对象
String filename = getFilename(part);
writeTo(filename, part);
}
private String getFilename(Part part) { //取得上传文件名
String header = part.getHeader("Content-Disposition");
String filename = header.substring(header.indexOf("filename=\"") + 10, header.lastIndexOf("\""));
return filename;
}
private void writeTo(String filename, Part part) throws IOException, FileNotFoundException { //储存文件
InputStream in = part.getInputStream();
OutputStream out = new FileOutputStream("/home/hxll/桌面/" + filename);
byte[] buffer = new byte[1024];
int length = -1;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
in.close();
out.close();
}
}
其实,Part有个方便的write()方法,可以直接将上传文件指定文件名写入磁盘中:
part.write(filename);
如果有多个文件要上传,可以调用getParts()方法,这会返回一个Collection< Part>,其中是每个上传文件的Part对象。
for(Part part : req.getParts()) { //迭代Collection中所有Part对象
if(part.getName().startsWith("file")) {
//只处理上传文件区段(因为"上传"按钮也会是其中一个Part对象,所以用if判断名称是不是以file开头)
String filename = getFilename(part);
part.write(filename);
}
}
使用RequestDispatcher调派请求
- 使用include()方法,可以将另一个Servlet的操作流程包括至目前Servlet操作流程之中
...
@WebServlet("/some.view")
...
PrintWriter out = resp.getWriter();
out.println("Some do one...");
RequestDispatcher dispatcher = req.getRequestDispatcher("other.view");
dispatcher.include(req, resp);
out.println("Some do two...");
out.close();
}
}
--------------------------------------------------------------------------
...
@WebServlet("other.view")
...
PrintWriter out = resp.getWriter();
out.println("Other do one...");
}
}
则网页上见到的响应顺序是Some do one…Other do one…Some do two…
- 使用forward()方法,将请求处理转发给别的Servlet
若要调用forward()方法,目前的Servlet不能有任何响应确认
@WebServlet("/hello.do")
public class HelloController extends HttpServlet {
private HelloModel model = new HelloModel();
@Override
protected void doGet(........ {
String name = request.getParameter("user"); //收集请求参数
String message = model.doHello(name); //委托HelloModel对象处理
request.setAttribute("message", message); //将结果信息设置请求对象成为属性
request.getRequestDispatcher("/hello.view").forward(request, response); //转发给hello.view进行响应
HelloController会收集请求参数并委托一个HelloModel对象处理。HelloModel对象处理的结果,会设置为请求对象中的属性。
请求属性范围
在调派中,如果有必须共享的“对象”,可以设置给请求对象成为属性,称为请求属性范围。setAttribute() 指定名称与对象设置属性 getAttribute() 指定名称取得属性 getAttributeNames() 取得所有属性名称 removeAttribute() 指定名称移除属性
关于HttpServletResponse
设置响应标头、缓冲区
容器可以对响应进行缓冲:所有的标头设置,必须在响应确认之前,否则会被容器忽略
getBufferSize() setBufferSize() isCommitted() 查看响应是否已确认 reset() 重置所有响应信息,连同已设置的标头一并清除 resetBuffer() 重置响应内容,但不清除标头 flushBuffer() 清除所有缓冲区中已设置的响应信息至客户端
setBufferSize()必须在调用getWriter()之前使用
reset()、resetBuffer()必须在响应未确认前调用
使用getWriter()输出字符
- 设置Locale
resp.setLocale(Locale.xxxx);
- 使用setCharacterEncoding()或setContentType()
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html; charset=UTF-8");
如果指定了charset,则setLocale()就会被忽略。
使用getOutputStream()输出二进制字符
有时候需要直接对浏览器进行字节输出,可以使用HttpServletResponse的getOutputStream()方法取得ServletOutputStream实例,它是OutputStream的子类。
...
response.setContentType("application/pdf");
InputStream in = getServletContext().getResourceAsStream("/WEB-INF/xxx.pdf");
OutputStream out = response.getOutputStream(); //取得输出串流
writeBytes(in, out);
}
private void writeBytes(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int length = -1;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
in.close();
out.close();
}
}
使用sendRedirect()、sendError()
- 可以使用HttpServletResponse的sendRedirect()要求浏览器重新请求另一个URL,又称为重定向:
response.sendRedirect("http://openhome.cc");
在响应未确认输出前执行。
- 如果在处理请求的过程中发现一些错误,而你想要传送服务器默认的状态与错误信息,可以使用sendError()方法:
response.sendError(HttpServletResponse.SC_NOT_FOUND);
在响应未确认输出前执行。