Http协议的学习
Http协议的学习
开始之前,这里推荐用几款用于学习Http协议的软件,这些软件也可以用于调试程序的BUG
Wireshark fiddler postman
1.1 介绍
HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写。它的发展是万维网协会(World Wide Web Consortium)和Internet工作小组IETF(Internet Engineering Task Force)合作的结果,(他们)最终发布了一系列的RFC,RFC 1945定义了HTTP/1.0版本。其中最著名的就是RFC 2616。RFC 2616定义了今天普遍使用的一个版本——HTTP 1.1。
HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效,使网络传输减少。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。
HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。
1.2 在TCP/IP协议栈中的位置
HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。如下图所示:
默认HTTP的端口号为80,HTTPS的端口号为443。
1.3 HTTP的请求响应模型
HTTP协议永远都是客户端发起请求,服务器回送响应。见下图:
这样就限制了使用HTTP协议,无法实现在客户端没有发起请求的时候,服务器将消息推送给客户端。
HTTP协议是一个无状态的协议,同一个客户端的这次请求和上次请求是没有对应关系。
1.4 工作流程
一次HTTP操作称为一个事务,其工作过程可分为四步:
1)首先客户机与服务器需要建立连接。只要单击某个超级链接,HTTP的工作开始。
2)建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL)、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。
3)服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。
4)客户端接收服务器所返回的信息通过浏览器显示在用户的显示屏上,然后客户机与服务器断开连接。
如果在以上过程中的某一步出现错误,那么产生错误的信息将返回到客户端,有显示屏输出。对于用户来说,这些过程是由HTTP自己完成的,用户只要用鼠标点击,等待信息显示就可以了。
1.5 三次握手和四次挥手
三次握手,所谓"三次握手"即对每次发送的数据量是怎么跟踪进行协商使数据段的发送和接收同步,根据所接收到的数据量而确定的数据确认数及数据发送、接收完毕后何时撤销联系,并建立虚拟连接。
为了提供可靠的传送,TCP在发送新的数据之前,以特定的顺序将数据包的序号,并需要这些包传送给目标机之后的确认消息。TCP总是用来发送大批量的数据。当应用程序在接收到数据后要做出确认时也要用到TCP。
为什么需要三次握手?
在谢希仁著《计算机网络》第四版中讲"三次握手"的目的是"为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误"。在另一部经典的《计算机网络》一书中讲"三次握手"的目的是为了解决"网络中存在延迟的重复分组"的问题。这两种不用的表述其实阐明的是同一个问题。
谢希仁版《计算机网络》中的例子是这样的,"已失效的连接请求报文段"的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用"三次握手",那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用"三次握手"的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。"。主要目的防止server端一直等待,浪费资源。
TCP Flags
暂时需要的信息有:
ACK : TCP协议规定,只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1
SYN(SYNchronization) :在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1. 因此, SYN置1就表示这是一个连接请求或连接接受报文。
FIN (finis)即完,终结的意思,用来释放一个连接。当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接。
序号Seq
序号占4字节。序号范围是[0,2^32-1],共2^32(即4284967296)个序号。序号增加到2^32-1后,下一个序号就又回到0。也就是说,序号使用mod 2^32运算。TCP是面向字节流的。在一个TCP连接中床送的字节流中的每一个字节都按顺序编号。整个要传送的数据的第一个字节的序号。例如,一报文的序号字段是301,而携带的数据共有100字节。这就表明:本报文段的数据的第一个字节的序号是301,最后一个字节的序号是400。显然,下一个报文段(如何还有的话)的数据序号应当从401开始,即下一个报文段的序号字段值应为401。这个字段的名称也就做"报文段序号"。
3次握手的过程
首先由Client发出请求连接即 SYN=1 ACK=0 , TCP规定SYN=1时不能携带数据,但要消耗一个序号,因此声明自己的序号是 seq=x
然后 Server 进行回复确认,即 SYN=1 ACK=1 seq=y, ack=x+1,
再然后 Client 再进行一次确认,但不用SYN 了,这时即为 ACK=1, seq=x+1, ack=y+1.
3次握手过程状态
LISTEN: 这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处于监听状态,可以接受连接了。
SYN_SENT: 当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。(发送端)
SYN_RCVD: 这个状态与SYN_SENT遥想呼应这个状态表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个 ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。(服务器端)
ESTABLISHED:这个容易理解了,表示连接已经建立了。
为什么需要四次挥手
确保数据能够完成传输
关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,在发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
4次握手过程
当客户A 没有东西要发送时就要释放 A 这边的连接,A会发送一个报文(没有数据),其中 FIN 设置为1, 服务器B收到后会给应用程序一个信,这时A那边的连接已经关闭,即A不再发送信息(但仍可接收信息)。 A收到B的确认后进入等待状态,等待B请求释放连接, B数据发送完成后就向A请求连接释放,也是用FIN=1 表示,并且用 ack = u+1(如图), A收到后回复一个确认信息,并进入 TIME_WAIT 状态,等待 2MSL 时间。
为什么要等待呢?
为了这种情况: B向A发送 FIN = 1 的释放连接请求,但这个报文丢失了, A没有接到不会发送确认信息, B 超时会重传,这时A在 WAIT_TIME 还能够接收到这个请求,这时再回复一个确认就行了。(A收到 FIN = 1 的请求后 WAIT_TIME会重新记时)
另外服务器B存在一个保活状态,即如果A突然故障死机了,那B那边的连接资源什么时候能释放呢? 就是保活时间到了后,B会发送探测信息,以决定是否释放连接。
4次握手状态
FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方)
FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。(主动方)
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)
CLOSING(比较少见): 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。(被动方)
LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)
CLOSED: 表示连接中断。
TCP的状态转换图
1.6 使用Wireshark抓TCP、http包
从上图可以很清楚的看到浏览器与服务器建立连接的过程
1)浏览器:172.17.147.17 服务器:60.205.8.179 协议TCP 协议的内容:[SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=4 SACK_PERM=1
此为TCP三次握手的第一次。从图中可以看出,[SYN] Seq=0;
2)服务器:60.205.8.179 浏览器:172.17.147.17 协议TCP 协议内容:[SYN, ACK] Seq=0 Ack=1 Win=14600 Len=0 MSS=1460 SACK_PERM=1 WS=128
此为TCP三次握手的第二次。从图中可以看出来[SYN,ACK] Seq=0 Ack=1;
3)浏览器:172.17.147.17 服务器:60.205.8.179 协议TCP 协议内容:[ACK] Seq=1 Ack=1 Win=65700 Len=0
此为TCP三次握手的第三次。从图中可以看出[ACK] Seq=1 Ack=1;
-
浏览器:172.17.147.17 服务器:60.205.8.179 协议HTTP 协议内容GET /chaosju/article/details/39694609 HTTP/1.1
发出一个页面的HTTP请求
-
服务器:60.205.8.179 浏览器:172.17.147.17 协议TCP 协议内容:[ACK] Seq=1 Ack=798 Win=16256 Len=0
服务器确认
-
服务器:60.205.8.179 浏览器:172.17.147.17 协议HTTP HTTP/1.1 200 OK (text/html)
服务器返回数据
........
关闭连接
1.7头域
每一个头域由一个域名,冒号(:) 和域值三部分组成。域名是大小写无关的,域值前可以添加任何数据数量的空格符,头域可以被扩展为多行,在每行开始出,使用至少一个空格或制表符。
HTTP最常见的请求头如下:
l Accept:浏览器可接受的MIME类型;
l Accept-Charset:浏览器可接受的字符集;
l Accept-Encoding:浏览器能够进行解码的数据编码方式,比如gzip。Servlet能够向支持gzip的浏览器返回经gzip编码的HTML页面。许多情形下这可以减少5到10倍的下载时间;
l Accept-Language:浏览器所希望的语言种类,当服务器能够提供一种以上的语言版本时要用到;
l Authorization:授权信息,通常出现在对服务器发送的WWW-Authenticate头的应答中;
l Connection:表示是否需要持久连接。如果Servlet看到这里的值为"Keep-Alive",或者看到请求使用的是HTTP 1.1(HTTP 1.1默认进行持久连接),它就可以利用持久连接的优点,当页面包含多个元素时(例如Applet,图片),显著地减少下载所需要的时间。要实现这一点,Servlet需要在应答中发送一个Content-Length头,最简单的实现方法是:先把内容写入ByteArrayOutputStream,然后在正式写出内容之前计算它的大小;
l Content-Length:表示请求消息正文的长度;
l Cookie:这是最重要的请求头信息之一;
l From:请求发送者的email地址,由一些特殊的Web客户程序使用,浏览器不会用到它;
l Host:初始URL中的主机和端口;
l If-Modified-Since:只有当所请求的内容在指定的日期之后又经过修改才返回它,否则返回304"Not Modified"应答;
l Pragma:指定"no-cache"值表示服务器必须返回一个刷新后的文档,即使它是代理服务器而且已经有了页面的本地拷贝;
l Referer:包含一个URL,用户从该URL代表的页面出发访问当前请求的页面。
l User-Agent:浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用;
l UA-Pixels,UA-Color,UA-OS,UA-CPU:由某些版本的IE浏览器所发送的非标准的请求头,表示屏幕大小、颜色深度、操作系统和CPU类型。
HTTP最常见的响应头如下所示:
l Allow:服务器支持哪些请求方法(如GET、POST等);
l Content-Encoding:文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此,Servlet应该通过查看Accept-Encoding头(即request.getHeader("Accept-Encoding"))检查浏览器是否支持gzip,为支持gzip的浏览器返回经gzip压缩的HTML页面,为其他浏览器返回普通页面;
l Content-Length:表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。如果你想要利用持久连接的优势,可以把输出文档写入ByteArrayOutputStram,完成后查看其大小,然后把该值放入Content-Length头,最后通过byteArrayStream.writeTo(response.getOutputStream()发送内容;
l Content-Type: 表示后面的文档属于什么MIME类型。Servlet默认为text/plain,但通常需要显式地指定为text/html。由于经常要设置Content-Type,因此HttpServletResponse提供了一个专用的方法setContentTyep。 可在web.xml文件中配置扩展名和MIME类型的对应关系;
l Date:当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦;
l Expires:指明应该在什么时候认为文档已经过期,从而不再缓存它。
l Last-Modified:文档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。Last-Modified也可用setDateHeader方法来设置;
l Location:表示客户应当到哪里去提取文档。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302;
l Refresh:表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过setHeader("Refresh", "5; URL=http://host/path")让浏览器读取指定的页面。注意这种功能通常是通过设置HTML页面HEAD区的<META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path">实现,这是因为,自动刷新或重定向对于那些不能使用CGI或Servlet的HTML编写者十分重要。但是,对于Servlet来说,直接设置Refresh头更加方便。注意Refresh的意义是"N秒之后刷新本页面或访问指定页面",而不是"每隔N秒刷新本页面或访问指定页面"。因此,连续刷新要求每次都发送一个Refresh头,而发送204状态代码则可以阻止浏览器继续刷新,不管是使用Refresh头还是<META HTTP-EQUIV="Refresh" ...>。注意Refresh头不属于HTTP 1.1正式规范的一部分,而是一个扩展,但Netscape和IE都支持它。
1.8 常用状态码
状态码
含义
100
这个状态码是告诉客户端应该继续发送请求,这个临时响应式用来通知客户端的,部分的请求服务器已经接受,但是客户端应继续发送请求的剩余部分,如果请求已经完成,就忽略这个响应,而且服务器会在请求完成后发送一个最终的结果
200
这个事最常见的http状态码,表示服务器已经成功接收请求,并将返回客户端所请求的最终结果
202
表示服务器已经接受了请求,但是还没有处理,而且这个请求最终会不会处理还不确定
204
服务器成功处理了请求,但没有返回任何实体内容,可能会返回新的头部元信息
301
客户端请求的页面已经永久移动到新的位置,当链接发送变化时,返回301代码告诉客户端链接的变化,客户端保存新的链接,并向新的链接发出请求,已返回请求结果。
404
请求失败,客户端请求的资源没有找到或者是不存在
500
服务器遇到位置的错误,导致无法完成客户端当前的请求
503
服务器由于临时的服务器过载或者是维护,无法解决当前的请求。
1.9 常用的请求方式
常用的请求方式是GET和POST
GET
对服务器资源的简单的请求
GET方法是默认的HTTP请求方法,我们日常用GET方法来提交表单数据,然而用GET方法提交的表单数据只经过了简单的编码,同时它将作为URL的一部分向Web服务器发送,因此,如果使用GET方法来提交表单数据就存在着安全隐患上。例如
Http://127.0.0.1/login.jsp?Name=zhangshi&Age=30
从上面的URL请求中,很容易就可以辨认出表单提交的内容。(?之后的内容)另外由于GET方法提交的数据是作为URL请求的一部分所以提交的数据量不能太大
POST
用于发送包含用户提交数据的请求
POST方法是GET方法的一个替代方法,它主要是向Web服务器提交表单数据,尤其是大批量的数据。POST方法克服了GET方法的一些缺点。通过POST方法提交表单数据时,数据不是作为URL请求的一部分而是作为标准数据传送给Web服务器,这就克服了GET方法中的信息无法保密和数据量太小的缺点。因此,出于安全的考虑以及对用户隐私的尊重,通常表单提交时采用POST方法。
除此之外还有
HEAD:类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
DELETE:发送一个用来删除指定文档的请求
TRACE:发送请求的一个副本,跟踪其处理进程
OPTIONS:返回所用可用的方法;可检查服务器支持那些方法
CONNECT:用于ssl隧道的基于代理的请求
2.0 java用get和post请求资源的例子
通过Socket进行GET访问
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class SimpleHttpGet {
public static final String SEQUENCE = "\r\n";
public static void main(String[] args) throws UnknownHostException,
IOException {
String host = "www.baidu.com";
Socket socket = new Socket(host, 80);
OutputStream os = socket.getOutputStream();
StringBuffer head = new StringBuffer();
// 这些是必须的
head.append("GET / HTTP/1.1" + SEQUENCE);
head.append("Host:" + host + SEQUENCE + SEQUENCE);
// 这些是可选的
head.append("Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
head.append("Accept-Language:zh-CN,zh;q=0.8");
head.append("User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) "
+ "AppleWebKit/537.11 (KHTML, like Gecko) "
+ "Chrome/23.0.1271.95 Safari/537.11");
os.write(head.toString().getBytes());
os.flush();
InputStream is = socket.getInputStream();
LineNumberReader lnr = new LineNumberReader(new InputStreamReader(is));
StringBuffer headRes = new StringBuffer();
String line = null;
int contentLength = 0;
do {
line = lnr.readLine();
headRes.append(line + SEQUENCE);
if (line.startsWith("Content-Length")) {
contentLength = Integer.parseInt(line.split(":")[1].trim());
}
// 由于LineNumberReader会把\r\n替换掉,所以如果读到一行为""证明http head结束
} while (!line.equals(""));
int totalCount = 0;
byte[] buff = new byte[256];
StringBuffer contentRes = new StringBuffer();
while (totalCount < contentLength) {
int len = is.read(buff);
totalCount += len;
contentRes.append(new String(buff, 0, len, "UTF-8"));
}
System.out.println(headRes.toString());
System.out.println(contentRes.toString());
socket.close();
}
}
响应头结果如下
HTTP/1.1 200 OK
Date: Fri, 23 Dec 2016 07:21:59 GMT
Content-Type: text/html
Content-Length: 14613
Last-Modified: Wed, 21 Dec 2016 02:18:00 GMT
Connection: Keep-Alive
Vary: Accept-Encoding
Set-Cookie: BAIDUID=ADC9F2074058DD2B4D5CC46E504B2EAE:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=ADC9F2074058DD2B4D5CC46E504B2EAE; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1482477719; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
P3P: CP=" OTI DSP COR IVA OUR IND COM "
Server: BWS/1.1
X-UA-Compatible: IE=Edge,chrome=1
Pragma: no-cache
Cache-control: no-cache
Accept-Ranges: bytes
Html内容如下
通过URLConnection进行GET和POST访问
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpRequest {
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + ":" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result +=line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
public static void main(String[] args) throws UnsupportedEncodingException {
// 发送 GET 请求
List<String> list = new ArrayList<String>();
list.add("福建省");
list.add("泉州市");
list.add("惠安县");
//list.add("黄塘镇");
String beforeEncode = "";
String matchString = "";
for (int i = 0; i < list.size(); i++) {
beforeEncode += list.get(i);
matchString += "<em>" + list.get(i) + "</em> ";
}
matchString = matchString.substring(0, matchString.length() - 1);
System.out.println(matchString);
String name = java.net.URLEncoder.encode(beforeEncode, "GB2312");
System.out.println(name);
String s = HttpRequest.sendGet("http://opendata.baidu.com/post/s",
"wd=" + name + "&rn=1");
System.out.println(s);
}
}
获得的响应头
null:[HTTP/1.1 200 OK]
Date:[Fri, 23 Dec 2016 07:47:41 GMT]
P3P:[CP=" OTI DSP COR IVA OUR IND COM "]
Content-Length:[5497]
Expires:[Fri, 23 Dec 2016 07:47:41 GMT]
Set-Cookie:[BAIDUID=C0C94ED217F6FD2026DAF86DC51765E1:FG=1; expires=Sat, 23-Dec-17 07:47:41 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1]
Connection:[Keep-Alive]
Content-Type:[text/html;charset=]
Server:[Apache]
X-Powered-By:[HPHP]
Cache-Control:[private]
Html内容如下
2.1 session和cookie
Cookie和Session都为了用来保存状态信息,都是保存客户端状态的机制,它们都是为了解决HTTP无状态的问题而所做的努力。
Session可以用Cookie来实现,也可以用URL回写的机制来实现。用Cookie来实现的Session可以认为是对Cookie更高级的应用。
两者的比较
Cookie和Session有以下明显的不同点:
-
Cookie将状态保存在客户端,Session将状态保存在服务器端;
-
Cookie是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器。Cookie最早在RFC2109中实现,后续RFC2965做了增强。网络服务器用HTTP头向客户端发送cookies,在客户终端,浏览器解析这些cookies并将它们保存为一个本地文件,它会自动将同一服务器的任何请求缚上这些cookies。Session并没有在HTTP的协议中定义。
-
Session是针对每一个用户,变量的值保存在服务器上,用一个sessionID来区分哪个用户session变量,这个值是通过用户的浏览器在访问的时候返回给服务器,当用户禁用cookie时,这个值也可能设置为get来返回给服务器;
-
就安全来说:当你访问一个使用session的站点,同时在自己机子上建立一个cookie,建议在服务器段的SESSION机制更安全些。因为它不会任意读取客户存储的信息。
Cookie
什么是Cookie
Cookie是浏览器存储在用户电脑上的一个文本文件,里面包含一些key=value格式的数据;浏览器按照一定的规范来管理和存储这些数据,并在之后的请求中将这些信息发送至服务器,服务器根据客户端传回的cookie数据进行用户识别,用户行为分析等操作。前言万语总结成一句话,cookie就是:cookie是浏览器存储在用户电脑上的一些数据;每次发起请求时,浏览器都将对应的cookie数据一起发送至服务器。
为什么要cookie
HTTP协议是无状态的,也就是说客户端和服务器端不需要建立持久的连接。由于客户端和服务器的连接是基于一种请求应答模式,及客户端和服务器建立一个连接,客户端提交一个请求,服务器端收到请求后返回一个响应,然后二者就断开连接。
现在问题的焦点就是这个用来标识每一次请求的状态位如何来设计呢?这的确是一个难题,于是上帝派出了一位青年才俊的工程师来帮程序猿解决了这个问题,他就是24岁的网景公司的moutulli,他设计出了cookie的雏形。
Cookie的工作原理
下面我通过浏览器向www.baidu.com发起请求,并简单的将cookie的工作原理图绘制出来。
还记得之前的例子吗?Java用get和post的方法来访问资源
当我首次向百度发起首次请求时,请求头如下:
当请求到达百度的服务器以后,百度的服务器需要生成响应,并会在响应的头部写入cookie信息:
Set-Cookie: BAIDUID=ADC9F2074058DD2B4D5CC46E504B2EAE:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=ADC9F2074058DD2B4D5CC46E504B2EAE; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
服务器通过发送一个带有Set-Cookie的HTTP消息响应头来创建一个cookie,并设置cookie的一些属性。
当客户端浏览器接收到响应头以后,会将cookie信息写入本地进行管理
当再次向百度服务器发起请求时,客户端会将之前写入的cookie一起发送过去,请求的头部信息为:
GET / HTTP/1.1
cache-control: no-cache
Postman-Token: 79235df2-e30e-42be-8121-db087435e74b
User-Agent: PostmanRuntime/3.0.9
Accept: */*
Host: www.baidu.com
cookie: BAIDUID=462CBE038F5ABFB84449CDCD29A9F82C:FG=1; BIDUPSID=462CBE038F5ABFB84449CDCD29A9F82C; PSTM=1482311755; H_PS_PSSID=1450_19036_21105_18133_17001_21554_21592; BDSVRTM=0; BD_HOME=0
accept-encoding: gzip, deflate
Connection: keep-alive
客户端通过发送一个带有Cookie: name=value; name2=value2的HTTP请求头来向服务器发送本地的cookie数据。
服务器接收到请求以后,从请求头中获得cookie信息,分析cookie数据,再向客户端响应。
Cookie的生命周期
Cookie是由生命的周期的,一旦到了cookie的失效的日期,客户端的cookie就会被删除。服务器在创建cookie时可以控制一个cookie可以再客户端"存活"多长时间。在以下几种情况下,cookie都都会结束它自己的生命周期:
未指定过期时间的cookie:当服务器创建一个cookie的时候没有指定对应的过期时间时,客户端会将这类coolie写入浏览器开辟的一块内存中,当关闭浏览器以后,这块内存也就被释放了,对应的cookie也就是结束了它的生命。
指定过期时间的cookie:当服务器创建一个cookie的时候指定了对应的过期时间时,当到达了过期时间时,对应的cookie就会被删除;
当浏览器中的cookie数量达到了限制时,那么浏览器就会按照某种策略删除一些旧的cookie,腾出空间来创建新的cookie。
当然了,我们也可以手动的人为的删除cookie。
浏览器不会让cookie肆意的发展,它总会在需要的时候出马,干掉一些cookie,结束它们的生命。
Cookie的管理
我每天要浏览那么多的网站,这个网站写两个cookie,那个网站也写两个cookie,那么浏览器中的cookie肯定会很多,那么浏览器如何管理这些cookie呢?这的确是一个问题。
首先,各大浏览器都对cookie的总个数和总大小都有限制
主流浏览器
IE7.0/8.0
Opera
Firefox
Safari
Chrome
cookie个数
每个域为50个
每个域为30个
每个域为50个
没有个数限制
每个域为53个
cookie大小
4095个字节
4096个字节
4097个字节
4097个字节
4097个字节
总之,在进行cookie操作的时候,应该尽量保证cookie个数小于20个,总大小小于4KB。
除了个数和大小的管理之外,每个站点各自写的cookie,浏览器又如何管理呢?我们在服务端创建一个cookie的时候,一般都会指定以下两个选项。
Domain选项
Path选线
这两个选项决定了创建的cookie属于哪个域名下的哪个位置。对于domain选项,默认情况下,domain会被设置为创建该cookie的页面所在的域名,所以当给相同域名发送请求时,该cookie会一起被发送至服务器。比如百度这样的大站,有很多的二级域名,例如:http://music.baidu.com/、http://picture.baidu.com/等,如果需要在所有的二级域名下都记录一个cookie,则需要在创建cookie的时候,将cookie的domain选项设置为baidu.com;此时域名为baidu.com下的所有二级域名都将拥有同样的一个cookie。创建域名时指定domain,这又是一个难点,经常会出现顶级域名和二级域名的cookie冲突问题。
我们在发送请求时,浏览器会把domain的值与请求的域名做一个尾部比较(即从字符串的尾部开始比较),并将匹配的cookie发送至服务器。所以以后在设计哪些数据需要写入cookie的时候,也要考虑清楚这个域名的问题。对于此,基本上总结为以下三点:
当我们未指定domain时,默认的domain为用哪个域名访问就是哪个。如果是顶级域名访问,那么设置的cookie也可以被其他二级域名所共享,因此登录等操作一般都在顶级域名下进行操作。
二级域名可以读取设置了domain为顶级域名或者自身的cookie,但是不能读取其他二级域名domain的cookie,因此想要cookie在多个二级域名中共享的时候,需要设置domain为顶级域名,这样就可以在所有二级域名里面使用该cookie,这里需要注意的是顶级域名只能获取到domain设置为顶级域名的cookie,无法获取domain设置为二级域名的cookie。
顶级域名的cookie在顶级域名或者二级域名都可以删除,但是非顶级域名访问的网站要删除顶级域名的cookie的时候,必须要设置获取到的cookie的domain为顶级域名,这样才能删除掉顶级域名的cookie,否则会无法删除。这里默认是删除访问的域名下对应的cookie,而不是顶级域名的那个。
举个例子来说:
我通过浏览器访问http://www.jellythink.com,请求到达服务器以后,我创建cookie时,指定的domain只能是www.jellythink.com、.jellythink.com或者jellythink.com
而当我通过浏览器访问http://picture.jellythink.com,请求到达服务器以后,我创建cookie时,指定的domain只能是picture.jellythink.com、.jellythink.com或者jellythink.com
说完domain,再来说说path选项。 在创建cookie的时候,也可以指定一个path值,path选项指定了请求的资源URL中只有在存在指定的路径时,才会发送Cookie消息头,它决定了客户端发送cookie到服务器端的匹配规则。通常是将path选项的值与请求的URL从头开始逐字符比较,如果字符匹配,则发送Cookie消息头。需要注意的是,只有在domain选项满足之后才会对path属性进行比较。path属性的默认值是发送Set-Cookie消息头所对应的URL中的path部分。
以上从浏览器本身的限制和生成cookie时的选项对cookie的管理进行了简单的总结。接下来就通过一些简单的代码来演示如何创建和获取cookie。
创建cookie和读取cookie
服务器通过发送一个带有set-cookie的HTTP消息响应头来创建一个cookie。例如:
// 创建一个cookie对象Cookie co = new Cookie("site", "http://www.jellythink.com");
co.setDomain("test.com");
// 通过响应头,将cookie发送到客户端
response.addCookie(co);
创建cookie本身没有多少难点,但是在创建创建cookie的时候,我们需要明白几个选项,以及这些选项的具体作用。
名称
作用
domain选项
请参见【cookie的管理】这一小节
path选项
请参见【cookie的管理】这一小节
maxage选项
设置浏览器何时删除cookie
secure选项
Secure字段告诉浏览器在https通道时,对Cookie进行安全加密,这样即时有黑客监听也无法获取cookie内容;默认情况下,在HTTPS连接上传输的cookie都会被自动添加上secure选项。当secure值为true时,cookie在HTTP中是无效,在HTTPS中才有效
httponly选项
HttpOnly字段告诉浏览器,只有在HTTP协议下使用,对浏览器的脚本不可见,所以跨站脚本攻击时也不会被窃取,此时JS则无法访问带有httponly的cookie
示例代码:
Cookie co = new Cookie("site", "http://www.jellythink.com");
co.setDomain("test.com");
co.setPath("/pages");
co.setMaxAge(3600); // 单位为秒
co.setHttpOnly(true);
co.setSecure(false);
response.addCookie(co);
读取cookie
客户端向服务器发起请求时,在domain和path匹配的情况下,会将对应的cookie一起发送到服务器端。所以,如果一个path下设置的cookie太多,就可能出现http请求头超长的问题。
请求到达服务器端以后,我们可以这样读取cookies。
Cookie[] cookies = request.getCookies();if (cookies != null) {
for (int i = 0; i < cookies.length; ++i) {
// 获得具体的Cookie
Cookie cookie = cookies[i];
// 获得Cookie的名称
String name = cookie.getName();
String value = cookie.getValue();
out.print("Cookie名:" + name + " Cookie值:" + value + "<br>");
}}
Session
什么是session
我们每天都在不经意和session打交道,对于普通的用户来说,他们可以不关心session这个东西;但是对于一个合格的程序员来说,你不得不懂session。
Session是什么?直译就是会话的意思;但是在Web开发总,session代表浏览器与服务器的一次会话过程,这个过程是连续的,也可以时断时续的;通常指从进入系统到退出系统之间经历的时间。
为什么说我们每天都在和session打交道呢?当你打开浏览器,输入www.baidu.com网址,发送请求时,服务器向你返回响应内容,此时就表示服务器已经为你创建了一个会话;后续再访问www.baidu.com上的其他内容时,都是基于这同一个会话进行。当你关闭浏览器之后,表示你已经退出系统,本次会话也就结束了。
为什么要session?
我们每天都在和session打交道,那么为什么要用session呢?由于web所使用的HTTP协议是无状态的,每一次请求之间都是彼此独立的。这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器没有必要记录彼此过去的行文。但是,有的时候,我们需要在两个彼此独立的请求之间共享一些状态,例如我通过淘宝首页发起登陆请求,成功登陆之后;我再对某个商品的详细页面发起请求,那么问题来了,我如何在商品的详细页面判断我是否登陆了淘宝呢?
虽然登录请求和商品详情页请求是两个彼此独立的请求,但是这两个请求之间需要共享我的登录信息,从而确定我是否已经正确登录,才能在详情页将商品正确的加入到我的购物车中。
综上所述,session就是一种用来在客户端与服务器端之间保持状态的解决方案。每一个不同的用户连接系统将得到不同的Session,也就是说session与用户之间是一种一对一的关系。Session在用户进入网站时由服务器自动产生,并在用户正常离开站点时释放。使用session的好处就在于,可以将很多与用户相关的信息,例如用户的帐号、昵称等保存到session中;利用session,可以跟踪用户在网站上的活动。最重要的是,你在购物时,你中意的东西不会加入到别人的购物车中。
Cookie和session的关系
Cookie机制采用的是在客户端保持状态的方案;而session机制采用的是在服务端保持状态的方案。Cookie的作用主要是为了解决HTTP协议无状态的问题,而Session机制则是一种在客户端与服务器之间保持状态的解决方案。
Cookie使用起来很方便,使用cookie也能完成session的工作;但是使用cookie有一个很大的弊端,cookie中的所有数据在客户端可以被修改该,数据非常容易被伪造,那么一些重要的数据就不能存放在cookie中了,而且如果cookie中数据字段太多会影响传输效率。为了解决这些问题,我们就需要使用session,session中的数据时保留在服务端的,相对来说更安全,同时也没有客户端到服务端这个传输问题。
当用户访问一个服务器,服务器就要为改用户创建一个session,在创建这个session的时候,服务器首先检查这个用户发发来的请求里是否包含了一个sessionID,如果包含了一个sessionID则说明之前该拥护已经登录过并为此拥用户创建过session,那服务器就按照这个sessionID从内存中找到对应的session;如果客户端请求里不包含有sessionID,则为该客户端创建一个session并生成一个于此session相关的sessionID。这个sessionID是唯一的,不重复的,不容易找到规律的字符串,这个sessionID将在本次响应放回到客户端保存,而保存这个sessionID的正是cookie,这样在交互过程中浏览器就可以自动的按照规则把这个标识发送给服务器。
从上面可以看出,没有了session,cookie依然可以玩的好好的;但是如果没有cookie的支持,session就不能愉快的玩耍了;session需要一种能够在客户端和服务器之间进行交互时,传递SessionID的机制,我们一般都是使用cookie来完成传递SessionID的任务,但是还有一种叫做URL重写的东西,也能完成这个任务。
2.2 Web缓存
什么是Web缓存
WEB缓存(cache)位于Web服务器和客户端之间。缓存会根据请求保存输出内容的副本,例如html页面,图片,文件,当下一个请求来到的时候:如果是相同的URL,缓存直接使用副本响应访问请求,而不是向源服务器再次发送请求。
HTTP协议定义了相关的消息头来使WEB缓存尽可能好的工作。
缓存的优点
减少相应延迟:因为请求从缓存服务器(离客户端更近)而不是源服务器被相应,这个过程耗时更少,让web服务器看上去相应更快。
减少网络带宽消耗:当副本被重用时会减低客户端的带宽消耗;客户可以节省带宽费用,控制带宽的需求的增长并更易于管理。
与缓存相关的HTTP扩展消息头
Expires:指示响应内容过期的时间,格林威治时间GMT
Cache-Control:更细致的控制缓存的内容
Last-Modified:响应中资源最后一次修改的时间
ETag:响应中资源的校验值,在服务器上某个时段是唯一标识的。
Date:服务器的时间
If-Modified-Since:客户端存取的该资源最后一次修改的时间,同Last-Modified。
If-None-Match:客户端存取的该资源的检验值,同ETag。
客户端缓存生效的常见流程
服务器收到请求时,会在200OK中回送该资源的Last-Modified和ETag头,客户端将该资源保存在cache中,并记录这两个属性。当客户端需要发送相同的请求时,会在请求中携带If-Modified-Since和If-None-Match两个头。两个头的值分别是响应中Last-Modified和ETag头的值。服务器通过这两个头判断本地资源未发生变化,客户端不需要重新下载,返回304响应。常见流程如下图所示:
Web缓存机制
HTTP/1.1中缓存的目的是为了在很多情况下减少发送请求,同时在许多情况下可以不需要发送完整响应。前者减少了网络回路的数量;HTTP利用一个"过期(expiration)"机制来为此目的。后者减少了网络应用的带宽;HTTP用"验证(validation)"机制来为此目的。
HTTP定义了3种缓存机制:
1)Freshness:允许一个回应消息可以在源服务器不被重新检查,并且可以由服务器和客户端来控制。例如,Expires回应头给了一个文档不可用的时间。Cache-Control中的max-age标识指明了缓存的最长时间;
2)Validation:用来检查以一个缓存的回应是否仍然可用。例如,如果一个回应有一个Last-Modified回应头,缓存能够使用If-Modified-Since来判断是否已改变,以便判断根据情况发送请求;
3)Invalidation: 在另一个请求通过缓存的时候,常常有一个副作用。例如,如果一个URL关联到一个缓存回应,但是其后跟着POST、PUT和DELETE的请求的话,缓存就会过期。
2.3 断点续传和多线程下载的实现原理
HTTP协议的GET方法,支持只请求某个资源的某一部分;
206 Partial Content 部分内容响应;
Range 请求的资源范围;
Content-Range 响应的资源范围;
在连接断开重连时,客户端只请求该资源未下载的部分,而不是重新请求整个资源,来实现断点续传。
分块请求资源实例:
Eg1:Range: bytes=306302- :请求这个资源从306302个字节到末尾的部分;
Eg2:Content-Range: bytes 306302-604047/604048:响应中指示携带的是该资源的第306302-604047的字节,该资源共604048个字节;
客户端通过并发的请求相同资源的不同片段,来实现对某个资源的并发分块下载。从而达到快速下载的目的。目前流行的FlashGet和迅雷基本都是这个原理。
多线程下载的原理:
下载工具开启多个发出HTTP请求的线程;
每个http请求只请求资源文件的一部分:Content-Range: bytes 20000-40000/47000;
合并每个线程下载的文件。
2.4 https通信过程
什么是https
HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容请看SSL。
https所用的端口号是443。
https的实现原理
有两种基本的加解密算法类型:
1)对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有DES、AES等;
2)非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的非对称加密算法有RSA、DSA等。
下面看一下https的通信过程:
https通信的优点:
1)客户端产生的密钥只有客户端和服务器端能得到;
2)加密的数据只有客户端和服务器端才能得到明文;
3)客户端到服务端的通信是安全的。
2.5 http代理
http代理服务器
代理服务器英文全称是Proxy Server,其功能就是代理网络用户去取得网络信息。形象的说:它是网络信息的中转站。
代理服务器是介于浏览器和Web服务器之间的一台服务器,有了它之后,浏览器不是直接到Web服务器去取回网页而是向代理服务器发出请求,Request信号会先送到代理服务器,由代理服务器来取回浏览器所需要的信息并传送给你的浏览器。
而且,大部分代理服务器都具有缓冲的功能,就好象一个大的Cache,它有很大的存储空间,它不断将新取得数据储存到它本机的存储器上,如果浏览器所请求的数据在它本机的存储器上已经存在而且是最新的,那么它就不重新从Web服务器取数据,而直接将存储器上的数据传送给用户的浏览器,这样就能显著提高浏览速度和效率。
更重要的是:Proxy Server(代理服务器)是Internet链路级网关所提供的一种重要的安全功能,它的工作主要在开放系统互联(OSI)模型的对话层。
http代理服务器的主要功能
主要功能如下:
1)突破自身IP访问限制,访问国外站点。如:教育网、169网等网络用户可以通过代理访问国外网站;
2)访问一些单位或团体内部资源,如某大学FTP(前提是该代理地址在该资源的允许访问范围之内),使用教育网内地址段免费代理服务器,就可以用于对教育 网开放的各类FTP下载上传,以及各类资料查询共享等服务;
3)突破中国电信的IP封锁:中国电信用户有很多网站是被限制访问的,这种限制是人为的,不同Serve对地址的封锁是不同的。所以不能访问时可以换一个国 外的代理服务器试试;
4)提高访问速度:通常代理服务器都设置一个较大的硬盘缓冲区,当有外界的信息通过时,同时也将其保存到缓冲区中,当其他用户再访问相同的信息时, 则直接由缓冲区中取出信息,传给用户,以提高访问速度;
5)隐藏真实IP:上网者也可以通过这种方法隐藏自己的IP,免受攻击。
http代理图示
http代理的图示见下图:
对于客户端浏览器而言,http代理服务器相当于服务器。
而对于Web服务器而言,http代理服务器又担当了客户端的角色。
2.6 虚拟主机的实现
什么是虚拟主机
虚拟主机:是在网络服务器上划分出一定的磁盘空间供用户放置站点、应用组件等,提供必要的站点功能与数据存放、传输功能。
所谓虚拟主机,也叫"网站空间"就是把一台运行在互联网上的服务器划分成多个"虚拟"的服务器,每一个虚拟主机都具有独立的域名和完整的Internet服务器(支持WWW、FTP、E-mail等)功能。一台服务器上的不同虚拟主机是各自独立的,并由用户自行管理。但一台服务器主机只能够支持一定数量的虚拟主机,当超过这个数量时,用户将会感到性能急剧下降。
虚拟主机的实现原理
虚拟主机是用同一个WEB服务器,为不同域名网站提供服务的技术。Apache、Tomcat等均可通过配置实现这个功能。
相关的HTTP消息头:Host。
例如:Host: www.baidu.com
客户端发送HTTP请求的时候,会携带Host头,Host头记录的是客户端输入的域名。这样服务器可以根据Host头确认客户要访问的是哪一个域名。