JavaWeb
一:Servlet
1.1. 什么是Servlet
Servlet是一个特殊的Java类, 是运行在 Web 服务器中的小型 Java 程序(即:服务器端的小应用程序)。servlet 通常通过 HTTP(超文本传输协议)接收和响应来自 Web 客户端的请求。这个Java类必须继承HttpServlet。每个Servlet可以响应客户端的请求,Servlet提供不同的方法用于响应客户端请求,例如doGet,doPost,doPut等
1.2 Tomcat与Servlet的关系
Tomcat 是Web应用服务器 , 是一个Servlet/JSP容器. Tomcat 作为Servlet容器, 负责处理客户请求, 把请求传送给Servlet,并将Servlet的响应传送回给客户 . 而Servlet是一种运行在支持Java语言的服务器上的组件.。
Servlet最常见的用途是扩展Java Web服务器功能,提供非常安全的,可移植的,易于使用的CGI替代品。
1.3 工作流
从http协议中的请求和响应可以得知,浏览器发出的请求是一个请求文本,而浏览器接收到的也应该是一个响应文本。
请求:Tomcat将http请求文本接收并解析,然后封装成HttpServletRequest类型的request对象,所有的HTTP头数据读可以通过request对象调用对应的方法查询到。
响应:Tomcat同时会要响应的信息封装为HttpServletResponse类型的response对象,通过设置response属性就可以控制要输出到浏览器的内容,然后将response交给tomcat,tomcat就会将其变成响应文本的格式发送给浏览器。
1.4 Java Servlet API
Java Servlet API 是Servlet容器(tomcat)和servlet之间的接口,它定义了serlvet的各种方法,还定义了Servlet容器传送给Servlet的对象类,其中最重要的就是ServletRequest和ServletResponse。所以说我们在编写servlet时,需要实现Servlet接口,按照其规范进行操作。
1.5 Servlet执行过程
在浏览器的地址栏输入:http://ip:port/appNames/servlet
- 通过浏览器和ip:port和这个服务器建立连接。
- 浏览器会生成一个请求数据包(路径appNames/servlet)向服务器发送请求。
- 服务器收到请求数据包,分析请求资源路径做精准定位,通过请求的appName查找webapps文件下面的appName做匹配,匹配上了需要获取web.xml中的servlet(mapping)。
- 服务器创建两个对象
第一个对象:请求对象,该对象实现了HttpServletRequest接口,服务器会将请求数据包中的数据解析出来,存储在该对象里。这样做的好处是没有必要理解http协议,只需要读取request。
第二个对象:响应对象,实现了HttpServletResponse接口,作用是servlet处理完成后的结果可以存放到该对象上,然后服务器依据该对象的数据生成响应数据包。
- servlet在执行servlet()方法时,可以通过request获取请求数据,也可以将处理结果存放到response上。然后服务器与响应对象直接形成一个默契,生成一个响应数据包给浏览器。
- 浏览器解析服务器返回的响应数据包,生成响应的结果。
1.6 Servlet访问的过程:
Http请求---->web.xml--------> url -pattern----->servlet-name----->servlet-class-----> QuickStratServlet(对应的Class文件)
1.7 Servlet的生命周期
Servlet生命周期可分为5个步骤
- 加载Servlet。当Tomcat第一次访问Servlet的时候,Tomcat会负责创建Servlet的实例
- 初始化。当Servlet被实例化后,Tomcat会调用init()方法初始化这个对象
- 处理服务。当浏览器访问Servlet的时候,Servlet 会调用service()方法处理请求
- 销毁。当Tomcat关闭时或者检测到Servlet要从Tomcat删除的时候会自动调用destroy()方法,让该实例释放掉所占的资源。一个Servlet如果长时间不被使用的话,也会被Tomcat自动销毁
- 卸载。当Servlet调用完destroy()方法后,等待垃圾回收。如果有需要再次使用这个Servlet,会重新调用init()方法进行初始化操作。
简单总结:只要访问Servlet,service()就会被调用。init()只有第一次访问Servlet的时候才会被调用。
destroy()只有在Tomcat关闭的时候才会被调用。
1.8 Servlet的配置
1.8.1 使用配置文件
- 写Servlet类
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
}
}
- 在web.xml中配置Servlet
1.8.2 使用springboot
写servlet类,在springboot启动类配置
@SpringBootApplication
public class ServletApp {
@Bean
public ServletRegistrationBean MyServlet(){
return new ServletRegistrationBean(new MyServlet(),"/myserv/*");
}
public static void main(String[] args){
SpringApplication.run(ServletApp.class, args);
}
}
1.9 Servlet细节
1.9.1 Servlet的url匹配顺序
当一个请求发送到servlet容器的时候,容器先会将请求的url减去当前应用上下文的路径作为servlet的映射url,比如我访问的是http://localhost/hiway/user/aaa.html,我的应用上下文是hiway,容器会将http://localhost/hiway去掉,剩下的/user/aaa.html部分拿来做servlet的映射匹配。这个映射匹配过程是有顺序的,而且当有一个servlet匹配成功以后,就不会去理会剩下的servlet了。其匹配规则和顺序如下:
精确路径匹配。例子:比如servletA 的url-pattern为 /test,servletB的url-pattern为 /* ,这个时候,如果我访问的url为http://localhost/test ,这个时候容器就会先进行精确路径匹配,发现/test正好被servletA精确匹配,那么就去调用servletA,也不会去理会其他的servlet了。
最长路径匹配。例子:servletA的url-pattern为/test/,而servletB的url-pattern为/test/a/,此时访问http://localhost/test/a时,容器会选择路径最长的servlet来匹配,也就是这里的servletB。
扩展匹配。 如果url最后一段包含扩展,容器将会根据扩展选择合适的servlet。例子:servletA的url-pattern:*.action
最后, 如果前面三条规则都没有找到一个servlet,容器会根据url选择对应的请求资源。如果应用定义了一个default servlet,则容器会将请求丢给default servlet
1.9.2 Servlet是单例的吗
在Servlet规范中,对于Servlet单例与多例定义如下:
“Deployment Descriptor”, controls how the servlet container provides instances of the servlet.For a servlet not hosted in a distributed environment (the default), the servlet container must use only one instance per servlet declaration. However, for a servlet implementing the SingleThreadModel interface, the servlet container may instantiate multiple instances to handle a heavy request load and serialize requests to a particular instance.
上面规范提到:
如果一个Servlet没有被部署在分布式的环境中,一般web.xml中声明的一个Servlet只对应一个实例。
而如果一个Servlet实现了SingleThreadModel接口,就会被初始化多个实例。默认20个
所以个人理解Servlet不算单例,只是容器让它只实例化一次,变现出来的是单例的效果而已
1.10 如何开发线程安全的Servlet
实现 SingleThreadModel 接口
使用synchronized同步对共享数据的操作
避免使用实例变量
对上面的三种方法进行测试,可以表明用它们都能设计出线程安全的Servlet程序。但是,如果一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销。SingleThreadModel在Servlet2.4中已不再提倡使用;同样如果在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大下降。这是因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。另外为保证主存内容和线程的工作内存中的数据的一致性,要频繁地刷新缓存,这也会大大地影响系统的性能。所以在实际的开发中也应避免或最小化 Servlet 中的同步代码;在Serlet中避免使用实例变量是保证Servlet线程安全的最佳选择。从Java 内存模型也可以知道,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全
1.11 Servlet的<load-on-startup>
在servlet的配置当中,
<load-on-startup>1</load-on-startup>的含义是:
标记容器是否在启动的时候就加载这个servlet。当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。正数的值越小,启动该servlet的优先级越高。
配置load-on-startup后,servlet在startup后立即加载,但只是调用servlet的init()方法,用以初始化该servlet相关的资源。初始化成功后,该servlet可响应web请求;如未配置load-on-startup,容器一般在第一次响应web请求时,会先检测该servlet是否初始化,如未初始化,则调用servlet的init()先初始化,初始化成功后,再响应请求。
1.12 default Servlet
可以将url-pattern 配置一个/,代表该servlet是缺省的servlet。什么是缺省的servlet?
当你访问资源地址所有的servlet都不匹配时,缺省的servlet赋值处理。其实,web应用中所有的资源的响应都是servlet负责,包括静态资源(html页面)。(有配置缺省的servlet,无法访问到静态资源。)
2. request和response
学习request和response之前先学习一下http请求
2.1 HTTP请求
浏览器向服务器请求某个web资源时,称之为浏览器向服务器发送了一个http请求。一个完整http请求应该包含三个部分:
- 请求行【描述客户端的请求方式、请求的资源名称,以及使用的HTTP协议版本号】
- 多个消息头【描述客户端请求哪台主机,以及客户端的一些环境信息等】
- 一个空行
2.1.1 请求行
GET /java.html HTTP/1.1
请求行中的GET称之为请求方式;请求方式有:POST,GET,HEAD,OPTIONS,DELETE,TRACE,PUT。
一般来说,当我们点击超链接,通过地址栏访问都是get请求方式。通过表单提交的数据一般是post方式。
可以简单理解GET方式用来查询数据,POST方式用来提交数据,get的提交速度比post快
GET方式:在URL地址后附带的参数是有限制的,其数据容量通常不能超过1K。
POST方式:可以在请求的实体内容中向服务器发送数据,传送的数据量无限制。
2.1.2 请求头
Accept: text/html,image/* 【浏览器告诉服务器,它支持的数据类型】
Accept-Charset: ISO-8859-1 【浏览器告诉服务器,它支持哪种字符集】
Accept-Encoding: gzip,compress 【浏览器告诉服务器,它支持的压缩格式】
Accept-Language: en-us,zh-cn 【浏览器告诉服务器,它的语言环境】
Host: www.it315.org:80【浏览器告诉服务器,它的想访问哪台主机】
If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT【浏览器告诉服务器,缓存数据的时间】
Referer: www.it315.org/index.jsp【浏览器告诉服务器,客户机是从那个页面来的---反盗链】
8.User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)【浏览器告诉服务器,浏览器的内核是什么】
Cookie【浏览器告诉服务器,带来的Cookie是什么】
Connection: close/Keep-Alive 【浏览器告诉服务器,请求完后是断开链接还是保持链接】
Date: Tue, 11 Jul 2000 18:23:51 GMT【浏览器告诉服务器,请求的时间】
2.2 HTTP响应
一个HTTP响应代表着服务器向浏览器回送数据,一个完整的HTTP响应应该包含四个部分:
- 一个状态行【用于描述服务器对请求的处理结果。】
- 多个消息头【用于描述服务器的基本信息,以及数据的描述,服务器通过这些数据的描述信息,可以通知客户端如何处理等一会儿它回送的数据】
- 一个空行
- 实体内容【服务器向客户端回送的数据】
2.2.1 状态行
格式: HTTP版本号 状态码 原因叙述
状态行:HTTP/1.1 200 OK
状态码用于表示服务器对请求的处理结果,它是一个三位的十进制数。响应状态码分为5类
2.2.2 响应头
Location: www.it315.org/index.jsp 【服务器告诉浏览器要跳转到哪个页面】
Server:apache tomcat【服务器告诉浏览器,服务器的型号是什么】
Content-Encoding: gzip 【服务器告诉浏览器数据压缩的格式】
Content-Length: 80 【服务器告诉浏览器回送数据的长度】
Content-Language: zh-cn 【服务器告诉浏览器,服务器的语言环境】
Content-Type: text/html; charset=GB2312 【服务器告诉浏览器,回送数据的类型】
Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT【服务器告诉浏览器该资源上次更新时间】
Refresh: 1;url=www.it315.org【服务器告诉浏览器要定时刷新】
Content-Disposition: attachment; filename=aaa.zip【服务器告诉浏览器以下载方式打开数据】
Transfer-Encoding: chunked 【服务器告诉浏览器数据以分块方式回送】
Set-Cookie:SS=Q0=5Lb_nQ; path=/search【服务器告诉浏览器要保存Cookie】
Expires: -1【服务器告诉浏览器不要设置缓存】
Cache-Control: no-cache 【服务器告诉浏览器不要设置缓存】
Pragma: no-cache 【服务器告诉浏览器不要设置缓存】
Connection: close/Keep-Alive 【服务器告诉浏览器连接方式】
Date: Tue, 11 Jul 2000 18:23:51 GMT【服务器告诉浏览器回送数据的时间】
3. HttpServletRequest
概述
HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,开发人员通过这个对象的方法,可以获得客户这些信息。
request就是将请求文本封装而成的对象,所以通过request能获得请求文本中的所有内容,请求头、请求体、请求行
常用方法
请求头
String getHeader(String name) 根据头名称得到头信息值
long getDateHeader(java.lang.String name) 获得指定头内容Date
int getIntHeader(java.lang.String name) 获得指定头内容int
Enumeration getHeaderNames() 得到所有头信息name
Enumeration getHeaders(String name) 根据头名称得到相同名称头信息值
Enumeration<String> headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()){
String key = (String)headerNames.nextElement();
String value = req.getHeader(key);
System.out.println(key+"="+value);
}
请求体
1)与表单获取相关的方法:
String getParameter(name) :根据表单中name属性的名,获取value属性的值方法
String[] getParameterValues(String name):专为复选框取取提供的方法
getParameterNames():得到表单提交的所有name的方法
Map<String , String[]> getParameterMap(): 得到表单提交的所有值的方法 //做框架用,非常实用
getInputStream: 以字节流的方式得到所有表单数据
2)与操作非表单数据相关的方法(request也是一个域对象):
void setAttribute(String name, Object value);
Object getAttribute(String name);
Void removeAttribute(String name);
3)与请求转发相关的方法:
RequestDispatcher getRequestDispatcher(String path) //得到请求转发或请求包含的协助对象
forward(ServletRequest request, ServletResponse response) //转发的方法
include(ServletRequest request, ServletResponse response) //请求包含
4)与编码相关的方法:
//解决post方式编码
request.setCharacterEncoding("UTF-8") :告诉服务器客户端什么编码,只能处理post请求方式
//解决get方式编码
String name = new String(name.getBytes(“iso-8859-1”),”UTF-8”);
- 其他常用请求方法
getMethod();
getRequestURL();
getRequestURI();
getServerName();
getServerPort();
getContextPath();
getServletPath();
getQueryString();
getRemoteAddr();
getProtocol();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.统一资源标记符 /Web_Servlet/ServletTest3
String uri = req.getRequestURI();
System.out.println(uri);
//2.统一资源定位符 http://localhost:6060/Web_Servlet/ServletTest3
StringBuffer url = req.getRequestURL();
System.out.println(url);
//3.协议和版本 HTTP/1.1
String potocol = req.getProtocol();
System.out.println(potocol);
//4.协议 http
String scheme = req.getScheme();
System.out.println(scheme);
//5.主机(域名) localhost,如果你使用的是ip地址,就显示ip地址
String serverName = req.getServerName();
System.out.println(serverName);
//6.端口 6060(这是我自己修改了的端口,默认是8080)
int port = req. getServerPort();
System.out.println(port);
//7.发布到tomcat下的项目名称 /Web_Servlet
String contextPath = req.getContextPath();
System.out.println(contextPath);
//8.servlet路径 /ServletTest3
String servletPath = req.getServletPath();
System.out.println(servletPath);
//9.获取所有请求参数,即?之后所有东西。 username=faker&password=mid
String queryString = req.getQueryString();
System.out.println(queryString);
//10.远程主机的ip地址 0:0:0:0:0:0:0:1
String remoteAddr = req.getRemoteAddr();
System.out.println(remoteAddr);
}
4. HttpServletResponse
概述
Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象、和代表响应的response对象。request和response对象即然代表请求和响应,那我们要获取客户机提交过来的数据,只需要找request对象就行了。要向容器输出数据,只需要找response对象就行了。
HttpServletResponse对象代表服务器的响应。这个对象中封装了向客户端发送数据、发送响应头,发送响应状态码的方法。
常用方法
响应行\响应头\响应体
setStatus(int sc) 设置响应状态码
setHeader(String name, String value) 设置响应头信息
getWrite(); 字符输出流
getOutputStream(); 字节输出流
setCharacterEncoding(String charset) 告知服务器使用什么编码
setContentType(String type) 告诉响应的内容类型(text/html,application/json等)
重定向
response.sendRedirect(path);
注意:重定向没有任何局限,可以重定向web项目内的任何路径,也可以访问别的web项目中的路径,并且这里就用"/"区分开来,如果path使用了"/"开头,就说明我要重新开始定位了,不访问刚才的web项目,自己写项目名,如果path没有使用"/"开始,那么就知道是访问刚才那个web项目下的servlet,就可以省略项目名了。就是这样来区别。
5. HttpServletResponse和HttpServletRequest对比
两种setCharacterEncoding
request.setCharacterEncoding()指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用iso8859-1编码。值得注意的是在执行setCharacterEncoding()之前,不能执行任何getParameter()。而且,该指定只对POST方法有效,对GET方法无效。
分析原因,应该是在执行第一个getParameter()的时候,Java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。而对于GET方法提交表单是,提交的内容在URL中,一开始就已经按照编码分析提交内容,setCharacterEncoding()自然就无效。
response.setCharacterEncoding设置HTTP 响应的编码,如果之前使用response.setContentType设置了编码格式,则使用response.setCharacterEncoding指定的编码格式覆盖之前的设置.与response.setContentType相同的是,调用此方法,必须在getWriter执行之前或者response被提交之前
转发与重定向
-
实际发生位置不同,地址栏不同
*转发是发生在服务器的:
转发是由服务器进行跳转的,细心的朋友会发现,在转发的时候,浏览器的地址栏是没有发生变化的,在我访问Servlet111的时候,即使跳转到了Servlet222的页面,浏览器的地址还是Servlet111的。也就是说浏览器是不知道该跳转的动作,转发是对浏览器透明的。通过上面的转发时序图我们也可以发现,实现转发只是一次的http请求,一次转发中request和response对象都是同一个。这也解释了,为什么可以使用request作为域对象进行Servlet之间的通讯。重定向是发生在浏览器的:
重定向是由浏览器进行跳转的,进行重定向跳转的时候,浏览器的地址会发生变化的。曾经介绍过:实现重定向的原理是由response的状态码和Location头组合而实现的。这是由浏览器进行的页面跳转实现重定向会发出两个http请求,request域对象是无效的,因为它不是同一个request对象 -
用法不同
很多人都搞不清楚转发和重定向的时候,资源地址究竟怎么写。有的时候要把应用名写上,有的时候不用把应用名写上。很容易把人搞晕。记住一个原则:给服务器用的直接从资源名开始写,给浏览器用的要把应用名写上
request.getRequestDispatcher("/资源名 URI").forward(request,response)
转发时"/"代表的是本应用程序的根目录response.send("/web应用/资源名 URI");
重定向时"/"代表的是webapps目录 能够去往的URL的范围不一样
转发是服务器跳转只能去往当前web应用的资源
重定向是服务器跳转,可以去往任何的资源
-
传递数据的类型不同
转发的request对象可以传递各种类型的数据,包括对象
重定向只能传递字符串
-
跳转的时间不同
转发时:执行到跳转语句时就会立刻跳转
重定向:整个页面执行完之后才执行跳转
getWriter和getOutputStream
1.选择getOutputStream 和getWriter方法的要点
PrintWriter对象输出字符文本内容时,它内部还是将字符串转换成了某种字符集编码的字节数组后再进行输出,使用PrintWriter对象的好处就是不用编程人员自己来完成字符串到字节数组的转换。
使用ServletOutputStream对象也能输出内容全为文本字符的网页文档,但是,如果网页文档内容是在Servlet程序内部使用文本字符串动态拼凑和创建出来的,则需要先将字符文本转换成字节数组后输出。
2.两种方法区别
getOutputStream方法用于返回Servlet引擎创建的字节输出流对象,Servlet程序可以按字节形式输出响应正文。
getWriter方法用于返回Servlet引擎创建的字符输出流对象,Servlet程序可以按字符形式输出响应正文。
getOutputStream和getWriter这两个方法互相排斥,调用了其中的任何一个方法后,就不能再调用另一方法。
getOutputStream方法返回的字节输出流对象的类型为ServletOutputStream,它可以直接输出字节数组中的二进制数据。
getWriter方法将Servlet引擎的数据缓冲区包装成PrintWriter类型的字符输出流对象后返回,PrintWriter对象可以直接输出字符文本内容。
Servlet程序向ServletOutputStream或PrintWriter对象中写入的数据将被Servlet引擎获取,Servlet引擎将这些数据当作响应消息的正文,然后再与响应状态行和各响应头组合后输出到客户端。
Serlvet的service方法结束后,Servlet引擎将检查getWriter或getOutputStream方法返回的输出流对象是否已经调用过close方法,如果没有,Servlet引擎将调用close方法关闭该输出流对象。
3.修改编码类型
getOutputStream解决办法:
通过更改浏览器的编码方式:IE/”查看”/”编码”/”UTF-8”(不可取)
通过设置响应头告知客户端编码方式:response.setHeader(“Content-type”, “text/html;charset=UTF-8”);//告知浏览器数据类型及编码
通过meta标签模拟请求头:out.write("".getBytes());
通过以下方法:response.setContentType("text/html;charset=UTF-8");
getWriter解决办法:
response. setContentType(“text/html;charset=UTF-8”);
两者的应用
-
文件下载
//通过路径得到一个输入流 String path = this.getServletContext().getRealPath(filepath); FileInputStream fis = new FileInputStream(path); //创建字节输出流 ServletOutputStream sos = response.getOutputStream(); //得到要下载的文件名 String filename = path.substring(path.lastIndexOf("\\")+1); //设置文件名的编码 filename = URLEncoder.encode(filename, "UTF-8");//将不安全的文件名改为UTF-8格式 //告知客户端要下载文件 response.setHeader("content-disposition", "attachment;filename="+filename); response.setHeader("content-type", "image/jpeg"); //执行输出操作 int len = 1; byte[] b = new byte[1024]; while((len=fis.read(b))!=-1){ sos.write(b,0,len); } sos.close(); fis.close();
验证码
-
定时刷新
response.setContentType("text/html;charset=UTF-8"); response.getWriter().write("3秒后跳转页面....."); //三秒后跳转到index.jsp页面去 response.setHeader("Refresh", "3;url='/index.jsp'");
-
设置缓存
//浏览器有三消息头设置缓存,为了兼容性!将三个消息头都设置了
response.setDateHeader("Expires", -1);
response.setHeader("Cache-Control","no-cache");
response.setHeader("Pragma", "no-cache");
- 数据压缩
//创建GZIPOutputStream对象,给予它ByteArrayOutputStream
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
//GZIP对数据压缩,GZIP写入的数据是保存在byteArrayOutputStream上的
gzipOutputStream.write("asdfzxcvasdfzxvasdfzxcv".getBytes());
//gzipOutputStream有缓冲,把缓冲清了,并顺便关闭流
gzipOutputStream.close();
byte[] bytes = byteArrayOutputStream.toByteArray();
//告诉浏览器这是gzip压缩的数据
response.setHeader("Content-Encoding","gzip");
//将压缩的数据写给浏览器
response.getOutputStream().write(bytes);
- 防盗链
//获取到网页是从哪里来的
String referer = request.getHeader("Referer");
Cookie和Session
一、会话概述
1.1、什么是会话?
会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话其中不管浏览器发送多少请求,都视为一次会话,直到浏览器关闭,本次会话结束。
其中注意,一个浏览器就相当于一部电话,如果使用火狐浏览器,访问服务器,就是一次会话了,然后打开google浏览器,访问服务器,这是另一个会话,虽然是在同一台电脑,同一个用户在访问,但是,这是两次不同的会话。
1.2、会话机制
Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。
二、Cookie
Cookie是客户端技术,程序把每个用户的数据以cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样,web资源处理的就是用户各自的数据了。由于cookie是由客户端浏览器保存和携带的,所以称之为客户端技术
2.1、Cookie的工作流程
1)servlet创建cookie,保存少量数据,发送浏览器。
2)浏览器获得服务器发送的cookie数据,将自动的保存到浏览器端。
3)下次访问时,浏览器将自动携带cookie数据发送给服务器。
2.2、Cookie特点
1)每一个cookie文件大小:4kb , 如果超过4kb浏览器不识别
2)一个web站点(web项目):发送20个
3)一个浏览器保存总大小:300个
4)cookie 不安全,可能泄露用户信息。浏览器支持禁用cookie操作。
5) 默认情况生命周期:与浏览器会话一样,当浏览器关闭时cookie销毁的。---临时cookie
cookie.setMaxAge(expiry); //设置cookie被浏览器保存的时间。
expiry:单位秒,默认为-1,
expiry=-1:代表浏览器关闭后,也就是会话结束后,cookie就失效了,也就没有了。
expiry>0:代表浏览器关闭后,cookie不会失效,仍然存在。并且会将cookie保存到硬盘中,直到设置时间过期才会被浏览器自动删除,
expiry=0:删除cookie。不管是之前的expiry=-1还是expiry>0,当设置expiry=0时,cookie都会被浏览器给删除。
2.3、Cookie操作
操作cookie
1)创建cookie:new Cookie(name,value)
2)发送cookie到浏览器:HttpServletResponse.addCookie(Cookie)
3)servlet接收cookie:HttpServletRequest.getCookies() 浏览器发送的所有cookie
cookie API
getName() 获得名称,cookie中的key
getValue() 获得值,cookie中的value
setValue(java.lang.String newValue) 设置内容,用于修改key对应的value值。
setMaxAge(int expiry) 设置有效时间【】
setPath(java.lang.String uri) 设置路径【】
setDomain(java.lang.String pattern) 设置域名 , 一般无效,有浏览器自动设置,setDomain(".zyh.com")
www.zyh.com / bbs.zyh.com 都可以访问
a.b.zyh.com无法访问
作用:设置cookie的作用范围,域名+路径在一起就构成了cookie的作用范围,上面单独设置的setPath有用,是因为有浏览器自动设置该域名属性,但是我们必须知道有这么个属性进行域名设置的
isHttpOnly() 是否只是http协议使用。只能servlet的通过getCookies()获得,javascript不能获得。
setComment(java.lang.String purpose) (了解) //对该cookie进行描述的信息(说明作用),浏览器显示cookie信息时能看到
setSecure(boolean flag) (了解) 是否使用安全传输协议。为true时,只有当是https请求连接时cookie才会发送给服务器端,而http时不会,但是服务端还是可以发送给浏览端的。
setVersion(int v) (了解) 参数为0(传统Netscape cookie规范编译)或1(RFC 2109规范编译)。这个没用到,不是很懂
注意事项
cookie不能发送中文,如果要发送中文,就需要进行特别处理。
Cookie cookie = new Cookie("country", URLEncoder.encode("中国", "UTF-8"));
response.addCookie(cookie);
//经过URLEncoding就要URLDecoding
String value = URLDecoder.decode(cookies[i].getValue(), "UTF-8");
三、Session
在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)
因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。
3.1、Session的原理
首先浏览器请求服务器访问web站点时,程序需要为客户端的请求创建一个session的时候,服务器首先会检查这个客户端请求是否已经包含了一个session标识、称为SESSIONID
如果已经包含了一个sessionid则说明以前已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用,如果客户端请求不包含session id,则服务器为此客户端创建一个session并且生成一个与此session相关联的session id,sessionid 的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个sessionid将在本次响应中返回到客户端保存,保存这个sessionid的方式一般就是cookie。
这样在交互的过程中,浏览器可以自动的按照规则把这个标识发回给服务器,服务器根据这个sessionid就可以找得到对应的session,又回到了步骤1。
3.2、Session的生命周期
常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。
恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,一般是30分钟,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,无论是否对Session进行读写,服务器都会认为Session活跃了一次,重新开始计算失效时间。
我们也可以自己来控制session的有效时间:
session.invalidate()将session对象销毁
setMaxInactiveInterval(int interval) 设置有效时间,单位秒
在web.xml中配置session的有效时间:
<session-config>
<session-timeout>30</session-timeout> 单位:分钟
<session-config>
所以,讨论了这么久,session的生命周期就是:
创建:Session存储在服务器端,一般放置在服务器的内存中(为了高速存取),Sessinon在用户访问第一次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session,可调用request.getSession(true)强制生成Session。
销毁:
1)超时,默认30分钟
2)执行api:session.invalidate()将session对象销毁、setMaxInactiveInterval(int interval) 设置有效时间,单位:秒
3)服务器非正常关闭(自杀,直接将JVM马上关闭)
如果正常关闭,session就会被持久化(写入到文件中,因为session默认的超时时间为30分钟,正常关闭后,就会将session持久化,等30分钟后,就会被删除)
位置: D:\java\tomcat\apache-tomcat-7.0.53\work\Catalina\localhost\test01\SESSIONS.ser
3.3、session id的URL重写
当浏览器将cookie禁用,基于cookie的session将不能正常工作,每次使用request.getSession() 都将创建一个新的session。达不到session共享数据的目的,但是我们知道原理,只需要将session id 传递给服务器session就可以正常工作的。
解决:通过URL将session id 传递给服务器:URL重写
1)手动方式: url;jsessionid=....
2)api方式:
encodeURL(java.lang.String url) 进行所有URL重写
encodeRedirectURL(java.lang.String url) 进行重定向 URL重写
这两个用法基本一致,只不过考虑特殊情况,要访问的链接可能会被Redirect到其他servlet去进行处理,这样你用上述方法带来的session的id信息不能被同时传送到其他servlet。这时候用encodeRedirectURL()方法就可以了。如果浏览器禁用cooke,api将自动追加session id ,如果没有禁用,api将不进行任何修改。
注意:如果浏览器禁用cookie,web项目的所有url都需进行重写。否则session将不能正常工作。
当cookie禁用时:
四、Session和Cookie的区别
4.1、从存储方式上比较
Cookie只能存储字符串,如果要存储非ASCII字符串还要对其编码。
Session可以存储任何类型的数据,可以把Session看成是一个容器
4.2、从隐私安全上比较
Cookie存储在浏览器中,对客户端是可见的。信息容易泄露出去。如果使用Cookie,最好将Cookie加密
Session存储在服务器上,对客户端是透明的。不存在敏感信息泄露问题。
4.3、从有效期上比较
Cookie保存在硬盘中,只需要设置maxAge属性为比较大的正整数,即使关闭浏览器,Cookie还是存在的
Session的保存在服务器中,设置maxInactiveInterval属性值来确定Session的有效期。并且Session依赖于名为JSESSIONID的Cookie,该Cookie默认的maxAge属性为-1。如果关闭了浏览器,该Session虽然没有从服务器中消亡,但也就失效了。
4.4、从对服务器的负担比较
Session是保存在服务器的,每个用户都会产生一个Session,如果是并发访问的用户非常多,是不能使用Session的,Session会消耗大量的内存。
Cookie是保存在客户端的。不占用服务器的资源。像baidu、Sina这样的大型网站,一般都是使用Cookie来进行会话跟踪。
4.5、从浏览器的支持上比较
如果浏览器禁用了Cookie,那么Cookie是无用的了!
如果浏览器禁用了Cookie,Session可以通过URL地址重写来进行会话跟踪。
4.6、从跨域名上比较
Cookie可以设置domain属性来实现跨域名
Session只在当前的域名内有效,不可夸域名
JSP
一、JSP概述
1.1、JSP结构
网络服务器需要一个JSP引擎,也就是一个容器来处理JSP页面。容器负责截获对JSP页面的请求。内嵌JSP容器的Apache支持JSP开发。
JSP容器与Web服务器协同合作,为JSP的正常运行提供必要的运行环境和其他服务,并且能够正确识别专属于JSP网页的特殊元素。
下图显示了JSP容器和JSP文件在Web应用中所处的位置。
1.2、JSP运行流程
以下步骤表明了Web服务器是如何使用JSP来创建网页的:
就像其他普通的网页一样,您的浏览器发送一个HTTP请求给服务器。Web服务器识别出这是一个对JSP网页的请求,并且将该请求传递给JSP引擎。通过使用URL或者.jsp文件来完成。
JSP引擎从磁盘中载入JSP文件,然后将它们转化为servlet。这种转化只是简单地将所有模板文本改用println()语句,并且将所有的JSP元素转化成Java代码。
JSP引擎将servlet编译成可执行类,并且将原始请求传递给servlet引擎。
Web服务器的某组件将会调用servlet引擎,然后载入并执行servlet类。在执行过程中,servlet产生HTML格式的输出并将其内嵌于HTTP response中上交给Web服务器。Web服务器以静态HTML网页的形式将HTTP response返回到您的浏览器中。
最终,Web浏览器处理HTTP response中动态产生的HTML网页,就好像在处理静态网页一样。
以上提及到的步骤可以用下图来表示:
一般情况下,JSP引擎会检查JSP文件对应的servlet是否已经存在,并且检查JSP文件的修改日期是否早于servlet。如果JSP文件的修改日期早于对应的servlet,那么容器就可以确定JSP文件没有被修改过并且servlet有效。这使得整个流程与其他脚本语言(比如PHP)相比要高效快捷一些。
Jsp 生成java源码,默认第一次生成,之后直接执行,除非内容修改,具体点说,由于JSP只会在客户端第一次请求的时候被编译,因此第一次请求JSP时会感觉比较慢,而之后的请求因为不会编译JSP,所以速度就快多了。
如果将Tomcat保存的JSP编译后的class文件删除,Tomcat也会重新编译JSP。在开发Web程序的时候经常需要修改JSP, Tomcat能够自动检测到JSP程序的改动,如果检测到JSP源代码发生了改动,Tomcat会在下次客户端请求JSP时重新编译JSP,而不需要重启Tomcat,这种自动检测功能默认是开启的,检测改动会消耗少量的时间,在部署web应用程序的时候可以在web.xml中将它关掉。
这也就是为什么我们能够在jsp页面直接修改内容,而不用重新启动服务器的原因。
总的来说,JSP网页就是用另一种方式来编写servlet而不用成为Java编程高手。除了解释阶段外,JSP网页几乎可以被当成一个普通的servlet来对待。
1.3、JSP生命周期
理解JSP底层功能的关键就是去理解它们所遵守的生命周期。JSP生命周期就是从创建到销毁的整个过程,类似于servlet生命周期,区别在于JSP生命周期还包括将JSP文件编译成servlet。
以下是JSP生命周期中所走过的几个阶段:
编译阶段:
servlet容器编译servlet源文件,生成servlet类
初始化阶段:
加载与JSP对应的servlet类,创建其实例,并调用它的初始化方法
执行阶段:
调用与JSP对应的servlet实例的服务方法
销毁阶段:
调用与JSP对应的servlet实例的销毁方法,然后销毁servlet实例
很明显,JSP生命周期的四个主要阶段和servlet生命周期非常相似,下面给出图示: