精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Go 1.20 相比 Go 1.19 有哪些值得注意的改動?

開發(fā) 前端
Rewrite?? 鉤子和 ??ProxyRequest?? 類型為 ??httputil.ReverseProxy?? 提供了更現(xiàn)代化、更安全、更靈活的定制方式,推薦在新的 Go 1.20+ 項目中使用 ??Rewrite?? 來替代 ??Director??。

https://go.dev/doc/go1.20

Go 1.20 值得關(guān)注的改動:

  1. 語言 Slice to Array 轉(zhuǎn)換: Go 1.20 擴(kuò)展了 Go 1.17 的功能,允許直接將 slice 轉(zhuǎn)換為固定大小的數(shù)組,例如使用 [4]byte(x) 替代 *(*[4]byte)(x)。
  2. unsafe 包更新: 新增 SliceData 、 String 和 StringData 函數(shù),與 Go 1.17 的 Slice 函數(shù)一起,提供了不依賴具體內(nèi)存布局來構(gòu)造和解構(gòu) slice 與 string 值的能力。
  3. 規(guī)范更新 結(jié)構(gòu)體與數(shù)組比較: 語言規(guī)范明確了結(jié)構(gòu)體按字段聲明順序、數(shù)組按索引順序逐個比較,并在遇到第一個不匹配時即停止,這澄清了潛在的歧義,但與實際實現(xiàn)行為一致。
  4. 泛型 comparable 約束放寬: 即使類型參數(shù)不是嚴(yán)格可比較的(運行時比較可能 panic),它們現(xiàn)在也可以滿足 comparable 約束,允許將接口類型等用作泛型映射的鍵。
  5. Runtime 垃圾回收器(Garbage Collector)優(yōu)化: 通過重組內(nèi)部數(shù)據(jù)結(jié)構(gòu),減少了內(nèi)存開銷并提高了 CPU 效率(整體 CPU 性能提升高達(dá) 2%),同時改善了 goroutine 輔助(goroutine assists)在某些情況下的行為穩(wěn)定性。
  6. 錯誤處理 多錯誤包裝(Wrapping multiple errors): 擴(kuò)展了錯誤包裝功能,允許一個錯誤通過實現(xiàn)返回 []error 的 Unwrap 方法來包裝多個錯誤;errors.Is 、 errors.As 、 fmt.Errorf 的 %w 以及新增的 errors.Join 函數(shù)均已支持此特性。
  7. net/http 新增 ResponseController: 引入 net/http.ResponseController 類型,提供了一種比可選接口更清晰、更易于發(fā)現(xiàn)的方式來訪問每個請求的擴(kuò)展功能,例如設(shè)置讀寫截止時間。
  8. httputil.ReverseProxy 新增 Rewrite 鉤子: 新的 Rewrite 鉤子取代了原有的 Director 鉤子,提供了對入站和出站請求更全面的控制,并引入了 ProxyRequest.SetURL 和 ProxyRequest.SetXForwarded 等輔助方法,同時修復(fù)了潛在的安全問題。

下面是一些值得展開的討論:

Go 1.20 簡化了從 Slice 到 Array 的轉(zhuǎn)換語法

Go 1.17 版本引入了一個特性,允許將 slice 轉(zhuǎn)換為指向數(shù)組的指針。例如,如果有一個 slice x,你可以通過 *(*[4]byte)(x) 的方式將其轉(zhuǎn)換為一個指向包含 4 個字節(jié)的數(shù)組的指針。這種轉(zhuǎn)換有其局限性,它得到的是一個指針。

Go 1.20 在此基礎(chǔ)上更進(jìn)一步,允許直接將 slice 轉(zhuǎn)換為一個數(shù)組值(而不是數(shù)組指針)。現(xiàn)在,對于一個 slice x,你可以直接使用 [4]byte(x) 來獲得一個包含 4 個字節(jié)的數(shù)組。

這種轉(zhuǎn)換有一個前提條件:slice 的長度必須大于或等于目標(biāo)數(shù)組的長度。如果 slice x 的長度 len(x) 小于目標(biāo)數(shù)組的長度(在這個例子中是 4),那么在運行時會發(fā)生 panic。轉(zhuǎn)換的結(jié)果數(shù)組將包含 slice x 的前 N 個元素,其中 N 是目標(biāo)數(shù)組的長度。

讓我們看一個簡單的例子:

package main

import "fmt"

func main() {
    s := []byte{'g', 'o', 'l', 'a', 'n', 'g'}

    // Go 1.20: Direct conversion from slice to array
    var a4 [4]byte = [4]byte(s) // a4 will be {'g', 'o', 'l', 'a'}
    fmt.Printf("Array a4: %v\n", a4)
    fmt.Printf("Array a4 as string: %s\n", string(a4[:]))

    var a6 [6]byte = [6]byte(s) // a6 will be {'g', 'o', 'l', 'a', 'n', 'g'}
    fmt.Printf("Array a6: %v\n", a6)
    fmt.Printf("Array a6 as string: %s\n", string(a6[:]))

    // Contrast with Go 1.17 style (slice to array pointer)
    ap4 := (*[4]byte)(s) // ap4 is a pointer to the first 4 bytes of s's underlying array
    fmt.Printf("Array pointer ap4: %v\n", *ap4)
    // Modify the array through the pointer - this affects the original slice's underlying data!
    ap4[0] = 'G'
    fmt.Printf("Original slice s after modifying via pointer: %s\n", string(s)) // Output: Golang

    // Note: Direct conversion creates a *copy* of the data
    a4_copy := [4]byte(s)
    a4_copy[0] = 'F' // Modify the copy
    fmt.Printf("Original slice s after modifying direct conversion copy: %s\n", string(s)) // Output: Golang (unaffected)
    fmt.Printf("Copied array a4_copy: %s\n", string(a4_copy[:]))                     // Output: Fola

    // Example that would panic at runtime
    shortSlice := []byte{'h', 'i'}
    var a3 [3]byte = [3]byte(shortSlice) // This line would cause a panic: runtime error
    _ = a3
    _ = shortSlice // Avoid unused variable error
}
Array a4: [103 111 108 97]
Array a4 as string: gola
Array a6: [103 111 108 97 110 103]
Array a6 as string: golang
Array pointer ap4: [103 111 108 97]
Original slice s after modifying via pointer: Golang
Original slice s after modifying direct conversion copy: Golang
Copied array a4_copy: Fola
panic: runtime error: cannot convert slice with length 2 to array or pointer to array with length 3

這個改動使得代碼更簡潔、易讀,特別是在需要數(shù)組值而不是指針的場景下。需要注意的是,直接轉(zhuǎn)換為數(shù)組會復(fù)制數(shù)據(jù),而轉(zhuǎn)換為數(shù)組指針則不會,它只是創(chuàng)建一個指向 slice 底層數(shù)組對應(yīng)部分的指針。

unsafe 包新增 SliceData, String, StringData,完善了 Slice 和 String 的底層操作能力

Go 語言的 unsafe 包提供了一些低層次的操作,允許開發(fā)者繞過 Go 的類型安全和內(nèi)存安全檢查。雖然應(yīng)謹(jǐn)慎使用,但在某些高性能場景或與 C 語言庫交互時非常有用。

