[转]Go net/http包

原文地址:https://studygolang.com/articles/9467

Go net/http包

这一篇就基本了解了go 的http包

Go Http客户端

get请求可以直接http.Get方法

package main

import (
	"fmt"
	"net/http"
	"log"
	"reflect"
	"bytes"
)

func main() {

	resp, err := http.Get("http://www.baidu.com")
	if err != nil {
		// handle error
		log.Println(err)
		return
	}

	defer resp.Body.Close()

	headers := resp.Header

	for k, v := range headers {
		fmt.Printf("k=%v, v=%v\n", k, v)
	}

	fmt.Printf("resp status %s,statusCode %d\n", resp.Status, resp.StatusCode)

	fmt.Printf("resp Proto %s\n", resp.Proto)

	fmt.Printf("resp content length %d\n", resp.ContentLength)

	fmt.Printf("resp transfer encoding %v\n", resp.TransferEncoding)

	fmt.Printf("resp Uncompressed %t\n", resp.Uncompressed)

	fmt.Println(reflect.TypeOf(resp.Body)) // *http.gzipReader

	buf := bytes.NewBuffer(make([]byte, 0, 512))

	length, _ := buf.ReadFrom(resp.Body)

	fmt.Println(len(buf.Bytes()))
	fmt.Println(length)
	fmt.Println(string(buf.Bytes()))
}

有时需要在请求的时候设置头参数、cookie之类的数据,就可以使用http.Do方法。

package main

import (
	"net/http"
	"strings"
	"fmt"
	"io/ioutil"
	"log"
	"encoding/json"
)

func main() {
	client := &http.Client{}

	req, err := http.NewRequest("POST", "http://www.maimaiche.com/loginRegister/login.do",
		strings.NewReader("mobile=xxxxxxxxx&isRemberPwd=1"))
	if err != nil {
		log.Println(err)
		return
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")

	resp, err := client.Do(req)

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Println(err)
		return
	}

	fmt.Println(resp.Header.Get("Content-Type")) //application/json;charset=UTF-8

	type Result struct {
		Msg    string
		Status string
		Obj    string
	}

	result := &Result{}
	json.Unmarshal(body, result) //解析json字符串

	if result.Status == "1" {
		fmt.Println(result.Msg)
	} else {
		fmt.Println("login error")
	}
	fmt.Println(result)
}

如果使用http POST方法可以直接使用http.Post 或 http.PostForm,

package main

import (
	"net/http"
	"strings"
	"fmt"
	"io/ioutil"
)

func main() {
	resp, err := http.Post("http://www.maimaiche.com/loginRegister/login.do",
		"application/x-www-form-urlencoded",
		strings.NewReader("mobile=xxxxxxxxxx&isRemberPwd=1"))
	if err != nil {
		fmt.Println(err)
		return
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(string(body))
}

http.PostForm方法,

package main

import (
	"net/http"
	"fmt"
	"io/ioutil"
	"net/url"
)

func main() {

	postParam := url.Values{
		"mobile":      {"xxxxxx"},
		"isRemberPwd": {"1"},
	}

	resp, err := http.PostForm("http://www.maimaiche.com/loginRegister/login.do", postParam)
	if err != nil {
		fmt.Println(err)
		return
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(string(body))
}

 

Go Http服务器端

一切的基础:ServeMux 和 Handler

Go 语言中处理 HTTP 请求主要跟两个东西相关:ServeMux 和 Handler。

ServrMux 本质上是一个 HTTP 请求路由器(或者叫多路复用器,Multiplexor)。它把收到的请求与一组预先定义的 URL 路径列表做对比,然后在匹配到路径的时候调用关联的处理器(Handler)。

处理器(Handler)负责输出HTTP响应的头和正文。任何满足了http.Handler接口的对象都可作为一个处理器。通俗的说,对象只要有个如下签名的ServeHTTP方法即可:

ServeHTTP(http.ResponseWriter, *http.Request)

Go 语言的 HTTP 包自带了几个函数用作常用处理器,比如FileServer,NotFoundHandler 和 RedirectHandler。我们从一个简单具体的例子开始:

package main

import (
	"log"
	"net/http"
)

func main() {
	mux := http.NewServeMux()

	rh := http.RedirectHandler("http://www.baidu.com", 307)
	mux.Handle("/foo", rh)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}

快速地过一下代码:

  1. 在 main 函数中我们只用了 http.NewServeMux 函数来创建一个空的 ServeMux。
  2. 然后我们使用 http.RedirectHandler 函数创建了一个新的处理器,这个处理器会对收到的所有请求,都执行307重定向操作到 http://www.baidu.com。
  3. 接下来我们使用 ServeMux.Handle 函数将处理器注册到新创建的 ServeMux,所以它在 URL 路径/foo 上收到所有的请求都交给这个处理器。
  4. 最后我们创建了一个新的服务器,并通过 http.ListenAndServe 函数监听所有进入的请求,通过传递刚才创建的 ServeMux来为请求去匹配对应处理器。

 

然后在浏览器中访问 http://localhost:3000/foo,你应该能发现请求已经成功的重定向了。

明察秋毫的你应该能注意到一些有意思的事情:ListenAndServer 的函数签名是 ListenAndServe(addr string, handler Handler) ,但是第二个参数我们传递的是个 ServeMux。

我们之所以能这么做,是因为 ServeMux 也有 ServeHTTP 方法,因此它也是个合法的 Handler。

对我来说,将 ServerMux 用作一个特殊的Handler是一种简化。它不是自己输出响应而是将请求传递给注册到它的其他 Handler。这乍一听起来不是什么明显的飞跃 – 但在 Go 中将 Handler 链在一起是非常普遍的用法。

 

自定义处理器(Custom Handlers)

让我们创建一个自定义的处理器,功能是将以特定格式输出当前的本地时间:

type timeHandler struct {
	format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	tm := time.Now().Format(th.format)
	w.Write([]byte("The time is: " + tm))
}

这个例子里代码本身并不是重点。

真正的重点是我们有一个对象(本例中就是个timerHandler结构体,但是也可以是一个字符串、一个函数或者任意的东西),我们在这个对象上实现了一个 ServeHTTP(http.ResponseWriter, *http.Request) 签名的方法,这就是我们创建一个处理器所需的全部东西。

我们把这个集成到具体的示例里:

//File: main.go

package main

import (
	"log"
	"net/http"
	"time"
)

type timeHandler struct {
	format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	tm := time.Now().Format(th.format)
	w.Write([]byte("The time is: " + tm))
}

func main() {
	mux := http.NewServeMux()

	th := &timeHandler{format: time.RFC1123}
	mux.Handle("/time", th)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}

main函数中,我们像初始化一个常规的结构体一样,初始化了timeHandler,用 & 符号获得了其地址。随后,像之前的例子一样,我们使用 mux.Handle 函数来将其注册到 ServerMux。

现在当我们运行这个应用,ServerMux 将会将任何对 /time的请求直接交给 timeHandler.ServeHTTP 方法处理。

访问一下这个地址看一下效果:http://localhost:3000/time 。

注意我们可以在多个路由中轻松的复用 timeHandler:

//File: main.go

package main

import (
	"log"
	"net/http"
	"time"
)

type timeHandler struct {
	format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	tm := time.Now().Format(th.format)
	w.Write([]byte("The time is: " + tm))
}

func main() {
	mux := http.NewServeMux()

	th1123 := &timeHandler{format: time.RFC1123}
	mux.Handle("/time/rfc1123", th1123)

	th3339 := &timeHandler{format: time.RFC3339}
	mux.Handle("/time/rfc3339", th3339)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}

 

将函数作为处理器

对于简单的情况(比如上面的例子),定义个新的有 ServerHTTP 方法的自定义类型有些累赘。我们看一下另外一种方式,我们借助 http.HandlerFunc 类型来让一个常规函数满足作为一个 Handler 接口的条件。

任何有 func(http.ResponseWriter, *http.Request) 签名的函数都能转化为一个 HandlerFunc 类型。这很有用,因为 HandlerFunc 对象内置了 ServeHTTP 方法,后者可以聪明又方便的调用我们最初提供的函数内容。

让我们使用这个技术重新实现一遍timeHandler应用:

package main

import (
	"log"
	"net/http"
	"time"
)

func timeHandler(w http.ResponseWriter, r *http.Request) {
	tm := time.Now().Format(time.RFC1123)
	w.Write([]byte("The time is: " + tm))
}

func main() {
	mux := http.NewServeMux()

	// Convert the timeHandler function to a HandlerFunc type
	th := http.HandlerFunc(timeHandler)
	// And add it to the ServeMux
	mux.Handle("/time", th)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}

实际上,将一个函数转换成 HandlerFunc 后注册到 ServeMux 是很普遍的用法,所以 Go 语言为此提供了个便捷方式:ServerMux.HandlerFunc 方法。

我们使用便捷方式重写 main() 函数看起来是这样的:

package main

import (
	"log"
	"net/http"
	"time"
)

func timeHandler(w http.ResponseWriter, r *http.Request) {
	tm := time.Now().Format(time.RFC1123)
	w.Write([]byte("The time is: " + tm))
}

func main() {
	mux := http.NewServeMux()

	mux.HandleFunc("/time", timeHandler)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}

绝大多数情况下这种用函数当处理器的方式工作的很好。但是当事情开始变得更复杂的时候,就会有些产生一些限制了。

你可能已经注意到了,跟之前的方式不同,我们不得不将时间格式硬编码到 timeHandler 的方法中。如果我们想从 main() 函数中传递一些信息或者变量给处理器该怎么办?

一个优雅的方式是将我们处理器放到一个闭包中,将我们要使用的变量带进去:

//File: main.go
package main

import (
	"log"
	"net/http"
	"time"
)

func timeHandler(format string) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		tm := time.Now().Format(format)
		w.Write([]byte("The time is: " + tm))
	}
	return http.HandlerFunc(fn)
}

