Hello Coder


  • 首页

  • 归档

  • 标签

  • 搜索
close

Comparable 和 Comparator比较

发表于 2016-09-20

有两种方式可以进行集合排序 :

  • 集合中对象的所属类实现了 java.lang.Comparable 接口
  • 给集合指定比较器 java.lang.Comparator 的实现类

1. java.lang.Comparable

public interface Comparable 接口强行对实现它的每个类的对象进行整体排序。 – 自然排序。类的compareTo称为自然比较方法。

接口的作用

若一个类实现了Comparable 接口,实现 Comparable 接口的类对象的 List 列表 ( 或数组)可以通过 Collections.sort(或 Arrays.sort)进行排序。

此外,实现 Comparable 接口的类的对象 可以用作 “有序映射 ( 如 TreeMap)” 中的键或 “有序集合 (TreeSet)” 中的元素,而不需要指定比较器。

实现的方式

利用Comparable接口创建自己的类的排序顺序,只是实现comparaTo方法的问题。
通常就是依赖几个数据成员的自然排序。同时类也应该覆盖equals()和hashCode() 以确保两个相等的对象返回同一个哈希码。

Comparable接口只有一个方法,compareTo(Object obj),定义如下:

1
2
3
public interface Comparable<T> {
public int compareTo(T o);
}

通常需要覆写 compareTo 方法实现排序规则的应用 :int compareTo(Object o): 比较当前实例对象与对象 o ,如果位于对象 o 之前,返回负值,如果两个对象在排序中位置相同,则返回 0 ,如果位于对象 o 后面,则返回正值.

下表展示了几种基本类型的自然排序。虽然一些类共享同一种自然排序,但只有相互可比的类才能排序。

类 排序(越大排在后面)
Integer 按数字大小排序
Long 按数字大小排序
Byte 按数字大小排序
Double 按数字大小排序
Character 按 Unicode 值的数字大小排序
String 按字符串中字符 Unicode 值排序

举个例子:一种商品(商品名,销售量,生产日期),根据生产日期降序 销售量升序 商品名称降序

思路:首先按照日期降序,如果日期相同 按照销售量升序,如果销售量相同,按周商品的名称降序

  • 创建需要比较的对象的java bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class ComparableExample implements Comparable<ComparableExample> {
private String title; // 名称
private int hits; // 销售量
private Date pubTime; // 日期
public ComparableExample() {
}
public ComparableExample(String title, int hits, Date pubTime) {
super();
this.title = title;
this.hits = hits;
this.pubTime = pubTime;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getHits() {
return hits;
}
public void setHits(int hits) {
this.hits = hits;
}
public Date getPubTime() {
return pubTime;
}
public void setPubTime(Date pubTime) {
this.pubTime = pubTime;
}
// 时间降序(越大排在前面) 点击量升序(越大排在后面) 标题降序
@Override
public int compareTo(ComparableExample o) {
int result = 0;
// 按照生产时间降序
result = -this.pubTime.compareTo(o.pubTime);
if (0 == result) {// 如果生产时间相同 就按照销售量升序排列
result = this.hits - o.hits;
if (0 == result) {// 如果销售量相同 按照名字降序排列
result = -this.title.compareTo(o.title);
}
}
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("商品名称").append(this.title);
sb.append("销售量").append(this.hits);
sb.append("生产时间").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.pubTime)).append("\n");
return sb.toString();
}
}
  • 测试,比较
1
2
3
4
5
6
7
8
9
10
11
12
13
// 时间降序, 销售量升序, 标题降序
public static void main(String[] args) {
List<ComparableExample> item = new ArrayList<ComparableExample>();
item.add(new ComparableExample("abcitems", 30, new Date(System.currentTimeMillis() - 1000 * 60 * 60)));
item.add(new ComparableExample("abcfgitems", 30, new Date(System.currentTimeMillis() - 1000 * 60 * 50)));
item.add(new ComparableExample("abcditems", 100, new Date()));
item.add(new ComparableExample("abefNews", 50, new Date(System.currentTimeMillis() - 1000 * 60 * 60)));
System.out.println("----------排序前----------");
System.out.println(item);
System.out.println("----------排序后----------");
Collections.sort(item);
System.out.println(item);
}
  • 运行结果
