четверг, 13 февраля 2020 г.

Defer, Panic, и Recover в Golang

Go имеет обычные механизмы управления потоком: if, for, switch, goto. Он также содержит утверждение (statement) go для запуска кода в отдельной goroutine. Но в этом посте мы обсудим некоторые из менее распространенных утверждений для управления потоком исполнения программы в Go: defer, panic, и recover.

Утверждение defer помещает вызов функции в список. Список сохраненных вызовов выполняется после возврата окружающей функции. Defer обычно используется для упрощения функций, которые выполняют различные действия по очистке.

Например, давайте посмотрим на функцию, которая открывает два файла и копирует содержимое одного файла в другой:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

Это работает, но есть ошибка. Если вызов os.Create завершится неудачно, функция вернется без закрытия исходного файла. Это можно легко исправить, поместив вызов src.Close перед вторым return утверждением, но если бы функция была более сложной, проблему было бы не так легко заметить и решить. Вводя defer утверждения, мы можем гарантировать, что файлы всегда закрыты:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

Defer утверждения позволяют нам думать о закрытии каждого файла сразу после его открытия, гарантируя, что, независимо от количества утверждений возврата в функции, файлы будут закрыты.

Поведение defer утверждений является простым и предсказуемым. Есть три простых правила:

1. Аргументы отложенной функции оцениваются, когда оценивается оператор defer.

В этом примере выражение "i" вычисляется при отсрочке вызова Println. Отложенный вызов выведет "0" после возврата из функции.

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

2. Отложенные вызовы функций выполняются в порядке "последний пришел - первый вышел" (Last In First Out, LIFO) после возврата окружающей функции.

Эта функция печатает "3210":

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

3. Отложенные функции могут читать и присваивать возвращаемой функции именованные возвращаемые значения.

В этом примере отложенная функция увеличивает возвращаемое значение i после возврата окружающей функции. Таким образом, эта функция возвращает 2:

func c() (i int) {
    defer func() { i++ }()
    return 1
}

Это удобно для изменения значения ошибки, возвращаемого функцией; мы увидим пример этого в ближайшее время.

Panic - это встроенная функция, которая останавливает обычный поток контроля и начинает паниковать. Когда функция F вызывает panic, выполнение F останавливается, все отложенные (defer) функции в F выполняются нормально, а затем F возвращается к своему вызывающему. Для вызывающей стороны F ведет себя как вызов panic. Процесс продолжается вверх по стеку до тех пор, пока не будут возвращены все функции в текущей процедуре, после чего программа завершится сбоем. Panic можно инициировать, вызывая panic напрямую. Они также могут быть вызваны ошибками во время выполнения, такими как доступ за пределы массива.

Recover - это встроенная функция, которая восстанавливает контроль над паникующими goroutine. Recover полезна только внутри отложенных (defer) функций. Во время обычного выполнения, вызов recover вернет nil и не будет иметь никакого другого эффекта. Если текущая goroutine вызывает panic, вызов recover захватит значение, переданное panic, и возобновит нормальное выполнение.

Вот пример программы, которая демонстрирует механику panic и recover:

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

Функция g принимает int i и паникует, если i больше 3, иначе она вызывает себя с аргументом i + 1. Функция f откладывает функцию, которая вызывает recover и печатает восстановленное значение (если оно не равно нулю).

Программа выведет:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

Если мы удалим отложенную (defer) функцию из f, panic не восстановится и достигнет вершины стека вызовов программы, завершив программу. Эта измененная программа выведет:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
 
panic PC=0x2a9cd8
[stack trace omitted]

Для реального примера panic и recover можно посмотреть пакет json из стандартной библиотеки Go. Он кодирует интерфейс с набором рекурсивных функций. Если при обходе значения возникает ошибка, вызывается паника, чтобы развернуть стек для вызова функции верхнего уровня, который восстанавливается после паники и возвращает соответствующее значение ошибки.

Соглашение в библиотеках Go заключается в том, что даже когда пакет использует внутреннюю panic, его внешний API все еще представляет явные возвращаемые значения ошибок.

Другие способы использования defer (кроме приведенный выше примера file.Close) включают освобождение мьютекса:

mu.Lock()
defer mu.Unlock()

печать нижнего колонтитула:

printHeader()
defer printFooter()

и другие.

Таким образом, оператор defer (с panic и recover или без них) обеспечивает необычный и мощный механизм управления потоком исполнения программы. Он может использоваться для моделирования ряда функций, реализованных структурами специального назначения в других языках программирования.


Читайте также:


среда, 12 февраля 2020 г.

Срезы в Golang: внутреннее устройство и использование

Тип среза (slice) в Go предоставляет удобный и эффективный способ работы с последовательностями типизированных данных. Срезы аналогичны массивам на других языках, но имеют некоторые необычные свойства. В этом посте мы рассмотрим, что такое срезы и как они используются.

Массивы

Тип среза (slice) - это абстракция, построенная поверх типа массива Go, и поэтому для понимания срезов мы должны сначала понять массивы.

Определение типа массива определяет длину и тип элемента. Например, тип [4]int представляет массив из четырех целых чисел. Размер массива фиксирован; его длина является частью его типа ([4]int и [5]int являются различными несовместимыми типами). Массивы можно индексировать обычным способом, поэтому выражение s[n] обращается к n-му элементу, начиная с нуля.

var a [4]int
a[0] = 1
i := a[0]
// i == 1

Массивы не нужно явно инициализировать; нулевое значение массива - это готовый массив, элементы которого сами обнуляются:

// a[2] == 0, нулевое значение типа int

Представление [4]int в памяти - это всего лишь четыре целочисленных значения, расположенных последовательно:

Массивы в Go являются значениями. Переменная массива обозначает весь массив; это не указатель на первый элемент массива (как в случае с C). Это означает, что когда вы присваиваете или передаете значение массива, вы будете делать копию его содержимого. (Чтобы избежать копирования, вы можете передать указатель на массив, но тогда это указатель на массив, а не массив.) Один из способов представить себе массивы - считать их некоей структурой, но с индексированными, а не именованными полями: составное значение с жестко заданным размером.

Литерал массива может быть указан так:

b := [2]string{"Ivan", "Anton"}

Или вы можете сделать так, чтобы компилятор подсчитал для вас элементы массива:

b := [...]string{"Ivan", "Anton"}

В обоих случаях тип b - это [2]string.

Срезы

Массивы имеют свое место, но они немного негибкие, поэтому вы не видите их слишком часто в коде Go. Срезы, однако, есть везде. Они основаны на массивах, чтобы обеспечить большую мощность и удобство.

Спецификация типа для среза: []T, где T - тип элементов среза. В отличие от типа массива, тип среза не имеет заданной длины.

Литерал среза объявляется так же, как литерал массива, за исключением того, что вы пропускаете количество элементов:

letters := []string{"a", "b", "c", "d"}

Срез может быть создан с помощью встроенной функции под названием make, которая имеет сигнатуру,

func make([]T, len, cap) []T

где T обозначает тип элемента среза, который будет создан. Функция make принимает тип, длину и дополнительную емкость. При вызове make выделяет массив и возвращает срез, который ссылается на этот массив.

var s []byte
s = make([]byte, 5, 5)
// s == []byte{0, 0, 0, 0, 0}

Если аргумент емкости (cap) опущен, по умолчанию используется указанная длина. Вот более краткая версия того же кода:

s := make([]byte, 5)

Длину и емкость среза можно проверить с помощью встроенных функций len и cap.

len(s) == 5
cap(s) == 5

В следующих двух разделах обсуждается связь между длиной и емкостью.

Нулевое значение среза равно nil. Функции len и cap возвращают 0 для нулевого среза.

Срез также может быть сформирован путем "нарезки" существующего среза или массива. Разрезание выполняется путем указания полуоткрытого диапазона с двумя индексами, разделенными двоеточием. Например, выражение b [1:4] создает срез, включающий элементы с 1 по 3 из b (индексы полученного среза будут от 0 до 2).

b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
// b[1:4] == []byte{'o', 'l', 'a'} 
// используя то же хранилище, что и b

