☆掌握Web的基础知识
☆熟悉HTTP及其工作原理
☆理解浏览器与Web服务器的交互原理
我们其实经常与Web打交道,例如,大家经常上百度查找资料,上淘宝购物等。那么什么是Web?Web是一个可通过互联网来访问的、由许多互相链接的超文本(HyperText)组成的系统。
Web的主要角色是浏览器(又称为客户端)和服务器(网站),它们的交互过程是这样的:用户打开浏览器,输入网址后按回车键,这时浏览器就会向网址所代表的服务器(网站)发出HTTP请求,该请求经过网络传输后到达服务器,服务器做出响应后再把结果(Web页面)返回给浏览器。简单来说就是浏览器发出请求,服务器做出响应。
Web服务器又称为WWW(World Wide Web,万维网)服务器、HTTP服务器或网站服务器,它将信息用超文本组织起来,并为用户在Internet上搜索和浏览信息提供服务。
Web服务器实际上是安装在高性能、高可靠性的计算机上的软件系统。常见的Web服务器有Microsoft IIS、IBM WebSphere、Oracle WebLogic、JBoss、Apache Tomcat等。一个Web服务器可以安装多个Web应用,一个Web应用就是一个相对独立的提供信息服务的“网站”,每个Web应用又包含多个Web页面。
Web应用在提供信息服务之前,所有信息都必须以文件的方式事先存放在Web服务器磁盘中的某个文件夹下,其中包含了由超文本标记语言(HyperText Markup Language,HTML)组成的文本文件,这些文本文件就称为Web页面或网页文件。
Web页面是一种可供人们通过网络访问的Web资源,Web资源又分为两部分:静态Web资源与动态Web资源。静态Web资源是指Web页面中供人们浏览的数据始终是不变的,如HTML页面、CSS文件、图片等。动态Web资源是指Web页面中供人们浏览的数据是由程序产生的,不同时间点访问Web页面看到的内容各不相同,如Java服务器页面(Java Server Pages,JSP)。
Java Web是用Java技术来解决Web领域的相关技术的总和。Web包括Web服务器和Web客户端两部分。Java在服务器端(简称服务器或服务端)的应用非常丰富,如Servlet技术、JSP技术和第三方框架等。其中,Servlet和JSP技术也是本书要重点讲解的内容。
Java的Web框架虽然各不相同,但基本都遵循特定的路线:使用Servlet或者Filter拦截请求,使用MVC的思想设计架构,使用约定、XML(可扩展标记语言)或Annotation实现配置,运用面向对象编程思想实现请求与响应的流程,使用JSP、FreeMarker、Velocity等实现视图。
上面提到的MVC是“模型(Model)-视图(View)-控制器(Controller)”的缩写,是一种软件设计典范,它用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。而XML则是“EXtensible Markup Language”的缩写,是标准通用标记语言的子集,是一种用于标记电子文件,使其具有结构性的标记语言。
在Web里,一般客户端访问Web网页都会遵循超文本传输协议(HyperText Transfer Protocol,HTTP)。HTTP是一个客户端和服务器端发送请求及响应请求的标准,是用于从WWW服务器传输超文本到本地浏览器的传送协议。
HTTP常用于客户端与服务器端的通信。在HTTP里,必定有一方担任客户端,另一方担任服务器端,如图1.1所示。请求都是由客户端发起的,而服务器端则是响应客户端发起的请求。例如,通过浏览器访问网址www.baidu.com的时候,浏览器是发起请求的一方,所以浏览器是客户端;而百度的服务器根据相应的请求,给浏览器返回其想要的资源,所以它是服务器端。
图1.1 客户端与服务器端的通信
当在客户端输入www.baidu.com的时候,浏览器会发送一个请求到某个服务器,服务器响应后就给客户端发回一个页面。而当客户端向服务器发送请求的时候,客户端需要遵循一定的协议才能与之进行交流。正如一个只会说中文的人和一个只会说英文的人是无法通过语言进行交流的!只有当他们都说中文或者英文的时候才能通过语言进行交流,这语言就是他们之间必须遵循的协议。
为了更好地理解HTTP,我们先来了解一下TCP/IP协议簇。人们通常使用的网络(包括互联网)都是在TCP/IP协议簇的基础上运作的,而HTTP就是它们内部的一个子集。
TCP/IP协议簇按层次可分为应用层、传输层、网络层和链路层。把TCP/IP层次化是有好处的。假设没有把TCP/IP层次化,当其中的某一部分需要改变时,整个TCP/IP都要被替换掉。但是TCP/IP层次化之后,层与层之间是通过接口进行通信的,若是其中一层内部发生了变化,而它的接口没有变化,这样只需要把这一层替换掉就行了。由此引申可知,程序员在设计程序的时候,也应该对程序进行层次化/模块化的划分,这样当程序的某一个模块发生改变时,只需改动某一个模块就行了,不用进行整体上的修改。TCP/IP协议簇的层次如图1.2所示。
图1.2 TCP/IP协议簇的层次
(1)应用层
应用层决定了向用户提供应用服务时通信的活动。TCP/IP协议簇内预存了各类通用的应用服务。例如,文本传输协议(File Transfer Protocol,FTP)和域名系统(Domain Name System,DNS)就是其中的两类。
HTTP也处于该层,且大部分网络应用程序的编程都是基于应用层进行的。
(2)传输层
传输层实现了处于网络连接的两台计算机之间的数据传输。传输层有两个性质不同的协议:传输控制协议(Transmission Control Protocol,TCP)和用户数据报协议(User Data Protocol,UDP)。
(3)网络层
网络层可用来处理在网络上流动的数据包(数据包是网络传输的最小数据单位)。该层规定了通过怎样的路径(即传输线路)到达对方计算机,并把数据包传送给对方。与对方计算机之间通过多台计算机或网络设备进行传输时,网络层所起的作用就是在众多可选的路线中选择一条传输路线。
(4)链路层
链路层是为网络层提供数据传送服务的,其最基本的服务是将源自网络层的数据可靠地传输到相邻节点的目标机网络层。链路层包括物理链路(物理线路)和数据链路(逻辑线路)。物理链路是由传输介质与硬件设备组成的;数据链路是指在一条物理线路之上,通过一些规则或协议控制数据的传输,以保证被传输数据的正确性。
数据的封装与解封装是客户端与服务器端的数据交换需要经过封装、传输与解封装的过程。封装是将一端发送的数据变为比特流的过程。封装过程中,在TCP/IP模型的每一层需要添加特定的协议报头,如图1.3左图所示。数据封装完毕,转变为比特流,经过网络传输到服务器端,服务器端则对比特流进行解封装。解封装是封装的逆过程,即数据从比特流还原为原始数据的过程。解封装是从底层往高层依次解封装,每解封一层,都会将该层的那个协议报头去掉,如图1.3右图所示,最终还原为原始数据。
TCP/IP协议簇中最重要的协议就是TCP和IP。
图1.3 TCP/IP数据传输
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP位于传输层,提供了可靠的字节流服务(Byte Stream Service,BSS)。字节流服务是指为了方便传输,将大块数据分割成报文段(Segment)为单位的数据包以便进行管理。TCP通过“三次握手”(见图1.4)确保数据能够到达目标。而客户端与服务器端“三次握手”之后的主要目的是建立连接,接下来双方就可以进行通信了。
图1.4 三次握手
IP(Internet Protocol,网际协议)的作用是把各种数据包传送给对方,要保证确实传送给了对方,需要满足各类条件。其中最重要的就是IP地址和媒体访问控制地址(Media Access Control Address,MAC)。IP地址指明了节点被分配到的地址,MAC则是指网卡所属的固定地址。
在网络上,经过多台计算机和网络设备中转才能连接到对方。而在进行中转的时候,会利用下一站中转的设备的MAC来搜索下一个中转目标,这时就要用到地址解析协议(Address Resolution Protocol,ARP)。ARP是一种用以解析地址的协议,它根据通信方的IP可以反查对应的MAC。图1.5所示为使用ARP凭借MAC进行IP间的通信。
图1.5 使用ARP凭借MAC进行IP间的通信
前面提到的www.baidu.com并不是一个IP地址,但是我们根据网址还是能够访问它的。其原因就是DNS解析了这个域名,并返回对应的IP地址给发送端。其工作流程如图1.6所示。
例如,客户端发送一个想要浏览http://javaweb.com/xss/Web的请求,这时DNS负责解析域名,并返回给客户端对应的IP地址。而HTTP则会生成一个针对目标Web服务器的HTTP请求报文。为了方便通信,TCP将HTTP请求报文分割成多个报文段,并保证会将之可靠地传给对方。路由器传递报文段的时候需要IP的协助,搜索对方的地址,边中转边传送,直到找到对应的服务器。这时TCP开始发挥作用,它将多个报文段按原来的顺序重组请求报文,而HTTP的职责就是对请求的内容进行处理,最后处理结果也是同样按照TCP/IP通信协议向用户进行回传。所有的流程如图1.7所示。
图1.6 DNS域名解析服务
图1.7 DNS域名解析服务
统一资源标识符(Uniform Resource Identifier,URI)用来唯一地标识一个资源。Web上可用的每种资源,如HTML文档、图像、视频片段、程序等都是用URI定位的。
URI一般由三部分组成。
(1)访问资源的命名机制。
(2)存放资源的主机名。
(3)资源自身的名称,由路径表示,着重强调资源。
统一资源定位器(Uniform Resource Locator,URL)是一种具体的URI,可用来标识一个资源,而且还会指明如何定位这个资源。
URL是Internet上用来描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上。URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。
URL一般由三部分组成。
(1)协议(或称为服务方式)。
(2)存有该资源的主机IP地址(有时也包括端口号)。
(3)主机资源的具体地址,如目录和文件名等。
URI和URL都能唯一标识资源,但URL还指明了该如何访问资源。URL是一种具体的URI,是URI的一个子集,它不仅唯一标识资源,而且提供了定位该资源的信息。URI是一种语义上的抽象概念,可以是绝对的,也可以是相对的;而URL必须提供足够的信息来定位,是绝对的。
HTTP可用来定义Web客户端如何向Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。
(1)简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定的客户与服务器联系的类型都不相同。由于HTTP简单,HTTP服务器的程序规模小,因而通信速度很快。
(2)灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
(3)无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即会断开连接。采用这种方式可以节省传输时间。
(4)无状态:HTTP是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另外,在服务器不需要先前信息时它的应答就比较快。HTTP协议支持浏览器/服务器(Browser/Server,B/S)和客户端/服务器(Client/Server,C/S)两种模式。
HTTP常用的两种请求方法是GET方法和POST方法。GET和POST的区别如下。
(1)通过GET方法提交的数据会放在URL之后,以“?”分割URL和传输数据,参数之间以“&”相连,如“EditPosts.aspx?name=test1&id=123456”。POST方法则是把提交的数据放在HTTP包的Body中。
(2)通过GET方法提交的数据大小有限制(因为浏览器对URL的长度有限制),而通过POST方法提交的数据大小没有限制。
(3)GET方法需要使用Request.QueryString来取得变量的值,而POST方法则可通过Request.Form来获取变量的值。
(4)通过GET方法提交数据会带来安全问题,例如,一个登录页面通过GET方法提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,他人就可以从历史记录中获得该用户的账号和密码。
HTTP是一种不保存状态的(即无状态的)协议。也就是说,HTTP不具有保存之前发送过的请求或者响应的功能。在使用HTTP的时候,每当有新的请求就会有对应的新响应产生。这样就会产生一个问题,例如,访问某系统的时候,由于HTTP是不保存状态的,也就是说在第一个页面登录之后,再到其他页面(如由网上选课页面到课表页面)的时候,是需要重新登录一遍的,这样显然是不合理的。而且在实际操作当中并没有每到一个页面都要重新登录一遍。这是如何完成的呢?其实这里引入了Cookie对象对登录的信息进行保存,而且值得注意的是,在HTTP的1.1版本中有持久连接。
假设这样一个场景,小明打电话给小强,让小强帮他带一份饭回来。如果没有建立持久连接,则拨通/挂掉电话就会建立/断开连接,每次说话都相当于请求/响应。
从图1.8中可以看到,如果每次发送或响应请求都需要建立连接,这样就会消耗很多的时间。看起来这个例子里面的消耗不是很多,但是当访问量达到百万级、千万级的时候,服务器中的资源就会浪费很多。所以为了解决上述问题,HTTP 1.1版本中就提出了建立持久连接。持久连接的特点是,只要任意一端没有明确提出断开连接,就会一直保持TCP的连接状态。
此时,发出请求和响应请求的速度明显加快了,但是这样还不够。从图1.9中可以看到,当小明(客户端)发出一个请求的时候,必须等待小强(服务器端)对这个请求响应之后,才能对下一个请求进行发送。
小明在等待小强响应的过程中可能还会发出其他的请求。为了解决这一问题,我们可以用一种管线化的方式发送请求。再用之前的例子来说明。不管小强有没有对小明刚刚的请求进行响应,只要他们之间建立了连接,小明就可以对小强发出请求。实现管线化之后的例子如图1.10所示。
图1.8 没有持久连接
图1.9 有持久连接
图1.10 用管线化的方式发送请求
前面提到过,HTTP是无状态的协议,不管之前发生过什么请求或响应,都是不会保存的。也就是说每次的页面跳转都要重新进行用户登录的操作,这样就会浪费很多资源,并且十分麻烦。所以就引入了Cookie对象,用来保存这些用户的状态信息。
第一次发送请求如图1.11所示,客户端发送的请求是没有Cookie信息的,当服务器端进行响应时,服务器就会在响应请求中包含一个Cookie信息,并且在响应行里标明信息,让客户端对Cookie信息进行保存。
当客户端发送第二次请求的时候(见图1.12),就会找到刚刚保存的Cookie,并在请求行里标明Cookie信息,把Cookie随着请求一起发送到服务器端。服务器端解析Cookie信息,就知道这是谁发送的请求。
图1.11 第一次发送请求
图1.12 第二次发送请求
HTTP采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文中包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。
HTTP请求/响应的步骤如下。
(1)客户端连接到Web服务器
一个HTTP客户端通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.baidu.com。
(2)发送HTTP请求
通过TCP套接字,客户端向Web服务器发送一个文本的请求报文。一个请求报文由请求行、请求头部、空行和请求数据4部分组成。
(3)服务器接受请求并返回HTTP响应
Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应报文由状态行、响应头部、空行和响应数据4部分组成。
(4)释放/保持TCP连接
若connection(连接)模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection(连接)模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求。
(5)客户端浏览器解析HTML内容
客户端浏览器首先解析状态行,查看标明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示数据。
用于HTTP交互的信息可称为HTTP报文。请求端(客户端)的HTTP报文叫作请求报文,响应端(服务器端)的叫作响应报文。而报文也可以分为报文首部和报文主体两块,如图1.13所示,报文首部和报文主体中间有一个空行。
图1.13 HTTP报文的格式
HTTP请求报文的首部也可以继续细分成请求头和请求行,如图1.14所示。
图1.14 HTTP请求报文
(1)GET请求例子,使用Charles抓取的请求:
GET /562f25980001b1b106000338.jpg HTTP/ 1.1 Host img.mukewang.com User-Agent Mozilla/5.0(Windows NT10.0; WOW64) AppleWebKit/537.36(KHTML.like Gecko) Chrome/51.0.2704.106 Safari/537.36 Accept image/webp.image/**/; q=0.8 Referer http://www.imooc.com/ Accept-Encoding gzip, deflate, sdch
第一部分:请求行,用来说明请求类型、要访问的资源以及所使用的HTTP版本。GET说明请求类型为GET,“/562f25980001b1b106000338.jpg”为要访问的资源,该行的最后一部分说明使用的是HTTP 1.1版本。
第二部分:请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息。从第二行起为请求头部,Host将指出请求的目的地“User-Agent”,服务器端和客户端脚本都能访问它,它是浏览器类型检测逻辑的重要基础。该信息由浏览器来定义,并且在每个请求中自动发送。
第三部分:空行,请求头部后面的空行是必需的,即使第四部分的请求数据为空,也必须有空行。
第四部分:请求数据(也叫作主体),可以添加任意的其他数据。这个例子的请求数据为空。
(2)POST请求例子,使用Charles抓取的请求:
POST/HTTP/1.1 Host:www.wrox.com User-Agent:Mozilla/4.0(compatible; MSIE6.0; Windows NT5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR3.5.21022) Content-Type:application/x-www-form-urlenco ded Content-Length:40 Connection:Keep-Alive name =Professional%20Ajax&publisher=Wiley
第一部分:请求行,第1行说明了是POST请求,以及HTTP 1.1版本。
第二部分:请求头部,第2~6行。
第三部分:空行,第7行。
第四部分:请求数据,第8行。
一般情况下,服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应信息。
HTTP响应也由4个部分组成:状态行、消息报头、空行和响应正文,如图1.15所示。
图1.15 HTTP响应报文
第一部分:状态行,由HTTP版本号、状态码、状态消息三部分组成。在第一行中,HTTP/1.1表明HTTP版本为1.1,状态码为200,状态消息为OK。
第二部分:消息报头,用来说明客户端要使用的一些附加信息。第二行和第三行为消息报头,Date是生成响应的日期和时间,Content-Type指定了MIME类型的HTML(text/html),编码类型是UTF-8。
第三部分:空行,消息报头后面的空行是必需的。
第四部分:响应正文,服务器返回给客户端的文本信息。
状态代码由三位数字组成,第一个数字定义了响应的类别。状态代码分为以下几种。
(1)1xx:指示信息,表示请求已接收,继续处理。
(2)2xx:成功,表示请求已被成功接收、理解、接受。
(3)3xx:重定向,表示完成请求必须进行更进一步的操作。
(4)4xx:客户端错误,表示请求有语法错误或请求无法实现。
(5)5xx:服务器端错误,表示服务器未能实现合法的请求。
(1)200 OK:客户端请求成功。
(2)400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
(3)401 Unauthorized:请求未经授权,这个状态码必须与WWW-Authenticate报头域一起使用。
(4)403 Forbidden:服务器收到请求,但是拒绝提供服务。
(5)404 Not Found:请求资源不存在,如输入了错误的URL。
(6)500 Internal Server Error:服务器发生不可预期的错误。
(7)503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常。
下面以访问百度网站www.baidu.com为例,介绍浏览器与Web服务器的交互过程。
(1)当用户第一次访问www.baidu.com时,浏览器并不知道www.baidu.com的IP地址,将请求报文发向DNS服务器,DNS服务器查询到www.baidu.com对应的IP地址后,将地址发回浏览器,浏览器将这个IP地址缓存到本地,下次访问www.baidu.com时将直接读取缓存中的IP地址。然后浏览器根据IP地址向www.baidu.com对应的Web服务器发送请求报文。
(2)Web服务器接收请求报文后,解析请求报文,将请求的资源存放到响应报文中,将响应报文发回浏览器。
(3)浏览器接收响应报文,解析响应报文,将请求到的资源显示在浏览器页面上。此时完成一个交互过程。
这里可以写一个小程序模仿浏览器和Tomcat的交互。
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; /** * @author yezl * @date 2018年4月26日 下午1:51:35 * @version 1.0.0 */ public class SimpleTomcat { public static void main(String[] args) throws IOException { ServerSocket serverSocket = null; int port = 8050; serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1") ); while(true) { Socket socket = null; InputStream input = null; OutputStream output = null; socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); //获取浏览器发送的请求, 这里是在控制台输出 dorequest(input); //回应浏览器, 这里返回一个字符串 String responseMessage = "<h1>Hello world, I am a simple server</h1>"; output.write(responseMessage.getBytes() ); socket.close(); } //serverSocket.close(); } /** * 输出浏览器发送的请求 * @author yezl * @date 2018年4月26日 * @version 1.0.0 * @param input */ public static void dorequest(InputStream input) { // 接受请求 StringBuffer request = new StringBuffer(2048); int i; byte[] buffer = new byte[2048] ; try { i = input.read(buffer); } catch(IOException e) { e.printStackTrace(); i = -1; } for(int j = 0; j < i; j++) { request.append((char) buffer[j] ); } System.out.print(request.toString() ); } }
上面这个Java类比较简单,用Java自带的Socket(套接字)进行编程,模拟了服务器应答请求的全过程。首先服务器监听本地的8050端口,127.0.0.1表示本地IP,input指向网络流的输入,output指向网络流的输出,利用dorequest( )函数解析输入流的内容,并且在控制台输出,然后写入“<h1>Hello world,I am a simple server</h1>”到输出流output,使之返回到发出请求的客户端浏览器,并显示在页面上。
①运行上面的程序,在浏览器中输入http://127.0.0.1:8050/,按回车键,待出现图1.16所示的画面就表示运行成功了。
②在控制台可以看到图1.17所示的输出,这些就是浏览器发送给服务器端的请求。
图1.16 浏览器显示服务器发送的“Hello world”
图1.17 浏览器发送给服务器端的请求
本章主要讲解了以下内容:
•Web概念;
•HTTP的基础知识;
•浏览器与服务器的交互原理。
第2章将会介绍Java Web中关于Servlet以及JSP的相关知识,并利用IDEA使用Maven搭建一个简单的Web应用。
1.TCP/IP协议簇分为哪几层?各层的主要功能是什么?
2.简述DNS的工作流程。
3.URI和URL有什么区别?
4.HTTP的常见状态码有哪些?