Go 1.20 在 unsafe 包中引入了三個新的函數(shù):SliceData、String 和 StringData。這些函數(shù),連同 Go 1.17 引入的 unsafe.Slice,提供了一套完整的、不依賴于 slice 和 string 內(nèi)部具體表示(這些表示在不同 Go 版本中可能變化)的構(gòu)造和解構(gòu)方法。

這意味著你可以編寫更健壯的底層代碼,即使未來 Go 改變了 slice 或 string 的內(nèi)部結(jié)構(gòu)(例如 SliceHeader 或 StringHeader 的字段),只要這些函數(shù)的語義不變,你的代碼依然能工作。

這四個核心函數(shù)的功能如下:

  1. SliceData(slice []T) *T :返回指向 slice 底層數(shù)組第一個元素的指針。如果 slice 為 nil,則返回 nil。它等價于 &slice[0],但即使 slice 為空(len(slice) == 0),只要容量不為零(cap(slice) > 0),它也能安全地返回底層數(shù)組的指針(而 &slice[0] 會 panic)。
  2. StringData(str string) *byte :返回指向 string 底層字節(jié)數(shù)組第一個元素的指針。如果 string 為空,則返回 nil。
  3. Slice[T any](ptr *T, lenOrCap int) []T (Go 1.17 引入,Go 1.20 仍重要):根據(jù)給定的指向類型 T 的指針 ptr 和指定的容量/長度 lenOrCap,創(chuàng)建一個新的 slice。這個 slice 的長度和容量都等于 lenOrCap,并且它的底層數(shù)組從 ptr 指向的內(nèi)存開始。注意:早期版本中此函數(shù)可能接受兩個參數(shù) len 和 cap,但在 Go 1.17 穩(wěn)定版及后續(xù)版本中,通常簡化為接受一個 lenOrCap 參數(shù),表示長度和容量相同。使用時請查閱對應(yīng) Go 版本的文檔確認(rèn)具體簽名。 假設(shè)這里我們使用 Go 1.17 引入的 Slice(ptr *ArbitraryType, cap IntegerType) []ArbitraryType 形式,它創(chuàng)建一個長度和容量都為 cap 的切片。或者更通用的 Slice(ptr *T, len int) []T,如果 Go 版本支持(創(chuàng)建 len==cap 的切片)。為了演示,我們假設(shè)存在一個函數(shù)能創(chuàng)建指定 len 和 cap 的 slice。*更新:根據(jù) Go 1.17 及后續(xù)文檔,unsafe.Slice(ptr *T, len IntegerType) []T 是標(biāo)準(zhǔn)形式,創(chuàng)建的 slice 長度和容量都為 len*。
  4. **String(ptr *byte, len int) string**:根據(jù)給定的指向字節(jié)的指針 ptr 和長度 len,創(chuàng)建一個新的 string。

使用 unsafe 包需要開發(fā)者深刻理解 Go 的內(nèi)存模型和潛在風(fēng)險。這些新函數(shù)提供了一個更穩(wěn)定、面向未來的接口來執(zhí)行這些底層操作,減少了代碼因 Go 內(nèi)部實現(xiàn)細(xì)節(jié)變化而失效的可能性。

語言規(guī)范明確了結(jié)構(gòu)體和數(shù)組的比較規(guī)則:逐元素比較,遇首個差異即停止

Go 語言規(guī)范定義了哪些類型是可比較的(comparable)。結(jié)構(gòu)體(struct)類型是可比較的,如果它的所有字段類型都是可比較的。數(shù)組(array)類型也是可比較的,如果它的元素類型是可比較的。

在 Go 1.20 之前,規(guī)范中關(guān)于結(jié)構(gòu)體和數(shù)組如何進(jìn)行比較的描述存在一定的模糊性。一種可能的解讀是,比較兩個結(jié)構(gòu)體或數(shù)組時,需要比較完它們所有的字段或元素,即使在中間已經(jīng)發(fā)現(xiàn)了不匹配。

Go 1.20 的規(guī)范明確了實際的比較行為,這也是 Go 編譯器一直以來的實現(xiàn)方式:

  1. 結(jié)構(gòu)體比較 :比較結(jié)構(gòu)體時,會按照字段在 struct 類型定義中出現(xiàn)的順序,逐個比較字段的值。一旦遇到第一個不匹配的字段,比較立即停止,并得出兩者不相等的結(jié)果。如果所有字段都相等,則結(jié)構(gòu)體相等。
  2. 數(shù)組比較 :比較數(shù)組時,會按照索引從 0 開始遞增的順序,逐個比較元素的值。一旦遇到第一個不匹配的元素,比較立即停止,并得出兩者不相等的結(jié)果。如果所有元素都相等,則數(shù)組相等。

這個規(guī)范的明確化主要影響的是包含不可比較類型(如接口類型,其動態(tài)值可能不可比較)時的 panic 行為。考慮以下情況:

package main

import "fmt"

type Data struct {
    ID   int
    Meta interface{} // interface{} or any
}

func main() {
    // Slices are not comparable
    slice1 := []int{1}
    slice2 := []int{1}

    d1 := Data{ID: 1, Meta: slice1}
    d2 := Data{ID: 2, Meta: slice2} // Different ID
    d3 := Data{ID: 1, Meta: slice2} // Same ID, different slice instance (but content might be same)
    d4 := Data{ID: 1, Meta: slice1} // Same ID, same slice instance

    // Comparison stops at the first differing field (ID)
    // The Meta field (interface{} holding a slice) is never compared.
    fmt.Printf("d1 == d2: %t\n", d1 == d2) // Output: false. Comparison stops after comparing ID (1 != 2). No panic.

    // Comparison proceeds to Meta field because IDs are equal (1 == 1).
    // Comparing interfaces containing slices will cause a panic.
    // fmt.Printf("d1 == d3: %t\n", d1 == d3) // This line would panic: runtime error: comparing uncomparable type []int

    // Comparison proceeds to Meta field.
    // Even though it's the *same* slice instance, the comparison itself panics.
    // fmt.Printf("d1 == d4: %t\n", d1 == d4) // This line would also panic: runtime error: comparing uncomparable type []int

    // Array comparison example
    type Info struct {
        Count int
    }
    // Arrays of comparable types are comparable
    a1 := [2]Info{{Count: 1}, {Count: 2}}
    a2 := [2]Info{{Count: 1}, {Count: 3}} // Differs at index 1
    a3 := [2]Info{{Count: 0}, {Count: 2}} // Differs at index 0

    fmt.Printf("a1 == a2: %t\n", a1 == a2) // Output: false. Stops after comparing a1[1] and a2[1].
    fmt.Printf("a1 == a3: %t\n", a1 == a3) // Output: false. Stops after comparing a1[0] and a3[0].

    _, _, _, _ = d1, d2, d3, d4
}

雖然這個規(guī)范的明確化沒有改變現(xiàn)有程序的行為(因為實現(xiàn)早已如此),但它消除了規(guī)范層面的歧義,使得開發(fā)者能更準(zhǔn)確地理解比較操作何時會因遇到不可比較類型而 panic。如果比較在遇到不可比較的字段或元素之前就因其他部分不匹配而停止,則不會發(fā)生 panic。

Go 1.20 放寬 comparable 約束,允許接口等類型作為類型參數(shù),即使運行時比較可能 panic

Go 1.18 引入泛型時,定義了一個 comparable 約束。這個約束用于限定類型參數(shù)必須是可比較的類型。這對于需要將類型參數(shù)用作 map 的鍵或在代碼中進(jìn)行 == 或 != 比較的泛型函數(shù)和類型非常重要。

最初,comparable 約束要求類型參數(shù)本身必須是嚴(yán)格可比較的。這意味著像接口類型(interface types)這樣的類型不能滿足 comparable 約束。為什么?因為雖然接口值本身可以用 == 比較(例如,比較它們是否都為 nil,或者是否持有相同的動態(tài)值),但如果兩個接口持有不同的動態(tài)類型,或者持有的動態(tài)類型本身是不可比較的(如 slice、map、function),那么在運行時比較它們會引發(fā) panic。由于這種潛在的運行時 panic,接口類型在 Go 1.18/1.19 中不被視為滿足 comparable 約束。

這帶來了一個問題:開發(fā)者無法輕松地創(chuàng)建以接口類型作為鍵的泛型 map 或 set。

Go 1.20 放寬了 comparable 約束的要求。現(xiàn)在,一個類型 T 滿足 comparable 約束,只要類型 T 的值可以用 == 或 != 進(jìn)行比較即可。這包括了接口類型,以及包含接口類型的復(fù)合類型(如結(jié)構(gòu)體、數(shù)組)。

關(guān)鍵變化在于,滿足 comparable 約束不再保證比較操作永遠(yuǎn)不會 panic。它僅僅保證了 == 和 != 運算符 可以 應(yīng)用于該類型的值。比較是否真的會 panic 取決于運行時的具體值。

這個改動使得以下代碼在 Go 1.20 中成為可能:

package main

import "fmt"

// Generic map using comparable constraint for the key
type GenericMap[K comparable, V any] struct {
    m map[K]V
}

func NewGenericMap[K comparable, V any]() *GenericMap[K, V] {
    return &GenericMap[K, V]{m: make(map[K]V)}
}

func (gm *GenericMap[K, V]) Put(key K, value V) {
    gm.m[key] = value
}

func (gm *GenericMap[K, V]) Get(key K) (V, bool) {
    v, ok := gm.m[key]
    return v, ok
}

func main() {
    // Instantiate GenericMap with K=int (strictly comparable) - Works always
    intMap := NewGenericMap[int, string]()
    intMap.Put(1, "one")
    fmt.Println(intMap.Get(1)) // Output: one true

    // Instantiate GenericMap with K=any (interface{}) - Works in Go 1.20+
    // In Go 1.18/1.19, this would fail compilation because 'any'/'interface{}'
    // did not satisfy the 'comparable' constraint.
    anyMap := NewGenericMap[any, string]()

    // Use comparable types as keys - Works fine
    anyMap.Put(10, "integer")
    anyMap.Put("hello", "string")
    type MyStruct struct{ V int }
    anyMap.Put(MyStruct{V: 5}, "struct")

    fmt.Println(anyMap.Get(10))      // Output: integer true
    fmt.Println(anyMap.Get("hello")) // Output: string true
    fmt.Println(anyMap.Get(MyStruct{V: 5})) // Output: struct true

    // Attempt to use an uncomparable type (slice) as a key.
    // The Put operation itself will cause a panic during the map key comparison.
    keySlice := []int{1, 2}
    fmt.Println("Attempting to put slice key...")
    // The following line will panic in Go 1.20+
    // panic: runtime error: hash of unhashable type []int
    // or panic: runtime error: comparing uncomparable type []int
    // (depending on map implementation details)
    // anyMap.Put(keySlice, "slice")
    _ = keySlice // Avoid unused variable

    // Using interface values holding different uncomparable types also panics
    var i1 any = []int{1}
    var i2 any = map[string]int{}
    // The comparison i1 == i2 during map access would panic.
    // anyMap.Put(i1, "interface holding slice")
    // anyMap.Put(i2, "interface holding map")
    _ = i1
    _ = i2
}

這個改變提高了泛型的靈活性,允許開發(fā)者編寫適用于更廣泛類型的泛型代碼,特別是涉及 map 鍵時。但開發(fā)者需要意識到,當(dāng)使用非嚴(yán)格可比較的類型(如 any 或包含接口的結(jié)構(gòu)體)作為滿足 comparable 約束的類型參數(shù)時,代碼中涉及比較的操作(如 map 查找、插入、刪除,或顯式的 ==/!=)可能會在運行時 panic。

Go 1.20 引入了對包裝多個錯誤的原生支持

在 Go 1.13 中,通過 errors.Unwrap、errors.Is、errors.As 以及 fmt.Errorf 的 %w 動詞,引入了標(biāo)準(zhǔn)的錯誤包裝(error wrapping)機制。這允許一個錯誤 "包含" 另一個錯誤,形成錯誤鏈,方便追蹤錯誤的根本原因。然而,該機制僅支持一個錯誤包裝 單個 其他錯誤。

在實際開發(fā)中,有時一個操作可能因為多個獨立的原因而失敗,或者一個聚合操作中的多個子操作都失敗了。例如,嘗試將數(shù)據(jù)寫入數(shù)據(jù)庫和文件系統(tǒng)都失敗了。在這種情況下,將多個錯誤合并成一個錯誤會很有用。

Go 1.20 擴(kuò)展了錯誤處理機制,原生支持一個錯誤包裝 多個 其他錯誤。主要通過以下幾種方式實現(xiàn):

  • Unwrap() []error 方法

一個錯誤類型可以通過實現(xiàn) Unwrap() []error 方法來表明它包裝了多個錯誤。如果一個類型同時定義了 Unwrap() error 和 Unwrap() []error,那么 errors.Is 和 errors.As 將優(yōu)先使用 Unwrap() []error。

  • fmt.Errorf 的多個 %w

fmt.Errorf 函數(shù)現(xiàn)在支持在格式字符串中多次使用 %w 動詞。調(diào)用 fmt.Errorf("...%w...%w...", err1, err2) 將返回一個包裝了 err1 和 err2 的新錯誤。這個返回的錯誤實現(xiàn)了 Unwrap() []error 方法。

  • errors.Join(...error) error 函數(shù)

新增的 errors.Join 函數(shù)接受一個或多個 error 參數(shù),并返回一個包裝了所有非 nil 輸入錯誤的新錯誤。如果所有輸入錯誤都是 nil,errors.Join 返回 nil。返回的錯誤也實現(xiàn)了 Unwrap() []error 方法。這是合并多個錯誤的推薦方式。

  • errors.Is 和 errors.As 更新

這兩個函數(shù)現(xiàn)在能夠遞歸地檢查通過 Unwrap() []error 暴露出來的所有錯誤。errors.Is(multiErr, target) 會檢查 multiErr 本身以及它(遞歸地)解包出來的任何一個錯誤是否等于 target。類似地,errors.As(multiErr, &targetVar) 會檢查 multiErr 或其解包鏈中的任何錯誤是否可以賦值給 targetVar。

下面是一個結(jié)合生產(chǎn)場景的例子,演示如何使用這些新特性:

package main

import (
    "errors"
    "fmt"
    "os"
    "time"
    "syscall"
)

// 定義一些具體的錯誤類型
var ErrDatabaseTimeout = errors.New("database timeout")
var ErrCacheFailed = errors.New("cache operation failed")
var ErrFileSystemReadOnly = errors.New("file system is read-only")

type NetworkError struct {
    Op  string
    Err error // Underlying network error
}

func (e *NetworkError) Error() string {
    return fmt.Sprintf("network error during %s: %v", e.Op, e.Err)
}

func (e *NetworkError) Unwrap() error {
    return e.Err // Implements single error unwrapping
}

// 模擬保存數(shù)據(jù)的操作,可能同時涉及多個系統(tǒng)
func saveData(data string) error {
    var errs []error // 用于收集所有發(fā)生的錯誤

    // 模擬數(shù)據(jù)庫操作
    if time.Now().Second()%2 == 0 { // 假設(shè)偶數(shù)秒時數(shù)據(jù)庫超時
        errs = append(errs, ErrDatabaseTimeout)
    }

    // 模擬緩存操作
    if len(data) < 5 { // 假設(shè)數(shù)據(jù)太短時緩存失敗
        // 包裝一個更具體的網(wǎng)絡(luò)錯誤
        netErr := &NetworkError{Op: "set cache", Err: errors.New("connection refused")}
        errs = append(errs, fmt.Errorf("%w: %w", ErrCacheFailed, netErr)) // 使用 %w 包裝原始錯誤和網(wǎng)絡(luò)錯誤
    }

    // 模擬文件系統(tǒng)操作
    if _, err := os.OpenFile("dummy.txt", os.O_WRONLY, 0666); err != nil {
        // 檢查是否是只讀文件系統(tǒng)錯誤(僅為示例,實際檢查更復(fù)雜)
        if os.IsPermission(err) { // os.IsPermission is a common check
            errs = append(errs, ErrFileSystemReadOnly)
        } else {
            errs = append(errs, fmt.Errorf("failed to open file: %w", err)) // 包裝底層 os 錯誤
        }
    }

    // 使用 errors.Join 將所有收集到的錯誤合并成一個
    // 如果 errs 為空 (即沒有錯誤發(fā)生), errors.Join 會返回 nil
    return errors.Join(errs...)
}

func main() {
    err := saveData("dat") // "dat" is short, likely triggers cache error
    if err != nil {
        fmt.Printf("Failed to save data:\n%v\n\n", err) // errors.Join 產(chǎn)生的錯誤會自動格式化,顯示所有子錯誤

        // 現(xiàn)在我們可以檢查這個聚合錯誤中是否包含特定的錯誤類型或值

        // 檢查是否包含數(shù)據(jù)庫超時錯誤
        if errors.Is(err, ErrDatabaseTimeout) {
            // errors.Is 會遍歷 err 解包出來的所有錯誤(通過 errors.Join 的 Unwrap() []error)
            // 如果找到 ErrDatabaseTimeout,則返回 true
            fmt.Println("Detected: Database Timeout")
        }

        // 檢查是否包含文件系統(tǒng)只讀錯誤
        if errors.Is(err, ErrFileSystemReadOnly) {
            // 同樣,errors.Is 會檢查所有被 Join 的錯誤
            fmt.Println("Detected: File System Read-Only")
        }

        // 提取具體的 NetworkError 類型
        var netErr *NetworkError
        if errors.As(err, &netErr) {
            // errors.As 會遍歷 err 解包出來的所有錯誤
            // 如果找到一個類型為 *NetworkError 的錯誤,就將其賦值給 netErr 并返回 true
            fmt.Printf("Detected Network Error: Op=%s, Underlying=%v\n", netErr.Op, netErr.Err)
            // 我們甚至可以進(jìn)一步檢查 NetworkError 內(nèi)部包裝的錯誤
            if errors.Is(netErr, syscall.ECONNREFUSED) { // 假設(shè)底層是 connection refused
                fmt.Println("   Network error specifically was: connection refused")
            }
        }

        // 也可以檢查原始的 Cache 失敗錯誤
        if errors.Is(err, ErrCacheFailed) {
            // 這會找到 fmt.Errorf("%w: %w", ErrCacheFailed, netErr) 中包裝的 ErrCacheFailed
            fmt.Println("Detected: Cache Failed (may have underlying network error)")
        }
    } else {
        fmt.Println("Data saved successfully!")
    }

    // 演示 fmt.Errorf 與多個 %w
    err1 := errors.New("error one")
    err2 := errors.New("error two")
    multiWError := fmt.Errorf("operation failed: %w; also %w", err1, err2)
    fmt.Printf("\nError from multiple %%w:\n%v\n", multiWError)
    if errors.Is(multiWError, err1) && errors.Is(multiWError, err2) {
        // multiWError 實現(xiàn) Unwrap() []error,包含 err1 和 err2
        fmt.Println("Multiple %w error contains both err1 and err2.")
    }
}
Failed to save data:
database timeout
cache operation failed: network error during set cache: connection refused
failed to open file: open dummy.txt: no such file or directory

Detected: Database Timeout
Detected Network Error: Op=set cache, Underlying=connection refused
Detected: Cache Failed (may have underlying network error)

Error from multiple %w:
operation failed: error one; also error two
Multiple %w error contains both err1 and err2.

(注意: 上述代碼中的 syscall.ECONNREFUSED 部分可能需要根據(jù)你的操作系統(tǒng)進(jìn)行調(diào)整或替換為更通用的網(wǎng)絡(luò)錯誤檢查方式,這里僅作 errors.Is 嵌套使用的演示)

這個多錯誤包裝功能使得錯誤處理更加靈活和富有表現(xiàn)力,特別是在需要聚合來自不同子系統(tǒng)或并發(fā)操作的錯誤時,能夠提供更完整的失敗上下文,同時保持了與現(xiàn)有 errors.Is 和 errors.As 的兼容性。

net/http 引入 ResponseController 以提供更清晰、可發(fā)現(xiàn)的擴(kuò)展請求處理控制

在 Go 的 net/http 包中,http.ResponseWriter 接口是 HTTP handler 處理請求并構(gòu)建響應(yīng)的核心。然而,隨著 HTTP 協(xié)議和服務(wù)器功能的發(fā)展,有時需要對請求處理過程進(jìn)行更精細(xì)的控制,而這些控制功能超出了 ResponseWriter 接口的基本定義(如 Write, WriteHeader, Header)。

過去,net/http 包通常通過定義 可選接口(optional interfaces)來添加這些擴(kuò)展功能。例如,如果 handler 需要主動將緩沖的數(shù)據(jù)刷新到客戶端,它可以檢查其接收到的 ResponseWriter 是否也實現(xiàn)了 http.Flusher 接口,如果實現(xiàn)了,就調(diào)用其 Flush() 方法。其他例子包括 http.Hijacker(用于接管 TCP 連接)和 http.Pusher(用于 HTTP/2 server push)。

這種依賴可選接口的模式有幾個缺點:

  1. 不易發(fā)現(xiàn) :開發(fā)者需要知道這些可選接口的存在,并在文檔或代碼中查找它們。
  2. 使用笨拙 :每次使用都需要進(jìn)行類型斷言(if hj, ok := w.(http.Hijacker); ok { ... }),使得代碼略顯冗長。
  3. 擴(kuò)展性問題 :隨著新功能的增加,可選接口的數(shù)量可能會不斷增多。

為了解決這些問題,Go 1.20 引入了 net/http.ResponseController 類型。這是一個新的結(jié)構(gòu)體,旨在提供一個統(tǒng)一的、更清晰、更易于發(fā)現(xiàn)的方式來訪問附加的、針對每個請求(per-request)的響應(yīng)控制功能。

你可以通過 http.NewResponseController(w ResponseWriter) 來獲取與給定 ResponseWriter 關(guān)聯(lián)的 ResponseController 實例。然后,你可以調(diào)用 ResponseController 上的方法來執(zhí)行擴(kuò)展操作。

Go 1.20 同時通過 ResponseController 引入了兩個新的控制功能:

  1. SetReadDeadline(time time.Time) error :設(shè)置此請求的底層連接的讀取截止時間。這對于需要長時間運行的 handler(例如,流式上傳或 WebSocket)想要覆蓋服務(wù)器的全局讀取超時(Server.ReadTimeout)非常有用。
  2. SetWriteDeadline(time time.Time) error :設(shè)置此請求的底層連接的寫入截止時間。這對于 handler 需要發(fā)送大量數(shù)據(jù)或進(jìn)行流式響應(yīng),并希望覆蓋服務(wù)器的全局寫入超時(Server.WriteTimeout)很有用。將截止時間設(shè)置為空的 time.Time{} (即零值) 表示禁用超時。

以下是如何使用 ResponseController 設(shè)置寫入截止時間的示例,改編自官方文檔:

package main

import (
    "fmt"
    "io"
    "net/http"
    "time"
)

// 模擬一個需要發(fā)送大量數(shù)據(jù)的 handler
func bigDataHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Handling request for big data...")

    // 獲取與 ResponseWriter 關(guān)聯(lián)的 ResponseController
    rc := http.NewResponseController(w)

    // 假設(shè)我們要發(fā)送大量數(shù)據(jù),可能超過服務(wù)器的默認(rèn) WriteTimeout
    // 我們可以為這個特定的請求禁用寫入超時
    // 將截止時間設(shè)置為空的 time.Time (零值) 即可禁用
    err := rc.SetWriteDeadline(time.Time{})
    if err != nil {
        // 如果設(shè)置截止時間失敗 (例如,底層連接不支持或已關(guān)閉)
        // 記錄錯誤并可能返回一個內(nèi)部服務(wù)器錯誤
        fmt.Printf("Error setting write deadline: %v\n", err)
        http.Error(w, "Failed to set write deadline", http.StatusInternalServerError)
        return
    }
    fmt.Println("Write deadline disabled for this request.")

    // 設(shè)置響應(yīng)頭
    w.Header().Set("Content-Type", "text/plain")
    w.WriteHeader(http.StatusOK)

    // 模擬發(fā)送大量數(shù)據(jù)
    for i := 0; i < 10; i++ {
        _, err := io.WriteString(w, fmt.Sprintf("This is line %d of a large response.\n", i+1))
        if err != nil {
            // 如果寫入過程中發(fā)生錯誤 (例如,連接被客戶端關(guān)閉)
            fmt.Printf("Error writing response data: %v\n", err)
            // 此時可能無法再向客戶端發(fā)送錯誤,但應(yīng)記錄日志
            return
        }
        // 模擬耗時操作
        time.Sleep(200 * time.Millisecond)
    }

    fmt.Println("Finished sending big data.")
}

// 模擬一個需要從客戶端讀取可能很慢的數(shù)據(jù)流的 handler
func slowUploadHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Handling slow upload...")

    // 獲取 ResponseController (雖然這里主要控制讀取,但通過 ResponseWriter 獲取)
    rc := http.NewResponseController(w)

    // 假設(shè)服務(wù)器有 ReadTimeout,但我們預(yù)期這個上傳可能很慢
    // 我們可以延長讀取截止時間,比如設(shè)置為 1 分鐘后
    deadline := time.Now().Add(1 * time.Minute)
    err := rc.SetReadDeadline(deadline)
    if err != nil {
        fmt.Printf("Error setting read deadline: %v\n", err)
        http.Error(w, "Failed to set read deadline", http.StatusInternalServerError)
        return
    }
    fmt.Printf("Read deadline set to %v for this request.\n", deadline)

    // 現(xiàn)在可以安全地從 r.Body 讀取,直到截止時間
    bodyBytes, err := io.ReadAll(r.Body)
    if err != nil {
        // 檢查是否是超時錯誤
        if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
            fmt.Println("Read timed out as expected.")
            http.Error(w, "Read timed out", http.StatusRequestTimeout)
        } else {
            fmt.Printf("Error reading request body: %v\n", err)
            http.Error(w, "Error reading body", http.StatusInternalServerError)
        }
        return
    }

    fmt.Printf("Received %d bytes.\n", len(bodyBytes))
    fmt.Fprintln(w, "Upload received successfully!")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/bigdata", bigDataHandler)
    mux.HandleFunc("/upload", slowUploadHandler)

    // 創(chuàng)建一個帶有默認(rèn)超時的服務(wù)器 (例如 5 秒)
    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  5 * time.Second, // 默認(rèn)讀取超時
        WriteTimeout: 5 * time.Second, // 默認(rèn)寫入超時
    }

    fmt.Println("Server starting on :8080...")
    fmt.Println("Try visiting http://localhost:8080/bigdata")
    fmt.Println("Try sending a POST request with a slow body to http://localhost:8080/upload")
    fmt.Println("Example using curl for slow upload:")
    fmt.Println(`  curl -X POST --data-binary @- http://localhost:8080/upload <<EOF`)
    fmt.Println(`  This is some data that will be sent slowly.`)
    fmt.Println(`  <You might need to wait or pipe slow input here>`)
    fmt.Println(`  EOF`)

    err := server.ListenAndServe()
    if err != nil && err != http.ErrServerClosed {
        fmt.Printf("Server error: %v\n", err)
    }
}

// 注意: SetReadDeadline/SetWriteDeadline 通常作用于底層的 net.Conn。
// 對于 HTTP/2,行為可能更復(fù)雜,因為一個連接上有多路復(fù)用的流。
// 對于 HTTP/3 (QUIC),這些可能不適用或有不同的機制。
// 查閱具體 Go 版本的 net/http 文檔了解詳細(xì)語義。

ResponseController 提供了一個更健壯、面向未來的機制來添加和使用 net/http 的擴(kuò)展功能。預(yù)期未來更多類似 SetReadDeadline 的細(xì)粒度控制將通過 ResponseController 的方法來提供,而不是增加新的可選接口。

httputil.ReverseProxy 獲得新的 Rewrite 鉤子,取代 Director,提供更安全、靈活的請求轉(zhuǎn)發(fā)定制

net/http/httputil.ReverseProxy 是 Go 標(biāo)準(zhǔn)庫中用于構(gòu)建反向代理服務(wù)器的核心組件。它接收客戶端(入站)請求,并將其轉(zhuǎn)發(fā)給一個或多個后端(出站)服務(wù)器。開發(fā)者可以通過設(shè)置 ReverseProxy 結(jié)構(gòu)體的字段來自定義其行為。

