Java Web基础(三)(HttpServletRequest-请求基础)
之前已经详细讲解了Servlet/JSP的基础知识,包括Servlet和JSP的关系、Servlet基本的编写和配置,以及一个请求/响应过程中,HTTP服务器、web容器、Servlet是如何配合工作的。 对于一个web应用程序来说,请求/响应是其工作工程的基础,ServletRequest代表请求,ServletResponse代表响应,这两个对象会随着一个请求的发起而建立,随着一
之前已经详细讲解了Servlet/JSP的基础知识,包括Servlet和JSP的关系、Servlet基本的编写和配置,以及一个请求/响应过程中,HTTP服务器、web容器、Servlet是如何配合工作的。
对于一个web应用程序来说,请求/响应是其工作工程的基础,我们这里只考虑基于HTTP协议的请求/响应模型,HttpServletRequest代表请求及相关参数,HttpServletResponse代表响应及相关参数,这两个对象会随着一个请求的发起而建立,随着一个响应的结束而销毁被回收。当一个请求到来时,HttpServlet会执行service()方法,在其中判断当前HTTP的请求方式(包括GET、POST、PUT、DELETE、HEAD、OPTIONS、TRACE),并创建HttpSevletRequest和HttpServletResponse对象,传给请求方式对应的方法doXXX()。
关于HttpSevletRequest,相关的知识无非就是请求参数的获取、编码处理、文件上传接收、请求重定向等,下面把常用的知识点整理一下,并附上demo。
获取请求参数与标头
HttpSevletRequest里面封装了获取请求参数的方法,请求参数是以键值对的形式存储在其中的,下面是相关方法:
方法 | 说明 |
String getParameter(String key) | 通过请求参数名称获取参数值 |
String[] getParameterValues(String key) | 同上,有时候请求参数名称对应多个值 |
Enumeration<String> getParameterNames() | 获取所有参数名称 |
Map<String, String[]> getParameterMap() | 请求参数以Map对象返回 |
对于HTTP的标头(Header)信息,可以使用下面的方法来获得:
方法 | 说明 |
String getHeader(String key) | 类似getParamater() |
String[] getHeaders(String key) | 类似getParamaterValues() |
Enumeration<String> getHeaderNames() | 类似getParamaterNames() |
请求参数编码处理
获取请求参数,编码是一定要考虑的,否则容易出现乱码,好在现在的框架帮我们完成了大部分编码处理的操作。什么情况下容易出现乱码呢?当客户端设置的编码和web容器使用的编码不一致时,最容易出现乱码。客户端的编码是我们自己设置的,web容器的编码一般是在一个请求中,在Content-Type表头中设置的,例如“ContentType: text/html; charset=UTF-8”就是告诉web容器:“你web容器想要获取正确的参数,就得用UTF-8解码,否则出现了问题别怪我客户端没提醒你!”。我们可以在HttpServletRequest中通过getCharacterEncoding()方法获取当前请求的编码,若客户端没有在标头中设置编码信息,这个方法将返回null,然后web容器默认使用ISO-8859-1(这是大部分浏览器默认的字符集)解码。
下面我们只讨论POST和GET两种最常用的请求方式下的编码处理。
POST
客户端会将参数封装到请求中,假如客户端的编码方式是UTF-8,它在封装参数的时候,相当于执行下面这段代码:
String parameter = java.net.URLEncoder.encode(value, "UTF-8");
然后web容器把请求和参数交给Servlet,在Servlet中取得请求参数时,若没有提前设置编码,则默认使用ISO-8859-1来解码,相当于执行了下面这段代码:
String parameter = java.net.URLEncoder.encode(value, "ISO-8859-1");
因为编码和解码使用的字符集不一样,所以就出现了乱码。
解决方式是在客户端发起请求的Content-Type标头中设置编码(charset=UTF-8),在Servlet中获取请求参数前,调用request.setCharacterEncoding():
request.setCharacterEncoding("GBK");
GET
为什么POST和GET不一样呢?因为POST和GET的传参方式不同,在HttpServletRequest的API中对serCharacterEncoding()有如下说明:
Overides the name of the character encoding userd in the body of this request
意思就是这个方式只对请求Body中的字符编码才有用,也就是说这个方法基本上只对POST有用,因为GET传参是通过URL实现的,而URL的处理是HTTP服务器来完成的,并非Web容器,所以要使用GET方式传参,其编码处理方式就不同了。
还是上面的例子,客户端的编码方式是UTF-8,执行下面代码,通过GET方式请求:
String parameter = java.net.URLEncoder.encode(value, "UTF-8");
因为使用GET方式,所以web容器设置啥编码都没用了,默认使用ISO-8859-1解码:
String parameter = java.net.URLEncoder.encode(value, "ISO-8859-1");
然后我们得在Servlet中这样解码:
String parameter = new String(value.getBytes("ISO-8859-1"), "utf-8");
下面以一个例子来演示这两种请求方式在编码处理中的差别:
test-get.html
<!DOCTYPE html>
<html>
<head>
<meta name="content-type" content="text/html; charset=GBK">
</head>
<body>
<form action="hello.view" method="get">
<input type="text" name="username" />
<button>发送GET请求</button>
</form>
</body>
</html>
test-post.html
<!DOCTYPE html>
<html>
<head>
<meta name="content-type" content="text/html; charset=GBK">
</head>
<body>
<form action="hello.view" method="post">
<input type="text" name="username" />
<button>发送POST请求</button>
</form>
</body>
</html>
HelloServlet.java
@WebServlet("/hello.view")
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getParameter("username");
name = new String(name.getBytes("ISO-8859-1"), "GBK");
System.out.println("GET:" + name);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("GBK");
String name = request.getParameter("username");
System.out.println("POST:" + name);
}
}
实际上,在Servlet中直接进行编码设置或转换,并不是最好的方法,以后学会了过滤器(Filter)时,我们就可以在过滤器中处理编码转换,将编码和逻辑解耦,这才是一种好的方式。
上传文件的接收
在HttpServletRequest中,有getReader()方法、getInputStream()方法,可以实现获取上传的文件,但这种方式实现起来很麻烦,需要自己判断文件的起始点、标签、key等,还容易出错。于是在Servlet 3.0中,新增了Part接口,可以让我们更方便地进行文件的上传处理,举个例子:
upload.html
<!DOCTYPE html>
<html>
<head>
<meta name="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<form action="hello.view" method="post" enctype="multipart/form-data">
上传文件:<input type="file" name="uploadfile" />
<input type="submit" name="upload" value="上传">
</form>
</body>
</html>
HelloServlet.java
@MultipartConfig
@WebServlet("/hello.view")
public class HelloServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Part part = request.getPart("uploadfile");
String fileName = getFileName(part);
save(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 save(String fileName, Part part) throws IOException{
InputStream in = part.getInputStream();
File file = new File("D:/" + fileName);
file.mkdirs();
OutputStream out = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int length = -1;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
in.close();
out.close();
}
}
需要注意不要忘了在类名前加上注解@MultipartConfig,加上这个标注才可以使用Part的API。另外,@MultipartConfig还可以设置下列属性:
属性 | 说明 |
fileSizeThreshold | 整数值,若上传文件大小超过这里设置的门槛,会先写入缓存文件,默认值为0 |
location | 字符串,设置写入文件时的目录,若设置该属性,则缓存文件就是写到指定的目录,可以搭配part的write()方法使用,默认为空字符串 |
maxFileSize | 限制上传文件大小,默认为-1L,即不限制大小 |
maxRequestSize | 显示multipart/form-data请求个数,默认同上 |
注意上面的getFileName()方法中,对header的截取是通过“filename=”来实现的,为什么这里要这样硬编码呢?因为我们在upload.html的form中设置了enctype="multipart/form-data",而使用此enctype发送的每个内容区段,都会有类似以下样式的标头信息:
<span style="font-family:Microsoft YaHei;font-size:14px;">Content-Disposition: form-data; name="filename"; filename="xxxx.txt"</span>
下面是一个为@MultipartConfig设置属性的例子:
@MultipartConfig(location="D:/")
@WebServlet("/hello.view")
public class HelloServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Part part = request.getPart("uploadfile");
String fileName = getFileName(part);
part.write(fileName);
}
private String getFileName(Part part) {
String header = part.getHeader("Content-Disposition");
String fileName = header.substring(header.indexOf("filename=\"") + 10,
header.lastIndexOf("\""));
return fileName;
}
}
对于多文件上传,只要在同一个form中,可以参考下面这个例子:
<body>
<form action="hello.view" method="post" enctype="multipart/form-data">
文件1:<input type="file" name="file1" />
文件2:<input type="file" name="file2" />
文件3:<input type="file" name="file3" />
<input type="submit" name="upload" value="上传">
</form>
</body>
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
for (Part part: request.getParts()) {
//加个判断是因为form表单中的上传按钮,也会被算作part
if(part.getName().startsWith("file")){
String fileName = getFileName(part);
part.write(fileName);
}
}
}
之前说过我们可以在web.xml中注册Servlet而不使用注解的方式,同样,@MultiConfig也可以放在web.xml中:
<servlet>
<servlet-name>SimpleServlet</servlet-name>
<servlet-class>com.web.SimpleServlet</servlet-class>
<multipart-config>
<location>D:/</location>
</multipart-config>
</servlet>
使用RequestDispatcher调派请求
web应用程序中,一个请求经常需要多个servlet协作完成,这就需要考虑多个servlet如何传递/交接请求、如何传递参数等。HttpServletRequest提供了getRequestDispatcher()方法取得RequestDispatcher接口的实现对象实例,调用时需要传入被调用的servlet名称:
RequestDispatcher dispatcher = req.getRequestDispatcher("test.view");
RequestDispatcher接口包含两个方法:
- include(ServletRequest,ServletResponse):请求转发后,原先的servlet还可以继续输出响应信息,接受转发的servlet对请求的响应会按代码顺序并入原先的servlet响应对象中;
- forward(ServletRequest,ServletResponse):必须在响应提交给客户端之前调用,否则抛出异常;原先的servlet会被终止,在请求中没有提交的内容将被清除,而接受转发的servlet负责对请求作出响应。
下面是一个例子:
@WebServlet("/hello.view")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.println("这是第一段输出的内容");
RequestDispatcher dispatcher = req.getRequestDispatcher("test.view");
dispatcher.include(req, resp);
out.println("这是第n+1段输出的内容(n >= 0)");
out.close();
}
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.println("这段内容将不会被输出");
RequestDispatcher dispatcher = req.getRequestDispatcher("test.view");
dispatcher.forward(req, resp);
out.println("这段内容将不会被输出");
// out.close(); 这段代码会抛出异常
}
}
调派请求时传递参数 由客户端传递过来的参数都被封装在HttpServletRequest里面,所以肯定会被传递到下一个servlet里面啦。假如我们在第一个servlet里有一些数据,想要传递到第二个servlet里,那就需要用到HttpServletRequest的setAttribute()方法了,这个用起来很简单,下面是一个例子:
@WebServlet("/hello.view")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter out = resp.getWriter();
RequestDispatcher dispatcher = req.getRequestDispatcher("test.view");
List<String> list = new ArrayList<String>();
req.setAttribute("userIds", list);
dispatcher.include(req, resp);
out.close();
}
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter out = resp.getWriter();
RequestDispatcher dispatcher = req.getRequestDispatcher("test.view");
List<String> list = new ArrayList<String>();
req.setAttribute("userIds", list);
dispatcher.forward(req, resp);
}
}
这里需要注意的一点是,对于我们传递的参数,仅在此次请求周期内有效,在请求/响应之后,参数就会被销毁,所以传递的参数的生命周期是随着请求/响应的生命周期而存在/消亡的。
更多推荐
所有评论(0)