Начальный и конечный индексы выражения среза являются необязательными; по умолчанию они равны нулю и длине среза соответственно:

// b[:2] == []byte{'g', 'o'}
// b[2:] == []byte{'l', 'a', 'n', 'g'}
// b[:] == b

Это также синтаксис для создания среза по массиву:

x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // срез, ссылающийся на хранилище x

Внутренности среза

Срез - это дескриптор сегмента массива. Он состоит из указателя на массив, длины сегмента и его емкости (максимальной длины сегмента).

Наша переменная s, созданная ранее make([]byte, 5), имеет следующую структуру:

Длина (len) - это количество элементов, на которые ссылается срез. Емкость (cap) - это количество элементов в базовом массиве (начиная с элемента, на который указывает указатель среза). Различие между длиной и емкостью станет понятным, когда мы рассмотрим следующие несколько примеров.

Когда мы срезаем s, наблюдаем за изменениями в структуре данных среза и их отношением к базовому массиву:

s = s[2:4]

Нарезка не копирует данные среза. Он создает новое значение среза, которое указывает на исходный массив. Это делает операции среза такими же эффективными, как манипулирование индексами массива. Следовательно, изменение элементов (не самого среза) повторного среза изменяет элементы исходного среза:

d := []byte{'r', 'o', 'a', 'd'}
e := d[2:] 
// e == []byte{'a', 'd'}
e[1] = 'm'
// e == []byte{'a', 'm'}
// d == []byte{'r', 'o', 'a', 'm'}

Ранее мы нарезали s до длины, меньшей его емкости. Мы можем увеличить s до его емкости, нарезав его снова:

s = s[:cap(s)]

Срез не может быть выращен за пределами его емкости. Попытка сделать это вызовет панику во время выполнения (runtime panic), так же как и при индексировании вне границ среза или массива. Точно так же срезы нельзя повторно разрезать ниже нуля для доступа к более ранним элементам в массиве.

Растущие срезы (функции copy и append)

Чтобы увеличить емкость среза, нужно создать новый, больший срез и скопировать в него содержимое исходного среза. Эта техника - то, как реализации динамического массива из других языков работают за кулисами. В следующем примере удваивается емкость s, создавая новый фрагмент t, копируя содержимое s в t, а затем присваивая значение фрагмента t к s:

// +1 в случае cap(s) == 0
t := make([]byte, len(s), (cap(s)+1)*2) 
for i := range s {
        t[i] = s[i]
}
s = t

Цикл этой общей операции упрощается благодаря встроенной функции copy. Как следует из названия, copy копирует данные из исходного среза в целевой срез. Возвращает количество скопированных элементов.

func copy(dst, src []T) int

Функция copy поддерживает копирование между срезами разной длины (она будет копировать только до меньшего количества элементов). Кроме того, copy может обрабатывать срезы источника и назначения, которые совместно используют один и тот же базовый массив, правильно обрабатывая перекрывающиеся срезы.

Используя copy, мы можем упростить приведенный выше фрагмент кода:

t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t

Обычной операцией является добавление данных в конец среза. Эта функция добавляет байтовые элементы к срезу байтов, при необходимости увеличивая его, и возвращает обновленное значение среза:

func AppendByte(slice []byte, data ...byte) []byte {
    m := len(slice)
    n := m + len(data)
    // если необходимо, перераспределяем
    if n > cap(slice) { 
        // выделяем вдвое больше, чем нужно, 
        // для будущего роста.
        newSlice := make([]byte, (n+1)*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:n]
    copy(slice[m:n], data)
    return slice
}

Можно использовать AppendByte следующим образом:

p := []byte{2, 3, 5}
p = AppendByte(p, 7, 11, 13)
// p == []byte{2, 3, 5, 7, 11, 13}

Такие функции, как AppendByte, полезны, потому что они предлагают полный контроль над тем, как растет срез. В зависимости от характеристик программы, может быть желательно распределить ее на более мелкие или большие куски или ограничить размер перераспределения.

Но большинству программ не требуется полный контроль, поэтому Go предоставляет встроенную функцию append, которая подходит для большинства целей; ее сигнатура

func append(s []T, x ...T) []T

Функция append добавляет элементы x в конец среза s и увеличивает его, если требуется большая емкость.

a := make([]int, 1)
// a == []int{0}
a = append(a, 1, 2, 3)
// a == []int{0, 1, 2, 3}

Чтобы добавить один фрагмент к другому, используйте ..., чтобы развернуть второй аргумент в список аргументов.

a := []string{"John", "Paul"}
b := []string{"George", "Ringo", "Pete"}

// эквивалентно "append(a, b[0], b[1], b[2])"
a = append(a, b...) 

// a == []string{"John", "Paul", "George", "Ringo", "Pete"}

Поскольку нулевое значение среза (nil) действует как срез нулевой длины, вы можете объявить переменную среза и затем добавить ее в цикл:

// Filter возвращает новый срез, содержащий только
// элементы s, которые удовлетворяют fn()
func Filter(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

Трюк со срезом

Как упоминалось ранее, повторная нарезка среза не делает копию базового массива. Полный массив будет храниться в памяти до тех пор, пока на него больше не будут ссылаться. Иногда это может привести к тому, что программа сохранит все данные в памяти, когда требуется только небольшой срез.

Например, эта функция FindDigits загружает файл в память и ищет в нем первую группу последовательных числовых цифр, возвращая их как новый фрагмент.

var digitRegexp = regexp.MustCompile("[0-9]+")

func FindDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    return digitRegexp.Find(b)
}

Этот код ведет себя как объявлено, но возвращенный []byte указывает на массив, содержащий весь файл. Поскольку срез ссылается на исходный массив, пока срез хранится вокруг сборщика мусора, он не может освободить массив; несколько полезных байтов файла сохраняют все содержимое в памяти.

Чтобы решить эту проблему, можно скопировать интересные данные в новый срез перед возвратом:

func CopyDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    b = digitRegexp.Find(b)
    c := make([]byte, len(b))
    copy(c, b)
    return c
}


Читайте также:


пятница, 7 февраля 2020 г.

Трассировка HTTP запросов в Golang

Начиная с версии 1.7 в Go появилась трассировка HTTP, средство для сбора детальной информации в течение всего жизненного цикла запроса клиента HTTP. Поддержка трассировки HTTP обеспечивается
пакетом net/http/httptrace. Собранная информация может использоваться для устранения проблем с задержкой, мониторинга служб, написания адаптивных систем и многого другого.

HTTP события

В пакете httptrace есть несколько хуков для сбора информации о различных событиях во время выполнения HTTP-запроса. Эти события включают в себя:

  • Создание соединения
  • Повторное использование соединения
  • DNS-поиск
  • Запись запроса в сеть (на провод)
  • Чтение ответа

Отслеживание событий

Вы можете включить HTTP-трассировку, поместив *httptrace.ClientTrace, содержащий функции ловушек (hook functions), в context.Context запроса. Различные реализации http.RoundTripper сообщают о внутренних событиях, ища контекст *httptrace.ClientTrace и вызывая соответствующие функции ловушек.

Трассировка ограничена контекстом запроса, и пользователи должны поместить *httptrace.ClientTrace в контекст запроса, прежде чем они начнут запрос.

req, _ := http.NewRequest("GET", "http://example.com", nil)
trace := &httptrace.ClientTrace{
    DNSDone: func(dnsInfo httptrace.DNSDoneInfo) {
        fmt.Printf("DNS Info: %+v\n", dnsInfo)
    },
    GotConn: func(connInfo httptrace.GotConnInfo) {
        fmt.Printf("Got Conn: %+v\n", connInfo)
    },
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
if _, err := http.DefaultTransport.RoundTrip(req); err != nil {
    log.Fatal(err)
}

Во время передачи туда и обратно http.DefaultTransport будет вызывать каждый хук, когда происходит событие. Программа выше напечатает информацию DNS, как только поиск DNS будет завершен. Он также будет печатать информацию о соединении, когда соединение установлено с хостом запроса.

Трассировка с http.Client

Механизм трассировки предназначен для отслеживания событий в жизненном цикле одного http.Transport.RoundTrip. Тем не менее, клиент может совершить несколько обращений для выполнения HTTP-запроса. Например, в случае перенаправления URL-адреса зарегистрированные хуки будут вызываться столько раз, сколько клиент выполняет перенаправления HTTP, делая несколько запросов. Пользователи несут ответственность за распознавание таких событий на уровне http.Client. Программа ниже идентифицирует текущий запрос, используя оболочку http.RoundTripper.

package main

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httptrace"
)

