суббота, 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» в другом.


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


Go Code Review Comments: Передаваемые значения

Не передавайте указатели в качестве аргументов функции только для того, чтобы сохранить несколько байтов. Если функция ссылается на свой аргумент x только как *x, то аргумент не должен быть указателем. Распространенные примеры этого включают передачу указателя на строку (*string) или указателя на значение интерфейса (*io.Reader). В обоих случаях само значение имеет фиксированный размер и может быть передано напрямую. Этот совет не относится к большим структурам или даже к небольшим структурам, которые могут расти.


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


Go Code Review Comments: Имена пакетов

Все ссылки на имена в вашем пакете будут выполняться с использованием имени пакета, поэтому вы можете опустить это имя в идентификаторах. Например, если вы находитесь в пакете chubby, вам не нужен тип ChubbyFile, который клиенты будут записывать как chubby.ChubbyFile. Вместо этого назовите тип File, который клиенты будут записывать как chubby.File. Избегайте бессмысленных имен пакетов, таких как util, common, misc, api, types и interfaces.


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


понедельник, 11 марта 2019 г.

Go Code Review Comments: Комментарии к пакету

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

// Пакет math предоставляет основные константы 
// и математические функции.
package math

/*
Пакет template реализует управляемые данными шаблоны 
для создания текстового вывода, такого как HTML.
....
*/
package template

Для "package main" комментариев другие стили комментария подходят после двойного имени (и оно может быть написано заглавными буквами, если оно идет первым), например, для пакета main в каталоге seedgen вы можете написать:

// Binary seedgen ...
package main

или

// Command seedgen ...
package main

или

// Program seedgen ...
package main

или

// The seedgen command ...
package main

или

// The seedgen program ...
package main

или

// Seedgen ...
package main

Это только примеры, и вполне приемлемы другие их варианты.

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


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


вторник, 5 марта 2019 г.

Go Code Review Comments: Именованные параметры результата

При использовании именованных параметров результата учитывайте как это будет выглядеть в godoc. Например, именованные параметры результата, такие как:

func (n *Node) Parent1() (node *Node)
func (n *Node) Parent2() (node *Node, err error)

будут иметь проблемы в godoc; лучше использовать:

func (n *Node) Parent1() *Node
func (n *Node) Parent2() (*Node, error)

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

func (f *Foo) Location() (float64, float64, error)

не так ясно как:

// Местоположение возвращает широту и долготу f.
// Отрицательные значения означают юг и запад соответственно.
func (f *Foo) Location() (lat, long float64, err error)

Безыменные возвраты это нормально, если функция состоит из нескольких строк. Как только это функция среднего размера, будьте явными со своими возвращаемыми значениями. Следствие: не стоит называть параметры результата только потому, что это позволяет вам использовать безыменные результаты. Ясность документов всегда важнее, чем сохранение одной или двух строк в вашей функции.

Наконец, в некоторых случаях вам нужно назвать параметр результата, чтобы изменить его в отложенном замыкании. Это всегда хорошо.


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


Go Code Review Comments: Длина строки кода

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

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

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

Это фактически такой же совет как и относительно того, какой длины должна быть функция. Нет правила «никогда не иметь функцию длиной более N строк», но определенно существует такая вещь, как слишком длинная функция и слишком запутанные крошечные функции, и решение состоит в том, чтобы изменить границы функций, а не начать считать строки.


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


понедельник, 4 марта 2019 г.

Go Code Review Comments: Интерфейсы

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

Не определяйте интерфейсы на имплементирующей стороне API «для создания mock'ов»; вместо этого спроектируйте API так, чтобы его можно было протестировать с использованием открытого API реальной реализации.

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

package consumer  // consumer.go

type Thinger interface { Thing() bool }

func Foo(t Thinger) string { … }

package consumer // consumer_test.go

type fakeThinger struct{ … }
func (t fakeThinger) Thing() bool { … }
…
if Foo(fakeThinger{…}) == "x" { … }

// НЕ ДЕЛАЙТЕ ТАК!!!
package producer

type Thinger interface { Thing() bool }

type defaultThinger struct{ … }
func (t defaultThinger) Thing() bool { … }

func NewThinger() Thinger { return defaultThinger{ … } }

Вместо этого верните конкретный тип и позвольте потребителю создать mock реализации producer.

package producer

type Thinger struct{ … }
func (t Thinger) Thing() bool { … }

func NewThinger() Thinger { return Thinger{ … } }


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


Go Code Review Comments: Инициализмы

Слова в именах, которые являются инициализмами или аббревиатурами (например, «URL» или «NATO»), имеют последовательный регистр. Например, «URL» должен отображаться как «URL» или «url» (как в «urlPony» или «URLPony»), а не «Url». Как пример: ServeHTTP не ServeHttp. Для идентификаторов с несколькими инициализированными «словами» используйте, например, «xmlHTTPRequest» или «XMLHTTPRequest».

Это правило также применяется к «ID», когда оно сокращенно от «Identity Document» (что практически во всех случаях, когда это не «id», как в «ego», «superego»), поэтому пишите «appID» вместо «appId».

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


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


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

Go Code Review Comments: Отступы в ответвлениях ошибок

Постарайтесь сохранить нормальный путь кода с минимальным отступом и сделайте отступ для обработки ошибок, чтобы разобраться с ними сначала. Это улучшает читабельность кода, позволяя быстро визуально сканировать нормальный путь. Например, не пишите:

if err != nil {
    // обработка ошибки
} else {
    // нормальный код
}

Вместо этого напишите:

if err != nil {
    // обработка ошибки
    return // или continue, и т.д.
}
// нормальный код

Если в операторе if есть оператор инициализации, например:

if x, err := f(); err != nil {
    // обработка ошибки
    return
} else {
    // использование x
}

тогда для этого может потребоваться переместить краткое объявление переменной в отдельную строку:

x, err := f()
if err != nil {
    // обработка ошибки
    return
}
// использование x


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


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

Go Code Review Comments: Внутренние ошибки

В C и аналогичных языках функции обычно возвращают значения, такие как -1 или ноль, чтобы сигнализировать об ошибках или пропущенных результатах:

// Lookup возвращает значение для ключа или "", 
// если нет соответствия для ключа.
func Lookup(key string) string

// Невозможность проверить значение внутренней ошибки 
// может привести к багам:
Parse(Lookup(key))  
// возвращает "parse failure for value" 
// вместо "no value for key" 

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

// Lookup возвращает значение для ключа или ok = false, 
// если для ключа нет соответствия.
func Lookup(key string) (value string, ok bool)

Это препятствует тому, чтобы вызывающая сторона использовала результат неправильно:

Parse(Lookup(key))  // compile-time error

И поощряет более надежный и читаемый код:

value, ok := Lookup(key)
if !ok  {
    return fmt.Errorf("no value for %q", key)
}
return Parse(value)

Это правило применяется к экспортируемым функциям, но также полезно для неэкспортируемых функций.

Возвращаемые значения, такие как nil, "", 0 и -1, хороши, когда они являются действительными результатами для функции, то есть когда вызывающей стороне нет необходимости обрабатывать их иначе, чем другие значения.

Некоторые стандартные библиотечные функции, такие как те, что в пакете "strings", возвращают значения внутренних ошибок. Это значительно упрощает код манипуляции со строками за счет требования большего усердия от программиста. В общем, Go код должен возвращать дополнительные значения для ошибок.


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


Go Code Review Comments: пустой импорт, импорт точки

Пустой импорт

Пакеты, которые импортируются только для их побочных эффектов (с использованием синтаксиса import _ "pkg"), следует импортировать только в основной пакет программы или в тесты, которые в них нуждаются.

Импорт точки

import . форма может быть полезна в тестах, которые из-за циклических зависимостей не могут быть частью тестируемого пакета:

package foo_test

import (
    "bar/testutil" // также испортирует "foo"
    . "foo"
)

В этом случае тестовый файл не может быть в пакете foo, поскольку он использует bar/testutil, который импортирует foo. Поэтому мы используем import . форму, позволяющую файлу претендовать на то, чтобы быть частью пакета foo, хотя это не так. За исключением этого случая, не используйте import . в ваших программах. Это делает программы намного труднее для чтения, потому что неясно, является ли имя, такое как Quux, идентификатором верхнего уровня в текущем пакете или в импортированном пакете.


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


Go примеры: Fizz buzz

В серии постов Go примеры будут представлены примеры программ на Go.

Начнем мы с классической задачи Fizz buzz.

Напомню требования этой задачи. Необходимо реализовать функцию, которая будет проходить по последовательному ряду чисел, начинающемуся с нуля. В случае если число кратно трем будет выводиться слово "Fizz", в случае если число кратно пяти будет выводиться слово "Buzz".

package main

import "fmt"

func main() {
  fmt.Println("Start Fizz Buzz game")
  for i := 0; i < 100; i++ {
    fmt.Println(i)
    if i%3 == 0 {
      fmt.Println("Fizz")
    } else if i%5 == 0 {
      fmt.Println("Buzz")
    }
  }
  fmt.Println("Game over")
}

Этот пример Fizz buzz также доступен в Go песочнице https://play.golang.org/p/WTY4dMNJZtu.


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


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

Go Code Review Comments: импорт

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

Импорт организован в группы с пустыми строками между ними. Пакеты стандартной библиотеки всегда находятся в первой группе.

package main

import (
  "fmt"
  "hash/adler32"
  "os"

  "appengine/foo"
  "appengine/user"

  "github.com/foo/bar"
  "rsc.io/goversion/version"
)

goimports выполняет все эти действия за вас.


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