在 Go 1.20 之前,最主要的定制點是 Director 字段。Director 是一個函數(shù) func(*http.Request),它在請求被轉(zhuǎn)發(fā) 之前 被調(diào)用。Director 的職責(zé)是修改傳入的 *http.Request 對象,使其指向目標(biāo)后端服務(wù)器,并進(jìn)行必要的頭部調(diào)整等。然而,Director 的設(shè)計存在一些局限和潛在的安全問題:

  1. 只操作出站請求 :Director 函數(shù)只接收即將發(fā)送到后端的請求對象。這個對象通常是入站請求的一個淺拷貝(shallow copy)。Director 無法直接訪問 原始 的入站請求對象。
  2. 安全風(fēng)險 (Issue #50580) :ReverseProxy 在調(diào)用 Director之后,但在實際發(fā)送請求 之前,會執(zhí)行一些默認(rèn)的頭部清理和設(shè)置操作(例如,移除 hop-by-hop headers,設(shè)置 X-Forwarded-* 頭等)。惡意的客戶端可以通過構(gòu)造特殊的入站請求頭,使得 Director 添加的某些頭信息(如自定義的認(rèn)證頭)在后續(xù)的清理步驟中被意外移除或覆蓋,從而繞過檢查。
  3. NewSingleHostReverseProxy 的局限 :這是一個常用的輔助函數(shù),用于創(chuàng)建一個將所有請求轉(zhuǎn)發(fā)到單個后端主機的 ReverseProxy。但它設(shè)置 Director 的方式有時不能正確處理 Host 頭部,并且不方便進(jìn)行除目標(biāo) URL 之外的更多自定義。

Go 1.20 引入了一個新的鉤子函數(shù) Rewrite,旨在取代 Director,并解決上述問題。

Rewrite 鉤子

  • 類型 : func(*httputil.ProxyRequest)
  • ProxyRequest 結(jié)構(gòu)體 : 這是一個新引入的結(jié)構(gòu)體,包含兩個字段:

In *http.Request: 指向原始的、未經(jīng)修改的入站請求。

Out *http.Request: 指向即將發(fā)送到后端的請求(初始時是 In 的淺拷貝)。Rewrite 函數(shù)應(yīng)該修改 Out。

  • 優(yōu)勢 :
  • 訪問原始請求 : Rewrite 可以同時訪問入站和出站請求,使得決策可以基于原始請求的真實信息。
  • 更安全的時機 : Rewrite 在 ReverseProxy 的默認(rèn)頭部處理邏輯 之后 執(zhí)行(或者說,Rewrite 完全取代了 Director 和部分默認(rèn)邏輯)。這意味著 Rewrite 設(shè)置的頭部(如 Out.Header.Set(...))不會被代理的后續(xù)步驟意外更改。
  • 更強大的控制 : 結(jié)合 ProxyRequest 提供的輔助方法,可以更方便、正確地完成常見任務(wù)。

ProxyRequest 的輔助方法

Go 1.20 同時為 ProxyRequest 添加了幾個實用的方法:

  1. SetURL(target *url.URL) :
  • 將出站請求 r.Out.URL 設(shè)置為 target。
  • 重要 : 它還會正確地設(shè)置出站請求的 Host 字段 (r.Out.Host = target.Host) 和 Host 頭部 (r.Out.Header.Set("Host", target.Host))。
  • 這有效地取代了 NewSingleHostReverseProxy 的核心功能,并且做得更正確。
  1. SetXForwarded() :
  • 這是一個便捷方法,用于在出站請求 r.Out 上設(shè)置標(biāo)準(zhǔn)的 X-Forwarded-For, X-Forwarded-Host, 和 X-Forwarded-Proto 頭部。
  • 重要 : 當(dāng)你提供 Rewrite 鉤子時,ReverseProxy默認(rèn)不再自動添加 這些 X-Forwarded-* 頭部。如果你的后端服務(wù)依賴這些頭部,你 必須 在 Rewrite 函數(shù)中顯式調(diào)用 r.SetXForwarded()。

示例用法

以下是如何使用 Rewrite 鉤子來創(chuàng)建一個將請求轉(zhuǎn)發(fā)到指定后端,并設(shè)置 X-Forwarded-* 頭部以及一個自定義頭部的 ReverseProxy:

package main

import (
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
    "os"
)

func main() {
    // 后端服務(wù)器的 URL
    backendURL, err := url.Parse("http://localhost:8081") // 假設(shè)后端服務(wù)運行在 8081 端口
    if err != nil {
        log.Fatal("Invalid backend URL")
    }

    // 創(chuàng)建一個簡單的后端服務(wù)器用于測試
    go startBackendServer()

    // 創(chuàng)建 ReverseProxy 并設(shè)置 Rewrite 鉤子
    proxy := &httputil.ReverseProxy{
        Rewrite: func(r *httputil.ProxyRequest) {
            // 1. 將出站請求的目標(biāo)設(shè)置為后端 URL
            // SetURL 會同時設(shè)置 r.Out.URL 和 r.Out.Host,并設(shè)置 Host header
            r.SetURL(backendURL)

            // 2. (可選) 如果需要 X-Forwarded-* 頭部,必須顯式調(diào)用 SetXForwarded
            // 如果不調(diào)用,這些頭部將不會被添加到出站請求中
            r.SetXForwarded()

            // 3. (可選) 添加或修改其他出站請求頭部
            r.Out.Header.Set("X-My-Proxy-Header", "Hello from proxy")

            // 4. (可選) 可以基于入站請求 r.In 進(jìn)行決策
            log.Printf("Rewriting request: In=%s %s, Out=%s %s",
                r.In.Method, r.In.URL.Path,
                r.Out.Method, r.Out.URL.String()) // 注意 r.Out.URL 已經(jīng)被 SetURL 修改

            // 注意:不需要再手動設(shè)置 r.Out.Host 或 Host header,SetURL 已處理
            // 注意:默認(rèn)情況下,ReverseProxy 會處理 hop-by-hop headers,這里無需手動處理
        },
        // 如果需要自定義錯誤處理
        ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
            log.Printf("Proxy error: %v", err)
            http.Error(rw, "Proxy Error", http.StatusBadGateway)
        },
    }

    // 設(shè)置 Director 為 nil,因為我們使用了 Rewrite
    // proxy.Director = nil // 這行不是必需的,因為設(shè)置 Rewrite 優(yōu)先

    // 啟動代理服務(wù)器
    log.Println("Starting reverse proxy on :8080, forwarding to", backendURL)
    if err := http.ListenAndServe(":8080", proxy); err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

// 簡單的后端 HTTP 服務(wù)器
func startBackendServer() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        log.Printf("[Backend] Received request: %s %s", r.Method, r.URL.String())
        log.Printf("[Backend] Host header: %s", r.Host)
        log.Printf("[Backend] X-Forwarded-For: %s", r.Header.Get("X-Forwarded-For"))
        log.Printf("[Backend] X-Forwarded-Host: %s", r.Header.Get("X-Forwarded-Host"))
        log.Printf("[Backend] X-Forwarded-Proto: %s", r.Header.Get("X-Forwarded-Proto"))
        log.Printf("[Backend] X-My-Proxy-Header: %s", r.Header.Get("X-My-Proxy-Header"))
        log.Printf("[Backend] User-Agent: %s", r.Header.Get("User-Agent"))

        w.Header().Set("Content-Type", "text/plain")
        w.WriteHeader(http.StatusOK)
        _, _ = w.Write([]byte("Hello from backend!"))
    })
    log.Println("Starting backend server on :8081")
    if err := http.ListenAndServe(":8081", mux); err != nil {
        log.Printf("Backend server error: %v", err)
        os.Exit(1) // 如果后端無法啟動,則退出
    }
}

User-Agent 行為變更

另外一個相關(guān)的改動是:如果入站請求 沒有User-Agent 頭部,ReverseProxy(無論是否使用 Rewrite)在 Go 1.20 中將 不再 為出站請求自動添加一個默認(rèn)的 User-Agent 頭部。如果需要確保出站請求總是有 User-Agent,你需要在 Rewrite 鉤子中檢查并設(shè)置它。

// 在 Rewrite 函數(shù)中添加:
if r.Out.Header.Get("User-Agent") == "" {
    r.Out.Header.Set("User-Agent", "MyCustomProxy/1.0")
}

總而言之,Rewrite 鉤子和 ProxyRequest 類型為 httputil.ReverseProxy 提供了更現(xiàn)代化、更安全、更靈活的定制方式,推薦在新的 Go 1.20+ 項目中使用 Rewrite 來替代 Director。

責(zé)任編輯:武曉燕 來源: Piper蛋窩
相關(guān)推薦

2025-04-29 08:03:18

2025-04-24 09:01:46

2025-04-21 08:00:56

2025-04-23 08:02:40

2025-04-27 08:00:35

2025-04-14 00:00:04

2025-04-27 00:00:01

Go 1.16Go 1.15接口

2025-04-21 00:00:00

Go 開發(fā)Go 語言Go 1.9

2025-04-22 08:02:23

2025-04-21 00:05:00

2025-04-18 08:07:12

2025-04-25 08:01:12

Go應(yīng)用程序部署

2025-05-06 00:00:08

2025-05-06 05:00:00

2025-04-17 08:00:48

2025-04-15 08:00:53

2025-05-06 08:00:35

2025-04-28 08:00:56

2025-04-14 08:06:04

2025-04-11 08:02:38

點贊
收藏

51CTO技術(shù)棧公眾號

国产亚洲欧美另类一区二区三区| 亚洲欧美一区二区视频| 欧美极品美女电影一区| 91丝袜在线观看| 超碰国产一区| 中文字幕在线一区免费| 成人黄色在线免费观看| 日韩精品一区二区av| 欧美理论电影大全| 欧美一级xxx| 久久国产精品久久精品国产| 国产黄色免费观看| 91综合久久一区二区| 精品久久久久久最新网址| 精品一区二区中文字幕| 尤物视频在线免费观看| 国产成人啪午夜精品网站男同| 91av在线看| 99精品中文字幕| 国产精品白丝av嫩草影院| 欧美在线免费播放| 成人av在线不卡| 成人免费在线观看| 成人永久aaa| 国产精品电影在线观看| 精品无码m3u8在线观看| 四虎国产精品免费观看| 日韩国产一区三区| 人妻少妇偷人精品久久久任期| 国产精品迅雷| 亚洲伊人色欲综合网| 亚洲亚洲精品三区日韩精品在线视频| 可以免费观看的毛片| 麻豆成人av在线| 55夜色66夜色国产精品视频| 老妇女50岁三级| 日韩欧美网站| 欧美在线视频你懂得| aa视频在线播放| 国产一二区在线观看| 久久品道一品道久久精品| 成人免费视频观看视频| 国产精品久久久久久久一区二区| 999久久久免费精品国产| 亚洲国产欧美一区二区三区久久| 久久精品亚洲天堂| 国产激情久久| 日韩欧美在线网址| 可以在线看的av网站| 中中文字幕av在线| 亚洲人妖av一区二区| 91九色极品视频| 中文字幕一级片| 日本vs亚洲vs韩国一区三区二区| 91精品国产九九九久久久亚洲| 欧美成人免费观看视频| 久久久久久美女精品 | 久久av导航| 日韩成人免费视频| 天堂久久久久久| 在线天堂资源| 国产精品美女一区二区三区| 欧美高清性xxxxhdvideosex| 黄色av一区二区| 亚洲综合精品四区| 色妞色视频一区二区三区四区| www.成人黄色| 成人自拍视频| 无吗不卡中文字幕| 国产男女免费视频| √天堂8资源中文在线| 午夜精品久久久久久久| 欧美 日韩 国产在线观看| www在线播放| 中文字幕国产精品一区二区| 亚洲自拍欧美色图| 99久久婷婷国产一区二区三区| 韩国av一区二区三区在线观看| 成人久久久久久久| 亚洲国产www| 成人丝袜18视频在线观看| 精品国产免费一区二区三区| 青青青草原在线| 中文字幕免费在线观看视频一区| 在线观看欧美亚洲| 羞羞污视频在线观看| 亚洲午夜久久久久久久久久久| 免费毛片网站在线观看| 三级成人黄色影院| 欧美日韩国产一级二级| wwwxxxx在线观看| 色婷婷精品视频| 最新日韩中文字幕| 麻豆亚洲av成人无码久久精品| 1024日韩| 国产精品久久久久久久久久ktv| 国产精品福利电影| 成人免费av在线| 日本一区二区三区视频在线播放 | 亚洲男人在线| 精品国产一区二区三区久久影院| 中文字幕xxx| 亚洲网色网站| 亚洲偷欧美偷国内偷| 亚洲少妇中文字幕| 国产精品一区二区99| 久久国产精品久久国产精品| 少妇精品无码一区二区免费视频| 亚洲成av人片乱码色午夜| 97视频在线观看视频免费视频 | 久久久久久久久久久影视| 欧美国产极品| 久久夜精品va视频免费观看| 九一国产在线观看| 国产精品一级片在线观看| 欧美日韩电影一区二区| 91精品久久| 色狠狠一区二区三区香蕉| 波多野结衣中文字幕在线播放| 免费精品国产的网站免费观看| 久久综合久久美利坚合众国| 91午夜精品亚洲一区二区三区| 国产精品一级在线| 五月婷婷综合色| 少妇淫片在线影院| 精品欧美黑人一区二区三区| 网站永久看片免费| 久久国产精品99国产| 97超碰最新| 日本韩国在线视频爽| 一本色道久久综合狠狠躁的推荐| 亚洲成年人av| 中文精品久久| 国产精品私拍pans大尺度在线| 国产精品自拍99| 国产成人av电影在线播放| 亚洲国产一区二区三区在线播| 欧美性爽视频| 欧美一区二区网站| 中文字幕91视频| 日韩国产欧美在线播放| 久久久99爱| av手机免费在线观看| 欧美一级黄色大片| 日本a级片视频| 国内一区二区在线| 亚洲欧洲一区二区福利| 亚洲mmav| 国产亚洲免费的视频看| 国产污视频网站| 久久久亚洲午夜电影| 春日野结衣av| 亚洲福利网站| 国产不卡视频在线| 国产在线视频网| 自拍视频在线观看一区二区| 网站一区二区三区| 成人情趣视频| 国产精自产拍久久久久久| 成人福利在线| 欧美日韩一区二区在线视频| 91制片厂在线| 国产麻豆视频精品| 奇米777四色影视在线看| 亚洲乱码一区| 国模极品一区二区三区| 五月婷婷综合久久| 午夜精品久久久久影视| 变态另类丨国产精品| 美女视频一区免费观看| 日产精品高清视频免费| av观看在线| 欧美mv日韩mv国产网站app| 精品午夜福利在线观看| 成人激情黄色小说| 成人在线免费观看av| 亚洲欧洲av| 国产精品视频精品| 国产在线更新| 亚洲国产精品大全| 亚洲国产av一区二区三区| 中文字幕免费不卡| 天天操精品视频| 亚洲黄色一区| 日本一区二区在线视频| 国产亚洲亚洲国产一二区| 欧美激情啊啊啊| 天堂av在线7| 欧美日韩精品欧美日韩精品一| 一区二区视频免费看| 97成人超碰视| 精品人妻人人做人人爽| 欧美黑人巨大videos精品| 国产精品久久久久久久久久尿 | 日本sm极度另类视频| 国产探花精品一区二区| 亚洲国产综合在线| 成人性生交大片免费看无遮挡aⅴ| 黄页视频在线91| 国产精品国产亚洲精品看不卡| 第一会所亚洲原创| aaa级精品久久久国产片| 暖暖成人免费视频| 欧美成人免费在线观看| 日韩av资源| 欧美一卡2卡三卡4卡5免费| 你懂的国产在线| 亚洲精品乱码久久久久久| 少妇大叫太粗太大爽一区二区| 免费欧美在线视频| a在线视频观看| 亚洲九九在线| 日韩福利二区| 国语一区二区三区| 91久久久久久久久久久久久| 欧美极品videos大乳护士| 久久影视电视剧免费网站| 久草福利在线视频| 亚洲第一精品夜夜躁人人躁| 一级黄色大片网站| 色婷婷综合视频在线观看| 久久久久人妻一区精品色欧美| 国产欧美一区二区精品仙草咪| 亚洲一级Av无码毛片久久精品| 美日韩一区二区| 午夜肉伦伦影院| 亚洲电影成人| 国产一二三四区在线观看| 欧美在线电影| 欧美精品与人动性物交免费看| 在线观看视频一区二区三区| 成人黄色中文字幕| 国产精品亚洲成在人线| 日韩av男人的天堂| 日本啊v在线| 精品国产成人在线影院| 国产农村妇女毛片精品| 欧美日韩一区二区三区不卡| 国产又粗又猛又黄视频| 精品国产鲁一鲁一区二区张丽| 日韩欧美中文字幕视频| 自拍偷拍国产精品| 在线观看黄网址| 国产精品电影一区二区三区| 性猛交娇小69hd| 久久精品亚洲乱码伦伦中文| 无码人妻精品一区二区三区温州| 成人激情午夜影院| 美女黄色一级视频| 成人免费观看视频| 波多野结衣影院| 成人aa视频在线观看| 成人在线电影网站| 菠萝蜜视频在线观看一区| 亚洲图片欧美另类| 不卡av免费在线观看| 国产视频久久久久久| av电影在线观看不卡| 国产精品无码专区| 久久青草欧美一区二区三区| aaaaaav| 99re66热这里只有精品3直播 | 中文字幕日韩视频| 中文日本在线观看| 色吧影院999| 国产原创精品视频| 欧美国产日韩一区二区三区| www中文字幕在线观看| 亚洲视频欧洲视频| 国产精品影院在线| 精品国产一区二区三区久久久狼| 日本不卡视频| 欧美日本国产在线| 亚洲美女尤物影院| 国产精品99久久久久久久久| 四虎永久精品在线| 不卡的av一区| 宅男在线一区| 一区二区三区视频| 欧美特黄一区| 欧美污视频网站| 美国十次了思思久久精品导航| 日韩欧美色视频| 99热这里都是精品| 成熟人妻av无码专区| 亚洲欧美激情小说另类| 日韩毛片在线播放| 欧美色电影在线| 亚洲av少妇一区二区在线观看| 日韩精品福利在线| 免费人成在线观看播放视频| 欧美极品在线视频| 欧洲一级精品| 91亚色免费| 女人av一区| 欧美日韩中文字幕在线播放| 美女尤物久久精品| а 天堂 在线| 91麻豆精品视频| 中国一级片在线观看| 欧美日韩免费观看中文| 91麻豆一区二区| 亚洲精品国产suv| 网友自拍视频在线| 51精品国产黑色丝袜高跟鞋| 99精品在线免费观看| 欧美激情第一页在线观看| 66视频精品| 成人免费观看毛片| 成人免费毛片片v| 日韩一级片在线免费观看| 偷拍日韩校园综合在线| 国产又粗又猛又爽| 国产午夜精品一区二区三区 | 久久综合另类图片小说| 亚洲综合五月天| 老鸭窝91久久精品色噜噜导演| 午夜影院免费观看视频| 亚洲国产成人午夜在线一区| 日本一区二区不卡在线| 91麻豆精品国产综合久久久久久| 水莓100国产免费av在线播放| 九九热这里只有在线精品视| 欧美色片在线观看| 免费在线一区二区| 亚洲国产日本| 佐山爱在线视频| 亚洲欧美综合在线精品| 无码人妻精品一区二区三区9厂| 欧美精品一区二区三区四区 | 亚洲色图.com| 糖心vlog精品一区二区| 亚洲欧美一区二区三区情侣bbw | www亚洲成人| 久久婷婷国产综合国色天香| 国产亚洲欧美久久久久| 日韩亚洲欧美在线观看| 国产原创在线观看| 91精品久久久久久久久| 成人综合久久| 亚洲第一狼人区| 国产精品视频一二三| 国产精品尤物视频| 精品视频www| 极品在线视频| 久久久久久草| 亚洲在线免费| www.色天使| 一本色道久久综合精品竹菊| 你懂的免费在线观看视频网站| 亚洲丝袜av一区| 一区二区三区短视频| 久久综合给合久久狠狠色| 午夜一级在线看亚洲| 中文字幕在线免费看线人| 欧美性高跟鞋xxxxhd| 激情综合闲人网| 国产精品久久久久久中文字| 精品久久成人| 99re6在线观看| 亚洲三级在线看| 国产suv一区二区| 欧美激情欧美狂野欧美精品| 91蝌蚪精品视频| 91免费黄视频| 久久蜜桃香蕉精品一区二区三区| 高潮毛片又色又爽免费 | 日韩欧美网站| 四季av一区二区三区| 亚洲女同女同女同女同女同69| 国产成a人亚洲精v品无码| 欧美黑人国产人伦爽爽爽| 激情小说亚洲图片| 欧美精品成人网| 国产精品一卡二卡在线观看| 久青草视频在线观看| 亚洲精品国精品久久99热| 日韩成人影音| 影音先锋男人的网站| 久久久精品午夜少妇| 亚洲不卡的av| 日韩无一区二区| 欧美裸体视频| 亚洲精品一区二区三区樱花| 国产一区二区视频在线播放| 国产精品成人久久| 日韩亚洲欧美一区| 绿色成人影院| 自拍视频一区二区三区| 成人高清伦理免费影院在线观看| 五月天婷婷激情| 久久中文字幕国产| 欧美丝袜足交| 中文字幕丰满乱码| 天天综合天天做天天综合| av网站无病毒在线| 国产精品久久精品国产| 老**午夜毛片一区二区三区 | 欧美午夜精彩| 国产香蕉精品视频|