1
2
3
4
5
6
7
8
9
10
11
12
----------排序前----------
[商品名称abcitems销售量30生产时间2016-09-20 15:04:37
, 商品名称abcfgitems销售量30生产时间2016-09-20 15:14:37
, 商品名称abcditems销售量100生产时间2016-09-20 16:04:37
, 商品名称abefNews销售量50生产时间2016-09-20 15:04:37
]
----------排序后----------
[商品名称abcditems销售量100生产时间2016-09-20 16:04:37
, 商品名称abcfgitems销售量30生产时间2016-09-20 15:14:37
, 商品名称abcitems销售量30生产时间2016-09-20 15:04:37
, 商品名称abefNews销售量50生产时间2016-09-20 15:04:37
]

2. Comparator 比较器接口

如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口);那么可以建立一个该类的比较器来排序,这个比较器只需要实现Comparator接口即可。 换句话说, 通过实现Comparator类来新建一个比较器,然后通过该比较器来对类进行排序。Comparator 接口其实就是一种策略模式的实践

接口作用

  1. 如果一个类已经开放完成,但是在此类建立的初期并没有实现 Comparable 接口,此时肯定是无法进行对象排序操作的,所以为了解决这一的问题,java 又定义了另一个比较器的操作接口 Comparator 此接口定义在 java.util 包中
  2. 为了使用不同的排序标准做准备,比如升序,降序或者其他什么序列

接口仅仅包括两个函数:

1
2
3
4
5
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
  1. 若一个类要实现Comparator接口:它一定要实现compare(T o1, T o2)函数,但是可以不实现equals函数。 因为任何类,默认都是已经实现了 equals(Object obj) 。 Java 中的一切类都是继承于 java.lang.Object,在 Object.java 中实现了 equals(Object obj) 函数;所以,其它所有的类也相当于都实现了该函数。
  2. int compare(T o1, T o2) 是 “比较 o1 和 o2 的大小”。返回 “负数”,意味着 “o1 比 o2 小”;返回 “零”,意味着 “o1 等于 o2”;返回 “正数”,意味着 “o1 大于 o2”。

比如 商品,我需要按照价格的降序排列,代码如下:

  • 商品类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.zsr.test.compare;
public class Product {
private String title;
private int price;
public Product() {
}
public Product(String title, int price) {
super();
this.title = title;
this.price = price;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "title=" + title + ",price=" + price + "\n";
}
}
  • 定义比较规则:
1
2
3
4
5
6
7
8
9
10
11
package com.zsr.test.compare;
import java.util.Comparator;
public class ProductCompare implements Comparator<Product> {
@Override
public int compare(Product o1, Product o2) {
return -(o1.getPrice() - o2.getPrice() > 0 ? 1 : (o1.getPrice() == o2.getPrice() ? 0 : -1));
}
}
  • 测试, 比较
1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
List<Product> product = new ArrayList<Product>();
product.add(new Product("a", 120));
product.add(new Product("b", 143432));
product.add(new Product("c", 1892));
product.add(new Product("d", 11092));
Collections.sort(product, new ProductCompare());
System.out.println(product);
}
  • 运行结果
1
2
3
4
5
[title=b,price=143432
, title=d,price=11092
, title=c,price=1892
, title=a,price=120
]

3. Comparable 和 Comparator比较

Comparable 是排序接口;若一个类实现了 Comparable 接口,就意味着 “该类支持排序”。 而 Comparator 是比较器;我们若需要控制某个类的次序,可以建立一个 “该类的比较器” 来进行排序。

前者应该比较固定,和一个具体类相绑定,而后者比较灵活,它可以被用于各个需要比较功能的类使用。可以说前者属于 “静态绑定”,而后者可以 “动态绑定”。

mvc:annotation-driven

发表于 2016-09-13

<mvc:annotation-driven />这个标签注册了Spring MVC分发请求到Controller控制器所必须的HandlerMapping和HandlerAdapter实例。

Spring 3.1 之前:

  1. DefaultAnnotationHandlerMapping
  2. AnnotationMethodHandlerAdapter
  3. AnnotationMethodHandlerExceptionResolver

Spring 3.1+ :

  1. RequestMappingHandlerMapping
  2. RequestMappingHandlerAdapter
  3. ExceptionHandlerExceptionResolver

DefaultAnnotationHandlerMapping 根据url决定使用哪个controller,AnnotationMethodHandlerAdapter 选择处理请求的实际方法。RequestMappingHandlerMapping包涵上面2个作用,所以将请求直接映射到方法。

