суббота, 23 марта 2019 г.

Пакет net/http, краткий обзор

Пакет http предоставляет реализации HTTP клиента и сервера.

Get, Head, Post, и PostForm выполняют HTTP (или HTTPS) запросы:

resp, err := http.Get("http://example.com/")
...
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
...
resp, err := http.PostForm("http://example.com/form",
  url.Values{"key": {"Value"}, "id": {"123"}})

Клиент должен закрывать тело ответа когда закончил с ним работать:

resp, err := http.Get("http://example.com/")
if err != nil {
  // handle error
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
// ...

Для контроля над клиентскими HTTP заголовками, политикой перенаправлений, и другими настройками создавайте Client:

client := &http.Client{
  CheckRedirect: redirectPolicyFunc,
}

resp, err := client.Get("http://example.com")
// ...

req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("If-None-Match", W/"wyzzy")
resp, err := client.Do(req)
// ...

Для контроля над прокси, TLS конфигурацией, сохранения открытого соединения (keep-alive), сжатием, и другими настройками, создавайте Transport:

tr := &http.Transport{
  MaxIdleConns:       10,
  IdleConnTimeout:    30 * time.Second,
  DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")

Client и Transport безопасны для конкурентного использования несколькими go-процедурами (goroutines) и для эффективности должны быть созданы только однажды и повторно использоваться.

ListenAndServe стартует HTTP-сервер с заданным адресом и обработчиком (handler). Обработчик (handler) обычно равен nil, что означает использовать DefaultServeMux. Handle и HandleFunc добавляют обработчики к DefaultServeMux:

http.Handle("/foo", fooHandler)

http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})

log.Fatal(http.ListenAndServe(":8080", nil))

Больше контроля над поведением сервера доступно посредством создания пользовательского Server:

s := &http.Server{
  Addr:           ":8080",
  Handler:        myHandler,
  ReadTimeout:    10 * time.Second,
  WriteTimeout:   10 * time.Second,
  MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())

Начиная с Go 1.6, http пакет имеет прозрачную поддержку HTTP/2 протокола при использовании HTTPS. Программы которые должны отключить HTTP/2 могут сделать это посредством настройки Transport.TLSNextProto (для клиентов) или Server.TLSNextProto (для серверов) к не-nil, пустой map. В качестве альтернативы на данный момент поддерживаются следующие переменные окружения GODEBUG:

GODEBUG=http2client=0  # отключаем клиентскую поддержку HTTP/2
GODEBUG=http2server=0  # отключаем серверную поддержку HTTP/2
GODEBUG=http2debug=1   # включаем подробные HTTP/2 отладочные логи
GODEBUG=http2debug=2   # ... еще более подробные, с дампами фреймов

В пакете http Transport и Server оба автоматически включают HTTP/2 поддержку в простых конфигурациях. Для включения HTTP/2 для более сложных конфигураций, для использования низкоуровневых HTTP/2 возможностей, или для использования новой версии Go http2 пакета выполняйте import "golang.org/x/net/http2" напрямую и используйте его ConfigureTransport и/или ConfigureServer функции. Ручное конфигурирование HTTP/2 посредством golang.org/x/net/http2 пакета имеет первенство над встроенной поддержкой HTTP/2 в net/http пакете.


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


воскресенье, 17 марта 2019 г.

Go примеры: Hello, gopher!

Сегодня я представляю вам пример простой веб-программы на Go. Программа принимает параметр name переданный в GET запросе и приветствует пользователя. В программе представлен пример обслуживания http-запроса с помощью пакета net/http. Также в программе использован пакет flag для обработки параметров командной строки, передаваемых программе при запуске - в нашем случае программа обрабатывает флаг port и прослушивает запросы по указанному порту.

package main

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

var PORT = flag.Int("port", 15001, "listen port")

func Hello(w http.ResponseWriter, r *http.Request) {

 keys, ok := r.URL.Query()["name"]

 if !ok || len(keys[0]) < 1 {
  log.Println("Url параметр 'name' пропущен")
  return
 }

 // Query()["key"] будет возвращать массив элементов,
 // нам нужен только один элемент.
 key := keys[0]

 log.Println("Url параметр 'name': " + string(key))

 message := "Hello, " + string(key) + "!"
 head := "" + message + ""
 response := "" + head + "" + message + ""

 w.Header().Set("Content-Type", "text/html")
 w.Write([]byte(response))
}

func main() {
 flag.Parse()
 addr := fmt.Sprintf(":%d", *PORT)
 log.Println("Программа запущена на порту " + addr)
 http.HandleFunc("/hello", Hello)
 err := http.ListenAndServe(addr, nil)
 if err != nil {
  log.Fatal("ListenAndServe: ", err)
 }
}

Пример сборки и запуска (из папки где находится файл hello.go с исходным кодом программы):

go build
./hello // для Linux

go build
hello.exe // для Windows

Пример запроса:


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


Go Code Review Comments: Имена переменных

Имена переменных в Go должны быть короткими, а не длинными. Это особенно верно для локальных переменных с ограниченной областью видимости. Предпочитайте с вместо lineCount. Предпочитайте i вместо sliceIndex.

Основное правило: чем дальше от декларации используется имя, тем более информативным должно быть имя. Для получателя метода достаточно одной или двух букв. Общие переменные, такие как индексы цикла и читатели (readers), могут состоять из одной буквы (i, r). Более необычные вещи и глобальные переменные требуют более описательных имен.


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


Go Code Review Comments: Полезные падения тестов

Тесты должны падать с полезными сообщениями, в которых говорится, что было не так, с какими вводными данными, что на самом деле было получено и что ожидалось. Может быть заманчиво написать кучу помощников assertFoo, но убедитесь, что ваши помощники выдают полезные сообщения об ошибках. Предположите, что человек, отлаживающий ваш упавший тест - это не вы и не ваша команда. Типичный Go тест проходит не так, как следующий:

if got != tt.want {
    t.Errorf("Foo(%q) = %d; want %d", tt.in, got, tt.want) 
    // или Fatalf, 
    // если тест не может тестировать что-либо далее 
    // этой точки
}

Обратите внимание, на порядок актуальный != ожидаемый, и сообщение также использует этот порядок. Некоторые тестовые среды рекомендуют записывать их в обратном порядке: 0 != x, «ожидаемый 0, полученный x» и так далее. Go не использует такой подход.

Если вам кажется, что набирается много текста, вы можете написать табличный тест.

Еще одна распространенная техника устранения неоднозначности неудачных тестов - это использование тестового помощника (test helper) с разными вводными данными - оберните каждого вызывающего различной функцией TestFoo, таким образом тест будет завершаться неудачно с разным именем:

func TestSingleValue(t *testing.T) { testHelper(t, []int{80}) }
func TestNoValues(t *testing.T)    { testHelper(t, []int{}) }

В любом случае, на вас ложится обязанность написать полезное сообщение тому, кто будет отлаживать ваш код в будущем.


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


пятница, 15 марта 2019 г.

Go Code Review Comments: Тип получателя (Receiver Type)

Выбор того, использовать ли получатель значения (value receiver) или получатель указателя (pointer receiver) в методах, может быть трудным, особенно для новых программистов на Go. В случае сомнений используйте указатель, но бывают случаи, когда получатель значения имеет смысл, обычно по соображениям эффективности, например, для небольших неизменяющихся структур или значений базового типа. Вот некоторые полезные рекомендации:

  • Если получателем является карта, func или chan, не используйте указатель на них. Если получатель является срезом (slice), а метод не срезает и не перераспределяет срез, не используйте указатель на него.
  • Если метод должен изменять получатель, получатель должен быть указателем.
  • Если получатель является структурой, которая содержит sync.Mutex или подобное поле синхронизации, получатель должен быть указателем, чтобы избежать копирования.
  • Если получатель является большой структурой или массивом, указатель получателя является более эффективным. Насколько большой структурой или массивом? Предположим, что это эквивалентно передаче всех его элементов в качестве аргументов методу. Если он кажется слишком большим, он также слишком велик для получателя.
  • Могут ли функции или методы, одновременно или при вызове из этого метода, изменять получателя? Тип значения создает копию получателя при вызове метода, поэтому внешние обновления не будут применяться к этому получателю. Если изменения должны быть видны в исходном получателе, получатель должен быть указателем.
  • Если получатель является структурой, массивом или срезом (slice) и любой из его элементов является указателем на что-то, что может изменяться, предпочтите получатель указателя, поскольку это сделает намерение более понятным для читателя.
  • Если получатель представляет собой небольшой массив или структуру, которая, естественно, является типом значения (получатель значения) (например, что-то типа time.Time), без изменяемых полей и указателей, или является простым базовым типом, таким как int или string, получатель значения имеет смысл. Получатель значения может уменьшить количество мусора, который может быть сгенерирован; если значение передается в метод значения, вместо размещения в куче (heap) может использоваться копия в стеке (stack). (Компилятор старается избегать этого выделения, но не всегда может быть успешным.) Не выбирайте тип получателя значения по этой причине без предварительного профилирования.
  • Наконец, если есть сомнения, используйте получатель указателя.

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


четверг, 14 марта 2019 г.

Go Code Review Comments: Синхронные функции

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

Синхронные функции позволяют локализовать go-процедуры (goroutines) в вызове, что упрощает анализ их времени жизни и позволяет избежать утечек и скачков данных. Их также проще протестировать: вызывающий может передать вход и проверить выход без необходимости опроса или синхронизации.

Если вызывающим абонентам требуется больше конкурентности, они могут легко добавить ее, вызвав функцию из отдельной go-процедуры. Напротив, бывает довольно трудно - иногда невозможно - удалить ненужную конкурентность на вызывающей стороне.


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


Go Code Review Comments: Имена получателей

Имя получателя метода должно отражать его идентичность; часто достаточно одной или двух буквенных аббревиатур этого типа (например, «c» или «cl» для «Client»). Не используйте общие имена, такие как «me», «this» или «self», идентификаторы, типичные для объектно-ориентированных языков, которые придают методу особое значение. В Go получатель метода - это просто еще один параметр, и поэтому он должен иметь соответствующее имя. Имя не должно быть таким же описательным, как у аргумента метода, так как его роль очевидна и не имеет никакой документальной цели. Оно может быть очень коротким, так как будет отображаться почти в каждой строке каждого метода типа; знакомство допускает краткость. Будьте последовательны: если вы вызываете получатель «c» в одном методе, не называйте его «cl» в другом.


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