func main() {
	mux := http.NewServeMux()

	th := timeHandler(time.RFC1123)
	mux.Handle("/time", th)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}

timeHandler 函数现在有了个更巧妙的身份。除了把一个函数封装成 Handler(像我们之前做到那样),我们现在使用它来返回一个处理器。这种机制有两个关键点:

首先是创建了一个fn,这是个匿名函数,将 format 变量封装到一个闭包里。闭包的本质让处理器在任何情况下,都可以在本地范围内访问到 format 变量。

其次我们的闭包函数满足 func(http.ResponseWriter, *http.Request) 签名。如果你记得之前我们说的,这意味我们可以将它转换成一个HandlerFunc类型(满足了http.Handler接口)。我们的timeHandler 函数随后转换后的 HandlerFunc 返回。

 

在上面的例子中我们已经可以传递一个简单的字符串给处理器。但是在实际的应用中可以使用这种方法传递数据库连接、模板组,或者其他应用级的上下文。使用全局变量也是个不错的选择,还能得到额外的好处就是编写更优雅的自包含的处理器以便测试。

 

你也可能见过相同的写法,像这样:

func timeHandler(format string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		tm := time.Now().Format(format)
		w.Write([]byte("The time is: " + tm))
	})
}

或者在返回时,使用一个到 HandlerFunc 类型的隐式转换:

func timeHandler(format string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tm := time.Now().Format(format)
		w.Write([]byte("The time is: " + tm))
	}
}

 

更便利的 DefaultServeMux

你可能已经在很多地方看到过 DefaultServeMux, 从最简单的 Hello World 例子,到 go 语言的源代码中。

我花了很长时间才意识到 DefaultServerMux 并没有什么的特殊的地方。DefaultServerMux 就是我们之前用到的 ServerMux,只是它随着 net/httpp 包初始化的时候被自动初始化了而已。Go 源代码中的相关行如下:

var DefaultServeMux = NewServeMux()

net/http 包提供了一组快捷方式来配合 DefaultServeMux:http.Handle 和 http.HandleFunc。这些函数与我们之前看过的类似的名称的函数功能一样,唯一的不同是他们将处理器注册到 DefaultServerMux ,而之前我们是注册到自己创建的 ServeMux。

此外,ListenAndServe在没有提供其他的处理器的情况下(也就是第二个参数设成了 nil),内部会使用 DefaultServeMux。

因此,作为最后一个步骤,我们使用 DefaultServeMux 来改写我们的 timeHandler应用:

//File: main.go
package main

import (
	"log"
	"net/http"
	"time"
)

func timeHandler(format string) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		tm := time.Now().Format(format)
		w.Write([]byte("The time is: " + tm))
	}
	return http.HandlerFunc(fn)
}

func main() {
	// Note that we skip creating the ServeMux...

	var format string = time.RFC1123
	th := timeHandler(format)

	// We use http.Handle instead of mux.Handle...
	http.Handle("/time", th)

	log.Println("Listening...")
	// And pass nil as the handler to ListenAndServe.
	http.ListenAndServe(":3000", nil)
}

======END======

[转]跨域资源共享 CORS 详解

原文链接:http://www.ruanyifeng.com/blog/2016/04/cors.html

一、简介

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

二、两种请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。

(1) 请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不同时满足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理,是不一样的。

三、简单请求

3.1 基本流程

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。


GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。


Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。

(1)Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

(2)Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

(3)Access-Control-Expose-Headers

该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

3.2 withCredentials 属性

上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。


Access-Control-Allow-Credentials: true

另一方面,开发者必须在AJAX请求中打开withCredentials属性。


var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。

但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials


xhr.withCredentials = false;

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

四、非简单请求

4.1 预检请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

下面是一段浏览器的JavaScript脚本。


var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header

浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,要求服务器确认可以这样请求。下面是这个”预检”请求的HTTP头信息。


OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

除了Origin字段,”预检”请求的头信息包括两个特殊字段。

(1)Access-Control-Request-Method

该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT

(2)Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

4.2 预检请求的回应

服务器收到”预检”请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。


Access-Control-Allow-Origin: *

如果浏览器否定了”预检”请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。


XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

服务器回应的其他CORS相关字段如下。


Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

(1)Access-Control-Allow-Methods

该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。

(2)Access-Control-Allow-Headers

如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。

(3)Access-Control-Allow-Credentials

该字段与简单请求时的含义相同。

(4)Access-Control-Max-Age

该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

4.3 浏览器的正常请求和回应

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

下面是”预检”请求之后,浏览器的正常CORS请求。


PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面头信息的Origin字段是浏览器自动添加的。

下面是服务器正常的回应。


Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。

五、与JSONP的比较

CORS与JSONP的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据

[转]HTTP 499 状态码 nginx下 499错误

日志记录中HTTP状态码出现499错误有多种情况,我遇到的一种情况是nginx反代到一个永远打不开的后端,就这样了,日志状态记录是499、发送字节数是0。

老是有用户反映网站系统时好时坏,因为线上的产品很长时间没有修改,所以前端程序的问题基本上可以排除,于是就想着是Get方式调用的接口不稳定,问了相关人员,说没有问题,为了拿到确切证据,于是我问相关人员要了nginx服务器的日志文件(awstats日志),分析后发现日志中很多错误码为499的错误,约占整个日志文件的1%,而它只占全部报错的70%左右(全部报错见下图),那么所有报错加起来就要超过1%了,这个量还是特别大的。

499错误是什么?让我们看看NGINX的源码中的定义:

ngx_string(ngx_http_error_495_page), /* 495, https certificate error */
ngx_string(ngx_http_error_496_page), /* 496, https no certificate */
ngx_string(ngx_http_error_497_page), /* 497, http to https */
ngx_string(ngx_http_error_404_page), /* 498, canceled */
ngx_null_string, /* 499, client has closed connection */

可以看到,499对应的是 “client has closed connection”。这很有可能是因为服务器端处理的时间过长,客户端“不耐烦”了。

Nginx 499错误的原因及解决方法

打开Nginx的access.log发现在最后一次的提交是出现了HTTP1.1 499 0 -这样的错误,在百度搜索nginx 499错误,结果都是说客户端主动断开了连接。

但经过我的测试这显然不是客户端的问题,因为使用端口+IP直接访问后端服务器不存在此问题,后来测试nginx发现如果两次提交post过快就会出现499的情况,看来是nginx认为是不安全的连接,主动拒绝了客户端的连接.

但搜索相关问题一直找不到解决方法,最后终于在google上搜索到一英文论坛上有关于此错误的解决方法:

proxy_ignore_client_abort on;
Don’t know if this is safe.

就是说要配置参数 proxy_ignore_client_abort on;

表示代理服务端不要主要主动关闭客户端连接。

以此配置重启nginx,问题果然得到解决。只是安全方面稍有欠缺,但比总是出现找不到服务器好多了。

还有一种原因是 我后来测试发现 确实是客户端关闭了连接,或者说连接超时 ,无论你设置多少超时时间多没用 原来是php进程不够用了 改善一下php进程数 问题解决 默认测试环境才开5个子进程。

以上所述是小编给大家介绍的HTTP 499 状态码 nginx下 499错误的解决办法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

[HTTP]HTTP,HTTP2.0,SPDY,HTTPS看这篇就够了

作为一个经常和web打交道的程序员,了解这些协议是必须的,本文就向大家介绍一下这些协议的区别和基本概念,文中可能不局限于前端知识,还包括一些运维,协议方面的知识,希望能给读者带来一些收获,如有不对之处还请指出。

1. web始祖HTTP

全称:超文本传输协议(HyperText Transfer Protocol) 伴随着计算机网络和浏览器的诞生,HTTP1.0也随之而来,处于计算机网络中的应用层,HTTP是建立在TCP协议之上,所以HTTP协议的瓶颈及其优化技巧都是基于TCP协议本身的特性,例如tcp建立连接的3次握手和断开连接的4次挥手以及每次建立连接带来的RTT延迟时间。

2. HTTP与现代化浏览器

早在HTTP建立之初,主要就是为了将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器。也是说对于前端来说,我们所写的HTML页面将要放在我们的web服务器上,用户端通过浏览器访问url地址来获取网页的显示内容,但是到了WEB2.0以来,我们的页面变得复杂,不仅仅单纯的是一些简单的文字和图片,同时我们的HTML页面有了CSS,Javascript,来丰富我们的页面展示,当ajax的出现,我们又多了一种向服务器端获取数据的方法,这些其实都是基于HTTP协议的。同样到了移动互联网时代,我们页面可以跑在手机端浏览器里面,但是和PC相比,手机端的网络情况更加复杂,这使得我们开始了不得不对HTTP进行深入理解并不断优化过程中。

3. HTTP的基本优化

影响一个HTTP网络请求的因素主要有两个:带宽和延迟。

  • 带宽:如果说我们还停留在拨号上网的阶段,带宽可能会成为一个比较严重影响请求的问题,当时现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了。
  • 延迟:
  1. 浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制,后续请求就会被阻塞。
  2. DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。
  3. 建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。

4. HTTP1.0和HTTP1.1的一些区别

HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议。 主要区别主要体现在:

  1. 缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
  2. 带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
  3. 错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
  4. Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。
  5. 长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。以下是常见的HTTP1.0:

5. HTTP1.0和1.1现存的一些问题

  1. 上面提到过的,HTTP1.x在传输数据时,每次都需要重新建立连接,无疑增加了大量的延迟时间,特别是在移动端更为突出。
  2. HTTP1.x在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,这在一定程度上无法保证数据的安全性。
  3. HTTP1.x在使用时,header里携带的内容过大,在一定程度上增加了传输的成本,并且每次请求header基本不怎么变化,尤其在移动端增加用户流量。
  4. 虽然HTTP1.x支持了keep-alive,来弥补多次创建连接产生的延迟,但是keep-alive使用多了同样会给服务端带来大量的性能压力,并且对于单个文件被不断请求的服务(例如图片存放网站),keep-alive可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。

 

6. HTTPS应声而出

为了解决以上问题,网景在1994年创建了HTTPS,并应用在网景导航者浏览器中。 最初,HTTPS是与SSL一起使用的;在SSL逐渐演变到TLS时(其实两个是一个东西,只是名字不同而已),最新的HTTPS也由在2000年五月公布的RFC 2818正式确定下来。简单来说,HTTPS就是安全版的HTTP,并且由于当今时代对安全性要求更高,chrome和firefox都大力支持网站使用HTTPS,苹果也在ios 10系统中强制app使用HTTPS来传输数据,由此可见HTTPS势在必行。

7. HTTPS与HTTP的一些区别

  1. HTTPS协议需要到ca申请证书,一般免费证书很少,需要交费。
  2. HTTP是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的ssl加密传输协议。
  3. HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  4. HTTPS的连接很简单,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

 

8. HTTPS改造

如果一个网站要全站由HTTP替换成HTTPS,可能需要关注以下几点:

  1. 安装CA证书,一般的证书都是需要收费的,这边推荐一个比较好的购买证书网站:1)Let’s Encrypt,免费,快捷,支持多域名(不是通配符),三条命令即时签署+导出证书。缺点是暂时只有三个月有效期,到期需续签。2,收费,但是比较稳定。
  2. 在购买证书之后,在证书提供的网站上配置自己的域名,将证书下载下来之后,配置自己的web服务器,同时进行代码改造。
  3. HTTPS 降低用户访问速度。SSL握手,HTTPS 对速度会有一定程度的降低,但是只要经过合理优化和部署,HTTPS 对速度的影响完全可以接受。在很多场景下,HTTPS 速度完全不逊于 HTTP,如果使用 SPDY,HTTPS 的速度甚至还要比 HTTP 快。
  4. 相对于HTTPS降低访问速度,其实更需要关心的是服务器端的CPU压力,HTTPS中大量的密钥算法计算,会消耗大量的CPU资源,只有足够的优化,HTTPS 的机器成本才不会明显增加。

推荐一则淘宝网改造HTTPS的文章。

9. 使用SPDY加快你的网站速度

2012年google如一声惊雷提出了SPDY的方案,大家才开始从正面看待和解决老版本HTTP协议本身的问题,SPDY可以说是综合了HTTPS和HTTP两者有点于一体的传输协议,主要解决:

  1. 降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率。
  2. 请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容。
  3. header压缩。前面提到HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量。
  4. 基于HTTPS的加密协议传输,大大提高了传输数据的可靠性。
  5. 服务端推送(server push),采用了SPDY的网页,例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。SPDY构成图:

SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTTP协议(将HTTP1.x的内容封装成一种新的frame格式),同时可以使用已有的SSL功能。 兼容性:

10. HTTP2.0的前世今生

S顾名思义有了HTTP1.x,那么HTTP2.0也就顺理成章的出现了。HTTP2.0可以说是SPDY的升级版(其实原本也是基于SPDY设计的),但是,HTTP2.0 跟 SPDY 仍有不同的地方,主要是以下两点:

  • HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
  • HTTP2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATE

 

11. HTTP2.0的新特性

  • 新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
  • 多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。多路复用原理图

  • header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
  • 服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。目前,有大多数网站已经启用HTTP2.0,例如YouTuBe淘宝网等网站,利用chrome控制台可以查看是否启用H2:

更多关于HTTP2的问题可以参考:HTTP2奇妙日常,以及HTTP2.0的官方网站

12. HTTP2.0的升级改造

对比HTTPS的升级改造,HTTP2.0获取会稍微简单一些,你可能需要关注以下问题:

  1. 前文说了HTTP2.0其实可以支持非HTTPS的,但是现在主流的浏览器像chrome,firefox表示还是只支持基于 TLS 部署的HTTP2.0协议,所以要想升级成HTTP2.0还是先生气HTTPS为好。
  2. 当你的网站已经升级HTTPS之后,那么升级HTTP2.0就简单很多,如果你使用NGINX,只要在配置文件中启动相应的协议就可以了,可以参考NGINX白皮书NGINX配置HTTP2.0官方指南
  3. 使用了HTTP2.0那么,原本的HTTP1.x怎么办,这个问题其实不用担心,HTTP2.0完全兼容HTTP1.x的语义,对于不支持HTTP2.0的浏览器,NGINX会自动向下兼容的。

 

后记

  1. 以上就是关于HTTP,HTTP2.0,SPDY,HTTPS的一些基本理论,有些内容没有深入讲解,大家可以跟进参考连接具体查看。
  2. 关于HTTP1.x的一些优化方式,例如文件合并压缩,资源cdn,js,css优化等等同样使用与HTTP2.0和HTTPS,所以web前端的优化,还是要继续进行。
  3. 其实WEB发展如此迅速的今天,有些技术是真的要与时俱进的,就像苹果宣布ios 10必须使用HTTPS开始,关于web协议革新就已经开始了,为了更好的性能,更优越的方式,现在就开始升级改造吧

http错误码收录