阅读全文 »

Spring MVC 适配器模式

发表于 2016-09-13
找到DispatcherServlet类中的`doDispatch`体,我们可以看到,它的作用是相当于在Servlet的 doService调用的。 也就是用来传递request给我们编写的Controller并执行相应的方法、返回ModeView对象。 

 执行的代码片段: 
1
2
3
4
...
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...

mappedHandler.getHandler()得到的是Controller对象
而此处并非采用直接 调用.handlerRequest或者MultiActionController中编写的自定义方法,而采用了一个HandlerAdapter的接口。

此处采用了适配器模式, 由于Controller的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要在代码中写成如下形式:

1
2
3
4
5
6
7
8
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
...

这样假设如果我们增加一个HardController,就要在代码中加入一行 if(mappedHandler.getHandler() instanceof HardController) , 这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。

因此Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类, 让适配器代替controller执行相应的方法。这样在扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了.

​ 实现一套代码来模拟springMVC:

1
2
3
4
5
//定义一个Adapter接口
public interface HandlerAdapter {
public boolean supports(Object handler);
public void handle(Object handler);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//以下是三种Controller实现
public interface Controller {
}
public class HttpController implements Controller{
public void doHttpHandler(){
System.out.println("http...");
}
}
public class SimpleController implements Controller{
public void doSimplerHandler(){
System.out.println("simple...");
}
}
public class AnnotationController implements Controller{
public void doAnnotationHandler(){
System.out.println("annotation...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//下面编写适配器类
public class SimpleHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((SimpleController)handler).doSimplerHandler();
}
public boolean supports(Object handler) {
return (handler instanceof SimpleController);
}
}
public class HttpHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((HttpController)handler).doHttpHandler();
}
public boolean supports(Object handler) {
return (handler instanceof HttpController);
}
}
public class AnnotationHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((AnnotationController)handler).doAnnotationHandler();
}
public boolean supports(Object handler) {
return (handler instanceof AnnotationController);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//模拟一个DispatcherServlet
import java.util.ArrayList;
import java.util.List;
public class DispatchServlet {
public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();
public DispatchServlet(){
handlerAdapters.add(new AnnotationHandlerAdapter());
handlerAdapters.add(new HttpHandlerAdapter());
handlerAdapters.add(new SimpleHandlerAdapter());
}
public void doDispatch(){
//此处模拟SpringMVC从request取handler的对象,仅仅new出.
//不论实现何种Controller,适配器总能经过适配以后得到想要的结果
// HttpController controller = new HttpController();
// AnnotationController controller = new AnnotationController();
SimpleController controller = new SimpleController();
//得到对应适配器
HandlerAdapter adapter = getHandler(controller);
//通过适配器执行对应的controller对应方法
adapter.handle(controller);
}
public HandlerAdapter getHandler(Controller controller){
for(HandlerAdapter adapter: this.handlerAdapters){
if(adapter.supports(controller)){
return adapter;
}
}
return null;
}
public static void main(String[] args){
new DispatchServlet().doDispatch();
}
}

Spring MVC HandlerAdapter ,有下面几个子类:

img

这几个子类,分别用于适配不同的Handler(我们写的请求处理代码)。

如果Handler是一个简单的Servlet(这是在web.xml配置自定义的Servlet时的方式),那么就使用SimpleServletHandlerAdapter

1
2
3
4
5
6
7
8
9
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((Servlet) handler).service(request, response);
return null ;
}

如果Handler是一个简单的Controller的实例,就是用SimpleControllerHandlerAdapter来适配(在Spring2.5时,一般会采用这种方式):

1
2
3
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return ((Controller) handler).handleRequest(request, response);
}

如果使用了注解方式,就使用AnnotationMethodHandlerAdapter.

从上面的可以采用的几种处理HttpRequest的写法上来看,这几种Handler分别属于不同的类,也就是处理的接口是不同的。然而在DispatcherServlet中,只用了一个接口,采用了适配器模式,来屏蔽掉这种差异,在适配器的内部,进行接口的转换工作。

HTTP协议详解

发表于 2016-09-11

Http:

  • 全称:超文本传输协议(HyperText Transfer Protocol)
  • 作用:设计之初是为了将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器。现在http的作用已不局限于HTML的传输。
  • 版本:http/1.0 http/1.1* http/2.0

URL详解

一个示例URL

