轻装上阵,安卓工程师之路---day06(HTTP & Servlet)
01 HTTP协议入门
HTTP协议是Web客户端和Web服务端通信的规则,用于定义客户端与web服务器通迅的格式,它是一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程。
HTTP协议分类二个版本:
1_HTTP1.0版本
客户端请求服务器后,服务器响应信息后,立即断开,且只能请求和响应一个资源
缺点:客户端创建连接不容易,需要消耗大量的时间和资源,这时服务器就只响应一个资源就断开,下次客户端再请求,又要创建创建新的连接
2_HTTP1.1版本
客户端请求服务器后,服务器响应信息后,不会立即断开,且在一定时间可可请求和响应多个资源
优点:客户端请求服务器后,得到了响应,在一定时间内,这个连接还是保留的,
在规则的时间内,客房端再次访问服务器,就不需要创建新的连接了
目前,Web应用都采用HTTP1.1协议规则,例如:GET(请求方式) /(请求资源) HTTP/1.1(协议名和版本)
开发最佳建议:在开发中要尽量减少客户端向WEB服务器发送HTTP请求数量,借此来提高客户端访问速度
面试题:
一个web页面中,使用img标签引用了三幅图片,当客户端访问服务器中的这个web页面时,客户端总共会访问几次服务器,即向服务器发送了几次HTTP请求。
通过软件的观察,总共有发送了四次请求,1次为文本请求,3次为图片请求。
体验:
1_用IE浏览器去访问www.baidu.com回车
2_用HttpWatch软件观察本次回车,浏览器共发送了多少个HTTP请求
02 HTTP请求
一个完整的HTTP请求,分为三部份:
1_请求行
请求方式 请求内容 协议名和版本
不管POST或GET,都用于向服务器请求某个WEB资源,这两种方式的区别主要表现在数据传递上,客户端通过这两种方式都可以带一些数据给服务器:
如请求方式为GET方式,则可以在请求的URL地址后以?的形式带上交给服务器的数据,多个数据之间以&进行分隔,例如:
GET /mail/1.html?name=abc&password=xyz HTTP/1.1
GET方式的特点:在URL地址后附带的参数是有限制的,其数据容量不能超过1K。
如请求方式为POST方式,则可以在请求的实体内容中向服务器发送数据,例如:
POST /servlet/ParamsServlet HTTP/1.1
Host:
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
name=abc&password=xyz
Post方式的特点:传送的数据量无限制,如文件上传
2_请求头
有很多key:value键值对组成的内容,每次请求都不相同
Accept: text/html,image/* 浏览器可以接收什么类型的响应数据
Accept-Charset: GBK 浏览器可以接收什么类型的编码方式
Accept-Encoding: gzip,compress 浏览器可以接收gzip和compress格式的压缩数据,即浏览器收到这些压缩数据后会自动解压
Accept-Language: en-us,zh-cn 浏览器可以接收什么类型的语言
Host: www.itheima.com:80 浏览器请求的主机是www.itheima.com,使用80端口号
If-Modified-Since: Tue, 11 Jul 2014 18:23:51 GMT(缓存时间) 浏览器收到数据后,可缓存时间
Referer: http://www.itheima.com/index.html(来自哪) 请求来源于哪个URL地址
User-Agent:Mozilla/4.0 (compatible; MSIE 9; Windows NT 5.0) 浏览器的类型和版本
Cookie:后面细讲 浏览器缓存服务器响应的信息
Connection: close/Keep-Alive 浏览器与服务器的连接是打开还是关闭
Date: Tue, 11 Jul 2014 18:23:51 GMT(访问时间) 浏览器请求服务器的时间,这个时间不是中国时间
3_请求体或实体内容
请求头与请求内容有一个空行的间隔
不是每次请求都有内容的,
GET方式请求体是没有内容的,因为内容都在请求行中
POST方式请求体是有内容的,例如:文件上传,表单以POST方式提交
03 HTTP响应
一个完整的HTTP响应,分为三部份:
响应行用于描述服务器,对请求的处理结果。
响应头用于描述服务器的基本信息,以及数据的描述,服务器通过这些数据的描述信息,可
以通知客户端如何处理等一会儿它回送的数据
实体内容,代表服务器向客户端回送的数据
1_响应行
协议名和版本 响应状态码 响应状态码的英文描述
2_响应头
有很多key:value组成的内容,每次响应都不相同
Location: http://www.itheima/index.html 服务器要求浏览器访问的URL地址
Server:apache tomcat 服务器通知浏览器服务器的名字
Content-Encoding: gzip 服务器通知浏览器需要接收的压缩数据类型
Content-Length: 80 服务器通知浏览器需要接收的响应内容的字节数
Content-Language: zh-cn 服务器通知浏览器需要接收的语言类型
Content-Type: text/html; charset=GBK 服务器通知浏览器需要接收的类型和使用什么方式解码
Last-Modified: Tue, 11 Jul 2014 18:23:51 GMT 服务器通知浏览器浏览器访问的请求最近一次修改的时间
Refresh: 1; url=http://www.itheima.com 服务器通知浏览器1秒钟后刷新,并且访问指定的URL页面
content-Disposition: attachment; filename=aaa.zip(下载文件) 服务器通知浏览器以下载方式打开资源
Transfer-Encoding: chunked(分块传递数据到客户端)服务器通知浏览器以分块方式下载文件的浏览器指定的目录
Set-Cookie:SS=Q0=5Lb_nQ; path=/search 服务器通知浏览器需要接收数据缓存到浏览器
Expires: -1//3种禁止缓存的头字段 以下三个响应头都表示服务器要求浏览器不要缓存来自服务器的web页面
Cache-Control: no-cache
Pragma: no-cache
Connection: close/Keep-Alive 服务器通知浏览器之间的连接是否已以关闭或者打开的
Date: Tue, 11 Jul 2014 18:23:51 GM 服务器通知浏览器的时间
如果上述头中,请求也有,响应也用,我们叫其通用头,Connection和Date就是通用头
3_响应体或实体内容
响应体和响应头之间有一个空行
实体内容包含各种数据,音乐,文件,网页等等
响应状态码是服务器对这次响应设置的一人唯一编号,
每个编码都有其特定的含义,常见的响应编号有:
200:表示服务器响应正确
302:客户端请求一台服务端的资源,该服务端并没有这个资源,服务器要求客户端自已去另一台服务端找资源,这种情况下叫做重定向
307:客户端请求一台服务端的资源,该服务端并没有这个资源,服务器自已去另一台服务端找资源,这种情况下叫做转发
304:客户端请求服务端的资源,服务器没有修改过,且已经缓存到了客户端,要求客户端去其缓存中获取即可
404:客户端请求服务端的URL出错了
405: 服务端无法找到该servlet的doGET方法或doPOST()方法,此错误一般都是方法确实引起的
500:客户端请求服务端的URL正确,但服务器处理资源出错了
04 Servlet入门
JavaEE系统结构分为三大块
1_客户层
2_JavaEE服务层
2_1_Web层/表现层,例如:Servlet&Jsp
2_2_商业逻辑层
3_EIS企业信息系统层,例如:数据库,第三方系统
什么是Servlet?
A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients, usually across HTTP, the HyperText Transfer Protocol.
Servlet是运行于Web服务器中的一个特殊的Java应用程序,Servlet能够接收来自每个客户端的请求,并做之响应,双方遵循HTTP协议
Servlet将来在企业中能做哪些工作呢?
1_能够接收客户端HTTP请求,并做以不同的响应,即动态响应
2_能做一些需要动态显示的Web资源内容,例如:下载,上传,权控显示,用户注册,用户登录。。。
总之,Servlet中做动态Web应用的核心技术之一
第一个Servlet开发步骤:
1_创建一个Demo01A类,实现Servlet接口,重写service()方法
public class Demo01A implements Servlet {
public void destroy() {
}
public ServletConfig getServletConfig() {
return null;
}
public String getServletInfo() {
return null;
}
public void init(ServletConfig arg0) throws ServletException {
}
public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException {
//向浏览器输出一短英文字符串
//从响应对象的获取输出流
//who访问me, 响应对应就指向who
//即然响应对象指向who,那么从响应对应中获取的输出流,自然指向who
PrintWriter pw = res.getWriter();
pw.write("<font style='color:red;font-size:111px'>HAHA</font>");
}
}
2_在/WEB-INF/web.xml文件中配置上述Servlet相关的信息,例用外界访问
<servlet>
<servlet-name>Demo01A</servlet-name>
<servlet-class>cn.itcast.android.servlet.Demo01A</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Demo01A</servlet-name>
<url-pattern>/Demo01A</url-pattern>
</servlet-mapping>
3_部署该web应用到web服务器,通过浏览器访问,地址如下:
http://127.0.0.1:8080/itcast-day06_http_servlet/Demo01A回车
创建Servlet方式有三种
1_普通类实现Servlet接口,并重写service()方法,每次请求服务器都调用service()方法,请求N次,调用N次service()
缺点:不必要的方法我们必须得实现
2_普通类继承GenericServlet实,并重写service(),每次请求服务器都调用service()方法,请求N次,调用N次service()
缺点:不能够区分不同的请求方式
3_普通类继承HttpServlet,并重写doGet()或doPost(),每次请求服务器都调用doXxx()方法,请求N次,调用N次doXxx()
ctrl+shift+F格式化代码
ServletRequest是父接口,既能处理http协议,也能处理非http协议
HttpServletRequest是子接口,只能处理Http协议
在web中,通常是http协议,所以我们建议用HttpServletRequest
05 Servlet生命周期
生命周期:Servlet在web服务器中,从出现到消失到整个过程
为什么在Web中,要基于接口编程呢?
如果你基于tomcat服务器中的具体类来编程的话,那么将来你的Servlet只能部署到tomcat服务器中才能运行部署到其它web服务器,例如:weblogic的话,就失败了;返之,我基于原SUN公司提供的接口编程,实现类都是由不同web服务器来完成,那么将来我的Servlet即可以在tomcat中运行成功,也可以在其它web服务器中运行成功,这样我的程序可移植性就强了
无参构造器()
通过调用的无参构造获取字节码文件的Class对象,通过反射newIntance构造出一个新的对象,该对象只会构造一次。
init()
含有servletconfig对象,可以调用xml文件的初始化参数
doGet/Post()
属于service来调拨的方法
destory()
服务器正常关闭时会执行,或者重新启动时也会执行,一个servlet生命周期中只会执行一次
默认情况下第一次访问会依次执行:无参构造器()->init()->doGet()/Post()---都只执行一次
第二次,第N次执行:doGet()/Post()---都只执行多次
重新部署时:destory()---都只执行一次
始终只有一个Servlet为所有客户端服务,即Servlet是单例的
非单列又叫多例
06 ServletConfig对象祥解
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
通过ServletConfig读取web.xml文件中为该Servlet配置的初始化参数
<servlet>
<servlet-name>Demo06</servlet-name>
<servlet-class>cn.itcast.android.servlet.Demo06</servlet-class>
<init-param>
<!-- Servlet初始化参数名 -->
<param-name>email</param-name>
<!-- Servlet初始化参数值 -->
<param-value>runsin0723@163.com</param-value>
</init-param>
<init-param>
<param-name>location</param-name>
<param-value>东圃</param-value>
</init-param>
</servlet>
ServletConfig API,并举例说明该对象的作用:
getInitParameterNames();
getInitParameter();
getServletName();
07 配置Servlet自动加载
在默认情况下,Servlet必须要首次访问时,方可创建并初始化
那有没有方式将Servlet的创建和初始化提前到web服务器启动时呢?
通常<load-on-startup>配置>=0的整数,如果配置<0的数和没配一样,即在首次访问Servlet时创建和初始化
当有多个Servlet时,数值越小,越先创建和初始化
<servlet>
<servlet-name>Demo07A</servlet-name>
<servlet-class>cn.itcast.android.servlet.Demo07A</servlet-class>
<!-- 配置Servlet自动加载 -->
<load-on-startup>1</load-on-startup>
</servlet>
08 提倡创建Servlet直接继承HttpServlet类
因为:
1_我们在企业中,处理的web请求,大多是HTTP协议的,正好HttpServlet类是符合HTTP协议规则的
2_HttpServlet类,继承了GenericServlet类,也实现了Servlet接口,不光有祖先的功能,也有自已独特的操作
3_HttpServlet类可以根据客户端提交的方法来处理,例如:get请求,doGet()方法,通常不用去重写service()方法
4_虽说HttpServlet类无init()或init(ServletConfig)方法,我们也可以获取web.xml文件中对servlet配置的初始化参数
this.getServletConfig()方法来获取ServletConfig对象
09 Servlet的运行过程
参见图解
注意:客户端不能直接请求Servlet
Servlet也不能直接响应客户端,中间必须经过Web服务器或容器
Servlet接口定义了Servlet生命周期
init()方法
服务器调用该方法初始化Servlet
service()方法
初始化完毕,服务器调用该方法响应客户的请求
destroy()方法
服务器调用该方法消灭servlet对象
注意:init()方法只在Servlet第一次被请求加载的时候被调用一次,当有客户再请求Servlet服务时,Web服务器将启动一个新的线程,在该线程中,调用service方法响应客户的请求整个过程,Servlet只创建了一次,即单例模式
10 配置Servlet虚拟路径
配置Servlet映射路径有二种类型的格式:
1_ /*
表示访问当前web应用下的所有资源都由同一个Servlet来处理
2_ *.do(do可以换成其它)
<servlet-mapping>
<servlet-name>Demo08</servlet-name>
<url-pattern>/*</url-pattern>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:一种格式是“*.扩展名”,另一种格式是以正斜杠(/)开头并以“/*”结尾。
总结:
1、只能使用 / 或 * 开头,
2、/ 和 *. 不能同时存在,即/*.do绝对会报错;
3、/ 或 /* 表示任意
4、*表示通配符
5. /* 的优先级大于 *.do
[包含符号:/、a-z、*、.]
11 缺省Servlet
项目的,将<url-pattern>/</url-pattern>的Servlet,叫缺省Servlet,
缺省Servlet通常做一些其它Servlet不处理的HTTP请求
缺省Servlet程序员不用去编写,因为每个web服务器中都由缺省Servlet,
例如:404页面就是由缺省Servlet来输出的
如果有二个缺省Servlet,只要访问的是当前web应用,就由当前web应用的缺省Servlet处理;
如果访问的不是当前web应用,则由tomcat中的缺省Servlet处理
所在当前web应用可以没有缺省Servlet,但tomcat必须有缺省Servlet
开发工具中的src目录,部署到tomcat中,就变成了/WEB-INF/classes目录
关于缺省Servlet
如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。
凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求。Tomcat默认存在一个缺省Servlet,报错的404页面就是例子,单若项目中含有缺省值文件,就会优先执行项目中的缺省servlet,忽视Tomcat默认处理界面。
12.配置Servlet自动加载
如果在<servlet>元素中配置了一个<load-on-startup>元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。正整数有效,数字越低,优先级越高。
Day06 重点代码
HTTP部分:
HTTP请求与响应练习
/**
* 禁止浏览器缓存Demo01这个web页面
*/
具体类
public class Demo01 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//禁止浏览器缓存Demo01这个web页面,因为每个浏览器不同,要针对所有浏览器进行设置
WebUtil.setIeNoCache(response);
//服务器向浏览器输出中文,text/html;charset=UTF-8就是响应头
response.setContentType("text/html;charset=UTF-8");
//以字符流方式输出
response.getWriter().write("<font style='font-size:111px;color:red'>哈哈</font>");
}
}
/**
* 工具类
*/
public class WebUtil {
/**
* 不要外界创建
*/
private WebUtil(){}
/**
* 禁止浏览器缓存web页面
*/
public static void setIeNoCache(HttpServletResponse response){
//禁止浏览器缓存Demo01这个web页面,因为每个浏览器不同,要针对所有浏览器进行设置
response.setDateHeader("expires",-1);
response.setHeader("cache-control","no-cache");
response.setHeader("pragma","no-cache");
}
}
---------------------------------------------------------------------------------------------------------------------------------------
/**
* 通知浏览器3秒后跳到url指定的web页面
*/
public class Demo02 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
response.setHeader("Refresh","3;url=http://127.0.0.1:8080");
}
}
---------------------------------------------------------------------------------------------------------------------------------------
/**
* 下载文件
*/
public class Demo03 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//响应头
//content-disposition表示下载响应头
//attachment表示浏览器会打开一个下载提示框
//filename=abc.jpg表示浏览器打开的下载提示框中显示的下载文件名
//今天下载的文件是假的
//如果是中文文件名的话,一定要进行URL编码,各个浏览器做URL解码
String filename = "中文.jpg";
filename = URLEncoder.encode(filename,"UTF-8");
response.setHeader("content-disposition","attachment;filename="+filename);
}
}
---------------------------------------------------------------------------------------------------------------------------------------
编写Servlet的三种方式:
方法一:实现Servlet接口
public class Demo01A implements Servlet {
public void destroy() {
}
public ServletConfig getServletConfig() {
return null;
}
public String getServletInfo() {
return null;
}
public void init(ServletConfig arg0) throws ServletException {
}
public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException {
//向浏览器输出一短英文字符串
//从响应对象的获取输出流
//who访问me, 响应对应就指向who
//即然响应对象指向who,那么从响应对应中获取的输出流,自然指向who
PrintWriter pw = res.getWriter();
pw.write("<font style='color:red;font-size:111px'>HAHA</font>");
}
}
方法二:继承GenericServlet抽象类,注意,GenericServlet该抽象类是一个空实现,仅仅为了快速编写servlet的service方法
/**
* 创建Servlet程序的方式二
* GenericServlet类已对Servlet接口的中的所有方法做了空实现,即方法体无内容
*/
public class Demo01B extends GenericServlet {
public Demo01B(){
System.out.println("创建Demo01B");
}
@Override
public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException {
//通知浏览器需要接收一个html页面,其中字符串用UTF-8的方式进行解码
res.setContentType("text/html;charset=utf-8");
//获取指向每个浏览器的字符输出流
PrintWriter pw = res.getWriter();
//向浏览器输出一段中文字符串
pw.write("<font style='font-size:111px;color:red'>Servlet是做动态Web技术之一</font>");
}
}
方法三:推荐使用,继承HTTPServlet类,该类是GenericServlet类的具体实现,service方法不用覆盖,作为中转方法,转到doGET或doPOST方法上
/**
* 创建Servlet程序的方式三
*/
public class Demo01C extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
System.out.println(request);
System.out.println(response);
//通知浏览器接收类型和解码方式
response.setContentType("text/html;charset=UTF-8");
//从响应对应的中获取字符输出流
PrintWriter pw = response.getWriter();
//以下代码输出一个登录表单
pw.write("<form action='#' method='POST'>");
pw.write("<table border='2' align='center'>");
pw.write("<caption><h2>用户登录</h2></caption>");
pw.write("<tr><th>姓名</th><td><input type='text' name='username'/></td></tr>");
pw.write("<tr><th>密码</th><td><input type='password' name='password'/></td></tr>");
pw.write("<tr><td colspan='2' align='center'><input type='submit' value='提交'/></td></tr>");
pw.write("</table>");
pw.write("</form>");
}
}
---------------------------------------------------------------------------------------------------------------------------------------
/**
* 演示Servlet生命周期中各方法的执行情况 通过多次的点击,发现除了首次执行空参构造与init方法外,其余点击都是执行service,而且地址值得hascode都相同,所以证明servlet是单例模式
*每次执行访问都创建新的httpservletrequest对象与httpservletreaonse对象
*/
public class Demo05 extends HttpServlet {
public Demo05() {
System.out.println("Demo05()::" + this.hashCode());
}
public void init() throws ServletException {
System.out.println("init()::" + this.hashCode());
}
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
System.out.println("doGet()::" + this.hashCode());
}
public void destroy() {
System.out.println("destroy()::" + this.hashCode());
}
}
---------------------------------------------------------------------------------------------------------------------------------------
init初始化方法能获取在servlet加载时设定在web.xml的初始化对象,ServletConfig类对象还有很多好方法,请查看API
/**
* 通过init(ServletConfig对象)方法获取web.xml文件中Servlet配置的初始化参数
* 单例
*/
public class Demo06 extends HttpServlet {
/**
* 将ServletConfig对象做成实例变量
* 被所有客户端共享,只能做读操作,如果做写操作的话,非常危险
*/
private ServletConfig config;
public Demo06(){
System.out.println("Demo06()");
}
/**
* 读取web.xml文件中的servlet初始化参数
*/
@Override
public void init(ServletConfig config) throws ServletException {
//为实例变量ServletConfig设置值
this.config = config;
}
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter pw = response.getWriter();
//一次性获取servlet的所有初始化参数名,初始化参数存放于web.xml文件中
Enumeration<String> enums = config.getInitParameterNames();
//迭代
while(enums.hasMoreElements()){
//获取初始化参数名,例如:email和location
String name = enums.nextElement();
//根据实始化参数名获取对应的参数值,例如:"xxxx"和"yyyy"
String value = config.getInitParameter(name);
//在浏览器在显示
pw.write(name+":"+value+"<br/>");
}
//获取Servlet名字,即在web.xml文件中<servlet-name>
pw.write(config.getServletName()+"<br/>");
}
}
---------------------------------------------------------------------------------------------------------------------------------------
load-on-startup:正整数,数字越小优先级越高,0的优先级最高
<servlet>
<servlet-name>Demo07A</servlet-name>
<servlet-class>cn.itcast.android.servlet.Demo07A</servlet-class>
<!-- 配置Servlet自动加载 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Demo07B</servlet-name>
<servlet-class>cn.itcast.android.servlet.Demo07B</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
---------------------------------------------------------------------------------------------------------------------------------------
/**
* 写一个Servlet,用于读取E:/下的图片,响应给所有浏览器
* 注意:文件若不是文本文件,只能采用字节流方式读取,不然会乱码,打不开
*/
public class Demo09 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
System.out.println("Demo09");
//字节输入流,路径要看tomcat中,不是看开发工具中
InputStream is = this.getClass().getClassLoader().getResourceAsStream("../../images/广州地铁规划图.jpg");
//字节输入流
OutputStream os = response.getOutputStream();
//以下是通过循环读取文件内容,并输出到浏览器中
byte[] buf = new byte[1024];
int len = 0;
while((len=is.read(buf))>0){
os.write(buf,0,len);
}
os.close();
is.close();
}
}