状态码 含义
100 客户端应当继续发送请求。这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应。
101 服务器已经理解了客户端的请求,并将通过Upgrade 消息头通知客户端采用不同的协议来完成这个请求。在发送完这个响应最后的空行后,服务器将会切换到在Upgrade 消息头中定义的那些协议。   只有在切换新的协议更有好处的时候才应该采取类似措施。例如,切换到新的HTTP 版本比旧版本更有优势,或者切换到一个实时且同步的协议以传送利用此类特性的资源。
102 由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行。
200 请求已成功,请求所希望的响应头或数据体将随此响应返回。
201 请求已经被实现,而且有一个新的资源已经依据请求的需要而建立,且其 URI 已经随Location 头信息返回。假如需要的资源无法及时建立的话,应当返回 ‘202 Accepted’。
202 服务器已接受请求,但尚未处理。正如它可能被拒绝一样,最终该请求可能会也可能不会被执行。在异步操作的场合下,没有比发送这个状态码更方便的做法了。   返回202状态码的响应的目的是允许服务器接受其他过程的请求(例如某个每天只执行一次的基于批处理的操作),而不必让客户端一直保持与服务器的连接直到批处理操作全部完成。在接受请求处理并返回202状态码的响应应当在返回的实体中包含一些指示处理当前状态的信息,以及指向处理状态监视器或状态预测的指针,以便用户能够估计操作是否已经完成。
203 服务器已成功处理了请求,但返回的实体头部元信息不是在原始服务器上有效的确定集合,而是来自本地或者第三方的拷贝。当前的信息可能是原始版本的子集或者超集。例如,包含资源的元数据可能导致原始服务器知道元信息的超级。使用此状态码不是必须的,而且只有在响应不使用此状态码便会返回200 OK的情况下才是合适的。
204 服务器成功处理了请求,但不需要返回任何实体内容,并且希望返回更新了的元信息。响应可能通过实体头部的形式,返回新的或更新后的元信息。如果存在这些头部信息,则应当与所请求的变量相呼应。   如果客户端是浏览器的话,那么用户浏览器应保留发送了该请求的页面,而不产生任何文档视图上的变化,即使按照规范新的或更新后的元信息应当被应用到用户浏览器活动视图中的文档。   由于204响应被禁止包含任何消息体,因此它始终以消息头后的第一个空行结尾。
205 服务器成功处理了请求,且没有返回任何内容。但是与204响应不同,返回此状态码的响应要求请求者重置文档视图。该响应主要是被用于接受用户输入后,立即重置表单,以便用户能够轻松地开始另一次输入。   与204响应一样,该响应也被禁止包含任何消息体,且以消息头后的第一个空行结束。
206 服务器已经成功处理了部分 GET 请求。类似于 FlashGet 或者迅雷这类的 HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。   该请求必须包含 Range 头信息来指示客户端希望得到的内容范围,并且可能包含 If-Range 来作为请求条件。   响应必须包含如下的头部域:   Content-Range 用以指示本次响应中返回的内容的范围;如果是 Content-Type 为 multipart/byteranges 的多段下载,则每一 multipart 段中都应包含 Content-Range 域用以指示本段的内容范围。假如响应中包含 Content-Length,那么它的数值必须匹配它返回的内容范围的真实字节数。   Date   ETag 和/或 Content-Location,假如同样的请求本应该返回200响应。   Expires, Cache-Control,和/或 Vary,假如其值可能与之前相同变量的其他响应对应的值不同的话。   假如本响应请求使用了 If-Range 强缓存验证,那么本次响应不应该包含其他实体头;假如本响应的请求使用了 If-Range 弱缓存验证,那么本次响应禁止包含其他实体头;这避免了缓存的实体内容和更新了的实体头信息之间的不一致。否则,本响应就应当包含所有本应该返回200响应中应当返回的所有实体头部域。   假如 ETag 或 Last-Modified 头部不能精确匹配的话,则客户端缓存应禁止将206响应返回的内容与之前任何缓存过的内容组合在一起。   任何不支持 Range 以及 Content-Range 头的缓存都禁止缓存206响应返回的内容。
207 由WebDAV(RFC 2518)扩展的状态码,代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码。
300 被请求的资源有一系列可供选择的回馈信息,每个都有自己特定的地址和浏览器驱动的商议信息。用户或浏览器能够自行选择一个首选的地址进行重定向。   除非这是一个 HEAD 请求,否则该响应应当包括一个资源特性及地址的列表的实体,以便用户或浏览器从中选择最合适的重定向地址。这个实体的格式由 Content-Type 定义的格式所决定。浏览器可能根据响应的格式以及浏览器自身能力,自动作出最合适的选择。当然,RFC 2616规范并没有规定这样的自动选择该如何进行。   如果服务器本身已经有了首选的回馈选择,那么在 Location 中应当指明这个回馈的 URI;浏览器可能会将这个 Location 值作为自动重定向的地址。此外,除非额外指定,否则这个响应也是可缓存的。
301 被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。   新的永久性的 URI 应当在响应的 Location 域中返回。除非这是一个 HEAD 请求,否则响应的实体中应当包含指向新的 URI 的超链接及简短说明。   如果这不是一个 GET 或者 HEAD 请求,因此浏览器禁止自动进行重定向,除非得到用户的确认,因为请求的条件可能因此发生变化。   注意:对于某些使用 HTTP/1.0 协议的浏览器,当它们发送的 POST 请求得到了一个301响应的话,接下来的重定向请求将会变成 GET 方式。
302 请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。   新的临时性的 URI 应当在响应的 Location 域中返回。除非这是一个 HEAD 请求,否则响应的实体中应当包含指向新的 URI 的超链接及简短说明。   如果这不是一个 GET 或者 HEAD 请求,那么浏览器禁止自动进行重定向,除非得到用户的确认,因为请求的条件可能因此发生变化。   注意:虽然RFC 1945和RFC 2068规范不允许客户端在重定向时改变请求的方法,但是很多现存的浏览器将302响应视作为303响应,并且使用 GET 方式访问在 Location 中规定的 URI,而无视原先请求的方法。状态码303和307被添加了进来,用以明确服务器期待客户端进行何种反应。
303 对应当前请求的响应可以在另一个 URI 上被找到,而且客户端应当采用 GET 的方式访问那个资源。这个方法的存在主要是为了允许由脚本激活的POST请求输出重定向到一个新的资源。这个新的 URI 不是原始资源的替代引用。同时,303响应禁止被缓存。当然,第二个请求(重定向)可能被缓存。   新的 URI 应当在响应的 Location 域中返回。除非这是一个 HEAD 请求,否则响应的实体中应当包含指向新的 URI 的超链接及简短说明。   注意:许多 HTTP/1.1 版以前的 浏览器不能正确理解303状态。如果需要考虑与这些浏览器之间的互动,302状态码应该可以胜任,因为大多数的浏览器处理302响应时的方式恰恰就是上述规范要求客户端处理303响应时应当做的。
304 如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。304响应禁止包含消息体,因此始终以消息头后的第一个空行结尾。   该响应必须包含以下的头信息:   Date,除非这个服务器没有时钟。假如没有时钟的服务器也遵守这些规则,那么代理服务器以及客户端可以自行将 Date 字段添加到接收到的响应头中去(正如RFC 2068中规定的一样),缓存机制将会正常工作。   ETag 和/或 Content-Location,假如同样的请求本应返回200响应。   Expires, Cache-Control,和/或Vary,假如其值可能与之前相同变量的其他响应对应的值不同的话。   假如本响应请求使用了强缓存验证,那么本次响应不应该包含其他实体头;否则(例如,某个带条件的 GET 请求使用了弱缓存验证),本次响应禁止包含其他实体头;这避免了缓存了的实体内容和更新了的实体头信息之间的不一致。   假如某个304响应指明了当前某个实体没有缓存,那么缓存系统必须忽视这个响应,并且重复发送不包含限制条件的请求。   假如接收到一个要求更新某个缓存条目的304响应,那么缓存系统必须更新整个条目以反映所有在响应中被更新的字段的值。
305 被请求的资源必须通过指定的代理才能被访问。Location 域中将给出指定的代理所在的 URI 信息,接收者需要重复发送一个单独的请求,通过这个代理才能访问相应资源。只有原始服务器才能建立305响应。   注意:RFC 2068中没有明确305响应是为了重定向一个单独的请求,而且只能被原始服务器建立。忽视这些限制可能导致严重的安全后果。
306 在最新版的规范中,306状态码已经不再被使用。
307 请求的资源现在临时从不同的URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。   新的临时性的URI 应当在响应的 Location 域中返回。除非这是一个HEAD 请求,否则响应的实体中应当包含指向新的URI 的超链接及简短说明。因为部分浏览器不能识别307响应,因此需要添加上述必要信息以便用户能够理解并向新的 URI 发出访问请求。   如果这不是一个GET 或者 HEAD 请求,那么浏览器禁止自动进行重定向,除非得到用户的确认,因为请求的条件可能因此发生变化。
400 1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。   2、请求参数有误。
401 当前请求需要用户验证。该响应必须包含一个适用于被请求资源的 WWW-Authenticate 信息头用以询问用户信息。客户端可以重复提交一个包含恰当的 Authorization 头信息的请求。如果当前请求已经包含了 Authorization 证书,那么401响应代表着服务器验证已经拒绝了那些证书。如果401响应包含了与前一个响应相同的身份验证询问,且浏览器已经至少尝试了一次验证,那么浏览器应当向用户展示响应中包含的实体信息,因为这个实体信息中可能包含了相关诊断信息。参见RFC 2617。
402 该状态码是为了将来可能的需求而预留的。
403 服务器已经理解请求,但是拒绝执行它。与401响应不同的是,身份验证并不能提供任何帮助,而且这个请求也不应该被重复提交。如果这不是一个 HEAD 请求,而且服务器希望能够讲清楚为何请求不能被执行,那么就应该在实体内描述拒绝的原因。当然服务器也可以返回一个404响应,假如它不希望让客户端获得任何信息。
404 请求失败,请求所希望得到的资源未被在服务器上发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。假如服务器知道情况的话,应当使用410状态码来告知旧资源因为某些内部的配置机制问题,已经永久的不可用,而且没有任何可以跳转的地址。404这个状态码被广泛应用于当服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。
405 请求行中指定的请求方法不能被用于请求相应的资源。该响应必须返回一个Allow 头信息用以表示出当前资源能够接受的请求方法的列表。   鉴于 PUT,DELETE 方法会对服务器上的资源进行写操作,因而绝大部分的网页服务器都不支持或者在默认配置下不允许上述请求方法,对于此类请求均会返回405错误。
406 请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体。   除非这是一个 HEAD 请求,否则该响应就应当返回一个包含可以让用户或者浏览器从中选择最合适的实体特性以及地址列表的实体。实体的格式由 Content-Type 头中定义的媒体类型决定。浏览器可以根据格式及自身能力自行作出最佳选择。但是,规范中并没有定义任何作出此类自动选择的标准。
407  与401响应类似,只不过客户端必须在代理服务器上进行身份验证。代理服务器必须返回一个 Proxy-Authenticate 用以进行身份询问。客户端可以返回一个 Proxy-Authorization 信息头用以验证。参见RFC 2617。
408 请求超时。客户端没有在服务器预备等待的时间内完成一个请求的发送。客户端可以随时再次提交这一请求而无需进行任何更改。
409 由于和被请求的资源的当前状态之间存在冲突,请求无法完成。这个代码只允许用在这样的情况下才能被使用:用户被认为能够解决冲突,并且会重新提交新的请求。该响应应当包含足够的信息以便用户发现冲突的源头。   冲突通常发生于对 PUT 请求的处理中。例如,在采用版本检查的环境下,某次 PUT 提交的对特定资源的修改请求所附带的版本信息与之前的某个(第三方)请求向冲突,那么此时服务器就应该返回一个409错误,告知用户请求无法完成。此时,响应实体中很可能会包含两个冲突版本之间的差异比较,以便用户重新提交归并以后的新版本。
410 被请求的资源在服务器上已经不再可用,而且没有任何已知的转发地址。这样的状况应当被认为是永久性的。如果可能,拥有链接编辑功能的客户端应当在获得用户许可后删除所有指向这个地址的引用。如果服务器不知道或者无法确定这个状况是否是永久的,那么就应该使用404状态码。除非额外说明,否则这个响应是可缓存的。   410响应的目的主要是帮助网站管理员维护网站,通知用户该资源已经不再可用,并且服务器拥有者希望所有指向这个资源的远端连接也被删除。这类事件在限时、增值服务中很普遍。同样,410响应也被用于通知客户端在当前服务器站点上,原本属于某个个人的资源已经不再可用。当然,是否需要把所有永久不可用的资源标记为’410 Gone’,以及是否需要保持此标记多长时间,完全取决于服务器拥有者。
411 服务器拒绝在没有定义 Content-Length 头的情况下接受请求。在添加了表明请求消息体长度的有效 Content-Length 头之后,客户端可以再次提交该请求。
412 服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。这个状态码允许客户端在获取资源时在请求的元信息(请求头字段数据)中设置先决条件,以此避免该请求方法被应用到其希望的内容以外的资源上。
413 服务器拒绝处理当前请求,因为该请求提交的实体数据大小超过了服务器愿意或者能够处理的范围。此种情况下,服务器可以关闭连接以免客户端继续发送此请求。   如果这个状况是临时的,服务器应当返回一个 Retry-After 的响应头,以告知客户端可以在多少时间以后重新尝试。
414 请求的URI 长度超过了服务器能够解释的长度,因此服务器拒绝对该请求提供服务。这比较少见,通常的情况包括:   本应使用POST方法的表单提交变成了GET方法,导致查询字符串(Query String)过长。   重定向URI “黑洞”,例如每次重定向把旧的 URI 作为新的 URI 的一部分,导致在若干次重定向后 URI 超长。   客户端正在尝试利用某些服务器中存在的安全漏洞攻击服务器。这类服务器使用固定长度的缓冲读取或操作请求的 URI,当 GET 后的参数超过某个数值后,可能会产生缓冲区溢出,导致任意代码被执行[1]。没有此类漏洞的服务器,应当返回414状态码。
415 对于当前请求的方法和所请求的资源,请求中提交的实体并不是服务器中所支持的格式,因此请求被拒绝。
416 如果请求中包含了 Range 请求头,并且 Range 中指定的任何数据范围都与当前资源的可用范围不重合,同时请求中又没有定义 If-Range 请求头,那么服务器就应当返回416状态码。   假如 Range 使用的是字节范围,那么这种情况就是指请求指定的所有数据范围的首字节位置都超过了当前资源的长度。服务器也应当在返回416状态码的同时,包含一个 Content-Range 实体头,用以指明当前资源的长度。这个响应也被禁止使用 multipart/byteranges 作为其 Content-Type。
417 在请求头 Expect 中指定的预期内容无法被服务器满足,或者这个服务器是一个代理服务器,它有明显的证据证明在当前路由的下一个节点上,Expect 的内容无法被满足。
421 从当前客户端所在的IP地址到服务器的连接数超过了服务器许可的最大范围。通常,这里的IP地址指的是从服务器上看到的客户端地址(比如用户的网关或者代理服务器地址)。在这种情况下,连接数的计算可能涉及到不止一个终端用户。
422 从当前客户端所在的IP地址到服务器的连接数超过了服务器许可的最大范围。通常,这里的IP地址指的是从服务器上看到的客户端地址(比如用户的网关或者代理服务器地址)。在这种情况下,连接数的计算可能涉及到不止一个终端用户。
422 请求格式正确,但是由于含有语义错误,无法响应。(RFC 4918 WebDAV)423 Locked   当前资源被锁定。(RFC 4918 WebDAV)
424 由于之前的某个请求发生的错误,导致当前请求失败,例如 PROPPATCH。(RFC 4918 WebDAV)
425 在WebDav Advanced Collections 草案中定义,但是未出现在《WebDAV 顺序集协议》(RFC 3658)中。
426 客户端应当切换到TLS/1.0。(RFC 2817)
449 由微软扩展,代表请求应当在执行完适当的操作后进行重试。
500 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器的程序码出错时出现。
501 服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。
502 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
503 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。如果能够预计延迟时间,那么响应中可以包含一个 Retry-After 头用以标明这个延迟时间。如果没有给出这个 Retry-After 信息,那么客户端应当以处理500响应的方式处理它。   注意:503状态码的存在并不意味着服务器在过载的时候必须使用它。某些服务器只不过是希望拒绝客户端的连接。
504 作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应。   注意:某些代理服务器在DNS查询超时时会返回400或者500错误
505 服务器不支持,或者拒绝支持在请求中使用的 HTTP 版本。这暗示着服务器不能或不愿使用与客户端相同的版本。响应中应当包含一个描述了为何版本不被支持以及服务器支持哪些协议的实体。
506 由《透明内容协商协议》(RFC 2295)扩展,代表服务器存在内部配置错误:被请求的协商变元资源被配置为在透明内容协商中使用自己,因此在一个协商处理中不是一个合适的重点。
507 服务器无法存储完成请求所必须的内容。这个状况被认为是临时的。WebDAV (RFC 4918)
509 服务器达到带宽限制。这不是一个官方的状态码,但是仍被广泛使用。
510 获取资源所需要的策略并没有没满足。(RFC 2774)

[转]HTTPS为什么安全 &分析 HTTPS 连接建立全过程

本文将分两个专题去理解HTTPS。

专题一:HTTPS为什么安全

1、http为什么不安全?

http协议属于明文传输协议,交互过程以及数据传输都没有进行加密,通信双方也没有进行任何认证,通信过程非常容易遭遇劫持、监听、篡改,严重情况下,会造成恶意的流量劫持等问题,甚至造成个人隐私泄露(比如银行卡卡号和密码泄露)等严重的安全问题。

可以把http通信比喻成寄送信件一样,A给B寄信,信件在寄送过程中,会经过很多的邮递员之手,他们可以拆开信读取里面的内容(因为http是明文传输的)。A的信件里面的任何内容(包括各类账号和密码)都会被轻易窃取。除此之外,邮递员们还可以伪造或者修改信件的内容,导致B接收到的信件内容是假的。

比如常见的,在http通信过程中,“中间人”将广告链接嵌入到服务器发给用户的http报文里,导致用户界面出现很多不良链接; 或者是修改用户的请求头URL,导致用户的请求被劫持到另外一个网站,用户的请求永远到不了真正的服务器。这些都会导致用户得不到正确的服务,甚至是损失惨重。

2、https如何保证安全?

要解决http带来的问题,就要引入加密以及身份验证机制。

如果Server(以后简称服务器)给Client(以后简称 客户端)的消息是密文的,只有服务器和客户端才能读懂,就可以保证数据的保密性。同时,在交换数据之前,验证一下对方的合法身份,就可以保证通信双方的安全。那么,问题来了,服务器把数据加密后,客户端如何读懂这些数据呢?这时服务器必须要把加密的密钥(对称密钥,后面会详细说明)告诉客户端,客户端才能利用对称密钥解开密文的内容。但是,服务器如果将这个对称密钥以明文的方式给客户端,还是会被中间人截获,中间人也会知道对称密钥,依然无法保证通信的保密性。但是,如果服务器以密文的方式将对称密钥发给客户端,客户端又如何解开这个密文,得到其中的对称密钥呢?

说到这里,大家是不是有点儿糊涂了?一会儿密钥,一会儿对称密钥,都有点儿被搞晕的节奏。在这里,提前给大家普及一下,这里的密钥,指的是非对称加解密的密钥,是用于TLS握手阶段的; 对称密钥,指的是对称加解密的密钥,是用于后续传输数据加解密的。下面将详细说明。

这时,我们引入了非对称加解密的概念。在非对称加解密算法里,公钥加密的数据,有且只有唯一的私钥才能够解密,所以服务器只要把公钥发给客户端,客户端就可以用这个公钥来加密进行数据传输的对称密钥。客户端利用公钥将对称密钥发给服务器时,即使中间人截取了信息,也无法解密,因为私钥只部署在服务器,其他任何人都没有私钥,因此,只有服务器才能够解密。服务器拿到客户端的信息并用私钥解密之后,就可以拿到加解密数据用的对称密钥,通过这个对称密钥来进行后续通信的数据加解密。除此之外,非对称加密可以很好的管理对称密钥,保证每次数据加密的对称密钥都是不相同的,这样子的话,即使客户端病毒拉取到通信缓存信息,也无法窃取正常通信内容。

上述通信过程,可以画成以下交互图:

但是这样似乎还不够,如果通信过程中,在三次握手或者客户端发起HTTP请求过程中,客户端的请求被中间人劫持,那么中间人就可以伪装成“假冒客户端”和服务器通信;中间人又可以伪装成“假冒服务器”和客户端通信。接下来,我们详细阐述中间人获取对称密钥的过程:

中间人在收到服务器发送给客户端的公钥(这里是“正确的公钥”)后,并没有发给客户端,而是中间人将自己的公钥(这里中间人也会有一对公钥和私钥,这里称呼为“伪造公钥”)发给客户端。之后,客户端把对称密钥用这个“伪造公钥”加密后,发送过程中经过了中间人,中间人就可以用自己的私钥解密数据并拿到对称密钥,此时中间人再把对称密钥用“正确的公钥”加密发回给服务器。此时,客户端、中间人、服务器都拥有了一样的对称密钥,后续客户端和服务器的所有加密数据,中间人都可以通过对称密钥解密出来。

中间人获取对称密钥的过程如下:

为了解决此问题,我们引入了数字证书的概念。服务器首先生成公私钥,将公钥提供给相关机构(CA),CA将公钥放入数字证书并将数字证书颁布给服务器,此时服务器就不是简单的把公钥给客户端,而是给客户端一个数字证书,数字证书中加入了一些数字签名的机制,保证了数字证书一定是服务器给客户端的。中间人发送的伪造证书,不能够获得CA的认证,此时,客户端和服务器就知道通信被劫持了。加入了CA数字签名认证的SSL会话过程如下所示:

所以综合以上三点:非对称加密算法(公钥和私钥)交换对称密钥+数字证书验证身份(验证公钥是否是伪造的)+利用对称密钥加解密后续传输的数据=安全

3、https协议简介

为什么是简单地介绍https协议呢?因为https涉及的东西实在太多了,尤其是其中的加解密算法,十分复杂,作者本身对这些算法也研究不完,只是懂其中的一些皮毛而已。这部分仅仅是简单介绍一些关于https的最基本原理,为后面分析https的建立过程以及https优化等内容打下理论基础。

3.1 对称加密算法

对称加密是指:加密和解密使用相同密钥的算法。它要求发送方和接收方在安全通信之前,商定一个对称密钥。对称算法的安全性完全依赖于密钥,密钥泄漏就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信至关重要。

3.1.1 对称加密又分为两种模式:流加密和分组加密

流加密是将消息作为字节流对待,并且使用数学函数分别作用在每一个字节位上。使用流加密时,每加密一次,相同的明文位会转换成不同的密文位。流加密使用了密钥流生成器,它生成的字节流与明文字节流进行异或,从而生成密文。

分组加密是将消息划分为若干个分组,这些分组随后会通过数学函数进行处理,每次一个分组。假设使用64位的分组密码,此时如果消息长度为640位,就会被划分成10个64位的分组(如果最后一个分组长度不到64,则用0补齐之后加到64位),每个分组都用一系列数学公式进行处理,最后得到10个加密文本分组。然后,将这条密文消息发送给对端。对端必须拥有相同的分组密码,以相反的顺序对10个密文分组使用前面的算法解密,最终得到明文消息。比较常用的分组加密算法有DES、3DES、AES。其中DES是比较老的加密算法,现在已经被证明不安全。而3DES是一个过渡的加密算法,相当于在DES基础上进行三重运算来提高安全性,但其本质上还是和DES算法一致。而AES是DES算法的替代算法,是现在最安全的对称加密算法之一。

3.1.2 对称加密算法的优缺点:

优点:计算量小、加密速度快、加密效率高。

缺点:

(1)交易双方都使用同样密钥,安全性得不到保证;

(2)每次使用对称加密算法时,都需要使用其他人不知道的惟一密钥,这会使得发收信息双方所拥有的钥匙数量呈几何级数增长,密钥管理成为负担。

3.2 非对称加密算法

在非对称密钥交换算法出现以前,对称加密的最主要缺陷就是不知道如何在通信双方之间传输对称密钥,而又不让中间人窃取。非对称密钥交换算法诞生之后,专门针对对称密钥传输做加解密,使得对称密钥的交互传输变得非常安全了。

非对称密钥交换算法本身非常复杂,密钥交换过程涉及到随机数生成,模指数运算,空白补齐,加密,签名等等一系列极其复杂的过程,作者本人也没有研究完全透彻。常见的密钥交换算法有RSA,ECDHE,DH,DHE等算法。涉及到比较复杂的数学问题。其中,最经典也是最常用的是RSA算法。

RSA:诞生于1977年,经过了长时间的破解测试,算法安全性很高,最重要的是,算法实现非常简单。缺点就是需要比较大的质数(目前常用的是2048位)来保证安全强度,极其消耗CPU运算资源。RSA是目前唯一一个既能用于密钥交换又能用于证书签名的算法,RSA 是最经典,同时也是最常用的是非对称加解密算法。

3.2.1 非对称加密相比对称加密更加安全,但也存在两个致命的缺点:

(1)CPU计算资源消耗非常大。一次完全TLS握手,密钥交换时的非对称解密计算量占整个握手过程的90%以上。而对称加密的计算量只相当于非对称加密的0.1%。如果后续的应用层数据传输过程也使用非对称加解密,那么CPU性能开销太庞大,服务器是根本无法承受的。赛门特克给出的实验数据显示,加解密同等数量的文件,非对称算法消耗的CPU资源是对称算法的1000倍以上。

(2)非对称加密算法对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是2048位,意味着待加密内容不能超过256个字节。

所以非对称加解密(极端消耗CPU资源)目前只能用来作对称密钥交换或者CA签名,不适合用来做应用层内容传输的加解密。

3.3 身份认证

https协议中身份认证的部分是由CA数字证书完成的,证书由公钥、证书主体、数字签名等内容组成。在客户端发起SSL请求后,服务端会将数字证书发给客户端,客户端会对证书进行验证(验证这张证书是否是伪造的?也就是公钥是否是伪造的),如果证书不是伪造的,客户端就获取用于对称密钥交换的非对称密钥(获取公钥)。

3.3.1 数字证书有三个作用:

1、身份授权。确保浏览器访问的网站是经过CA验证的可信任的网站。

2、分发公钥。每个数字证书都包含了注册者生成的公钥(验证确保是合法的,非伪造的公钥)。在SSL握手时会通过certificate消息传输给客户端。

3、验证证书合法性。客户端接收到数字证书后,会对证书合法性进行验证。只有验证通过后的证书,才能够进行后续通信过程。

3.3.2 申请一个受信任的CA数字证书通常有如下流程:

(1)公司(实体)的服务器生成公钥和私钥,以及CA数字证书请求。

(2)RA(证书注册及审核机构)检查实体的合法性(在注册系统里面是否注册过的正规公司)。

(3)CA(证书签发机构)签发证书,发送给申请者实体。

(4)证书更新到repository(负责数字证书及CRL内容存储和分发),实体终端后续从repository更新证书,查询证书状态等。

3.4 数字证书验证

申请者拿到CA的证书并部署在网站服务器端,那浏览器发起握手并接收到证书后,如何确认这个证书就是CA签发的呢?怎样避免第三方伪造这个证书?答案就是数字签名(digital signature)。数字签名是证书的防伪标签,目前使用最广泛的SHA-RSA(SHA用于哈希算法,RSA用于非对称加密算法)。数字签名的制作和验证过程如下:

1、数字签名的签发。首先是使用哈希函数对待签名内容进行安全哈希,生成消息摘要,然后使用CA自己的私钥对消息摘要进行加密。

2、数字签名的校验。使用CA的公钥解密签名,然后使用相同的签名函数对签名证书内容进行签名,并和服务端数字签名里的签名内容进行比较,如果相同就认为校验成功。


需要注意的是:

(1)数字签名签发和校验使用的非对称密钥是CA自己的公钥和私钥,跟证书申请者(提交证书申请的公司实体)提交的公钥没有任何关系。

(2)数字签名的签发过程跟公钥加密的过程刚好相反,即是用私钥加密,公钥解密。(一对公钥和私钥,公钥加密的内容只有私钥能够解密;反过来,私钥加密的内容,也就有公钥才能够解密)

(3)现在大的CA都会有证书链,证书链的好处:首先是安全,保持CA的私钥离线使用。第二个好处是方便部署和撤销。这里为啥要撤销呢?因为,如果CA数字证书出现问题(被篡改或者污染),只需要撤销相应级别的证书,根证书依然是安全的。

(4)根CA证书都是自签名,即用自己的公钥和私钥完成了签名的制作和验证。而证书链上的证书签名都是使用上一级证书的非对称密钥进行签名和验证的。

(5)怎样获取根CA和多级CA的密钥对?还有,既然是自签名和自认证,那么它们是否安全可信?这里的答案是:当然可信,因为这些厂商跟浏览器和操作系统都有合作,它们的根公钥都默认装到了浏览器或者操作系统环境里。

3.5 数据完整性验证

数据传输过程中的完整性使用MAC算法来保证。为了避免网络中传输的数据被非法篡改,或者数据比特被污染,SSL利用基于MD5或SHA的MAC算法来保证消息的完整性(由于MD5在实际应用中存在冲突的可能性比较大,所以尽量别采用MD5来验证内容一致性)。 MAC算法是在密钥参与下的数据摘要算法,能将密钥和任意长度的数据转换为固定长度的数据。发送者在密钥的作用下,利用MAC算法计算出消息的MAC值,并将其添加在需要发送的消息之后,并发送给接收者。接收者利用同样的密钥和MAC算法计算出消息的MAC值,并与接收到的MAC值比较。如果二者相同,则报文没有改变;否则,报文在传输过程中被修改或者污染,接收者将丢弃该报文。 SHA也不能使用SHA0和SHA1,山东大学的王小云教授(很牛的一个女教授,大家有兴趣可以上网搜索一下她的事迹)在2005年就宣布破解了 SHA-1完整版算法,并获得了业内专家的认可。微软和google都已经宣布16年及17年之后不再支持sha1签名证书。

专题二:实际抓包分析

本文对百度搜索进行了两次抓包,第一次抓包之前清理了浏览器的所有缓存;第二次抓包是在第一次抓包后的半分钟内。

百度在2015年已经完成了百度搜索的全站https,这在国内https发展中具有重大的意义(目前BAT三大家中,只有百度宣称自己完成了全站HTTPS)。所以这篇文章就以www.baidu.com为例进行分析。

同时,作者采用的是chrome浏览器,chrome支持SNI (server Name Indication) 特性,对于HTTPS性能优化有很大的用处。

注:SNI是为了解决一个服务器使用多个域名和证书的SSL/TLS扩展。一句话简述它的工作原理就是:在和服务器建立SSL连接之前,先发送要访问的域名(hostname),这样服务器根据这个域名返回一个合适的证书。目前,大多数操作系统和浏览器都已经很好地支持SNI扩展,OpenSSL 0.9.8已经内置这一功能,新版的nginx和apache也支持SNI扩展特性。

本文抓包访问的URL为:http://www.baidu.com/

(如果是https://www.baidu.com/,则以下结果不一样!)

抓包结果:

可以看到,百度采用以下策略:

(1)对于高版本浏览器,如果支持 https,且加解密算法在TLS1.0 以上的,都将所有 http请求重定向到 https请求

(2)对于https请求,则不变。

【详细解析过程】

1、TCP三次握手

可以看到,我的电脑访问的是http://www.baidu.com/,在初次建立三次握手的时候, 客户端是去连接 8080端口的(我所在小区网络总出口做了一层总代理,因此,客户端实际和代理机做的三次握手,代理机再帮客户端去连接百度服务器)

2、tunnel建立

由于小区网关设置了代理访问,因此,在进行https访问的时候,客户端需要和代理机做”HTTPS CONNECT tunnel” 连接(关于”HTTPS CONNECT tunnel”连接,可以理解为:虽然后续的https请求都是代理机和百度服务器进行公私钥连接和对称密钥交换,以及数据通信;但是,有了隧道连接之后,可以认为客户端也在直接和百度服务器进行通信。)

fiddler抓包结果:

3、client hello

3.1 随机数

在客户端问候中,有四个字节以Unix时间格式记录了客户端的协调世界时间(UTC)。协调世界时间是从1970年1月1日开始到当前时刻所经历的秒数。在这个例子中,0x2516b84b就是协调世界时间。在他后面有28字节的随机数(random_C),在后面的过程中我们会用到这个随机数。

3.2 SID(Session ID)

如果出于某种原因,对话中断,就需要重新握手。为了避免重新握手而造成的访问效率低下,这时候引入了session ID的概念, session ID的思想很简单,就是每一次对话都有一个编号(session ID)。如果对话中断,下次重连的时候,只要客户端给出这个编号,且服务器有这个编号的记录,双方就可以重新使用已有的“对称密钥”,而不必重新生成一把。

因为我们抓包的时候,是几个小时内第一次访问https://www.baodu.com ,因此,这里并没有Session ID.(稍会儿我们会看到隔了半分钟,第二次抓包就有这个Session ID)

session ID是目前所有浏览器都支持的方法,但是它的缺点在于session ID往往只保留在一台服务器上。所以,如果客户端的请求发到另一台服务器(这是很有可能的,对于同一个域名,当流量很大的时候,往往后台有几十台RS机在提供服务),就无法恢复对话。session ticket就是为了解决这个问题而诞生的,目前只有Firefox和Chrome浏览器支持。

3.3 密文族(Cipher Suites)

RFC2246中建议了很多中组合,一般写法是”密钥交换算法-对称加密算法-哈希算法,以“TLS_RSA_WITH_AES_256_CBC_SHA”为例:

(a)TLS为协议,RSA为密钥交换的算法;

(b)AES_256_CBC是对称加密算法(其中256是密钥长度,CBC是分组方式);

(c)SHA是哈希的算法。

浏览器支持的加密算法一般会比较多,而服务端会根据自身的业务情况选择比较适合的加密组合发给客户端。(比如综合安全性以及速度、性能等因素)

3.4 Server_name扩展(一般浏览器也支持 SNI扩展)

当我们去访问一个站点时,一定是先通过DNS解析出站点对应的ip地址,通过ip地址来访问站点,由于很多时候一个ip地址是给很多的站点公用,因此如果没有server_name这个字段,server是无法给与客户端相应的数字证书的,Server_name扩展则允许服务器对浏览器的请求授予相对应的证书。

服务器回复

(包括Server Hello,Certificate,Certificate Status)

服务器在收到client hello后,会回复三个数据包,下面分别看一下:

4、Server Hello

4.1、我们得到了服务器的以Unix时间格式记录的UTC和28字节的随机数 (random_S)。

4.2、Seesion ID,服务端对于session ID一般会有三种选择  (稍会儿我们会看到隔了半分钟,第二次抓包就有这个Session ID):

(1)恢复的session ID:我们之前在client hello里面已经提到,如果client hello里面的session ID在服务端有缓存,服务端会尝试恢复这个session;

(2)新的session ID:这里又分两种情况,第一种是client hello里面的session ID是空值,此时服务端会给客户端一个新的session ID,第二种是client hello里面的session ID此服务器并没有找到对应的缓存,此时也会回一个新的session ID给客户端;

(3)NULL:服务端不希望此session被恢复,因此session ID为空。

4.3、我们记得在client hello里面,客户端给出了多种加密族 Cipher,而在客户端所提供的加密族中,服务端挑选了“TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256”

(a)TLS为协议,RSA为密钥交换的算法;

(b)AES_256_CBC是对称加密算法(其中256是密钥长度,CBC是分组方式);

(c)SHA是哈希的算法。

这就意味着服务端会使用ECDHE-RSA算法进行密钥交换,通过AES_128_GCM对称加密算法来加密数据,利用SHA256哈希算法来确保数据完整性。

5、Certificate

在前面的https原理研究中,我们知道为了安全的将公钥发给客户端,服务端会把公钥放入数字证书中并发给客户端(数字证书可以自签发,但是一般为了保证安全会有一个专门的CA机构签发),所以这个报文就是数字证书,4097 bytes就是证书的长度。

我们打开这个证书,可以看到证书的具体信息,这个具体信息通过抓包报文的方式不是太直观,可以在浏览器上直接看。(点击chrome浏览器左上方的绿色锁型按钮)

6、Server Hello Done

我们抓的包是将 Server Hello Done  和 server key exchage 合并的包:

7、客户端验证证书真伪性

客户端验证证书的合法性,如果验证通过才会进行后续通信,否则根据错误情况不同做出提示和操作,合法性验证包括如下:

(1)证书链的可信性trusted certificate path,方法如前文所述;

(2)证书是否吊销revocation,有两类方式离线CRL与在线OCSP,不同的客户端行为会不同;

(3)有效期expiry date,证书是否在有效时间范围;

(4)域名domain,核查证书域名是否与当前的访问域名匹配,匹配规则后续分析;

8、秘钥交换

这个过程非常复杂,大概总结一下:

(1)首先,客户端利用CA数字证书实现身份认证,利用非对称加密协商对称密钥。

(2)客户端会向服务器传输一个“pubkey”随机数,服务器收到之后,利用特定算法生成另外一个“pubkey”随机数,客户端利用这两个“pubkey”随机数生成一个 pre-master 随机数。

(3)客户端利用自己在 client hello 里面传输的随机数 random_C,以及收到的 server hello 里面的随机数 random_S,外加 pre-master 随机数,利用对称密钥生成算法生成 对称密钥enc_key:enc_key=Fuc(random_C, random_S, Pre-Master)

9、生成session ticket

如果出于某种原因,对话中断,就需要重新握手。为了避免重新握手而造成的访问效率低下,这时候引入了session ID的概念, session ID(以及session ticke)的思想很简单,就是每一次对话都有一个编号(session ID)。如果对话中断,下次重连的时候,只要客户端给出这个编号,且服务器有这个编号的记录,双方就可以重新使用已有的“对话密钥”,而不必重新生成一把。

因为我们抓包的时候,是几个小时内第一次访问 https://www.baodu.com 首页,因此,这里并没有 Session ID.(稍会儿我们会看到隔了半分钟,第二次抓包就有这个Session ID)

session ID是目前所有浏览器都支持的方法,但是它的缺点在于session ID往往只保留在一台服务器上。所以,如果客户端的请求发到另一台服务器,就无法恢复对话。session ticket就是为了解决这个问题而诞生的,目前只有Firefox和Chrome浏览器支持。

后续建立新的https会话,就可以利用 session ID 或者 session Tickets , 对称秘钥可以再次使用,从而免去了 https 公私钥交换、CA认证等等过程,极大地缩短 https 会话连接时间。

10、利用对称秘钥传输数据


三、半分钟后,再次访问百度:

有这些大的不同:

由于服务器和浏览器缓存了 Session ID 和 Session Tickets,不需要再进行 公钥证书传递,CA认证,生成 对称密钥等过程,直接利用半分钟前的对称密钥加解密数据进行会话。

1、Client Hello

2、Server Hello

[转]HTTP/2 对 Web 性能的影响

【上】

一.前言

HTTP/2 于 2015 年 5 月正式推出。诞生以来,它就一直在影响着网络性能最佳实践。在本篇文章中,我们将讨论 HTTP/2 的二进制帧、延迟削减、潜在利弊以及相应的应对措施。
超文本传输协议(简称 HTTP)正是万维网与网络空间的基石。现在,HTTP 听起来已经有些过时,毕竟该协议中使用最广泛的版本——HTTP 1.1,已快迎来它的第二十个年头。时光回溯到1997年,HTTP1.1刚刚出现的年代,当时,软驱与调制解调器还是 PC 设备的必备周边产品,Java 也仅仅是一个刚刚崭露头角的、前途一片光明的编程语言。
而今,2015 年 5 月,HTTP/2 正式亮相,致力于解决 HTTP 1.1 在现代网络时代下无法应对的某些重大性能难题。过去一年以来,越来越多的浏览器、Web 服务、商用代理以及主要内容交付网络开始支持 HTTP/2。
遗憾的是,对于负责编写 Web 代码的开发人员而言,HTTP/2 的过渡工作并不简单,所谓的速度提升也不会自行出现,必要的 Web APM 工具和厂商仍然是当今时代不可或缺的,例如 OneAPM Browser Insight、Newrelic、APPdynamic 等等。
如何才能采用这个新协议打造高性能 Web 应用,该怎样处理那些尚不支持该协议的现有工具——例如 debug 代理工具,这些问题对所有人来说仍是一个挑战。今天的文章将向您介绍 HTTP/2,以及其如何改变 Web 性能的最佳实践。

二.二进制帧:HTTP/2的「基本单位」

HTTP 1.1 的一大优势(至少相较于非安全连接而言)在于其支持在端口 80 的telnet 会话中利用文本与 Web 服务器进行交互:在大多数 Web 服务器上输入 GET/HTTP/1.1,都能返回一个 HTML 文档。由于这是一项文本协议,因此其调试工作相对简单。
相对于 HTTP1.1 这个文本协议,HTTP/2 中的请求与响应则通过二进制帧流的形式来表现,我们将其称为 HTTP/2 RFC 中的「基本协议单位」。每一帧都拥有自己的类型,用于实现不同的作用。考虑到 HTTP 1.1 将会「永远」存在(毕竟 Gopher 协议都还在用!),因此 HTTP/2 的作者们要求,HTTP/2 请求的二进制帧都必须被映射到 HTTP 1.1 请求上去,从而确保其向下兼容的能力。
当然,HTTP/2 当中也有一些其他新特性,无法映射至 HTTP 1.1。例如,服务器推送(也叫“缓存推送”)和流重置都是利用二进制帧类型实现的新特性。帧也可以支持优先级排序,允许客户端向服务器发送排序,从而优先处理一部分资产类别。
除了使用 Wireshark 2.0 之外,对个别二进制帧进行查看的最简便方法就是利用谷歌 Chrome 浏览器的 net-internals 标签(在浏览器地址栏中输入chrome://net-internals/#http2)。由于理解大型网页的数据通常比较困难,Rebecca Murphey 特意编写了一款极为实用的可视化工具,从而将其显示在命令行中。
除此之外,这个用于获取资产的协议还可以显示在 Chrome Web 的开发者工具当中–只需右键点击列标题,接着选择「协议」即可:

在谷歌Chrome开发者工具中查看协议类型。
这里列出的所有 HTTP/2 请求都使用通过传输层安全(TLS)建立的安全连接。各主流浏览器都要求 HTTP/2 连接是安全的。这样做是有切实理由的:TLS 的一套称为应用层协议协商(ALPN)的扩展,让服务器知道浏览器支持 HTTP2(除了其他协议以外),从而避免了额外的数据往来。这也能保住那些无法解读 HTTP/2 的服务,例如代理——只看得见传输线路上的加密数据。

三.多路复用减少延迟

HTTP 1.1 的一大问题是延迟,换而言之就是它花在提出请求和接受响应上的时间。随着典型网页中图片数量、JavaScript 和 CSS 的使用量不断增加,这个问题日益严重。每获取一项资产,通常都得新建一个 TCP 连接。
这种需求因为两个理由很重要:每台主机能同时打开的 TCP 连接数受浏览器的限制;新建连接都会引发性能损失。如果物理服务器离用户很远(如:一位新加坡用户向美国东海岸数据中心请求一个页面),延迟会变多。这不是罕见的情况——近期一份报告表示全球 70% 以上的互联网流通都会通过北佛吉尼亚一些不知名的数据中心。
其实对于 Web 页面的优化从前端页面方面反而可能见效比较大,我们都知道页面资源对响应速度的影响是非常巨大的,常见的图片压缩、css 聚合都可以帮助我们优化 Web 性能,问题就是如何找到相应的页面慢加载元素。
这也是国内外 APM 行业兴起的最初原因之一。
拿国内的一个页面优化工具 Browser Insight 举例,这种通过页面插码来获取真实用户体验的 APM 工具,虽然部署起来有些麻烦,但是这类的工具也没有更好的部署方法,手动的反而更稳定。

HTTP 1.1 提供了多种方案以解决延迟问题,包括通道传输与 Keep-Alive header。然而,通道传输从未被广泛采纳过,而 Keep-Alive header 则饱受 head line 阻塞的困扰:只有在当前请求必须彻底完成后,下一请求才能被发送出去。
在 HTTP/2 当中,多条资产请求可以重复利用单一 TCP 连接。与使用 Keep-Alive header 的 HTTP 1.1 请求不同,HTTP/2 的请求与响应二进制帧以交错方式进行,线头阻塞问题也不复存在。建立连接的成本(即著名的的‘三方握手’)在每台主机上只进行一次。多路复用对于安全连接来说尤为重要,因为多次 TLS 协商方案的性能成本将会得到显著提高。
在 HTTP/2 中,单一主机内的多资产请求只使用单一 TCP 连接。

四.总结

其实 Web 性能优化已经不是一个新的话题了,从 21 世纪初期直到现在,很多成熟的互联网公司已经开始关注除了产品、研发之外的工作,例如用户体验、性能优化等与产品使用者息息相关的事情,伴随着的就是 APM 行业的全面兴起。
本文主要和大家聊了一些关于 http1 和 http2 有关的基础内容,之后还会有一篇,预计与大家分享一些 http2 使用利弊、以及正在进行的相关工作等等。
Browser Insight 是一个基于真实用户的 Web 前端性能监控平台,能够帮大家定位网站性能瓶颈,网站加速效果可视化;支持浏览器、微信、App 浏览 HTML 和 HTML5 页面。想阅读更多技术文章,请访问 OneAPM 官方技术博客。
本文转自 OneAPM 官方博客
【下】

一.前言

我们在 HTTP/2 对 Web 性能的影响(上)已经和大家分享了一些关于 Http2 的二项制帧、多用复路以及 APM 工具等,本文作为姊妹篇,主要从 http2 对 Web 性能的影响、http2 使用的利弊以及一些正在进行中的相关工作等方面与大家进行分享。

二.Web 性能影响:与内联、级联及图像精灵说再见?

HTTP/2 多路复用对前端 Web 开发人员造成了深远的影响。长久以来,人们用尽方法,试图通过捆绑相关资产来削减连接的数量,而现在这一切都不需要了。人们曾经尝试过的方法包括:

  • JavaScript 与 CSS 文件级联:将多个小文件合成一个大文件,从而降低总体请求数量。
  • 图像精灵:将多个小图像合成一张大图像。
  • 域名分片:在多个域之间发送静态资产请求,从而增加浏览器所能允许的总体开放 TCP 连接数量。
  • 内联资产:将资产同 HTML 文档源绑定,包括 base-64 编码图片,以及直接写入 script 标签的 JavaScript 代码。

因为不需要再绑定资产,我们就有了更多机会将 Web 应用程序中的小片段加以缓存。举个例子可以帮我们更好的理解这一点:
一个级联且指纹验证型 CSS 文件被解绑为四个较小的指纹验证文件。
常见的级联模式是将一个应用程序内不同页面的样式表文件进行绑定,形成单一的 CSS 文件,以减少资产请求的数量。这个大文件随后会通过文件名内的 MD5 哈希值进行指纹校验,确保其能够被浏览器主动缓存。遗憾的是,这样的解决方案意味着,当站点的可视化布局中出现了任何一点小的改变,如标题字体的改变,都需要重新下载整个级联文件。
当对小型资产文件进行指纹校验时,相当一部分的 JavaScript 与 CSS 组件都不会频繁产生变动,因此可以被浏览器缓存 –也就是说,任何一个单一功能的小型重构,不会再导致大量的 JavaScript 应用程序代码或者 CSS 失效。
最后,级联机制的消失能够降低前端构建基础的复杂性。与以往通过一系列预置步骤来级联资产不同,现在它们作为小型文件,可以直接被放入 HTML 文档中。

三.实际使用 HTTP/2 的潜在弊端

仅仅针对支持 HTTP/2 客户端而做出的各类优化,意味着那些不支持 HTTP/2 的浏览器可能因此陷入不利境地。那些“有年头”的浏览器们仍然倾向于绑定资产,以此降低连接数量。截至 2016 年 2 月,caniuse.com 网站报道称,全球浏览器中能够支持 HTTP/2 的占比 71%。与之前浏览器们决定放弃支持 IE 8.0 时一样,支持 HTTP/2 或采取某种混合作业的方式——这样的决定只能根据各个网站身的相关数据来做出。
但是我们相信大规模支持肯定是不可避免的,就像一开始只有 Chrome 浏览器支持 window.performance 接口,方便一些 Web 工具进行数据的采集,像上面说过的Browser Insight ,我曾经和他们的技术支持聊过,就是靠这种方式来实时的采集用户对网站的访问信息等。之后,大势所趋,各个浏览器厂商都纷纷开放了相关接口
正如可汗学院的博文所述,他们曾分析其网站上的 HTTP/2 流量,事实上,拆分大量资产会增加所传输字节的总量。而使用zlib压缩单一大型文件,比压缩多个小型文件要更有效率。对于拥有成百上千解绑资产的 HTTP/2 站点来说,这种效应更为显著。
在浏览器中使用 HTTP/2 还要求我们通过 TLS 进行资产传递。对于菜鸟们来说,设置 TLS 证书就是个烦人的活儿。幸运的是,诸如 Let’s Encrypt 的开源项目正努力让证书注册工作变得更加便捷。

四.仍在进行中的工作

大部分用户并不在意你的站点用了啥协议——他们只想要它速度快,运行如人预期。虽然 HTTP/2 已经获得正式批准快一年了,开发人员还在学习如何利用它来建立更快速网站的最优实践。换用 HTTP/2 的好处更多取决于具体站点的架构情况以及使用现代浏览器的用户比率。再有就是,调试新协议很有挑战性,更易用的开发工具还在研制中。
虽然有这些挑战,HTTP/2 的采纳度仍在增加。根据研究人员扫描流行网站属性的结果,排名前列的站点中使用 HTTP/2 的一直在增加,特别是 CloudFlare 和WordPress 在 2015 年宣布提供支持之后。在考虑转换到新协议时,很重要的一点是利用 Browser insight 和 NewRelic 之类的 APM 工具,仔细测量资源和页面在不同环境下的加载时间。
如下图所示,可以看到每一次慢加载的详细情况,非常方便。

供应商和专业网站人员都熟悉这一转换背后的含义,从真实用户数据中做出判断才是关键的。在网站臃肿危机的当下,无论何种协议,都应该以削减资源数量为目标。
在此 HTTP/2 系列的第二部分中,我们会聚焦于如何在服务上实现 HTTP/2 和调试真实网络通信的具体实现细节。
本文作者为 Clay Smith,由 OneAPM 产品运营进行翻译编辑
原文地址:https://dzone.com/articles/how-http2-is-changing-web-performance-best-practic
Browser Insight 是一个基于真实用户的 Web 前端性能监控平台,能够帮大家定位网站性能瓶颈,网站加速效果可视化;支持浏览器、微信、App 浏览 HTML 和 HTML5 页面。想阅读更多技术文章,请访问 OneAPM 官方技术博客。
本文转自 OneAPM 官方博客