1
2
3
4
5
6
7
8
http://www.mywebsite.com/sj/test;id=8079?name=sviergn&x=true#stuff
Schema: http
host: www.mywebsite.com
path: /sj/test
URL params: id=8079
Query String: name=sviergn&x=true
Anchor: stuff
  • scheme:指定低层使用的协议(例如:http, https, ftp)
  • host:HTTP服务器的IP地址或者域名
  • port#:HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明,例如http://www.mywebsite.com:8080/
  • path:访问资源的路径
  • url-params
  • query-string:发送给http服务器的数据
  • anchor:锚

无状态的协议

http协议是无状态的:

同一个客户端的这次请求和上次请求是没有对应关系,对http服务器来说,它并不知道这两个请求来自同一个客户端。

解决方法:Cookie机制来维护状态

既然Http协议是无状态的,那么Connection:keep-alive 又是怎样一回事?

无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。从另一方面讲,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。

http消息结构

  1. Request 消息的结构:三部分

    第一部分叫Request line(请求行), 第二部分叫http header, 第三部分是body

    • 请求行:包括http请求的种类,请求资源的路径,http协议版本

    • http header:http头部信息

    • body:发送给服务器的query信息
      当使用的是”GET“ 方法的时候,body是为空的(GET只能读取服务器上的信息,post能写入)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      > GET /hope/ HTTP/1.1 //---请求行
      > Host: ce.sysu.edu.cn
      > Accept: */*
      > Accept-Encoding: gzip, deflate, sdch
      > Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.6
      > Cache-Control: max-age=0
      > Cookie:.........
      > Referer: http://ce.sysu.edu.cn/hope/
      > User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36
      > ---分割线---
      > POST /hope/ HTTP/1.1 //---请求行
      > Host: ce.sysu.edu.cn
      > Accept: */*
      > Accept-Encoding: gzip, deflate, sdch
      > Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.6
      > Cache-Control: max-age=0
      > Cookie:.........
      > Referer: http://ce.sysu.edu.cn/hope/
      > User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36
      > ...body...
      >

    >

    ​

  2. Response消息的结构

    也分为三部分,第一部分叫request line, 第二部分叫request header,第三部分是body

    • request line:协议版本、状态码、message

    • request header:request头信息

    • body:返回的请求资源主体

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      > HTTP/1.1 200 OK
      > Accept-Ranges: bytes
      > Content-Encoding: gzip
      > Content-Length: 4533
      > Content-Type: text/html
      > Date: Sun, 06 Sep 2015 07:56:07 GMT
      > ETag: "2788e6e716e7d01:0"
      > Last-Modified: Fri, 04 Sep 2015 13:37:55 GMT
      > Server: Microsoft-IIS/7.5
      > Vary: Accept-Encoding
      > X-Powered-By: ASP.NET
      > <!DOCTYPE html>
      > ...
      > <>
      > ...
      >

get 和 post 区别

http协议定义了很多与服务器交互的方法,最基本的有4种,分别是GET,POST,PUT,DELETE。 一个URL地址用于描述一个网络上的资源,而HTTP中的GET, POST, PUT, DELETE 就对应着对这个资源的查,改,增,删4个操作。 我们最常见的就是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请求的Get与Post

状态码

Response 消息中的第一行叫做状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。

状态码用来告诉HTTP客户端,HTTP服务器是否产生了预期的Response.

HTTP/1.1中定义了 5 类状态码。

状态码由三位数字组成,第一个数字定义了响应的类别

  • 1XX 提示信息 - 表示请求已被成功接收,继续处理
  • 2XX 成功 - 表示请求已被成功接收,理解,接受
  • 3XX 重定向 - 要完成请求必须进行更进一步的处理
  • 4XX 客户端错误 - 请求有语法错误或请求无法实现
  • 5XX 服务器端错误 - 服务器未能实现合法的请求
    1. 200 OK
      请求被成功地完成,所请求的资源发送回客户端
    2. 302 Found
      重定向,新的URL会在response中的Location中返回,浏览器将会使用新的URL发出新的Request
    3. 304 Not Modified
      文档已经被缓存,直接从缓存调用
    4. 400 Bad Request
      客户端请求与语法错误,不能被服务器所理解
      403 Forbidden
      服务器收到请求,但是拒绝提供服务
      404 Not Found
      请求资源不存在
    5. 500 Internal Server Error
      服务器发生了不可预期的错误
      503 Server Unavailable
      服务器当前不能处理客户端的请求,一段时间后可能恢复正常

http reauest header

http 请求头包括很多键值对,这些键值对有什么意义与作用?如何根据功能为他们分一下组呢?

  1. cache 头域

    • If-Modified-Since
      用法:If-Modified-Since: Thu, 09 Feb 2012 09:07:57 GMT

      把浏览器端缓存页面的最后修改时间发送到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行对比。如果时间一致,那么返回304,客户端就直接使用本地缓存文件。如果时间不一致,就会返回200和新的文件内容。客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示在浏览器中。

    • If-None-Match
      用法:If-None-Match: "03f2b33c0bfcc1:0"

      If-None-Match和ETag一起工作,工作原理是在HTTP Response中添加ETag信息。 当用户再次请求该资源时,将在HTTP Request 中加入If-None-Match信息(ETag的值)。如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个304状态告诉客户端使用本地缓存文件。否则将返回200状态和新的资源和Etag. 使用这样的机制将提高网站的性能

    • Pragma:Pragma: no-cache
      Pargma只有一个用法, 例如: Pragma: no-cache

      作用: 防止页面被缓存, 在HTTP/1.1版本中,它和Cache-Control:no-cache作用一模一样

    • Cache-Control
      用法:

      • Cache-Control:Public 可以被任何缓存所缓存()
      • Cache-Control:Private 内容只缓存到私有缓存中
      • Cache-Control:no-cache 所有内容都不会被缓存

      作用:用来指定Response-Request遵循的缓存机制

  2. Client 头域

    • Accept
      用法:Accept: */*,Accept: text/html

      作用: 浏览器端可以接受的媒体类型;
      Accept: */* 代表浏览器可以处理所有回发的类型,(一般浏览器发给服务器都是发这个)
      Accept: text/html 代表浏览器可以接受服务器回发的类型为 text/html ;如果服务器无法返回text/html类型的数据,服务器应该返回一个406错误(non acceptable)

    • Accept-Encoding
      用法:Accept-Encoding: gzip, deflate

      作用: 浏览器申明自己接收的文件编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是指字符编码)

    • Accept-Language
      用法:Accept-Language: en-us

      作用: 浏览器申明自己接收的语言。
      语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等;

    • User-Agent
      用法: User-Agent: Mozilla/4.0......

      作用:告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本.

    • Accept-Charset
      用法:Accept-Charset:utf-8

      作用:浏览器申明自己接收的字符集,这就是本文前面介绍的各种字符集和字符编码,如gb2312,utf-8(通常我们说Charset包括了相应的字符编码方案)

  3. Cookie/Login 头域

    • Cookie

      1
      Cookie: bdshare_firstime=1439081296143; ASP.NET_SessionId=rcqayd4ufldcke0wkbm1vhxb; pgv_pvi=7361416192; pgv_si=s6686106624; ce.sysu.edu.cn80.ASPXAUTH=9E099592DD5A414BEECD8CF43CFC71664

      作用: 最重要的header, 将cookie的值发送给HTTP 服务器

  4. Entity 头域

    • Content-Length
      用法:Content-Length: 38

      作用:发送给HTTP服务器数据的长度。

    • Content-Type
      用法:Content-Type: application/x-www-form-urlencoded

      不常出现,一般出现在response头部,用于指定数据文件类型

  5. Miscellaneous 头域

    • Referer
      用法:Referer: http://ce.sysu.edu.cn/hope/

      作用:提供了Request的上下文信息的服务器,告诉服务器我是从哪个链接过来的,比如从我主页上链接到一个朋友那里,他的服务器就能够从HTTP Referer中统计出每天有多少用户点击我主页上的链接访问他的网站。

  6. Transport 头域

    • Connection
      Connection: keep-alive: 当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接

      Connection: close: 代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接

    • Host
      用法:Host: ce.sysu.edu.cn

      作用: 请求报头域主要用于指定被请求资源的Internet主机和端口号(默认80),它通常从HTTP URL中提取出来的