// transport это http.RoundTripper 
// который отслеживает выполняемый запрос
// и реализуек хуки для сообщения HTTP событий отслеживания
type transport struct {
    current *http.Request
}

// RoundTrip оборачивает http.DefaultTransport.RoundTrip
// чтобы отслеживать текущий запрос.
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
    t.current = req
    return http.DefaultTransport.RoundTrip(req)
}

// GotConn печатает информацию о том 
// было ли соединение использовано ранее для текущего запроса
func (t *transport) GotConn(info httptrace.GotConnInfo) {
    fmt.Printf("Connection reused for %v? %v\n", t.current.URL, info.Reused)
}

func main() {
    t := &transport{}

    req, _ := http.NewRequest("GET", "https://google.com", nil)
    trace := &httptrace.ClientTrace{
        GotConn: t.GotConn,
    }
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

    client := &http.Client{Transport: t}
    if _, err := client.Do(req); err != nil {
        log.Fatal(err)
    }
}

Программа выполнит перенаправление google.com на www.google.com и выведет:

Connection reused for https://google.com? false
Connection reused for https://www.google.com/? false

Transport в пакете net/http поддерживает трассировку запросов HTTP/1 и HTTP/2.

Если вы являетесь автором пользовательской реализации http.RoundTripper, вы можете поддержать трассировку, проверив контекст запроса для *httptest.ClientTrace и вызвав соответствующие хуки по мере возникновения событий.

Вывод

HTTP-трассировка является ценным дополнением к Go для тех, кто интересуется отладкой задержки HTTP-запросов и инструментами записи для сетевой отладки для исходящего трафика.


Читайте также:


четверг, 6 февраля 2020 г.

Тип ServeMux пакета net/http в Golang

Тип ServeMux

ServeMux - это мультиплексор HTTP-запросов. Он сопоставляет URL-адрес каждого входящего запроса со списком зарегистрированных шаблонов и вызывает обработчик для шаблона, который наиболее точно соответствует URL-адресу.

Фиксированные имена шаблонов, корневые пути, например "/favicon.ico", или корневые поддеревья, например "/images/" (обратите внимание на закрывающий слэш). Более длинные шаблоны имеют приоритет над более короткими, поэтому, если есть обработчики, зарегистрированные как для "/images/", так и "/images/thumbnails/", последний обработчик будет вызываться для путей, начинающихся с "/images/thumbnails/", а первый будет получать запросы на любые другие пути в поддереве "/images/".

Обратите внимание, что, поскольку шаблон, заканчивающийся слэшем, называет корневое поддерево, шаблон "/" соответствует всем путям, которые не соответствуют другим зарегистрированным шаблонам, а не только URL-адресу с путем == "/".

Если поддерево было зарегистрировано и получен запрос с именем корня поддерева без его косой черты, ServeMux перенаправляет этот запрос в корень поддерева (добавляя косую черту). Это поведение может быть отменено отдельной регистрацией пути без завершающего слэша. Например, регистрация "/images/" заставляет ServeMux перенаправить запрос "/images" на "/images/", если только "/images" не был зарегистрирован отдельно.

При желании шаблоны могут начинаться с имени хоста, ограничивая совпадения только с URL-адресами на этом хосте. Специфичные для хоста шаблоны имеют приоритет над общими шаблонами, так что обработчик может зарегистрироваться для двух шаблонов "/codesearch" и "codesearch.google.com/", не принимая также запросы на "http://www.google.com/".

ServeMux также заботится о санации пути URL-запроса и заголовка хоста, удалении номера порта и перенаправлении любого запроса содержащего . или .. элементы или повторяющиеся слэши к эквивалентному, более чистому URL.

type ServeMux struct {
    // содержит фильтруемые или неэкспортируемые поля
}

