Java Web基础(三)(HttpServletRequest-请求基础)

之前已经详细讲解了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);
	}
}

这里需要注意的一点是,对于我们传递的参数,仅在此次请求周期内有效,在请求/响应之后,参数就会被销毁,所以传递的参数的生命周期是随着请求/响应的生命周期而存在/消亡的。

时间: 2024-08-12 23:53:10

Java Web基础(三)(HttpServletRequest-请求基础)的相关文章

Java Web系列:Spring Security 基础

Spring Security虽然比JAAS进步很大,但还是先天不足,达不到ASP.NET中的认证和授权的方便快捷.这里演示登录.注销.记住我的常规功能,认证上自定义提供程序避免对数据库的依赖,授权上自定义提供程序消除从缓存加载角色信息造成的角色变更无效副作用. 1.基于java config的Spring Security基础配置 (1)使用AbstractSecurityWebApplicationInitializer集成到Spring MVC 1 public class Securit

Java Web系列:Spring MVC 基础

1.Web MVC基础 MVC的本质是表现层模式,我们以视图模型为中心,将视图和控制器分离出来.就如同分层模式一样,我们以业务逻辑为中心,把表现层和数据访问层代码分离出来是一样的方法.框架只能在技术层面上给我们帮助,无法在思考和过程上帮助我们,而我们很多人都不喜欢思考和尝试. 2.实现Web MVC的基础 实现Web MVC基础可以概括为1个前段控制器和2个映射. (1)前端控制器FrontController ASP.NET和JSP都是以Page路径和URL一一对应,Web MVC要通过URL

Java Web -- Servlet(13)HttpServletRequest详解(获取请求数据、请求分派、请求域)(2)

                                    HttpServletRequest ---------------------------------------------------------------- (4)请求分派 Servlet API中定义了一个RequestDispatcher接口,俗称请求分派器. 它有两个方法 forward(ServletRequest request,ServletResponse response) include(Serv

Java Web系列:Spring Boot 基础 (转)

Spring Boot 项目(参考1) 提供了一个类似ASP.NET MVC的默认模板一样的标准样板,直接集成了一系列的组件并使用了默认的配置.使用Spring Boot 不会降低学习成本,甚至增加了学习成本,但显著降低了使用成本并提高了开发效率.如果没有Spring基础不建议直接上手. 1.基础项目 这里只关注基于Maven的项目构建,使用Spring Boot CLI命令行工具和Gradle构建方式请参考官网. (1)创建项目: 创建类型为quickstart的Maven项目,删除默认生成的

Java Web系列:Spring Boot 基础

Spring Boot 项目(参考1) 提供了一个类似ASP.NET MVC的默认模板一样的标准样板,直接集成了一系列的组件并使用了默认的配置.使用Spring Boot 不会降低学习成本,甚至增加了学习成本,但显著降低了使用成本并提高了开发效率.如果没有Spring基础不建议直接上手. 1.基础项目 这里只关注基于Maven的项目构建,使用Spring Boot CLI命令行工具和Gradle构建方式请参考官网. (1)创建项目: 创建类型为quickstart的Maven项目,删除默认生成的

Java Web学习日志(HTTP基础知识)

1.C/S和B/S C/S是客户端/服务器模式,它是软件系统体系结构,通过它可充分利用两端硬件的优势,将任务合理分配到两端实现,降低开销 B/S是浏览器/服务器模式,在这种模式下,WEB浏览器使用户最主要的应用软件,这种模式统一了客户端,将系统功能实现的核心部分集中到服务器上. 2.HTTP基础知识 HTTP是超文本传输协议的简称,他是Web应用的核心.HTTP协议由两部分程序实现:一个客户端程序和一个服务器程序,它们运行在不同的端系统,通过交换HTTP报文进行会话. 1.2.1.HTTP请求和

Java Web学习(7):JSP基础语法

一个JSP页面可以被分为以下几部份: 1)静态数据,如HTML: 2)JSP指令,如include指令: 3)JSP脚本元素和变量: 4)JSP动作: 5)用户自定义标签: 一静态数据 静态数据在输入文件中的内容和输出给HTTP响应的内容完全一致.此时,该JSP输入文件会是一个没有内嵌 Java或动作的HTML页面.而且,客户端每次请求都会得到相同的响应内容. 这个静态数据的本质就是HTML文档. 二JSP指令 JSP指令控制JSP编译器如何去生成servlet,以下是可用的指令: (1)pag

Java Web(三) -- Cookie &amp; Session

中文文件下载 针对浏览器类型,对文件名字做编码处理 Firefox (Base64) , IE.Chrome ... 使用的是URLEncoder /* * 如果文件的名字带有中文,那么需要对这个文件名进行编码处理 * 如果是IE ,或者 Chrome (谷歌浏览器) ,使用URLEncoding 编码 * 如果是Firefox , 使用Base64编码 */ // 获取来访的客户端类型 String clientType = request.getHeader("User-Agent"

JavaWeb-08 (JavaWeb-Servlet基础&amp;java web之request/respone)

JavaWeb-08 JavaWeb-Servlet基础&java web之request/respone JavaWeb-Servlet基础 一.ServletConfig对象 在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数. 实验1:day0800servletConfig 工程架构: 工程下的web.xml文件内容 <?xml version="1.0" encoding="UTF

java web 开发三剑客 -------电子书

Internet,人们通常称为因特网,是当今世界上覆盖面最大和应用最广泛的网络.根据英语构词法,Internet是Inter + net,Inter-作为前缀在英语中表示“在一起,交互”,由此可知Internet的目的是让各个net交互.所以,Internet实质上是将世界上各个国家.各个网络运营商的多个网络相互连接构成的一个全球范围内的统一网,使各个网络之间能够相互到达.各个国家和运营商构建网络采用的底层技术和实现可能各不相同,但只要采用统一的上层协议(TCP/IP)就可以通过Internet