HTTP Response header

  1. Cache 头域

    • Date
      用法:Date: Sat, 11 Feb 2012 11:35:14 GMT

      作用: 生成消息的具体时间和日期

    • Expires
      用法:Expires: Tue, 08 Feb 2022 11:35:14 GMT
      作用: 浏览器会在指定过期时间内使用本地缓存

    • Vary
      用法:Vary: Accept-Encoding

  2. Cookie/Login 头域

    • P3P
      用法:
      P3P: CP=CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR

      作用: 用于跨域设置Cookie, 这样可以解决iframe跨域访问cookie的问题

    • Set-Cookie
      用法:
      Set-Cookie: sc=4c31523a; path=/; domain=.acookie.taobao.com

      作用:非常重要的header, 用于把cookie 发送到客户端浏览器, 每一个写入cookie都会生成一个Set-Cookie.

  3. Entity 头域

    • ETag
      用法:ETag: "03f2b33c0bfcc1:0"

      作用: 和request header的If-None-Match 配合使用

    • Last-Modified
      用法:Last-Modified: Wed, 21 Dec 2011 09:09:10 GMT

      作用:用于指示资源的最后修改日期和时间。(实例请看上节的If-Modified-Since的实例)

    • Content-Type
      用法:

      • Content-Type: text/html; charset=utf-8
      • Content-Type:text/html;charset=GB2312
      • Content-Type: image/jpeg

      作用:WEB服务器告诉浏览器自己响应的对象的类型和字符集

    • Content-Encoding
      用法:Content-Encoding:gzip

      作用:WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。

    • Content-Language
      用法: Content-Language:da

      WEB服务器告诉浏览器自己响应的对象的语言

  4. Miscellaneous 头域

    • Server
      用法:Server: Microsoft-IIS/7.5

      作用:指明HTTP服务器的软件信息

    • X-AspNet-Version
      用法:X-AspNet-Version: 4.0.30319

      作用:如果网站是用ASP.NET开发的,这个header用来表示ASP.NET的版本

    • X-Powered-By
      用法:X-Powered-By: ASP.NET

      作用:表示网站是用什么技术开发的

  5. Transport头域

    • Connection
      用法与作用:
      Connection: keep-alive:当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接Connection: close:代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接
  6. Location头域

    • Location
      用法:Location:http://ce.sysu.edu.cn/hope/
      作用: 用于重定向一个新的位置, 包含新的URL地址

Http请求过程

发表于 2016-09-10

浏览器 http请求过程

1. 首先是输入网址

以www.facebook.com为例.

2. 浏览器查找域名对应IP

2.1 DNS查找过程:

  1. 浏览器缓存——浏览器会记录DNS一段时间(2-30分钟不等,视浏览器而定)
  2. 系统缓存——浏览器里没找到DNS缓存,此事浏览器做一个系统调用(window下是gethostbyname)。如发现匹配则采用。(与此对应有host恶意劫持更改攻击)
  3. 路由器缓存——路由器也会有DNS缓存(缓存你上过的网站,所以有时路由器需要进行DNS刷新)
  4. ISP DNS缓存——接下来是在ISP(互联网服务提供商)的DNS服务器的缓存上查找。**
  5. 递归查找——DNS缓存里没有的话,ISP DNS服务器会先后从根域名服务器(root)、.com顶级域名服务器、Facebook域名服务器获取IP(一般缓存内都会有,所以这一步一般不会发生)

DNS递归查找如下图所示:

img

DNS有一点令人担忧,这就是像wikipedia.org 或者 facebook.com这样的整个域名看上去只是对应一个单独的IP地址。还好,有几种方法可以消除这个瓶颈:

2.2 多IP域名DNS查询解决方案

  1. 循环DNS——单个域名、多个IP列表循环应对DNS查询
  2. 负载均衡器——一个特定IP的负载均衡服务器(例如:反向代理服务器)负责监听请求并转发给后面的多个服务器集群的某一个,实现多个服务器负载均衡
  3. 地理DNS——根据用户所处地理位置,返回不同的IP(应用:CDN)
  4. anycast——一个IP地址映射多个物理主机的路由技术

大多数DNS服务器使用Anycast来获得高效低延迟的DNS查找。

3. 发送请求

得到域名对应的IP后,就开始发送HTTP(S)请求了.

浏览器将把请求发送到Facebook所在的服务器, 请求头详解:

1
2
3
4
5
6
7
GET http://facebook.com/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: facebook.com
Cookie: datr=1265876274-[...]; locale=en_US; lsd=WW[...]; c_user=2101[...]

请求告诉服务器:

  1. 我要获取(GET) http://facebook.com/(GET的URL)这个页面
  2. Accept:我能接受这些类型的文件
  3. 我使用的是何种操作系统上的哪个类型那个版本的浏览器
  4. 接受何种方式的压缩文件
  5. 连接类型:短连接?长连接?
  6. 主机域名
  7. 发送存储在本机的cookies信息给服务器

除了获取请求,还有一种是发送请求,它常在提交表单用到。(如:搜索时要把搜索的内容一并发给服务器进行处理(在请求URL后面增加特定的用户参数),以获取特定的内容)

注意:URL后面加斜杠与不加斜杠的区别(文件夹与单个文件的区别)
http://www.facebook.com
http://www.facebook.com/

当我们输入http://www.facebook.com时,浏览器会自动添加斜杠,保证URL的严谨。
当我们输入:http://www.facebook.com/folderOrFile 时,因为浏览器不清楚folderOrFile到底是文件夹还是文件,所以不能自动添加 斜杠。这时,浏览器就不加斜杠直接访问地址,服务器会响应一个重定向,结果造成一次不必要的握手。

4. 重定向

当我们输入不完整的网址(http://facebook.com)时,或者网站迁移做了重定向设置时,服务器会进行一次重定向响应。
下面是重定向之后返回的响应头:

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 301 Moved Permanently
Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
Location: http://www.facebook.com/
P3P: CP="DSP LAW"
Pragma: no-cache
Set-Cookie: made_write_conn=deleted; expires=Thu, 12-Feb-2009 05:09:50 GMT;
path=/; domain=.facebook.com; httponly
Content-Type: text/html; charset=utf-8
X-Cnection: close
Date: Fri, 12 Feb 2010 05:09:51 GMT
Content-Length: 0
  1. 301 永久重定向
  2. 新的Location:……

为什么要重定向,而不直接返回用户想看的内容呢?(既然服务器已经经过重定向知道了用户需要什么)
答:原因之一:与搜索引擎排名有关。你看,如果一个页面有两个地址,就像http://www.igoro.com/ 和http://igoro.com/,搜索引擎会认为它们是两个网站,结果造成每一个的搜索链接都减少从而降低排名。而搜索引擎知道301永久重定向是 什么意思,这样就会把访问带www的和不带www的地址归到同一个网站排名下。还有一个是用不同的地址会造成缓存友好性变差。当一个页面有好几个名字时,它可能会在缓存里出现好几次。

5. 新的请求

重定向之后会发布一个新的获取请求

6. 服务器处理请求

6.1 web服务器软件

  • 服务器操作系统种类:Linux(一般是厂家根据开源定制)、windows server系列(微软)
  • 主要的服务器软件:IIS、Apache、Tomcat、JBOSS、Nginx、lighttpd、Tetty
  • 服务器软件的作用:接收、处理与响应请求(了解CGI的作用)

6.2 处理流程:

  1. web服务器软件(如IIS或者Apache)接收到HTTP请求
  2. 确定执行那个请求处理程序(一个能读懂请求并且能生成HTML来进行响应的程序)(例如:java)来处理它
  3. 请求处理器阅读请求头的参数和cookies信息
  4. 更新服务器上的信息:例如更新数据库信息
  5. 生成HTML,压缩(gzip或其他),响应请求发送给用户

7. 服务器发回一个HTML响应

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 200 OK
Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
P3P: CP="DSP LAW"
Pragma: no-cache
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
X-Cnection: close
Transfer-Encoding: chunked
Date: Fri, 12 Feb 2010 09:05:55 GMT
2b3Tn@[...]

响应包括响应头(响应参数与信息)、响应body(主体文件)

响应包采用特定方法压缩,整个响应以blob类型传输,响应头指示响应包以何种方式压缩

这个响应头与重定向的响应头不太一样,这个响应头还包含着缓存选项,cookies设置和隐私信息等

注意: 报头中把Content-type设置为“text/html”。报头让浏览器将该响应内容以HTML形式呈现,而不是以文件形式下载它。浏览器会根据报头信息决定如何解释该响应,不过同时也会考虑像URL扩展内容等其他因素。

8. 浏览器开始显示HTML

浏览器在没有完整接收全部HTML文件,就已经开始显示页面了

9. 浏览器获取其他文件

浏览器解析HTML遇到需要下载的文件时,便再次向服务器(CDN)发送获取文件的请求。

下面是几个访问facebook.com时需要重获取的几个URL:

图片

http://static.ak.fbcdn.net/rsrc.php/z12E0/hash/8q2anwu7.gif

http://static.ak.fbcdn.net/rsrc.php/zBS5C/hash/7hwy7at6.gif

…

CSS 式样表

http://static.ak.fbcdn.net/rsrc.php/z448Z/hash/2plh8s4n.css

http://static.ak.fbcdn.net/rsrc.php/zANE1/hash/cvtutcee.css

…

JavaScript 文件

http://static.ak.fbcdn.net/rsrc.php/zEMOA/hash/c8yzb6ub.js

http://static.ak.fbcdn.net/rsrc.php/z6R9L/hash/cq2lgbs8.js

…

这些地址都要经历一个和HTML读取类似的过程。所以浏览器会在DNS中查找这些域名,发送请求,重定向等等…

但 不像动态页面那样,静态文件会允许浏览器对其进行缓存。有的文件可能会不需要与服务器通讯,而从缓存中直接读取。服务器的响应中包含了静态文件保存的期限 信息,所以浏览器知道要把它们缓存多长时间。还有,每个响应都可能包含像版本号一样工作的ETag头(被请求变量的实体值),如果浏览器观察到文件的版本 ETag信息已经存在,就马上停止这个文件的传输。

试着猜猜看“fbcdn.net”在地址中代表什么?聪明的答案是”Facebook内容分发网络”。Facebook利用内容分发网络(CDN)分发像图片,CSS表和JavaScript文件这些静态文件。所以,这些文件会在全球很多CDN的数据中心中留下备份。

静态内容往往代表站点的带宽大小,也能通过CDN轻松的复制。通常网站会使用第三方的CDN。例如,Facebook的静态文件由最大的CDN提供商Akamai来托管。

举例来讲,当你试着ping static.ak.fbcdn.net的时候,可能会从某个akamai.net服务器上获得响应。有意思的是,当你同样再ping一次的时候,响应的服务器可能就不一样,这说明幕后的负载平衡开始起作用了。

注意:

  1. 动态页面无法缓存,静态文件允许浏览器进行缓存。
  2. 静态文件本地有缓存时直接从本地读取
  3. 请求响应头内包含着静态文件保存的期限,浏览器知道下载的静态文件要静默保留多久。
  4. 响应头还会有静态文件的ETag(相当于版本号),当浏览器发现请求的静态文件的响应头的ETag与现有的缓存文件不符时,便会再次向服务器获取静态文件。

10. 浏览器发送异步(AJAX)请求

以 Facebook聊天功能为例,它会持续与服务器保持联系来及时更新你那些亮亮灰灰的好友状态。为了更新这些头像亮着的好友状态,在浏览器中执行的 JavaScript代码会给服务器发送异步请求。这个异步请求发送给特定的地址,它是一个按照程式构造的获取或发送请求。还是在Facebook这个例子中,客户端发送给http://www.facebook.com/ajax/chat/buddy_list.php一个发布请求来获取你好友里哪个在线的状态信息。

Facebook聊天功能提供了关于AJAX一个有意思的问题案例:把数据从服务器端推送到客户端。因为HTTP是一个请求-响应协议,所以聊天服务器不能把新消息发给客户。取而代之的是客户端不得不隔几秒就轮询下服务器端看自己有没有新消息。

这些情况发生时长轮询是个减轻服务器负载挺有趣的技术。如果当被轮询时服务器没有新消息,它就不理这个客户端。而当尚未超时的情况下收到了该客户的新消息,服务器就会找到未完成的请求,把新消息做为响应返回给客户端。

总结:

  • web 2.0的一大特征就是页面显示完全后客户端仍旧与服务器端保持联系(keep-alive)
  • 浏览器执行特定的JS代码会给服务器发送异步请求,获取最新的动态消息,使得页面能保持较新的状态。
  • HTTP是一个请求-响应协议,只有在客户端发送请求,服务器端才能做出响应,而不能主动把消息或者文档发给客户
  • 所以,要想保持页面处于最新的状态,需要定时进行轮询(定时发送AJAX请求以更新页面内容)
  • AJAX请求十分容易更改,且用户十分容易自己制造和发送AJAX请求,所以没有验证码的没有IP限制条件的投票就是一个小游戏了(参照工作室两次刷票:自己定义IP,自己定时发送AJAX请求,然后票就哗哗的上了)。
1…202122…31
David

David

Develop Notes

155 日志
37 标签
GitHub Weibo
© 2016 - 2020 David
由 Hexo 强力驱动
主题 - NexT.Pisces