Функция NewServeMux

func NewServeMux() *ServeMux

NewServeMux выделяет и возвращает новый ServeMux.

Метод (*ServeMux) Handle

func (mux *ServeMux) Handle(pattern string, handler Handler)

Handle регистрирует обработчик для данного шаблона. Если обработчик для шаблона уже существует, Handle вызывает panic.

Пример использования ServeMux и его метода Handle:

package main

import (
  "fmt"
  "net/http"
)

type apiHandler struct{}

func (apiHandler) ServeHTTP(http.ResponseWriter, *http.Request) {}

func main() {
  mux := http.NewServeMux()
  mux.Handle("/api/", apiHandler{})
  mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    // "/" паттерн соотвествует всему, 
    // поэтому нам нужно проверить
    // что это на самом деле запрос корня
    if req.URL.Path != "/" {
      http.NotFound(w, req)
      return
    }
    fmt.Fprintf(w, "Welcome to the home page!")
  })
  log.Fatal(http.ListenAndServe(":8080", mux))
}

Метод (*ServeMux) HandleFunc

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))

HandleFunc регистрирует функцию-обработчик для данного шаблона.

Метод (*ServeMux) Handler

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)

Появился начиная с версии Go 1.1

Handler возвращает обработчик для использования для данного запроса, консультируясь с r.Method, r.Host и r.URL.Path. Он всегда возвращает ненулевой обработчик. Если путь не в своей канонической форме, обработчик будет внутренне сгенерированным обработчиком, который перенаправляет на канонический путь. Если хост содержит порт, он игнорируется при сопоставлении обработчиков.

Путь и хост используются без изменений для запросов CONNECT.

Handler также возвращает зарегистрированный шаблон, соответствующий запросу, или, в случае внутренних переадресаций, шаблон, который будет соответствовать после выполнения перенаправления.

Если нет зарегистрированного обработчика, который применяется к запросу, Handler возвращает обработчик “page not found” (страница не найдена) и пустой шаблон.

Метод (*ServeMux) ServeHTTP

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

ServeHTTP отправляет запрос обработчику, чей шаблон наиболее точно соответствует URL-адресу запроса.


Читайте также:


Функции Get, Head, Post, PostForm пакета net/http в Golang

Функция Get

func Get(url string) (resp *Response, err error)

Get выдает GET по указанному URL. Если ответ является одним из следующих кодов перенаправления, Get следует за перенаправлением, максимум до 10 перенаправлений:

301 (Moved Permanently)
302 (Found)
303 (See Other)
307 (Temporary Redirect)
308 (Permanent Redirect)

Ошибка возвращается, если было слишком много перенаправлений или если произошла ошибка протокола HTTP. Ответ не-2xx не вызывает ошибку. Любая возвращаемая ошибка будет иметь тип *url.Error. Метод Timeout значения url.Error сообщит true, если запрос истек или был отменен.

Когда err равно nil, resp всегда содержит не-nil resp.Body. Вызывающий должен закрыть resp.Body когда закончил чтение с него.

Get - это обертка вокруг DefaultClient.Get.

Чтобы сделать запрос с пользовательскими заголовками, используйте NewRequest и DefaultClient.Do.

Пример использования Get:

package main

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

func main() {
  res, err := http.Get("http://www.google.com/robots.txt")
  if err != nil {
    log.Fatal(err)
  }
  robots, err := ioutil.ReadAll(res.Body)
  res.Body.Close()
  if err != nil {
    log.Fatal(err)
  }
  fmt.Printf("%s", robots)
}

Функция Head

func Head(url string) (resp *Response, err error)

Head отправляет HEAD на указанный URL. Если ответом является один из следующих кодов перенаправления, Head следует за перенаправлением, максимум до 10 перенаправлений:

301 (Moved Permanently)
302 (Found)
303 (See Other)
307 (Temporary Redirect)
308 (Permanent Redirect)

Head является оберткой вокруг DefaultClient.Head

Функция Post

func Post(url, contentType string, body io.Reader) (resp *Response, err error)

Post отправляет POST по указанному URL.

Вызывающий должен закрыть resp.Body когда закончит чтение с него.

Если предоставленное тело является io.Closer, оно закрывается после запроса.

Post является оберткой вокруг DefaultClient.Post.

Чтобы установить пользовательские заголовки, используйте NewRequest и DefaultClient.Do.

Функция PostForm

func PostForm(url string, data url.Values) (resp *Response, err error)

PostForm отправляет POST для указанного URL с ключами и значениями данных, закодированными в виде тела запроса.

Заголовок Content-Type установлен на application/x-www-form-urlencoded. Чтобы установить другие заголовки, используйте NewRequest и DefaultClient.Do.

Когда err равно nil, resp всегда содержит не-nil resp.Body. Вызывающий должен закрыть resp.Body, когда закончит чтение с него.

PostForm - это обертка вокруг DefaultClient.PostForm.


Читайте также:


Тип Handler, функции FileServer, NotFoundHandler пакета net/http в Golang

Тип Handler

Handler отвечает на запрос HTTP.

ServeHTTP должен записать заголовки ответа и данные в ResponseWriter и затем вернуться. Возвращает сигнал о том, что запрос завершен; недопустимо использовать ResponseWriter или читать из Request.Body после или одновременно с завершением вызова ServeHTTP.

В зависимости от программного обеспечения HTTP-клиента, версии протокола HTTP и любых посредников между клиентом и сервером Go может быть невозможным чтение из Request.Body после записи в ResponseWriter. Осторожные обработчики должны сначала прочитать Request.Body, а затем ответить.

За исключением чтения тела, обработчики не должны изменять предоставленный запрос.

Если ServeHTTP вызывает panic, сервер (вызывающий ServeHTTP) предполагает, что эффект паники был изолирован для активного запроса. Он восстанавливает панику, записывает трассировку стека в журнал ошибок сервера и либо закрывает сетевое соединение, либо отправляет HTTP/2 RST_STREAM, в зависимости от протокола HTTP. Чтобы прервать обработчик, чтобы клиент увидел прерванный ответ, но сервер не зарегистрировал ошибку, вызовите panic со значением ErrAbortHandler.

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Функция FileServer

func FileServer(root FileSystem) Handler

FileServer возвращает обработчик, который обслуживает HTTP-запросы с содержимым файловой системы с корнем в root.

Чтобы использовать реализацию файловой системы операционной системы, используйте http.Dir:

http.Handle("/", http.FileServer(http.Dir("/tmp")))

В особом случае возвращаемый файловый сервер перенаправляет любой запрос, оканчивающийся на "/index.html", по тому же пути, без конечного "index.html".

Примеры:

package main

import (
  "log"
  "net/http"
)

func main() {
  // Простой статический веб-сервер:
  log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
}

Пример:

package main

import (
  "net/http"
)

func main() {
  // Обслуживаем каталог на диске (/tmp) под альтернативным URL
  // путем (/tmpfiles/), используем StripPrefix для изменения запроса
  // пути URL до того, как FileServer его увидит:
  http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
}

Функция NotFoundHandler

func NotFoundHandler() Handler

NotFoundHandler возвращает простой обработчик запроса, который отвечает на каждый запрос с ответом “404 page not found”.

Пример:

package main

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

func newPeopleHandler() http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "This is the people handler.")
  })
}

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

  // Создаем тестовый обработчик для возврата 404
  mux.Handle("/resources", http.NotFoundHandler())

  // Создаем тестовый обработчик для возврата 200
  mux.Handle("/resources/people/", newPeopleHandler())

  log.Fatal(http.ListenAndServe(":8080", mux))
}

Функция StripPrefix

func StripPrefix(prefix string, h Handler) Handler

StripPrefix возвращает обработчик, который обслуживает HTTP-запросы, удаляя указанный префикс из пути URL запроса и вызывая обработчик h. StripPrefix обрабатывает запрос пути, который не начинается с префикса, отвечая сообщением об ошибке HTTP 404 not found.

Пример:

package main

import (
  "net/http"
)

func main() {
  // Обслуживаем каталог на диске (/tmp) по альтернативному URL
  // пути (/tmpfiles/), используем StripPrefix для изменения 
  // пути URL запроса до того, как FileServer его увидит:
  http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
}


Читайте также:


суббота, 1 февраля 2020 г.

Тип Client пакета net/http в Golang

Client - это клиент HTTP. Его нулевое значение (DefaultClient) является используемым клиентом, который использует DefaultTransport.

Transport клиента обычно имеет внутреннее состояние (кэшированные TCP-соединения), поэтому клиенты должны использоваться повторно, а не создаваться по мере необходимости. Клиенты безопасны для одновременного использования несколькими go-процедурами (goroutines).

Client более высокого уровня, чем RoundTripper (например, Transport), дополнительно обрабатывает детали HTTP, такие как cookie-файлы и перенаправления.

При следующих перенаправлениях Client будет пересылать все заголовки, установленные в начальном Request, кроме:

  • при пересылке конфиденциальных заголовков, таких как "Authorization", "WWW-Authenticate" и "Cookie", ненадежным целям. Эти заголовки будут игнорироваться при перенаправлении на домен, который не совпадает с поддоменом или не совпадает с исходным доменом. Например, перенаправление с "foo.com" на "foo.com" или "sub.foo.com" будет перенаправлять конфиденциальные заголовки, а перенаправление на "bar.com" - нет.
  • при пересылке заголовка "Cookie" с не-nil cookie Jar. Поскольку каждое перенаправление может изменить состояние cookie jar, перенаправление может изменить набор cookie jar в начальном запросе. При пересылке заголовка "Cookie" любые мутированные cookies будут опущены, при этом ожидается, что Jar вставит эти мутированные cookies с обновленными значениями (при условии, что происхождение совпадает). Если Jar равен nil, исходные файлы cookie отправляются без изменений.

type Client struct {
    // Transport определяет механизм, с помощью которого
    // индивидуальные HTTP-запросы сделаны.
    // Если nil, используется DefaultTransport.
    Transport RoundTripper

    // CheckRedirect указывает политику для обработки 
    // перенаправлений.
    // Если CheckRedirect не равен nil, клиент вызывает 
    // его раньше после перенаправления HTTP. 
    // Аргументы req и via являются предстоящим запросом
    // и уже сделанными запросами, самый старый первый.
    // Если CheckRedirect возвращает ошибку, 
    // клиент получает метод возвращающий оба предыдущих 
    // ответа (со своим Body закрытым) 
    // и ошибку CheckRedirect (обернута в url.Error)
    // вместо выдачи Request req
    // Как частный случай, 
    // если CheckRedirect возвращает ErrUseLastResponse,
    // тогда самый последний ответ возвращается с его Body
    // незакрытым, вместе с ошибкой nil.
    //
    // Если CheckRedirect равен nil, 
    // Client использует свою политику по умолчанию,
    // который должен остановиться 
    // после 10 последовательных запросов.
    CheckRedirect func(req *Request, via []*Request) error

    // Jar определяет cookie jar.
    //
    // Jar используется для вставки 
    // соответствующих cookie в каждый
    // исходящий Request и обновляется значениями cookie
    // каждого входящего ответа. 
    // Jar консультируют для каждого
    // перенаправления, которому клиент следует.
    //
    // Если Jar nil, cookies отправляются 
    // только если они явно
    // устанавливается по запросу.
    Jar CookieJar

    // Timeout указывает ограничение по времени 
    // для запросов, сделанных этим Client. 
    // Тайм-аут включает время подключения, любое
    // перенаправление и чтение тела ответа. 
    // Таймер остается выполняемым 
    // после возврата Get, Head, Post или Do
    // и будет прерывать чтение Response.Body.
    //
    // Timeout нулевой означает отсутствие тайм-аута.
    //
    // Client отменяет запросы к подлежащему Transport
    // как если бы Context запроса завершился.
    //
    // Для совместимости Client 
    // также будет использовать устаревший
    // метод CancelRequest на Transport, если найден. 
    // Новые реализации RoundTripper 
    // должны использовать Context запроса
    // для отмены вместо реализации CancelRequest.
    Timeout time.Duration // Go 1.3
}


Читайте также: