精品欧美一区二区三区在线观看 _久久久久国色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ù)棧公眾號

色综合影院在线观看| 欧美一区深夜视频| 特种兵之深入敌后| free性m.freesex欧美| 2020国产精品自拍| 国产男人精品视频| 久久久久久久久影院| 日韩精品首页| 亚洲精品美女网站| 久久久精品高清| 在线看片福利| 一区二区三区视频在线观看| 欧洲精品亚洲精品| 国产高清免费在线观看| 日韩电影一区二区三区| 欧美国产精品人人做人人爱| 一级黄色片大全| jazzjazz国产精品久久| 欧美午夜影院一区| 99在线精品免费视频| 幼a在线观看| 久久免费国产精品| 成人免费在线一区二区三区| 中文字幕一区二区久久人妻| 亚洲美女黄色| 毛片精品免费在线观看| 特级西西人体高清大胆| 亚洲人成伊人成综合图片| 欧美成人免费网站| 亚洲欧美日韩三级| 久久精品女人天堂av免费观看 | 日本一二三不卡视频| 97久久综合区小说区图片区| 欧美老女人在线| 久久精品香蕉视频| 自拍在线观看| 五月激情六月综合| 国产日韩亚洲欧美在线| 国产激情在线观看| 国产精品欧美一区喷水| 日本不卡久久| 日本v片在线免费观看| 成人性生交大片免费看视频在线 | 国产午夜精品理论片a级大结局| 国产 高清 精品 在线 a| 一级黄色小视频| 免费不卡在线观看| 国产成人亚洲综合青青| 成人毛片在线播放| 欧美一级播放| 4444欧美成人kkkk| 欧美日韩综合在线观看| 99国产精品久久久久久久成人热 | 一本色道久久综合亚洲二区三区| 激情小视频在线| 国产婷婷色一区二区三区在线| 久久久久久亚洲精品不卡4k岛国| 色屁屁草草影院ccyycom| 国产成人综合视频| 成人影片在线播放| 免费观看a视频| 91丨国产丨九色丨pron| 精品国产综合| 久久天堂电影| 中文字幕不卡一区| 99久re热视频精品98| 欧美极品少妇videossex| 亚洲一区av在线| 国产欧美日韩网站| 国产高清不卡| 欧美性感一类影片在线播放| jizz欧美性11| 精品99re| 日韩经典第一页| 精品人伦一区二区三电影| 日韩电影二区| 欧美高清视频在线| 亚洲日本视频在线观看| 麻豆中文一区二区| 91精品入口蜜桃| 蜜臀久久久久久999| 91麻豆国产在线观看| 色阁综合av| 在线观看免费91| av资源种子在线观看| 国产精品久久一级| 免费看欧美黑人毛片| 澳门成人av网| 8x8x8国产精品| 一级特级黄色片| 四季av一区二区三区免费观看| 久久久999国产精品| 日本黄色片视频| 毛片一区二区三区| 精品亚洲欧美日韩| 午夜免费播放观看在线视频| 亚洲国产一区在线观看| 久久久久国产精品熟女影院| 精品国产亚洲日本| 亚洲欧美国产一区二区三区 | 精品亚洲成人| 欧美日韩电影在线观看| 国产性生活视频| 国产馆精品极品| 日韩欧美亚洲日产国产| 日本不卡影院| 欧美色爱综合网| 星空大象在线观看免费播放| 天天影视欧美综合在线观看| 欧美亚洲视频在线看网址| 91精品国产综合久| 久久精品一区四区| 日韩视频在线视频| 2020国产精品小视频| 亚洲欧美色婷婷| 精品无码人妻一区二区三区 | 欧美人与性动交α欧美精品| 国产精品一国产精品| 久久久亚洲影院| 国产精品欧美激情在线| 久久免费国产精品| 亚洲视频日韩精品| 污污网站免费观看| 亚洲黄页在线观看| 欧美激情中文网| 国产av一区二区三区精品| 国产日韩欧美亚洲| 中文字幕日本最新乱码视频| 欧美经典一区| 久久久国产91| 一区二区三区日| 国产女同性恋一区二区| 日韩av资源在线| 欧美色图五月天| 欧美激情一二三| 精品人妻无码一区二区色欲产成人| 中文字幕乱码日本亚洲一区二区| 成人观看免费完整观看| 欧洲亚洲一区二区三区| 97色在线观看| 日日夜夜精品免费| 亚洲国产一区视频| 中国xxxx性xxxx产国| 韩国在线视频一区| 国产传媒一区| 丁香花在线高清完整版视频| 日韩欧美黄色影院| 久久99久久久| 国产成人av一区二区三区在线 | 国产日韩欧美一区二区三区在线观看| 波多野结衣一区二区三区在线观看| 男人影院在线观看| 欧美区在线观看| 天天色影综合网| 国产在线精品视频| 国产人妻互换一区二区| 日韩三级久久| 欧美精品久久久久a| 亚洲经典一区二区三区| 亚洲成精国产精品女| 亚洲精品女人久久久| 国产精品色网| 色涩成人影视在线播放| 高清欧美日韩| 精品自拍视频在线观看| 蜜臀av免费在线观看| 香蕉加勒比综合久久| 无遮挡aaaaa大片免费看| 久久久久一区| 一区二区在线观看网站| 国语精品视频| 欧美性大战久久久久久久蜜臀| 99国产超薄丝袜足j在线观看| 深夜福利在线视频| 欧美性大战久久| 午夜少妇久久久久久久久| 成人免费视频国产在线观看| 91成人在线观看喷潮教学| 亚洲精品无吗| 91美女片黄在线观| аⅴ资源天堂资源库在线| 亚洲人高潮女人毛茸茸| 国产精品亚洲lv粉色| 亚洲va欧美va国产va天堂影院| 日本xxx在线播放| 蜜桃一区二区三区四区| 日韩精品久久一区二区| 自拍偷拍欧美一区| 亚洲最大的av网站| 在线天堂资源www在线污| www.欧美免费| 亚洲av成人精品毛片| 欧美日韩在线亚洲一区蜜芽| 麻豆91精品91久久久| 久久一日本道色综合| 超碰在线免费av| 美女网站久久| 日韩精品手机在线观看| 国产精品免费大片| 国产精品果冻传媒潘| 韩国精品主播一区二区在线观看| 欧美大尺度激情区在线播放| 男人的天堂在线| 日韩欧美高清dvd碟片| 色一情一乱一伦| 伊人婷婷欧美激情| 91在线无精精品白丝| 成人一区二区三区中文字幕| 在线看的黄色网址| 一本久道久久综合狠狠爱| 自拍偷拍亚洲色图欧美| 精品不卡一区| 国产一区免费观看| 综合久久伊人| 国产精品爽爽爽爽爽爽在线观看| 黄色aa久久| 欧美日韩国产成人在线观看| 香蕉视频免费在线播放| 亚洲欧美精品一区二区| 亚洲第一免费视频| 欧美一区二区三区在线电影| 波多野结衣av无码| 偷窥国产亚洲免费视频| 久久成人在线观看| 亚洲免费观看高清完整版在线观看| 国产伦理片在线观看| 91在线云播放| 国产免费一区二区| 亚洲精品午夜国产va久久成人| 国产亚洲1区2区3区| 欧美日韩人妻精品一区在线| 国产盗摄精品一区二区三区在线| 97超碰成人在线| 青青草视频一区| 99免费视频观看| 蜜乳av另类精品一区二区| 成人网站免费观看入口| 国内精品久久久久久久影视蜜臀| 一区二区三区日韩视频| 日韩国产一区二区| 午夜视频久久久| 色喇叭免费久久综合| 日韩欧美精品久久| 精品欧美激情在线观看| 日本成人黄色| 欧美日韩一二三四| 亚洲a∨一区二区三区| 国产一区二区三区四区五区| 欧美二区在线看| 国产一区二区三区站长工具| 欧洲精品码一区二区三区免费看| 九九免费精品视频在线观看| 欧美日韩三区四区| 精品日产免费二区日产免费二区| 水蜜桃一区二区三区| 日韩国产一区| gogogo免费高清日本写真| 天天综合精品| 日本福利视频在线观看| 国产精品videossex久久发布| 黄色一级片国产| 亚洲精品免费观看| 成年人视频在线免费| 日本不卡高清视频| 奇米影视四色在线| 国产盗摄精品一区二区三区在线| 国产污在线观看| 久久亚洲免费视频| 精品熟妇无码av免费久久| 亚洲欧美色图小说| 久草精品视频在线观看| 91成人免费在线| 国产探花精品一区二区| 亚洲第一av网| 极品美乳网红视频免费在线观看| 日韩视频中文字幕| 欧美日韩在线视频免费观看| 欧美在线观看日本一区| 97人人做人人爽香蕉精品| 99久久99| 日韩电影不卡一区| 伊人久久大香线蕉午夜av| 夜间精品视频| 日韩激情免费视频| 精品在线视频一区| 久久人人妻人人人人妻性色av| 久久精品欧美一区二区三区不卡 | 久久国产精品毛片| 无码熟妇人妻av在线电影| 久久资源在线| 手机在线观看日韩av| av中文一区二区三区| 国产真人真事毛片视频| 亚洲国产人成综合网站| 中文字幕一区二区三区四区免费看| 欧美一区二区啪啪| 欧美理论在线观看| 欧美成人精品影院| 欧美成a人片在线观看久| 97netav| 精品一区二区三| 丰满少妇久久久| 精品一区二区三区免费观看| 97人妻天天摸天天爽天天| 亚洲免费伊人电影| 波多野结衣视频观看| 亚洲第一区在线观看| 91福利在线视频| 亚州欧美日韩中文视频| 国产精品高清一区二区| 欧美xxxx黑人又粗又长精品| 欧美日本精品| 视频在线观看免费高清| 久久精品亚洲麻豆av一区二区| 久久久久久久中文字幕| 欧美日韩国产一级片| 撸视在线观看免费视频| 国产+人+亚洲| 日韩欧美中文字幕一区二区三区 | 久久久久麻豆v国产精华液好用吗| 国产精品久久久久久久久动漫| 成年免费在线观看| 精品少妇一区二区三区在线视频| 1pondo在线播放免费| 日韩av理论片| 亚洲盗摄视频| 欧美一级免费播放| 国产a区久久久| 国产高潮国产高潮久久久91 | 欧美成人精品一区二区三区在线看| 国产一区二区三区精彩视频| 成人三级伦理片| 久久久久亚洲av无码专区体验| 欧美日韩国产综合一区二区 | 久久国产精品网站| 亚洲欧美综合久久久久久v动漫| 色99中文字幕| 日本视频一区二区三区| 国产 欧美 在线| 欧美性高潮床叫视频| 欧洲综合视频| 91地址最新发布| 午夜先锋成人动漫在线| 自慰无码一区二区三区| 91在线免费播放| 国产成人精品网| 日韩精品在线私人| 另类图片综合电影| 欧美三日本三级少妇三99| 麻豆视频在线免费观看| 精品国产91乱码一区二区三区| a在线免费观看| 99久久精品久久久久久ai换脸| 永久91嫩草亚洲精品人人| 国产黄色一区二区三区| 亚洲综合视频网| 欧美一区二区三区激情| 69av在线播放| 激情婷婷综合| 日本一二区免费| 一区二区三区四区蜜桃| 乱精品一区字幕二区| 69精品小视频| 免费av一区| 五月天婷婷亚洲| 一区二区三区免费看视频| 日韩有码第一页| 国产不卡在线观看| 欧美国产美女| 无码人妻aⅴ一区二区三区玉蒲团| 亚洲线精品一区二区三区八戒| 日韩一级片免费| 国产成人+综合亚洲+天堂| 日韩一区亚洲二区| 亚洲一级片免费观看| 精品久久久久久中文字幕| 成人免费在线电影| 51国产成人精品午夜福中文下载| 精品成人在线| 亚洲AV无码国产成人久久| 欧美日韩精品欧美日韩精品| 色呦呦久久久| 欧美日韩综合网| 国产成人免费网站| av黄色在线播放| 久久久精品免费| 外国成人在线视频| 777一区二区| 精品女同一区二区三区在线播放| yiren22综合网成人| 91精品国产高清久久久久久91裸体| 国产精品日韩久久久| 成人自拍小视频| 亚洲免费视频网站| 国产精品久久久久久久久久辛辛 | 夜夜爽妓女8888视频免费观看| 久久中文字幕视频| 欧美猛男男男激情videos| 国产精品嫩草影视| 色美美综合视频|