1 文件的下载和上传(***重要***)
HTTP请求及HTTP响应中都包含正文部分。HTTP响应的正文部分最常见的是HTML文档,此外还可以是其他任意格式的数据,如图片和声音文件中的数据。同样,HTTP请求的正文部分不仅可以是字符串格式的请求参数,也可以是其他任意格式的数据。
Web服务器只要把特定文件中的数据放到HTTP响应的正文部分,就能向浏览器发送任意格式的文件。同样,浏览器只要把特定文件中的数据放到HTTP请求的正文部分,也能向服务器发送任意格式的文件。
文件下载
文件下载是指把服务器端的文件发送到客户端,Servlet能够向客户端发送任意格式的文件数据。
文件上传
文件上传指把客户端文件发送到服务器端。此时,客户端发送的HTTP请求正文采用 multipart/form-data数据类型,它表示复杂的包含多个子部分的复合表单。
文件上传原理分析:
HTML核心代码 <form method="post" enctype="MULTIPART/FORM-DATA" action=" UploadServlet"> Choose File: <input type="file" name="filedata" size="30"/> <input type="submit" name="submit" value="upload"> form> 运行显示如下图 此时点击浏览,选择Data.txt文件作为上传文件。 Data.txt中包含如下内容 Test Dataupload01 Test Dataupload02 Test Dataupload03 Test Dataupload04 此时点击upload按钮,提交给Web服务器。此时HTTP请求信息 分析上传文件的HTTP请求 HTTP请求的正文部分为复合类型,包含两个子部分:文件部分和提交按钮部分。提交请求时,浏览器随机产生了一个字符串形式的边界(boundary)作为HTTP请求头的一部分: HTTP请求的正文部分的各个子部分之间用边界进行分割。每个子部分由头和正文部分组成,头和正文部分之间用空行分割。如下图 |
根据以前所学,如果要将Data.txt文件中数据上传到服务器,需要对HTTP请求进行解析,读取其中的边界值,再根据边界值定位到文件部分,进而定位到文件部分的正文部分,再把正文部分的数据保存到本地文件系统中。 此程序可向服务器端上传任意格式的文件数据。一般处理文件部分的正文部分时,会按照字节流而不是字符流处理写到本地文件整。 思路:解析该HTTP请求,将上传的信息保存到服务器。 |
根据以上分析,不管HTTP请求正文为何种数据类型,Servlet容器都会把HTTP请求包装成一个HttpServletRequest对象。请求正文为”multipart/form-data”数据类型时,Servlet直接从HttpServletRequest对象中解析出复合表单的每个子部分,但工作量依然非常复杂。
使用Apache开源软件组织提供的两个软件包实现文件上传,可以简化操作。
fileupload软件包(commons-fileupload-xxx.jar):负责上传文件的软件包
io软件包(commons-io-xx.jar):负责输入输出的软件包。该包是fileupload包的依赖包。
对于正文部分为”multipart/form-data”类型的HTTP请求,uploadfile软件包把请求正文包含的复合表单中的每个子部分看做是一个FileItem对象。FileItem对象分为两种类型。
Ø formField:普通表单域类型。表单中的文本框及提交按钮等都是这种类型。
Ø 非formField:上传文件类型。表单中的文本域就是这种类型,包含了文本数据。
fileupload涉及的接口或类的解释
Ø DiskFileItemFactory类实现了FileItemFactory接口,可用于创建DiskFileItem对象的工厂。
Ø DiskFileItem类实现了FileItem接口,表示基于硬盘的FileItem,DiskFileItem能够把客户端上传的文件数据保存到硬盘上,
Ø ServletFileUpload类为文件上传类,与FileItemFactory关联。是文件上传处理类。可设置上传文件的大小,解析请求对象中的复合表单。
DiskFileItemFactory:(可理解为向本地磁盘写数据的IO流对象) //创建一个基于硬盘的FileItem工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //设置向硬盘写数据时所用的缓冲区大小.为了提高向硬盘写入数据的效率,特别是大容量数据的效率 factory.setSizeThreshold(2*1024);//2K //设置临时目录. 写数据会向临时目录存放一些临时数据。 factory.setRepository(new File(tempFilePath)); |
ServletFileUpload:(可理解为接收客户端请求,解析请求的流对象) //创建一个文件上传的处理器对象 ServletFileUpload upload = new ServletFileUpload(factory); //设置允许上传的文件的最大尺寸 upload.setSizeMax(4*1024*1024);//4M //items存放都是FileItem对象 List 遍历items,根据FileItem对象的 isFormField()方法判断类型,采取相应操作。 |
2request获得请求参数
浏览器请求方法最常用的两种:GET, POST。两种方式都可以向服务器发送请求参数。
² GET请求方式
1)在浏览器地址栏直接输入URL?name=value&name=value形式。
3)表单或不写method属性,默认即为GET方式。此时提交到服务器时,在浏览器的URL和请求首行的URI中,会包含?name=value&name=value…形式的表单数据信息。
² POST请求方式
仅在表单中,通过method设置。。
POST方式提交时,所有表单数据的值在请求正文中以name=value&name=value…形式发送给服务器。
² GET请求和POST请求区别? (面试题!)
1)GET请求参数的值会在浏览器地址栏显示,不安全;POST方式请求参数的值都在请求正文中,不会显示在浏览器上,相对安全。
2)GET请求参数在地址栏中有长度限制(1k);POST请求参数在请求正文中,没有长度限制。
3)GET方式所有请求参数数据都在请求首行的URI中,服务器只能通过new String(param.getBytes(“ISO-8859-1”, “utf-8”))形式设置参数编码;POST方式既可以通过上述方式,也可通过reqeust.setCharacterEncoding(“UTF-8”)方式设置参数编码。此方法用于设置请求正文的编码(必须在getPrameter之前设置)。
案例: 演示超链接
3验证码(动态生成图片)
最初的网站是没有验证码的,黑客就利用程序自动注册大量垃圾用户(僵尸用户)。为了防止自动注册,才使用验证码。
验证码原理:在服务器端产生一个随机的(字母,数字,图片(12306))等序列,然后生成随机的图片,将图片发送给客户端,用户需要识别出图片内容,在注册或登录时,填入验证码的值,连同注册或登录信息一并发送到服务器上,服务器接收后,比对验证码是否一致,如果一致才允许注册或登录操作
即使有验证码,黑客也很快找到了应对技术(OCR: 文字识别软件),对网页上的验证码图片进行自动识别,为了对抗OCR,又在生成的验证码图片上面加入了干扰线。然而目前OCR解决了干扰线。所以,目前验证码并不能完全保证暴力破解或恶意注册。验证码技术还在发展,OCR也在发展,还需要探索更好的算法,去应对这些问题。
案例:如何绘制图片,并将图片发送到浏览器
案例:实现网页验证码功能
总结:以上的验证码还是比较弱,现在出现了很多字符显示的变换和其他类型的验证码,字符显示变换常见的有字体和大小随机变化,显示位置变化,字符变形显示(需要动用三角函数)。还有回答问题的验证码,找图片的验证码等形式。
4Cookie
Cookie,英文含义”点心”。
作用:浏览器访问Web服务器时,服务器在客户端硬盘上存放的信息(服务器给客户的点心),服务器可根据Cookie跟踪客户端信息。
运行机制类比:健身房会员卡相似,第一次报名,健身房发送会员卡,会员卡存储了客户信息。以后客户每次去健身房,先出示会员卡,健身房根据会员卡信息,判断是否允许健身。
Cookie运行机制:客户端首次访问服务器时,服务器先在客户端存放包含该客户相关信息的Cookie,以后客户端每次请求访问服务器时,都会在HTTP请求数据中包含Cookie,服务器解析HTTP请求中的Cookie,获得客户信息。
Cookie运行机制由HTTP协议规定,大多数浏览器和服务器都支持Cookie。服务器为了支持Cookie,需要具备可以在HTTP响应结果中添加Cookie数据,解析HTTP请求中的Cookie数据;浏览器为了支持Cookie,需要具备可以解析HTTP响应结果中的Cookie数据,将Cookie数据保存在本地硬盘,读取本地硬盘上的Cookie数据,将其添加到HTTP请求中。
Servlet访问Cookie的实现:
² Servlet无需和HTTP请求或响应中的原始Cookie数据关联,Servlet容器提供了为Servlet访问Cookie的方法。Cookie用javax.servlet.http.Cookie类表示,每个Cookie对象包含一个name和value。
² Servlet中创建Cookie对象,并添加到HTTP响应头中
Cookiecookie = new Cookie(“username”, “tom”); response.addCookie(cookie); |
² Servlet中获取客户端发来的Cookie对象
//返回客户端发来所有Cookie,如果请求中没有,返回null Cookie []cookies = request.getCookies(); 遍历,通过Cookie对象getName,和getValue获取Cookie数据 |
² 设置Cookie对象的有效期
Servlet向客户端写数据Cookie时,可以设置Cookie对象的有效期(Cookie默认的有效期为-1)。告诉浏览器如何处理Cookie。使用方法 cookie.setMaxAge(int expiry) 参数expiry ² 大于0:就告诉浏览器再客户端硬盘上保存Cookie的时间为expiry秒。 ² 等于0:告诉浏览器删除当前Cookie ² 小于0:告诉浏览器不要把Cookie保存到客户端硬盘上,仅仅存在于当前浏览器进程中,浏览器关闭,Cookie对象也消失。
Servlet可通过getMaxAge()获取Cookie的有效期。 |
案例:演示Cookie
1- 当设置的cookiename相同,value不相同时,后设置的value会覆盖以前。
2- 一个浏览器可以保存一个服务器的多个cookie
3- 不同的浏览器,只能访问自己的cookie。
4- Servlet只有一个对象,那么该类中的属性,被所有的客户端共享。
案例:演示对Cookie的修改和删除
了解以下内容即可:
如图:浏览器访问app1应用时,保存了一个Cookie。当再次访问app1, app2, app3应用中的web组件时,浏览器是否会把Cookie添加到HTTP请求中,从而让AServlet,BServlet,CServlet组件读取Cookie呢?
app1应用中的组件: 默认情况下出于安全考虑,只有app1应用中的Web组件能读取该Cookie。如果希望app2, app3能共享Cookie。可在app1的AServlet组件发送Cookie时,使用setPath(String path)和setDomain(String domain)方法,改变Cookie的共享范围
l app1和app2共享Cookie(同一个tomcat服务器)
Cookie cookie = new Cookie(“username”, “Tom”); ///表示tomcat服务器根目录webapps,所有的web应用共享该cookie。 cookie.setPath(“/”); resp.addCookie(cookie); |
l 只能让app2访问(同一个tomcat服务器)
Cookie cookie = new Cookie(“username”, “Tom”); ///表示tomcat服务器根目录webapps,只有app2能访问该cookie。app1也无法访问该Cookie cookie.setPath(“/app2/”); resp.addCookie(cookie); |
l 只能让app1应用中/sub子目录下的web组件访问
Cookie cookie = new Cookie(“username”, “Tom”); //只有app1下的sub子目录中组件可访问该cookie. cookie.setPath(“/app1/sub/”); resp.addCookie(cookie); |
l 让其他Tomcat中所有web应用都能访问
Cookie cookie = new Cookie(“username”, “Tom”); //必须以. 开头 cookie.setDomain(“.cat.com”); resp.addCookie(cookie); |
l 仅仅让其他tomcat服务器的指定web应用能访问
Cookie cookie = new Cookie(“username”, “Tom”); //仅仅让tomcat服务器B的app3应用访问cookie. cookie.setDomain(“.cat.com”); cookie.setPath(“/app3/”); resp.addCookie(cookie); |
注意:服务器对客户端进行读写Cookie操作,会给客户端带来安全隐患。服务器可能会发送包含恶意代码的Cookie数据;服务器可能会依据客户单Cookie信息窃取用户的保密信息。处于安全考虑,多数浏览器可以设置是否启动Cookie。工具->Internet选项->隐私->高级。
5访问Web应用的工作目录(了解)
每个Web应用都有一个工作目录。Servlet容器会把Web应用的相关临时文件存放到该目录。默认的工作目录为work\Catalina\localhost\web名称。
Tomcat还允许配置Web应用的
Servlet容器及web应用中的Servlet都可以访问工作目录。当Servlet容器初始化一个Web应用时,会向刚创建的ServletContext对象中设置一个名为”javax.servlet.context.tempdir”的属性(预先设置很多属性),属性值为java.io.File对象,代表当前web应用的工作目录。
可通过以下方式获得:
File workDir = (File)context.getAttribute(“javax.servlet.context.tempdir”);
案例演示:
6请求转发和包含
Servlet与Servlet之间无法直接调用,因为Servlet对象由Servlet容器创建并调用,在一个Servlet中无法获得另一个Servlet对象的引用。
实际使用中,Web应用响应客户端的一个请求时,可能需要多个Web组件共同协作,才能生成响应结果。此时,就需要一直机制,能够协调web组件之间协作。Servlet规范中为Web组件之间的协作提供了两种途径。请求转发和包含
² 请求转发(源组件一般为Servlet,目标组件一般为JSP页面)
Servlet(源组件)先对客户端请求做一些预处理操作,然后把请求转发给其他Web组件(目标组件)来完成包括生成响应结果在内的后续操作。
² 包含(经常使用在JSP,多个页面的合并,提高复用)
Servlet(源组件)把其他Web组件(目标组件)生成的响应结果包含到自身的响应结果中。
请求转发和包含的共同点:
² 源组件和目标组件处理的都是同一个客户请求,源组件和目标组件共享同一个ServletRequest和ServletResponse对象。
² 目标组件可以为Servlet, JSP, HTML。
² 都依赖javax.servlet.RequestDispatcher接口。该接口表示请求分发器。Servlet中可通过两种方式得到RequestDispatcher对象
Ø 调用ServletContext的getRequestDispatcher(String path)方法
参数path必须以/ 开头,表示当前Web应用的URL入口。此种形式也叫绝对路径
Ø 调用ServletRequest的getRequestDispatcher(String path)方法
参数path既可以以/ 开头(绝对路径),也可以不以/ 开头(相对路径)。
注意:
以 / 开头的可称为绝对路径,表示当前Web应用的URL入口。 不以 / 开头为相对路径,表示相对于当前源Servlet组件的URL路径(web.xml中配置的url-pattern)。
案例: 请求转发案例及细节分析:
请求转发使用ReqeustDispatcher的forward(RequestServlet req, ResponseServlet res)转发给目标组件。
请求转发注意细节:
a)dispatcher.forward()方法处理流程,首先清空存放响应正文数据的缓冲区,其次如果目标组件为Servlet或JSP,就调用它的service方法,并把该方法响应的结果发送到客户端;如果目标组件为HTML文档,就读取文档中的数据,将其发送到客户单。
b)根据a所言,forward方法会清空源组件存放响应正文的缓冲区,因此Servlet源组件生成的响应结果不会被发送到客户端,只有目标组件生成的响应结果才会被发送到客户端。
c)如果源组件在请求转发之前,已经提交了响应结果(比如调用ServletResponse的flushBuffer()方法,或者关闭了输出流out对象),会将forward之前发送给客户端的信息成功发送,但forward方法会抛出IllegalStateException,请求转发将不成功。因此为了避免该异常,不应该在源组件中提交响应结果。(演示)
d)根据案例,forward方法之后的代码也会被执行。只是源组件生成的响应结果不会被发送到客户端。
案例: 包含案例及细节分析:
包含使用ReqeustDispatcher的include(RequestServlet req, ResponseServlet res)包含目标组件的响应结果。
包含注意细节:
a)MainServlet类将自己的响应结果, header.html内容, GreetServlet生成的响应正文,以及footer.html内容都包含到自己的响应结果中
b)包含的如果为Servlet或JSP,就调用相应的service方法,把该方法产生的响应正文添加到源组件的响应结果中;如果目标组件为HTML文档,就直接把文档的内容添加到源组件的响应结果中
c)include方法之后的代码继续执行
d)目标组件中对响应状态码或者响应头的修改都会被忽略。
面试题:请求转发和包含的区别?
请求转发,web容器会清空response数据,且转发后,源组件无法写入数据。包含,web容器会把包含的目标组件数据和源组件数据一起响应给客户端。
7请求范围共享
以上案例,用到请求范围内的共享。使用ServletRequest接口中getAttribute和setAttribute方法。只要Web组件共享同一个ServletRequest对象(请求转发 和包含都共享请求和响应对象),就可以共享请求范围内的共享数据。
ServletRequest和ServletResponse对象生命周期:Servlet容器每次接受到一个客户请求,后会先创建这两个对象,并将这两个对象作为参数传给响应的Servlet的service方法。当容器把本次响应结果返回给客户后,这两个对象生命周期结束。
8重定向
HTTP协议规定了重定向机制。运行流程
1)浏览器输入URL,访问服务器端的组件
2)服务器端组件返回一个状态码为302及响应头Location(Location的值可能是同一个Web服务器上另一个Web组件的URL,也可能是不在同一个Web服务器上的Web组件的URL),该响应结果是告诉浏览器再请求访问另一个web组件。
3)浏览器接收到响应结果,再立即自动请求访问另一个Web组件
4)浏览器接收另一个Web组件的响应结果
案例:重定向案例及细节分析
重定向使用HttpServletResponse接口中的sendRedirect(String location)方法实现。(注意,sendRedirect方法不在ServletResponse接口中,因为重定向机制是HTTP协议规定的)
重定向细节分析:
。客户端接收的是目标Web组件的响应结果。
² 源组件在重定向之前,提交了响应结果(flushBuffer或close),则sendRedirect()方法会抛出IllegalStateException,为了避免该异常,不应该在源组件中提交响应结果。
² response.sendRedirect(String path)方法之后的代码也会被执行
² 源组件和目标组件是浏览器两次请求,所以不共享ServletRequest对象,因此不共享请求范围内的共享数据。
² response.sendRedirect(String location)方法中的location参数,如果以/开头,表示的是当前服务器根路径的URL(webapps)。如果以http:开头,可以是Internet上的任意一个有效的网页。
² 可用response.setStatus(302); response.setHeader("Location", "http://www.163.cn")两句代替sendRedirect方法。² Servlet源组件生成的响应结果不会被发送到客户端
请求转发和重定向区别?
1请求重定向地址栏有变化,而转发无变化
2请求重定向客户端向服务器发送两次请求,转发发送一次请求
3重定向不共享ServletRequest对象,因此不共享请求范围内的共享数据
9线程安全问题
同一个servlet-name对应的servlet-class为同一个实例。即servlet对象是单例的。此时多个客户端冰法访问同一个servlet-name对应的对象,则Servlet容器为了保证同时响应多个客户的访问请求,通常为每个请求分配一个线程,这些线程并发执行同一个Servlet对象的service方法。当多个线程并发执行同一个Servlet对象时,可能会导致线程安全问题。
案例1:
案例2:
两个浏览器同时访问。以上会出现并发问题。
既然如此,如何解决并发安全问题?
² 根据实际应用需求,合理决定在Servlet中定义的变量的作用域。变量是实例变量还是局部变量,由实际应用需求决定。
² 对于多线程并发方法共享数据而导致的安全问题,使用Java同步机制对线程进行同步。
对以上案例进行修改:
l 合理决定Servlet中定义变量的作用域类型
局部变量:在方法中定义,每个线程调用service方法,都拥有一份自己的局部变量,方法结束,生命周期结束。
实例变量:类的每一个实例都拥有一份自己的实例变量。多个线程同时执行一个Servlet对象的方法,则该方法中访问实例变量,此时这些线程访问的是同一个实例中的同一个实例变量。
案例修改:上例只需将实例变量改为局部变量即可。
l 使用Java线程同步机制对多线程同步
Java同步机制确保在任意一时刻,只允许一个工作线程执行同步代码块中代码。只有当一个线程退出同步代码块时,其他工作线程才允许执行同步代码块。
l 补充:JavaWeb中路径
1)ServletContext的两个方法:
不管是否以/开头 都表示当前web应用URL
getResourceAsStream(“/WEB-INF/classes/web.xml”)
getResourceAsStream(“WEB-INF/classes/web.xml”)
getRealPath(“/WEB-INF/classes/web.xml”)
getRealPath(“WEB-INF/classes/web.xml”)
2)Class获取资源
可以以/开头也可以不以/开头
//当前目录为classes
InputStream is2 = Test.class.getResourceAsStream("/web.xml");
//相对目录为当前类的.class文件所在目录。
InputStream is2 = Test.class.getResourceAsStream("web.xml");
3)ClassLoader获取资源
可以以/开头,也可以不以/开头,都相对当前classes目录。
Test.class.getClassLoader().getResourceAsStream("/web.xml");
Test.class.getClassLoader().getResourceAsStream("web.xml");
4)url-pattern
必须用 / 开头,表示当前web应用目录。提供外部访问入口
5)请求转发和包含路径
context.getRequestDispatcher(“/xxServlet”);此种形式必须以/开头,表示当前Web应用。
以下两种形式,可以/开头,也可以不用/开头。
/开头,表示相对当前Web应用。
request.getRequestDispatcher(“/xxservlet”)
不以/开头,表示相对源组件的url-pattern当前位置。比如访问的是http://ip:port/test/demo01/myservlet, 此时xxServlet相当于寻找http://ip:port/test/demo01/xxservlet
request.getRequestDispatcher(“xxservlet”)
6)重定向
以/开头,表示当前请求主机的webapps目录,test表示Web应用名。 如果不以/开头,表示相对源组件的url-pattern当前目录,很少用。
response.sendRedirect(“/test/testservlet”)。
通用方式获得当前web应用入口:context.getContextPath()。
除了以上方式, 重定向可以直接给出http://形式访问。
7)form表单的action属性
以/开头,表示当前请求主机的webapps目录下testweb应用。
不以/开头,表示相对当前页面的路径。即当前显示页面在服务器上的路径。如果当前页面的路径为http://ip:port/test/page/a.html。则此时提交表单,请求的是http://ip:port/test/page/hello.html.
8)超链接, 图片
/开头,表示访问主机下webapps中test的资源。
“/test/testservlet”>link
不以/开头,表示相对当前页面的路径。和form相同
总结:涉及到浏览器访问服务器时,建议以 / 开头(form表单,超链接,重定向,图片)表示相对当前主机的根URL; 涉及到服务器内部路径相关,如(重定向,包含,url-pattern, context路径,class),也建议使用 / 开头,表示当前web应用。对于classloader读取资源,建议不用/。
总之:路径以/开头,请求转发不加web应用名,其他全部要加。
l 补充:JavaWeb编码
1)在地址栏中直接输入http://localhost:8080/test/hello?username=张三。
请求数据的编码由浏览器决定。不同浏览器使用的编码也不相同。IE默认为GB2312,Chrome, FireFox使用UTF-8。实际中很少用。
2)从一个静态页面中发出请求信息
一般访问网站,首页大多为html文件。浏览器得到首页后,从首页上通过提交表单数据或超链接,再向服务器发送数据。此时,首页本身的编码由服务器指定,而用户通过首页输入数据的编码也是由页面本身编码决定的。
静态页面可使用meta标签,告诉浏览器页面编码。
案例演示:
3)GET请求和POST请求编码解码问题
4)URL编码和解码
页面数据不管以GET或POST方式传输,提交的数据都是以name=value&name=value…格式进行传输。键值之间以=分割,键值与键值之间以&分割,如果此时value值中包含=或&等特殊字符,则会造成服务器端解析错误。因此必须将引起歧义或特殊的字符进行转义,即对其编码。
POST方式提交时,表单数据在请求正文中,默认自动对其进行URL编码。需要手动进行URL编码的,只有GET,超链接。
URL编码的规则:
浏览器端对传输数据进行URL编码,服务器自动识别数据是否使用了URL编码,如果使用了,服务器会自动把数据进行URL解码。可通过如下方式修改服务器默认的URL解码方式 :在%tomcat%\conf\server.xml 中找到当前连接器元素 Connector配置URIEncoding 属性
注意:对于IE,如果你勾选了高级设置"总是以UTF-8发送Url",那么Url中的路径部分的中文会使用UTF-8进行Url编码之后发送给服务端,而查询参数中的中文部分使用系统默认字符集进行Url编码。
后面讲解如何对GET,超链接进行URL编码。