从容器到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);

在响应未确认输出前执行。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