Это многостраничный печатный вид этого раздела. Нажмите что бы печатать.

Вернуться к обычному просмотру страницы.

Пакеты языка Go

Пакеты и библиотеки языка программирования Go на русском языке

1 - Описание пакета html для GO

Пакеты и функции для работы с html в GO

Пакет html предоставляет функции для экранирования и отмены экранирования HTML-текста.

func EscapeString

func EscapeString(s string) string

EscapeString экранирует специальные символы, такие как “<”, превращаясь в “<”. Он экранирует только пять таких символов: <, >, &, ’ и “. UnescapeString(EscapeString(s)) == s всегда выполняется, но обратное не всегда верно.

Пример
package main

import (
	"fmt"
	"html"
)

func main() {
	const s = `"Fran & Freddie's Diner" <tasty@example.com>`
	fmt.Println(html.EscapeString(s))
}
Output:

`&#34;Fran &amp; Freddie&#39;s Diner&#34; &lt;tasty@example.com&gt;`

func UnescapeString

func UnescapeString(s string) string

UnescapeString отменяет экранирование таких сущностей, как “<”, чтобы они стали “<”. Он отменяет экранирование большего диапазона сущностей, чем EscapeString. Например, “á” отменяет экранирование на “á”, как и “á” и “á”. UnescapeString(EscapeString(s)) == s всегда выполняется, но обратное не всегда верно.

Пример
package main

import (
	"fmt"
	"html"
)

func main() {
	const s = `&quot;Fran &amp; Freddie&#39;s Diner&quot; &lt;tasty@example.com&gt;`
	fmt.Println(html.UnescapeString(s))
}
Output:

"Fran & Freddie's Diner" <tasty@example.com>

1.1 - Описание пакета html/template в Go

Пакет html/template - шаблоны HTML с защитой от инъекций

Пакет html/template реализует шаблоны, управляемые данными, для генерации HTML-вывода, защищённого от внедрения кода. Он предоставляет тот же интерфейс, что и text/template, и должен использоваться вместо него, когда выводом является HTML.

Данная документация фокусируется на функциях безопасности пакета. Информацию о программировании самих шаблонов см. в документации text/template.

Введение

Этот пакет оборачивает text/template, позволяя использовать его API шаблонов для безопасного разбора и выполнения HTML-шаблонов.

Пример:

tmpl, err := template.New("name").Parse(...)
// Проверка ошибок опущена
err = tmpl.Execute(out, data)

Если операция успешна, tmpl будет защищён от инъекций. В противном случае err будет содержать ошибку, как описано в ErrorCode.

HTML-шаблоны обрабатывают значения данных как обычный текст, который должен быть закодирован для безопасного встраивания в HTML-документ. Экранирование является контекстно-зависимым, поэтому действия могут появляться в JavaScript, CSS и URI-контекстах.

Модель безопасности, используемая этим пакетом, предполагает, что авторы шаблонов являются доверенными, а параметр данных Execute - нет. Более подробная информация приведена ниже.

import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")

Вывод

Hello, <script>alert('you have been pwned')</script>!

но контекстное автоэкранирование в html/шаблоне

import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")

создает безопасный, экранированный HTML-вывод

Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!

Контексты

Этот пакет понимает HTML, CSS, JavaScript и URI. Он добавляет функции очистки к каждому простому конвейеру действий, поэтому, рассмотрим пример

<a href="/search?q={{.}}">{{.}}</a>

Во время разбора каждый {{.}} перезаписывается для добавления экранирующих функций по мере необходимости. В этом случае это становится

<a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a>

где urlescaper, attrescaper и htmlescaper — псевдонимы для внутренних функций экранирования.

Для этих внутренних функций экранирования, если конвейер действий оценивает значение интерфейса nil, он обрабатывается так, как будто это пустая строка.

Namespaced и data-атрибуты

Атрибуты с пространством имен обрабатываются так, как будто у них нет пространства имен. Рассмотрим пример:

<a my:href="{{.}}"></a>

Во время анализа атрибут будет обработан так, как если бы это был просто “href”. Таким образом, во время анализа шаблон становится:

<a my:href="{{. | urlescaper | attrescaper}}"></a>

Аналогично атрибутам с пространствами имен, атрибуты с префиксом “data-” обрабатываются так, как если бы у них не было префикса “data-”. Рассмотрим пример:

<a data-href="{{.}}"></a>

Во время анализа это становится

<a data-href="{{. | urlescaper | attrescaper}}"></a>

Если атрибут имеет как пространство имен, так и префикс “data-”, то при определении контекста будет удалено только пространство имен. Например:

<a my:data-href="{{.}}"></a>

Это обрабатывается так, как если бы “my:data-href” было просто “data-href”, а не “href”, как было бы, если бы префикс “data-” тоже игнорировался. Таким образом, во время анализа это становится просто

<a my:data-href="{{. | attrescaper}}"></a>

В качестве особого случая атрибуты с пространством имен “xmlns” всегда рассматриваются как содержащие URL-адреса. Например:

<a xmlns:title="{{.}}"></a>
<a xmlns:href="{{.}}"></a>
<a xmlns:onclick="{{.}}"></a>

Во время анализа они становятся:

<a xmlns:title="{{. | urlescaper | attrescaper}}"></a>
<a xmlns:href="{{. | urlescaper | attrescaper}}"></a>
<a xmlns:onclick="{{. | urlescaper | attrescaper}}"></a>

Ошибки

Подробности см. в документации ErrorCode.

Более полная картина

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

Контексты

Предположив, что {{.}} — это O'Reilly: How are <i>you</i>?, в таблице ниже показано, как выглядит {{.}} при использовании в контексте слева.

Context                          {{.}} After
{{.}}                            O'Reilly: How are &lt;i&gt;you&lt;/i&gt;?
<a title='{{.}}'>                O&#39;Reilly: How are you?
<a href="/{{.}}">                O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}">              O&#39;Reilly%3a%20How%20are%3ci%3e...%3f
<a onx='f("{{.}}")'>             O\x27Reilly: How are \x3ci\x3eyou...?
<a onx='f({{.}})'>               "O\x27Reilly: How are \x3ci\x3eyou...?"
<a onx='pattern = /{{.}}/;'>     O\x27Reilly: How are \x3ci\x3eyou...\x3f

При использовании в небезопасном контексте значение может быть отфильтровано:

Context                          {{.}} After
<a href="{{.}}">                 #ZgotmplZ

поскольку “O’Reilly:” не является разрешенным протоколом, как “http:”.

Если {{.}} — это безобидное слово left, то оно может появляться более широко,

Context                              {{.}} After
{{.}}                                left
<a title='{{.}}'>                    left
<a href='{{.}}'>                     left
<a href='/{{.}}'>                    left
<a href='?dir={{.}}'>                left
<a style="border-{{.}}: 4px">        left
<a style="align: {{.}}">             left
<a style="background: '{{.}}'>       left
<a style="background: url('{{.}}')>  left
<style>p.{{.}} {color:red}</style>   left

Нестроковые значения могут использоваться в контекстах JavaScript. Если {{.}} — это

struct{A,B string}{ "foo", "bar" }

в экранированном шаблоне

<script>var pair = {{.}};</script>

то вывод шаблона будет

<script>var pair = {"A": "foo", "B": "bar"};</script>

Ознакомьтесь с пакетом json, чтобы понять, как нестроковый контент маршалируется для встраивания в контексты JavaScript.

Типизированные строки

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

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

Типы HTML, JS, URL и другие из content.go могут содержать безопасный контент, который освобожден от экранирования.

Например шаблон:

Hello, {{.}}!

может быть вызван с помощью

tmpl.Execute(out, template.HTML(`<b>World</b>`))

вывод

Hello, <b>World</b>!

вместо

Hello, &lt;b&gt;World&lt;b&gt;!

который был бы получен, если бы {{.}} был обычной строкой.

Модель безопасности

Определение “безопасности”, используемое в этом пакете, основано на спецификации.

Данный пакет исходит из следующих допущений:

  • Авторы шаблонов считаются доверенными
  • Данные, передаваемые в Execute, — ненадёжные

При этом обеспечиваются следующие свойства безопасности при работе с ненадёжными данными:

1. Сохранение структуры (Structure Preservation Property)

Если автор шаблона пишет HTML-тег на безопасном языке шаблонов, браузер всегда будет интерпретировать соответствующую часть вывода именно как тег — независимо от значений ненадёжных данных. То же самое относится к другим структурам, таким как границы атрибутов, строки JavaScript и CSS.

2. Контроль исполнения кода (Code Effect Property)

В результате вставки вывода шаблона на страницу должен выполняться только код, явно указанный автором шаблона. При этом весь код, указанный автором, должен выполняться корректно.

3. Принцип наименьшего удивления (Least Surprise Property)

Разработчик (или ревьюер кода), знакомый с HTML, CSS и JavaScript и знающий о контекстном автоэкранировании, должен быть способен, увидев {{.}}, однозначно определить, какое экранирование будет применено.

Поддержка шаблонных литералов ES6

Ранее шаблонные литералы ECMAScript 6 (${...}) были отключены по умолчанию, и их можно было включить через переменную окружения GODEBUG=jstmpllitinterp=1.

Сейчас шаблонные литералы поддерживаются по умолчанию, а настройка jstmpllitinterp больше не имеет эффекта.

Пример
package main

import (
	"html/template"
	"log"
	"os"
)

func main() {
	const tpl = `
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>{{.Title}}</title>
	</head>
	<body>
		{{range .Items}}<div>{{ . }}</div>{{else}}<div><strong>no rows</strong></div>{{end}}
	</body>
</html>`

	check := func(err error) {
		if err != nil {
			log.Fatal(err)
		}
	}
	t, err := template.New("webpage").Parse(tpl)
	check(err)

	data := struct {
		Title string
		Items []string
	}{
		Title: "My page",
		Items: []string{
			"My photos",
			"My blog",
		},
	}

	err = t.Execute(os.Stdout, data)
	check(err)

	noItems := struct {
		Title string
		Items []string
	}{
		Title: "My another page",
		Items: []string{},
	}

	err = t.Execute(os.Stdout, noItems)
	check(err)

}
Output:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>My page</title>
	</head>
	<body>
		<div>My photos</div><div>My blog</div>
	</body>
</html>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>My another page</title>
	</head>
	<body>
		<div><strong>no rows</strong></div>
	</body>
</html>
Пример Autoescaping
package main

import (
	"html/template"
	"log"
	"os"
)

func main() {
	check := func(err error) {
		if err != nil {
			log.Fatal(err)
		}
	}
	t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
	check(err)
	err = t.ExecuteTemplate(os.Stdout, "T", "<script>alert('you have been pwned')</script>")
	check(err)
}
Output:

Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!
Пример Escape
package main

import (
	"fmt"
	"html/template"
	"os"
)

func main() {
	const s = `"Fran & Freddie's Diner" <tasty@example.com>`
	v := []any{`"Fran & Freddie's Diner"`, ' ', `<tasty@example.com>`}

	fmt.Println(template.HTMLEscapeString(s))
	template.HTMLEscape(os.Stdout, []byte(s))
	fmt.Fprintln(os.Stdout, "")
	fmt.Println(template.HTMLEscaper(v...))

	fmt.Println(template.JSEscapeString(s))
	template.JSEscape(os.Stdout, []byte(s))
	fmt.Fprintln(os.Stdout, "")
	fmt.Println(template.JSEscaper(v...))

	fmt.Println(template.URLQueryEscaper(v...))

}
Output:

&#34;Fran &amp; Freddie&#39;s Diner&#34; &lt;tasty@example.com&gt;
&#34;Fran &amp; Freddie&#39;s Diner&#34; &lt;tasty@example.com&gt;
&#34;Fran &amp; Freddie&#39;s Diner&#34;32&lt;tasty@example.com&gt;
\"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E
\"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E
\"Fran \u0026 Freddie\'s Diner\"32\u003Ctasty@example.com\u003E
%22Fran+%26+Freddie%27s+Diner%2232%3Ctasty%40example.com%3E

1.1.1 - Шпаргалка по html/template в Go

html/template - это пакет Go для безопасного создания HTML-выхода с автоматическим экранированием.

Инициализация шаблона

import "html/template"

// Создание нового шаблона
tmpl := template.New("templateName")

// Парсинг шаблона из строки
tmpl, err := template.New("test").Parse(`<h1>{{.Title}}</h1>`)

// Парсинг шаблона из файла
tmpl, err := template.ParseFiles("template.html")

// Парсинг нескольких файлов
tmpl, err := template.ParseGlob("templates/*.html")

Основные конструкции в шаблоне

1. Вывод значения

<!-- Простой вывод -->
<p>{{.UserName}}</p>

<!-- Вывод с экранированием HTML -->
<p>{{.UserComment}}</p>

<!-- Вывод без экранирования (осторожно!) -->
<p>{{.SafeHTML | html}}</p>

2. Условия

{{if .User}}
    <p>Welcome, {{.User}}!</p>
{{else}}
    <p>Please log in.</p>
{{end}}

{{if eq .Role "admin"}}
    <p>Admin dashboard</p>
{{end}}

3. Циклы

<ul>
{{range .Items}}
    <li>{{.}}</li>
{{end}}
</ul>

<!-- С индексом -->
<ul>
{{range $index, $item := .Items}}
    <li>Item #{{$index}}: {{$item}}</li>
{{end}}
</ul>

<!-- Если слайс пуст -->
{{range .Items}}
    {{.}}
{{else}}
    <p>No items found</p>
{{end}}

4. Определение переменных

{{$name := .UserName}}
{{$count := len .Items}}
<p>Hello, {{$name}}. You have {{$count}} items.</p>

5. Функции и конвейеры

<!-- Вызов встроенных функций -->
<p>{{printf "Hello, %s" .Name}}</p>
<p>{{len .Items}} items</p>
<p>{{index .Items 0}}</p>

<!-- Конвейер -->
<p>{{.Title | upper | truncate 50}}</p>

6. Вложенные шаблоны

// Определение в коде
tmpl := template.Must(template.New("base").Parse(`
{{define "content"}}Default content{{end}}
<html>
<body>
    {{template "content" .}}
</body>
</html>
`))

// Переопределение в другом шаблоне
tmpl2 := template.Must(tmpl.Parse(`{{define "content"}}New content{{end}}`))
<!-- В шаблоне -->
{{template "header" .}}

<!-- С передачей других данных -->
{{template "footer" .FooterData}}

7. Пользовательские функции

funcMap := template.FuncMap{
    "upper": strings.ToUpper,
    "add": func(a, b int) int { return a + b },
}

tmpl := template.New("test").Funcs(funcMap)
tmpl, err := tmpl.Parse(`{{upper .Name}} {{add 1 2}}`)

Пример полного шаблона

<!DOCTYPE html>
<html>
<head>
    <title>{{.Title}} - My Site</title>
</head>
<body>
    <header>
        {{if .User}}
            <p>Welcome, {{.User.Name}} ({{.User.Email}})</p>
        {{else}}
            <a href="/login">Login</a>
        {{end}}
    </header>
    
    <nav>
        <ul>
            {{range .Menu}}
                <li><a href="{{.URL}}">{{.Title}}</a></li>
            {{end}}
        </ul>
    </nav>
    
    <main>
        {{template "content" .}}
    </main>
    
    <footer>
        <p>© {{.CurrentYear}} My Company</p>
    </footer>
</body>
</html>

Выполнение шаблона

// В память
err := tmpl.Execute(os.Stdout, data)

// В строку
var buf bytes.Buffer
err := tmpl.Execute(&buf, data)
result := buf.String()

// Конкретный шаблон из набора
err := tmpl.ExecuteTemplate(os.Stdout, "templateName", data)

Обработка ошибок

tmpl := template.Must(template.New("").Parse("...")) // паника при ошибке

if err := tmpl.Execute(w, data); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
}

Безопасность

  • Автоматическое экранирование HTML/JS/CSS
  • Для отключения экранирования используйте типы template.HTML, template.JS, template.CSS:
type Data struct {
    SafeContent template.HTML
}

data := Data{SafeContent: template.HTML("<b>Safe HTML</b>")}

1.1.2 - Подробное руководство по define, template и block в html/template

В пакете html/template есть три основные директивы для работы с повторно используемыми частями шаблонов: define, template, block

Основные концепции

В пакете html/template есть три основные директивы для работы с повторно используемыми частями шаблонов:

  1. {{define "name"}}...{{end}} - определяет именованный шаблон
  2. {{template "name"}} - вставляет именованный шаблон
  3. {{block "name"}}...{{end}} - комбинация define+template с возможностью переопределения

1. Директива define

Где описывать:

  • В любом месте шаблона (но обычно в начале или конце файла)
  • В отдельных файлах, которые потом объединяются

Пример определения:

{{define "header"}}
<header>
    <h1>{{.Title}}</h1>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
    </nav>
</header>
{{end}}

{{define "footer"}}
<footer>
    <p>Copyright © {{.Year}} My Company</p>
</footer>
{{end}}

2. Директива template

Где применять:

  • В любом месте основного шаблона
  • Можно передавать данные (по умолчанию - текущий контекст)

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

<!DOCTYPE html>
<html>
<head>
    <title>{{.Title}}</title>
</head>
<body>
    {{template "header" .}}  <!-- Передаем текущий контекст -->
    
    <main>
        {{.Content}}
    </main>
    
    {{template "footer" .}}  <!-- Передаем текущий контекст -->
</body>
</html>

Передача другого контекста:

{{template "user_profile" .UserData}}

3. Директива block

Особенности:

  • Комбинация define + template
  • Можно переопределять в дочерних шаблонах
  • Если не переопределен - используется содержимое по умолчанию

Пример:

<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
    {{block "head" .}}
    <title>{{.Title}} - Default</title>
    {{end}}
</head>
<body>
    {{block "content" .}}
    <p>Default content</p>
    {{end}}
</body>
</html>

Переопределение в дочернем шаблоне:

<!-- home.html -->
{{define "content"}}
<h1>Welcome!</h1>
<p>This is the home page.</p>
{{end}}

{{template "base.html" .}}

Практические схемы организации

1. Многофайловая структура

templates/
├── base.html       # Основной каркас
├── header.html     # Шапка сайта
├── footer.html     # Подвал
├── home.html       # Главная страница
└── about.html      # Страница "О нас"

base.html:

<!DOCTYPE html>
<html>
<head>
    {{template "head" .}}
</head>
<body>
    {{template "header" .}}
    {{block "content" .}}{{end}}
    {{template "footer" .}}
</body>
</html>

home.html:

{{define "content"}}
<h1>Welcome</h1>
<p>Home page content</p>
{{end}}

{{template "base.html" .}}

2. Наследование шаблонов

// Загрузка шаблонов
templates := template.Must(template.ParseFiles(
    "base.html",
    "header.html",
    "footer.html",
    "home.html",
))

// Исполнение конкретного шаблона
err := templates.ExecuteTemplate(w, "home.html", data)

3. Вложенные блоки

<!-- base.html -->
{{define "layout"}}
<div class="container">
    {{block "sidebar" .}}
    <!-- Default sidebar -->
    <div class="sidebar">Default sidebar</div>
    {{end}}
    
    <div class="content">
        {{template "content" .}}
    </div>
</div>
{{end}}
<!-- profile.html -->
{{define "sidebar"}}
<div class="profile-sidebar">
    <img src="{{.User.Avatar}}">
    <h3>{{.User.Name}}</h3>
</div>
{{end}}

{{define "content"}}
<div class="profile-content">
    <!-- Контент профиля -->
</div>
{{end}}

{{template "layout" .}}

Особенности работы

  1. Порядок загрузки: шаблоны, на которые есть ссылки, должны быть загружены до их использования
  2. Контекст данных: по умолчанию передается текущий контекст, но можно передать другой
  3. Рекурсия: шаблоны могут вызывать сами себя (но осторожно с бесконечной рекурсией)
  4. Переопределение: последний загруженный шаблон с тем же именем переопределяет предыдущий

Пример с пользовательскими функциями

func main() {
    funcMap := template.FuncMap{
        "formatDate": func(t time.Time) string {
            return t.Format("2006-01-02")
        },
    }

    tmpl := template.Must(
        template.New("base").Funcs(funcMap).ParseFiles("base.html", "home.html"),
    )

    err := tmpl.ExecuteTemplate(os.Stdout, "home.html", data)
}
<!-- В шаблоне: -->
<p>Created: {{.CreatedAt | formatDate}}</p>

Лучшие практики

  1. Разделяйте шаблоны на логические компоненты
  2. Используйте block для переопределяемых секций
  3. Основной каркас выносите в base.html
  4. Называйте шаблоны осмысленно (не “template1”, а “user_profile”)
  5. Документируйте назначение шаблонов в комментариях

1.1.3 - Шпаргалка по конвейерам и функциям в html/template (Go)

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

Синтаксис:

{{value | function1 | function2 | ...}}

Примеры:

<!-- Простой конвейер -->
<p>{{.Name | upper}}</p>

<!-- Множественные функции -->
<p>{{.Title | trim | lower | trunc 50}}</p>

<!-- С аргументами -->
<p>{{.Date | format "2006-01-02"}}</p>

<!-- Вложенные конвейеры -->
<p>{{.Description | trunc 100 | html}}</p>

Встроенные функции

1. Строковые функции

{{"hello" | title}}      <!-- Hello -->
{{"HELLO" | lower}}      <!-- hello -->
{{"hello" | upper}}      <!-- HELLO -->
{{"  hello  " | trim}}   <!-- hello -->
{{"hello" | repeat 3}}   <!-- hellohellohello -->

2. HTML-функции

{{"<script>" | html}}         <!-- Экранирование HTML -->
{{"<b>safe</b>" | html}}      <!-- &lt;b&gt;safe&lt;/b&gt; -->
{{"<b>safe</b>" | safeHTML}}  <!-- <b>safe</b> (без экранирования) -->

3. Функции для коллекций

{{len .Items}}               <!-- Длина массива/слайса/мапы -->
{{index .Users 0}}           <!-- Первый элемент -->
{{index .Map "key"}}         <!-- Значение по ключу -->
{{slice .List 1 3}}          <!-- Подслайс -->

4. Логические функции

{{if eq .A .B}}...{{end}}     <!-- Равно -->
{{if ne .A .B}}...{{end}}     <!-- Не равно -->
{{if lt .A .B}}...{{end}}     <!-- Меньше -->
{{if le .A .B}}...{{end}}     <!-- Меньше или равно -->
{{if gt .A .B}}...{{end}}     <!-- Больше -->
{{if ge .A .B}}...{{end}}     <!-- Больше или равно -->

{{if and .A .B}}...{{end}}    <!-- Логическое И -->
{{if or .A .B}}...{{end}}     <!-- Логическое ИЛИ -->
{{if not .A}}...{{end}}       <!-- Логическое НЕ -->

5. Математические функции

{{add 1 2}}        <!-- 3 -->
{{sub 5 2}}        <!-- 3 -->
{{mul 3 4}}        <!-- 12 -->
{{div 10 2}}       <!-- 5 -->
{{mod 10 3}}       <!-- 1 -->

6. Функции форматирования

{{printf "%s - %d" .Name .Age}}  <!-- Форматированная строка -->
{{.Price | printf "$%.2f"}}      <!-- Форматирование числа -->
{{.Date | date "2006-01-02"}}    <!-- Форматирование даты -->

Пользовательские функции

Регистрация функций:

funcMap := template.FuncMap{
    "upper":    strings.ToUpper,
    "trunc":    func(s string, max int) string {
        if len(s) > max {
            return s[:max] + "..."
        }
        return s
    },
    "add":      func(a, b int) int { return a + b },
    "contains": strings.Contains,
    "now":      time.Now,
}

tmpl := template.New("").Funcs(funcMap)

Использование в шаблоне:

{{.Title | trunc 50 | upper}}
{{add .Value 10}}
{{if contains .Description "important"}}Important!{{end}}
{{now | date "2006-01-02"}}

Примеры сложных конвейеров

1. Обработка текста

{{.UserComment | trim | trunc 200 | html}}

2. Форматирование данных

{{.CreatedAt | date "02.01.2006" | printf "Created: %s"}}

3. Работа с коллекциями

{{range .Items | sortBy "Name" | limit 10}}
    {{.Name | upper}}
{{end}}

4. Условные конвейеры

{{if .Price | gt 100}}
    <p class="expensive">{{.Price | printf "$%.2f"}}</p>
{{else}}
    <p>{{.Price | printf "$%.2f"}}</p>
{{end}}

Полезные комбинации

Безопасный вывод HTML:

{{.UserContent | sanitizeHTML | safeHTML}}

Форматирование денежных значений:

{{.Amount | div 100 | printf "$%.2f"}}

Сортировка и фильтрация:

{{range .Products | filter "category" "electronics" | sortBy "price"}}
    {{.Name}} - {{.Price | printf "$%.2f"}}
{{end}}

Ограничения

  1. Функции должны возвращать только одно значение (и опционально ошибку)
  2. Невозможно передавать функции как аргументы
  3. Нет поддержки сложных операций с каналами
  4. Автоматическое экранирование применяется к конечному результату

Лучшие практики

  1. Выносите сложную логику в Go-код, а не в шаблоны
  2. Используйте конвейеры для читаемости, но не переусердствуйте
  3. Документируйте пользовательские функции
  4. Тестируйте шаблоны с разными входными данными
  5. Избегайте побочных эффектов в функциях

1.1.4 - Функции экранирования HTML/JavaScript/URL пакета html/template языка Go

Описание функций пакета html/template языка Go в соответствии с официальной документацией

HTMLEscape

func HTMLEscape(w io.Writer, b []byte)

HTMLEscape записывает в w экранированный HTML-эквивалент текстовых данных b.

HTMLEscapeString

func HTMLEscapeString(s string) string

HTMLEscapeString возвращает экранированный HTML-эквивалент текстовых данных s.

HTMLEscaper

func HTMLEscaper(args ...any) string

HTMLEscaper возвращает экранированный HTML-эквивалент текстового представления своих аргументов.

IsTrue

func IsTrue(val any) (truth, ok bool)

IsTrue определяет, является ли значение “истинным” (не нулевым для своего типа) и имеет ли оно вообще смысл как булево значение. Это определение истинности используется в условных операциях типа if.

JSEscape

func JSEscape(w io.Writer, b []byte)

JSEscape записывает в w экранированный JavaScript-эквивалент текстовых данных b.

JSEscapeString

func JSEscapeString(s string) string

JSEscapeString возвращает экранированный JavaScript-эквивалент текстовых данных s.

JSEscaper

func JSEscaper(args ...any) string

JSEscaper возвращает экранированный JavaScript-эквивалент текстового представления своих аргументов.

URLQueryEscaper

func URLQueryEscaper(args ...any) string

URLQueryEscaper возвращает экранированное значение текстового представления аргументов в форме, пригодной для вставки в URL-запрос.

1.1.5 - Типы экранирования HTML/JavaScript/URL пакета html/template языка Go

Описание типов и функций пакета html/template языка Go в соответствии с официальной документацией

type CSS

type CSS string

CSS инкапсулирует заранее проверенное содержимое, соответствующее:

  • Производству CSS3 таблиц стилей (напр. p { color: purple })
  • Производству CSS3 правил (напр. a[href=~"https:"].foo#bar)
  • Производству CSS3 деклараций (напр. color: red; margin: 2px)
  • Производству CSS3 значений (напр. rgba(0, 0, 255, 127))

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

type Error

type Error struct {
    ErrorCode    ErrorCode  // Тип ошибки
    Node         parse.Node // Узел, вызвавший проблему (если известен)
    Name         string     // Имя шаблона с ошибкой
    Line         int        // Номер строки в исходнике шаблона
    Description  string     // Человекочитаемое описание
}

Описывает проблему, возникшую при экранировании шаблона.

Коды ошибок (ErrorCode)

const (
    OK ErrorCode = iota  // Нет ошибки
    
    // Неоднозначный контекст в URL
    ErrAmbigContext
    
    // Некорректный HTML (незакрытые теги/атрибуты)
    ErrBadHTML
    
    // Ветки if/range заканчиваются в разных контекстах
    ErrBranchEnd
    
    // Шаблон заканчивается в нетекстовом контексте
    ErrEndContext
    
    // Отсутствует указанный шаблон
    ErrNoSuchTemplate
    
    // Невозможно вычислить контекст вывода
    ErrOutputContext
    
    // Незавершённый набор символов в regexp JS
    ErrPartialCharset
    
    // Незавершённая escape-последовательность
    ErrPartialEscape
    
    // Повторный вход в range с изменением контекста
    ErrRangeLoopReentry
    
    // Неоднозначная интерпретация '/' (деление или regexp)
    ErrSlashAmbig
    
    // Использование запрещённых экранировщиков
    ErrPredefinedEscaper
    
    // Устаревшая ошибка для JS template literals
    ErrJSTemplate
)

Особое значение ZgotmplZ

Пример:

<img src="{{.X}}"> 

где {{.X}} вычисляется в javascript:...

Результат:

<img src="#ZgotmplZ">

Объяснение: “ZgotmplZ” - специальное значение, указывающее, что небезопасное содержимое достигло CSS или URL контекста во время выполнения. Для доверенных источников используйте типы содержимого для отключения фильтрации: URL(javascript:…).

type FuncMap

type FuncMap = template.FuncMap

FuncMap - это псевдоним для типа template.FuncMap из пакета text/template, представляющий карту функций для использования в шаблонах.

type HTML

type HTML string

HTML инкапсулирует безопасный фрагмент HTML-документа. Не следует использовать для:

  • HTML из ненадёжных источников
  • HTML с незакрытыми тегами или комментариями

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

type HTMLAttr

type HTMLAttr string

HTMLAttr инкапсулирует безопасный HTML-атрибут, например dir="ltr".

Внимание: Как и с HTML, содержимое должно быть доверенным, так как включается без экранирования.

type JS

type JS string

JS инкапсулирует безопасное выражение EcmaScript5, например (x + y * z()).

Особенности:

  • Авторы шаблонов должны обеспечивать правильный порядок операций
  • Не должно быть неоднозначности между выражениями и инструкциями

Внимание: Не безопасно использовать для непроверенного JSON. Для JSON лучше использовать json.Unmarshal.

type JSStr

type JSStr string

JSStr инкапсулирует безопасную строку для JavaScript, которая будет заключена в кавычки.

Требования:

  • Допустимые символы или escape-последовательности
  • Запрещены переносы строк внутри escape-последовательностей

Пример:

JSStr("foo\\nbar")  // допустимо
JSStr("foo\\\nbar") // недопустимо

type Srcset

type Srcset string

Srcset инкапсулирует безопасный атрибут srcset для тегов <img> (спецификация HTML5).

type Template

type Template struct {
    Tree *parse.Tree  // Абстрактное синтаксическое дерево шаблона
    // ... скрытые поля
}

Template - это специализированная версия text/template, которая генерирует безопасные фрагменты HTML-документов.

Ключевые особенности:

  • Расширяет базовый функционал text/template
  • Автоматически применяет контекстно-зависимое экранирование
  • Гарантирует безопасность вывода при правильном использовании

Все типы безопасного контента (HTML, JS и др.) требуют, чтобы их содержимое поступало из доверенных источников, так как они включаются в вывод без дополнительного экранирования.

Пример Block
package main

import (
	"html/template"
	"log"
	"os"
	"strings"
)

func main() {
	const (
		master  = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
		overlay = `{{define "list"}} {{join . ", "}}{{end}} `
	)
	var (
		funcs     = template.FuncMap{"join": strings.Join}
		guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"}
	)
	masterTmpl, err := template.New("master").Funcs(funcs).Parse(master)
	if err != nil {
		log.Fatal(err)
	}
	overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay)
	if err != nil {
		log.Fatal(err)
	}
	if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
		log.Fatal(err)
	}
	if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
		log.Fatal(err)
	}
}
Output:

Names:
- Gamora
- Groot
- Nebula
- Rocket
- Star-Lord
Names: Gamora, Groot, Nebula, Rocket, Star-Lord
Пример Glob

Здесь мы демонстрируем загрузку набора шаблонов из каталога.

package main

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"text/template"
)

// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
	name     string
	contents string
}

func createTestDir(files []templateFile) string {
	dir, err := os.MkdirTemp("", "template")
	if err != nil {
		log.Fatal(err)
	}
	for _, file := range files {
		f, err := os.Create(filepath.Join(dir, file.name))
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		_, err = io.WriteString(f, file.contents)
		if err != nil {
			log.Fatal(err)
		}
	}
	return dir
}

func main() {
	// Here we create a temporary directory and populate it with our sample
	// template definition files; usually the template files would already
	// exist in some location known to the program.
	dir := createTestDir([]templateFile{
		// T0.tmpl is a plain template file that just invokes T1.
		{"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
		// T1.tmpl defines a template, T1 that invokes T2.
		{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
		// T2.tmpl defines a template T2.
		{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
	})
	// Clean up after the test; another quirk of running as an example.
	defer os.RemoveAll(dir)

	// pattern is the glob pattern used to find all the template files.
	pattern := filepath.Join(dir, "*.tmpl")

	// Here starts the example proper.
	// T0.tmpl is the first name matched, so it becomes the starting template,
	// the value returned by ParseGlob.
	tmpl := template.Must(template.ParseGlob(pattern))

	err := tmpl.Execute(os.Stdout, nil)
	if err != nil {
		log.Fatalf("template execution: %s", err)
	}
}
Output:

T0 invokes T1: (T1 invokes T2: (This is T2))
Пример Helpers

Этот пример демонстрирует один из способов поделиться некоторыми шаблонами и использовать их в разных контекстах. В этом варианте мы вручную добавляем несколько шаблонов драйверов в существующий пакет шаблонов.

package main

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"text/template"
)

// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
	name     string
	contents string
}

func createTestDir(files []templateFile) string {
	dir, err := os.MkdirTemp("", "template")
	if err != nil {
		log.Fatal(err)
	}
	for _, file := range files {
		f, err := os.Create(filepath.Join(dir, file.name))
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		_, err = io.WriteString(f, file.contents)
		if err != nil {
			log.Fatal(err)
		}
	}
	return dir
}

func main() {
	// Here we create a temporary directory and populate it with our sample
	// template definition files; usually the template files would already
	// exist in some location known to the program.
	dir := createTestDir([]templateFile{
		// T1.tmpl defines a template, T1 that invokes T2.
		{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
		// T2.tmpl defines a template T2.
		{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
	})
	// Clean up after the test; another quirk of running as an example.
	defer os.RemoveAll(dir)

	// pattern is the glob pattern used to find all the template files.
	pattern := filepath.Join(dir, "*.tmpl")

	// Here starts the example proper.
	// Load the helpers.
	templates := template.Must(template.ParseGlob(pattern))
	// Add one driver template to the bunch; we do this with an explicit template definition.
	_, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
	if err != nil {
		log.Fatal("parsing driver1: ", err)
	}
	// Add another driver template.
	_, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
	if err != nil {
		log.Fatal("parsing driver2: ", err)
	}
	// We load all the templates before execution. This package does not require
	// that behavior but html/template's escaping does, so it's a good habit.
	err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
	if err != nil {
		log.Fatalf("driver1 execution: %s", err)
	}
	err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
	if err != nil {
		log.Fatalf("driver2 execution: %s", err)
	}
}
Output:

Driver 1 calls T1: (T1 invokes T2: (This is T2))
Driver 2 calls T2: (This is T2)
Пример Parsefiles

Здесь мы демонстрируем загрузку набора шаблонов из файлов в разных каталогах.

package main

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"text/template"
)

// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
	name     string
	contents string
}

func createTestDir(files []templateFile) string {
	dir, err := os.MkdirTemp("", "template")
	if err != nil {
		log.Fatal(err)
	}
	for _, file := range files {
		f, err := os.Create(filepath.Join(dir, file.name))
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		_, err = io.WriteString(f, file.contents)
		if err != nil {
			log.Fatal(err)
		}
	}
	return dir
}

func main() {
	// Here we create different temporary directories and populate them with our sample
	// template definition files; usually the template files would already
	// exist in some location known to the program.
	dir1 := createTestDir([]templateFile{
		// T1.tmpl is a plain template file that just invokes T2.
		{"T1.tmpl", `T1 invokes T2: ({{template "T2"}})`},
	})

	dir2 := createTestDir([]templateFile{
		// T2.tmpl defines a template T2.
		{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
	})

	// Clean up after the test; another quirk of running as an example.
	defer func(dirs ...string) {
		for _, dir := range dirs {
			os.RemoveAll(dir)
		}
	}(dir1, dir2)

	// Here starts the example proper.
	// Let's just parse only dir1/T0 and dir2/T2
	paths := []string{
		filepath.Join(dir1, "T1.tmpl"),
		filepath.Join(dir2, "T2.tmpl"),
	}
	tmpl := template.Must(template.ParseFiles(paths...))

	err := tmpl.Execute(os.Stdout, nil)
	if err != nil {
		log.Fatalf("template execution: %s", err)
	}
}
Output:

T1 invokes T2: (This is T2)
Пример Share

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

package main

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"text/template"
)

// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
	name     string
	contents string
}

func createTestDir(files []templateFile) string {
	dir, err := os.MkdirTemp("", "template")
	if err != nil {
		log.Fatal(err)
	}
	for _, file := range files {
		f, err := os.Create(filepath.Join(dir, file.name))
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		_, err = io.WriteString(f, file.contents)
		if err != nil {
			log.Fatal(err)
		}
	}
	return dir
}

func main() {
	// Here we create a temporary directory and populate it with our sample
	// template definition files; usually the template files would already
	// exist in some location known to the program.
	dir := createTestDir([]templateFile{
		// T0.tmpl is a plain template file that just invokes T1.
		{"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
		// T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
		{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
	})
	// Clean up after the test; another quirk of running as an example.
	defer os.RemoveAll(dir)

	// pattern is the glob pattern used to find all the template files.
	pattern := filepath.Join(dir, "*.tmpl")

	// Here starts the example proper.
	// Load the drivers.
	drivers := template.Must(template.ParseGlob(pattern))

	// We must define an implementation of the T2 template. First we clone
	// the drivers, then add a definition of T2 to the template name space.

	// 1. Clone the helper set to create a new name space from which to run them.
	first, err := drivers.Clone()
	if err != nil {
		log.Fatal("cloning helpers: ", err)
	}
	// 2. Define T2, version A, and parse it.
	_, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
	if err != nil {
		log.Fatal("parsing T2: ", err)
	}

	// Now repeat the whole thing, using a different version of T2.
	// 1. Clone the drivers.
	second, err := drivers.Clone()
	if err != nil {
		log.Fatal("cloning drivers: ", err)
	}
	// 2. Define T2, version B, and parse it.
	_, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
	if err != nil {
		log.Fatal("parsing T2: ", err)
	}

	// Execute the templates in the reverse order to verify the
	// first is unaffected by the second.
	err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
	if err != nil {
		log.Fatalf("second execution: %s", err)
	}
	err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
	if err != nil {
		log.Fatalf("first: execution: %s", err)
	}

}
Output:

T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))

func Must

func Must(t *Template, err error) *Template

Must - вспомогательная функция, которая оборачивает вызов функции, возвращающей (*Template, error), и вызывает панику, если ошибка не равна nil. Предназначена для инициализации переменных:

Пример:

var t = template.Must(template.New("name").Parse("html"))

func New

func New(name string) *Template

New создает новый HTML-шаблон с указанным именем.

func ParseFS (добавлена в Go 1.16)

func ParseFS(fs fs.FS, patterns ...string) (*Template, error)

ParseFS аналогична ParseFiles и ParseGlob, но читает из файловой системы fs вместо файловой системы хоста. Принимает список glob-шаблонов (большинство имен файлов работают как glob-шаблоны, соответствующие только самим себе).

func ParseFiles

func ParseFiles(filenames ...string) (*Template, error)

ParseFiles создает новый шаблон и парсит определения шаблонов из указанных файлов. Имя возвращаемого шаблона будет соответствовать базовому имени первого файла. Должен быть указан хотя бы один файл.

Особенности:

  • При парсинге нескольких файлов с одинаковыми именами будет использован последний указанный файл
  • Например, ParseFiles("a/foo", "b/foo") сохранит “b/foo” как шаблон “foo”, а “a/foo” будет недоступен

func ParseGlob

func ParseGlob(pattern string) (*Template, error)

ParseGlob создает новый шаблон и парсит определения шаблонов из файлов, соответствующих указанному шаблону (согласно семантике filepath.Match). Шаблон должен соответствовать хотя бы одному файлу.

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

func (*Template) AddParseTree

func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)

AddParseTree создает новый шаблон с указанным именем и деревом разбора, ассоциируя его с t.

Возвращает ошибку, если t или любой ассоциированный шаблон уже были выполнены.

func (*Template) Clone

func (t *Template) Clone() (*Template, error)

Clone создает копию шаблона, включая все ассоциированные шаблоны. Последующие вызовы Parse в копии будут добавлять шаблоны только в копию.

Возвращает ошибку, если t уже был выполнен.

func (*Template) DefinedTemplates (добавлена в Go 1.6)

func (t *Template) DefinedTemplates() string

DefinedTemplates возвращает строку с перечислением определенных шаблонов с префиксом “; defined templates are: “. Если шаблонов нет, возвращает пустую строку. Используется для генерации сообщений об ошибках.

func (*Template) Delims

func (t *Template) Delims(left, right string) *Template

Delims устанавливает разделители действий для последующих вызовов Parse, ParseFiles или ParseGlob. Вложенные определения шаблонов наследуют эти настройки. Пустые разделители соответствуют значениям по умолчанию: {{ и }}.

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

Пример
package main

import (
	"html/template"
	"log"
	"os"
)

func main() {
	const text = "<<.Greeting>> {{.Name}}"

	data := struct {
		Greeting string
		Name     string
	}{
		Greeting: "Hello",
		Name:     "Joe",
	}

	t := template.Must(template.New("tpl").Delims("<<", ">>").Parse(text))

	err := t.Execute(os.Stdout, data)
	if err != nil {
		log.Fatal(err)
	}

}
Output:

Hello {{.Name}}

func (*Template) Execute

func (t *Template) Execute(wr io.Writer, data any) error

Применяет разобранный шаблон к указанным данным, записывая результат в wr. В случае ошибки выполнение прерывается, но частичные результаты могут быть уже записаны. Шаблон можно выполнять параллельно, но при использовании общего Writer вывод может перемешиваться.

func (*Template) ExecuteTemplate

func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error

Применяет шаблон с указанным именем, ассоциированный с t, к данным и записывает результат в wr. Поведение при ошибках и параллельном выполнении аналогично Execute.

func (*Template) Funcs

func (t *Template) Funcs(funcMap FuncMap) *Template

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

func (*Template) Lookup

func (t *Template) Lookup(name string) *Template

Возвращает шаблон с указанным именем, ассоциированный с t, или nil если такого шаблона нет.

func (*Template) Name

func (t *Template) Name() string

Возвращает имя шаблона.

func (*Template) New

func (t *Template) New(name string) *Template

Создает новый HTML-шаблон с указанным именем, ассоциированный с текущим и имеющий те же разделители. Позволяет использовать вызовы шаблонов через {{template}}. Если шаблон с таким именем уже существует, он будет заменен.

func (*Template) Option

func (t *Template) Option(opt ...string) *Template

Устанавливает опции шаблона в формате строк “ключ=значение” или простых строк. Вызывает панику при неизвестной или неверной опции.

Поддерживаемые опции:

  • missingkey=default или missingkey=invalid - поведение по умолчанию (продолжать выполнение)
  • missingkey=zero - возвращать нулевое значение для отсутствующего ключа
  • missingkey=error - прерывать выполнение с ошибкой

func (*Template) Parse

func (t *Template) Parse(text string) (*Template, error)

Разбирает текст как тело шаблона для t. Определения именованных шаблонов ({{define}}/{{block}}) становятся ассоциированными шаблонами. Шаблоны можно переопределять до первого вызова Execute. Пустые определения (только пробелы/комментарии) не заменяют существующие шаблоны.

func (*Template) ParseFS

func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error)

ParseFS аналогична Template.ParseFiles и Template.ParseGlob, но читает из абстрактной файловой системы fs вместо файловой системы хоста. Принимает список glob-шаблонов (большинство имен файлов работают как glob-шаблоны, соответствующие только самим себе).

func (*Template) ParseFiles

func (t *Template) ParseFiles(filenames ...string) (*Template, error)

ParseFiles парсит указанные файлы и ассоциирует полученные шаблоны с t. При ошибке парсинг останавливается и возвращается nil, иначе возвращается t. Должен быть указан хотя бы один файл.

Особенности:

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

func (*Template) ParseGlob

func (t *Template) ParseGlob(pattern string) (*Template, error)

ParseGlob парсит шаблоны из файлов, соответствующих указанному шаблону (согласно семантике filepath.Match), и ассоциирует их с t. Эквивалентно вызову t.ParseFiles со списком файлов, соответствующих шаблону.

Особенности:

  • Как и в ParseFiles, при дублировании имен используется последний файл
  • Возвращает ошибку, если t или любой ассоциированный шаблон уже были выполнены

func (*Template) Templates

func (t *Template) Templates() []*Template

Templates возвращает срез всех шаблонов, ассоциированных с t, включая сам t.

type URL

type URL string

URL инкапсулирует безопасный URL или подстроку URL (согласно RFC 3986). URL типа javascript:checkThatFormNotEditedBeforeLeavingPage() из доверенного источника может быть использован, но по умолчанию динамические javascript: URL фильтруются как частый вектор атак.

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

2 - Пакет bufio встроенных функций Go

Пакет bufio реализует буферизованный ввод-вывод. Он оборачивает объект io.Reader или io.Writer, создавая другой объект (Reader или Writer), который также реализует интерфейс, но обеспечивает буферизацию и некоторую помощь для текстового ввода-вывода.

Константы

const ( 
	// maxscantokensize - максимальный размер, используемый для буфера токена 
	// Если пользователь не предоставит явный буфер с [scanner.buffer]. 
	// фактический максимальный размер токена может быть меньше в качестве буфера 
	// может потребоваться включить, например, новую линию. 
	Maxscantokensize = 64 * 1024
)

Переменные

var ( 
	ErrinValidunReadByte = errors.new ("Bufio: недействительное использование UnReadByte") 
	Errinvalidunreadrune = errors.new ("bufio: недействительное использование Undeardrune") 
	Errbufferfull = errors.new ("bufio: buffer full") 
	Errnegativecount = errors.new ("bufio: отрицательный счет")
	)
var ( 
	Errtoolong = errors.new ("bufio.scanner: токен слишком длинный") 
	Errnegativeadvance = errors.new ("bufio.scanner: splitfunc возвращает отрицательное значение сдвига") ") 
	ErradvancetOfar = errors.new ("bufio.scanner: splitfunc возвращает сдвиг, превышающий входные данные") 
	ErrbadReadCount = errors.new ("bufio.scanner: read возвращается невозможное количество")
)

Ошибки возвращаемые Scanner.

var errfinaltoken = errors.new ("Окончательный токен")

ErrFinalToken — это специальное контрольное значение ошибки. Оно предназначено для возврата функцией Split, чтобы указать, что сканирование должно быть остановлено без ошибки. Если токен, передаваемый с этой ошибкой, не равен nil, то этот токен является последним токеном.

Это значение полезно для досрочного прекращения обработки или когда необходимо передать последний пустой токен (который отличается от токена nil). Такое же поведение можно достичь с помощью пользовательского значения ошибки, но использование этого значения более удобно. Пример использования этого значения см. в примере emptyFinalToken.

Функции

func ScanBytes

func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)

ScanBytes — это функция разделения для сканера, которая возвращает каждый байт в виде токена.

Назначение: Чтение входных данных побайтово.

Пример:

scanner := bufio.NewScanner(strings.NewReader("Go"))
scanner.Split(bufio.ScanBytes)

for scanner.Scan() {
    fmt.Printf("%q ", scanner.Bytes())
}
// Вывод: 'G' 'o'

Когда использовать:

  • Когда нужно обрабатывать каждый байт данных
  • Для низкоуровневой обработки бинарных данных

func ScanLines

func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error)

ScanLines — это функция разделения для сканера, которая возвращает каждую строку текста без конечного маркера конца строки. Возвращаемая строка может быть пустой. Маркер конца строки — это один необязательный возврат каретки, за которым следует один обязательный символ новой строки. В нотации регулярных выражений это \r?\n. Последняя непустая строка ввода будет возвращена, даже если в ней нет символа новой строки.

Назначение: Чтение входных данных построчно (по умолчанию в Scanner).

Пример:

scanner := bufio.NewScanner(strings.NewReader("line1\nline2\n"))
scanner.Split(bufio.ScanLines)

for scanner.Scan() {
    fmt.Println(scanner.Text())
}
// Вывод:
// line1
// line2

Когда использовать:

  • Для обработки логов
  • Чтения конфигурационных файлов
  • Обработки любого текста, организованного по строкам

func ScanRunes

func ScanRunes(data []byte, atEOF bool) (advance int, token []byte, err error)

ScanRunes — это функция разделения для сканера, которая возвращает каждую руну, закодированную в UTF-8, в виде токена. Последовательность возвращаемых рун эквивалентна последовательности из цикла range над входом в виде строки, что означает, что ошибочные кодировки UTF-8 переводятся в U+FFFD = “\xef\xbf\xbd”. Из-за интерфейса Scan это делает невозможным для клиента отличить правильно закодированные руны-замены от ошибок кодирования.

Назначение: Чтение входных данных посимвольно (с поддержкой UTF-8).

Пример:

scanner := bufio.NewScanner(strings.NewReader("Привет"))
scanner.Split(bufio.ScanRunes)

for scanner.Scan() {
    fmt.Printf("%q ", scanner.Text())
}
// Вывод: 'П' 'р' 'и' 'в' 'е' 'т'

Когда использовать:

  • Для обработки Unicode-текстов
  • Когда нужно работать с отдельными символами (рунами)
  • Для анализа текста на уровне символов

func ScanWords

func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error)

ScanWords — это функция разделения для Scanner, которая возвращает каждое слово текста, разделенное пробелом, с удалением окружающих пробелов. Она никогда не возвращает пустую строку. Определение пробела устанавливается unicode.IsSpace.

Назначение: Чтение входных данных по словам.

Пример:

scanner := bufio.NewScanner(strings.NewReader("Hello world!"))
scanner.Split(bufio.ScanWords)

for scanner.Scan() {
    fmt.Println(scanner.Text())
}
// Вывод:
// Hello
// world!

Когда использовать:

  • Для анализа текста по словам
  • При обработке естественного языка
  • Для подсчета слов в тексте

Типы

type ReadWriter

type ReadWriter struct {
	*Reader
	*Writer
}

ReadWriter хранит указатели на Reader и Writer. Он реализует io.ReadWriter.

func NewReadWriter

func NewReadWriter(r *Reader, w *Writer) *ReadWriter

NewReadWriter выделяет новый ReadWriter, который отправляет данные в R и W.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader реализует буферизацию для объекта io.Reader. Новый Reader создается вызовом NewReader или NewReaderSize; в качестве альтернативы можно использовать нулевое значение Reader после вызова [Reset] для него.

func NewReader

func NewReader(rd io.Reader) *Reader

NewReader возвращает новый объект Reader, буфер которого имеет размер по умолчанию.

func NewReaderSize

func NewReaderSize(rd io.Reader, size int) *Reader

NewReaderSize возвращает новый Reader, буфер которого имеет как минимум указанный размер. Если аргумент io.Reader уже является Reader с достаточно большим размером, то возвращается базовый Reader.

func (*Reader) Buffered

func (b *Reader) Buffered() int

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

func (*Reader) Discard

added in go1.5

func (b *Reader) Discard(n int) (discarded int, err error)

Discard пропускает следующие n байтов и возвращает количество пропущенных байтов.

Если Discard пропускает меньше n байтов, он также возвращает ошибку. Если 0 <= n <= b.Buffered(), Discard гарантированно выполнится успешно без чтения из базового io.Reader.

func (*Reader) Peek

func (b *Reader) Peek(n int) ([]byte, error)

Peek возвращает следующие n байтов, не продвигая считывающее устройство. Байты перестают быть действительными при следующем вызове чтения. При необходимости Peek прочитает больше байтов в буфер, чтобы сделать доступными n байтов. Если Peek возвращает меньше n байтов, он также возвращает ошибку, объясняющую, почему чтение является коротким. Ошибка ErrBufferFull возникает, если n больше размера буфера b.

Вызов Peek предотвращает выполнение вызова Reader.UnreadByte или Reader.UnreadRune до следующей операции чтения.

func (*Reader) Read

func (b *Reader) Read(p []byte) (n int, err error)

Read считывает данные в p. Он возвращает количество байтов, прочитанных в p. Байты берутся максимум из одного Read в базовом Reader, поэтому n может быть меньше len(p). Чтобы прочитать ровно len(p) байтов, используйте io.ReadFull(b, p). Если базовый Reader может возвращать ненулевое значение с io.EOF, то этот метод Read также может это делать; см. документацию io.Reader.

func (*Reader) ReadByte

func (b *Reader) ReadByte() (byte, error)

ReadByte считывает и возвращает один байт. Если байт недоступен, возвращает ошибку.

func (*Reader) ReadBytes

func (b *Reader) ReadBytes(delim byte) ([]byte, error)

ReadBytes читает входные данные до первого появления разделителя delim, возвращая фрагмент, содержащий данные до разделителя включительно. Если ReadBytes обнаруживает ошибку до нахождения разделителя, он возвращает данные, прочитанные до ошибки, и саму ошибку (часто io.EOF). ReadBytes возвращает err != nil, если и только если возвращаемые данные не заканчиваются на delim. Для простых задач может быть более удобно использовать Scanner.

func (*Reader) ReadLine

func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)

ReadLine — это низкоуровневая примитивная функция чтения строк. Большинству вызывающих функций следует использовать Reader.ReadBytes(’\n’) или Reader.ReadString(’\n’) вместо нее, либо использовать Scanner.

ReadLine пытается вернуть одну строку, не включая байты конца строки. Если строка была слишком длинной для буфера, то устанавливается isPrefix и возвращается начало строки. Остальная часть строки будет возвращена при последующих вызовах. isPrefix будет false при возвращении последнего фрагмента строки. Возвращаемый буфер действителен только до следующего вызова ReadLine. ReadLine либо возвращает строку, отличную от nil, либо возвращает ошибку, но никогда и то, и другое одновременно.

Текст, возвращаемый из ReadLine, не включает конец строки («\r\n» или «\n»). Если ввод заканчивается без конечного конца строки, никаких указаний или ошибок не выдается. Вызов Reader.UnreadByte после ReadLine всегда отменяет чтение последнего прочитанного байта (возможно, символа, принадлежащего концу строки), даже если этот байт не является частью строки, возвращаемой ReadLine.

func (*Reader) ReadRune

func (b *Reader) ReadRune() (r rune, size int, err error)

ReadRune считывает один символ Unicode, закодированный в UTF-8, и возвращает руну и ее размер в байтах. Если закодированная руна недействительна, она потребляет один байт и возвращает unicode.ReplacementChar (U+FFFD) с размером 1.

func (*Reader) ReadSlice

func (b *Reader) ReadSlice(delim byte) (line []byte, err error)

ReadSlice читает до первого появления разделителя в входных данных, возвращая фрагмент, указывающий на байты в буфере. Байты перестают быть действительными при следующем чтении. Если ReadSlice встречает ошибку до нахождения разделителя, он возвращает все данные в буфере и саму ошибку (часто io.EOF). ReadSlice завершается с ошибкой ErrBufferFull, если буфер заполняется без delim. Поскольку данные, возвращаемые из ReadSlice, будут перезаписаны следующей операцией ввода-вывода, большинство клиентов должны использовать Reader.ReadBytes или ReadString вместо этого. ReadSlice возвращает err != nil, если и только если строка не заканчивается на delim.

func (*Reader) ReadString

func (b *Reader) ReadString(delim byte) (string, error)

ReadString читает входные данные до первого появления delim, возвращая строку, содержащую данные до разделителя включительно. Если ReadString встречает ошибку до нахождения разделителя, он возвращает данные, прочитанные до ошибки, и саму ошибку (часто io.EOF). ReadString возвращает err != nil тогда и только тогда, когда возвращаемые данные не заканчиваются на delim. Для простых задач может быть более удобно использовать Scanner.

func (*Reader) Reset

func (b *Reader) Reset(r io.Reader)

Reset удаляет все данные из буфера, сбрасывает все состояния и переключает буферизованный считыватель на чтение из r. Вызов Reset с нулевым значением Reader инициализирует внутренний буфер до размера по умолчанию. Вызов b.Reset(b) (то есть сброс Reader на себя) ничего не делает.

func (*Reader) Size

func (b *Reader) Size() int

Size возвращает размер базового буфера в байтах.

func (*Reader) UnreadByte

func (b *Reader) UnreadByte() error

UnreadByte отменяет чтение последнего байта. Отменить чтение можно только последнего прочитанного байта.

UnreadByte возвращает ошибку, если последний вызов метода Reader не был операцией чтения. В частности, методы Reader.Peek, Reader.Discard и Reader.WriteTo не считаются операциями чтения.

func (*Reader) UnreadRune

func (b *Reader) UnreadRune() error

UnreadRune снимает отметку о прочтении с последней руны. Если последний вызов метода Reader не был Reader.ReadRune, Reader.UnreadRune возвращает ошибку. (В этом отношении он более строг, чем Reader.UnreadByte, который снимает отметку о прочтении с последнего байта любой операции чтения.)

func (*Reader) WriteTo

func (b *Reader) WriteTo(w io.Writer) (n int64, err error)

WriteTo реализует io.WriterTo. Это может привести к нескольким вызовам метода Reader.Read базового Reader. Если базовый Reader поддерживает метод Reader.WriteTo, то вызывается базовый Reader.WriteTo без буферизации.

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

Scanner предоставляет удобный интерфейс для чтения данных, таких как файл текста, разбитый на строки с разделителями новой строки. Последовательные вызовы метода Scanner.Scan будут проходить по «токену» файла, пропуская байты между токенами. Спецификация токена определяется функцией разбиения типа SplitFunc; функция разбиения по умолчанию разбивает входные данные на строки с удалением символов окончания строки. Функции Scanner.Split определены в этом пакете для сканирования файла на строки, байты, руны с кодировкой UTF-8 и слова, разделенные пробелами. Клиент может вместо этого предоставить собственную функцию разделения.

Сканирование останавливается без возможности восстановления при EOF, первой ошибке ввода-вывода или токене, который слишком велик, чтобы поместиться в Scanner.Buffer. Когда сканирование останавливается, читатель может продвинуться произвольно далеко за последний токен. Программы, которым требуется больший контроль над обработкой ошибок или большими токенами, или которые должны выполнять последовательное сканирование на читателе, должны использовать bufio.Reader.

Пример пользовательский

Используйте сканер с настраиваемой функцией разделения (созданный с помощью ScanWords) для проверки 32-разрядного десятичного ввода.

package main

import (
	"bufio"
	"fmt"
	"strconv"
	"strings"
)

func main() {
	// An artificial input source.
	const input = "1234 5678 1234567901234567890"
	scanner := bufio.NewScanner(strings.NewReader(input))
	// Create a custom split function by wrapping the existing ScanWords function.
	split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		advance, token, err = bufio.ScanWords(data, atEOF)
		if err == nil && token != nil {
			_, err = strconv.ParseInt(string(token), 10, 32)
		}
		return
	}
	// Set the split function for the scanning operation.
	scanner.Split(split)
	// Validate the input
	for scanner.Scan() {
		fmt.Printf("%s\n", scanner.Text())
	}

	if err := scanner.Err(); err != nil {
		fmt.Printf("Invalid input: %s", err)
	}
}
Output:

1234
5678
Invalid input: strconv.ParseInt: parsing "1234567901234567890": value out of range
Пример EarlyStop

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

package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strings"
)

func main() {
	onComma := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		i := bytes.IndexByte(data, ',')
		if i == -1 {
			if !atEOF {
				return 0, nil, nil
			}
			// If we have reached the end, return the last token.
			return 0, data, bufio.ErrFinalToken
		}
		// If the token is "STOP", stop the scanning and ignore the rest.
		if string(data[:i]) == "STOP" {
			return i + 1, nil, bufio.ErrFinalToken
		}
		// Otherwise, return the token before the comma.
		return i + 1, data[:i], nil
	}
	const input = "1,2,STOP,4,"
	scanner := bufio.NewScanner(strings.NewReader(input))
	scanner.Split(onComma)
	for scanner.Scan() {
		fmt.Printf("Got a token %q\n", scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading input:", err)
	}
}
Output:

Got a token "1"
Got a token "2"
Пример EmptyFinalToken

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

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	// Comma-separated list; last entry is empty.
	const input = "1,2,3,4,"
	scanner := bufio.NewScanner(strings.NewReader(input))
	// Define a split function that separates on commas.
	onComma := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		for i := 0; i < len(data); i++ {
			if data[i] == ',' {
				return i + 1, data[:i], nil
			}
		}
		if !atEOF {
			return 0, nil, nil
		}
		// There is one final token to be delivered, which may be the empty string.
		// Returning bufio.ErrFinalToken here tells Scan there are no more tokens after this
		// but does not trigger an error to be returned from Scan itself.
		return 0, data, bufio.ErrFinalToken
	}
	scanner.Split(onComma)
	// Scan.
	for scanner.Scan() {
		fmt.Printf("%q ", scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading input:", err)
	}
}
Output:

"1" "2" "3" "4" ""
Пример Lines

Самый простой способ использования сканера — чтение стандартного ввода в виде набора строк.

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		fmt.Println(scanner.Text()) // Println will add back the final '\n'
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}
}
Пример Words

Используйте сканер для реализации простой утилиты подсчета слов, сканируя входные данные как последовательность токенов, разделенных пробелами.

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	// An artificial input source.
	const input = "Now is the winter of our discontent,\nMade glorious summer by this sun of York.\n"
	scanner := bufio.NewScanner(strings.NewReader(input))
	// Set the split function for the scanning operation.
	scanner.Split(bufio.ScanWords)
	// Count the words.
	count := 0
	for scanner.Scan() {
		count++
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading input:", err)
	}
	fmt.Printf("%d\n", count)
}
Output:

15

func NewScanner

func NewScanner(r io.Reader) *Scanner

NewScanner возвращает новый сканер для чтения из r. По умолчанию функция разделения — ScanLines.

func (*Scanner) Buffer

func (s *Scanner) Buffer(buf []byte, max int)

Buffer устанавливает начальный буфер, который будет использоваться при сканировании, и максимальный размер буфера, который может быть выделен во время сканирования. Максимальный размер токена должен быть меньше большего из max и cap(buf). Если max <= cap(buf), Scanner.Scan будет использовать только этот буфер и не будет выделять дополнительную память.

По умолчанию Scanner.Scan использует внутренний буфер и устанавливает максимальный размер токена равным MaxScanTokenSize.

Buffer вызывает панику, если его вызвать после начала сканирования.

func (*Scanner) Bytes

func (s *Scanner) Bytes() []byte

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

Пример
package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	scanner := bufio.NewScanner(strings.NewReader("gopher"))
	for scanner.Scan() {
		fmt.Println(len(scanner.Bytes()) == 6)
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "shouldn't see an error scanning a string")
	}
}
Output:

true

func (*Scanner) Err

func (s *Scanner) Err() error

Err возвращает первую ошибку, не являющуюся EOF, которая была обнаружена Scanner.

func (*Scanner) Scan

func (s *Scanner) Scan() bool

Scan перемещает Scanner к следующему токену, который затем будет доступен через метод Scanner.Bytes или Scanner.Text. Он возвращает false, когда больше нет токенов, либо по достижении конца ввода, либо из-за ошибки. После того, как Scan возвращает false, метод Scanner.Err вернет любую ошибку, которая произошла во время сканирования, за исключением того, что если это было io.EOF, Scanner.Err вернет nil. Scan вызывает панику, если функция split возвращает слишком много пустых токенов, не продвигая ввод. Это обычный режим ошибки для сканеров.

func (*Scanner) Split

func (s *Scanner) Split(split SplitFunc)

Split устанавливает функцию разделения для сканера. Функция разделения по умолчанию — ScanLines.

Split вызывает панику, если его вызывают после начала сканирования.

func (*Scanner) Text

добавлено в go1.1

func (s *Scanner) Text() string

Text возвращает последний токен, сгенерированный вызовом Scanner.Scan, в виде вновь выделенной строки, содержащей его байты.

type SplitFunc

type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)

SplitFunc — это сигнатура функции разделения, используемой для токенизации входных данных. Аргументами являются начальная подстрока оставшихся необработанных данных и флаг atEOF, который сообщает, есть ли у Reader еще данные для передачи. Возвращаемые значения — это количество байтов для продвижения входных данных и следующий токен для возврата пользователю, если таковой имеется, плюс ошибка, если таковая имеется.

Сканирование останавливается, если функция возвращает ошибку, и в этом случае часть ввода может быть отброшена. Если эта ошибка — ErrFinalToken, сканирование останавливается без ошибки. Токен, отличный от nil, доставленный с ErrFinalToken, будет последним токеном, а токен nil с ErrFinalToken немедленно останавливает сканирование.

В противном случае сканер продвигает ввод. Если токен не равен nil, сканер возвращает его пользователю. Если токен равен nil, сканер считывает больше данных и продолжает сканирование; если данных больше нет (если atEOF было true), сканер возвращается. Если данные еще не содержат полный токен, например, если при сканировании строк в них нет символа новой строки, SplitFunc может вернуть (0, nil, nil), чтобы сигнализировать сканеру о необходимости считывать больше данных в срез и повторить попытку с более длинным срезом, начиная с той же точки во входных данных.

Функция никогда не вызывается с пустым фрагментом данных, если atEOF не равно true. Однако, если atEOF равно true, данные могут быть непустыми и, как всегда, содержать необработанный текст.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

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

Пример
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	w := bufio.NewWriter(os.Stdout)
	fmt.Fprint(w, "Hello, ")
	fmt.Fprint(w, "world!")
	w.Flush() // Don't forget to flush!
}
Output:

Hello, world!

func NewWriter

func NewWriter(w io.Writer) *Writer

NewWriter возвращает новый Writer, буфер которого имеет размер по умолчанию. Если аргумент io.Writer уже является Writer с достаточно большим размером буфера, он возвращает базовый Writer.

func NewWriterSize

func NewWriterSize(w io.Writer, size int) *Writer

NewWriterSize возвращает новый Writer, буфер которого имеет как минимум указанный размер. Если аргумент io.Writer уже является Writer с достаточно большим размером, он возвращает базовый Writer.

func (*Writer) Available

func (b *Writer) Available() int

Available возвращает количество неиспользуемых байтов в буфере.

func (*Writer) AvailableBuffer

func (b *Writer) AvailableBuffer() []byte

AvailableBuffer возвращает пустой буфер с емкостью b.Available(). Этот буфер предназначен для добавления и передачи в следующий вызов Writer.Write. Буфер действителен только до следующей операции записи в b.

Пример
package main

import (
	"bufio"
	"os"
	"strconv"
)

func main() {
	w := bufio.NewWriter(os.Stdout)
	for _, i := range []int64{1, 2, 3, 4} {
		b := w.AvailableBuffer()
		b = strconv.AppendInt(b, i, 10)
		b = append(b, ' ')
		w.Write(b)
	}
	w.Flush()
}
Output:

1 2 3 4

func (*Writer) Buffered

func (b *Writer) Buffered() int

Buffered возвращает количество байтов, которые были записаны в текущий буфер.

func (*Writer) Flush

func (b *Writer) Flush() error

Flush записывает все буферизованные данные в базовый io.Writer.

func (*Writer) ReadFrom

func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)

ReadFrom реализует io.ReaderFrom. Если базовый writer поддерживает метод ReadFrom, то вызывается базовый ReadFrom. Если есть буферизованные данные и базовый ReadFrom, то буфер заполняется и записывается перед вызовом ReadFrom.

Пример
package main

import (
	"bufio"
	"bytes"
	"fmt"
	"strings"
)

func main() {
	var buf bytes.Buffer
	writer := bufio.NewWriter(&buf)

	data := "Hello, world!\nThis is a ReadFrom example."
	reader := strings.NewReader(data)

	n, err := writer.ReadFrom(reader)
	if err != nil {
		fmt.Println("ReadFrom Error:", err)
		return
	}

	if err = writer.Flush(); err != nil {
		fmt.Println("Flush Error:", err)
		return
	}

	fmt.Println("Bytes written:", n)
	fmt.Println("Buffer contents:", buf.String())
}
Output:

Bytes written: 41
Buffer contents: Hello, world!
This is a ReadFrom example.

func (*Writer) Reset

func (b *Writer) Reset(w io.Writer)

Reset удаляет все невыгруженные данные из буфера, очищает все ошибки и сбрасывает b, чтобы записать его вывод в w. Вызов Reset с нулевым значением Writer инициализирует внутренний буфер до размера по умолчанию. Вызов w.Reset(w) (то есть сброс Writer к самому себе) ничего не делает.

func (*Writer) Size

added in go1.10

func (b *Writer) Size() int

Size возвращает размер базового буфера в байтах.

func (*Writer) Write

func (b *Writer) Write(p []byte) (nn int, err error)

Write записывает содержимое p в буфер. Он возвращает количество записанных байтов. Если nn < len(p), он также возвращает ошибку, объясняющую, почему запись не завершена.

func (*Writer) WriteByte

func (b *Writer) WriteByte(c byte) error

WriteByte записывает один байт.

func (*Writer) WriteRune

func (b *Writer) WriteRune(r rune) (size int, err error)

WriteRune записывает одну кодовую точку Unicode, возвращая количество записанных байтов и любую ошибку.

func (*Writer) WriteString

func (b *Writer) WriteString(s string) (int, error)

WriteString записывает строку. Он возвращает количество записанных байтов. Если количество меньше len(s), он также возвращает ошибку с объяснением, почему запись не завершена.

3 - Пакет builtin встроенных функций Go

Пакет builtin содержит документацию по заранее объявленным идентификаторам языка Go.

Элементы, задокументированные здесь, на самом деле не входят в пакет builtin, но их описание здесь позволяет godoc представить документацию для специальных идентификаторов языка.

Константы


const (
	true = 0 == 0 // Untyped bool.
	false = 0 != 0 // Untyped bool.
)

true и false - это два нетипизированных булевых значения.

const iota = 0 // Untyped int.

iota - это заранее объявленный идентификатор, представляющий нетипизированный целочисленный порядковый номер текущей спецификации const в объявлении const (обычно в круглых скобках). Он имеет нулевую индексацию.

Переменные ¶

var nil Type // Тип должен быть указателем, каналом, func, интерфейсом, map или slice-типом

nil - это заранее объявленный идентификатор, представляющий нулевое значение для указателя, канала, func, интерфейса, map или slice-типа.

3.1 - Функции builtin

Описание функций builtin

func append

func append(slice []Type, elems ...Type) []Type

Встроенная функция append добавляет элементы в конец слайса. Если он имеет достаточную емкость, целевой слайс переразбивается, чтобы вместить новые элементы. Если нет, выделяется новый базовый массив. Append возвращает обновленный слайс. Поэтому необходимо сохранить результат append, часто в переменной, содержащей сам слайс:

slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)

В качестве особого случая допустимо добавлять строку к байтовому слайсу, например:

slice = append([]byte(«hello »), «world»...)

func cap

func cap(v Type) int

Встроенная функция cap возвращает емкость v в зависимости от его типа:

  • Массив: количество элементов в v (то же, что len(v)).
  • Указатель на массив: количество элементов в *v (то же, что len(v)).
  • Срез: максимальная длина, которую может достичь срез при повторном разрезании; если v равно nil, cap(v) равно нулю.
  • Канал: емкость буфера канала в единицах элементов; если v равно nil, cap(v) равно нулю.

Для некоторых аргументов, таких как простое массивное выражение, результатом может быть константа. Подробности см. в разделе «Длина и емкость» спецификации языка Go.

func clear

func clear[T ~[]Type | ~map[Type]Type1](t T)

Встроенная функция clear очищает карты и фрагменты.

  • Для карт clear удаляет все записи, в результате чего карта становится пустой.
  • Для слайсов clear устанавливает все элементы до длины слайса в нулевое значение соответствующего типа элемента.

Если тип аргумента является типовым параметром, набор типов типового параметра должен содержать только типы карт или слайсов, и clear выполняет операцию, подразумеваемую типовым аргументом. Если t равно nil, clear не выполняет никаких действий.

func close

func close(c chan<- Type)

Встроенная функция close закрывает канал, который должен быть либо двунаправленным, либо только для отправки. Она должна выполняться только отправителем, никогда не получателем, и приводит к закрытию канала после получения последнего отправленного значения. После получения последнего значения из закрытого канала c любое получение из c будет выполняться без блокировки, возвращая нулевое значение для элемента канала. Форма

x, ok := <-c

также установит ok в false для закрытого и пустого канала.

func complex

func complex(r, i FloatType) ComplexType

Встроенная функция complex создает комплексное значение из двух значений с плавающей запятой. Действительная и мнимая части должны быть одинакового размера, либо float32, либо float64 (или приравниваемы к ним), и возвращаемое значение будет соответствующим комплексным типом (complex64 для float32, complex128 для float64).

func copy

func copy(dst, src []Type) int

Встроенная функция copy копирует элементы из исходного слайса в целевой слайс. (В качестве особого случая она также копирует байты из строки в слайс байтов.) Исходный и целевой слайсы могут пересекаться. Copy возвращает количество скопированных элементов, которое будет минимальным из len(src) и len(dst).

func delete

func delete(m map[Type]Type1, key Type)

Встроенная функция delete удаляет элемент с указанным ключом (m[key]) из карты. Если m равно nil или такого элемента нет, delete не выполняет никаких действий.

func imag

func imag(c ComplexType) FloatType

Встроенная функция imag возвращает мнимую часть комплексного числа c. Возвращаемое значение будет иметь тип с плавающей запятой, соответствующий типу c.

func len

func len(v Type) int

Встроенная функция len возвращает длину v в соответствии с его типом:

  • Массив: количество элементов в v.
  • Указатель на массив: количество элементов в *v (даже если v равен nil).
  • Срез или карта: количество элементов в v; если v равен nil, len(v) равен нулю.
  • Строка: количество байтов в v.
  • Канал: количество элементов в очереди (непрочитанных) в буфере канала; если v равно nil, len(v) равно нулю.

Для некоторых аргументов, таких как строковый литерал или простое массивное выражение, результатом может быть константа. Подробности см. в разделе «Длина и емкость» спецификации языка Go.

func make

func make(t Type, size ...IntegerType) Type

Встроенная функция make выделяет и инициализирует объект типа slice, map или chan (только). Как и в случае с new, первый аргумент является типом, а не значением. В отличие от new, тип возвращаемого значения make совпадает с типом его аргумента, а не является указателем на него. Спецификация результата зависит от типа:

  • Срез: размер определяет длину. Емкость среза равна его длине. Можно указать второй целочисленный аргумент, чтобы задать другую емкость; она не должна быть меньше длины. Например, make([]int, 0, 10) выделяет базовый массив размером 10 и возвращает срез длиной 0 и емкостью 10, который поддерживается этим базовым массивом.
  • Карта: выделяется пустая карта с достаточным пространством для хранения указанного количества элементов. Размер может быть опущен, в этом случае выделяется небольшой начальный размер.
  • Канал: буфер канала инициализируется с указанной емкостью буфера. Если размер равен нулю или опущен, канал не имеет буфера.

func max

func max[T cmp.Ordered](x T, y ...T) T

Встроенная функция max возвращает наибольшее значение из фиксированного числа аргументов типов cmp.Ordered. Должно быть как минимум один аргумент. Если T является типом с плавающей запятой и любой из аргументов является NaN, max вернет NaN.

func min

func min[T cmp.Ordered](x T, y ...T) T

Встроенная функция min возвращает наименьшее значение из фиксированного числа аргументов типов cmp.Ordered. Должно быть как минимум один аргумент. Если T является типом с плавающей запятой и любой из аргументов является NaN, min вернет NaN.

func new

func new(Type) *Type

Встроенная функция new выделяет память. Первый аргумент — это тип, а не значение, и возвращаемое значение — это указатель на вновь выделенное нулевое значение этого типа.

func panic

func panic(v any)

Встроенная функция panic останавливает нормальное выполнение текущего goroutine.

Когда функция F вызывает panic, нормальное выполнение F немедленно останавливается. Все функции, выполнение которых было отложено F, выполняются обычным образом, а затем F возвращается к своему вызывающему. Для вызывающего G вызов F ведет себя как вызов panic, прекращая выполнение G и запуская все отложенные функции. Это продолжается до тех пор, пока все функции в выполняющемся goroutine не остановятся в обратном порядке. В этот момент программа завершается с ненулевым кодом выхода. Эта последовательность завершения называется паникой и может контролироваться встроенной функцией recover.

Начиная с Go 1.21, вызов panic с нулевым значением интерфейса или нетипизированным nil вызывает ошибку выполнения (другой вид паники). Настройка GODEBUG panicnil=1 отключает ошибку выполнения.

func print

func print(args ...Type)

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

func println

func println(args ...Type)

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

func real

func real(c ComplexType) FloatType

Встроенная функция real возвращает действительную часть комплексного числа c. Возвращаемое значение будет иметь тип с плавающей запятой, соответствующий типу c.

func recover

func recover() any

Встроенная функция recover позволяет программе управлять поведением goroutine, входящего в состояние паники.

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

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

До Go 1.21 recover также возвращала nil, если panic вызывалась с аргументом nil. Подробности см. в разделе [panic].

Пример

Функция recover() используется для перехвата паники (panic) и восстановления нормального выполнения программы. Вот несколько практических примеров:

Пример 1: Базовое использование recover

package main

import "fmt"

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()

    fmt.Println("Start")
    panic("something went wrong")
    fmt.Println("This won't be executed")
}

Вывод:

Start
Recovered from panic: something went wrong

Пример 2: Восстановление после деления на ноль

package main

import "fmt"

func safeDivide(a, b int) (result int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
            result = 0 // устанавливаем значение по умолчанию
        }
    }()
    
    return a / b // может вызвать панику при b = 0
}

func main() {
    fmt.Println(safeDivide(10, 2)) // 5
    fmt.Println(safeDivide(10, 0)) // 0 (после восстановления)
    fmt.Println("Program continues")
}

Пример 3: Разные типы паники

package main

import "fmt"

func handlePanic() {
    if r := recover(); r != nil {
        switch v := r.(type) {
        case string:
            fmt.Println("String panic:", v)
        case error:
            fmt.Println("Error panic:", v)
        default:
            fmt.Printf("Unknown panic: %v (%T)\n", v, v)
        }
    }
}

func main() {
    defer handlePanic()
    
    // Можно раскомментировать любой вариант:
    panic("string panic")
    // panic(fmt.Errorf("error panic"))
    // panic(42)
}

Пример 4: recover в горутинах

package main

import (
    "fmt"
    "time"
)

func worker(id int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("Worker %d recovered: %v\n", id, r)
        }
    }()
    
    if id == 2 {
        panic(fmt.Sprintf("panic in worker %d", id))
    }
    fmt.Printf("Worker %d working\n", id)
}

func main() {
    for i := 1; i <= 3; i++ {
        go worker(i)
    }
    time.Sleep(time.Second) // Даем время горутинам завершиться
}

Пример 5: Вложенные recover

package main

import "fmt"

func inner() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Inner recovered:", r)
        }
    }()
    panic("inner panic")
}

func outer() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Outer recovered:", r)
        }
    }()
    inner()
    fmt.Println("This won't be executed")
}

func main() {
    outer()
    fmt.Println("Program continues")
}

Важные замечания:

  1. recover() работает только внутри defer-функций
  2. Каждый вызов recover() обрабатывает только одну панику
  3. После восстановления выполнение продолжается после блока defer
  4. В Go 1.21+ recover() возвращает nil только если паники не было
  5. Не злоупотребляйте recover - используйте только для действительно неожиданных ошибок

3.2 - Типы в пакете builtin

Описание типов из пакета builtin

type ComplexType

type ComplexType complex64

ComplexType используется здесь исключительно в целях документирования. Он заменяет любой из типов complex: complex64 или complex128.

type FloatType

type FloatType float32

FloatType используется здесь исключительно в целях документирования. Он заменяет любой из типов float: float32 или float64.

type IntegerType

type IntegerType int

IntegerType используется здесь только в целях документирования. Он заменяет любой тип целого числа: int, uint, int8 и т. д.

type Type

type Type int

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

type Type1

type Type1 int

Type1 используется здесь только в целях документирования. Он является заменителем для любого типа Go, но представляет один и тот же тип для любого вызова функции.

type any

type any = interface{}

any является псевдонимом для interface{} и во всех отношениях эквивалентен interface{}.

type bool

type bool bool

bool — это набор булевых значений true и false.

type byte

type byte = uint8

byte — это псевдоним для uint8 и во всех отношениях эквивалентен uint8. По соглашению он используется для отличия значений byte от 8-битных целых чисел без знака.

type comparable

type comparable interface{ comparable }

comparable — это интерфейс, который реализуется всеми сопоставимыми типами (булевыми значениями, числами, строками, указателями, каналами, массивами сопоставимых типов, структурами, все поля которых являются сопоставимыми типами). Интерфейс comparable может использоваться только в качестве ограничения параметра типа, а не в качестве типа переменной.

Подробнее

Интерфейс comparable - это специальный встроенный интерфейс в Go, который представляет все типы, поддерживающие операции сравнения == и !=. Он используется исключительно как ограничение (constraint) для параметров типа в дженериках.

Особенности comparable:

  1. Неявная реализация - все сопоставимые типы автоматически удовлетворяют этому интерфейсу
  2. Ограниченное использование - может применяться только как ограничение типа в дженериках
  3. Строгая типизация - гарантирует, что типы можно сравнивать между собой

Пример 1: Простая функция сравнения

package main

import "fmt"

// Equal проверяет равенство двух значений comparable типа
func Equal[T comparable](a, b T) bool {
    return a == b
}

func main() {
    fmt.Println(Equal(1, 1))       // true
    fmt.Println(Equal("a", "b"))   // false
    fmt.Println(Equal(true, false)) // false
}

Пример 2: Проверка наличия элемента в слайсе

func Contains[T comparable](slice []T, item T) bool {
    for _, v := range slice {
        if v == item {
            return true
        }
    }
    return false
}

func main() {
    nums := []int{1, 2, 3}
    fmt.Println(Contains(nums, 2))  // true
    fmt.Println(Contains(nums, 5))  // false

    strs := []string{"a", "b", "c"}
    fmt.Println(Contains(strs, "b")) // true
}

Пример 3: Уникальные элементы в слайсе

func Unique[T comparable](slice []T) []T {
    seen := make(map[T]bool)
    result := []T{}
    for _, v := range slice {
        if !seen[v] {
            seen[v] = true
            result = append(result, v)
        }
    }
    return result
}

func main() {
    dupes := []int{1, 2, 2, 3, 4, 4, 5}
    fmt.Println(Unique(dupes)) // [1 2 3 4 5]
}

Какие типы являются comparable:

  • Базовые типы: bool, int, float64, string и т.д.
  • Указатели (*T)
  • Каналы (chan T)
  • Массивы, если их элементы comparable ([3]int, [2]string)
  • Структуры, если все их поля comparable

Какие типы НЕ являются comparable:

  • Слайсы ([]T)
  • Мапы (map[K]V)
  • Функции
  • Структуры, содержащие несопоставимые поля

Пример 4: Ошибка при использовании несопоставимого типа

func main() {
    // Ошибка: slice does not satisfy comparable
    fmt.Println(Equal([]int{1}, []int{1}))
    
    // Ошибка: map does not satisfy comparable
    fmt.Println(Equal(map[int]int{}, map[int]int{}))
}

Практическое применение:

  1. Создание универсальных контейнеров
  2. Реализация алгоритмов для разных типов
  3. Написание тестовых утилит
  4. Создание библиотечных функций

type complex128

type complex128 complex128

complex128 — это набор всех комплексных чисел с вещественными и мнимыми частями float64.

type complex64

type complex64 complex64

complex64 — это набор всех комплексных чисел с вещественными и мнимыми частями типа float32.

type error

type error интерфейс {
    Error() string
}

Встроенный интерфейсный тип error — это стандартный интерфейс для представления условия ошибки, причем значение nil означает отсутствие ошибки.

type float32

type float32 float32

float32 — это набор всех 32-разрядных чисел с плавающей запятой IEEE 754.

type float64

type float64 float64

float64 — это набор всех 64-разрядных чисел с плавающей запятой IEEE 754.

type int

type int int

int — это тип целых чисел с знаком, размер которого составляет не менее 32 бит. Однако это отдельный тип, а не псевдоним, например, int32.

type int16

type int16 int16

int16 — это набор всех 16-разрядных целых чисел со знаком. Диапазон: от -32768 до 32767.

type int32

type int32 int32

int32 — это набор всех 32-разрядных целых чисел со знаком. Диапазон: от -2147483648 до 2147483647.

type int64

type int64 int64

int64 — это набор всех 64-разрядных целых чисел со знаком. Диапазон: от -9223372036854775808 до 9223372036854775807.

type int8

type int8 int8

int8 — это набор всех 8-битных целых чисел со знаком. Диапазон: от -128 до 127.

type rune

type rune = int32

rune — это псевдоним для int32 и во всех отношениях эквивалентен int32. По соглашению он используется для отличия значений символов от целочисленных значений.

type string

type string string

string — это набор всех строк из 8-битных байтов, которые по соглашению, но не обязательно, представляют текст, закодированный в UTF-8. Строка может быть пустой, но не может быть nil. Значения типа string являются неизменяемыми.

type uint

type uint uint

uint — это тип целого числа без знака, размер которого составляет не менее 32 бит. Однако это отдельный тип, а не псевдоним, например, для uint32.

type uint16

type uint16 uint16

uint16 — это набор всех 16-битных целых чисел без знака. Диапазон: от 0 до 65535.

type uint32

type uint32 uint32

uint32 — это набор всех 32-битных целых чисел без знака. Диапазон: от 0 до 4294967295.

type uint64

type uint64 uint64

uint64 — это набор всех 64-разрядных целых чисел без знака. Диапазон: от 0 до 18446744073709551615.

type uint8

type uint8 uint8

uint8 — это набор всех 8-разрядных целых чисел без знака. Диапазон: от 0 до 255.

type uintptr

type uintptr uintptr

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

4 - Описание пакета context языка программирования Go

Пакет Context определяет тип Context, который передает сроки, сигналы отмены и другие значения в рамках запроса через границы API и между процессами.

Входящие запросы к серверу должны создавать Context, а исходящие вызовы к серверам должны принимать Context. Цепочка вызовов функций между ними должна передавать Context, при необходимости заменяя его производным Context, созданным с помощью WithCancel, WithDeadline, WithTimeout или WithValue.

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

Функции WithCancel, WithDeadline и WithTimeout принимают контекст (родительский) и возвращают производный контекст (дочерний) и CancelFunc. Прямой вызов CancelFunc отменяет дочерний контекст и его дочерние контексты, удаляет ссылку родительского контекста на дочерний и останавливает все связанные таймеры. Невыполнение вызова CancelFunc приводит к утечке дочернего контекста и его дочерних контекстов до тех пор, пока не будет отменен родительский контекст. Инструмент go vet проверяет, что CancelFuncs используются на всех путях управления потоком.

Функции WithCancelCause, WithDeadlineCause и WithTimeoutCause возвращают CancelCauseFunc, который принимает ошибку и записывает ее как причину отмены. Вызов Cause на отмененном контексте или любом из его дочерних элементов извлекает причину. Если причина не указана, Cause(ctx) возвращает то же значение, что и ctx.Err().

Программы, использующие контексты, должны следовать этим правилам, чтобы обеспечить согласованность интерфейсов между пакетами и позволить инструментам статического анализа проверять распространение контекста:

Не храните контексты внутри типа struct; вместо этого явно передавайте контекст каждой функции, которой он нужен. Это подробнее обсуждается в разделе «https://go.dev/blog/context-and-structs». Контекст должен быть первым параметром, обычно называемым ctx:

func DoSomething(ctx context.Context, arg Arg) error {
    // ... использовать ctx ...
}

Не передавайте nil Context, даже если функция это допускает. Передайте context.TODO, если не уверены, какой Context использовать.

Используйте значения контекста только для данных в рамках запроса, которые проходят через процессы и API, а не для передачи опциональных параметров функциям.

Один и тот же Context может передаваться функциям, выполняющимся в разных goroutines; Context безопасен для одновременного использования несколькими goroutines.

См. https://go.dev/blog/context для примера кода сервера, использующего Context.

Переменные пакета

var Canceled = errors.New(«context canceled»)

Canceled — это ошибка, возвращаемая [Context.Err], когда контекст отменяется по какой-либо причине, кроме истечения срока.

var DeadlineExceeded error = deadlineExceededError{}

DeadlineExceeded — это ошибка, возвращаемая [Context.Err], когда контекст отменяется из-за истечения срока.

4.1 - Описание функций пакета Context

Описание функций пакета Context: AfterFunc, Cause, WithCancel, WithCancelCause, WithDeadline, WithDeadlineCause, WithTimeout, WithTimeoutCause

func AfterFunc

func AfterFunc(ctx Context, f func()) (stop func() bool)

AfterFunc организует вызов f в собственной горутине после отмены ctx. Если ctx уже отменен, AfterFunc вызывает f сразу в своей собственной горутине.

Несколько вызовов AfterFunc на одном контексте работают независимо; один не заменяет другой.

Вызов возвращаемой функции stop прекращает ассоциацию ctx с f. Он возвращает true, если вызов остановил запуск f. Если stop возвращает false, то либо контекст отменен и f была запущена в своей собственной goroutine, либо f уже была остановлена. Функция stop не ждет завершения f перед возвратом. Если вызывающей стороне необходимо знать, завершено ли выполнение f, она должна явно согласовать это с f.

Если у ctx есть метод “AfterFunc(func()) func() bool”, AfterFunc будет использовать его для планирования вызова.

Пример (Cond)

В этом примере AfterFunc используется для определения функции, которая ожидает синхронизацию с Cond, прекращая ожидание при отмене контекста.

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	waitOnCond := func(ctx context.Context, cond *sync.Cond, conditionMet func() bool) error {
		// Создаем канал для отмены AfterFunc
		done := make(chan struct{})
		defer close(done)

		// Настраиваем функцию отмены по таймауту контекста
		stopf := context.AfterFunc(ctx, func() {
			cond.L.Lock()
			defer cond.L.Unlock()
			select {
			case <-done:
				// Уже завершились, не нужно broadcast
				return
			default:
				// Пробуждаем все ожидающие горутины
				cond.Broadcast()
			}
		})
		defer stopf()

		// Ожидаем выполнения условия
		for !conditionMet() {
			// Проверяем контекст перед ожиданием
			if ctx.Err() != nil {
				return ctx.Err()
			}
			cond.Wait()
			// Проверяем контекст после пробуждения
			if ctx.Err() != nil {
				return ctx.Err()
			}
		}

		return nil
	}

	cond := sync.NewCond(&sync.Mutex{})

	var wg sync.WaitGroup
	start := time.Now()
	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()

			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
			defer cancel()

			cond.L.Lock()
			defer cond.L.Unlock()

			err := waitOnCond(ctx, cond, func() bool { return false })
			fmt.Printf("Goroutine %d finished after %v with error: %v\n", 
				id, time.Since(start), err)
		}(i)
	}

	wg.Wait()
	fmt.Println("All goroutines completed")
}
Пример Connection

В этом примере используется AfterFunc для определения функции, которая считывает данные из net.Conn, останавливая считывание при отмене контекста.

package main

import (
	"context"
	"fmt"
	"net"
	"time"
)

func readFromConn(ctx context.Context, conn net.Conn, b []byte) (n int, err error) {
	stopc := make(chan struct{})
	defer close(stopc) // Гарантируем закрытие канала

	// Устанавливаем функцию отмены чтения при отмене контекста
	stop := context.AfterFunc(ctx, func() {
		conn.SetReadDeadline(time.Now()) // Прерываем текущее чтение
		close(stopc)
	})
	defer func() {
		if !stop() {
			// Если AfterFunc был запущен, сбрасываем дедлайн
			<-stopc
			conn.SetReadDeadline(time.Time{})
		}
	}()

	n, err = conn.Read(b)
	if ctx.Err() != nil {
		return 0, ctx.Err() // Возвращаем ошибку контекста если он отменен
	}
	return n, err
}

func main() {
	// Создаем тестовый сервер
	listener, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		fmt.Println("Failed to create listener:", err)
		return
	}
	defer listener.Close()

	// Устанавливаем соединение
	conn, err := net.Dial(listener.Addr().Network(), listener.Addr().String())
	if err != nil {
		fmt.Println("Failed to dial:", err)
		return
	}
	defer conn.Close()

	// Создаем контекст с таймаутом
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
	defer cancel()

	// Читаем данные
	b := make([]byte, 1024)
	_, err = readFromConn(ctx, conn, b)
	
	// Проверяем тип ошибки
	switch {
	case err == nil:
		fmt.Println("Read completed successfully")
	case ctx.Err() != nil:
		fmt.Println("Operation canceled:", ctx.Err())
	default:
		fmt.Println("Read error:", err)
	}
}

Дополнительные рекомендации:

  1. Таймауты соединения:

    conn.SetDeadline(time.Now().Add(30 * time.Second))
    
  2. Буферизация:

    bufReader := bufio.NewReader(conn)
    n, err = bufReader.Read(b)
    
  3. Повторные попытки:

    for retries := 0; retries < 3; retries++ {
        n, err = readFromConn(ctx, conn, b)
        if err == nil || !isTemporary(err) {
            break
        }
    }
    
  4. Логирование:

    log.Printf("Read %d bytes, error: %v", n, err)
    
Пример Merge

В этом примере AfterFunc используется для определения функции, которая объединяет сигналы отмены двух Контекстов.

package main

import (
	"context"
	"errors"
	"fmt"
	"sync"
)

func main() {
	// mergeCancel возвращает контекст, который отменяется при отмене любого из исходных контекстов
	mergeCancel := func(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) {
		mergedCtx, cancel := context.WithCancelCause(ctx1)
		var once sync.Once

		// Отслеживаем отмену первого контекста
		go func() {
			select {
			case <-ctx1.Done():
				once.Do(func() {
					cancel(context.Cause(ctx1))
				})
			case <-ctx2.Done():
				once.Do(func() {
					cancel(context.Cause(ctx2))
				})
			case <-mergedCtx.Done():
				// Уже отменен другим путем
			}
		}()

		return mergedCtx, func() {
			once.Do(func() {
				cancel(context.Canceled)
			})
		}
	}

	// Создаем два контекста с возможностью отмены
	ctx1, cancel1 := context.WithCancelCause(context.Background())
	defer cancel1(errors.New("ctx1 canceled"))

	ctx2, cancel2 := context.WithCancelCause(context.Background())

	// Объединяем контексты
	mergedCtx, mergedCancel := mergeCancel(ctx1, ctx2)
	defer mergedCancel()

	// Отменяем второй контекст
	cancel2(errors.New("ctx2 canceled"))

	// Ждем отмены объединенного контекста
	<-mergedCtx.Done()
	
	// Выводим причину отмены
	fmt.Println("Merged context canceled because:", context.Cause(mergedCtx))
}

Ключевые моменты:

  1. Потокобезопасность:

    var once sync.Once
    once.Do(func() {
        cancel(context.Cause(ctx))
    })
    
  2. Полное отслеживание контекстов:

    select {
    case <-ctx1.Done():
    case <-ctx2.Done():
    case <-mergedCtx.Done():
    }
    
  3. Четкая причина отмены:

    fmt.Println("Merged context canceled because:", context.Cause(mergedCtx))
    
  4. Гарантированная очистка:

    defer mergedCancel()
    

func Cause

func Cause(c Context) error

Cause возвращает не нулевую ошибку, объясняющую, почему c был отменен. Первая отмена c или одного из его родителей устанавливает причину. Если эта отмена произошла через вызов CancelCauseFunc(err), то Cause возвращает err. В противном случае Cause(c) возвращает то же значение, что и c.Err(). Cause возвращает nil, если c еще не был отменен.

func WithCancel

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

WithCancel возвращает производный контекст, который указывает на родительский контекст, но имеет новый канал Done. Канал Done возвращенного контекста закрывается при вызове возвращенной функции отмены или при закрытии канала Done родительского контекста, в зависимости от того, что произойдет раньше.

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

Пример

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

package main

import (
	"context"
	"fmt"
	"sync"
)

func main() {
	// gen генерирует целые числа в отдельной горутине и
	// отправляет их в возвращаемый канал.
	// Вызывающая сторона должна отменить контекст после
	// завершения потребления чисел, чтобы избежать утечки горутины.
	gen := func(ctx context.Context) <-chan int {
		dst := make(chan int)
		var wg sync.WaitGroup
		wg.Add(1)

		go func() {
			defer wg.Done()
			defer close(dst) // Закрываем канал при завершении
			n := 1
			for {
				select {
				case <-ctx.Done():
					return
				case dst <- n:
					n++
				}
			}
		}()

		// Горутина для безопасного завершения
		go func() {
			wg.Wait()
		}()

		return dst
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // Отменяем контекст при завершении

	for n := range gen(ctx) {
		fmt.Println(n)
		if n == 5 {
			break
		}
	}

	// Дополнительная отмена контекста (хотя defer уже сделает это)
	cancel()
}

func WithCancelCause

func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)

WithCancelCause ведет себя как WithCancel, но возвращает CancelCauseFunc вместо CancelFunc. Вызов cancel с no-nil ошибкой (причина) записывает эту ошибку в ctx; затем ее можно получить с помощью Cause(ctx). Вызов cancel с nil устанавливает причину в Canceled.

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

ctx, cancel := context.WithCancelCause(parent)
cancel(myError)
ctx.Err() // возвращает context.Canceled
context.Cause(ctx) // возвращает myError

func WithDeadline

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

WithDeadline возвращает производный контекст, который указывает на родительский контекст, но имеет срок, скорректированный так, чтобы он не был позднее d. Если срок родительского контекста уже раньше d, WithDeadline(parent, d) семантически эквивалентен parent. Возвращаемый канал [Context.Done] закрывается по истечении срока, при вызове возвращаемой функции cancel или при закрытии канала Done родительского контекста, в зависимости от того, что произойдет раньше.

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

Пример

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

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	shortDuration := 50 * time.Millisecond // Явное указание времени таймаута
	d := time.Now().Add(shortDuration)
	
	// Создаем контекст с дедлайном
	ctx, cancel := context.WithDeadline(context.Background(), d)
	defer cancel() // Важно вызывать cancel для освобождения ресурсов

	neverReady := make(chan struct{}) // Канал, который никогда не будет готов

	select {
	case <-neverReady:
		fmt.Println("ready") // Эта ветка никогда не выполнится
	case <-ctx.Done():
		// Проверяем причину завершения контекста
		switch ctx.Err() {
		case context.DeadlineExceeded:
			fmt.Println("context deadline exceeded")
		case context.Canceled:
			fmt.Println("context canceled")
		default:
			fmt.Println("context done for unknown reason")
		}
	}
}

func WithDeadlineCause

func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)

WithDeadlineCause ведет себя как WithDeadline, но также устанавливает причину возвращаемого контекста при превышении срока. Возвращаемая CancelFunc не устанавливает причину.

func WithTimeout

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithTimeout возвращает WithDeadline(parent, time.Now().Add(timeout)).

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

func slowOperationWithTimeout(ctx context.Context) (Result, error) {
    ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
    defer cancel()  // освобождает ресурсы, если slowOperation завершается до истечения времени ожидания
	return slowOperation(ctx)
}
Пример

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

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	shortDuration := 100 * time.Millisecond // Явное указание времени таймаута

	// Создаем контекст с таймаутом
	ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
	defer cancel() // Всегда вызываем cancel для освобождения ресурсов

	neverReady := make(chan struct{}) // Создаем канал, который никогда не закроется

	select {
	case <-neverReady:
		fmt.Println("ready") // Эта ветка никогда не выполнится
	case <-ctx.Done():
		// Проверяем причину завершения контекста
		switch err := ctx.Err(); err {
		case context.DeadlineExceeded:
			fmt.Println("context deadline exceeded")
		case context.Canceled:
			fmt.Println("context canceled")
		default:
			fmt.Printf("context done: %v\n", err)
		}
	}
}

func WithTimeoutCause

func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)

WithTimeoutCause ведет себя как WithTimeout, но также устанавливает причину возвращаемого Context при истечении таймаута. Возвращаемый CancelFunc не устанавливает причину.

4.2 - Описание типов пакета Context

Описание типов пакета Context

type CancelCauseFunc

type CancelCauseFunc func(cause error)

CancelCauseFunc ведет себя как CancelFunc, но дополнительно устанавливает причину отмены. Эту причину можно получить, вызвав Cause на отмененном Context или на любом из его производных Context.

Если контекст уже был отменен, CancelCauseFunc не устанавливает причину. Например, если childContext является производным от parentContext:

  • если parentContext отменен с причиной cause1 до того, как childContext отменен с причиной cause2, то Cause(parentContext) == Cause(childContext) == cause1
  • если childContext отменен с причиной cause2 до того, как parentContext отменен с причиной cause1, то Cause(parentContext) == cause1 и Cause(childContext) == cause2

type CancelFunc

type CancelFunc func()

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

type Context

type Context interface {
    // Deadline возвращает время, когда работа, выполняемая от имени этого 
	// контекста должна быть отменена. Deadline возвращает ok==false, если 
    // срок не установлен. Последовательные вызовы Deadline возвращают 
	// одинаковые результаты.
    Deadline() (deadline time.Time, ok bool)

    // Done возвращает канал, который закрывается, когда работа, выполняемая 
    // от имени этого контекста, должна быть отменена. Done может возвращать 
    // nil, если этот контекст никогда не может быть отменен. Последовательные 
	// вызовы Done возвращают одинаковое значение.
	// Закрытие канала Done может происходить асинхронно,
    // после возврата функции cancel.
    //
    // WithCancel организует закрытие Done при вызове cancel;
    // WithDeadline организует закрытие Done по истечении срока;
    // WithTimeout организует закрытие Done по истечении таймаута
    //.
    //
	// Done предоставляется для использования в операторах select:
    //
    //  // Stream генерирует значения с помощью DoSomething и отправляет их в out,
    //  // пока DoSomething не вернет ошибку или ctx.Done не будет закрыт.
    //  func Stream(ctx context.Context, out chan<- Value) error {
    //      for {
    //          v, err := DoSomething(ctx)
	//          if err != nil {
    //              return err
    //          }
    //          select {
    //          case <-ctx.Done():
    //              return ctx.Err()
    //          case out <- v:
    //          }
    //      }
    //  }
    //
	// См. https://blog.golang.org/pipelines для получения дополнительных примеров 
	// использования канала Done для отмены.
    Done() <-chan struct{}

    // Если Done еще не закрыт, Err возвращает nil.
    // Если Done закрыт, Err возвращает no-nil ошибку, объясняющую причину:
    // DeadlineExceeded, если срок контекста истек, или Canceled, если контекст 
	// был отменен по какой-либо другой причине.
    // После того, как Err возвращает ошибку, отличную от nil, последующие вызовы 
	// Err возвращают ту же ошибку.
    Err() error

    // Value возвращает значение, связанное с этим контекстом для ключа, или nil,
    // если с ключом не связано никакое значение. Последовательные вызовы Value с
    // одним и тем же ключом возвращают один и тот же результат.
    //
	// Используйте значения контекста только для данных в рамках запроса, которые 
	// проходят через процессы и границы API, а не для передачи опциональных 
	// параметров в функции.
    //
    // Ключ идентифицирует конкретное значение в контексте. Функции, которые хотят
    // сохранить значения в контексте, обычно выделяют ключ в глобальной
    // переменной, а затем используют этот ключ в качестве аргумента для 
	// context.WithValue и Context.Value. Ключ может быть любого типа, который 
	// поддерживает равенство;
    // пакеты должны определять ключи как неэкспортируемый тип, чтобы избежать коллизий.
    //
    // Пакеты, которые определяют ключ контекста, должны предоставлять типобезопасные 
	// аксессоры для значений, хранящихся с использованием этого ключа:
    //
    //     // Пользователь пакета определяет тип User, который хранится в контекстах.
	//     пакет user
    //
    //     import «context»
    //
    //     // User — это тип значения, хранящегося в Contexts.
    //     type User struct {...}
    //
    //     // key — это неэкспортируемый тип для ключей, определенных в этом пакете.
    //     // Это предотвращает конфликты с ключами, определенными в других пакетах.
	//     type key int
    //
    //     // userKey — ключ для значений user.User в Contexts. Он
    //     // не экспортируется; клиенты используют user.NewContext и user.FromContext
    //     // вместо прямого использования этого ключа.
    //     var userKey key
    //
    //     // NewContext возвращает новый Context, который несет значение u.
	//     func NewContext(ctx context.Context, u *User) context.Context {
    //         return context.WithValue(ctx, userKey, u)
    //     }
    //
    //     // FromContext возвращает значение User, хранящееся в ctx, если оно есть.
    //     func FromContext(ctx context.Context) (*User, bool) {
	//         u, ok := ctx.Value(userKey).(*User)
    //         return u, ok
    //     }
    Value(key any) any
}

Context несет в себе срок, сигнал отмены и другие значения через границы API.

Методы контекста могут вызываться несколькими goroutines одновременно.

func Background

func Background() Context

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

func TODO

func TODO() Context

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

func WithValue

func WithValue(parent Context, key, val any) Context

WithValue возвращает производный контекст, который указывает на родительский Context. В производном контексте значение, связанное с ключом, является val.

Используйте контекстные значения только для данных в рамках запроса, которые проходят через процессы и API, а не для передачи опциональных параметров функциям.

Предоставленный ключ должен быть сопоставимым и не должен быть типом string или любым другим встроенным типом, чтобы избежать конфликтов между пакетами, использующими контекст. Пользователи WithValue должны определять свои собственные типы для ключей. Чтобы избежать выделения памяти при присвоении interface {}, ключи контекста часто имеют конкретный тип struct {}. В качестве альтернативы статический тип экспортированных переменных ключей контекста должен быть указателем или интерфейсом.

Пример

Этот пример демонстрирует, как можно передать значение в контекст, а также как получить его, если оно существует.

package main

import (
	"context"
	"fmt"
)

func main() {
	// Определяем пользовательский тип для ключей контекста
	type favContextKey string

	// Функция для поиска значения в контексте
	lookupValue := func(ctx context.Context, k favContextKey) {
		if v := ctx.Value(k); v != nil {
			fmt.Printf("Found value for key '%s': %v\n", k, v)
			return
		}
		fmt.Printf("Key '%s' not found in context\n", k)
	}

	// Создаем ключ и контекст со значением
	languageKey := favContextKey("language")
	ctx := context.WithValue(context.Background(), languageKey, "Go")

	// Ищем существующее значение
	lookupValue(ctx, languageKey)

	// Ищем несуществующее значение
	colorKey := favContextKey("color")
	lookupValue(ctx, colorKey)

	// Дополнительная проверка с другим типом (демонстрация безопасности)
	otherKey := "otherKey" // обычная строка, не favContextKey
	lookupValue(ctx, favContextKey(otherKey)) // конвертируем в правильный тип
}

Дополнительные рекомендации:

  1. Для production-кода:
// Лучше выносить ключи в package-level константы
const (
	LanguageKey favContextKey = "language"
	ColorKey    favContextKey = "color"
)
  1. Добавить проверку типа значения:
if v, ok := ctx.Value(k).(string); ok {
    fmt.Printf("Found string value: %s\n", v)
}
  1. Для сложных данных использовать указатели:
type configKey struct{}
ctx = context.WithValue(ctx, configKey{}, &MyConfig{...})

func WithoutCancel

func WithoutCancel(parent Context) Context

WithoutCancel возвращает производный контекст, который указывает на родительский контекст и не отменяется при отмене родительского контекста. Возвращаемый контекст не возвращает Deadline или Err, а его канал Done равен nil. Вызов Cause на возвращаемом контексте возвращает nil.

5 - Пакет bytes языка программирования Go

Пакет bytes реализует функции для работы с байтовыми фрагментами. Он аналогичен средствам пакета strings.

Константы

const MinRead = 512

MinRead — минимальный размер фрагмента, передаваемый вызову Buffer.Read из Buffer.ReadFrom. Если буфер содержит не менее MinRead байт сверх того, что требуется для хранения содержимого r, Buffer.ReadFrom не будет увеличивать базовый буфер.

Переменные

var ErrTooLarge = errors.New("bytes.Buffer: too large")

ErrTooLarge передается в panic, если не удается выделить память для хранения данных в буфере.

Функции

func Clone

func Clone(b []byte) []byte

Clone возвращает копию b[:len(b)]. Результат может иметь дополнительный неиспользованный объем. Clone(nil) возвращает nil.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	b := []byte("abc")
	clone := bytes.Clone(b)
	fmt.Printf("%s\n", clone)
	clone[0] = 'd'
	fmt.Printf("%s\n", b)
	fmt.Printf("%s\n", clone)
}
Output:

abc
abc
dbc

func Compare

 func Compare(a, b []byte) int

Compare возвращает целое число, сравнивая два лексикографических фрагмента байтов. Результат будет равен 0, если a == b, -1, если a < b, и +1, если a > b. Аргумент nil эквивалентен пустому фрагменту.

Пример
package main

import (
	"bytes"
)

func main() {
	// Interpret Compare's result by comparing it to zero.
	var a, b []byte
	if bytes.Compare(a, b) < 0 {
		// a less b
	}
	if bytes.Compare(a, b) <= 0 {
		// a less or equal b
	}
	if bytes.Compare(a, b) > 0 {
		// a greater b
	}
	if bytes.Compare(a, b) >= 0 {
		// a greater or equal b
	}

	// Prefer Equal to Compare for equality comparisons.
	if bytes.Equal(a, b) {
		// a equal b
	}
	if !bytes.Equal(a, b) {
		// a not equal b
	}
}
Пример Поиск
package main

import (
	"bytes"
	"slices"
)

func main() {
	// Binary search to find a matching byte slice.
	var needle []byte
	var haystack [][]byte // Assume sorted
	_, found := slices.BinarySearchFunc(haystack, needle, bytes.Compare)
	if found {
		// Found it!
	}
}

func Contains

 func Contains(b, subslice []byte) bool

Contains сообщает, находится ли подфрагмент в пределах b.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.Contains([]byte("seafood"), []byte("foo")))
	fmt.Println(bytes.Contains([]byte("seafood"), []byte("bar")))
	fmt.Println(bytes.Contains([]byte("seafood"), []byte("")))
	fmt.Println(bytes.Contains([]byte(""), []byte("")))
}
Output:

true
false
true
true

func ContainsAny

 func ContainsAny(b []байт, chars string) bool

ContainsAny сообщает, находится ли какая-либо из кодовых точек в кодировке UTF-8 в chars в пределах b.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.ContainsAny([]byte("I like seafood."), "fÄo!"))
	fmt.Println(bytes.ContainsAny([]byte("I like seafood."), "去是伟大的."))
	fmt.Println(bytes.ContainsAny([]byte("I like seafood."), ""))
	fmt.Println(bytes.ContainsAny([]byte(""), ""))
}
Output:

true
true
false
false

func ContainsFunc

 func ContainsFunc(b []byte, f func(rune) bool) bool

ContainsFunc сообщает, удовлетворяет ли f(r) любая из кодовых точек r в кодировке UTF-8 в пределах b.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	f := func(r rune) bool {
		return r >= 'a' && r <= 'z'
	}
	fmt.Println(bytes.ContainsFunc([]byte("HELLO"), f))
	fmt.Println(bytes.ContainsFunc([]byte("World"), f))
}
Output:

false
true

func ContainsRune

 func ContainsRune(b []byte, r rune) bool

ContainsRune сообщает, содержится ли руна в UTF-8-кодированном байтовом фрагменте b.

func Count

 func Count(s, sep []byte) int

Count подсчитывает количество непересекающихся экземпляров sep в s. Если sep - пустой фрагмент, Count возвращает 1 + количество кодовых точек в s, закодированных в UTF-8.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.Count([]byte("cheese"), []byte("e")))
	fmt.Println(bytes.Count([]byte("five"), []byte(""))) // before & after each rune
}
Output:

3
5

func Cut

 func Cut(s, sep []byte) (before, after []byte, found bool)

Cut разрезает s вокруг первого экземпляра sep, возвращая текст до и после sep. Результат found сообщает, появляется ли sep в s. Если sep не появляется в s, cut возвращает s, nil, false.

Cut возвращает фрагменты исходного фрагмента s, а не его копии.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	show := func(s, sep string) {
		before, after, found := bytes.Cut([]byte(s), []byte(sep))
		fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
	}
	show("Gopher", "Go")
	show("Gopher", "ph")
	show("Gopher", "er")
	show("Gopher", "Badger")
}
Output:

Cut("Gopher", "Go") = "", "pher", true
Cut("Gopher", "ph") = "Go", "er", true
Cut("Gopher", "er") = "Goph", "", true
Cut("Gopher", "Badger") = "Gopher", "", false

func CutPrefix

 func CutPrefix(s, prefix []byte) (after []byte, found bool)

CutPrefix возвращает s без предоставленного ведущего префиксного байтового фрагмента и сообщает, найден ли префикс. Если s не начинается с префикса, CutPrefix возвращает s, false. Если префикс - это пустой байтовый фрагмент, CutPrefix возвращает s, true.

CutPrefix возвращает фрагменты исходного фрагмента s, а не его копии.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	show := func(s, sep string) {
		after, found := bytes.CutPrefix([]byte(s), []byte(sep))
		fmt.Printf("CutPrefix(%q, %q) = %q, %v\n", s, sep, after, found)
	}
	show("Gopher", "Go")
	show("Gopher", "ph")
}
Output:

CutPrefix("Gopher", "Go") = "pher", true
CutPrefix("Gopher", "ph") = "Gopher", false

func CutSuffix

 func CutSuffix(s, suffix []byte) (before []byte, found bool)

CutSuffix возвращает s без предоставленного завершающего суффикса байтового фрагмента и сообщает, найден ли суффикс. Если s не заканчивается суффиксом, CutSuffix возвращает s, false. Если суффикс - пустой байтовый фрагмент, CutSuffix возвращает s, true.

CutSuffix возвращает фрагменты исходного фрагмента s, а не его копии.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	show := func(s, sep string) {
		before, found := bytes.CutSuffix([]byte(s), []byte(sep))
		fmt.Printf("CutSuffix(%q, %q) = %q, %v\n", s, sep, before, found)
	}
	show("Gopher", "Go")
	show("Gopher", "er")
}
Output:

CutSuffix("Gopher", "Go") = "Gopher", false
CutSuffix("Gopher", "er") = "Goph", true

func Equal

 func Equal(a, b []byte) bool

Equal сообщает, имеют ли a и b одинаковую длину и содержат ли они одинаковые байты. Аргумент nil эквивалентен пустому фрагменту.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.Equal([]byte("Go"), []byte("Go")))
	fmt.Println(bytes.Equal([]byte("Go"), []byte("C++")))
}
Output:

true
false

func EqualFold

 func EqualFold(s, t []byte) bool

EqualFold сообщает, равны ли s и t, интерпретируемые как строки UTF-8, при простом преобразовании регистра Юникода, которое является более общей формой нечувствительности к регистру.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.EqualFold([]byte("Go"), []byte("go")))
}
Output:

true

func Fields

 func Fields(s []byte) [][]byte

Fields интерпретирует s как последовательность кодовых точек, закодированных в UTF-8. Он разделяет срез s вокруг каждого экземпляра одного или нескольких последовательных пробельных символов, как определено в unicode.IsSpace, возвращая срез подсрезов s или пустой срез, если s содержит только пробельные символы.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("Fields are: %q", bytes.Fields([]byte("  foo bar  baz   ")))
}
Output:

Fields are: ["foo" "bar" "baz"]

func FieldsFunc

 func FieldsFunc(s []byte, f func(rune) bool) [][]byte

FieldsFunc интерпретирует s как последовательность кодовых точек, закодированных в UTF-8. Он разбивает срез s на каждом проходе кодовых точек c, удовлетворяющих f(c), и возвращает срез подсрезов s. Если все кодовые точки в s удовлетворяют f(c) или len(s) == 0, возвращается пустой срез.

FieldsFunc не дает никаких гарантий относительно порядка вызова f(c) и предполагает, что f всегда возвращает одно и то же значение для данного c.

Пример
package main

import (
	"bytes"
	"fmt"
	"unicode"
)

func main() {
	f := func(c rune) bool {
		return !unicode.IsLetter(c) && !unicode.IsNumber(c)
	}
	fmt.Printf("Fields are: %q", bytes.FieldsFunc([]byte("  foo1;bar2,baz3..."), f))
}
Output:

Fields are: ["foo1" "bar2" "baz3"]

func FieldsFuncSeq

 func FieldsFuncSeq(s []byte, f func(rune) bool) iter.Seq[[]byte]

FieldsFuncSeq возвращает итератор над подфрагментами s, разбитыми по последовательностям кодовых точек Unicode, удовлетворяющих f(c). Итератор возвращает те же подфрагменты, которые были бы возвращены FieldsFunc(s), но без создания нового фрагмента, содержащего подфрагменты.

func FieldsSeq

 func FieldsSeq(s []byte) iter.Seq[[]byte]

FieldsSeq возвращает итератор по подсрезам s, разделённым пробельными символами (как определено в unicode.IsSpace). Итератор возвращает те же подсрезы, что и функция Fields(s), но без создания нового среза.

func HasPrefix

func HasPrefix(s, prefix []byte) bool

HasPrefix проверяет, начинается ли байтовый срез s с prefix.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.HasPrefix([]byte("Gopher"), []byte("Go")))
	fmt.Println(bytes.HasPrefix([]byte("Gopher"), []byte("C")))
	fmt.Println(bytes.HasPrefix([]byte("Gopher"), []byte("")))
Output:

true
false
true

func HasSuffix

func HasSuffix(s, suffix []byte) bool

HasSuffix проверяет, заканчивается ли байтовый срез s на suffix.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.HasSuffix([]byte("Amigo"), []byte("go")))
	fmt.Println(bytes.HasSuffix([]byte("Amigo"), []byte("O")))
	fmt.Println(bytes.HasSuffix([]byte("Amigo"), []byte("Ami")))
	fmt.Println(bytes.HasSuffix([]byte("Amigo"), []byte("")))
}
Output:

true
false
false
true

func Index

func Index(s, sep []byte) int

Index возвращает индекс первого вхождения sep в s или -1, если sep отсутствует в s.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.Index([]byte("chicken"), []byte("ken")))
	fmt.Println(bytes.Index([]byte("chicken"), []byte("dmr")))
}
Output:

4
-1

func IndexAny

func IndexAny(s []byte, chars string) int
Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.IndexAny([]byte("chicken"), "aeiouy"))
	fmt.Println(bytes.IndexAny([]byte("crwth"), "aeiouy"))
}

Output:

2
-1

IndexAny интерпретирует s как последовательность UTF-8 кодовых точек. Возвращает байтовый индекс первого вхождения любого символа из chars в s. Возвращает -1, если chars пуст или нет совпадений.

func IndexByte

func IndexByte(b []byte, c byte) int

IndexByte возвращает индекс первого вхождения байта c в b или -1, если c отсутствует в b.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.IndexByte([]byte("chicken"), byte('k')))
	fmt.Println(bytes.IndexByte([]byte("chicken"), byte('g')))
}
Output:

4
-1

func IndexFunc

func IndexFunc(s []byte, f func(r rune) bool) int

IndexFunc интерпретирует s как последовательность UTF-8 кодовых точек. Возвращает байтовый индекс первой кодовой точки, удовлетворяющей условию f(c), или -1, если таких нет.

Пример
package main

import (
	"bytes"
	"fmt"
	"unicode"
)

func main() {
	f := func(c rune) bool {
		return unicode.Is(unicode.Han, c)
	}
	fmt.Println(bytes.IndexFunc([]byte("Hello, 世界"), f))
	fmt.Println(bytes.IndexFunc([]byte("Hello, world"), f))
}
Output:

7
-1

func IndexRune

func IndexRune(s []byte, r rune) int

IndexRune интерпретирует s как последовательность UTF-8 кодовых точек. Возвращает байтовый индекс первого вхождения руны r. Если r — utf8.RuneError, возвращает первый индекс невалидной UTF-8 последовательности.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.IndexRune([]byte("chicken"), 'k'))
	fmt.Println(bytes.IndexRune([]byte("chicken"), 'd'))
}
Output:

4
-1

func Join

func Join(s [][]byte, sep []byte) []byte

Join объединяет элементы среза s в новый байтовый срез, разделяя их sep.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	s := [][]byte{[]byte("foo"), []byte("bar"), []byte("baz")}
	fmt.Printf("%s", bytes.Join(s, []byte(", ")))
}
Output:

foo, bar, baz

func LastIndex

func LastIndex(s, sep []byte) int
Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.Index([]byte("go gopher"), []byte("go")))
	fmt.Println(bytes.LastIndex([]byte("go gopher"), []byte("go")))
	fmt.Println(bytes.LastIndex([]byte("go gopher"), []byte("rodent")))
}
Output:

0
3
-1

LastIndex возвращает индекс последнего экземпляра sep в s или -1, если sep отсутствует в s.

func LastIndexAny

func LastIndexAny(s []byte, chars string) int

LastIndexAny интерпретирует s как последовательность кодовых точек Unicode в кодировке UTF-8. Возвращает индекс байта последнего вхождения в s любой из кодовых точек Unicode в chars. Возвращает -1, если chars пуст или если нет общей кодовой точки.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.LastIndexAny([]byte("go gopher"), "MüQp"))
	fmt.Println(bytes.LastIndexAny([]byte("go 地鼠"), "地大"))
	fmt.Println(bytes.LastIndexAny([]byte("go gopher"), "z,!."))
}
Output:

5
3
-1

func LastIndexByte

 func LastIndexByte(s []byte, c byte) int

LastIndexByte возвращает индекс последнего экземпляра c в s или -1, если c отсутствует в s.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.LastIndexByte([]byte("go gopher"), byte('g')))
	fmt.Println(bytes.LastIndexByte([]byte("go gopher"), byte('r')))
	fmt.Println(bytes.LastIndexByte([]byte("go gopher"), byte('z')))
}
Output:

3
8
-1

func LastIndexFunc

 func LastIndexFunc(s []byte, f func(r rune) bool) int

LastIndexFunc интерпретирует s как последовательность кодовых точек, закодированных в UTF-8. Он возвращает индекс байта в s последней кодовой точки Unicode, удовлетворяющей f(c), или -1, если таковой нет.

Пример
package main

import (
	"bytes"
	"fmt"
	"unicode"
)

func main() {
	fmt.Println(bytes.LastIndexFunc([]byte("go gopher!"), unicode.IsLetter))
	fmt.Println(bytes.LastIndexFunc([]byte("go gopher!"), unicode.IsPunct))
	fmt.Println(bytes.LastIndexFunc([]byte("go gopher!"), unicode.IsNumber))
}
Output:

8
9
-1

func Lines

 func Lines(s []byte) iter.Seq[[]byte]

Lines возвращает итератор по строкам, завершающимся символом новой строки, в байтовом срезе s. Строки, возвращаемые итератором, включают в себя завершающие символы новой строки. Если s пустой, итератор не возвращает никаких строк. Если s не заканчивается символом новой строки, последняя возвращаемая строка не будет заканчиваться символом новой строки. Возвращает итератор однократного использования.

func Map

 func Map(mapping func(r rune) rune, s []byte) []byte

Map возвращает копию байтового среза s со всеми символами, измененными в соответствии с функцией отображения. Если отображение возвращает отрицательное значение, символ удаляется из байтового среза без замены. Символы в s и выводе интерпретируются как кодовые точки, закодированные в UTF-8.

Пример
import (
	"bytes"
	"fmt"
)

func main() {
	rot13 := func(r rune) rune {
		switch {
		case r >= 'A' && r <= 'Z':
			return 'A' + (r-'A'+13)%26
		case r >= 'a' && r <= 'z':
			return 'a' + (r-'a'+13)%26
		}
		return r
	}
	fmt.Printf("%s\n", bytes.Map(rot13, []byte("'Twas brillig and the slithy gopher...")))
}
Output:

'Gjnf oevyyvt naq gur fyvgul tbcure...

func Repeat

 func Repeat(b []byte, count int) []byte

Repeat возвращает новый байтовый срез, состоящий из count копий b.

Он вызывает панику, если count отрицательно или если результат (len(b) * count) переполняется.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("ba%s", bytes.Repeat([]byte("na"), 2))
}
Output:

banana

func Replace

 func Replace(s, old, new []byte, n int) []byte

Replace возвращает копию фрагмента s с первыми n непересекающимися экземплярами old, замененными на new. Если old пуст, то он совпадает в начале фрагмента и после каждой последовательности UTF-8, что позволяет получить до k+1 замен для k-рангового фрагмента. Если n < 0, то количество замен не ограничено.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%s\n", bytes.Replace([]byte("oink oink oink"), []byte("k"), []byte("ky"), 2))
	fmt.Printf("%s\n", bytes.Replace([]byte("oink oink oink"), []byte("oink"), []byte("moo"), -1))
}
Output:

oinky oinky oink
moo moo moo

func ReplaceAll

 func ReplaceAll(s, old, new []byte) []byte

ReplaceAll возвращает копию фрагмента s со всеми непересекающимися экземплярами old, замененными на new. Если old пуст, то совпадение происходит в начале фрагмента и после каждой последовательности UTF-8, что позволяет получить до k+1 замен для фрагмента длиной в k строк.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%s\n", bytes.ReplaceAll([]byte("oink oink oink"), []byte("oink"), []byte("moo")))
}
Output:

moo moo moo

func Runes

 func Runes(s []byte) []rune

Runes интерпретирует s как последовательность кодовых точек в кодировке UTF-8. Она возвращает фрагмент рун (кодовых точек Юникода), эквивалентный s.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	rs := bytes.Runes([]byte("go gopher"))
	for _, r := range rs {
		fmt.Printf("%#U\n", r)
	}
}
Output:

U+0067 'g'
U+006F 'o'
U+0020 ' '
U+0067 'g'
U+006F 'o'
U+0070 'p'
U+0068 'h'
U+0065 'e'
U+0072 'r'

func Split

 func Split(s, sep []byte) [][]byte

Split разбивает s на все подфрагменты, разделенные sep, и возвращает фрагмент подфрагментов между этими разделителями. Если sep пуст, Split разделяет после каждой последовательности UTF-8. Это эквивалентно SplitN с числом -1.

Для разбиения вокруг первого экземпляра разделителя смотрите Cut.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%q\n", bytes.Split([]byte("a,b,c"), []byte(",")))
	fmt.Printf("%q\n", bytes.Split([]byte("a man a plan a canal panama"), []byte("a ")))
	fmt.Printf("%q\n", bytes.Split([]byte(" xyz "), []byte("")))
	fmt.Printf("%q\n", bytes.Split([]byte(""), []byte("Bernardo O'Higgins")))
}
Output:

["a" "b" "c"]
["" "man " "plan " "canal panama"]
[" " "x" "y" "z" " "]
[""]

func SplitAfter

 func SplitAfter(s, sep []byte) [][]byte

SplitAfter разбивает s на все подфрагменты после каждого экземпляра sep и возвращает фрагмент этих подфрагментов. Если sep пуст, SplitAfter разбивает после каждой последовательности UTF-8. Это эквивалентно SplitAfterN с количеством -1.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%q\n", bytes.SplitAfter([]byte("a,b,c"), []byte(",")))
}
Output:

["a," "b," "c"]

func SplitAfterN

 func SplitAfterN(s, sep []byte, n int) [][]byte

SplitAfterN разбивает s на подфрагменты после каждого экземпляра sep и возвращает фрагмент этих подфрагментов. Если sep пуст, SplitAfterN разбивает после каждой последовательности UTF-8. Параметр count определяет количество возвращаемых подфрагментов:

  • n > 0: не более n подфрагментов; последний подфрагмент будет нерасщепленным остатком;
  • n == 0: результат равен nil (нулевые подмножества);
  • n < 0: все поддоли.
Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%q\n", bytes.SplitAfterN([]byte("a,b,c"), []byte(","), 2))
}

Output:

["a," "b,c"]

func SplitAfterSeq

 func SplitAfterSeq(s, sep []byte) iter.Seq[[]byte]

SplitAfterSeq возвращает итератор по подфрагментам s, разделенным после каждого экземпляра sep. Итератор дает те же подфрагменты, которые были бы возвращены SplitAfter(s, sep), но без построения нового фрагмента, содержащего подфрагменты. Возвращается одноразовый итератор.

func SplitN

 func SplitN(s, sep []байт, n int) [][]байт

SplitN разбивает s на подфрагменты, разделенные sep, и возвращает фрагмент подфрагментов между этими разделителями. Если sep пуст, SplitN разбивает после каждой последовательности UTF-8. Параметр count определяет количество возвращаемых подфрагментов:

  • n > 0: не более n подфрагментов; последний подфрагмент будет нерасщепленным остатком;
  • n == 0: результат равен nil (нулевые подмножества);
  • n < 0: все подфрагменты.

Для разбиения вокруг первого экземпляра разделителя смотрите Cut.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%q\n", bytes.SplitN([]byte("a,b,c"), []byte(","), 2))
	z := bytes.SplitN([]byte("a,b,c"), []byte(","), 0)
	fmt.Printf("%q (nil = %v)\n", z, z == nil)
}
Output:

["a" "b,c"]
[] (nil = true)

func SplitSeq

 func SplitSeq(s, sep []byte) iter.Seq[[]byte]

SplitSeq возвращает итератор по всем подфрагментам s, разделенным sep. Итератор дает те же подфрагменты, которые были бы возвращены функцией Split(s, sep), но без построения нового фрагмента, содержащего эти подфрагменты. Возвращается одноразовый итератор.

func Title (устарело)

func ToLower

 func ToLower(s []byte) []byte

ToLower возвращает копию байтового фрагмента s со всеми буквами Юникода, переведенными в нижний регистр.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%s", bytes.ToLower([]byte("Gopher")))
}
Output:

gopher

func ToLowerSpecial

 func ToLowerSpecial(c unicode.SpecialCase, s []byte) []byte

ToLowerSpecial обрабатывает s как байт в кодировке UTF-8 и возвращает копию со всеми буквами Юникода, переведенными в нижний регистр, отдавая приоритет правилам специального регистра.

Пример
package main

import (
	"bytes"
	"fmt"
	"unicode"
)

func main() {
	str := []byte("AHOJ VÝVOJÁRİ GOLANG")
	totitle := bytes.ToLowerSpecial(unicode.AzeriCase, str)
	fmt.Println("Original : " + string(str))
	fmt.Println("ToLower : " + string(totitle))
}
Output:

Original : AHOJ VÝVOJÁRİ GOLANG
ToLower : ahoj vývojári golang

func ToTitle

 func ToTitle(s []byte) []byte

ToTitle обрабатывает s как байт в кодировке UTF-8 и возвращает его копию со всеми буквами Юникода, отображенными в регистр заголовка.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%s\n", bytes.ToTitle([]byte("loud noises")))
	fmt.Printf("%s\n", bytes.ToTitle([]byte("брат")))
}
Output:

LOUD NOISES
БРАТ

func ToTitleSpecial

 func ToTitleSpecial(c unicode.SpecialCase, s []byte) []byte

ToTitleSpecial обрабатывает s как байты в кодировке UTF-8 и возвращает копию со всеми буквами Юникода, отображенными в их заглавный регистр, отдавая приоритет правилам специального регистра.

Пример
package main

import (
	"bytes"
	"fmt"
	"unicode"
)

func main() {
	str := []byte("ahoj vývojári golang")
	totitle := bytes.ToTitleSpecial(unicode.AzeriCase, str)
	fmt.Println("Original : " + string(str))
	fmt.Println("ToTitle : " + string(totitle))
}
Output:

Original : ahoj vývojári golang
ToTitle : AHOJ VÝVOJÁRİ GOLANG

func ToUpper

 func ToUpper(s []byte) []byte

ToUpper возвращает копию байтового фрагмента s со всеми буквами Юникода, переведенными в верхний регистр.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%s", bytes.ToUpper([]byte("Gopher")))
}
Output:

GOPHER

func ToUpperSpecial

 func ToUpperSpecial(c unicode.SpecialCase, s []byte) []byte

ToUpperSpecial обрабатывает s как байт в кодировке UTF-8 и возвращает копию со всеми буквами Юникода, переведенными в верхний регистр, отдавая приоритет правилам специального регистра.

Пример
package main

import (
	"bytes"
	"fmt"
	"unicode"
)

func main() {
	str := []byte("ahoj vývojári golang")
	totitle := bytes.ToUpperSpecial(unicode.AzeriCase, str)
	fmt.Println("Original : " + string(str))
	fmt.Println("ToUpper : " + string(totitle))
}
Output:

Original : ahoj vývojári golang
ToUpper : AHOJ VÝVOJÁRİ GOLANG

func ToValidUTF8

 func ToValidUTF8(s, replacement []byte) []byte

ToValidUTF8 обрабатывает s как байты в кодировке UTF-8 и возвращает копию, в которой каждый ряд байтов, представляющих недопустимый UTF-8, заменен байтами в replacement, которая может быть пустой.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%s\n", bytes.ToValidUTF8([]byte("abc"), []byte("\uFFFD")))
	fmt.Printf("%s\n", bytes.ToValidUTF8([]byte("a\xffb\xC0\xAFc\xff"), []byte("")))
	fmt.Printf("%s\n", bytes.ToValidUTF8([]byte("\xed\xa0\x80"), []byte("abc")))
}
Output:

abc
abc
abc

func Trim

 func Trim(s []байт, cutset string) []байт

Trim возвращает подфрагмент s, отсекая все ведущие и последующие кодовые точки в кодировке UTF-8, содержащиеся в cutset.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("[%q]", bytes.Trim([]byte(" !!! Achtung! Achtung! !!! "), "! "))
}
Output:

["Achtung! Achtung"]

func TrimFunc

 func TrimFunc(s []byte, f func(r rune) bool) []byte

TrimFunc возвращает подфрагмент s, отсекая все ведущие и последующие точки кода c в кодировке UTF-8, которые удовлетворяют f(c).

Пример
package main

import (
	"bytes"
	"fmt"
	"unicode"
)

func main() {
	fmt.Println(string(bytes.TrimFunc([]byte("go-gopher!"), unicode.IsLetter)))
	fmt.Println(string(bytes.TrimFunc([]byte("\"go-gopher!\""), unicode.IsLetter)))
	fmt.Println(string(bytes.TrimFunc([]byte("go-gopher!"), unicode.IsPunct)))
	fmt.Println(string(bytes.TrimFunc([]byte("1234go-gopher!567"), unicode.IsNumber)))
}
Output:

-gopher!
"go-gopher!"
go-gopher
go-gopher!

func TrimLeft

 func TrimLeft(s []байт, cutset string) []байт

TrimLeft возвращает подфрагмент s, отсекая все ведущие точки кода в кодировке UTF-8, содержащиеся в cutset.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Print(string(bytes.TrimLeft([]byte("453gopher8257"), "0123456789")))
}
Output:

gopher8257

func TrimLeftFunc

 func TrimLeftFunc(s []байт, f func(r rune) bool) []байт

TrimLeftFunc рассматривает s как байт в кодировке UTF-8 и возвращает подфрагмент s, отрезая все ведущие кодовые точки c в кодировке UTF-8, которые удовлетворяют f(c).

Пример
package main

import (
	"bytes"
	"fmt"
	"unicode"
)

func main() {
	fmt.Println(string(bytes.TrimLeftFunc([]byte("go-gopher"), unicode.IsLetter)))
	fmt.Println(string(bytes.TrimLeftFunc([]byte("go-gopher!"), unicode.IsPunct)))
	fmt.Println(string(bytes.TrimLeftFunc([]byte("1234go-gopher!567"), unicode.IsNumber)))
}
Output:

-gopher
go-gopher!
go-gopher!567

func TrimPrefix

 func TrimPrefix(s, prefix []byte) []byte

TrimPrefix возвращает s без предоставленной ведущей строки префикса. Если s не начинается с префикса, s возвращается без изменений.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	var b = []byte("Goodbye,, world!")
	b = bytes.TrimPrefix(b, []byte("Goodbye,"))
	b = bytes.TrimPrefix(b, []byte("See ya,"))
	fmt.Printf("Hello%s", b)
}
Output:

Hello, world!

func TrimRight

 func TrimRight(s []байт, cutset string) []байт

TrimRight возвращает подфрагмент s, отрезая все кодовые точки в кодировке UTF-8, которые содержатся в cutset.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Print(string(bytes.TrimRight([]byte("453gopher8257"), "0123456789")))
}
Output:

453gopher

func TrimRightFunc

 func TrimRightFunc(s []byte, f func(r rune) bool) []byte

TrimRightFunc возвращает подфрагмент s, отсекая все кодовые точки c, которые удовлетворяют f(c).

Пример
package main

import (
	"bytes"
	"fmt"
	"unicode"
)

func main() {
	fmt.Println(string(bytes.TrimRightFunc([]byte("go-gopher"), unicode.IsLetter)))
	fmt.Println(string(bytes.TrimRightFunc([]byte("go-gopher!"), unicode.IsPunct)))
	fmt.Println(string(bytes.TrimRightFunc([]byte("1234go-gopher!567"), unicode.IsNumber)))
}
Output:

go-
go-gopher
1234go-gopher!

func TrimSpace

 func TrimSpace(s []byte) []byte

TrimSpace возвращает подфрагмент s, отсекая все ведущие и последующие белые пробелы, как определено Unicode.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Printf("%s", bytes.TrimSpace([]byte(" \t\n a lone gopher \n\t\r\n")))
}
Output:

a lone gopher

func TrimSuffix

 func TrimSuffix(s, suffix []byte) []byte

TrimSuffix возвращает s без предоставленной строки суффикса. Если s не заканчивается суффиксом, s возвращается без изменений.

Пример
package main

import (
	"bytes"
	"os"
)

func main() {
	var b = []byte("Hello, goodbye, etc!")
	b = bytes.TrimSuffix(b, []byte("goodbye, etc!"))
	b = bytes.TrimSuffix(b, []byte("gopher"))
	b = append(b, bytes.TrimSuffix([]byte("world!"), []byte("x!"))...)
	os.Stdout.Write(b)
}
Output:

Hello, world!

Типы

type Buffer

type Buffer struct {
	// содержит отфильтрованные или неэкспонированные поля
}

Буфер - это буфер переменного размера из байтов с методами Buffer.Read и Buffer.Write. Нулевое значение для Buffer - это пустой буфер, готовый к использованию.

Пример
package main

import (
	"bytes"
	"fmt"
	"os"
)

func main() {
	var b bytes.Buffer // A Buffer needs no initialization.
	b.Write([]byte("Hello "))
	fmt.Fprintf(&b, "world!")
	b.WriteTo(os.Stdout)
}
Output:

Hello world!
Пример Reader
package main

import (
	"bytes"
	"encoding/base64"
	"io"
	"os"
)

func main() {
	// A Buffer can turn a string or a []byte into an io.Reader.
	buf := bytes.NewBufferString("R29waGVycyBydWxlIQ==")
	dec := base64.NewDecoder(base64.StdEncoding, buf)
	io.Copy(os.Stdout, dec)
}
Output:

Gophers rule!

func NewBuffer

func NewBuffer(buf []byte) *Buffer

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

В большинстве случаев для инициализации буфера достаточно использовать new(Buffer) (или просто объявить переменную Buffer).

func NewBufferString

 func NewBufferString(s string) *Buffer

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

В большинстве случаев для инициализации буфера достаточно использовать new(Buffer) (или просто объявить переменную Buffer).

func (*Buffer) Available

 func (b *Buffer) Available() int

Available возвращает количество неиспользованных байт в буфере.

func (*Buffer) AvailableBuffer

 func (b *Buffer) AvailableBuffer() []byte

AvailableBuffer возвращает пустой буфер с емкостью b.Available(). Этот буфер предназначен для

Пример
package main

import (
	"bytes"
	"os"
	"strconv"
)

func main() {
	var buf bytes.Buffer
	for i := 0; i < 4; i++ {
		b := buf.AvailableBuffer()
		b = strconv.AppendInt(b, int64(i), 10)
		b = append(b, ' ')
		buf.Write(b)
	}
	os.Stdout.Write(buf.Bytes())
}
Output:

0 1 2 3

func (*Buffer) Bytes

 func (b *Buffer) Bytes() []byte

Bytes возвращает фрагмент длины b.Len(), содержащий непрочитанную часть буфера. Этот фрагмент действителен для использования только до следующего изменения буфера (то есть только до следующего вызова метода типа Buffer.Read, Buffer.Write, Buffer.Reset или Buffer.Truncate). Слайс псевдоним содержимого буфера, по крайней мере, до следующей модификации буфера, поэтому немедленные изменения в слайсе повлияют на результат последующих чтений.

Пример
package main

import (
	"bytes"
	"os"
)

func main() {
	buf := bytes.Buffer{}
	buf.Write([]byte{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'})
	os.Stdout.Write(buf.Bytes())
}
Output:

hello world

func (*Buffer) Cap

 func (b *Buffer) Cap() int

Cap возвращает емкость базового байтового среза буфера, то есть общее пространство, выделенное для данных буфера.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	buf1 := bytes.NewBuffer(make([]byte, 10))
	buf2 := bytes.NewBuffer(make([]byte, 0, 10))
	fmt.Println(buf1.Cap())
	fmt.Println(buf2.Cap())
}
Output:

10
10

func (*Buffer) Grow

 func (b *Buffer) Grow(n int)

Grow увеличивает емкость буфера, если необходимо, чтобы гарантировать место для еще n байт. После Grow(n) в буфер может быть записано не менее n байт без дополнительного выделения. Если n отрицательно, Grow паникует. Если буфер не может вырасти, то произойдет паника с ошибкой ErrTooLarge.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	var b bytes.Buffer
	b.Grow(64)
	bb := b.Bytes()
	b.Write([]byte("64 bytes or fewer"))
	fmt.Printf("%q", bb[:b.Len()])
}
Output:

"64 bytes or fewer"

func (*Buffer) Len

 func (b *Buffer) Len() int

Len возвращает количество байт непрочитанной части буфера; b.Len() == len(b.Bytes()).

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	var b bytes.Buffer
	b.Grow(64)
	b.Write([]byte("abcde"))
	fmt.Printf("%d", b.Len())
}
Output:

5

func (*Buffer) Next

 func (b *Buffer) Next(n int) []byte

Next возвращает фрагмент, содержащий следующие n байт из буфера, продвигая буфер так, как если бы байты были возвращены функцией Buffer.Read. Если в буфере меньше n байт, Next возвращает весь буфер. Фрагмент действителен только до следующего вызова метода чтения или записи.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	var b bytes.Buffer
	b.Grow(64)
	b.Write([]byte("abcde"))
	fmt.Printf("%s\n", b.Next(2))
	fmt.Printf("%s\n", b.Next(2))
	fmt.Printf("%s", b.Next(2))
}
Output:

ab
cd
e

func (*Buffer) Read

 func (b *Buffer) Read(p []byte) (n int, err error)

Read считывает следующие len(p) байт из буфера или пока буфер не будет исчерпан. Возвращаемое значение n - количество прочитанных байт. Если в буфере нет данных для возврата, err будет io.EOF (если только len(p) не равно нулю); в противном случае это будет nil.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	var b bytes.Buffer
	b.Grow(64)
	b.Write([]byte("abcde"))
	rdbuf := make([]byte, 1)
	n, err := b.Read(rdbuf)
	if err != nil {
		panic(err)
	}
	fmt.Println(n)
	fmt.Println(b.String())
	fmt.Println(string(rdbuf))
}
Output:

1
bcde
a

func (*Buffer) ReadByte

 func (b *Buffer) ReadByte() (byte, error)

ReadByte считывает и возвращает следующий байт из буфера. Если ни один байт не доступен, возвращается ошибка io.EOF.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	var b bytes.Buffer
	b.Grow(64)
	b.Write([]byte("abcde"))
	c, err := b.ReadByte()
	if err != nil {
		panic(err)
	}
	fmt.Println(c)
	fmt.Println(b.String())
}
Output:

97
bcde

func (*Buffer) ReadBytes

 func (b *Buffer) ReadBytes(delim byte) (line []byte, err error)

ReadBytes читает до первого появления delim во входных данных, возвращая фрагмент, содержащий данные до разделителя включительно. Если ReadBytes встречает ошибку до нахождения разделителя, он возвращает данные, прочитанные до ошибки, и саму ошибку (часто io.EOF). ReadBytes возвращает err != nil тогда и только тогда, когда возвращаемые данные не заканчиваются на delim.

func (*Buffer) ReadFrom

 func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error)

ReadFrom читает данные из r до EOF и добавляет их в буфер, увеличивая буфер по мере необходимости. Возвращаемое значение n - это количество прочитанных байт. Также возвращается любая ошибка, кроме io.EOF, возникшая во время чтения. Если буфер становится слишком большим, ReadFrom паникует с сообщением ErrTooLarge.

func (*Buffer) ReadRune

 func (b *Buffer) ReadRune() (r rune, size int, err error)

ReadRune считывает и возвращает следующую кодовую точку Юникода в кодировке UTF-8 из буфера. Если байтов нет, возвращается ошибка io.EOF. Если байты имеют ошибочную кодировку UTF-8, то считывается один байт и возвращается U+FFFD, 1.

func (*Buffer) ReadString

 func (b *Buffer) ReadString(delim byte) (line string, err error)

ReadString читает до первого появления delim во входных данных, возвращая строку, содержащую данные до разделителя включительно. Если ReadString встречает ошибку до нахождения разделителя, она возвращает данные, считанные до ошибки, и саму ошибку (часто io.EOF). ReadString возвращает err != nil тогда и только тогда, когда возвращаемые данные не заканчиваются разделителем.

func (*Buffer) Reset

 func (b *Buffer) Сброс()

Сброс приводит к тому, что буфер становится пустым, но сохраняет базовое хранилище для использования в будущих записях. Сброс - это то же самое, что и Buffer.Truncate(0).

func (*Buffer) String

 func (b *Buffer) String() string

String возвращает содержимое непрочитанной части буфера в виде строки. Если буфер является нулевым указателем, возвращается «».

Для более эффективного создания строк см. тип strings.Builder.

func (*Buffer) Truncate

 func (b *Buffer) Truncate(n int)

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

func (*Buffer) UnreadByte

 func (b *Buffer) UnreadByte() error

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

func (*Buffer) UnreadRune

 func (b *Buffer) UnreadRune() error

UnreadRune считывает последнюю руну, возвращенную Buffer.ReadRune. Если последняя операция чтения или записи в буфер не была успешной Buffer.ReadRune, UnreadRune возвращает ошибку. (В этом отношении она строже, чем Buffer.UnreadByte, которая отменяет чтение последнего байта из любой операции чтения).

func (*Buffer) Write

 func (b *Buffer) Write(p []byte) (n int, err error)

Write добавляет содержимое p в буфер, увеличивая буфер по мере необходимости. Возвращаемое значение n - длина p; err всегда равно nil. Если буфер становится слишком большим, Write паникует с сообщением ErrTooLarge.

func (*Buffer) WriteByte

 func (b *Buffer) WriteByte(c byte) error

WriteByte добавляет байт c в буфер, увеличивая буфер по мере необходимости. Возвращаемая ошибка всегда равна nil, но она включается, чтобы соответствовать WriteByte от bufio.Writer. Если буфер становится слишком большим, WriteByte паникует с сообщением ErrTooLarge.

func (*Buffer) WriteRune

 func (b *Buffer) WriteRune(r rune) (n int, err error)

WriteRune добавляет кодировку UTF-8 кодовой точки Юникода r к буферу, возвращая его длину и ошибку, которая всегда равна nil, но включается для соответствия WriteRune от bufio.Writer. Буфер увеличивается по мере необходимости; если он становится слишком большим, WriteRune паникует с сообщением ErrTooLarge.

func (*Buffer) WriteString

 func (b *Buffer) WriteString(s string) (n int, err error)

WriteString добавляет содержимое s в буфер, увеличивая буфер по мере необходимости. Возвращаемое значение n - длина s; err всегда равно nil. Если буфер становится слишком большим, WriteString паникует с сообщением ErrTooLarge.

func (*Buffer) WriteTo

 func (b *Buffer) WriteTo(w io.Writer) (n int64, err error)

WriteTo записывает данные в w до тех пор, пока буфер не будет исчерпан или не произойдет ошибка. Возвращаемое значение n - это количество записанных байт; оно всегда помещается в int, но для соответствия интерфейсу io.WriterTo оно равно int64. Также возвращается любая ошибка, возникшая во время записи.

type Reader

type Reader struct {
	// содержит отфильтрованные или неотфильтрованные поля
}

Reader реализует интерфейсы io.Reader, io.ReaderAt, io.WriterTo, io.Seeker, io.ByteScanner и io.RuneScanner, читая из байтового фрагмента. В отличие от буфера, Reader доступен только для чтения и поддерживает поиск. Нулевое значение для Reader работает как Reader пустого фрагмента.

func NewReader

 func NewReader(b []byte) *Reader

NewReader возвращает новый Reader, читающий из b.

func (*Reader) Len

 func (r *Reader) Len() int

Len возвращает количество байт непрочитанной части фрагмента.

Пример
package main

import (
	"bytes"
	"fmt"
)

func main() {
	fmt.Println(bytes.NewReader([]byte("Hi!")).Len())
	fmt.Println(bytes.NewReader([]byte("こんにちは!")).Len())
}
Output:

3
16

func (*Reader) Read

 func (r *Reader) Read(b []byte) (n int, err error)

Read реализует интерфейс io.Reader.

func (*Reader) ReadAt

 func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)

ReadAt реализует интерфейс io.ReaderAt.

func (*Reader) ReadByte

 func (r *Reader) ReadByte() (byte, error)

ReadByte реализует интерфейс io.ByteReader.

func (*Reader) ReadRune

 func (r *Reader) ReadRune() (ch rune, size int, err error)

ReadRune реализует интерфейс io.RuneReader.

 func (*Reader) Reset 
 func (r *Reader) Reset(b []byte)

Сброс переводит ридер в режим чтения из b.

func (*Reader) Seek

 func (r *Reader) Seek(offset int64, whence int) (int64, error)

Seek реализует интерфейс io.Seeker.

func (*Reader) Size

 func (r *Reader) Size() int64

Size возвращает исходную длину базового байтового фрагмента. Size - это количество байт, доступных для чтения через Reader.ReadAt. На результат не влияют никакие вызовы методов, кроме Reader.Reset.

func (*Reader) UnreadByte

 func (r *Reader) UnreadByte() error

UnreadByte дополняет Reader.ReadByte в реализации интерфейса io.ByteScanner.

func (*Reader) UnreadRune

 func (r *Reader) UnreadRune() error

UnreadRune дополняет Reader.ReadRune в реализации интерфейса io.RuneScanner.

func (*Reader) WriteTo

 func (r *Reader) WriteTo(w io.Writer) (n int64, err error)

WriteTo реализует интерфейс io.WriterTo.

6 - Описание пакета database языка программирования Go

Пакет для работы с базами данных

Пакет sql предоставляет общий интерфейс для баз данных SQL (или SQL-подобных).

Пакет sql должен использоваться вместе с драйвером базы данных. Список драйверов см. на сайте https://golang.org/s/sqldrivers.

Драйверы, не поддерживающие отмену контекста, вернут результат только после завершения запроса.

Примеры использования приведены на вики-странице https://golang.org/s/sqlwiki.

Пакет driver определяет интерфейсы, которые должны быть реализованы драйверами баз данных, используемыми пакетом sql.

Большая часть кода должна использовать пакет database/sql.

Подробная документация по драйверам: https://pkg.go.dev/database/sql/driver

6.1 - Работа с пакетом database/sql в Go: ошибки и их обработка

Переменные в пакете database/sql с примерами

Пакет database/sql в Go предоставляет универсальный интерфейс для работы с SQL-базами данных. Рассмотрим основные ошибки, которые могут возникнуть при работе с этим пакетом, и как их правильно обрабатывать.

Основные ошибки пакета database/sql

1. ErrConnDone - соединение уже закрыто

var ErrConnDone = errors.New("sql: connection is already closed")

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

Пример:

db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

conn, err := db.Conn(context.Background())
if err != nil {
    log.Fatal(err)
}

// Возвращаем соединение в пул
conn.Close()

// Пытаемся использовать закрытое соединение
err = conn.PingContext(context.Background())
if errors.Is(err, sql.ErrConnDone) {
    fmt.Println("Ошибка: соединение уже закрыто")
}

2. ErrNoRows - нет строк в результате

var ErrNoRows = errors.New("sql: no rows in result set")

Эта ошибка возвращается методом Scan, когда QueryRow не находит ни одной строки.

Правильная обработка:

var name string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 123).Scan(&name)
if err != nil {
    if errors.Is(err, sql.ErrNoRows) {
        fmt.Println("Пользователь не найден")
    } else {
        log.Fatal(err)
    }
} else {
    fmt.Printf("Имя пользователя: %s\n", name)
}

3. ErrTxDone - транзакция уже завершена

var ErrTxDone = errors.New("sql: transaction has already been committed or rolled back")

Эта ошибка возникает при попытке выполнить операцию в уже завершенной транзакции.

Пример:

tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}

// Фиксируем транзакцию
err = tx.Commit()
if err != nil {
    log.Fatal(err)
}

// Пытаемся выполнить запрос в завершенной транзакции
_, err = tx.Exec("INSERT INTO users(name) VALUES (?)", "Alice")
if errors.Is(err, sql.ErrTxDone) {
    fmt.Println("Ошибка: транзакция уже завершена")
}

Советы по работе с database/sql

  1. Всегда проверяйте ошибки после операций с базой данных.
  2. Используйте errors.Is для проверки конкретных ошибок пакета database/sql.
  3. Закрывайте ресурсы (соединения, транзакции, результаты запросов) с помощью defer.
  4. Используйте контексты для управления таймаутами и отменой операций.

Полный пример работы с базой данных:

package main

import (
    "context"
    "database/sql"
    "errors"
    "fmt"
    "log"
    _ "github.com/go-sql-driver/mysql"
)

type User struct {
    ID   int
    Name string
}

func main() {
    db, err := sql.Open("mysql", "user:password@/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Проверка соединения
    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }

    // Пример запроса с обработкой ErrNoRows
    user, err := getUser(db, 123)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            fmt.Println("Пользователь не найден")
        } else {
            log.Fatal(err)
        }
    } else {
        fmt.Printf("Найден пользователь: %+v\n", user)
    }

    // Пример транзакции
    err = transferMoney(db, 1, 2, 100)
    if err != nil {
        log.Fatal(err)
    }
}

func getUser(db *sql.DB, id int) (*User, error) {
    var user User
    err := db.QueryRow("SELECT id, name FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name)
    if err != nil {
        return nil, err
    }
    return &user, nil
}

func transferMoney(db *sql.DB, from, to, amount int) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        if err != nil {
            tx.Rollback()
            return
        }
        err = tx.Commit()
    }()

    // Списание денег
    _, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from)
    if err != nil {
        return err
    }

    // Зачисление денег
    _, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to)
    if err != nil {
        return err
    }

    return nil
}

6.2 - Подробное описание функций пакета database/sql в Go

Пакет database/sql предоставляет универсальный интерфейс для работы с SQL-базами данных. Рассмотрим основные функции этого пакета с примерами использования.

Основные функции управления драйверами

func Drivers() []string

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

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

package main

import (
	"database/sql"
	"fmt"
	"log"
	_ "github.com/go-sql-driver/mysql" // регистрируем MySQL драйвер
	_ "github.com/lib/pq"              // регистрируем PostgreSQL драйвер
)

func main() {
	// Получаем список зарегистрированных драйверов
	drivers := sql.Drivers()
	fmt.Println("Зарегистрированные драйверы:")
	for _, driver := range drivers {
		fmt.Println("-", driver)
	}

	// Выведет что-то вроде:
	// Зарегистрированные драйверы:
	// - mysql
	// - postgres
}

func Register(name string, driver driver.Driver)

Эта функция регистрирует драйвер базы данных под указанным именем. Если попытаться зарегистрировать два драйвера с одинаковым именем или передать nil, функция вызовет panic.

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

package main

import (
	"database/sql"
	"database/sql/driver"
	"fmt"
)

// Простой mock-драйвер для примера
type mockDriver struct{}

func (d *mockDriver) Open(name string) (driver.Conn, error) {
	fmt.Println("Mock driver открывает соединение с:", name)
	return nil, nil
}

func main() {
	// Регистрируем наш mock-драйвер
	sql.Register("mock", &mockDriver{})

	// Теперь можем его использовать
	db, err := sql.Open("mock", "test-connection")
	if err != nil {
		fmt.Println("Ошибка:", err)
		return
	}
	defer db.Close()

	// Проверяем, что драйвер зарегистрирован
	fmt.Println("Зарегистрированные драйверы:", sql.Drivers())
}

Основные функции для работы с БД

func Open(driverName, dataSourceName string) (*DB, error)

Открывает новое соединение с базой данных. На самом деле не устанавливает соединение сразу, а только готовит объект DB.

Пример:

db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

func (*DB) Ping() error и func (*DB) PingContext(ctx context.Context) error

Проверяет, что соединение с БД живо и доступно.

Пример:

err = db.Ping()
if err != nil {
    log.Fatal("Не удалось подключиться к БД:", err)
}
fmt.Println("Успешное подключение к БД!")

Функции выполнения запросов

func (*DB) Exec(query string, args ...interface{}) (Result, error)

func (*DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)

Выполняет запрос без возврата строк (INSERT, UPDATE, DELETE).

Пример:

result, err := db.Exec(
    "INSERT INTO users(name, age) VALUES (?, ?)",
    "Alice",
    30,
)
if err != nil {
    log.Fatal(err)
}

lastID, err := result.LastInsertId()
rowsAffected, err := result.RowsAffected()

func (*DB) Query(query string, args ...interface{}) (*Rows, error)

func (*DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)

Выполняет запрос, возвращающий строки (SELECT).

Пример:

rows, err := db.Query("SELECT id, name, age FROM users WHERE age > ?", 25)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

for rows.Next() {
    var id int
    var name string
    var age int
    err = rows.Scan(&id, &name, &age)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%d: %s, %d\n", id, name, age)
}

if err = rows.Err(); err != nil {
    log.Fatal(err)
}

func (*DB) QueryRow(query string, args ...interface{}) *Row

func (*DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row

Выполняет запрос, который должен вернуть не более одной строки.

Пример:

var name string
var age int
err := db.QueryRow("SELECT name, age FROM users WHERE id = ?", 1).Scan(&name, &age)
if err != nil {
    if err == sql.ErrNoRows {
        fmt.Println("Пользователь не найден")
    } else {
        log.Fatal(err)
    }
} else {
    fmt.Printf("Имя: %s, Возраст: %d\n", name, age)
}

Функции для работы с транзакциями

func (*DB) Begin() (*Tx, error)

func (*DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)

Начинает новую транзакцию.

Пример:

tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}

// Откатываем транзакцию в случае ошибки
defer func() {
    if err != nil {
        tx.Rollback()
    }
}()

_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
    return err
}

_, err = tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
if err != nil {
    return err
}

// Если все успешно - коммитим
err = tx.Commit()
if err != nil {
    return err
}

Работа с соединениями

func (*DB) Conn(ctx context.Context) (*Conn, error)

Получает одно соединение из пула для выполнения нескольких операций в одном контексте.

Пример:

conn, err := db.Conn(context.Background())
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

// Выполняем несколько операций в одном соединении
var count int
err = conn.QueryRowContext(context.Background(), "SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
    log.Fatal(err)
}

_, err = conn.ExecContext(context.Background(), "UPDATE stats SET user_count = ?", count)
if err != nil {
    log.Fatal(err)
}

Заключение

Пакет database/sql предоставляет все необходимые функции для работы с SQL-базами данных в Go. Основные принципы:

  • Всегда проверяйте ошибки
  • Закрывайте ресурсы (Rows, Tx, Conn) с помощью defer
  • Используйте контексты для управления таймаутами
  • Для разных типов запросов используйте соответствующие методы (Exec, Query, QueryRow)

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

6.3 - Контекст (context) в транзакциях database/sql

Контекст (context.Context) - это механизм в Go для управления временем жизни операций, отмены и передачи значений между вызовами функций. В работе с базой данных через database/sql контекст играет ключевую роль.

Основные цели использования контекста в транзакциях

  1. Отмена операций - можно прервать долгий запрос
  2. Таймауты - установка максимального времени выполнения
  3. Распространение значений - передача метаданных через цепочку вызовов

Методы с поддержкой контекста

В database/sql большинство операций имеют две версии:

  • Обычная (Begin, Exec, Query и т.д.)
  • С контекстом (BeginTx, ExecContext, QueryContext и т.д.)

Пример использования контекста с таймаутом

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

// Начинаем транзакцию с таймаутом
tx, err := db.BeginTx(ctx, nil)
if err != nil {
    log.Fatal(err)
}
defer tx.Rollback() // Безопасный откат при ошибках

// Выполняем запрос в транзакции
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
    // Если контекст истек, получим context.DeadlineExceeded
    log.Printf("Update failed: %v", err)
    return
}

// Коммитим транзакцию
err = tx.Commit()
if err != nil {
    log.Printf("Commit failed: %v", err)
}

Особенности работы контекста в транзакциях

  1. Каскадная отмена - отмена контекста прерывает все операции в транзакции
  2. Изоляция транзакций - контекст не влияет на другие транзакции
  3. Ресурсы - отмена не освобождает соединение автоматически

Практические сценарии использования

  1. HTTP-обработчики - привязка к времени жизни запроса:
func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    tx, err := db.BeginTx(ctx, nil)
    // ...
}
  1. Долгие отчеты - возможность отмены:
ctx, cancel := context.WithCancel(context.Background())
go func() {
    time.Sleep(10*time.Second)
    cancel() // Принудительная отмена через 10 сек
}()

rows, err := db.QueryContext(ctx, "SELECT * FROM big_table")
  1. Распределенные транзакции - передача идентификаторов:
ctx := context.WithValue(context.Background(), "txID", generateID())
tx, _ := db.BeginTx(ctx, nil)

Важные нюансы

  • Всегда проверяйте ошибки на context.Canceled и context.DeadlineExceeded
  • Освобождайте ресурсы с помощью defer даже при отмене контекста
  • Не передавайте один контекст в несколько независимых транзакций

Использование контекста делает ваши транзакции более управляемыми и устойчивыми к долгим операциям.

6.4 - Описание типа database/sql DB

DB - это хэндл базы данных, представляющий пул из нуля или более базовых соединений. Он безопасен для одновременного использования несколькими горутинами.
type DB struct {
	// содержит отфильтрованные или неэкспонированные поля
}

Пакет sql создает и освобождает соединения автоматически; он также поддерживает свободный пул незадействованных соединений. Если в базе данных есть понятие состояния каждого соединения, то такое состояние можно надежно наблюдать в рамках транзакции (Tx) или соединения (Conn). После вызова DB.Begin возвращаемая Tx привязывается к одному соединению. После вызова Tx.Commit или Tx.Rollback для транзакции, соединение этой транзакции возвращается в пул незанятых соединений DB. Размер пула можно контролировать с помощью DB.SetMaxIdleConns.

func Open

func Open(driverName, dataSourceName string) (*DB, error)

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

Большинство пользователей открывают базу данных с помощью специфической для драйвера вспомогательной функции подключения, которая возвращает *DB. Драйверы баз данных не включены в стандартную библиотеку Go. Список драйверов сторонних разработчиков см. на https://golang.org/s/sqldrivers.

Open может просто проверить свои аргументы, не создавая соединения с базой данных. Чтобы убедиться, что имя источника данных действительно, вызовите DB.Ping.

Возвращаемая БД безопасна для одновременного использования несколькими горутинами и поддерживает собственный пул незанятых соединений. Таким образом, функцию Open следует вызывать только один раз. Редко возникает необходимость закрывать БД.

func OpenDB

func OpenDB(c driver.Connector) *DB

OpenDB открывает базу данных с помощью driver.Connector, позволяя драйверам обходить строковое имя источника данных.

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

OpenDB может просто проверить свои аргументы, не создавая соединения с базой данных. Чтобы убедиться, что имя источника данных действительно, вызовите DB.Ping.

Возвращаемая БД безопасна для одновременного использования несколькими горутинами и поддерживает свой собственный пул незанятых соединений. Таким образом, функция OpenDB должна быть вызвана только один раз. Редко возникает необходимость закрывать БД.

func (*DB) Close

func (db *DB) Close() error

Close закрывает базу данных и предотвращает запуск новых запросов. Затем Close ожидает завершения всех запросов, которые начали обрабатываться на сервере.

Закрывать БД приходится редко, так как хэндл БД должен быть долгоживущим и использоваться совместно многими горутинами.

db, err := sql.Open("mysql", dsn)
if err != nil {
    log.Fatal(err)
}
defer db.Close() // Всегда закрывайте соединение

func (*DB) Ping

func (db *DB) Ping() error

Ping проверяет, что соединение с базой данных еще живо, и при необходимости устанавливает соединение.

Внутри Ping использует context.Background; чтобы указать контекст, используйте DB.PingContext.

err = db.Ping()
if err != nil {
    log.Fatal("Connection failed:", err)
}

func (*DB) PingContext

func (db *DB) PingContext(ctx context.Context) error

PingContext проверяет, что соединение с базой данных еще живо, и при необходимости устанавливает соединение.

Пример
package main

import (
	"context"
	"database/sql"
	"log"
	"time"
)

var (
	ctx context.Context
	db  *sql.DB
)

func main() {

	ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
	defer cancel()

	status := "up"
	if err := db.PingContext(ctx); err != nil {
		status = "down"
	}
	log.Println(status)
}

func (*DB) Begin

func (db *DB) Begin() (*Tx, error)

Begin начинает транзакцию. Уровень изоляции по умолчанию зависит от драйвера.

Begin внутренне использует context.Background; чтобы указать контекст, используйте DB.BeginTx.

func (*DB) BeginTx

func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)

BeginTx начинает транзакцию.

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

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

Пример
import (
	"context"
	"database/sql"
	"log"
)

var (
	ctx context.Context
	db  *sql.DB
)

func main() {
	tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
	if err != nil {
		log.Fatal(err)
	}
	id := 37
	_, execErr := tx.Exec(`UPDATE users SET status = ? WHERE id = ?`, "paid", id)
	if execErr != nil {
		_ = tx.Rollback()
		log.Fatal(execErr)
	}
	if err := tx.Commit(); err != nil {
		log.Fatal(err)
	}
}

func (*DB) Conn

func (db *DB) Conn(ctx context.Context) (*Conn, error)

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

Каждый Conn должен быть возвращен в пул баз данных после использования вызовом Conn.Close.

func (*DB) Driver

func (db *DB) Driver() driver.Driver

Driver возвращает базовый драйвер базы данных.

func (*DB) Exec

func (db *DB) Exec(query string, args ...any) (Result, error)

Exec выполняет запрос, не возвращая ни одной строки. В качестве args используются любые параметры-заполнители в запросе.

Exec использует внутренний контекст context.Background; чтобы указать контекст, используйте DB.ExecContext.

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

func (*DB) ExecContext

func (db *DB) ExecContext(ctx context.Context, query string, args ...any) (Result, error)

ExecContext выполняет запрос, не возвращая ни одной строки. В качестве args используются любые параметры-заполнители в запросе.

func (*DB) Prepare

func (db *DB) Prepare(query string) (*Stmt, error)

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

Prepare внутренне использует context.Background; чтобы указать контекст, используйте DB.PrepareContext.

Пример
import (
	"context"
	"database/sql"
	"log"
)

var db *sql.DB

func main() {
	projects := []struct {
		mascot  string
		release int
	}{
		{"tux", 1991},
		{"duke", 1996},
		{"gopher", 2009},
		{"moby dock", 2013},
	}

	stmt, err := db.Prepare("INSERT INTO projects(id, mascot, release, category) VALUES( ?, ?, ?, ? )")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close() // Prepared statements take up server resources and should be closed after use.

	for id, project := range projects {
		if _, err := stmt.Exec(id+1, project.mascot, project.release, "open source"); err != nil {
			log.Fatal(err)
		}
	}
}

func (*DB) PrepareContext

func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error)

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

Предоставленный контекст используется для подготовки утверждения, а не для его выполнения.

func (*DB) Query

func (db *DB) Query(query string, args ...any) (*Rows, error)

Query выполняет запрос, возвращающий строки, обычно SELECT. В качестве args используются любые параметры-заполнители в запросе.

Query внутренне использует context.Background; чтобы указать контекст, используйте DB.QueryContext.

Пример
package main

import (
	"context"
	"database/sql"
	"log"

	_ "github.com/lib/pq" // или другой драйвер БД
)

var db *sql.DB

func main() {
	// Инициализация подключения к БД
	var err error
	db, err = sql.Open("postgres", "user=postgres dbname=test sslmode=disable") // замените на свои параметры
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()

	// Проверка соединения
	if err := db.Ping(); err != nil {
		log.Fatal("Failed to ping database:", err)
	}

	age := 27
	q := `
-- Создаем временную таблицу
CREATE TEMP TABLE uid (id bigint) ON COMMIT DROP;

-- Заполняем временную таблицу
INSERT INTO uid
SELECT id FROM users WHERE age < $1;

-- Первый набор результатов
SELECT
	users.id, name
FROM
	users
	JOIN uid ON users.id = uid.id;

-- Второй набор результатов
SELECT 
	ur.user_id, ur.role_id
FROM
	user_roles AS ur
	JOIN uid ON uid.id = ur.user_id;
	`

	// Используем контекст для запроса
	ctx := context.Background()
	rows, err := db.QueryContext(ctx, q, age)
	if err != nil {
		log.Fatal("Query failed:", err)
	}
	defer rows.Close()

	// Обрабатываем первый набор результатов
	log.Println("Processing users:")
	for rows.Next() {
		var (
			id   int64
			name string
		)
		if err := rows.Scan(&id, &name); err != nil {
			log.Fatal("Failed to scan user row:", err)
		}
		log.Printf("id %d name is %s\n", id, name)
	}

	// Переходим ко второму набору результатов
	if !rows.NextResultSet() {
		if err := rows.Err(); err != nil {
			log.Fatal("Failed to get next result set:", err)
		}
		log.Fatal("Expected more result sets, but none available")
	}

	// Обрабатываем второй набор результатов
	roleMap := map[int64]string{
		1: "user",
		2: "admin",
		3: "gopher",
	}

	log.Println("\nProcessing roles:")
	for rows.Next() {
		var (
			userID int64
			roleID int64
		)
		if err := rows.Scan(&userID, &roleID); err != nil {
			log.Fatal("Failed to scan role row:", err)
		}
		roleName, ok := roleMap[roleID]
		if !ok {
			roleName = "unknown"
		}
		log.Printf("user %d has role %s\n", userID, roleName)
	}

	if err := rows.Err(); err != nil {
		log.Fatal("Rows error:", err)
	}
}

func (*DB) QueryContext

func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)

QueryContext выполняет запрос, возвращающий строки, обычно SELECT. В качестве args используются любые параметры-заполнители запроса.

Пример
package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"strings"

	_ "github.com/go-sql-driver/mysql" // или другой драйвер БД
)

func main() {
	// 1. Инициализация подключения к БД
	var err error
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()

	// 2. Проверка соединения
	ctx := context.Background()
	if err := db.PingContext(ctx); err != nil {
		log.Fatal("Failed to ping database:", err)
	}

	// 3. Выполнение запроса
	age := 27
	rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
	if err != nil {
		log.Fatal("Query failed:", err)
	}
	defer rows.Close()

	// 4. Обработка результатов
	names := make([]string, 0)
	for rows.Next() {
		var name string
		if err := rows.Scan(&name); err != nil {
			log.Printf("Scan error (skipping row): %v", err) // Не прерываем выполнение
			continue
		}
		names = append(names, name)
	}

	// 5. Проверка ошибок после итерации
	if err := rows.Err(); err != nil {
		log.Fatal("Rows error:", err)
	}

	// 6. Проверка ошибок закрытия (более аккуратная обработка)
	if err := rows.Close(); err != nil {
		log.Printf("Warning: error closing rows: %v", err) // Не фатальная ошибка
	}

	// 7. Вывод результатов
	if len(names) > 0 {
		fmt.Printf("%s are %d years old\n", strings.Join(names, ", "), age)
	} else {
		fmt.Printf("No users found at age %d\n", age)
	}
}

func (*DB) QueryRow

func (db *DB) QueryRow(query string, args ...any) *Row

QueryRow выполняет запрос, который, как ожидается, вернет не более одной строки. QueryRow всегда возвращает значение, не являющееся нулем. Ошибки откладываются до вызова метода Row’s Scan. Если запрос не выбрал ни одной строки, то *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.

QueryRow внутренне использует context.Background;

func (*DB) QueryRowContext

func (db *DB) QueryRowContext(ctx context.Context, query string, args ...any) *Row

QueryRowContext выполняет запрос, который, как ожидается, вернет не более одной строки. QueryRowContext всегда возвращает значение non-nil. Ошибки откладываются до вызова метода Row’s Scan. Если запрос не выбрал ни одной строки, то *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.

Пример
package main

import (
	"context"
	"database/sql"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)

func main() {
	// Инициализация подключения к БД
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
	if err != nil {
		log.Fatalf("Failed to connect to database: %v", err)
	}
	defer db.Close()

	// Проверка соединения
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := db.PingContext(ctx); err != nil {
		log.Fatalf("Failed to ping database: %v", err)
	}

	// Выполнение запроса
	id := 123
	var username string
	var created time.Time

	// Используем контекст с таймаутом для запроса
	queryCtx, queryCancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer queryCancel()

	err = db.QueryRowContext(queryCtx, 
		"SELECT username, created_at FROM users WHERE id=?", 
		id,
	).Scan(&username, &created)

	switch {
	case err == sql.ErrNoRows:
		log.Printf("No user found with id %d", id)
	case err != nil:
		log.Printf("Query error: %v", err) // Не используем Fatalf чтобы не прерывать программу
	default:
		log.Printf(
			"Username: %q, Account created on: %s", 
			username, 
			created.Format("2006-01-02 15:04:05"),
		)
	}
}

func (*DB) SetConnMaxIdleTime

func (db *DB) SetConnMaxIdleTime(d time.Duration)

SetConnMaxIdleTime устанавливает максимальное время, в течение которого соединение может простаивать.

Просроченные соединения могут быть лениво закрыты перед повторным использованием.

Если d <= 0, соединения не закрываются из-за времени простоя соединения.

func (*DB) SetConnMaxLifetime

func (db *DB) SetConnMaxLifetime(d time.Duration)

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

Просроченные соединения могут быть лениво закрыты перед повторным использованием.

Если d <= 0, соединения не закрываются из-за возраста соединения.

func (*DB) SetMaxIdleConns

func (db *DB) SetMaxIdleConns(n int)

SetMaxIdleConns устанавливает максимальное количество соединений в пуле незадействованных соединений.

Если MaxOpenConns больше 0, но меньше нового MaxIdleConns, то новый MaxIdleConns будет уменьшен, чтобы соответствовать лимиту MaxOpenConns.

Если n <= 0, простаивающие соединения не сохраняются.

По умолчанию максимальное количество незадействованных соединений в настоящее время равно 2. Это может измениться в будущем выпуске.

func (*DB) SetMaxOpenConns

func (db *DB) SetMaxOpenConns(n int)

SetMaxOpenConns устанавливает максимальное количество открытых соединений с базой данных.

Если MaxIdleConns больше 0, а новое значение MaxOpenConns меньше MaxIdleConns, то MaxIdleConns будет уменьшено, чтобы соответствовать новому ограничению MaxOpenConns.

Если n <= 0, то количество открытых соединений не ограничено. По умолчанию 0 (неограниченно).

func (*DB) Stats

func (db *DB) Stats() DBStats

Stats возвращает статистику базы данных.

6.5 - Описание типа database/sql ColumnType

ColumnType тип для управлени структурой базы данных
type ColumnType struct {
// содержит отфильтрованные или неэкспортированные поля
}

ColumnType содержит имя и тип колонки.

func (*ColumnType) DatabaseTypeName

func (ci *ColumnType) DatabaseTypeName() string

DatabaseTypeName возвращает системное имя базы данных типа столбца. Если возвращается пустая строка, значит, имя типа драйвером не поддерживается. Список типов данных драйвера см. в документации к драйверу.

Спецификаторы ColumnType.Length не учитываются. Общие имена типов включают “VARCHAR”, “TEXT”, “NVARCHAR”, “DECIMAL”, “BOOL”, “INT” и “BIGINT”.

func (*ColumnType) DecimalSize

func (ci *ColumnType) DecimalSize() (precision, scale int64, ok bool)

DecimalSize возвращает масштаб и точность десятичного типа. Если они не применяются или не поддерживаются, ok равно false.

func (*ColumnType) Length

func (ci *ColumnType) Length() (length int64, ok bool)

Length возвращает длину типа столбца для типов столбцов переменной длины, таких как текстовые и бинарные типы полей. Если длина типа не ограничена, то значение будет math.MaxInt64 (все ограничения базы данных будут действовать). Если тип столбца не переменной длины, например int, или если он не поддерживается драйвером, ok будет false.

func (*ColumnType) Name

func (ci *ColumnType) Name() string

Name возвращает имя или псевдоним колонки.

func (*ColumnType) Nullable

func (ci *ColumnType) Nullable() (nullable, ok bool)

Nullable сообщает, может ли столбец быть нулевым. Если драйвер не поддерживает это свойство, ok будет равно false.

func (*ColumnType) ScanType

func (ci *ColumnType) ScanType() reflect.Type

ScanType возвращает тип Go, подходящий для сканирования с помощью Rows.Scan. Если драйвер не поддерживает это свойство, ScanType вернет тип пустого интерфейса.

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	// Подключение к базе данных
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/testdb")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Проверка соединения
	if err := db.Ping(); err != nil {
		log.Fatal(err)
	}

	// Выполнение запроса
	rows, err := db.QueryContext(context.Background(), 
		"SELECT id, username, created_at, balance, is_active FROM users LIMIT 1")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	// Получение информации о столбцах
	columnTypes, err := rows.ColumnTypes()
	if err != nil {
		log.Fatal(err)
	}

	// Анализ метаданных каждого столбца
	for i, ct := range columnTypes {
		fmt.Printf("\n--- Столбец %d: %s ---\n", i+1, ct.Name())

		// Тип данных в БД
		databaseType := ct.DatabaseTypeName()
		fmt.Printf("Тип в БД: %s\n", databaseType)

		// Nullable
		nullable, ok := ct.Nullable()
		if ok {
			fmt.Printf("Может быть NULL: %v\n", nullable)
		} else {
			fmt.Println("Информация о NULL недоступна")
		}

		// Длина/размер
		length, ok := ct.Length()
		if ok {
			fmt.Printf("Длина: %d\n", length)
		}

		// Точность и масштаб (для чисел)
		precision, scale, ok := ct.DecimalSize()
		if ok {
			fmt.Printf("Точность: %d, Масштаб: %d\n", precision, scale)
		}

		// Тип сканирования в Go
		scanType := ct.ScanType()
		fmt.Printf("Тип в Go: %v\n", scanType)
	}

	// Пример использования информации о типах для динамического сканирования
	var (
		id        int64
		username  string
		createdAt time.Time
		balance   float64
		isActive  bool
	)

	if rows.Next() {
		err = rows.Scan(&id, &username, &createdAt, &balance, &isActive)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("\nРеальные значения: %d, %s, %v, %.2f, %t\n", 
			id, username, createdAt, balance, isActive)
	}
}

6.6 - Описание типа Conn database/sql

Conn представляет собой одно соединение с базой данных, а не пул соединений с базой данных.
type Conn struct {
// содержит отфильтрованные или неэкспонированные поля
}

Conn представляет собой одно соединение с базой данных, а не пул соединений с базой данных. Предпочтительнее запускать запросы из БД, если нет особой необходимости в постоянном соединении с одной базой данных.

Conn должен вызвать Conn.Close, чтобы вернуть соединение в пул баз данных, и может делать это одновременно с выполняющимся запросом.

После вызова Conn.Close все операции над соединением завершаются с ошибкой ErrConnDone.

func (*Conn) BeginTx

func (c *Conn) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)

BeginTx начинает транзакцию.

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

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

func (*Conn) Close

func (c *Conn) Close() error

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

func (*Conn) ExecContext

func (c *Conn) ExecContext(ctx context.Context, query string, args ...any) (Result, error)

ExecContext выполняет запрос, не возвращая никаких строк. В качестве args используются любые параметры-заполнители в запросе.

Пример
package main

import (
	"context"
	"database/sql"
	"log"
	"os"

	_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)

func main() {
	// 1. Инициализация подключения к БД
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()

	// 2. Проверка соединения
	ctx := context.Background()
	if err := db.PingContext(ctx); err != nil {
		log.Fatal("Failed to ping database:", err)
	}

	// 3. Получение выделенного соединения
	conn, err := db.Conn(ctx)
	if err != nil {
		log.Fatal("Failed to get connection:", err)
	}
	defer func() {
		if err := conn.Close(); err != nil {
			log.Printf("Warning: error closing connection: %v", err)
		}
	}()

	// 4. Выполнение транзакции
	id := 41
	tx, err := conn.BeginTx(ctx, nil)
	if err != nil {
		log.Fatal("Failed to begin transaction:", err)
	}

	// 5. Выполнение UPDATE в транзакции
	result, err := tx.ExecContext(ctx,
		`UPDATE balances SET balance = balance + 10 WHERE user_id = ?`,
		id,
	)
	if err != nil {
		tx.Rollback()
		log.Fatal("Failed to update balance:", err)
	}

	// 6. Проверка количества измененных строк
	rows, err := result.RowsAffected()
	if err != nil {
		tx.Rollback()
		log.Fatal("Failed to get rows affected:", err)
	}

	if rows != 1 {
		tx.Rollback()
		log.Printf("Expected single row affected, got %d rows affected", rows)
		os.Exit(1) // Более мягкое завершение чем log.Fatal
	}

	// 7. Фиксация транзакции
	if err := tx.Commit(); err != nil {
		log.Fatal("Failed to commit transaction:", err)
	}

	log.Println("Balance updated successfully")
}

func (*Conn) PingContext

func (c *Conn) PingContext(ctx context.Context) error

PingContext проверяет, что соединение с базой данных все еще живо.

func (*Conn) PrepareContext

func (c *Conn) PrepareContext(ctx context.Context, query string) (*Stmt, error)

PrepareContext создает подготовленный запрос для последующих запросов или выполнений. Несколько запросов или выполнений могут быть запущены одновременно из полученного утверждения. Вызывающая сторона должна вызвать метод *Stmt.Close оператора, когда он больше не нужен.

Предоставленный контекст используется для подготовки утверждения, а не для его выполнения.

func (*Conn) QueryContext

func (c *Conn) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)

QueryContext выполняет запрос, возвращающий строки, обычно SELECT. В args указываются любые параметры-заполнители запроса.

func (*Conn) QueryRowContext

func (c *Conn) QueryRowContext(ctx context.Context, query string, args ...any) *Row

QueryRowContext выполняет запрос, который, как ожидается, вернет не более одной строки. QueryRowContext всегда возвращает значение non-nil. Ошибки откладываются до вызова метода *Row.Scan. Если запрос не выбрал ни одной строки, то *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.

func (*Conn) Raw

func (c *Conn) Raw(f func(driverConn any) error) (err error)

Raw выполняет f, раскрывая базовое соединение драйвера на время выполнения f. DriverConn не должен использоваться вне f.

После того как f вернется и err не будет driver.ErrBadConn, Conn будет продолжать использоваться до вызова Conn.Close.

Разбираем метод Raw() для работы с низкоуровневыми соединениями

Метод Raw() в database/sql позволяет получить прямое доступ к драйвер-специфичному соединению, минуя абстракции пакета database/sql. Это нужно для использования специфичных функций драйвера, которые не доступны через стандартный API.

Что такое f в этом контексте?

f - это ваша функция-колбэк, которая получает доступ к нативному соединению драйвера. Она должна иметь сигнатуру:

func(driverConn any) error

Зачем это нужно?

  1. Использование специфичных функций драйвера (например, PostgreSQL-специфичные команды)
  2. Оптимизация производительности для критичных участков кода
  3. Работа с нестандартными возможностями БД

Простой пример (PostgreSQL)

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

func main() {
	db, err := sql.Open("postgres", "user=postgres dbname=test sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	conn, err := db.Conn(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	// Используем Raw для доступа к нативному соединению PostgreSQL
	err = conn.Raw(func(driverConn interface{}) error {
		// Приводим к типу, который ожидаем (для PostgreSQL)
		pgConn, ok := driverConn.(driver.Conn) // Здесь нужен ваш тип соединения
		if !ok {
			return fmt.Errorf("unexpected driver connection type")
		}

		// Пример: выполняем LISTEN для PostgreSQL-нотификаций
		// Это специфичная функция PostgreSQL, недоступная через стандартный API
		_, err := pgConn.(*pq.Conn).Exec("LISTEN channel_name")
		return err
	})

	if err != nil {
		log.Fatal(err)
	}
}

Более практичный пример (MySQL)

package main

import (
	"context"
	"database/sql"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	db, err := sql.Open("mysql", "user:password@/dbname")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	conn, err := db.Conn(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	// Получаем низкоуровневое соединение MySQL
	err = conn.Raw(func(driverConn interface{}) error {
		// Приводим к ожидаемому типу соединения MySQL
		mysqlConn, ok := driverConn.(*mysql.Conn) // Требуется import драйвера
		if !ok {
			return fmt.Errorf("expected mysql.Conn, got %T", driverConn)
		}

		// Используем специфичные методы MySQL
		fmt.Println("Server version:", mysqlConn.GetServerVersion())
		fmt.Println("Connection ID:", mysqlConn.GetConnectionID())
		
		// Можно выполнить специфичные команды
		return mysqlConn.Ping()
	})

	if err != nil {
		log.Fatal(err)
	}
}

Важные предупреждения:

  1. Тип соединения зависит от драйвера:

    • PostgreSQL: *pq.Conn
    • MySQL: *mysql.Conn
    • SQLite: зависит от используемого драйвера
  2. Безопасность:

    • Не сохраняйте соединение вне функции f
    • Все ошибки должны быть обработаны
  3. Совместимость:

    • Код становится зависимым от конкретного драйвера
    • Может сломаться при смене драйвера

Когда стоит использовать Raw()?

  1. Когда вам нужен доступ к специфичным функциям СУБД
  2. Для оптимизации критических участков
  3. При работе с нестандартными возможностями (нотификации, специфичные команды)

В большинстве случаев стандартного API database/sql достаточно, и Raw() не понадобится.

6.7 - Описание типа DBStats database/sql

DBStats DBStats содержит статистику базы данных. В этом разделе представлены другие типы: IsolationLevel, NamedArg, Null, NullBool

type DBStats


type DBStats struct {
	MaxOpenConnections int // Максимальное количество открытых соединений с базой данных.

	// Состояние пула
	OpenConnections int // Количество установленных соединений, как используемых, так и простаивающих.
	InUse int // Количество соединений, используемых в данный момент.
	Idle int // Количество простаивающих соединений.

	// Счетчики
	WaitCount int64 // Общее количество ожидающих соединений.
	WaitDuration time.Duration // Общее время, заблокированное в ожидании нового соединения.
	MaxIdleClosed int64 // Общее количество соединений, закрытых из-за SetMaxIdleConns.
	MaxIdleTimeClosed int64 // Общее количество соединений, закрытых из-за SetConnMaxIdleTime.
	MaxLifetimeClosed int64 // Общее количество соединений, закрытых из-за SetConnMaxLifetime.
}

DBStats содержит статистику базы данных.

type IsolationLevel

type IsolationLevel int

IsolationLevel - это уровень изоляции транзакции, используемый в TxOptions.

const (
	LevelDefault IsolationLevel = iota
	LevelReadUncommitted
	LevelReadCommitted
	LevelWriteCommitted
	LevelRepeatableRead
	LevelSnapshot
	LevelSerializable
	LevelLinearizable
)

Различные уровни изоляции, которые драйверы могут поддерживать в DB.BeginTx. Если драйвер не поддерживает заданный уровень изоляции, может быть возвращена ошибка.

См. https://en.wikipedia.org/wiki/Isolation_(database_systems)#Isolation_levels.

func (IsolationLevel) String

func (i IsolationLevel) String() string

String возвращает имя уровня изоляции транзакции.

type NamedArg

type NamedArg struct {

	// Name - имя параметра-заместителя.
	//
    // Если пусто, то будет использоваться
    // порядковая позиция в списке аргументов.
	//
    // Имя не должно содержать префикса символа.
	Name string

	// Value - это значение параметра.
	// Ему могут быть присвоены те же типы значений, что и аргументам запроса
    //.
	Value any
    // содержит отфильтрованные или неэкспонированные поля
}

NamedArg - это именованный аргумент. Значения NamedArg могут использоваться в качестве аргументов DB.Query или DB.Exec и связываться с соответствующим именованным параметром в операторе SQL.

Для более краткого способа создания значений NamedArg см. функцию Named.

func Named

func Named(name string, value any) NamedArg

Named предоставляет более лаконичный способ создания значений NamedArg.

Пример
db.ExecContext(ctx, `
 delete from Invoice
 where
 TimeCreated < @end
 and TimeCreated >= @start;`,
    sql.Named("start", startTime),
    sql.Named("end", endTime),
)

type Null

type Null[T any] struct {
V T
Valid bool
}

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

var s Null[string]
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
...
if s.Valid {
 // используем s.V
} else {
 // NULL значение
}

T должен быть одним из типов, принимаемых driver.Value.

func (*Null[T]) Scan

func (n *Null[T]) Scan(value any) error

func (Null[T]) Value

func (n Null[T]) Value() (driver.Value, error)

type NullBool ¶

type NullBool struct {
	Bool  bool
	Valid bool // Valid is true if Bool is not NULL
}

NullBool представляет bool, который может быть нулевым. NullBool реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.

func (*NullBool) Scan

func (n *NullBool) Scan(value any) error

Scan реализует интерфейс Scanner.

func (NullBool) Value

func (n NullBool) Value() (driver.Value, error)

Value реализует интерфейс driver.Valuer.

type NullByte

type NullByte struct {
    Byte    byte
    Valid   bool // Valid is true if Byte is not NULL
}

NullByte представляет байт, который может быть нулевым. NullByte реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.

func (*NullByte) Scan

func (n *NullByte) Scan(value any) error

Scan реализует интерфейс Scanner.

func (NullByte) Value

func (n NullByte) Value() (driver.Value, error)

Value реализует интерфейс driver.Valuer.

type NullFloat64 ¶

type NullFloat64 struct {
    Float64 float64
    Valid bool // Valid is true if Float64 is not NULL
}

NullFloat64 представляет float64, который может быть нулевым. NullFloat64 реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.

func (*NullFloat64) Scan ¶

func (n *NullFloat64) Scan(value any) error

Scan реализует интерфейс Scanner.

func (NullFloat64) Value ¶

func (n NullFloat64) Value() (driver.Value, error)

Value реализует интерфейс driver.Valuer.

type NullInt16

type NullInt16 struct {
    Int16 int16
    Valid bool // Valid is true if Int16 is not NULL
}

NullInt16 представляет int16, который может быть нулевым. NullInt16 реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.

func (*NullInt16) Scan

func (n *NullInt16) Scan(value any) error

Scan реализует интерфейс Scanner.

func (NullInt16) Value

func (n NullInt16) Value() (driver.Value, error)

Value реализует интерфейс driver.Valuer.

type NullInt32

type NullInt32 struct {
    Int32 int32
    Valid bool // Valid is true if Int32 is not NULL
}

NullInt32 представляет int32, который может быть нулевым. NullInt32 реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.

func (*NullInt32) Scan

func (n *NullInt32) Scan(value any) error

Scan реализует интерфейс Scanner.

func (NullInt32) Value

func (n NullInt32) Value() (driver.Value, error)

Value реализует интерфейс driver.Valuer.

type NullInt64

type NullInt64 struct {
    Int64 int64
    Valid bool // Valid is true if Int64 is not NULL
}

NullInt64 представляет int64, который может быть нулевым. NullInt64 реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.

func (*NullInt64) Scan

func (n *NullInt64) Scan(value any) error

Scan реализует интерфейс Scanner.

func (NullInt64) Value

func (n NullInt64) Value() (driver.Value, error)

Value реализует интерфейс driver.Valuer.

type NullString

type NullString struct {
    String string
    Valid bool // Valid is true if String is not NULL
}

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

var s NullString
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
...
if s.Valid {
 // использовать s.String
} else {
 // NULL значение
}

func (*NullString) Scan

func (ns *NullString) Scan(value any) error

Scan реализует интерфейс Scanner.

func (NullString) Value

func (ns NullString) Value() (driver.Value, error)

Value реализует интерфейс driver.Valuer.

type NullTime

type NullTime struct {
    Time.Time
    Valid bool // Valid is true if Time is not NULL
}

NullTime представляет время time.Time, которое может быть нулевым. NullTime реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.

func (*NullTime) Scan

func (n *NullTime) Scan(value any) error

Scan реализует интерфейс Scanner.

func (NullTime) Value

func (n NullTime) Value() (driver.Value, error)

Value реализует интерфейс driver.Valuer.

Практическое использование Null-типов в Go

Null-типы (NullString, NullInt64, NullTime и др.) нужны для работы с NULL-значениями из базы данных. Разберём на реальных примерах.

Интерфейсы

  1. Scanner - позволяет сканировать (читать) значение из БД
  2. driver.Valuer - позволяет преобразовывать значение для записи в БД

Полный пример с NullString и NullTime

Пример
package main

import (
	"database/sql"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

type UserProfile struct {
	ID        int64
	Name      sql.NullString
	Bio       sql.NullString
	DeletedAt sql.NullTime
}

func main() {
	db, err := sql.Open("mysql", "user:password@/dbname")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// 1. Запись данных с NULL-значениями
	profile := UserProfile{
		Name:      sql.NullString{String: "Иван Иванов", Valid: true},
		Bio:       sql.NullString{}, // NULL
		DeletedAt: sql.NullTime{Time: time.Now(), Valid: false}, // Будет записан как NULL
	}

	_, err = db.Exec(
		"INSERT INTO users (name, bio, deleted_at) VALUES (?, ?, ?)",
		profile.Name,
		profile.Bio,
		profile.DeletedAt,
	)
	if err != nil {
		log.Fatal(err)
	}

	// 2. Чтение данных с возможными NULL-значениями
	var user UserProfile
	err = db.QueryRow(`
		SELECT id, name, bio, deleted_at 
		FROM users 
		WHERE id = ?
	`, 1).Scan(
		&user.ID,
		&user.Name,
		&user.Bio,
		&user.DeletedAt,
	)
	if err != nil {
		log.Fatal(err)
	}

	// 3. Обработка NULL-значений
	log.Println("ID:", user.ID)
	
	if user.Name.Valid {
		log.Println("Name:", user.Name.String)
	} else {
		log.Println("Name not set")
	}
	
	if user.Bio.Valid {
		log.Println("Bio:", user.Bio.String)
	} else {
		log.Println("Bio not set")
	}
	
	if user.DeletedAt.Valid {
		log.Println("Deleted at:", user.DeletedAt.Time)
	} else {
		log.Println("Not deleted")
	}
}

Как работают Null-типы?

1. Scan (чтение из БД)

Пример
func (ns *NullString) Scan(value interface{}) error {
	if value == nil {
		ns.String, ns.Valid = "", false
		return nil
	}
	ns.Valid = true
	return convertAssign(&ns.String, value)
}

2. Value (запись в БД)

Пример
func (ns NullString) Value() (driver.Value, error) {
	if !ns.Valid {
		return nil, nil
	}
	return ns.String, nil
}

Пример с пользовательским Null-типом

Создадим свой NullStatus для enum-поля:

Пример
type NullStatus struct {
	Status string
	Valid  bool
}

func (ns *NullStatus) Scan(value interface{}) error {
	if value == nil {
		ns.Status, ns.Valid = "", false
		return nil
	}
	ns.Valid = true
	switch v := value.(type) {
	case []byte:
		ns.Status = string(v)
	case string:
		ns.Status = v
	default:
		return fmt.Errorf("unsupported type: %T", value)
	}
	return nil
}

func (ns NullStatus) Value() (driver.Value, error) {
	if !ns.Valid {
		return nil, nil
	}
	return ns.Status, nil
}

Когда использовать Null-типы?

  1. Когда поле в БД может быть NULL
  2. Когда нужно отличать “нулевое значение” от “неустановленного”
  3. При работе с опциональными полями

Альтернативы

В новых версиях Go можно использовать указатели:

Пример
var name *string
err := db.QueryRow("SELECT name FROM users WHERE id = 1").Scan(&name)
if name != nil {
    // есть значение
}

Но Null-типы предоставляют более удобный интерфейс и явный флаг Valid.

6.8 - Описание типа Out database/sql

Out может использоваться для извлечения параметров OUTPUT из хранимых процедур. В этом разделе представлены другие типы: RawBytes, Result

type Out

type Out struct {

    // Dest — указатель на значение, которое будет установлено в качестве результата
    // параметра OUTPUT хранящейся процедуры.
    Dest any

    // In — является ли параметр параметром INOUT. Если да, то входное значение для хранящейся
    // процедуры — это разыменованное значение указателя Dest, которое затем заменяется
	// выходным значением.
    In bool
	// содержит отфильтрованные или неэкспортированные поля
}

Out может использоваться для извлечения параметров OUTPUT из хранимых процедур.

Не все драйверы и базы данных поддерживают параметры OUTPUT.

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

var outArg string
_, err := db.ExecContext(ctx, «ProcName», sql.Named(«Arg1», sql.Out{Dest: &outArg}))

type RawBytes

type RawBytes []byte

RawBytes — это байтовый срез, который содержит ссылку на память, принадлежащую самой базе данных. После выполнения Rows.Scan в RawBytes срез остается действительным только до следующего вызова Rows.Next, Rows.Scan или Rows.Close.

RawBytes - это специальный тип в пакете database/sql, определённый как []byte, который используется для сканирования данных из базы данных без копирования памяти. Это может быть полезно для оптимизации производительности при работе с большими бинарными данными.

Основные принципы работы с RawBytes

  1. Временное владение памятью: RawBytes содержит ссылку на память, управляемую драйвером БД, а не на копию данных.
  2. Ограниченное время жизни: Данные в RawBytes действительны только до следующего вызова методов:
    • Next()
    • Scan()
    • Close()
  3. Небезопасность: Если сохранить RawBytes и попытаться использовать после вышеуказанных вызовов, это приведёт к неопределённому поведению.

Пример 1: Базовое сканирование

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql", "user:password@/dbname")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT blob_column FROM my_table WHERE id = ?", 1)
    if err != nil {
        panic(err)
    }
    defer rows.Close()

    for rows.Next() {
        var raw sql.RawBytes
        if err := rows.Scan(&raw); err != nil {
            panic(err)
        }
        // Используем raw сразу, пока он действителен
        fmt.Printf("Data: %s\n", raw)
        // Если нужно сохранить данные, нужно сделать копию:
        dataCopy := make([]byte, len(raw))
        copy(dataCopy, raw)
        // Теперь dataCopy можно использовать после rows.Next()
    }
}

Пример 2: Сканирование нескольких столбцов

func scanMultipleColumns(db *sql.DB) error {
    rows, err := db.Query("SELECT id, name, data FROM documents")
    if err != nil {
        return err
    }
    defer rows.Close()

    for rows.Next() {
        var id int
        var name string
        var data sql.RawBytes
        
        if err := rows.Scan(&id, &name, &data); err != nil {
            return err
        }
        
        fmt.Printf("ID: %d, Name: %s, Data length: %d\n", id, name, len(data))
        
        // Обработка данных должна быть здесь
        processData(data)
    }
    return rows.Err()
}

func processData(data []byte) {
    // Обработка данных
}

Пример 3: Опасное использование (неправильное)

func incorrectUsage(db *sql.DB) {
    rows, err := db.Query("SELECT data FROM large_blobs")
    if err != nil {
        panic(err)
    }
    defer rows.Close()

    var allData [][]byte
    
    for rows.Next() {
        var raw sql.RawBytes
        if err := rows.Scan(&raw); err != nil {
            panic(err)
        }
        // ОШИБКА: сохраняем RawBytes, который станет недействительным
        allData = append(allData, raw)
    }
    
    // Здесь allData содержит недействительные данные!
    for _, data := range allData {
        fmt.Println(data) // Может привести к панике или неверным данным
    }
}

Когда использовать RawBytes

  1. Для больших бинарных данных, когда копирование нежелательно
  2. Для временной обработки данных, которые не нужно сохранять
  3. Когда производительность критична, и вы готовы следить за временем жизни данных

Альтернативы

Если нужно сохранить данные:

var raw sql.RawBytes
var data []byte

rows.Scan(&raw)
data = make([]byte, len(raw))
copy(data, raw)
// Теперь data можно использовать в любом месте

type Result ¶

type Result интерфейс {
    // LastInsertId возвращает целое число, сгенерированное базой данных
	// в ответ на команду. Обычно это будет из
    // столбца «автоинкремент» при вставке новой строки. Не все
    // базы данных поддерживают эту функцию, и синтаксис таких
    // операторов варьируется.
    LastInsertId() (int64, error)

    // RowsAffected возвращает количество строк, затронутых
    // обновлением, вставкой или удалением. Не все базы данных или драйверы баз данных
	// драйвер базы данных может поддерживать эту функцию.
    RowsAffected() (int64, error)
}

Результат обобщает выполненную команду SQL.

6.9 - Описание типа Row database/sql

Row — результат вызова DB.QueryRow для выбора одной строки. В этом разделе представлены другие типы: Rows, Scanner

type Row

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

Row — результат вызова DB.QueryRow для выбора одной строки.

func (*Row) Err

func (r *Row) Err() error

Err предоставляет способ для обертывающих пакетов проверять ошибки запроса без вызова Row.Scan. Err возвращает ошибку, если таковая имела место при выполнении запроса. Если эта ошибка не равна nil, она также будет возвращена из Row.Scan.

func (*Row) Scan

func (r *Row) Scan(dest ...any) error

Scan копирует столбцы из соответствующей строки в значения, на которые указывает dest. Подробности см. в документации по Rows.Scan. Если запросу соответствует более одной строки, Scan использует первую строку и отбрасывает остальные. Если ни одна строка не соответствует запросу, Scan возвращает ErrNoRows.

type Rows

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

Rows — это результат запроса. Его курсор начинается перед первой строкой набора результатов. Используйте Rows.Next для перехода от строки к строке.

Пример
package main

import (
	"context"
	"database/sql"
	"log"
	"strings"
)

// Глобальные переменные лучше инициализировать
var (
	ctx = context.Background() // Инициализация контекста
	db  *sql.DB
)

func main() {
	// На практике db должен быть инициализирован перед использованием
	// Например:
	// var err error
	// db, err = sql.Open("driver-name", "datasource")
	// if err != nil {
	//     log.Fatal(err)
	// }
	// defer db.Close()

	age := 27
	// Добавляем проверку, что db не nil
	if db == nil {
		log.Fatal("database connection is not initialized")
	}

	// Используем QueryContext вместо Query для явного указания контекста
	rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	names := make([]string, 0)
	for rows.Next() {
		var name string
		if err := rows.Scan(&name); err != nil {
			log.Fatal(err)
		}
		names = append(names, name)
	}
	// Проверяем ошибки после итерации
	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}

	// Добавляем проверку на пустой результат
	if len(names) == 0 {
		log.Printf("No users found with age %d", age)
		return
	}

	log.Printf("%s are %d years old", strings.Join(names, ", "), age)
}

func (*Rows) Close

func (rs *Rows) Close() error

Close закрывает Rows, предотвращая дальнейшее перечисление. Если Rows.Next вызывается и возвращает false, а дальнейших наборов результатов нет, Rows закрывается автоматически, и достаточно проверить результат Rows.Err. Close является идемпотентным и не влияет на результат Rows.Err.

func (*Rows) ColumnTypes

func (rs *Rows) ColumnTypes() ([]*ColumnType, error)

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

func (*Rows) Columns

func (rs *Rows) Columns() ([]string, error)

Columns возвращает имена столбцов. Columns возвращает ошибку, если строки закрыты.

func (*Rows) Err

func (rs *Rows) Err() error

Err возвращает ошибку, если таковая возникла во время итерации. Err может быть вызван после явного или неявного Rows.Close.

func (*Rows) Next

func (rs *Rows) Next() bool

Next подготавливает следующую строку результата для чтения с помощью метода Rows.Scan. Он возвращает true в случае успеха или false, если следующей строки результата нет или при ее подготовке произошла ошибка. Для различения этих двух случаев следует обратиться к Rows.Err.

Каждому вызову Rows.Scan, даже первому, должен предшествовать вызов Rows.Next.

Функция Rows.Next() используется для итерации по строкам результата SQL-запроса.

Пример

Базовый пример использования Rows.Next()

package main

import (
	"database/sql"
	"fmt"
	"log"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	// Подключение к базе данных
	db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Выполнение запроса
	rows, err := db.Query("SELECT id, name, email FROM users WHERE active = ?", true)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	// Итерация по строкам результата
	for rows.Next() {
		var id int
		var name, email string
		
		// Сканирование значений из текущей строки
		if err := rows.Scan(&id, &name, &email); err != nil {
			log.Fatal(err)
		}
		
		fmt.Printf("ID: %d, Name: %s, Email: %s\n", id, name, email)
	}

	// Проверка ошибок после итерации
	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}
}

Почему используется цикл for?

Цикл for rows.Next() выполняет итерации по всем строкам результата запроса:

  1. Количество итераций равно количеству строк, возвращенных запросом.

  2. Механизм работы:

    • При первом вызове rows.Next() перемещает курсор к первой строке результата
    • При последующих вызовах перемещает курсор к следующей строке
    • Когда строки заканчиваются, возвращает false и цикл завершается
  3. Важно: Каждый вызов rows.Scan() должен быть предварен вызовом rows.Next()

Детализированный пример с обработкой ошибок

func getActiveUsers(db *sql.DB) ([]User, error) {
	rows, err := db.Query("SELECT id, name, email FROM users WHERE active = ?", true)
	if err != nil {
		return nil, fmt.Errorf("query failed: %v", err)
	}
	defer rows.Close()

	var users []User

	for rows.Next() {
		var u User
		if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
			// Можно продолжить обработку других строк или прервать
			return nil, fmt.Errorf("scan failed: %v", err)
		}
		users = append(users, u)
	}

	// Проверяем ошибки, которые могли возникнуть во время итерации
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("rows iteration failed: %v", err)
	}

	return users, nil
}

type User struct {
	ID    int
	Name  string
	Email string
}

Что происходит внутри цикла?

  1. Первая итерация:

    • rows.Next() перемещает курсор к первой строке (если она есть)
    • Возвращает true, если строка доступна
    • Выполняется rows.Scan()
  2. Последующие итерации:

    • rows.Next() перемещает курсор к следующей строке
    • Цикл продолжается, пока есть строки
  3. Завершение:

    • Когда строки заканчиваются, rows.Next() возвращает false
    • Цикл прерывается
    • Проверяется rows.Err() на наличие ошибок

Важные нюансы:

  1. Всегда вызывайте defer rows.Close() для освобождения ресурсов
  2. Проверяйте rows.Err() после цикла для выявления ошибок итерации
  3. Не используйте rows.Next() без последующего rows.Scan()
  4. Для пустых результатов цикл не выполнится ни разу

Альтернативный пример с обработкой пустого результата

func printUserCount(db *sql.DB) {
	rows, err := db.Query("SELECT COUNT(*) FROM users")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	// rows.Next() вернет true, даже если только одна строка
	if rows.Next() {
		var count int
		if err := rows.Scan(&count); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("Total users: %d\n", count)
	}

	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}
}

Этот пример показывает, что даже для запросов, возвращающих одну строку (как COUNT), необходимо использовать rows.Next() перед rows.Scan().

func (*Rows) NextResultSet

func (rs *Rows) NextResultSet() bool

NextResultSet подготавливает следующий набор результатов для чтения. Он сообщает, есть ли дальнейшие наборы результатов, или false, если дальнейших наборов результатов нет или если произошла ошибка при переходе к ним. Для различения этих двух случаев следует обратиться к методу Rows.Err.

После вызова NextResultSet перед сканированием всегда следует вызывать метод Rows.Next. Если есть дальнейшие наборы результатов, они могут не содержать строк в наборе результатов.

Функция NextResultSet() используется, когда запрос к базе данных возвращает несколько наборов результатов (например, при выполнении хранимых процедур или пакетных запросов).

Пример

Пример с несколькими наборами результатов

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	_ "github.com/go-sql-driver/mysql" // Импорт драйвера MySQL
)

func main() {
	// Инициализация подключения к базе данных
	db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Запрос, возвращающий несколько наборов результатов
	// (например, хранимая процедура с несколькими SELECT)
	query := `
		SELECT id, name FROM users WHERE active = 1;
		SELECT id, title FROM products WHERE price > 100;
		SELECT COUNT(*) FROM orders WHERE status = 'completed';
	`

	// Выполнение запроса
	rows, err := db.QueryContext(context.Background(), query)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	// Обработка первого набора результатов (пользователи)
	fmt.Println("=== Active Users ===")
	for rows.Next() {
		var id int
		var name string
		if err := rows.Scan(&id, &name); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("ID: %d, Name: %s\n", id, name)
	}
	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}

	// Переход к следующему набору результатов (продукты)
	if rows.NextResultSet() {
		fmt.Println("\n=== Expensive Products ===")
		for rows.Next() {
			var id int
			var title string
			if err := rows.Scan(&id, &title); err != nil {
				log.Fatal(err)
			}
			fmt.Printf("ID: %d, Title: %s\n", id, title)
		}
		if err := rows.Err(); err != nil {
			log.Fatal(err)
		}
	}

	// Переход к следующему набору результатов (количество заказов)
	if rows.NextResultSet() {
		fmt.Println("\n=== Completed Orders Count ===")
		for rows.Next() {
			var count int
			if err := rows.Scan(&count); err != nil {
				log.Fatal(err)
			}
			fmt.Printf("Completed orders: %d\n", count)
		}
		if err := rows.Err(); err != nil {
			log.Fatal(err)
		}
	}

	// Проверяем, есть ли еще наборы результатов
	if rows.NextResultSet() {
		fmt.Println("\nThere are more result sets available")
	} else {
		fmt.Println("\nNo more result sets")
	}
}

Ключевые моменты работы с NextResultSet():

  1. Порядок обработки: Всегда сначала обрабатывайте текущий набор результатов с помощью Next() и Scan(), прежде чем переходить к следующему.

  2. Проверка наличия результатов: NextResultSet() возвращает true, если есть следующий набор результатов, даже если он пустой.

  3. Обработка ошибок: После NextResultSet() всегда проверяйте rows.Err() на наличие ошибок.

  4. Пустые наборы: Набор результатов может не содержать строк - это нормально, просто Next() вернет false сразу.

Альтернативный пример с хранимой процедурой

func callMultiResultStoredProcedure(db *sql.DB) error {
	// Вызов хранимой процедуры, возвращающей несколько наборов результатов
	rows, err := db.Query("CALL get_report_data()")
	if err != nil {
		return err
	}
	defer rows.Close()

	// Обработка всех наборов результатов
	resultSetIndex := 0
	for {
		// Обработка текущего набора результатов
		for rows.Next() {
			// В зависимости от набора результатов используем разную логику сканирования
			switch resultSetIndex {
			case 0:
				var id int
				var name string
				if err := rows.Scan(&id, &name); err != nil {
					return err
				}
				fmt.Printf("User: %d - %s\n", id, name)
			case 1:
				var product string
				var price float64
				if err := rows.Scan(&product, &price); err != nil {
					return err
				}
				fmt.Printf("Product: %s - $%.2f\n", product, price)
			// Можно добавить дополнительные case для других наборов
			}
		}
		if err := rows.Err(); err != nil {
			return err
		}

		// Переход к следующему набору результатов
		if !rows.NextResultSet() {
			break
		}
		resultSetIndex++
	}

	return nil
}

Важные предупреждения:

  1. Не все драйверы баз данных поддерживают несколько наборов результатов.
  2. Всегда проверяйте rows.Err() после NextResultSet().
  3. После перехода к новому набору результатов обязательно вызывайте rows.Next() перед сканированием.
  4. Структура данных в каждом наборе результатов может отличаться.

func (*Rows) Scan

func (rs *Rows) Scan(dest ...any) error

Scan копирует столбцы в текущей строке в значения, на которые указывает dest. Количество значений в dest должно совпадать с количеством столбцов в Rows.

Scan преобразует столбцы, прочитанные из базы данных, в следующие общие типы Go и специальные типы, предоставляемые пакетом sql:

*string
*[]byte
*int, *int8, *int16, *int32, *int64
*uint, *uint8, *uint16, *uint32, *uint64
*bool
*float32, *float64
*interface{}
*RawBytes
*Rows (cursor value)
любой тип, реализующий Scanner (см. документацию по Scanner)

В самом простом случае, если тип значения из исходного столбца является целым, bool или строковым типом T, а dest имеет тип *T, Scan просто присваивает значение через указатель.

Scan также преобразует строковые и числовые типы, если при этом не теряется информация. В то время как Scan преобразует все числа, сканированные из числовых столбцов базы данных, в *string, сканирование в числовые типы проверяется на переполнение. Например, float64 со значением 300 или строка со значением «300» могут быть отсканированы в uint16, но не в uint8, хотя float64(255) или «255» могут быть отсканированы в uint8. Исключением является то, что при сканировании некоторых чисел float64 в строки может произойти потеря информации при преобразовании в строку. В общем случае, сканируйте столбцы с плавающей запятой в *float64.

Если аргумент dest имеет тип *[]byte, Scan сохраняет в этом аргументе копию соответствующих данных. Копия принадлежит вызывающему и может быть изменена и храниться неограниченное время. Копирование можно избежать, используя вместо этого аргумент типа *RawBytes; ограничения на его использование см. в документации по RawBytes.

Если аргумент имеет тип *interface{}, Scan копирует значение, предоставленное базовым драйвером, без преобразования. При сканировании из исходного значения типа []byte в *interface{} создается копия среза, и вызывающая сторона владеет результатом.

Исходные значения типа time.Time могут быть отсканированы в значения типа *time.Time, *interface{}, *string или *[]byte. При преобразовании в два последних используется time.RFC3339Nano.

Источниковые значения типа bool могут быть просканированы в типы *bool, *interface{}, *string, *[]byte или *RawBytes.

Для сканирования в *bool источником могут быть true, false, 1, 0 или строковые входы, которые можно проанализировать с помощью strconv.ParseBool.

Scan также может преобразовать курсор, возвращенный из запроса, такого как «select cursor(select * from my_table) from dual», в значение *Rows, которое само по себе может быть просканировано. Родительский запрос select закроет любой курсор *Rows, если родительский *Rows закрыт.

Если любой из первых аргументов, реализующих Scanner, возвращает ошибку, эта ошибка будет обернута в возвращаемую ошибку.

type Scanner

type Scanner interface {
    // Scan назначает значение из драйвера базы данных.
    //
    // Значение src будет одного из следующих типов:
    //
    //    int64
    //    float64
    //    bool
    //    []byte
    //    string
    //    time.Time
    //    nil - для значений NULL
    //
	// Если значение не может быть сохранено
	// без потери информации.
    //
    // Типы ссылок, такие как []byte, действительны только до следующего вызова Scan
    // и не должны сохраняться. Их базовая память принадлежит драйверу.
    // Если сохранение необходимо, скопируйте их значения перед следующим вызовом Scan.
    Scan(src any) error
}

Scanner — интерфейс, используемый Rows.Scan.

6.10 - Описание типа Stmt database/sql

Stmt — это подготовленное выражение. Stmt безопасно для одновременного использования несколькими goroutines.

type Stmt

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

Stmt — это подготовленное выражение. Stmt безопасно для одновременного использования несколькими goroutines.

Если Stmt подготовлен на Tx или Conn, он будет навсегда привязан к одному базовому соединению. Если Tx или Conn закрывается, Stmt станет непригодным для использования, и все операции будут возвращать ошибку. Если Stmt подготовлен на DB, он будет оставаться пригодным для использования в течение всего срока жизни DB. Когда Stmt необходимо выполнить на новом базовом соединении, он автоматически подготовится на новом соединении.

Пример
package main

import (
	"context"
	"database/sql"
	"log"
	_ "github.com/go-sql-driver/mysql" // Добавляем импорт драйвера
)

func main() {
	// Инициализируем контекст
	ctx := context.Background()
	
	// Инициализируем соединение с базой данных (в реальном коде)
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Проверяем соединение
	if err := db.PingContext(ctx); err != nil {
		log.Fatal(err)
	}

	// Создаем подготовленное выражение
	stmt, err := db.PrepareContext(ctx, "SELECT username FROM users WHERE id = ?")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()

	// Выполняем запрос с параметром
	id := 43
	var username string
	err = stmt.QueryRowContext(ctx, id).Scan(&username)
	switch {
	case err == sql.ErrNoRows:
		log.Printf("no user with id %d", id) // Используем Printf вместо Fatalf чтобы не завершать программу
	case err != nil:
		log.Fatal(err)
	default:
		log.Printf("username is %s\n", username)
	}
}

func (*Stmt) Close

func (s *Stmt) Close() error

Close закрывает оператор.

func (*Stmt) Exec

func (s *Stmt) Exec(args ...any) (Result, error)

Exec выполняет подготовленный оператор с заданными аргументами и возвращает Result, обобщающий результат оператора.

Exec использует context.Background внутренне; чтобы указать контекст, используйте Stmt.ExecContext.

func (*Stmt) ExecContext

func (s *Stmt) ExecContext(ctx context.Context, args ...any) (Result, error)

ExecContext выполняет подготовленное выражение с заданными аргументами и возвращает Result, обобщающий результат выражения.

func (*Stmt) Query

func (s *Stmt) Query(args ...any) (*Rows, error)

Query выполняет подготовленный запрос с заданными аргументами и возвращает результаты запроса в виде *Rows.

Query использует context.Background внутренне; для указания контекста используйте Stmt.QueryContext.

func (*Stmt) QueryContext ¶

func (s *Stmt) QueryContext(ctx context.Context, args ...any) (*Rows, error)

QueryContext выполняет подготовленное запросное выражение с заданными аргументами и возвращает результаты запроса в виде *Rows.

func (*Stmt) QueryRow

func (s *Stmt) QueryRow(args ...any) *Row

QueryRow выполняет подготовленный запрос с заданными аргументами. Если во время выполнения запроса происходит ошибка, она будет возвращена вызовом Scan на возвращенном *Row, который всегда не равен nil. Если запрос не выбирает никаких строк, *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.

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

var name string
err := nameByUseridStmt.QueryRow(id).Scan(&name)

QueryRow использует context.Background внутренне; для указания контекста используйте Stmt.QueryRowContext.

func (*Stmt) QueryRowContext

func (s *Stmt) QueryRowContext(ctx context.Context, args ...any) *Row

QueryRowContext выполняет подготовленный запрос с заданными аргументами. Если во время выполнения запроса происходит ошибка, она будет возвращена вызовом Scan на возвращенном *Row, который всегда не равен nil. Если запрос не выбирает никаких строк, *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.

Пример
package main

import (
	"context"
	"database/sql"
	"log"
	_ "github.com/go-sql-driver/mysql" // Не забудьте импортировать драйвер
)

func main() {
	// 1. Инициализация контекста
	ctx := context.Background()
	
	// 2. Инициализация подключения к БД (пример для MySQL)
	db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/dbname")
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()
	
	// 3. Проверка соединения
	if err := db.PingContext(ctx); err != nil {
		log.Fatal("Failed to ping database:", err)
	}

	// 4. Создание подготовленного выражения
	stmt, err := db.PrepareContext(ctx, "SELECT username FROM users WHERE id = ?")
	if err != nil {
		log.Fatal("Failed to prepare statement:", err)
	}
	defer stmt.Close()

	// 5. Выполнение запроса
	id := 43
	var username string
	err = stmt.QueryRowContext(ctx, id).Scan(&username)
	
	// 6. Обработка результатов
	switch {
	case err == sql.ErrNoRows:
		log.Printf("User with id %d not found", id) // Не фатальная ошибка
	case err != nil:
		log.Fatal("Query failed:", err)
	default:
		log.Printf("Found user: %s (ID: %d)", username, id)
	}
}

6.11 - Описание типа Tx database/sql

Tx — это незавершенная транзакция базы данных.

type Tx

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

Tx — это незавершенная транзакция базы данных.

Транзакция должна заканчиваться вызовом Tx.Commit или Tx.Rollback.

После вызова Tx.Commit или Tx.Rollback все операции по транзакции завершаются с ошибкой ErrTxDone.

Инструкции, подготовленные для транзакции вызовом методов Tx.Prepare или Tx.Stmt транзакции, закрываются вызовом Tx.Commit или Tx.Rollback.

func (*Tx) Commit

func (tx *Tx) Commit() error

Commit фиксирует транзакцию.

func (*Tx) Exec

func (tx *Tx) Exec(query string, args ...any) (Result, error)

Exec выполняет запрос, который не возвращает строки. Например: INSERT и UPDATE.

Exec использует context.Background внутренне; для указания контекста используйте Tx.ExecContext.

func (*Tx) ExecContext

func (tx *Tx) ExecContext(ctx context.Context, query string, args ...any) (Result, error)

ExecContext выполняет запрос, который не возвращает строки. Например: INSERT и UPDATE.

Пример
package main

import (
	"context"
	"database/sql"
	"log"
	_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)

func main() {
	// Инициализация подключения к БД
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()

	// Проверка соединения
	ctx := context.Background()
	if err := db.PingContext(ctx); err != nil {
		log.Fatal("Database connection failed:", err)
	}

	// Начало транзакции
	tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
	if err != nil {
		log.Fatal("Failed to begin transaction:", err)
	}

	// Выполнение операций в транзакции
	id := 37
	_, execErr := tx.ExecContext(ctx, "UPDATE users SET status = ? WHERE id = ?", "paid", id)
	if execErr != nil {
		// Попытка отката при ошибке
		if rollbackErr := tx.Rollback(); rollbackErr != nil {
			log.Printf("UPDATE failed: %v, rollback also failed: %v", execErr, rollbackErr)
		} else {
			log.Printf("UPDATE failed, transaction rolled back: %v", execErr)
		}
		return
	}

	// Фиксация транзакции
	if err := tx.Commit(); err != nil {
		log.Fatal("Failed to commit transaction:", err)
	}

	log.Println("Transaction completed successfully")
}

func (*Tx) Prepare

func (tx *Tx) Prepare(query string) (*Stmt, error)

Prepare создает подготовленное выражение для использования в транзакции.

Возвращаемое выражение работает в рамках транзакции и будет закрыто после фиксации или отката транзакции.

Чтобы использовать существующее подготовленное выражение в этой транзакции, см. Tx.Stmt.

Prepare использует context.Background внутренне; чтобы указать контекст, используйте Tx.PrepareContext.

Пример
package main

import (
	"context"
	"database/sql"
	"log"
	_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)

func main() {
	// Инициализация подключения к БД
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname?parseTime=true")
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()

	// Проверка соединения
	if err := db.Ping(); err != nil {
		log.Fatal("Database connection failed:", err)
	}

	projects := []struct {
		mascot  string
		release int
	}{
		{"tux", 1991},
		{"duke", 1996},
		{"gopher", 2009},
		{"moby dock", 2013},
	}

	// Начало транзакции
	ctx := context.Background()
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		log.Fatal("Failed to begin transaction:", err)
	}
	// Откат в случае ошибки (игнорируется если был Commit)
	defer func() {
		if err := tx.Rollback(); err != nil && err != sql.ErrTxDone {
			log.Printf("Warning: rollback failed: %v", err)
		}
	}()

	// Подготовка выражения
	stmt, err := tx.PrepareContext(ctx, "INSERT INTO projects(id, mascot, release, category) VALUES(?, ?, ?, ?)")
	if err != nil {
		log.Fatal("Failed to prepare statement:", err)
	}
	defer stmt.Close()

	// Вставка данных
	for id, project := range projects {
		if _, err := stmt.ExecContext(ctx, id+1, project.mascot, project.release, "open source"); err != nil {
			log.Fatal("Failed to insert project:", err)
		}
	}

	// Фиксация транзакции
	if err := tx.Commit(); err != nil {
		log.Fatal("Failed to commit transaction:", err)
	}

	log.Println("Successfully inserted", len(projects), "projects")
}

Дополнительные рекомендации:

  1. Параметры подключения:

    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(5*time.Minute)
    
  2. Пакетная вставка: Для большого количества данных рассмотрите возможность:

    // Начало пакетной вставки
    values := make([]interface{}, 0, len(projects)*4)
    query := "INSERT INTO projects(id, mascot, release, category) VALUES"
    
    for id, project := range projects {
        if id > 0 {
            query += ","
        }
        query += "(?, ?, ?, ?)"
        values = append(values, id+1, project.mascot, project.release, "open source")
    }
    
    if _, err := tx.ExecContext(ctx, query, values...); err != nil {
        log.Fatal(err)
    }
    
  3. Таймауты:

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    
  4. Валидация данных: Добавьте проверку данных перед вставкой:

    if project.release < 1990 || project.release > time.Now().Year() {
        log.Printf("Invalid release year for %s: %d", project.mascot, project.release)
        continue
    }
    

func (*Tx) PrepareContext

func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error)

PrepareContext создает подготовленное выражение для использования в транзакции.

Возвращенное выражение работает в рамках транзакции и будет закрыто после фиксации или отката транзакции.

Чтобы использовать существующее подготовленное выражение в этой транзакции, см. Tx.Stmt.

Предоставленный контекст будет использоваться для подготовки контекста, а не для выполнения возвращаемого оператора. Возвращаемый оператор будет выполняться в контексте транзакции.

func (*Tx) Query

func (tx *Tx) Query(query string, args ...any) (*Rows, error)

Query выполняет запрос, который возвращает строки, обычно SELECT.

Query использует context.Background внутренне; чтобы указать контекст, используйте Tx.QueryContext.

func (*Tx) QueryContext

func (tx *Tx) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)

QueryContext выполняет запрос, который возвращает строки, обычно SELECT.

func (*Tx) QueryRow

func (tx *Tx) QueryRow(query string, args ...any) *Row

QueryRow выполняет запрос, который должен вернуть не более одной строки. QueryRow всегда возвращает значение, отличное от nil. Ошибки откладываются до вызова метода Scan Row. Если запрос не выбирает ни одной строки, *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.

QueryRow использует context.Background внутренне; для указания контекста используйте Tx.QueryRowContext.

func (*Tx) QueryRowContext

func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...any) *Row

QueryRowContext выполняет запрос, который должен вернуть не более одной строки. QueryRowContext всегда возвращает значение, отличное от nil. Ошибки откладываются до вызова метода Scan Row. Если запрос не выбирает ни одной строки, *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.

func (*Tx) Rollback

func (tx *Tx) Rollback() error

Rollback прерывает транзакцию.

Пример

код транзакции для обновления водителей и заказов

package main

import (
	"context"
	"database/sql"
	"log"
	_ "github.com/lib/pq" // Импорт драйвера PostgreSQL
)

func main() {
	// Инициализация подключения к БД
	connStr := "user=postgres dbname=mydb sslmode=disable"
	db, err := sql.Open("postgres", connStr)
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()

	// Проверка соединения
	if err := db.Ping(); err != nil {
		log.Fatal("Database connection failed:", err)
	}

	// Создаем контекст с таймаутом
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Начало транзакции
	tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
	if err != nil {
		log.Fatal("Failed to begin transaction:", err)
	}
	// Гарантированный откат при ошибках
	defer func() {
		if err := tx.Rollback(); err != nil && err != sql.ErrTxDone {
			log.Printf("Warning: rollback error: %v", err)
		}
	}()

	id := 53

	// 1. Обновление статуса водителя
	_, err = tx.ExecContext(ctx, "UPDATE drivers SET status = $1 WHERE id = $2", "assigned", id)
	if err != nil {
		log.Printf("Failed to update driver status: %v", err)
		return
	}

	// 2. Назначение водителя на заказы
	_, err = tx.ExecContext(ctx, "UPDATE pickups SET driver_id = $1 WHERE driver_id IS NULL", id)
	if err != nil {
		log.Printf("Failed to assign driver to pickups: %v", err)
		return
	}

	// Фиксация транзакции
	if err := tx.Commit(); err != nil {
		log.Fatal("Failed to commit transaction:", err)
	}

	log.Printf("Successfully assigned driver %d and updated pickup orders", id)
}

Дополнительные улучшения:

  1. Безопасность UPDATE:

    // Ограничиваем обновление только незанятыми заказами
    "UPDATE pickups SET driver_id = $1 WHERE driver_id IS NULL"
    
  2. Проверка результата:

    res, err := tx.ExecContext(...)
    if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 {
        log.Printf("Warning: no rows were updated")
    }
    
  3. Повторные попытки:

    maxRetries := 3
    for i := 0; i < maxRetries; i++ {
        // ... выполнение транзакции ...
        if err == nil {
            break
        }
        if shouldRetry(err) {
            continue
        }
        break
    }
    
  4. Connection Pool:

    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(5*time.Minute)
    

func (*Tx) Stmt

func (tx *Tx) Stmt(stmt *Stmt) *Stmt

Stmt возвращает подготовленное заявление, специфичное для транзакции, из существующего заявления.

Пример:

updateMoney, err := db.Prepare(«UPDATE balance SET money=money+? WHERE id=)
...
tx, err := db.Begin()...

res, err := tx.Stmt(updateMoney).Exec(123.45, 98293203)

Возвращенное выражение работает в рамках транзакции и будет закрыто после фиксации или отката транзакции.

Stmt использует context.Background внутри всегда; для указания контекста используйте Tx.StmtContext.

func (*Tx) StmtContext

func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt

StmtContext возвращает подготовленное выражение, специфичное для транзакции, из существующего выражения.

Пример:

updateMoney, err := db.Prepare(«UPDATE balance SET money=money+? WHERE id=?») … tx, err := db.Begin()…

res, err := tx.StmtContext(ctx, updateMoney).Exec(123.45, 98293203) Предоставленный контекст используется для подготовки оператора, а не для его выполнения.

Возвращенный оператор работает в рамках транзакции и будет закрыт после фиксации или отката транзакции.

type TxOptions

type TxOptions struct {
    // Isolation — уровень изоляции транзакции.
    // Если равен нулю, используется уровень по умолчанию драйвера или базы данных.
    Isolation IsolationLevel
    ReadOnly  bool
}

TxOptions содержит параметры транзакции, которые будут использоваться в DB.BeginTx.

7 - Описание пакета flag языка программирования Go

Пакет flag реализует разбор флагов командной строки.

Использование

Определите флаги с помощью flag.String, Bool, Int и т. д.

Это объявляет целочисленный флаг -n, хранящийся в указателе nFlag, с типом *int:

import «flag»
var nFlag = flag.Int("n", 1234, "сообщение справки для флага n")

При желании можно привязать флаг к переменной с помощью функций Var().

var flagvar int
func init() {
    flag.IntVar(&flagvar, "flagname", 1234, "сообщение справки для flagname")
}

Или можно создать пользовательские флаги, которые удовлетворяют интерфейсу Value (с указателями-приемниками), и связать их с разбором флагов с помощью

flag.Var(&flagVal, "name", "сообщение справки для flagname")

Для таких флагов значением по умолчанию является просто начальное значение переменной.

После определения всех флагов вызовите

flag.Parse()

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

Затем флаги можно использовать напрямую. Если вы используете сами флаги, все они являются указателями; если вы связываете их с переменными, они являются значениями.

fmt.Println(«ip имеет значение », *ip)
fmt.Println(«flagvar имеет значение », flagvar)

После разбора аргументы, следующие за флагами, доступны как срез flag.Args или по отдельности как flag.Arg(i). Аргументы индексируются от 0 до flag.NArg-1.

Синтаксис флагов командной строки ¶

Допускаются следующие формы:

-flag
--flag   // также допускаются двойные тире
-flag=x
-flag x  // только небулевые флаги

Можно использовать один или два тире; они эквивалентны. Последняя форма не допускается для булевых флагов, поскольку значение команды

cmd -x *

где * — подстановочный знак оболочки Unix, изменится, если есть файл с именем 0, false и т. д. Для отключения булевого флага необходимо использовать форму -flag=false.

Разбор флагов прекращается непосредственно перед первым аргументом, не являющимся флагом («-» является аргументом, не являющимся флагом), или после терминатора «–».

  • Целочисленные флаги принимают значения 1234, 0664, 0x1234 и могут быть отрицательными.
  • Булевы флаги могут быть: 1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False

Флаги продолжительности принимают любой ввод, допустимый для time.ParseDuration.

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

Пример
package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"strings"
	"time"
)

// Пример 1: Флаг строки с именем "species" и значением по умолчанию "gopher"
var species = flag.String("species", "gopher", "the species we are studying")

// Пример 2: Два флага (длинный и короткий) с общей переменной
var gopherType string

func init() {
	const (
		defaultGopher = "pocket"
		usage         = "the variety of gopher"
	)
	flag.StringVar(&gopherType, "gopher_type", defaultGopher, usage)
	flag.StringVar(&gopherType, "g", defaultGopher, usage+" (shorthand)")
}

// Пример 3: Пользовательский тип флага - срез интервалов времени
type interval []time.Duration

// String реализует интерфейс flag.Value
func (i *interval) String() string {
	if len(*i) == 0 {
		return ""
	}
	var b strings.Builder
	for idx, d := range *i {
		if idx > 0 {
			b.WriteString(", ")
		}
		b.WriteString(d.String())
	}
	return b.String()
}

// Set реализует интерфейс flag.Value
func (i *interval) Set(value string) error {
	// Разрешаем множественные установки флага для накопления значений
	for _, dt := range strings.Split(value, ",") {
		duration, err := time.ParseDuration(strings.TrimSpace(dt))
		if err != nil {
			return fmt.Errorf("invalid duration %q: %v", dt, err)
		}
		*i = append(*i, duration)
	}
	return nil
}

var intervalFlag interval

func init() {
	flag.Var(&intervalFlag, "deltaT", "comma-separated list of intervals to use between events")
}

func main() {
	// Парсим флаги
	flag.Parse()

	// Выводим значения флагов
	fmt.Println("Species:", *species)
	fmt.Println("Gopher type:", gopherType)
	fmt.Println("Intervals:", intervalFlag.String())

	// Пример использования
	if len(intervalFlag) == 0 {
		fmt.Println("No intervals specified. Use -deltaT flag")
		os.Exit(1)
	}

	// Демонстрация работы с интервалами
	for i, d := range intervalFlag {
		fmt.Printf("Event %d will happen after %v\n", i+1, d)
		time.Sleep(d) // В реальном коде здесь была бы полезная работа
	}
}

Как использовать:

  1. Сборка:
go build -o app
  1. Примеры запуска:
# Простой запуск
./app -species=rabbit -gopher_type=arctic -deltaT=1s,2s,3s

# Использование короткого флага
./app -g=arctic -deltaT=500ms,1s

# Просмотр помощи
./app -help

Переменные

var ErrHelp = errors.New("flag: help requested")

ErrHelp - это ошибка, возвращаемая при вызове флага -help или -h, но такой флаг не определен.

var Usage = func() {
fmt.Fprintf(CommandLine.Output(), "Usage of %s:\n", os.Args[0])
PrintDefaults()
}

Usage печатает сообщение об использовании всех определенных флагов командной строки в выходной файл CommandLine, который по умолчанию является os.Stderr.

Функция вызывается при возникновении ошибки во время разбора флагов.

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

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

7.1 - Функции пакета flag

Основные функции пакета flag

func Arg

func Arg(i int) string

Arg возвращает i-й аргумент командной строки. Arg(0) — это первый аргумент, оставшийся после обработки флагов. Arg возвращает пустую строку, если запрошенный элемент не существует.

func Args

func Args() []string

Args возвращает аргументы командной строки, не являющиеся флагами.

func Bool

func Bool(name string, value bool, usage string) *bool

Bool определяет флаг bool с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной bool, в которой хранится значение флага.

func BoolFunc

добавлено в go1.21.0

func BoolFunc(name, usage string, fn func(string) error)

BoolFunc определяет флаг с указанным именем и строкой использования без требования значений. Каждый раз, когда флаг встречается, вызывается fn со значением флага. Если fn возвращает ошибку, отличную от nil, она будет рассматриваться как ошибка разбора значения флага.

Пример
package main

import (
	"flag"
	"fmt"
	"os"
)

func main() {
	// Создаем новый набор флагов
	fs := flag.NewFlagSet("ExampleBoolFunc", flag.ContinueOnError)
	fs.SetOutput(os.Stdout) // Выводим ошибки в stdout

	// Регистрируем BoolFunc флаг
	fs.BoolFunc("log", "logs a dummy message (default: false)", func(s string) error {
		if s == "" {
			// Без значения - просто флаг присутствует
			fmt.Println("dummy message: flag is set")
			return nil
		}

		// С значением - выводим его
		fmt.Printf("dummy message: %s\n", s)
		return nil
	})

	// Пример 1: Флаг без значения
	fmt.Println("\nCase 1: -log (no value)")
	if err := fs.Parse([]string{"-log"}); err != nil {
		fmt.Println("Error:", err)
	}

	// Пример 2: Флаг с произвольным значением
	fmt.Println("\nCase 2: -log=0")
	if err := fs.Parse([]string{"-log=0"}); err != nil {
		fmt.Println("Error:", err)
	}

	// Пример 3: Неизвестный флаг
	fmt.Println("\nCase 3: -unknown")
	if err := fs.Parse([]string{"-unknown"}); err != nil {
		fmt.Println("Error:", err)
	}

	// Пример 4: Вывод помощи
	fmt.Println("\nCase 4: -help")
	fs.PrintDefaults()
}

Запуск программы:

go run main.go -log -log=hello

Результат:

Case 1: -log (no value)
dummy message: flag is set

Case 2: -log=0
dummy message: 0

Case 3: -unknown
Error: flag provided but not defined: -unknown

Case 4: -help
  -log
        logs a dummy message (default: false)

func BoolVar

func BoolVar(p *bool, name string, value bool, usage string)

BoolVar определяет флаг bool с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную bool, в которой будет храниться значение флага.

func Duration

func Duration(name string, value time.Duration, usage string) *time.Duration

Duration определяет флаг time.Duration с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной time.Duration, в которой хранится значение флага. Флаг принимает значение, допустимое для time.ParseDuration.

func DurationVar

func DurationVar(p *time.Duration, name string, value time.Duration, usage string)

DurationVar определяет флаг time.Duration с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную time.Duration, в которой хранится значение флага. Флаг принимает значение, допустимое для time.ParseDuration.

func Float64

func Float64(name string, value float64, usage string) *float64

Float64 определяет флаг float64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной float64, в которой хранится значение флага.

func Float64Var

func Float64Var(p *float64, name string, value float64, usage string)

Float64Var определяет флаг float64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную float64, в которой хранится значение флага.

func Float64Var

func Float64Var(p *float64, name string, value float64, usage string)

Float64Var определяет флаг float64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную float64, в которой будет храниться значение флага.

func Func

func Func(name, usage string, fn func(string) error)

Func определяет флаг с указанным именем и строкой использования. Каждый раз, когда флаг встречается, вызывается функция fn со значением флага. Если fn возвращает ошибку, отличную от nil, это будет расценено как ошибка разбора значения флага.

Пример
package main

import (
	"errors"
	"flag"
	"fmt"
	"net"
	"os"
)

func main() {
	// Создаем новый набор флагов
	fs := flag.NewFlagSet("ExampleFunc", flag.ContinueOnError)
	fs.SetOutput(os.Stdout)

	// Переменная для хранения IP
	var ip net.IP

	// Регистрируем функцию-парсер для IP
	fs.Func("ip", "`IP address` to parse (e.g. 192.168.1.1 or ::1)", func(s string) error {
		parsedIP := net.ParseIP(s)
		if parsedIP == nil {
			return fmt.Errorf("invalid IP address format: %q", s)
		}
		ip = parsedIP
		return nil
	})

	// Тест 1: Валидный IPv4 адрес
	fmt.Println("Test case 1: Valid IPv4 address")
	if err := fs.Parse([]string{"-ip", "127.0.0.1"}); err != nil {
		fmt.Printf("Error: %v\n", err)
	} else {
		fmt.Printf("Parsed IP: %v\n", ip)
		fmt.Printf("Is loopback: %t\n\n", ip.IsLoopback())
	}

	// Тест 2: Невалидный IP адрес
	fmt.Println("Test case 2: Invalid IP address")
	if err := fs.Parse([]string{"-ip", "256.0.0.1"}); err != nil {
		fmt.Printf("Error: %v\n\n", err)
	} else {
		fmt.Printf("Parsed IP: %v\n", ip)
	}

	// Тест 3: Валидный IPv6 адрес
	fmt.Println("Test case 3: Valid IPv6 address")
	if err := fs.Parse([]string{"-ip", "::1"}); err != nil {
		fmt.Printf("Error: %v\n", err)
	} else {
		fmt.Printf("Parsed IP: %v\n", ip)
		fmt.Printf("Is loopback: %t\n\n", ip.IsLoopback())
	}

	// Вывод справки
	fmt.Println("Help message:")
	fs.PrintDefaults()
}

Вывод программы:

Test case 1: Valid IPv4 address
Parsed IP: 127.0.0.1
Is loopback: true

Test case 2: Invalid IP address
Error: invalid IP address format: "256.0.0.1"

Test case 3: Valid IPv6 address
Parsed IP: ::1
Is loopback: true

Help message:
  -ip IP address
        `IP address` to parse (e.g. 192.168.1.1 or ::1)

func Int

func Int(name string, value int, usage string) *int

Int определяет флаг int с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной int, в которой хранится значение флага.

func Int64

func Int64(name string, value int64, usage string) *int64

Int64 определяет флаг int64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной int64, в которой хранится значение флага.

func Int64Var

func Int64Var(p *int64, name string, value int64, usage string)

Int64Var определяет флаг int64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную int64, в которой хранится значение флага.

func IntVar

func IntVar(p *int, name string, value int, usage string)

IntVar определяет флаг int с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную int, в которой будет храниться значение флага.

func NArg

func NArg() int

NArg — количество аргументов, оставшихся после обработки флагов.

func NFlag

func NFlag() int

NFlag возвращает количество установленных флагов командной строки.

func Parse

func Parse()

Parse анализирует флаги командной строки из os.Args[1:]. Должен вызываться после определения всех флагов и до доступа к флагам из программы.

func Parsed

func Parsed() bool

Parsed сообщает, были ли проанализированы флаги командной строки.

func PrintDefaults

func PrintDefaults()

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

-x int
    usage-message-for-x (default 7)

Сообщение об использовании будет отображаться в отдельной строке для всех флагов, кроме флагов типа bool с однобайтовым именем.

Для флагов типа bool тип опускается, и если имя флага состоит из одного байта, сообщение об использовании отображается в той же строке.

Значение по умолчанию в скобках опускается, если значение по умолчанию для типа равно нулю.

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

flag.String("I", "", "search `directory` for include files")

вывод будет следующим

-I directory
	search directory for include files.

Чтобы изменить место назначения для сообщений флага, вызовите CommandLine.SetOutput.

func Set

func Set(name, value string) error

Set устанавливает значение флага командной строки с указанным именем.

func String

func String(name string, value string, usage string) *string

String определяет строковый флаг с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес строковой переменной, в которой хранится значение флага.

func StringVar

func StringVar(p *string, name string, value string, usage string)

StringVar определяет строковый флаг с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на строковую переменную, в которой хранится значение флага.

func TextVar

func TextVar(p encoding.TextUnmarshaler, name string, value encoding.TextMarshaler, usage string)

TextVar определяет флаг с указанным именем, значением по умолчанию и строкой использования. Аргумент p должен быть указателем на переменную, которая будет хранить значение флага, и p должен реализовывать encoding.TextUnmarshaler. Если флаг используется, его значение будет передано методу UnmarshalText p. Тип значения по умолчанию должен быть таким же, как тип p.

Пример
package main

import (
	"flag"
	"fmt"
	"net"
	"os"
)

func main() {
	// Создаем новый набор флагов
	fs := flag.NewFlagSet("ExampleTextVar", flag.ContinueOnError)
	fs.SetOutput(os.Stdout)

	// Переменная для хранения IP с значением по умолчанию
	var ip net.IP = net.IPv4(192, 168, 0, 100)

	// Регистрируем TextVar флаг для парсинга IP
	fs.TextVar(&ip, "ip", ip, "`IP address` to parse (e.g. 192.168.1.1 or ::1)")

	// Тест 1: Валидный IPv4 адрес
	fmt.Println("Test case 1: Valid IPv4 address (127.0.0.1)")
	if err := fs.Parse([]string{"-ip", "127.0.0.1"}); err != nil {
		fmt.Printf("Error: %v\n", err)
	} else {
		fmt.Printf("Parsed IP: %v\n", ip)
		fmt.Printf("Is loopback: %t\n\n", ip.IsLoopback())
	}

	// Тест 2: Невалидный IP адрес
	fmt.Println("Test case 2: Invalid IP address (256.0.0.1)")
	ip = nil // Сбрасываем предыдущее значение
	if err := fs.Parse([]string{"-ip", "256.0.0.1"}); err != nil {
		fmt.Printf("Error: %v\n\n", err)
	} else {
		fmt.Printf("Parsed IP: %v\n\n", ip)
	}

	// Тест 3: Вывод справки
	fmt.Println("Test case 3: Help message")
	fs.PrintDefaults()
}

Вывод программы:

Test case 1: Valid IPv4 address (127.0.0.1)
Parsed IP: 127.0.0.1
Is loopback: true

Test case 2: Invalid IP address (256.0.0.1)
Error: invalid IP address: 256.0.0.1

Test case 3: Help message
  -ip IP address
        `IP address` to parse (e.g. 192.168.1.1 or ::1) (default 192.168.0.100)

func Uint

func Uint(name string, value uint, usage string) *uint

Uint определяет флаг uint с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной uint, которая хранит значение флага.

func Uint64

func Uint64(name string, value uint64, usage string) *uint64

Uint64 определяет флаг uint64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной uint64, в которой хранится значение флага.

func Uint64Var

func Uint64Var(p *uint64, name string, value uint64, usage string)

Uint64Var определяет флаг uint64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную uint64, в которой будет храниться значение флага.

func UintVar

func UintVar(p *uint, name string, value uint, usage string)

UintVar определяет флаг uint с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную uint, в которой будет храниться значение флага.

func UnquoteUsage

func UnquoteUsage(flag *Flag) (name string, usage string)

UnquoteUsage извлекает имя в обратных кавычках из строки использования для флага и возвращает его и использование без кавычек. При заданном “a `name` to show” возвращает (“name”, “a name to show”). Если обратные кавычки отсутствуют, имя является обоснованным предположением о типе значения флага или пустой строкой, если флаг является булевым.

func Var

func Var(value Value, name string, usage string)

Var определяет флаг с указанным именем и строкой использования. Тип и значение флага представлены первым аргументом типа Value, который обычно содержит пользовательскую реализацию Value. Например, вызывающий может создать флаг, который преобразует строку, разделенную запятыми, в набор строк, предоставив набору методы Value; в частности, Set разложит строку, разделенную запятыми, на набор.

Пример

Пример использования flag.Var() с пользовательским типом

package main

import (
	"errors"
	"flag"
	"fmt"
	"strings"
)

// StringSet - пользовательский тип для множества строк
type StringSet map[string]struct{}

// String реализует интерфейс flag.Value
func (s StringSet) String() string {
	return strings.Join(s.ToSlice(), ",")
}

// Set реализует интерфейс flag.Value
func (s StringSet) Set(value string) error {
	if len(s) > 0 {
		return errors.New("StringSet already set")
	}
	
	for _, item := range strings.Split(value, ",") {
		item = strings.TrimSpace(item)
		if item != "" {
			s[item] = struct{}{}
		}
	}
	return nil
}

// ToSlice преобразует StringSet в срез строк
func (s StringSet) ToSlice() []string {
	result := make([]string, 0, len(s))
	for k := range s {
		result = append(result, k)
	}
	return result
}

func main() {
	// Инициализируем наш StringSet
	var tags StringSet = make(map[string]struct{})

	// Регистрируем флаг через flag.Var
	flag.Var(&tags, "tags", "comma-separated list of tags")

	// Парсим аргументы командной строки
	flag.Parse()

	// Выводим результат
	fmt.Println("Tags count:", len(tags))
	fmt.Println("Tags list:", tags.ToSlice())

	// Примеры запуска:
	//   go run main.go -tags=go,programming,example
	//   go run main.go -tags="rust, systems programming"
}

Ключевые компоненты:

  1. Пользовательский тип StringSet:

    • Реализует интерфейс flag.Value с методами String() и Set()
    • Хранит уникальные строки как ключи map
  2. Метод Set():

    • Разбивает строку по запятым
    • Игнорирует пустые элементы
    • Возвращает ошибку при повторной установке
  3. Метод String():

    • Формирует строку из элементов через запятую
    • Используется для вывода значения по умолчанию
  4. Дополнительный метод ToSlice():

    • Удобное преобразование в срез строк

Как это работает:

  1. При вызове flag.Var() пакет flag получает:

    • Указатель на наш StringSet
    • Имя флага (“tags”)
    • Описание для справки
  2. При парсинге аргументов:

    • Для флага -tags=go,programming вызывается Set("go,programming")
    • Наш метод разбивает строку и заполняет map
  3. При выводе справки:

    • Вызывается String() для показа текущего значения

func Visit

func Visit(fn func(*Flag))

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

func VisitAll

func VisitAll(fn func(*Flag))

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

7.2 - Типы пакета flag

Типы и их функции, включенные в пакет flag

type ErrorHandling

type ErrorHandling int

ErrorHandling определяет, как будет вести себя FlagSet.Parse в случае неудачи разбора.

const (
	ContinueOnError ErrorHandling = iota // Возвращаем описательную ошибку.
	ExitOnError // Вызов os.Exit(2) или для -h/-help Exit(0).
	PanicOnError // Вызываем панику с описанием ошибки.
)

Эти константы заставляют FlagSet.Parse вести себя так, как описано в случае неудачи разбора.

type Flag

type Flag struct {
Name string // имя в командной строке
Usage string // справочное сообщение
Value Value // значение по умолчанию
DefValue string // значение по умолчанию (в виде текста); для сообщения об использовании
}

Флаг представляет состояние флага.

func Lookup

func Lookup(name string) *Flag

Lookup возвращает структуру Flag именованного флага командной строки, возвращая nil, если таковой не существует.

type FlagSet

type FlagSet struct {
	// Usage - это функция, вызываемая при возникновении ошибки во время разбора флагов.
	// Поле представляет собой функцию (не метод), которая может быть изменена для указания на
	// пользовательский обработчик ошибок. То, что произойдет после вызова Usage, зависит
	// от настройки ErrorHandling; для командной строки по умолчанию
	// установлено значение ExitOnError, которое завершает работу программы после вызова Usage.
	Usage func()
	// содержит отфильтрованные или неэкспонированные поля
}

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

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

Пример
package main

import (
	"flag"
	"fmt"
	"os"
	"time"
)

func main() {
	if len(os.Args) < 2 {
		printUsage()
		os.Exit(1)
	}

	switch os.Args[1] {
	case "start":
		start(os.Args[2:])
	case "stop":
		stop(os.Args[2:])
	case "help", "-h", "--help":
		printUsage()
	default:
		fmt.Printf("error: unknown command %q\n", os.Args[1])
		printUsage()
		os.Exit(1)
	}
}

func start(args []string) {
	fs := flag.NewFlagSet("start", flag.ExitOnError)
	addr := fs.String("addr", ":8080", "address to listen on")
	logLevel := fs.String("log-level", "info", "log level (debug, info, warn, error)")

	if err := fs.Parse(args); err != nil {
		fs.Usage()
		os.Exit(1)
	}

	fmt.Printf("Starting server on %s with log level %s\n", *addr, *logLevel)
	// Здесь была бы реальная логика запуска сервера
}

func stop(args []string) {
	fs := flag.NewFlagSet("stop", flag.ExitOnError)
	timeout := fs.Duration("timeout", 5*time.Second, "stop timeout duration")
	force := fs.Bool("force", false, "force shutdown")

	if err := fs.Parse(args); err != nil {
		fs.Usage()
		os.Exit(1)
	}

	fmt.Printf("Stopping server (timeout=%v, force=%t)\n", *timeout, *force)
	// Здесь была бы реальная логика остановки сервера
}

func printUsage() {
	fmt.Println("Usage:")
	fmt.Println("  httpd start [-addr :port] [-log-level level]")
	fmt.Println("  httpd stop [-timeout duration] [-force]")
	fmt.Println("  httpd help")
	fmt.Println("\nFlags:")
	fmt.Println("  -h, --help  Show this help message")
}

Как использовать:

  1. Запуск сервера:
./httpd start -addr :9999 -log-level debug
  1. Остановка сервера:
./httpd stop -timeout 10s -force
  1. Просмотр справки:
./httpd help
# или
./httpd -h
var CommandLine *FlagSet

CommandLine - это набор флагов командной строки по умолчанию, взятый из os.Args. Функции верхнего уровня, такие как BoolVar, Arg и так далее, являются обертками для методов CommandLine.

func NewFlagSet

func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet

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

Пример

Объяснение flag.NewFlagSet с примерами

Функция flag.NewFlagSet создает новый независимый набор флагов, что особенно полезно для реализации:

  1. Подкоманд в CLI-приложениях (например, git commit, docker run)
  2. Изолированных групп флагов
  3. Повторного использования флагов в разных контекстах

Параметры:

  • name - имя набора флагов (используется в сообщениях об ошибках и справке)
  • errorHandling - стратегия обработки ошибок:
    • flag.ContinueOnError - продолжить выполнение после ошибки
    • flag.ExitOnError - вызвать os.Exit(2) при ошибке
    • flag.PanicOnError - вызвать panic при ошибке

Пример 1: Базовое использование

package main

import (
	"flag"
	"fmt"
)

func main() {
	// Создаем новый набор флагов
	fs := flag.NewFlagSet("example", flag.ExitOnError)

	// Добавляем флаги
	verbose := fs.Bool("verbose", false, "enable verbose output")
	port := fs.Int("port", 8080, "port to listen on")

	// Парсим аргументы
	fs.Parse([]string{"-verbose", "-port", "9090"})

	// Используем значения
	fmt.Printf("Verbose: %v, Port: %d\n", *verbose, *port)
}

Пример 2: Подкоманды (как в git/docker)

package main

import (
	"flag"
	"fmt"
	"os"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Expected 'greet' or 'time' subcommands")
		os.Exit(1)
	}

	switch os.Args[1] {
	case "greet":
		// Набор флагов для команды greet
		greetCmd := flag.NewFlagSet("greet", flag.ExitOnError)
		name := greetCmd.String("name", "World", "name to greet")
		greetCmd.Parse(os.Args[2:])
		fmt.Printf("Hello, %s!\n", *name)

	case "time":
		// Набор флагов для команды time
		timeCmd := flag.NewFlagSet("time", flag.ExitOnError)
		format := timeCmd.String("format", "15:04:05", "time format")
		timeCmd.Parse(os.Args[2:])
		fmt.Println("Current time:", time.Now().Format(*format))

	default:
		fmt.Printf("Unknown command %q\n", os.Args[1])
		os.Exit(1)
	}
}

Пример 3: Разные стратегии обработки ошибок

package main

import (
	"flag"
	"fmt"
)

func main() {
	// ContinueOnError - продолжить после ошибки
	fs1 := flag.NewFlagSet("continue", flag.ContinueOnError)
	fs1.Int("port", 0, "port number")
	err := fs1.Parse([]string{"-port", "invalid"})
	fmt.Println("ContinueOnError:", err)

	// ExitOnError - выход при ошибке
	fs2 := flag.NewFlagSet("exit", flag.ExitOnError)
	fs2.Int("port", 0, "port number")
	// В реальном коде это вызвало бы os.Exit(2)
	fmt.Print("ExitOnError: ")
	fs2.Parse([]string{"-port", "invalid"})
}

Рекомендации по использованию:

  1. Для CLI-приложений используйте flag.ExitOnError
  2. Для библиотек лучше flag.ContinueOnError
  3. Имя набора флагов помогает пользователю понять контекст ошибки
  4. Можно создавать несколько независимых наборов флагов в одной программе

func (*FlagSet) Arg

func (f *FlagSet) Arg(i int) string

Arg возвращает i-й аргумент. Arg(0) — это первый оставшийся аргумент после обработки флагов. Arg возвращает пустую строку, если запрошенный элемент не существует.

func (*FlagSet) Args

func (f *FlagSet) Args() []string

Args возвращает аргументы, не являющиеся флагами.

func (*FlagSet) Bool

func (f *FlagSet) Bool(name string, value bool, usage string) *bool

Bool определяет флаг bool с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной bool, в которой хранится значение флага.

func (*FlagSet) BoolFunc

func (f *FlagSet) BoolFunc(name, usage string, fn func(string) error)

BoolFunc определяет флаг с указанным именем и строкой использования без требования значений. Каждый раз, когда флаг встречается, вызывается fn со значением флага. Если fn возвращает ошибку, отличную от nil, она будет рассматриваться как ошибка разбора значения флага.

func (*FlagSet) BoolVar

func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string)

BoolVar определяет флаг bool с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную bool, в которой будет храниться значение флага.

func (*FlagSet) Duration

func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration

Duration определяет флаг time.Duration с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной time.Duration, в которой хранится значение флага. Флаг принимает значение, допустимое для time.ParseDuration.

Пример

Объяснение flag.Duration с примерами

Метод Duration создает флаг для работы с временными интервалами (тип time.Duration). Это особенно полезно для параметров, которые представляют временные значения (таймауты, интервалы и т.д.).

Основные параметры:

  • name - имя флага (например, “timeout”)
  • value - значение по умолчанию (например, 5*time.Second)
  • usage - описание флага для справки

Пример 1: Простое использование

package main

import (
	"flag"
	"fmt"
	"time"
)

func main() {
	// Создаем флаг с продолжительностью
	timeout := flag.Duration("timeout", 3*time.Second, "operation timeout duration")

	// Парсим аргументы командной строки
	flag.Parse()

	// Используем значение
	fmt.Printf("Waiting for %v before timeout...\n", *timeout)
	time.Sleep(*timeout)
	fmt.Println("Timeout reached!")
}
Использование:
go run main.go -timeout=2s
# или
go run main.go -timeout=500ms

Пример 2: В составе FlagSet (подкоманды)

package main

import (
	"flag"
	"fmt"
	"time"
)

func main() {
	serverCmd := flag.NewFlagSet("server", flag.ExitOnError)
	
	// Флаги для команды server
	port := serverCmd.Int("port", 8080, "port to listen on")
	readTimeout := serverCmd.Duration("read-timeout", 5*time.Second, "read timeout")
	writeTimeout := serverCmd.Duration("write-timeout", 10*time.Second, "write timeout")

	serverCmd.Parse([]string{"-port=9000", "-read-timeout=2s", "-write-timeout=5s"})

	fmt.Printf("Server config:\n")
	fmt.Printf("  Port: %d\n", *port)
	fmt.Printf("  Read Timeout: %v\n", *readTimeout)
	fmt.Printf("  Write Timeout: %v\n", *writeTimeout)
}

Пример 3: Поддержка разных форматов времени

Флаг Duration автоматически поддерживает все форматы, которые понимает time.ParseDuration:

package main

import (
	"flag"
	"fmt"
)

func main() {
	interval := flag.Duration("interval", 1*time.Minute, "check interval")

	flag.Parse()

	fmt.Printf("Checking every %v\n", *interval)
	fmt.Printf("In nanoseconds: %d\n", interval.Nanoseconds())
}
Допустимые форматы:
  • “300ms” - миллисекунды
  • “1.5h” - часы с дробной частью
  • “2h45m” - комбинированный формат
  • “1d” - дни (расширение Go, означает 24h)

Типичные сценарии использования:

  1. Таймауты операций:
timeout := flag.Duration("timeout", 30*time.Second, "operation timeout")
  1. Интервалы повторения:
interval := flag.Duration("interval", 1*time.Hour, "check interval")
  1. Временные ограничения:
ttl := flag.Duration("ttl", 24*time.Hour, "time to live")

func (*FlagSet) DurationVar

func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string)

DurationVar определяет флаг time.Duration с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную time.Duration, в которой хранится значение флага. Флаг принимает значение, допустимое для time.ParseDuration.

func (*FlagSet) ErrorHandling

func (f *FlagSet) ErrorHandling() ErrorHandling

ErrorHandling возвращает поведение обработки ошибок набора флагов.

Пример

Объяснение ErrorHandling() с примерами

Метод ErrorHandling() возвращает текущую стратегию обработки ошибок для набора флагов (FlagSet). Это полезно, когда нужно проверить или изменить поведение обработки ошибок во время выполнения.

Возможные значения:

  1. flag.ContinueOnError - продолжить выполнение после ошибки
  2. flag.ExitOnError - вызвать os.Exit(2) при ошибке
  3. flag.PanicOnError - вызвать panic при ошибке

Пример 1: Проверка текущей стратегии

package main

import (
	"flag"
	"fmt"
)

func main() {
	// Создаем набор флагов с разными стратегиями
	fs1 := flag.NewFlagSet("server", flag.ContinueOnError)
	fs2 := flag.NewFlagSet("client", flag.ExitOnError)

	// Проверяем стратегии обработки ошибок
	fmt.Println("Server error handling:", fs1.ErrorHandling())
	fmt.Println("Client error handling:", fs2.ErrorHandling())
}
Вывод:
Server error handling: 1  // ContinueOnError
Client error handling: 2  // ExitOnError

Пример 2: Динамическое изменение поведения

package main

import (
	"flag"
	"fmt"
	"os"
)

func main() {
	fs := flag.NewFlagSet("app", flag.ContinueOnError)
	fs.Int("port", 8080, "port number")

	// Для демонстрации - имитируем ошибку
	args := []string{"-port", "invalid"}

	// Парсим с текущей стратегией (ContinueOnError)
	err := fs.Parse(args)
	fmt.Printf("With ContinueOnError: error=%v, port=%d\n", err, fs.Lookup("port").Value)

	// Меняем стратегию на ExitOnError
	fs.Init(fs.Name(), flag.ExitOnError)
	fmt.Println("New error handling:", fs.ErrorHandling())

	// При новой стратегии парсинг вызовет os.Exit(2)
	fmt.Print("With ExitOnError: ")
	fs.Parse(args) // В реальности здесь будет выход
}

Пример 3: Использование в кастомной обработке ошибок

package main

import (
	"flag"
	"fmt"
	"os"
)

func main() {
	fs := flag.NewFlagSet("config", flag.ContinueOnError)
	fs.String("config", "", "config file path")
	fs.Duration("timeout", 5*time.Second, "operation timeout")

	err := fs.Parse(os.Args[1:])
	switch fs.ErrorHandling() {
	case flag.ContinueOnError:
		if err != nil {
			fmt.Printf("Configuration error: %v\n", err)
			// Продолжаем работу с значениями по умолчанию
		}
	case flag.ExitOnError:
		// Обработка уже выполнена flag.Parse
	case flag.PanicOnError:
		// panic уже вызван
	}

	// Основная логика приложения
	fmt.Println("Using config:", fs.Lookup("config").Value)
}

Основные сценарии использования:

  1. Библиотеки - когда нужно передать управление ошибкой вызывающему коду
  2. Тестирование - проверка поведения при разных стратегиях
  3. Гибкие CLI-утилиты - изменение поведения в зависимости от режима работы

Советы:

  1. Для приложений обычно используют ExitOnError
  2. Для библиотек лучше ContinueOnError
  3. PanicOnError редко используется на практике

func (*FlagSet) Float64

func (f *FlagSet) Float64(name string, value float64, usage string) *float64

Float64 определяет флаг float64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной float64, в которой хранится значение флага.

func (*FlagSet) Float64Var

func (f *FlagSet) Float64Var(p *float64, name string, value float64, usage string)

Float64Var определяет флаг float64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную float64, в которой хранится значение флага.

func (*FlagSet) Func

func (f *FlagSet) Func(name, usage string, fn func(string) error)

Func определяет флаг с указанным именем и строкой использования. Каждый раз, когда флаг встречается, вызывается fn со значением флага. Если fn возвращает ошибку, отличную от nil, она будет рассматриваться как ошибка разбора значения флага.

func (*FlagSet) Init

func (f *FlagSet) Init(name string, errorHandling ErrorHandling)

Init устанавливает имя и свойство обработки ошибок для набора флагов. По умолчанию нулевой FlagSet использует пустое имя и политику обработки ошибок ContinueOnError.

func (*FlagSet) Int

func (f *FlagSet) Int(name string, value int, usage string) *int

Int определяет флаг int с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной int, в которой хранится значение флага.

func (*FlagSet) Int64

func (f *FlagSet) Int64(name string, value int64, usage string) *int64

Int64 определяет флаг int64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной int64, в которой хранится значение флага.

func (*FlagSet) Int64Var

func (f *FlagSet) Int64Var(p *int64, name string, value int64, usage string)

Int64Var определяет флаг int64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную int64, в которой хранится значение флага.

func (*FlagSet) IntVar

func (f *FlagSet) IntVar(p *int, name string, value int, usage string)

IntVar определяет флаг int с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную int, в которой будет храниться значение флага.

func (*FlagSet) Lookup

func (f *FlagSet) Lookup(name string) *Flag

Lookup возвращает структуру Flag с указанным именем флага, возвращая nil, если такой флаг не существует.

func (*FlagSet) NArg

func (f *FlagSet) NArg() int

NArg — количество аргументов, оставшихся после обработки флагов.

func (*FlagSet) NFlag

func (f *FlagSet) NFlag() int

NFlag возвращает количество установленных флагов.

func (*FlagSet) Name

func (f *FlagSet) Name() string

Name возвращает имя набора флагов.

func (*FlagSet) Output

func (f *FlagSet) Output() io.Writer

Output возвращает место назначения для сообщений об использовании и ошибках. os.Stderr возвращается, если выход не был установлен или был установлен в nil.

func (*FlagSet) Parse

func (f *FlagSet) Parse(arguments []string) error

Parse анализирует определения флагов из списка аргументов, который не должен включать имя команды. Должен вызываться после того, как все флаги в FlagSet определены, и до того, как программа получит доступ к флагам. Возвращаемое значение будет ErrHelp, если -help или -h были установлены, но не определены.

func (*FlagSet) Parsed

func (f *FlagSet) Parsed() bool

Parsed сообщает, был ли вызван f.Parse.

func (*FlagSet) PrintDefaults

func (f *FlagSet) PrintDefaults()

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

func (*FlagSet) Set

func (f *FlagSet) Set(name, value string) error

Set устанавливает значение флага с указанным именем.

func (*FlagSet) SetOutput

func (f *FlagSet) SetOutput(output io.Writer)

SetOutput устанавливает место назначения для сообщений об использовании и ошибках. Если output равно nil, используется os.Stderr.

func (*FlagSet) String

func (f *FlagSet) String(name string, value string, usage string) *string

String определяет строковый флаг с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес строковой переменной, в которой хранится значение флага.

func (*FlagSet) StringVar

func (f *FlagSet) StringVar(p *string, name string, value string, usage string)

StringVar определяет строковый флаг с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на строковую переменную, в которой будет храниться значение флага.

func (*FlagSet) TextVar

func (f *FlagSet) TextVar(p encoding.TextUnmarshaler, name string, value encoding.TextMarshaler, usage string)

TextVar определяет флаг с указанным именем, значением по умолчанию и строкой использования. Аргумент p должен быть указателем на переменную, которая будет хранить значение флага, и p должен реализовывать encoding.TextUnmarshaler. Если флаг используется, его значение будет передано методу UnmarshalText p. Тип значения по умолчанию должен быть таким же, как тип p.

func (*FlagSet) Uint

func (f *FlagSet) Uint(name string, value uint, usage string) *uint

Uint определяет флаг uint с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной uint, которая хранит значение флага.

func (*FlagSet) Uint64

func (f *FlagSet) Uint64(name string, value uint64, usage string) *uint64

Uint64 определяет флаг uint64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной uint64, в которой хранится значение флага.

func (*FlagSet) Uint64Var

func (f *FlagSet) Uint64Var(p *uint64, name string, value uint64, usage string)

Uint64Var определяет флаг uint64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную uint64, в которой хранится значение флага.

func (*FlagSet) UintVar

func (f *FlagSet) UintVar(p *uint, name string, value uint, usage string)

UintVar определяет флаг uint с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную uint, в которой будет храниться значение флага.

func (*FlagSet) Var

func (f *FlagSet) Var(value Value, name string, usage string)

Var определяет флаг с указанным именем и строкой использования. Тип и значение флага представлены первым аргументом типа Value, который обычно содержит пользовательскую реализацию Value. Например, вызывающая сторона может создать флаг, который преобразует строку, разделенную запятыми, в набор строк, предоставив набору методы Value; в частности, Set разложит строку, разделенную запятыми, на набор.

func (*FlagSet) Visit

func (f *FlagSet) Visit(fn func(*Flag))

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

func (*FlagSet) VisitAll

func (f *FlagSet) VisitAll(fn func(*Flag))

VisitAll посещает флаги в лексикографическом порядке, вызывая fn для каждого из них. Он посещает все флаги, даже те, которые не были установлены.

type Getter

type Getter interface {
    Value
    Get() any
}

Getter — это интерфейс, который позволяет извлекать содержимое Value. Он оборачивает интерфейс Value, а не является его частью, поскольку появился после Go 1 и его правил совместимости. Все типы Value, предоставляемые этим пакетом, удовлетворяют интерфейсу Getter, за исключением типа, используемого Func.

Пример

Объяснение интерфейса Getter в пакете flag

Интерфейс Getter в пакете flag предоставляет способ получения значения флага в виде interface{} (тип any). Это расширение базового интерфейса Value, добавленное для обеспечения большей гибкости при работе с флагами.

Структура интерфейса:

type Getter interface {
    Value
    Get() any
}

Где:

  • Value - стандартный интерфейс для работы с флагами (требует методов String() и Set())
  • Get() any - метод, возвращающий текущее значение флага в виде пустого интерфейса

Пример 1: Проверка значения флага

package main

import (
	"flag"
	"fmt"
	"time"
)

func main() {
	// Создаем несколько флагов разных типов
	flag.Int("port", 8080, "server port")
	flag.Duration("timeout", 5*time.Second, "operation timeout")
	flag.String("name", "default", "service name")

	flag.Parse()

	// Получаем значения через Getter
	printFlagValue("port")
	printFlagValue("timeout")
	printFlagValue("name")
}

func printFlagValue(name string) {
	if f := flag.Lookup(name); f != nil {
		if getter, ok := f.Value.(flag.Getter); ok {
			fmt.Printf("%s: %v (%T)\n", name, getter.Get(), getter.Get())
		} else {
			fmt.Printf("%s: does not implement Getter\n", name)
		}
	} else {
		fmt.Printf("%s: flag not found\n", name)
	}
}

Пример 2: Кастомный тип с Getter

package main

import (
	"flag"
	"fmt"
	"strings"
)

type StringSlice []string

func (s *StringSlice) String() string {
	return strings.Join(*s, ",")
}

func (s *StringSlice) Set(value string) error {
	*s = append(*s, value)
	return nil
}

func (s *StringSlice) Get() any {
	return []string(*s)
}

func main() {
	var hosts StringSlice
	flag.Var(&hosts, "host", "target host (can be specified multiple times)")

	flag.Parse()

	if g, ok := flag.Lookup("host").Value.(flag.Getter); ok {
		fmt.Printf("Hosts: %v\n", g.Get())
	}
}

Пример 3: Использование с разными типами флагов

package main

import (
	"flag"
	"fmt"
	"reflect"
)

func main() {
	// Разные типы флагов
	intFlag := flag.Int("num", 42, "an integer")
	boolFlag := flag.Bool("verbose", false, "verbose output")
	stringFlag := flag.String("msg", "hello", "a message")

	flag.Parse()

	// Проверяем реализацию Getter для каждого флага
	flags := []string{"num", "verbose", "msg"}
	for _, name := range flags {
		f := flag.Lookup(name)
		if getter, ok := f.Value.(flag.Getter); ok {
			val := getter.Get()
			fmt.Printf("%s: %v (type: %v)\n", name, val, reflect.TypeOf(val))
		}
	}
}

Когда использовать Getter:

  1. Когда нужно работать с флагами разных типов единообразно
  2. Для создания обобщенных функций обработки флагов
  3. При реализации кастомных типов флагов с возможностью извлечения значения

Важные замечания:

  1. Почти все стандартные типы флагов реализуют Getter
  2. Исключение - флаги, созданные через flag.Func()
  3. Для проверки реализации используйте type assertion:
    if getter, ok := flag.Value.(flag.Getter); ok {
        value := getter.Get()
        // ...
    }
    

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

type Value

type Value интерфейс {
    String() string
	Set(string) error
}

Value — это интерфейс к динамическому значению, хранящемуся в флаге. (Значение по умолчанию представлено в виде строки.)

Если Value имеет метод IsBoolFlag() bool, возвращающий true, парсер командной строки делает -name эквивалентным -name=true, а не использует следующий аргумент командной строки.

Set вызывается один раз, в порядке командной строки, для каждого присутствующего флага. Пакет flag может вызывать метод String с нулевым значением приемника, таким как указатель nil.

Пример
package main

import (
	"flag"
	"fmt"
	"net/url"
)

// URLValue - кастомный тип для парсинга URL в флагах
type URLValue struct {
	URL *url.URL // Хранит указатель на разобранный URL
}

// String реализует интерфейс flag.Value
func (v URLValue) String() string {
	if v.URL != nil {
		return v.URL.String()
	}
	return ""
}

// Set реализует интерфейс flag.Value
func (v *URLValue) Set(s string) error {
	u, err := url.Parse(s)
	if err != nil {
		return fmt.Errorf("failed to parse URL: %w", err)
	}
	*v.URL = *u // Копируем разобранный URL
	return nil
}

// Get реализует интерфейс flag.Getter
func (v URLValue) Get() any {
	return v.URL
}

// Глобальная переменная для хранения разобранного URL
var targetURL = &url.URL{}

func main() {
	// Создаем новый набор флагов
	fs := flag.NewFlagSet("URLParser", flag.ExitOnError)
	
	// Регистрируем наш кастомный флаг
	fs.Var(&URLValue{targetURL}, "url", "URL to parse (e.g. 'https://example.com/path')")

	// Парсим аргументы (в реальном приложении используйте os.Args[1:])
	err := fs.Parse([]string{"-url", "https://golang.org/pkg/flag/"})
	if err != nil {
		fmt.Printf("Error parsing flags: %v\n", err)
		return
	}

	// Выводим разобранные компоненты URL
	fmt.Printf("Parsed URL components:\n")
	fmt.Printf("{scheme: %q, host: %q, path: %q}\n", 
		targetURL.Scheme, 
		targetURL.Host, 
		targetURL.Path)
}

Комментарии по коду:

  1. Интерфейс Value:

    • String() используется для вывода текущего значения
    • Set() парсит строку и сохраняет значение
  2. Интерфейс Getter:

    • Позволяет получить значение как interface{}
    • Полезно для обобщенной обработки флагов
  3. Особенности реализации:

    • Используем указатель на url.URL для модификации
    • Метод Set() должен быть с pointer receiver
    • Всегда проверяем ошибки парсинга URL
  4. Использование в реальных приложениях:

    // Вместо fs.Parse([]string{...}) используйте:
    fs.Parse(os.Args[1:])
    
  5. Дополнительные улучшения:

    • Можно добавить валидацию URL (например, требовать HTTPS)
    • Можно добавить метод IsSet() для проверки установки значения

Пример запуска:

go run main.go -url "https://example.com/api/v1?param=value"

Вывод:

Parsed URL components:
{scheme: "https", host: "example.com", path: "/api/v1"}

Этот код теперь представляет собой надежную реализацию кастомного флага для работы с URL в Go-приложениях.

8 - Полный справочник по пакету fmt в Go

Полное описание пакета fmt в Go с примерами применения и разными потоками вывода

Пакет fmt (format) — один из самых часто используемых в Go. Он предоставляет функции для форматированного ввода/вывода, аналогичные printf и scanf в C.

1. Основные функции вывода

1.1. Вывод в стандартный поток (stdout)

fmt.Print("Hello, ", "World!")       // Без перевода строки
fmt.Println("Hello, World!")        // С переводом строки
fmt.Printf("Name: %s, Age: %d\n", "Alice", 25) // Форматированный вывод

1.2. Вывод в строку (Sprint, Sprintf, Sprintln)

s1 := fmt.Sprint("Hello, ", "World!")      // => "Hello, World!"
s2 := fmt.Sprintf("Name: %s, Age: %d", "Bob", 30) // => "Name: Bob, Age: 30"
s3 := fmt.Sprintln("Line with newline")    // => "Line with newline\n"

1.3. Вывод в файл или io.Writer (Fprint, Fprintf, Fprintln)

file, _ := os.Create("output.txt")
fmt.Fprint(file, "Hello, file!")           // Запись в файл
fmt.Fprintf(file, "Value: %v", 42)        // Форматированная запись
fmt.Fprintln(file, "End of line")         // Запись с переводом строки

2. Форматирование вывода (Printf, Sprintf, Fprintf)

2.1. Основные спецификаторы

Спецификатор Описание Пример (%v) Вывод (42)
%v Универсальный формат %v 42
%+v С полями структур %+v {X:42}
%#v Go-синтаксис %#v 42 (int)
%T Тип переменной %T int
%% Символ % %% %

2.2. Форматирование разных типов данных

// Целые числа
fmt.Printf("%d %b %o %x %X\n", 42, 42, 42, 42, 42) // 42 101010 52 2a 2A

// Вещественные числа
fmt.Printf("%f %.2f %e %g\n", 3.1415, 3.1415, 3.1415, 3.1415) // 3.141500 3.14 3.141500e+00 3.1415

// Строки и символы
fmt.Printf("%s %q %c\n", "Go", "Go", 'G') // Go "Go" G

// Логические значения
fmt.Printf("%t\n", true) // true

2.3. Ширина и точность

// Ширина поля (выравнивание)
fmt.Printf("|%5d|%-5d|\n", 42, 42) // |   42|42   |

// Точность для float
fmt.Printf("%.2f\n", 3.141592) // 3.14

// Комбинация ширины и точности
fmt.Printf("%10.2f\n", 3.141592) // "      3.14"

2.4. Позиционные аргументы

fmt.Printf("%[2]d %[1]d\n", 10, 20) // 20 10

3. Ввод данных (Scan, Scanf, Scanln)

3.1. Чтение из стандартного ввода (stdin)

var name string
var age int

fmt.Print("Enter name and age: ")
fmt.Scan(&name, &age) // Читает до пробела/перевода строки
fmt.Printf("Hello, %s (%d)\n", name, age)

3.2. Форматированный ввод (Scanf)

var a, b int
fmt.Scanf("%d,%d", &a, &b) // Ввод "10,20" → a=10, b=20

3.3. Чтение строки (Scanln)

var input string
fmt.Scanln(&input) // Читает до перевода строки

3.4. Чтение из строки (Sscan, Sscanf, Sscanln)

var x, y int
fmt.Sscanf("10 20", "%d %d", &x, &y) // x=10, y=20

3.5. Чтение из файла (Fscan, Fscanf, Fscanln)

file, _ := os.Open("input.txt")
var value int
fmt.Fscanf(file, "%d", &value) // Чтение числа из файла

4. Дополнительные функции

4.1. Форматирование ошибок (Errorf)

err := fmt.Errorf("invalid value: %d", 42) // Создает error

4.2. Вывод в io.Writer с форматированием (Fprintf)

var buf bytes.Buffer
fmt.Fprintf(&buf, "Value: %d", 42) // Запись в буфер

4.3. Сканирование из io.Reader (Fscanf)

reader := strings.NewReader("100")
var num int
fmt.Fscanf(reader, "%d", &num) // num = 100

5. Полезные примеры

5.1. Вывод таблицы

fmt.Printf("|%-10s|%10s|\n", "Name", "Age")
fmt.Printf("|%-10s|%10d|\n", "Alice", 25)
fmt.Printf("|%-10s|%10d|\n", "Bob", 30)

Вывод:

|Name      |       Age|
|Alice     |        25|
|Bob       |        30|

5.2. Чтение нескольких значений

var a, b int
fmt.Print("Enter two numbers: ")
fmt.Scan(&a, &b)
fmt.Println("Sum:", a+b)

5.3. Форматирование JSON

data := map[string]interface{}{"name": "Alice", "age": 25}
jsonStr := fmt.Sprintf("%+v", data) // {"age":25, "name":"Alice"}

Дополнительный раздел: Форматирование различных типов данных в Go


1. Форматирование базовых типов (Printf, Sprintf, Fprintf)

1.1. Целые числа (int, int32, int64, uint)

num := 42
fmt.Printf("Десятичное: %d\n", num)       // 42
fmt.Printf("Двоичное: %b\n", num)         // 101010
fmt.Printf("Восьмеричное: %o\n", num)     // 52
fmt.Printf("Шестнадцатеричное (нижний регистр): %x\n", num) // 2a
fmt.Printf("Шестнадцатеричное (верхний регистр): %X\n", num) // 2A
fmt.Printf("С ведущими нулями: %05d\n", num) // 00042

1.2. Вещественные числа (float32, float64)

pi := 3.1415926535
fmt.Printf("По умолчанию: %f\n", pi)      // 3.141593
fmt.Printf("С точностью 2 знака: %.2f\n", pi) // 3.14
fmt.Printf("Экспоненциальная запись: %e\n", pi) // 3.141593e+00
fmt.Printf("Автовыбор формата: %g\n", pi)  // 3.1415926535
fmt.Printf("Ширина 10 символов: %10.2f\n", pi) // "      3.14"

1.3. Строки (string) и символы (rune)

str := "Hello"
fmt.Printf("Строка: %s\n", str)           // Hello
fmt.Printf("Строка в кавычках: %q\n", str) // "Hello"
fmt.Printf("Ширина 10 символов: %10s\n", str) // "     Hello"
fmt.Printf("Символ: %c\n", 'A')           // A
fmt.Printf("Юникод-символ: %U\n", 'Я')    // U+042F

1.4. Логические значения (bool)

flag := true
fmt.Printf("Логическое: %t\n", flag)      // true

1.5. Указатели (*int, *string)

x := 42
ptr := &x
fmt.Printf("Указатель: %p\n", ptr)        // 0xc0000180a8
fmt.Printf("Значение по указателю: %v\n", *ptr) // 42

2. Форматирование структур (struct) и массивов (slice, array)

2.1. Вывод структуры

type Person struct {
    Name string
    Age  int
}
p := Person{"Alice", 25}

fmt.Printf("Универсальный формат: %v\n", p)  // {Alice 25}
fmt.Printf("С полями: %+v\n", p)             // {Name:Alice Age:25}
fmt.Printf("Go-синтаксис: %#v\n", p)         // main.Person{Name:"Alice", Age:25}

2.2. Форматирование слайсов и массивов

arr := [3]int{1, 2, 3}
slice := []string{"a", "b", "c"}

fmt.Printf("Массив: %v\n", arr)    // [1 2 3]
fmt.Printf("Слайс: %v\n", slice)   // [a b c]
fmt.Printf("Go-синтаксис: %#v\n", slice) // []string{"a", "b", "c"}

3. Форматирование с помощью fmt.Append (Go 1.19+)

Функция fmt.Append позволяет форматировать данные и добавлять их в слайс байт ([]byte).

3.1. Базовое использование

buf := []byte("Данные: ")
buf = fmt.Append(buf, 42, " ", true)
fmt.Println(string(buf)) // Данные: 42 true

3.2. Форматированный вывод (Appendf)

buf := []byte("Число: ")
buf = fmt.Appendf(buf, "%05d", 42)
fmt.Println(string(buf)) // Число: 00042

3.3. Добавление в буфер (bytes.Buffer)

var buffer bytes.Buffer
buffer.WriteString("Строка: ")
fmt.Fprintf(&buffer, "%q", "Hello")
fmt.Println(buffer.String()) // Строка: "Hello"

4. Специальные форматы

4.1. Вывод времени (time.Time)

t := time.Now()
fmt.Printf("RFC3339: %v\n", t.Format(time.RFC3339)) // 2024-03-15T14:20:10Z
fmt.Printf("Кастомный формат: %02d.%02d.%04d\n", t.Day(), t.Month(), t.Year()) // 15.03.2024

4.2. Форматирование JSON

data := map[string]any{"name": "Alice", "age": 25}
jsonStr := fmt.Sprintf("%+v", data) // map[age:25 name:Alice]

4.3. Ширина и выравнивание

fmt.Printf("|%10s|\n", "left")    // |     left|
fmt.Printf("|%-10s|\n", "right")  // |right     |
fmt.Printf("|%10.2f|\n", 3.1415)  // |      3.14|

5. Полезные примеры

5.1. Таблица с выравниванием

fmt.Printf("|%-10s|%10s|\n", "Name", "Age")
fmt.Printf("|%-10s|%10d|\n", "Alice", 25)
fmt.Printf("|%-10s|%10d|\n", "Bob", 30)

Вывод:

|Name      |       Age|
|Alice     |        25|
|Bob       |        30|

5.2. Форматирование ошибок

err := fmt.Errorf("ошибка: значение %d недопустимо", 100)
fmt.Println(err) // ошибка: значение 100 недопустимо

5.3. Чтение и запись в файл

// Запись
file, _ := os.Create("data.txt")
fmt.Fprintf(file, "Число: %d", 42)
file.Close()

// Чтение
file, _ = os.Open("data.txt")
var num int
fmt.Fscanf(file, "Число: %d", &num)
fmt.Println(num) // 42

9 - Описание пакета io языка программирования Go

Пакет io предоставляет базовые интерфейсы для примитивов ввода-вывода.

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

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

Константы


const (
    SeekStart   = 0 // поиск относительно начала файла
    SeekCurrent = 1 // поиск относительно текущего смещения
    SeekEnd     = 2 // поиск относительно конца)

Значения whence поиска.

Переменные

var EOF = errors.New(«EOF»)

EOF — это ошибка, возвращаемая Read, когда входные данные больше не доступны. (Read должен возвращать сам EOF, а не ошибку, оборачивающую EOF, потому что вызывающие функции будут проверять EOF с помощью ==.) Функции должны возвращать EOF только для сигнализации о корректном завершении ввода. Если EOF возникает неожиданно в структурированном потоке данных, соответствующей ошибкой является либо ErrUnexpectedEOF, либо какая-либо другая ошибка, дающая более подробную информацию.

var ErrClosedPipe = errors.New(«io: read/write on closed pipe»)

ErrClosedPipe — это ошибка, используемая для операций чтения или записи в закрытом канале.

var ErrNoProgress = errors.New(«multiple Read calls return no data or error»)

ErrNoProgress возвращается некоторыми клиентами Reader, когда много вызовов Read не возвращают никаких данных или ошибок, что обычно является признаком неисправной реализации Reader.

var ErrShortBuffer = errors.New(«короткий буфер»)

ErrShortBuffer означает, что для чтения потребовался буфер большего размера, чем был предоставлен.

var ErrShortWrite = errors.New(«короткая запись»)

ErrShortWrite означает, что запись приняла меньше байтов, чем было запрошено, но не вернула явную ошибку.

var ErrUnexpectedEOF = errors.New(«неожиданный EOF»)

ErrUnexpectedEOF означает, что EOF был обнаружен в середине чтения блока фиксированного размера или структуры данных.

9.1 - Описание функций пакета io

Спецификация функций пакета io с примерами на языке Go

func Copy

func Copy(dst Writer, src Reader) (written int64, err error)

Copy копирует данные из src в dst до тех пор, пока не будет достигнут EOF в src или не произойдет ошибка. Функция возвращает количество скопированных байтов и первую ошибку, возникшую при копировании, если таковая имеется.

Успешная функция Copy возвращает err == nil, а не err == EOF. Поскольку функция Copy определена для чтения из src до EOF, она не рассматривает EOF из Read как ошибку, о которой следует сообщать.

Если src реализует WriterTo, копирование реализуется вызовом src.WriteTo(dst). В противном случае, если dst реализует ReaderFrom, копирование реализуется вызовом dst.ReadFrom(src).

Пример
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")

	if _, err := io.Copy(os.Stdout, r); err != nil {
		log.Fatal(err)
	}

}
Output:

some io.Reader stream to be read

func CopyBuffer

func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)

CopyBuffer идентична Copy, за исключением того, что она проходит через предоставленный буфер (если он требуется), а не выделяет временный. Если buf равна nil, выделяется один; в противном случае, если она имеет нулевую длину, CopyBuffer вызывает панику.

Если src реализует WriterTo или dst реализует ReaderFrom, buf не будет использоваться для выполнения копирования.

Пример
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r1 := strings.NewReader("first reader\n")
	r2 := strings.NewReader("second reader\n")
	buf := make([]byte, 8)

	// buf is used here...
	if _, err := io.CopyBuffer(os.Stdout, r1, buf); err != nil {
		log.Fatal(err)
	}

	// ... reused here also. No need to allocate an extra buffer.
	if _, err := io.CopyBuffer(os.Stdout, r2, buf); err != nil {
		log.Fatal(err)
	}

}
Output:

first reader
second reader

func CopyN

func CopyN(dst Writer, src Reader, n int64) (written int64, err error)

CopyN копирует n байт (или до возникновения ошибки) из src в dst. Он возвращает количество скопированных байтов и самую раннюю ошибку, возникшую во время копирования. При возвращении written == n тогда и только тогда, когда err == nil.

Если dst реализует ReaderFrom, копирование реализуется с его помощью.

Пример
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read")

	if _, err := io.CopyN(os.Stdout, r, 4); err != nil {
		log.Fatal(err)
	}

}
Output:

some

func Pipe

func Pipe() (*PipeReader, *PipeWriter)

Pipe создает синхронный конвейер в памяти. Его можно использовать для соединения кода, ожидающего io.Reader, с кодом, ожидающим io.Writer.

Чтение и запись в канале сопоставляются один к одному, за исключением случаев, когда для потребления одной записи требуется несколько чтений. То есть каждая запись в PipeWriter блокируется до тех пор, пока не будет удовлетворено одно или несколько чтений из PipeReader, которые полностью потребляют записанные данные. Данные копируются непосредственно из записи в соответствующее чтение (или чтения); внутренней буферизации нет.

Безопасно вызывать Read и Write параллельно друг с другом или с Close. Параллельные вызовы Read и параллельные вызовы Write также безопасны: отдельные вызовы будут последовательно блокироваться.

Пример
package main

import (
	"fmt"
	"io"
	"log"
	"os"
)

func main() {
	r, w := io.Pipe()

	go func() {
		fmt.Fprint(w, "some io.Reader stream to be read\n")
		w.Close()
	}()

	if _, err := io.Copy(os.Stdout, r); err != nil {
		log.Fatal(err)
	}

}
Output:

some io.Reader stream to be read

func ReadAll

func ReadAll(r Reader) ([]byte, error)

ReadAll читает из r до ошибки или EOF и возвращает прочитанные данные. Успешный вызов возвращает err == nil, а не err == EOF. Поскольку ReadAll определено для чтения из src до EOF, оно не рассматривает EOF из Read как ошибку, о которой следует сообщать.

Пример
package main

import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.")

	b, err := io.ReadAll(r)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s", b)

}
Output:

Go is a general-purpose language designed with systems programming in mind.

func ReadAtLeast

func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)

ReadAtLeast читает из r в buf, пока не прочитает как минимум min байт. Он возвращает количество скопированных байтов и ошибку, если было прочитано меньше байтов. Ошибка EOF возникает только в том случае, если не было прочитано ни одного байта. Если EOF возникает после чтения менее min байтов, ReadAtLeast возвращает ErrUnexpectedEOF. Если min больше длины buf, ReadAtLeast возвращает ErrShortBuffer. При возвращении n >= min, если и только если err == nil. Если r возвращает ошибку, прочитав не менее min байтов, ошибка игнорируется.

Пример
package main

import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")

	buf := make([]byte, 14)
	if _, err := io.ReadAtLeast(r, buf, 4); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", buf)

	// buffer smaller than minimal read size.
	shortBuf := make([]byte, 3)
	if _, err := io.ReadAtLeast(r, shortBuf, 4); err != nil {
		fmt.Println("error:", err)
	}

	// minimal read size bigger than io.Reader stream
	longBuf := make([]byte, 64)
	if _, err := io.ReadAtLeast(r, longBuf, 64); err != nil {
		fmt.Println("error:", err)
	}

}
Output:

some io.Reader
error: short buffer
error: unexpected EOF

func ReadFull

func ReadFull(r Reader, buf []byte) (n int, err error)

ReadFull читает ровно len(buf) байтов из r в buf. Он возвращает количество скопированных байтов и ошибку, если было прочитано меньше байтов. Ошибка EOF возникает только в том случае, если не было прочитано ни одного байта. Если EOF возникает после чтения некоторых, но не всех байтов, ReadFull возвращает ErrUnexpectedEOF. При возвращении n == len(buf) тогда и только тогда, когда err == nil. Если r возвращает ошибку после чтения по крайней мере len(buf) байтов, ошибка игнорируется.

Пример
package main

import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")

	buf := make([]byte, 4)
	if _, err := io.ReadFull(r, buf); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", buf)

	// minimal read size bigger than io.Reader stream
	longBuf := make([]byte, 64)
	if _, err := io.ReadFull(r, longBuf); err != nil {
		fmt.Println("error:", err)
	}

}
Output:

some
error: unexpected EOF

func WriteString

func WriteString(w Writer, s string) (n int, err error)

WriteString записывает содержимое строки s в w, который принимает набор байтов. Если w реализует StringWriter, [StringWriter.WriteString] вызывается напрямую. В противном случае [Writer.Write] вызывается ровно один раз.

Пример
package main

import (
	"io"
	"log"
	"os"
)

func main() {
	if _, err := io.WriteString(os.Stdout, "Hello World"); err != nil {
		log.Fatal(err)
	}

}
Output:

Hello World

9.2 - Описание типов пакета io языка программирования GO

Описание типов и их функций пакета io языка программирования GO

type ByteReader

type ByteReader interface {
    ReadByte() (byte, error)
}

ByteReader — это интерфейс, который оборачивает метод ReadByte.

ReadByte считывает и возвращает следующий байт из ввода или любую возникшую ошибку. Если ReadByte возвращает ошибку, входной байт не был обработан, и возвращаемое значение байта не определено.

ReadByte предоставляет эффективный интерфейс для обработки по одному байту за раз. Reader, который не реализует ByteReader, можно обернуть с помощью bufio.NewReader, чтобы добавить этот метод.

type ByteScanner

type ByteScanner interface {
    ByteReader
    UnreadByte() error
}

ByteScanner — это интерфейс, который добавляет метод UnreadByte к базовому методу ReadByte.

UnreadByte заставляет следующий вызов ReadByte возвращать последний прочитанный байт. Если последняя операция не была успешным вызовом ReadByte, UnreadByte может вернуть ошибку, не прочитать последний прочитанный байт (или байт, предшествующий последнему непрочитанному байту), или (в реализациях, поддерживающих интерфейс Seeker) перейти на один байт перед текущим смещением.

type ByteWriter

type ByteWriter interface {
    WriteByte(c byte) error
}

ByteWriter — это интерфейс, который оборачивает метод WriteByte.

type Closer

type Closer interface {
    Close() error
}

Closer — это интерфейс, который оборачивает базовый метод Close.

Поведение Close после первого вызова не определено. Конкретные реализации могут документировать свое собственное поведение.

type LimitedReader

type LimitedReader struct {
    R Reader // базовый читатель
    N int64  // максимальное количество оставшихся байтов
}

LimitedReader читает из R, но ограничивает количество возвращаемых данных до N байтов. Каждый вызов Read обновляет N, чтобы отразить новое количество оставшихся данных. Read возвращает EOF, когда N <= 0 или когда базовый R возвращает EOF.

func (*LimitedReader) Read

func (l *LimitedReader) Read(p []byte) (n int, err error)
Пример
package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	// Создаем источник данных - строка длиной 100 символов
	data := strings.Repeat("abcdefghij", 10) // 100 байт
	reader := strings.NewReader(data)

	// Создаем LimitedReader, который прочитает только первые 35 байт
	limitedReader := &io.LimitedReader{
		R: reader, // базовый reader
		N: 35,     // лимит в 35 байт
	}

	// Буфер для чтения
	buf := make([]byte, 10) // читаем по 10 байт за раз

	var totalRead int
	for {
		// Читаем данные
		n, err := limitedReader.Read(buf)
		if err != nil {
			if err == io.EOF {
				fmt.Println("\nДостигнут лимит или конец данных")
			} else {
				fmt.Println("\nОшибка чтения:", err)
			}
			break
		}

		totalRead += n
		fmt.Printf("Прочитано %d байт: %q\n", n, buf[:n])
		fmt.Printf("Осталось прочитать: %d байт\n", limitedReader.N)
	}

	fmt.Println("Всего прочитано:", totalRead, "байт")
	fmt.Println("Осталось данных в основном reader:", reader.Len(), "байт")
}
Прочитано 10 байт: "abcdefghij"
Осталось прочитать: 25 байт
Прочитано 10 байт: "abcdefghij"
Осталось прочитать: 15 байт
Прочитано 10 байт: "abcdefghij"
Осталось прочитать: 5 байт
Прочитано 5 байт: "abcde"
Осталось прочитать: 0 байт

Достигнут лимит или конец данных
Всего прочитано: 35 байт
Осталось данных в основном reader: 65 байт

type OffsetWriter

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

func NewOffsetWriter

func NewOffsetWriter(w WriterAt, off int64) *OffsetWriter

NewOffsetWriter возвращает OffsetWriter, который записывает в w, начиная с смещения off.

func (*OffsetWriter) Seek

func (o *OffsetWriter) Seek(offset int64, whence int) (int64, error)

func (*OffsetWriter) Write

func (o *OffsetWriter) Write(p []byte) (n int, err error)

func (*OffsetWriter) WriteAt

func (o *OffsetWriter) WriteAt(p []byte, off int64) (n int, err error)

type PipeReader

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

PipeReader — это читающая часть канала (pipe).

func (*PipeReader) Close

func (r *PipeReader) Close() error

Close закрывает читатель; последующие записи в записывающую половину канала будут возвращать ошибку ErrClosedPipe.

func (*PipeReader) CloseWithError

func (r *PipeReader) CloseWithError(err error) error

CloseWithError закрывает читатель; последующие записи в записывающую половину канала будут возвращать ошибку err.

CloseWithError никогда не перезаписывает предыдущую ошибку, если она существует, и всегда возвращает nil.

func (*PipeReader) Read

func (r *PipeReader) Read(data []byte) (n int, err error)

Read реализует стандартный интерфейс Read: он считывает данные из канала, блокируя до тех пор, пока не появится записывающее устройство или не будет закрыта записывающая часть. Если записывающая часть закрыта с ошибкой, эта ошибка возвращается как err; в противном случае err равен EOF.

type PipeWriter

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

PipeWriter — это записывающая часть канала (pipe).

func (*PipeWriter) Close

func (w *PipeWriter) Close() error

Close закрывает записывающее устройство; последующие чтения из читающей половины канала не будут возвращать байты и EOF.

func (*PipeWriter) CloseWithError

func (w *PipeWriter) CloseWithError(err error) error

CloseWithError закрывает запись; последующие чтения из части трубы, отвечающей за чтение, не будут возвращать байты и будут возвращать ошибку err или EOF, если err равна nil.

CloseWithError никогда не перезаписывает предыдущую ошибку, если она существует, и всегда возвращает nil.

func (*PipeWriter) Write

func (w *PipeWriter) Write(data []byte) (n int, err error)

Write реализует стандартный интерфейс Write: он записывает данные в канал, блокируя его до тех пор, пока один или несколько читателей не потребят все данные или читающая сторона не будет закрыта. Если читающая сторона закрыта с ошибкой, эта ошибка возвращается как err; в противном случае err равна ErrClosedPipe.

type ReadCloser

type ReadCloser интерфейс {
    Reader
    Closer
}

ReadCloser — это интерфейс, который группирует основные методы Read и Close.

func NopCloser

func NopCloser(r Reader) ReadCloser

NopCloser возвращает ReadCloser с методом Close, не выполняющим никаких действий, который оборачивает предоставленный Reader r. Если r реализует WriterTo, возвращаемый ReadCloser будет реализовывать WriterTo, перенаправляя вызовы r.

type ReadSeekCloser

type ReadSeekCloser интерфейс {
    Reader
    Seeker
    Closer
}

ReadSeekCloser — интерфейс, который группирует основные методы Read, Seek и Close.

type ReadSeeker

type ReadSeeker интерфейс {
    Reader
    Seeker
}

ReadSeeker — интерфейс, который группирует основные методы Read и Seek.

type ReadWriteCloser

type ReadWriteCloser интерфейс {
    Reader
    Writer
    Closer
}

ReadWriteCloser — это интерфейс, который группирует основные методы Read, Write и Close.

type ReadWriteSeeker

type ReadWriteSeeker интерфейс {
	Reader
    Writer
    Seeker
}

ReadWriteSeeker — интерфейс, который группирует основные методы Read, Write и Seek.

type ReadWriter

type ReadWriter интерфейс {
    Reader
    Writer
}

ReadWriter — интерфейс, который группирует основные методы Read и Write.

type Reader

type Reader interface {
    Read(p []byte) (n int, err error)
}

Reader — это интерфейс, который оборачивает базовый метод Read.

Read считывает до len(p) байт в p. Он возвращает количество прочитанных байтов (0 <= n <= len(p)) и любую возникшую ошибку. Даже если Read возвращает n < len(p), он может использовать все p в качестве временного пространства во время вызова. Если некоторые данные доступны, но не len(p) байтов, Read по умолчанию возвращает то, что доступно, вместо того, чтобы ждать больше.

Когда Read встречает ошибку или условие конца файла после успешного чтения n > 0 байтов, он возвращает количество прочитанных байтов. Он может вернуть ошибку (не nil) из того же вызова или вернуть ошибку (и n == 0) из последующего вызова. Примером этого общего случая является то, что Reader, возвращающий ненулевое количество байтов в конце входного потока, может возвращать либо err == EOF, либо err == nil. Следующий Read должен возвращать 0, EOF.

Вызывающие функции должны всегда обрабатывать возвращенные n > 0 байтов, прежде чем рассматривать ошибку err. Это позволяет правильно обрабатывать ошибки ввода-вывода, возникающие после чтения некоторых байтов, а также оба допустимых поведения EOF.

Если len(p) == 0, Read всегда должен возвращать n == 0. Он может возвращать ошибку, отличную от nil, если известно о каком-либо условии ошибки, таком как EOF.

Реализации Read не рекомендуется возвращать нулевое количество байтов с ошибкой nil, за исключением случаев, когда len(p) == 0. Вызывающие должны рассматривать возвращение 0 и nil как указание на то, что ничего не произошло; в частности, это не указывает на EOF.

Реализации не должны сохранять p.

func LimitReader

func LimitReader(r Reader, n int64) Reader

LimitReader возвращает Reader, который читает из r, но останавливается с EOF после n байтов. Базовой реализацией является *LimitedReader.

Пример
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	lr := io.LimitReader(r, 4)

	if _, err := io.Copy(os.Stdout, lr); err != nil {
		log.Fatal(err)
	}

}
Output:

some

func MultiReader

func MultiReader(readers ...Reader) Reader

MultiReader возвращает Reader, который является логическим соединением предоставленных входных считывателей. Они считываются последовательно. Как только все входы вернут EOF, Read вернет EOF. Если какой-либо из считывателей вернет ошибку, отличную от nil и EOF, Read вернет эту ошибку.

Пример
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r1 := strings.NewReader("first reader ")
	r2 := strings.NewReader("second reader ")
	r3 := strings.NewReader("third reader\n")
	r := io.MultiReader(r1, r2, r3)

	if _, err := io.Copy(os.Stdout, r); err != nil {
		log.Fatal(err)
	}

}
Output:

first reader second reader third reader

func TeeReader

func TeeReader(r Reader, w Writer) Reader

TeeReader возвращает Reader, который записывает в w то, что он читает из r. Все чтения из r, выполняемые через него, сопоставляются с соответствующими записями в w. Внутренней буферизации нет — запись должна быть завершена до завершения чтения. Любая ошибка, возникшая во время записи, сообщается как ошибка чтения.

Пример
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	var r io.Reader = strings.NewReader("some io.Reader stream to be read\n")

	r = io.TeeReader(r, os.Stdout)

	// Everything read from r will be copied to stdout.
	if _, err := io.ReadAll(r); err != nil {
		log.Fatal(err)
	}

}
Output:

some io.Reader stream to be read

type ReaderAt

type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error)
}

ReaderAt — это интерфейс, который оборачивает базовый метод ReadAt.

ReadAt считывает len(p) байт в p, начиная с смещения off в базовом источнике ввода. Он возвращает количество прочитанных байтов (0 <= n <= len(p)) и любую возникшую ошибку.

Когда ReadAt возвращает n < len(p), он возвращает не нулевую ошибку, объясняющую, почему не было возвращено больше байтов. В этом отношении ReadAt более строг, чем Read.

Даже если ReadAt возвращает n < len(p), он может использовать все p в качестве временного пространства во время вызова. Если некоторые данные доступны, но не len(p) байтов, ReadAt блокируется до тех пор, пока не будут доступны все данные или не произойдет ошибка. В этом отношении ReadAt отличается от Read.

Если n = len(p) байтов, возвращаемых ReadAt, находятся в конце источника ввода, ReadAt может вернуть либо err == EOF, либо err == nil.

Если ReadAt читает из источника ввода с смещением поиска, ReadAt не должен влиять на базовое смещение поиска и не должен подвергаться его влиянию.

Клиенты ReadAt могут выполнять параллельные вызовы ReadAt на одном и том же источнике ввода.

Реализации не должны сохранять p.

type ReaderFrom

type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error)
}

ReaderFrom — это интерфейс, который оборачивает метод ReadFrom.

ReadFrom читает данные из r до EOF или ошибки. Возвращаемое значение n — это количество прочитанных байтов. Любая ошибка, кроме EOF, возникшая во время чтения, также возвращается.

Функция Copy использует ReaderFrom, если он доступен.

type RuneReader

type RuneReader interface {
    ReadRune() (r rune, size int, err error)
}

RuneReader — это интерфейс, который оборачивает метод ReadRune.

ReadRune считывает один закодированный символ Unicode и возвращает руну и ее размер в байтах. Если символ недоступен, будет установлено err.

type RuneScanner

type RuneScanner interface {
    RuneReader
    UnreadRune() error
}

RuneScanner — это интерфейс, который добавляет метод UnreadRune к базовому методу ReadRune.

UnreadRune заставляет следующий вызов ReadRune возвращать последнюю прочитанную руну. Если последняя операция не была успешным вызовом ReadRune, UnreadRune может вернуть ошибку, не прочитать последнюю прочитанную руну (или руну, предшествующую последней непрочитанной руне), или (в реализациях, поддерживающих интерфейс Seeker) перейти к началу руны перед текущим смещением.

type SectionReader

type SectionReader struct {
    // содержит отфильтрованные или неэкспортированные поля
}

SectionReader реализует Read, Seek и ReadAt на секции базового ReaderAt.

func NewSectionReader

func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader

NewSectionReader возвращает SectionReader, который читает из r, начиная с смещения off, и останавливается с EOF после n байтов.

func (*SectionReader) Outer

func (s *SectionReader) Outer() (r ReaderAt, off int64, n int64)

Outer возвращает базовый ReaderAt и смещения для секции.

Возвращаемые значения совпадают с теми, которые были переданы в NewSectionReader при создании SectionReader.

func (*SectionReader) Read

func (s *SectionReader) Read(p []byte) (n int, err error)
Пример
import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	s := io.NewSectionReader(r, 5, 17)

	buf := make([]byte, 9)
	if _, err := s.Read(buf); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s\n", buf)

}
Output:

io.Reader

func (*SectionReader) ReadAt

func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error)
Пример
import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	s := io.NewSectionReader(r, 5, 17)

	buf := make([]byte, 6)
	if _, err := s.ReadAt(buf, 10); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s\n", buf)

}
Output:

stream

func (*SectionReader) Seek

func (s *SectionReader) Seek(offset int64, whence int) (int64, error)
Пример
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	s := io.NewSectionReader(r, 5, 17)

	if _, err := s.Seek(10, io.SeekStart); err != nil {
		log.Fatal(err)
	}

	if _, err := io.Copy(os.Stdout, s); err != nil {
		log.Fatal(err)
	}

}
Output:

stream

func (*SectionReader) Size

func (s *SectionReader) Size() int64

Size возвращает размер секции в байтах.

Пример
package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	s := io.NewSectionReader(r, 5, 17)

	fmt.Println(s.Size())

}
Output:

17

type Seeker

type Seeker interface {
	Seek(offset int64, whence int) (int64, error)
}

Seeker — это интерфейс, который оборачивает базовый метод Seek.

Seek устанавливает смещение для следующего Read или Write в offset, интерпретируемое в соответствии с whence: SeekStart означает относительно начала файла, SeekCurrent означает относительно текущего смещения, а SeekEnd означает относительно конца (например, offset = -2 указывает предпоследний байт файла). Seek возвращает новое смещение относительно начала файла или ошибку, если таковая имеется.

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

type StringWriter

type StringWriter interface {
    WriteString(s string) (n int, err error)
}

StringWriter — это интерфейс, который оборачивает метод WriteString.

type WriteCloser

type WriteCloser interface {
    Writer
    Closer
}

WriteCloser — это интерфейс, который группирует базовые методы Write и Close.

type WriteSeeker

type WriteSeeker interface {
    Writer
    Seeker
}

WriteSeeker — это интерфейс, который группирует базовые методы Write и Seek.

type Writer

type Writer interface {
    Write(p []byte) (n int, err error)
}

Writer — это интерфейс, который оборачивает базовый метод Write.

Write записывает len(p) байт из p в базовый поток данных. Он возвращает количество байт, записанных из p (0 <= n <= len(p)), и любую ошибку, которая привела к преждевременному прекращению записи. Write должен возвращать ошибку, отличную от nil, если он возвращает n < len(p). Write не должен изменять данные слайса, даже временно.

Реализации не должны сохранять p.

var Discard Writer = discard{}

Discard — это Writer, на котором все вызовы Write выполняются успешно, не выполняя никаких действий.

func MultiWriter

func MultiWriter(writers ...Writer) Writer

MultiWriter создает писатель, который дублирует свои записи во всех предоставленных писателях, аналогично команде Unix tee(1).

Каждая запись записывается в каждый из перечисленных writer, по одному за раз. Если перечисленный writer возвращает ошибку, вся операция записи останавливается и возвращает ошибку; она не продолжается по списку.

Пример
package main

import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")

	var buf1, buf2 strings.Builder
	w := io.MultiWriter(&buf1, &buf2)

	if _, err := io.Copy(w, r); err != nil {
		log.Fatal(err)
	}

	fmt.Print(buf1.String())
	fmt.Print(buf2.String())

}
Output:

some io.Reader stream to be read
some io.Reader stream to be read

type WriterAt

type WriterAt interface {
    WriteAt(p []byte, off int64) (n int, err error)
}

WriterAt — это интерфейс, который оборачивает базовый метод WriteAt.

WriteAt записывает len(p) байт из p в базовый поток данных со смещением off. Он возвращает количество байт, записанных из p (0 <= n <= len(p)), и любую ошибку, которая привела к преждевременному прекращению записи. WriteAt должен возвращать ошибку, отличную от nil, если он возвращает n < len(p).

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

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

Реализации не должны сохранять p.

type WriterTo

type WriterTo interface {
    WriteTo(w Writer) (n int64, err error)
}

WriterTo — это интерфейс, который оборачивает метод WriteTo.

WriteTo записывает данные в w до тех пор, пока не закончатся данные для записи или не произойдет ошибка. Возвращаемое значение n — это количество записанных байтов. Любая ошибка, возникшая во время записи, также возвращается.

Функция Copy использует WriterTo, если он доступен.

9.3 - Пакет для работы с файловой системой io/fs

“Пакет fs определяет базовые интерфейсы для файловой системы. Файловая система может быть предоставлена операционной системой хоста, а также другими пакетами.”

Для поддержки тестирования реализаций файловых систем смотрите пакет testing/fstest.

Переменные

var (
ErrInvalid = errInvalid() // "недопустимый аргумент"
ErrPermission = errPermission() // "разрешение отклонено"
ErrExist = errExist() // "файл уже существует"
ErrNotExist = errNotExist() // "файл не существует"
ErrClosed = errClosed() // "файл уже закрыт"
)

Общие ошибки файловой системы. Ошибки, возвращаемые файловыми системами, можно проверить на соответствие этим ошибкам с помощью errors.Is.

var SkipAll = errors.New("пропустить все и остановить прогулку")

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

var SkipDir = errors.New("пропустить этот каталог")

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

Функции

func FormatDirEntry

func FormatDirEntry(dir DirEntry) string

FormatDirEntry возвращает отформатированную версию dir для удобства чтения. Реализации DirEntry могут вызывать эту функцию из метода String. Результаты для каталога с именем subdir и файла с именем hello.go:

d subdir/
- hello.go

func FormatFileInfo

func FormatFileInfo(info FileInfo) string

FormatFileInfo возвращает отформатированную версию info для удобства чтения. Реализации FileInfo могут вызывать эту функцию из метода String. Результатом для файла с именем «hello.go», размером 100 байт, режимом 0o644, созданного 1 января 1970 года в полдень, будет

-rw-r--r-- 100 1970-01-01 12:00:00 hello.go

func Glob

func Glob(fsys FS, pattern string) (matches []string, err error)

Glob возвращает имена всех файлов, соответствующих pattern, или nil, если нет соответствующих файлов. Синтаксис шаблонов такой же, как в path.Match. Шаблон может описывать иерархические имена, такие как usr/*/bin/ed.

Glob игнорирует ошибки файловой системы, такие как ошибки ввода-вывода при чтении каталогов. Единственная возможная возвращаемая ошибка — path.ErrBadPattern, сообщающая о неверном формате шаблона.

Если fs реализует GlobFS, Glob вызывает fs.Glob. В противном случае Glob использует ReadDir для обхода дерева каталогов и поиска совпадений с шаблоном.

func ReadFile

func ReadFile(fsys FS, name string) ([]byte, error)

ReadFile читает файл с указанным именем из файловой системы fs и возвращает его содержимое. Успешный вызов возвращает ошибку nil, а не io.EOF. (Поскольку ReadFile читает весь файл, ожидаемый EOF от последнего Read не рассматривается как ошибка, о которой следует сообщать).

Если fs реализует ReadFileFS, ReadFile вызывает fs.ReadFile. В противном случае ReadFile вызывает fs.Open и использует Read и Close на возвращенном File.

func ValidPath

func ValidPath(name string) bool

ValidPath сообщает, является ли данное имя пути действительным для использования в вызове Open.

Имена путей, передаваемые в open, представляют собой закодированные в UTF-8, не имеющие корня, разделенные косой чертой последовательности элементов пути, например «x/y/z». Имена путей не должны содержать элемент «.» или «..» или пустую строку, за исключением особого случая, когда имя «.» может использоваться для корневого каталога. Пути не должны начинаться или заканчиваться косой чертой: «/x» и «x/» являются недопустимыми.

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

func WalkDir

func WalkDir(fsys FS, root string, fn WalkDirFunc) error

WalkDir проходит по дереву файлов, корнем которого является root, вызывая fn для каждого файла или каталога в дереве, включая root.

Все ошибки, возникающие при посещении файлов и каталогов, фильтруются fn: подробности см. в документации fs.WalkDirFunc.

Файлы просматриваются в лексическом порядке, что делает вывод детерминированным, но требует, чтобы WalkDir считывал весь каталог в память, прежде чем приступать к просмотру этого каталога.

WalkDir не следует по символьным ссылкам, найденным в каталогах, но если root сам является символьной ссылкой, то будет пройдена его цель.

Пример
package main

import (
	"fmt"
	"io/fs"
	"log"
	"os"
	"path/filepath"
	"time"
)

func main() {
	// Создаем временную файловую систему для демонстрации
	tmpDir, err := os.MkdirTemp("", "fs-example-*")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	// Создаем тестовые файлы и директории
	createTestFS(tmpDir)

	// Получаем файловую систему os.DirFS
	fsys := os.DirFS(tmpDir)

	// 1. Пример использования FormatDirEntry и FormatFileInfo
	demoFormatFunctions(fsys)

	// 2. Пример использования Glob
	demoGlob(fsys)

	// 3. Пример использования ReadFile
	demoReadFile(fsys)

	// 4. Пример использования ValidPath
	demoValidPath()

	// 5. Пример использования WalkDir
	demoWalkDir(fsys)
}

func createTestFS(root string) {
	// Создаем структуру:
	// /tmp/fs-example-12345/
	//   ├── docs/
	//   │   ├── notes.md
	//   │   └── draft.txt
	//   ├── src/
	//   │   └── main.go
	//   └── README.txt

	dirs := []string{
		filepath.Join(root, "docs"),
		filepath.Join(root, "src"),
	}

	files := map[string]string{
		filepath.Join(root, "README.txt"):        "Пример файла README",
		filepath.Join(root, "docs", "notes.md"):  "# Заметки\nПример содержимого",
		filepath.Join(root, "docs", "draft.txt"): "Черновик документа",
		filepath.Join(root, "src", "main.go"):    "package main\n\nfunc main() {\n\tprintln(\"Hello\")\n}",
	}

	// Создаем директории
	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			log.Fatal(err)
		}
	}

	// Создаем файлы
	for path, content := range files {
		if err := os.WriteFile(path, []byte(content), 0644); err != nil {
			log.Fatal(err)
		}
	}

	// Устанавливаем время модификации для демонстрации FormatFileInfo
	modTime := time.Date(2023, time.January, 15, 12, 30, 0, 0, time.UTC)
	for path := range files {
		if err := os.Chtimes(path, modTime, modTime); err != nil {
			log.Fatal(err)
		}
	}
}

func demoFormatFunctions(fsys fs.FS) {
	fmt.Println("\n=== FormatDirEntry и FormatFileInfo ===")

	entries, err := fs.ReadDir(fsys, ".")
	if err != nil {
		log.Fatal(err)
	}

	for _, entry := range entries {
		// FormatDirEntry
		fmt.Printf("DirEntry: %s\n", fs.FormatDirEntry(entry))

		// Получаем FileInfo для FormatFileInfo
		info, err := entry.Info()
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("FileInfo: %s\n", fs.FormatFileInfo(info))
	}
}

func demoGlob(fsys fs.FS) {
	fmt.Println("\n=== Glob ===")

	// Ищем все .txt файлы
	matches, err := fs.Glob(fsys, "*.txt")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Файлы .txt в корне:", matches)

	// Ищем все .go файлы в любых поддиректориях
	matches, err = fs.Glob(fsys, "*/*.go")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Файлы .go в поддиректориях:", matches)

	// Ищем все .md файлы рекурсивно
	matches, err = fs.Glob(fsys, "**/*.md")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Файлы .md рекурсивно:", matches)
}

func demoReadFile(fsys fs.FS) {
	fmt.Println("\n=== ReadFile ===")

	// Читаем содержимое файла
	content, err := fs.ReadFile(fsys, "src/main.go")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Содержимое main.go:\n%s\n", content)

	// Попытка чтения несуществующего файла
	_, err = fs.ReadFile(fsys, "nonexistent.txt")
	fmt.Println("Ошибка при чтении несуществующего файла:", err)
}

func demoValidPath() {
	fmt.Println("\n=== ValidPath ===")

	paths := []string{
		"valid/path",
		"invalid/../path",
		"invalid\\path", // Обратные слеши не считаются разделителями
		"",
		"trailing/slash/",
	}

	for _, path := range paths {
		fmt.Printf("%q valid: %t\n", path, fs.ValidPath(path))
	}
}

func demoWalkDir(fsys fs.FS) {
	fmt.Println("\n=== WalkDir ===")

	fmt.Println("Содержимое файловой системы:")
	err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		fmt.Printf("- %s\n", fs.FormatDirEntry(d))
		return nil
	})
	if err != nil {
		log.Fatal(err)
	}
}
=== FormatDirEntry и FormatFileInfo ===
DirEntry: d docs/
FileInfo: drwxr-xr-x 4096 2023-01-15 12:30:00 docs
DirEntry: d src/
FileInfo: drwxr-xr-x 4096 2023-01-15 12:30:00 src
DirEntry: - README.txt
FileInfo: -rw-r--r-- 19 2023-01-15 12:30:00 README.txt

=== Glob ===
Файлы .txt в корне: [README.txt]
Файлы .go в поддиректориях: [src/main.go]
Файлы .md рекурсивно: [docs/notes.md]

=== ReadFile ===
Содержимое main.go:
package main

func main() {
	println("Hello")
}
Ошибка при чтении несуществующего файла: open nonexistent.txt: file does not exist

=== ValidPath ===
"valid/path" valid: true
"invalid/../path" valid: false
"invalid\\path" valid: true
"" valid: false
"trailing/slash/" valid: false

=== WalkDir ===
Содержимое файловой системы:
- .
- d docs/
- - docs/draft.txt
- - docs/notes.md
- d src/
- - src/main.go
- - README.txt

Типы

type DirEntry

type DirEntry interface {
    // Name возвращает имя файла (или подкаталога), описанного записью.
    // Это имя является только конечным элементом пути (базовым именем), а не всем путем.
    // Например, Name вернет «hello.go», а не «home/gopher/hello.go».
    Name() string

	// IsDir сообщает, описывает ли запись каталог.
    IsDir() bool

    // Type возвращает биты типа для запис
    // Биты типа являются подмножеством обычных битов FileMode, возвращаемых методом FileMode.Type.
    Type() FileMode

    // Info возвращает FileInfo для файла или подкаталога, описанного записью.
	// Возвращаемая информация FileInfo может быть взята из времени первоначального чтения каталога
    // или из времени вызова Info. Если файл был удален или переименован
    // после чтения каталога, Info может вернуть ошибку, удовлетворяющую errors.Is(err, ErrNotExist).
    // Если запись обозначает символическую ссылку, Info сообщает информацию о самой ссылке,
    // а не о цели ссылки.
	Info() (FileInfo, error)
}

DirEntry — это запись, прочитанная из каталога (с помощью функции ReadDir или метода ReadDir класса ReadDirFile).

func FileInfoToDirEntry

func FileInfoToDirEntry(info FileInfo) DirEntry

FileInfoToDirEntry возвращает DirEntry, который возвращает информацию из info. Если info равно nil, FileInfoToDirEntry возвращает nil.

func ReadDir

func ReadDir(fsys FS, name string) ([]DirEntry, error)

ReadDir считывает указанный каталог и возвращает список записей каталога, отсортированных по имени файла.

Если fs реализует ReadDirFS, ReadDir вызывает fs.ReadDir. В противном случае ReadDir вызывает fs.Open и использует ReadDir и Close для возвращенного файла.

type FS

type FS interface {
    // Open открывает указанный файл.
    // [File.Close] должен быть вызван для освобождения всех связанных ресурсов.
    //
    // Когда Open возвращает ошибку, она должна быть типа *PathError
	// с полем Op, установленным в «open», полем Path, установленным в name,
    // и полем Err, описывающим проблему.
    //
    // Open должен отклонять попытки открыть имена, которые не удовлетворяют
    // ValidPath(name), возвращая *PathError с Err, установленным в
    // ErrInvalid или ErrNotExist.
    Open(name string) (File, error)
}

FS обеспечивает доступ к иерархической файловой системе.

Интерфейс FS является минимальной реализацией, необходимой для файловой системы. Файловая система может реализовывать дополнительные интерфейсы, такие как ReadFileFS, для предоставления дополнительных или оптимизированных функций.

testing/fstest.TestFS может использоваться для проверки правильности реализации FS.

func Sub

func Sub(fsys FS, dir string) (FS, error)

Sub возвращает FS, соответствующую поддереву с корнем в fsys’s dir.

Если dir равна «.», Sub возвращает fsys без изменений. В противном случае, если fs реализует SubFS, Sub возвращает fsys.Sub(dir). В противном случае Sub возвращает новую реализацию FS sub, которая фактически реализует sub.Open(name) как fsys.Open(path.Join(dir, name)). Реализация также соответствующим образом преобразует вызовы ReadDir, ReadFile и Glob.

Обратите внимание, что Sub(os.DirFS(«/»), „prefix“) эквивалентно os.DirFS(«/prefix») и что ни одно из них не гарантирует отсутствие доступа операционной системы за пределами «/prefix», поскольку реализация os.DirFS не проверяет символьные ссылки внутри «/prefix», которые указывают на другие каталоги. То есть os.DirFS не является общей заменой механизма безопасности типа chroot, и Sub не меняет этот факт.

type File

type File interface {
    Stat() (FileInfo, error)
    Read([]byte) (int, error)
    Close() error
}

File предоставляет доступ к одному файлу. Интерфейс File является минимальной реализацией, требуемой для файла. Файлы каталогов также должны реализовывать ReadDirFile. Файл может реализовывать io.ReaderAt или io.Seeker в качестве оптимизаций.

type FileInfo

type FileInfo interface {
    Name() string       // базовое имя файла
    Size() int64        // длина в байтах для обычных файлов; зависит от системы для других
    Mode() FileMode     // биты режима файла
    ModTime() time.Time // время изменения
	IsDir() bool        // сокращение для Mode().IsDir()
    Sys() any           // базовый источник данных (может возвращать nil)
}

FileInfo описывает файл и возвращается Stat.

func Stat

func Stat(fsys FS, name string) (FileInfo, error)

Stat возвращает FileInfo, описывающий файл с указанным именем из файловой системы.

Если fs реализует StatFS, Stat вызывает fs.Stat. В противном случае Stat открывает файл для его статистики.

type FileMode

type FileMode uint32

FileMode представляет режим файла и биты разрешений. Биты имеют одинаковое определение во всех системах, поэтому информация о файлах может быть перенесена из одной системы в другую. Не все биты применимы ко всем системам. Единственный обязательный бит — ModeDir для каталогов.

const (
    // Одиночные буквы являются аббревиатурами,
    // используемыми для форматирования методом String.
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: является каталогом
    ModeAppend                                     // a: только для добавления
    ModeExclusive                                  // l: исключительное использование
    ModeTemporary                                  // T: временный файл; только Plan 9
	ModeSymlink                                    // L: символическая ссылка
    ModeDevice                                     // D: файл устройства
    ModeNamedPipe                                  // p: именованный канал (FIFO)
    ModeSocket                                     // S: сокет домена Unix
    ModeSetuid                                     // u: setuid
    ModeSetgid                                     // g: setgid
    ModeCharDevice                                 // c: символьное устройство Unix, когда установлен ModeDevice
    ModeSticky                                     // t: sticky
    ModeIrregular                                  // ?: нерегулярный файл; ничего больше не известно об этом файле

    // Маска для битов типа. Для обычных файлов не будет установлено ничего.
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular

	ModePerm FileMode = 0777 // биты разрешений Unix)

Определенные биты режима файла являются наиболее значимыми битами FileMode. Девять наименее значимых битов являются стандартными разрешениями Unix rwxrwxrwx. Значения этих битов следует рассматривать как часть общедоступного API и могут использоваться в протоколах передачи данных или представлениях диска: они не должны изменяться, хотя могут быть добавлены новые биты.

func (FileMode) IsDir

func (m FileMode) IsDir() bool

IsDir сообщает, описывает ли m каталог. То есть, он проверяет, установлен ли бит ModeDir в m.

func (FileMode) IsRegular

func (m FileMode) IsRegular() bool

IsRegular сообщает, описывает ли m обычный файл. То есть, он проверяет, что биты типа режима не установлены.

func (FileMode) Perm

func (m FileMode) Perm() FileMode

Perm возвращает биты разрешений Unix в m (m & ModePerm).

func (FileMode) String

func (m FileMode) String() string

func (FileMode) Type

func (m FileMode) Type() FileMode

Type возвращает биты типа в m (m & ModeType).

type GlobFS

type GlobFS interface {
    FS

    // Glob возвращает имена всех файлов, соответствующих шаблону,
    // предоставляя реализацию функции верхнего уровня
    // Glob.
    Glob(pattern string) ([]string, error)
}

GlobFS — это файловая система с методом Glob.

type PathError

type PathError struct {
    Op   string
    Path string
    Err  error
}

PathError регистрирует ошибку, а также операцию и путь к файлу, которые ее вызвали.

func (*PathError) Error

func (e *PathError) Error() string

func (*PathError) Timeout

func (e *PathError) Timeout() bool

Timeout сообщает, является ли эта ошибка тайм-аутом.

func (*PathError) Unwrap

func (e *PathError) Unwrap() error

type ReadDirFS

type ReadDirFS interface {
    FS

    // ReadDir читает указанный каталог
    // и возвращает список записей каталога, отсортированных по имени файла.
    ReadDir(name string) ([]DirEntry, error)
}

ReadDirFS — это интерфейс, реализованный файловой системой, который обеспечивает оптимизированную реализацию ReadDir.

type ReadDirFile

type ReadDirFile interface {
    file

    // ReadDir считывает содержимое каталога и возвращает
    // массив из n значений DirEntry в порядке каталога.
    // Последующие вызовы для того же файла будут возвращать дальнейшие значения DirEntry.
    //
    // Если n > 0, ReadDir возвращает не более n структур DirEntry.
	// В этом случае, если ReadDir возвращает пустой срез, он вернет
    // не нулевую ошибку с объяснением причины.
    // В конце каталога ошибкой будет io.EOF.
    // (ReadDir должен возвращать io.EOF сам, а не ошибку, оборачивающую io.EOF.)
    //
    // Если n <= 0, ReadDir возвращает все значения DirEntry из каталога
	// в одном срезе. В этом случае, если ReadDir завершается успешно (считывает все
    // до конца каталога), он возвращает срез и ошибку nil.
    // Если он встречает ошибку до конца каталога,
    // ReadDir возвращает список DirEntry, прочитанный до этого момента, и ошибку, отличную от nil.
    ReadDir(n int) ([]DirEntry, error)
}

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

type ReadFileFS

type ReadFileFS interface {
    FS

    // ReadFile читает указанный файл и возвращает его содержимое.
	// Успешный вызов возвращает ошибку nil, а не io.EOF.
    // (Поскольку ReadFile считывает весь файл, ожидаемый EOF
    // от последнего Read не рассматривается как ошибка, о которой следует сообщать.)
    //
    // Вызывающему разрешается изменять возвращаемый байтовый срез.
    // Этот метод должен возвращать копию базовых данных.
    ReadFile(name string) ([]byte, error)
}

ReadFileFS — это интерфейс, реализованный файловой системой, который обеспечивает оптимизированную реализацию ReadFile.

type StatFS

type StatFS interface {
    FS

    // Stat возвращает FileInfo, описывающий файл.
    // Если происходит ошибка, она должна быть типа *PathError.
    Stat(name string) (FileInfo, error)
}

StatFS — это файловая система с методом Stat.

type SubFS

type SubFS interface {
    FS

    // Sub возвращает FS, соответствующий поддереву с корнем в dir.
    Sub(dir string) (FS, error)
}

SubFS — это файловая система с методом Sub.

type WalkDirFunc

type WalkDirFunc func(path string, d DirEntry, err error) error

WalkDirFunc — это тип функции, вызываемой WalkDir для посещения каждого файла или каталога.

Аргумент path содержит аргумент WalkDir в качестве префикса. То есть, если WalkDir вызывается с корневым аргументом «dir» и находит файл с именем «a» в этом каталоге, функция walk будет вызвана с аргументом «dir/a».

Аргумент d — это DirEntry для указанного пути.

Результат error, возвращаемый функцией, контролирует продолжение работы WalkDir. Если функция возвращает специальное значение SkipDir, WalkDir пропускает текущий каталог (path, если d.IsDir() равно true, в противном случае — родительский каталог path). Если функция возвращает специальное значение SkipAll, WalkDir пропускает все оставшиеся файлы и каталоги. В противном случае, если функция возвращает ошибку, отличную от nil, WalkDir полностью останавливается и возвращает эту ошибку.

Аргумент err сообщает об ошибке, связанной с путем, сигнализируя, что WalkDir не будет проходить в этот каталог. Функция может решить, как обработать эту ошибку; как описано ранее, возврат ошибки приведет к тому, что WalkDir прекратит прохождение всего дерева.

WalkDir вызывает функцию с аргументом err, отличным от nil, в двух случаях.

Во-первых, если первоначальная Stat в корневом каталоге завершается с ошибкой, WalkDir вызывает функцию с path, установленным в root, d, установленным в nil, и err, установленным в ошибку из fs.Stat.

Во-вторых, если метод ReadDir каталога (см. ReadDirFile) завершается с ошибкой, WalkDir вызывает функцию с path, установленным в путь каталога, d, установленным в DirEntry, описывающий каталог, и err, установленным в ошибку из ReadDir. Во втором случае функция вызывается дважды с путем к каталогу: первый вызов происходит до попытки чтения каталога, и err установлен в nil, что дает функции возможность вернуть SkipDir или SkipAll и полностью избежать ReadDir. Второй вызов происходит после неудачного ReadDir и сообщает об ошибке из ReadDir. (Если ReadDir завершается успешно, второго вызова не происходит.)

Различия между WalkDirFunc и path/filepath.WalkFunc заключаются в следующем:

Второй аргумент имеет тип DirEntry вместо FileInfo. Функция вызывается перед чтением каталога, чтобы SkipDir или SkipAll могли полностью обойти чтение каталога или пропустить все оставшиеся файлы и каталоги соответственно. Если чтение каталога завершилось неудачно, функция вызывается второй раз для этого каталога, чтобы сообщить об ошибке.

10 - Пакет Sync для Go

Пакет sync предоставляет базовые примитивы синхронизации, такие как взаимоисключающие блокировки.

За исключением типов Once и WaitGroup, большинство из них предназначены для использования низкоуровневыми библиотечными процедурами. Синхронизацию более высокого уровня лучше выполнять через каналы и коммуникации.

Значения, содержащие типы, определенные в этом пакете, не должны копироваться.

Функции

func OnceFunc

func OnceFunc(f func()) func()

OnceFunc возвращает функцию, которая вызывает f только один раз. Возвращаемая функция может вызываться одновременно.

Если f вызывает панику, возвращаемая функция будет вызывать панику с тем же значением при каждом вызове.

func OnceValue

func OnceValue[T any](f func() T) func() T

OnceValue возвращает функцию, которая вызывает f только один раз и возвращает значение, возвращаемое f. Возвращаемая функция может вызываться одновременно.

Если f вызывает панику, возвращаемая функция будет вызывать панику с тем же значением при каждом вызове.

Пример

В этом примере OnceValue используется для выполнения «дорогостоящего» вычисления только один раз, даже при одновременном использовании.

package main

import (
	"fmt"
	"sync"
)

func main() {
	once := sync.OnceValue(func() int {
		sum := 0
		for i := 0; i < 1000; i++ {
			sum += i
		}
		fmt.Println("Computed once:", sum)
		return sum
	})
	done := make(chan bool)
	for i := 0; i < 10; i++ {
		go func() {
			const want = 499500
			got := once()
			if got != want {
				fmt.Println("want", want, "got", got)
			}
			done <- true
		}()
	}
	for i := 0; i < 10; i++ {
		<-done
	}
}
Output:

Computed once: 499500

func OnceValues

func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2)

OnceValues возвращает функцию, которая вызывает f только один раз и возвращает значения, возвращаемые f. Возвращаемая функция может вызываться одновременно.

Если f вызывает панику, возвращаемая функция будет вызывать панику с тем же значением при каждом вызове.

Пример

В этом примере используется OnceValues для однократного чтения файла.

package main

import (
	"fmt"
	"os"
	"sync"
)

func main() {
	once := sync.OnceValues(func() ([]byte, error) {
		fmt.Println("Reading file once")
		return os.ReadFile("example_test.go")
	})
	done := make(chan bool)
	for i := 0; i < 10; i++ {
		go func() {
			data, err := once()
			if err != nil {
				fmt.Println("error:", err)
			}
			_ = data // Ignore the data for this example
			done <- true
		}()
	}
	for i := 0; i < 10; i++ {
		<-done
	}
}

Типы

type Cond

type Cond struct {

    // L удерживается во время наблюдения или изменения условия
    L Locker
    // содержит отфильтрованные или неэкспортируемые поля
}

Cond реализует переменную условия, точку встречи для goroutines, ожидающих или объявляющих о наступлении события.

Каждый Cond имеет связанный с ним Locker L (часто *Mutex или *RWMutex), который должен удерживаться при изменении условия и при вызове метода Cond.Wait.

Cond не должен копироваться после первого использования.

В терминологии модели памяти Go, Cond организует так, что вызов Cond.Broadcast или Cond.Signal «синхронизируется перед» любым вызовом Wait, который он разблокирует.

Для многих простых случаев использования пользователям будет удобнее использовать каналы, чем Cond (Broadcast соответствует закрытию канала, а Signal — отправке по каналу).

Для получения дополнительной информации о заменах sync.Cond см. серию статей Роберто Клаписа о расширенных моделях параллелизма, а также доклад Брайана Миллса о моделях параллелизма.

func NewCond

func NewCond(l Locker) *Cond

NewCond возвращает новый Cond с Locker l.

func (*Cond) Broadcast

func (c *Cond) Broadcast()

Broadcast пробуждает все goroutines, ожидающие c.

Вызывающему разрешается, но не требуется, удерживать c.L во время вызова.

func (*Cond) Signal

func (c *Cond) Signal()

Signal пробуждает одну goroutine, ожидающую c, если таковая имеется.

Вызывающему разрешается, но не требуется, удерживать c.L во время вызова.

Signal() не влияет на приоритет планирования goroutine; если другие goroutines пытаются заблокировать c.L, они могут быть пробуждены раньше «ожидающей» goroutine.

func (*Cond) Wait

func (c *Cond) Wait()

Wait атомарно разблокирует c.L и приостанавливает выполнение вызывающего goroutine. После возобновления выполнения Wait блокирует c.L перед возвратом. В отличие от других систем, Wait не может вернуться, если его не разбудит Cond.Broadcast или Cond.Signal.

Поскольку c.L не блокируется во время ожидания Wait, вызывающий обычно не может предполагать, что условие будет выполнено, когда Wait вернется. Вместо этого вызывающий должен ждать в цикле:

c.L.Lock()
for !condition() {
    c.Wait()
}...
 использовать условие ...
c.L.Unlock()
Подробное объяснение типа Cond
Что такое Cond?

Cond (от слова “condition” - условие) - это примитив синхронизации из пакета sync, который позволяет горутинам ожидать или объявлять о наступлении некоторого события.

Проще говоря, Cond нужен для того, чтобы:

  1. Одни горутины могли “уснуть” и ждать какого-то условия
  2. Другие горутины могли их “разбудить”, когда это условие выполнится
Основные методы:
  • Wait() - блокирует горутину до получения уведомления
  • Signal() - пробуждает одну случайную горутину из ожидающих
  • Broadcast() - пробуждает все ожидающие горутины
Зачем это нужно?

Cond особенно полезен в ситуациях, где:

  • Несколько горутин ожидают какого-то общего условия
  • Состояние может измениться в любой момент
  • Вы не хотите постоянно опрашивать условие в цикле (busy waiting)
Реальный пример: Ограниченная очередь

Допустим, у нас есть очередь с ограниченным размером, и мы хотим:

  • Блокировать писателей, когда очередь полна
  • Блокировать читателей, когда очередь пуста
package main

import (
	"fmt"
	"sync"
	"time"
)

type BoundedQueue struct {
	mu    sync.Mutex
	cond  *sync.Cond
	queue []int
	size  int
	cap   int
}

func NewBoundedQueue(capacity int) *BoundedQueue {
	q := &BoundedQueue{
		queue: make([]int, 0, capacity),
		cap:   capacity,
	}
	q.cond = sync.NewCond(&q.mu)
	return q
}

func (q *BoundedQueue) Put(item int) {
	q.mu.Lock()
	defer q.mu.Unlock()

	// Ждем, пока освободится место
	for q.size == q.cap {
		q.cond.Wait()
	}

	q.queue = append(q.queue, item)
	q.size++
	fmt.Printf("Добавлен элемент %d. Размер очереди: %d\n", item, q.size)

	// Уведомляем ожидающих читателей
	q.cond.Broadcast()
}

func (q *BoundedQueue) Get() int {
	q.mu.Lock()
	defer q.mu.Unlock()

	// Ждем, пока появится элемент
	for q.size == 0 {
		q.cond.Wait()
	}

	item := q.queue[0]
	q.queue = q.queue[1:]
	q.size--
	fmt.Printf("Извлечен элемент %d. Размер очереди: %d\n", item, q.size)

	// Уведомляем ожидающих писателей
	q.cond.Broadcast()

	return item
}

func main() {
	queue := NewBoundedQueue(3)

	// Писатели
	for i := 0; i < 5; i++ {
		go func(val int) {
			queue.Put(val)
		}(i)
	}

	// Читатели
	for i := 0; i < 5; i++ {
		go func() {
			time.Sleep(1 * time.Second)
			queue.Get()
		}()
	}

	time.Sleep(5 * time.Second)
}
Когда использовать Cond вместо каналов?

Cond полезен, когда:

  1. У вас сложное условие ожидания (не просто “есть данные”)
  2. Нужно уведомлять сразу несколько горутин
  3. Состояние может меняться часто и нужно минимизировать накладные расходы

Каналы лучше подходят для более простых случаев передачи данных между горутинами.

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

type Locker

type Locker интерфейс {
    Lock()
    Unlock()
}

Locker - это интерфейс из пакета sync, который определяет базовые методы для блокировки:

Подробное объяснение типа Locker

Этот интерфейс реализуют:

  • sync.Mutex - обычная мьютекс-блокировка
  • sync.RWMutex - блокировка с возможностью множественного чтения
  • Любые другие типы, которые реализуют эти два метода

Зачем нужен Locker?

  1. Унификация работы с разными типами блокировок - вы можете писать функции, которые работают с любым типом блокировки
  2. Абстракция - позволяет не зависеть от конкретной реализации блокировки
  3. Тестирование - можно создавать mock-объекты для тестирования

Пример 1: Использование с sync.Mutex

package main

import (
	"fmt"
	"sync"
	"time"
)

func increment(counter *int, locker sync.Locker) {
	locker.Lock()
	defer locker.Unlock()
	
	*counter++
	fmt.Println(*counter)
}

func main() {
	var counter int
	var mu sync.Mutex

	for i := 0; i < 5; i++ {
		go increment(&counter, &mu)
	}

	time.Sleep(1 * time.Second)
}

Пример 2: Собственная реализация Locker

type DebugLocker struct {
	mu sync.Mutex
}

func (d *DebugLocker) Lock() {
	fmt.Println("Lock acquired")
	d.mu.Lock()
}

func (d *DebugLocker) Unlock() {
	fmt.Println("Lock released")
	d.mu.Unlock()
}

func main() {
	var counter int
	locker := &DebugLocker{}

	for i := 0; i < 5; i++ {
		go increment(&counter, locker)
	}

	time.Sleep(1 * time.Second)
}

Реальный пример использования

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

type Cache struct {
	locker sync.Locker
	data   map[string]string
}

func NewCache(locker sync.Locker) *Cache {
	return &Cache{
		locker: locker,
		data:   make(map[string]string),
	}
}

func (c *Cache) Set(key, value string) {
	c.locker.Lock()
	defer c.locker.Unlock()
	c.data[key] = value
}

func (c *Cache) Get(key string) (string, bool) {
	c.locker.Lock()
	defer c.locker.Unlock()
	val, ok := c.data[key]
	return val, ok
}

func main() {
	// Можно использовать обычный Mutex
	cache1 := NewCache(&sync.Mutex{})
	
	// Или RWMutex для оптимизации чтения
	cache2 := NewCache(&sync.RWMutex{})
	
	// Или даже нашу DebugLocker
	cache3 := NewCache(&DebugLocker{})
}Locker представляет объект, который можно заблокировать и разблокировать.

Когда использовать Locker?

  1. Когда ваша функция/метод должен работать с разными типами блокировок
  2. Когда вы хотите сделать код более гибким для тестирования
  3. Когда вы разрабатываете библиотеку и хотите оставить выбор блокировки пользователю

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

type Map

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

Map похож на Go map[any]any, но безопасен для одновременного использования несколькими goroutines без дополнительной блокировки или координации. Загрузка, хранение и удаление выполняются за амортизированное постоянное время.

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

Тип Map оптимизирован для двух распространенных случаев использования:

  1. когда запись для данного ключа записывается только один раз, но читается много раз, как в кэшах, которые только растут, или
  2. когда несколько goroutines читают, записывают и перезаписывают записи для непересекающихся наборов ключей.

В этих двух случаях использование Map может значительно уменьшить конфликты блокировок по сравнению с Go map в паре с отдельным Mutex или RWMutex.

Карта с нулевым значением пуста и готова к использованию. Карту нельзя копировать после первого использования.

В терминологии модели памяти Go карта организует так, что операция записи «синхронизируется перед» любой операцией чтения, которая наблюдает эффект записи, где операции чтения и записи определяются следующим образом.

  • Map.Load, Map.LoadAndDelete, Map.LoadOrStore, Map.Swap, Map.CompareAndSwap и Map.CompareAndDelete — операции чтения;
  • Map.Delete, Map.LoadAndDelete, Map.Store и Map.Swap — операции записи;
  • Map.LoadOrStore — операция записи, когда она возвращает загруженный набор, установленный в false;
  • Map.CompareAndSwap — операция записи, когда она возвращает помененный набор, установленный в true; и
  • Map.CompareAndDelete — операция записи, когда она возвращает удаленный набор, установленный в true.

func (*Map) Clear

func (m *Map) Clear()

Clear удаляет все записи, в результате чего карта становится пустой.

func (*Map) CompareAndDelete

func (m *Map) CompareAndDelete(key, old any) (deleted bool)

CompareAndDelete удаляет запись для ключа, если его значение равно old. Старое значение должно быть сопоставимого типа.

Если в карте нет текущего значения для ключа, CompareAndDelete возвращает false (даже если старое значение является значением интерфейса nil).

Подробное объяснение типа Map и функции CompareAndDelete
Что делает CompareAndDelete?

Метод CompareAndDelete выполняет атомарную операцию:

  1. Проверяет, соответствует ли текущее значение для указанного ключа old значению
  2. Если значения совпадают - удаляет запись из мапы
  3. Возвращает true, если удаление произошло, и false в противном случае

Это операция “compare-and-delete” (сравнить и удалить), аналогичная атомарным операциям CAS (Compare-And-Swap).

Как это работает?
deleted := m.CompareAndDelete(key, oldValue)
  • Если в мапе key отсутствует → возвращает false
  • Если в мапе key есть, но значение ≠ oldValue → возвращает false
  • Если в мапе key есть и значение == oldValue → удаляет запись и возвращает true
Пример использования
package main

import (
	"fmt"
	"sync"
)

func main() {
	var m sync.Map

	// Добавляем значения в мапу
	m.Store("counter", 42)
	m.Store("flag", true)
	m.Store("name", "Alice")

	// Попытка удалить с неправильным старым значением
	deleted := m.CompareAndDelete("counter", 100)
	fmt.Printf("Удаление counter=100: %v\n", deleted) // false

	// Удаление с правильным значением
	deleted = m.CompareAndDelete("counter", 42)
	fmt.Printf("Удаление counter=42: %v\n", deleted) // true

	// Проверяем, что counter удален
	_, ok := m.Load("counter")
	fmt.Printf("counter существует: %v\n", ok) // false

	// Попытка удалить несуществующий ключ
	deleted = m.CompareAndDelete("nonexistent", nil)
	fmt.Printf("Удаление nonexistent: %v\n", deleted) // false

	// Пример с nil значением
	m.Store("nil-value", nil)
	deleted = m.CompareAndDelete("nil-value", nil)
	fmt.Printf("Удаление nil-value: %v\n", deleted) // true
}
Реальный кейс использования

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

type TaskStatus string

const (
	Pending   TaskStatus = "pending"
	Running   TaskStatus = "running"
	Completed TaskStatus = "completed"
)

func completeTask(m *sync.Map, taskID string) bool {
	// Пытаемся перевести задачу из running в completed
	return m.CompareAndDelete(taskID, Running)
}

func main() {
	var tasks sync.Map

	// Инициализируем задачи
	tasks.Store("task1", Pending)
	tasks.Store("task2", Running)
	tasks.Store("task3", Running)

	// Горутины пытаются завершить задачи
	var wg sync.WaitGroup
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if completeTask(&tasks, "task2") {
				fmt.Println("Задача task2 завершена")
			}
		}()
	}

	wg.Wait()

	// Проверяем оставшиеся задачи
	tasks.Range(func(key, value interface{}) bool {
		fmt.Printf("%s: %s\n", key, value)
		return true
	})
}
Особенности работы
  1. Безопасность для concurrent-использования: метод можно вызывать из нескольких горутин без дополнительной синхронизации
  2. Сравнение значений: сравнение происходит через ==, поэтому для сложных типов нужно быть внимательным
  3. nil значения: даже если old - nil, метод вернет false если ключа нет в мапе
Когда использовать?

CompareAndDelete полезен в сценариях:

  • Удаление устаревших данных (когда значение соответствует ожидаемому)
  • Реализация конечных автоматов (state machines)
  • Оптимистичные блокировки (optimistic locking)
  • Удаление элементов только при определенных условиях

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

func (*Map) CompareAndSwap

func (m *Map) CompareAndSwap(key, old, new any) (swapped bool)

CompareAndSwap меняет местами старое и новое значения для ключа, если значение, хранящееся в карте, равно old. Старое значение должно быть сравнимого типа.

func (*Map) Delete

func (m *Map) Delete(key any)

Delete удаляет значение для ключа.

func (*Map) Load

func (m *Map) Load(key any) (value any, ok bool)

Load возвращает значение, хранящееся в карте для ключа, или nil, если значение отсутствует. Результат ok указывает, было ли найдено значение в карте.

func (*Map) LoadAndDelete

func (m *Map) LoadAndDelete(key any) (value any, loaded bool)

LoadAndDelete удаляет значение для ключа, возвращая предыдущее значение, если оно есть. Результат loaded сообщает, присутствовал ли ключ.

func (*Map) LoadOrStore

func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool)

Метод LoadOrStore выполняет атомарную операцию “загрузить или сохранить” и особенно полезен в конкурентных сценариях, когда несколько горутин могут одновременно пытаться работать с одними и теми же ключами.

Объяснеие, что происходит на самом деле
Как это работает?
actual, loaded := m.LoadOrStore(key, value)
  • Если ключ key уже существует в мапе:

    • Возвращает существующее значение (в actual)
    • loaded = true (значение было загружено)
  • Если ключ key не существует в мапе:

    • Сохраняет переданное value по этому ключу
    • Возвращает это же значение (в actual)
    • loaded = false (значение было сохранено)
Простой пример
package main

import (
	"fmt"
	"sync"
)

func main() {
	var m sync.Map

	// Пытаемся сохранить значение "apple" по ключу "fruit"
	// Так как ключа нет - оно будет сохранено
	actual, loaded := m.LoadOrStore("fruit", "apple")
	fmt.Printf("Ключ 'fruit': actual=%v, loaded=%v\n", actual, loaded)
	// Вывод: Ключ 'fruit': actual=apple, loaded=false

	// Пытаемся сохранить "banana" по тому же ключу
	// Но ключ уже существует - возвращается текущее значение
	actual, loaded = m.LoadOrStore("fruit", "banana")
	fmt.Printf("Ключ 'fruit': actual=%v, loaded=%v\n", actual, loaded)
	// Вывод: Ключ 'fruit': actual=apple, loaded=true

	// Проверяем текущее значение
	value, _ := m.Load("fruit")
	fmt.Println("Текущее значение для 'fruit':", value)
	// Вывод: Текущее значение для 'fruit': apple
}
Реальный пример: кэширование результатов

Представим сервис, который кэширует результаты дорогих вычислений:

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

var cache sync.Map

func expensiveCalculation(id int) int {
	// Имитация долгого вычисления
	time.Sleep(time.Second)
	return rand.Intn(1000)
}

func getCachedResult(id int) int {
	// Пытаемся получить результат из кэша
	result, loaded := cache.LoadOrStore(id, expensiveCalculation(id))
	
	if loaded {
		fmt.Printf("Результат для %d взят из кэша\n", id)
	} else {
		fmt.Printf("Результат для %d вычислен и сохранен\n", id)
	}
	
	return result.(int)
}

func main() {
	rand.Seed(time.Now().UnixNano())

	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			getCachedResult(id % 3) // Используем только 3 разных ID
		}(i)
	}
	wg.Wait()
}
Возможный вывод:
Результат для 0 вычислен и сохранен
Результат для 1 вычислен и сохранен
Результат для 2 вычислен и сохранен
Результат для 0 взят из кэша
Результат для 1 взят из кэша
Особенности LoadOrStore
  1. Атомарность: Операция выполняется атомарно, что делает её безопасной для использования из нескольких горутин
  2. Эффективность: Избегает “гонки” при инициализации значений
  3. Удобство: Заменяет распространённый паттерн “проверить-затем-сохранить”
Когда использовать?

LoadOrStore идеально подходит для:

  • Кэширования результатов
  • Инициализации синглтонов
  • Создания элементов по требованию
  • Любых сценариев, где нужно “получить или создать” значение атомарно

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

func (*Map) Range

func (m *Map) Range(f func(key, value any) bool)

Range вызывает f последовательно для каждого ключа и значения, присутствующих в карте. Если f возвращает false, range останавливает итерацию.

Range не обязательно соответствует какому-либо последовательному снимку содержимого карты: ни один ключ не будет посещен более одного раза, но если значение для любого ключа хранится или удаляется одновременно (в том числе f), Range может отражать любое сопоставление для этого ключа из любой точки во время вызова Range. Range не блокирует другие методы на приемнике; даже f может вызывать любой метод на m.

Range может быть O(N) с количеством элементов в карте, даже если f возвращает false после постоянного числа вызовов.

func (*Map) Store

func (m *Map) Store(key, value any)

Store устанавливает значение для ключа.

func (*Map) Swap

func (m *Map) Swap(key, value any) (previous any, loaded bool)

Swap заменяет значение ключа и возвращает предыдущее значение, если оно есть. Результат loaded сообщает, присутствовал ли ключ.

type Mutex

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

Mutex — это блокировка взаимного исключения. Нулевое значение для Mutex — это разблокированный мьютекс.

Mutex не должен копироваться после первого использования.

В терминологии модели памяти Go n-й вызов Mutex.Unlock «синхронизирует перед» m-й вызов Mutex.Lock для любого n < m. Успешный вызов Mutex.TryLock эквивалентен вызову Lock. Неудачный вызов TryLock не устанавливает никаких отношений «синхронизирует перед».

Объяснение Mutex

Mutex (сокращение от “mutual exclusion” - взаимное исключение) - это примитив синхронизации, который позволяет только одной горутине за раз получать доступ к общему ресурсу.

Представьте его как дверь в туалет:

  • Когда кто-то внутри, дверь заперта (Lock)
  • Другие ждут снаружи (блокируются)
  • Когда человек выходит, он открывает дверь (Unlock)
  • Тогда следующий может войти

Основные методы:

  1. Lock() - захватывает мьютекс (блокирует, если он уже захвачен)
  2. Unlock() - освобождает мьютекс
  3. TryLock() - пытается захватить мьютекс без блокировки (возвращает успех/неудачу)

Простой пример:

package main

import (
	"fmt"
	"sync"
	"time"
)

var counter int
var mu sync.Mutex // Создаем мьютекс

func increment() {
	mu.Lock()         // Захватываем мьютекс
	defer mu.Unlock() // Гарантируем освобождение

	temp := counter
	time.Sleep(1 * time.Millisecond) // Имитируем работу
	counter = temp + 1
}

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			increment()
		}()
	}

	wg.Wait()
	fmt.Println("Итоговое значение счетчика:", counter) // Всегда 100
}

Реальный пример: Банковский перевод

type BankAccount struct {
	balance int
	mu      sync.Mutex
}

func (acc *BankAccount) Deposit(amount int) {
	acc.mu.Lock()
	defer acc.mu.Unlock()
	
	acc.balance += amount
}

func (acc *BankAccount) Withdraw(amount int) bool {
	acc.mu.Lock()
	defer acc.mu.Unlock()
	
	if acc.balance < amount {
		return false
	}
	
	acc.balance -= amount
	return true
}

func (acc *BankAccount) Transfer(to *BankAccount, amount int) bool {
	// Важно: блокируем оба счета в одном порядке, чтобы избежать deadlock
	acc.mu.Lock()
	defer acc.mu.Unlock()
	to.mu.Lock()
	defer to.mu.Unlock()
	
	if acc.balance < amount {
		return false
	}
	
	acc.balance -= amount
	to.balance += amount
	return true
}

Когда использовать Mutex?

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

Важные правила:

  1. Всегда освобождайте мьютекс (лучше через defer)
  2. Не копируйте мьютекс после использования
  3. Избегайте блокировок на долгое время
  4. Соблюдайте порядок блокировки нескольких мьютексов

TryLock пример:

func tryUpdate(data *string) {
	var mu sync.Mutex
	
	if mu.TryLock() {
		defer mu.Unlock()
		*data = "updated"
	} else {
		fmt.Println("Не удалось получить блокировку")
	}
}

func (*Mutex) Lock

func (m *Mutex) Lock()

Lock блокирует m. Если блокировка уже используется, вызывающая goroutine блокируется до тех пор, пока мьютекс не станет доступным.

func (*Mutex) TryLock

func (m *Mutex) TryLock() bool

TryLock пытается заблокировать m и сообщает, удалось ли это.

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

func (*Mutex) Unlock

func (m *Mutex) Unlock()

Unlock разблокирует m. Если m не заблокирован при входе в Unlock, возникает ошибка выполнения.

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

type Once

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

Once — это объект, который выполнит ровно одно действие.

Once нельзя копировать после первого использования.

В терминологии модели памяти Go возврат из f «синхронизируется перед» возвратом из любого вызова once.Do(f).

Пример
package main

import (
	"fmt"
	"sync"
)

func main() {
	var once sync.Once
	onceBody := func() {
		fmt.Println("Only once")
	}
	done := make(chan bool)
	for i := 0; i < 10; i++ {
		go func() {
			once.Do(onceBody)
			done <- true
		}()
	}
	for i := 0; i < 10; i++ {
		<-done
	}
}

Этот код демонстрирует использование sync.Once в Go - структуры, которая гарантирует, что определённая функция будет выполнена ровно один раз, даже если её вызов происходит из нескольких горутин.

Разберём код по частям:

  1. Инициализация:
var once sync.Once
onceBody := func() {
    fmt.Println("Only once")
}
  • Создаётся объект sync.Once
  • Определяется функция onceBody, которая будет выполнена один раз
  1. Канал для синхронизации:
done := make(chan bool)
  • Создаётся буферизированный канал для ожидания завершения всех горутин
  1. Запуск горутин:
for i := 0; i < 10; i++ {
    go func() {
        once.Do(onceBody)
        done <- true
    }()
}
  • Запускается 10 горутин
  • Каждая вызывает once.Do(onceBody)
  • После выполнения отправляет сигнал в канал done
  1. Ожидание завершения:
for i := 0; i < 10; i++ {
    <-done
}
  • Основная горутина ждёт 10 сигналов (по одному от каждой горутины)

Что произойдёт при выполнении:

  • Все 10 горутин попытаются выполнить onceBody через once.Do()
  • sync.Once гарантирует, что:
    • Функция onceBody будет выполнена только один раз
    • Остальные горутин будут ждать завершения этого выполнения
    • Все последующие вызовы будут проигнорированы
  • В результате в консоли мы увидим только одно сообщение “Only once”

Практическое применение:

  • Инициализация глобальных ресурсов
  • Ленивая инициализация
  • Создание синглтонов
  • Однократное выполнение настройки

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

func (*Once) Do

func (o *Once) Do(f func())

Do вызывает функцию f, если и только если Do вызывается впервые для этого экземпляра Once. Другими словами, при условии

var once Once

если once.Do(f) вызывается несколько раз, только первый вызов вызовет f, даже если f имеет разное значение при каждом вызове. Для выполнения каждой функции требуется новый экземпляр Once.

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

config.once.Do(func() { config.init(filename) })

Поскольку ни один вызов Do не возвращается до тех пор, пока не вернется один вызов f, если f вызывает Do, это приведет к тупиковой ситуации.

Если f вызывает панику, Do считает, что она вернулась; будущие вызовы Do возвращаются без вызова f.

type Pool

type Pool struct {

    // New опционально указывает функцию для генерации
    // значения, когда Get в противном случае вернул бы nil.
    // Он не может быть изменен одновременно с вызовами Get.
    New func() any
    // содержит отфильтрованные или неэкспортируемые поля
}

Pool — это набор временных объектов, которые могут быть индивидуально сохранены и извлечены.

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

Pool безопасен для одновременного использования несколькими goroutines.

Цель пула — кэшировать выделенные, но неиспользуемые элементы для повторного использования в будущем, снимая нагрузку с сборщика мусора. То есть он упрощает создание эффективных, потокобезопасных списков свободных элементов. Однако он подходит не для всех списков свободных элементов.

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

Примером правильного использования пула является пакет fmt, который поддерживает хранилище временных буферов вывода динамического размера. Хранилище масштабируется под нагрузкой (когда многие goroutines активно печатают) и сокращается в состоянии покоя.

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

Пул не должен копироваться после первого использования.

В терминологии модели памяти Go вызов Put(x) «синхронизируется перед» вызовом Pool.Get, возвращающим то же значение x. Аналогично, вызов New, возвращающий x, «синхронизируется перед» вызовом Get, возвращающим то же значение x.

Пример
package main

import (
	"bytes"
	"io"
	"os"
	"sync"
	"time"
)

var bufPool = sync.Pool{
	New: func() any {
		return new(bytes.Buffer)
	},
}

func timeNow() time.Time {
	return time.Unix(1136214245, 0)
}

func Log(w io.Writer, key, val string) {
	b := bufPool.Get().(*bytes.Buffer)
	b.Reset()

    b.WriteString(timeNow().UTC().Format(time.RFC3339))
	b.WriteByte(' ')
	b.WriteString(key)
	b.WriteByte('=')
	b.WriteString(val)
	w.Write(b.Bytes())
	bufPool.Put(b)
}

func main() {
	Log(os.Stdout, "path", "/search?q=flowers")
}

Этот пример демонстрирует эффективное использование sync.Pool для оптимизации работы с временными объектами (в данном случае - bytes.Buffer).

1. Пул буферов (sync.Pool)
var bufPool = sync.Pool{
    New: func() any {
        return new(bytes.Buffer)
    },
}
  • Создается пул объектов bytes.Buffer
  • Функция New создает новый буфер, когда пул пуст
  • Важно: возвращаются указатели (new(bytes.Buffer)), чтобы избежать лишних аллокаций
2. Функция логирования
func Log(w io.Writer, key, val string) {
    // 1. Получаем буфер из пула (или создаем новый)
    b := bufPool.Get().(*bytes.Buffer)
    
    // 2. Сбрасываем состояние буфера перед использованием
    b.Reset()
    
    // 3. Формируем строку лога
    b.WriteString(timeNow().UTC().Format(time.RFC3339))
    b.WriteByte(' ')
    b.WriteString(key)
    b.WriteByte('=')
    b.WriteString(val)
    
    // 4. Выводим лог
    w.Write(b.Bytes())
    
    // 5. Возвращаем буфер в пул для повторного использования
    bufPool.Put(b)
}
3. Тестовая реализация времени
func timeNow() time.Time {
    return time.Unix(1136214245, 0) // Фиксированное время для демонстрации
}
Как это работает:
  1. При первом вызове Log:

    • Пул пуст, поэтому вызывается New и создается новый bytes.Buffer
    • Буфер используется и возвращается в пул
  2. При последующих вызовах:

    • Буфер берется из пула (без аллокации)
    • Сбрасывается (Reset())
    • Используется повторно
Преимущества подхода:
  1. Снижение нагрузки на GC:

    • Буферы переиспользуются, а не создаются заново
    • Меньше работы для сборщика мусора
  2. Экономия памяти:

    • Не нужно постоянно выделять/освобождать память
    • Размер пула автоматически регулируется
  3. Потокобезопасность:

    • sync.Pool безопасен для использования из нескольких горутин
Важные нюансы:
  1. Обязательно сбрасывайте состояние:

    • Перед использованием вызовите Reset(), чтобы очистить предыдущие данные
  2. Не сохраняйте объекты из пула:

    • После Put() считайте объект более недоступным
  3. Подходит для часто создаваемых объектов:

    • Идеально для объектов, которые:
      • Дорого создавать
      • Используются кратковременно
      • Имеют примерно одинаковый размер
Реальный вывод программы:
2006-01-02T15:04:05Z path=/search?q=flowers

Этот паттерн особенно полезен в:

  • Логгерах
  • HTTP middleware
  • Любом коде, где часто создаются временные буферы

func (*Pool) Get

func (p *Pool) Get() any

Get выбирает произвольный элемент из пула, удаляет его из пула и возвращает вызывающему. Get может игнорировать пул и рассматривать его как пустой. Вызывающие не должны предполагать какую-либо связь между значениями, переданными в Pool.Put, и значениями, возвращаемыми Get.

Если Get в противном случае вернул бы nil, а p.New не равен nil, Get возвращает результат вызова p.New.

func (*Pool) Put

func (p *Pool) Put(x any)

Put добавляет x в пул.

type RWMutex

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

RWMutex — это блокировка взаимного исключения для чтения/записи. Блокировка может удерживаться произвольным количеством читателей или одним записывающим устройством. Нулевое значение для RWMutex — это разблокированный мьютекс.

RWMutex не должен копироваться после первого использования.

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

В терминологии модели памяти Go n-й вызов RWMutex.Unlock «синхронизируется перед» m-м вызовом Lock для любого n < m, так же как и для Mutex. Для любого вызова RLock существует n, такое что n-й вызов Unlock «синхронизируется перед» этим вызовом RLock, а соответствующий вызов RWMutex.RUnlock «синхронизируется перед» n+1-м вызовом Lock.

func (*RWMutex) Lock

func (rw *RWMutex) Lock()

Lock блокирует rw для записи. Если блокировка уже заблокирована для чтения или записи, Lock блокируется до тех пор, пока блокировка не станет доступной.

func (*RWMutex) RLock

func (rw *RWMutex) RLock()

RLock блокирует rw для чтения.

Его не следует использовать для рекурсивной блокировки чтения; заблокированный вызов Lock исключает возможность получения блокировки новыми читателями. См. документацию по типу RWMutex.

func (*RWMutex) RLocker

func (rw *RWMutex) RLocker() Locker

RLocker возвращает интерфейс Locker, который реализует методы [Locker.Lock] и [Locker.Unlock] путем вызова rw.RLock и rw.RUnlock.

func (*RWMutex) RUnlock

func (rw *RWMutex) RUnlock()

RUnlock отменяет один вызов RWMutex.RLock; это не влияет на других одновременных читателей. Если rw не заблокирован для чтения при входе в RUnlock, возникает ошибка выполнения.

func (*RWMutex) TryLock

func (rw *RWMutex) TryLock() bool

TryLock пытается заблокировать rw для записи и сообщает, удалось ли это.

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

func (*RWMutex) TryRLock

func (rw *RWMutex) TryRLock() bool

TryRLock пытается заблокировать rw для чтения и сообщает, удалось ли это.

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

func (*RWMutex) Unlock

func (rw *RWMutex) Unlock()

Unlock разблокирует rw для записи. Если rw не заблокирован для записи при входе в Unlock, возникает ошибка выполнения.

Как и в случае с мьютексами, заблокированный RWMutex не связан с конкретной горутиной. Одна горутина может RWMutex.RLock (RWMutex.Lock) RWMutex, а затем организовать, чтобы другая горутина RWMutex.RUnlock (RWMutex.Unlock) его.

type WaitGroup

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

WaitGroup ожидает завершения работы набора goroutines. Основной goroutine вызывает WaitGroup.Add, чтобы установить количество goroutines, которые необходимо дождаться. Затем каждый из goroutines запускается и вызывает WaitGroup.Done по завершении работы. В то же время WaitGroup.Wait можно использовать для блокировки до тех пор, пока все goroutines не завершат работу.

WaitGroup нельзя копировать после первого использования.

В терминологии модели памяти Go вызов WaitGroup.Done «синхронизирует перед» возвратом любого вызова Wait, который он разблокирует.

Пример
package main

import (
	"sync"
)

type httpPkg struct{}

func (httpPkg) Get(url string) {}

var http httpPkg

func main() {
	var wg sync.WaitGroup
	var urls = []string{
		"http://www.golang.org/",
		"http://www.google.com/",
		"http://www.example.com/",
	}
	for _, url := range urls {
		// Increment the WaitGroup counter.
		wg.Add(1)
		// Launch a goroutine to fetch the URL.
		go func(url string) {
			// Decrement the counter when the goroutine completes.
			defer wg.Done()
			// Fetch the URL.
			http.Get(url)
		}(url)
	}
	// Wait for all HTTP fetches to complete.
	wg.Wait()
}

Этот пример демонстрирует классический паттерн использования sync.WaitGroup для ожидания завершения группы горутин.

1. Структура и имитация HTTP-клиента

type httpPkg struct{}

func (httpPkg) Get(url string) {}  // Пустой метод для демонстрации

var http httpPkg  // Глобальная переменная "HTTP-клиента"
  • Создается пустая структура httpPkg с методом Get (имитация HTTP-запроса)
  • Создается глобальная переменная http этого типа

2. Основная функция

func main() {
	var wg sync.WaitGroup  // Создаем WaitGroup
	urls := []string{      // Список URL для обработки
		"http://www.golang.org/",
		"http://www.google.com/",
		"http://www.example.com/",
	}
  • Инициализируется WaitGroup для отслеживания горутин
  • Определяется список URL для обработки

3. Запуск горутин

	for _, url := range urls {
		wg.Add(1)  // Увеличиваем счетчик WaitGroup перед запуском горутины
		
		go func(url string) {  // Запускаем горутину для каждого URL
			defer wg.Done()    // Уменьшаем счетчик при завершении (defer гарантирует выполнение)
			http.Get(url)      // "Выполняем" HTTP-запрос
		}(url)  // Важно: передаем url как параметр, чтобы избежать проблем с замыканием
	}
  • Для каждого URL:
    1. wg.Add(1) - увеличиваем счетчик ожидаемых горутин
    2. Запускаем анонимную функцию как горутину
    3. defer wg.Done() - гарантируем уменьшение счетчика при любом выходе из функции
    4. Выполняем “запрос” с помощью http.Get

4. Ожидание завершения

	wg.Wait()  // Блокируемся, пока счетчик не станет 0
}
  • Основная горутина блокируется, пока все запущенные горутины не вызовут Done()

Как это работает:

  1. Изначально счетчик WaitGroup = 0
  2. Для каждого URL:
    • Add(1) увеличивает счетчик (становится 1, 2, 3…)
    • Горутина запускается
  3. Каждая горутина при завершении вызывает Done() (уменьшает счетчик)
  4. Wait() блокируется, пока счетчик не вернется к 0

Важные моменты:

  1. Порядок операций:

    • Всегда вызывайте Add() ДО запуска горутины
    • Используйте defer wg.Done() для надежности
  2. Передача параметров:

    • URL передается как аргумент в горутину (go func(url string))
    • Это избегает проблем с общим доступом к переменной цикла
  3. Реальные применения:

    • Параллельные HTTP-запросы
    • Обработка файлов/данных в нескольких горутинах
    • Любые задачи, которые можно распараллелить

Что произойдет при выполнении:

  1. Запустятся 3 горутины (по одной на каждый URL)
  2. Основная горутина заблокируется на wg.Wait()
  3. Когда все 3 горутины завершатся (вызовут Done())
  4. Основная горутина продолжит выполнение (и завершит программу)

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

func (*WaitGroup) Add

func (wg *WaitGroup) Add(delta int)

Add добавляет delta, которая может быть отрицательной, к счетчику WaitGroup. Если счетчик становится равным нулю, все goroutines, заблокированные WaitGroup.Wait, освобождаются. Если счетчик становится отрицательным, Add вызывает панику.

Обратите внимание, что вызовы с положительной дельтой, которые происходят, когда счетчик равен нулю, должны происходить перед Wait. Вызовы с отрицательной дельтой или вызовы с положительной дельтой, которые начинаются, когда счетчик больше нуля, могут происходить в любое время. Обычно это означает, что вызовы Add должны выполняться до оператора, создающего goroutine или другое событие, которое необходимо ожидать. Если WaitGroup повторно используется для ожидания нескольких независимых наборов событий, новые вызовы Add должны происходить после того, как все предыдущие вызовы Wait вернулись. См. пример WaitGroup.

func (*WaitGroup) Done

func (wg *WaitGroup) Done()

Done уменьшает счетчик WaitGroup на единицу.

func (*WaitGroup) Wait

func (wg *WaitGroup) Wait()

Wait блокируется до тех пор, пока счетчик WaitGroup не станет равным нулю.

10.1 - Описание пакета sync/atomic

Пакет atomic предоставляет низкоуровневые примитивы атомарной памяти, полезные для реализации алгоритмов синхронизации.

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

Операция обмена, реализуемая функциями SwapT, является атомарным эквивалентом:

old = *addr
*addr = new
return old

Операция сравнения и замены, реализуемая функциями CompareAndSwapT, является атомарным эквивалентом:

if *addr == old {
	*addr = new
	return true
}
return false

Операция сложения, реализуемая функциями AddT, является атомарным эквивалентом:

*addr += delta
return *addr

Операции load и store, реализуемые функциями LoadT и StoreT, являются атомарными эквивалентами “return *addr” и “*addr = val”.

В терминологии модели памяти Go, если эффект атомарной операции A наблюдается атомарной операцией B, то A “синхронизируется раньше” B. Кроме того, все атомарные операции, выполняемые в программе, ведут себя так, как будто выполняются в некотором последовательно согласованном порядке. Это определение обеспечивает ту же семантику, что и последовательные атомики в C++ и волатильные переменные в Java.

Функции

func AddInt32

func AddInt32(addr *int32, delta int32) (new int32)

AddInt32 атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Add.

func AddInt64

func AddInt64(addr *int64, delta int64) (new int64)

AddInt64 атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Add (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func AddUint32

func AddUint32(addr *uint32, delta uint32) (new uint32)

AddUint32 атомарно добавляет дельту к *addr и возвращает новое значение. Чтобы вычесть из x знаковое положительное константное значение c, выполните AddUint32(&x, ^uint32(c-1)). В частности, чтобы уменьшить x, выполните AddUint32(&x, ^uint32(0)). Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Add.

func AddUint64

func AddUint64(addr *uint64, delta uint64) (new uint64)

AddUint64 атомарно добавляет delta к *addr и возвращает новое значение. Чтобы вычесть из x знаковое положительное константное значение c, выполните AddUint64(&x, ^uint64(c-1)). В частности, чтобы уменьшить x, выполните AddUint64(&x, ^uint64(0)). Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Add (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func AddUintptr

func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)

AddUintptr атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Add.

func AndInt32

func AndInt32(addr *int32, mask int32) (old int32)

AndInt32 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.And.

func AndInt64

func AndInt64(addr *int64, mask int64) (old int64)

AndInt64 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.And вместо этого.

func AndUint32

func AndUint32(addr *uint32, mask uint32) (old uint32)

AndUint32 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.And вместо него.

func AndUint64

func AndUint64(addr *uint64, mask uint64) (old uint64)

AndUint64 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.And вместо этого.

func AndUintptr

func AndUintptr(addr *uintptr, mask uintptr) (old uintptr)

AndUintptr атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.And вместо этого.

func CompareAndSwapInt32

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

CompareAndSwapInt32 выполняет операцию сравнения и замены для значения int32. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.CompareAndSwap.

func CompareAndSwapInt64

func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)

CompareAndSwapInt64 выполняет операцию сравнения и замены для значения int64. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.CompareAndSwap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func CompareAndSwapPointer

func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)

CompareAndSwapPointer выполняет операцию сравнения и обмена для значения unsafe.Pointer. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.CompareAndSwap.

func CompareAndSwapUint32

func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)

CompareAndSwapUint32 выполняет операцию сравнения и замены для значения uint32. Рассмотрите возможность использования более удобной и менее подверженной ошибкам функции Uint32.CompareAndSwap.

func CompareAndSwapUint64

func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)

CompareAndSwapUint64 выполняет операцию сравнения и замены для значения uint64. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.CompareAndSwap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func CompareAndSwapUintptr

func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)

CompareAndSwapUintptr выполняет операцию сравнения и замены для значения uintptr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.CompareAndSwap.

func LoadInt32

func LoadInt32(addr *int32) (val int32)

LoadInt32 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Load.

func LoadInt64

func LoadInt64(addr *int64) (val int64)

LoadInt64 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Load (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func LoadPointer

func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)

LoadPointer атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Load.

func LoadUint32

func LoadUint32(addr *uint32) (val uint32)

LoadUint32 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Load.

func LoadUint64

func LoadUint64(addr *uint64) (val uint64)

LoadUint64 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Load (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func LoadUintptr

func LoadUintptr(addr *uintptr) (val uintptr)

LoadUintptr атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Load.

func OrInt32

func OrInt32(addr *int32, mask int32) (old int32)

OrInt32 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, указанную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Or.

func OrInt64

func OrInt64(addr *int64, mask int64) (old int64)

OrInt64 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Or вместо него.

func OrUint32

func OrUint32(addr *uint32, mask uint32) (старый uint32)

OrUint32 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Or.

func OrUint64

func OrUint64(addr *uint64, mask uint64) (old uint64)

OrUint64 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Or вместо него.

func OrUintptr

func OrUintptr(addr *uintptr, mask uintptr) (old uintptr)

OrUintptr атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Or.

func StoreInt32

func StoreInt32(addr *int32, val int32)

StoreInt32 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Store.

func StoreInt64

func StoreInt64(addr *int64, val int64)

StoreInt64 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Store (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func StorePointer

func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)

StorePointer атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Store.

func StoreUint32

func StoreUint32(addr *uint32, val uint32)

StoreUint32 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Store.

func StoreUint64

func StoreUint64(addr *uint64, val uint64)

StoreUint64 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Store (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func StoreUintptr

func StoreUintptr(addr *uintptr, val uintptr)

StoreUintptr атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Store.

func SwapInt32

func SwapInt32(addr *int32, new int32) (old int32)

SwapInt32 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Swap.

func SwapInt64

func SwapInt64(addr *int64, new int64) (old int64)

SwapInt64 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Swap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func SwapPointer

func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)

SwapPointer атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Swap.

func SwapUint32

func SwapUint32(addr *uint32, new uint32) (old uint32)

SwapUint32 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Swap.

func SwapUint64

func SwapUint64(addr *uint64, new uint64) (old uint64)

SwapUint64 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Swap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).

func SwapUintptr

func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)

SwapUintptr атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Swap.

Типы

type Bool

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

Bool — это атомарное булево значение. Нулевое значение — false.

func (*Bool) CompareAndSwap

func (x *Bool) CompareAndSwap(old, new bool) (swapped bool)

CompareAndSwap выполняет операцию сравнения и замены для булевого значения x.

package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	var flag atomic.Bool
	flag.Store(false)

	// Пытаемся изменить false → true (успешно)
	swapped := flag.CompareAndSwap(false, true)
	fmt.Println("Swapped (false→true):", swapped, "Flag:", flag.Load())

	// Пытаемся изменить false → true (неуспешно, текущее значение уже true)
	swapped = flag.CompareAndSwap(false, true)
	fmt.Println("Swapped (false→true):", swapped, "Flag:", flag.Load())
}

func (*Bool) Load

func (x *Bool) Load() bool

Load атомарно загружает и возвращает значение, хранящееся в x.

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

func main() {
	var ready atomic.Bool
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		time.Sleep(500 * time.Millisecond)
		ready.Store(true)
	}()

	for !ready.Load() {
		fmt.Println("Waiting...")
		time.Sleep(100 * time.Millisecond)
	}
	fmt.Println("Ready!")
	wg.Wait()
}

func (*Bool) Store

func (x *Bool) Store(val bool)

Store атомарно сохраняет val в x.

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var initialized atomic.Bool
	var wg sync.WaitGroup

	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			if initialized.Load() {
				fmt.Printf("Worker %d: already initialized\n", id)
				return
			}

			initialized.Store(true)
			fmt.Printf("Worker %d: did initialization\n", id)
		}(i)
	}

	wg.Wait()
}

func (*Bool) Swap

func (x *Bool) Swap(new bool) (old bool)

Swap атомарно сохраняет new в x и возвращает предыдущее значение.

package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	var toggle atomic.Bool
	toggle.Store(false)

	// Переключаем значение и получаем старое
	old := toggle.Swap(true)
	fmt.Println("Old value:", old, "New value:", toggle.Load())

	// Переключаем ещё раз
	old = toggle.Swap(false)
	fmt.Println("Old value:", old, "New value:", toggle.Load())
}

type Int32/Int64/Uint32/Uint64/Uintptr

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

Int32 — это атомарное целое число int32. Нулевое значение — ноль. Int64 — это атомарное целое число int64. Нулевое значение — ноль. Uint32 - это атомарный uint32. Нулевое значение равно нулю. Uint64 - это атомарный uint64. Нулевое значение равно нулю. Uintptr - это атомарный uintptr. Нулевое значение равно нулю.

func (*Int32) Add

func (x *Int32) Add(delta int32) (new int32)

Add атомарно добавляет delta к x и возвращает новое значение.

func (*Int32) And

func (x *Int32) And(mask int32) (old int32)

And атомарно выполняет побитовую операцию AND над x, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение.

func (*Int32) CompareAndSwap

func (x *Int32) CompareAndSwap(old, new int32) (swapped bool)

CompareAndSwap выполняет операцию сравнения и замены для x.

func (*Int32) Load

func (x *Int32) Load() int32

Load атомарно загружает и возвращает значение, хранящееся в x.

func (*Int32) Or

func (x *Int32) Or(mask int32) (old int32)

Or атомарно выполняет операцию побитового ИЛИ над x, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение.

func (*Int32) Store

func (x *Int32) Store(val int32)

Store атомарно сохраняет val в x.

func (*Int32) Swap

func (x *Int32) Swap(new int32) (old int32)

Swap атомарно сохраняет new в x и возвращает предыдущее значение.

type Pointer

type Pointer[T any] struct {
    // содержит отфильтрованные или неэкспортируемые поля
}

Pointer — это атомарный указатель типа *T. Нулевое значение — nil *T.

func (*Pointer[T]) CompareAndSwap

func (x *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool)

CompareAndSwap выполняет операцию сравнения и замены для x.

func (*Pointer[T]) Load

func (x *Pointer[T]) Load() *T

Load атомарно загружает и возвращает значение, хранящееся в x.

func (*Pointer[T]) Store

func (x *Pointer[T]) Store(val *T)

Store атомарно сохраняет val в x.

func (*Pointer[T]) Swap

func (x *Pointer[T]) Swap(new *T) (old *T)

Swap атомарно сохраняет new в x и возвращает предыдущее значение.

type Value

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

Value обеспечивает атомарную загрузку и хранение значения с постоянным типом. Нулевое значение для Value возвращает nil из Value.Load. После вызова Value.Store Value не должен копироваться.

Value не должен копироваться после первого использования.

Пример config
package main

import (
	"fmt"
	"math/rand"
	"sync/atomic"
	"time"
)

// ServerConfig содержит конфигурацию сервера
type ServerConfig struct {
	Host    string
	Port    int
	Timeout time.Duration
	Debug   bool
}

var (
	config  atomic.Value // Текущая конфигурация
	counter atomic.Int32 // Счетчик запросов
)

func loadConfig() ServerConfig {
	// Имитация загрузки конфигурации (в реальности может быть из файла, БД и т.д.)
	rand.Seed(time.Now().UnixNano())
	return ServerConfig{
		Host:    fmt.Sprintf("server-%d.example.com", rand.Intn(3)+1),
		Port:    8000 + rand.Intn(100),
		Timeout: time.Duration(rand.Intn(5)+1) * time.Second,
		Debug:   rand.Intn(2) == 0,
	}
}

func processRequest(workerID int) {
	for {
		// Имитация обработки запроса
		time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)

		// Атомарно получаем текущую конфигурацию
		conf := config.Load().(ServerConfig)
		reqID := counter.Add(1)

		fmt.Printf("Worker %d processing request #%d with config: %+v\n",
			workerID, reqID, conf)
	}
}

func configUpdater() {
	for {
		time.Sleep(5 * time.Second)
		newConfig := loadConfig()
		config.Store(newConfig)
		fmt.Printf("\nConfig updated: %+v\n\n", newConfig)
	}
}

func main() {
	// Инициализация начальной конфигурации
	config.Store(loadConfig())
	fmt.Println("Initial config:", config.Load())

	// Запускаем обновление конфигурации в фоне
	go configUpdater()

	// Запускаем воркеров для обработки запросов
	for i := 1; i <= 5; i++ {
		go processRequest(i)
	}

	// Даем поработать 30 секунд
	time.Sleep(30 * time.Second)
	fmt.Println("Server shutting down...")
}

Вывод программы:

Initial config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}

Worker 5 processing request #1 with config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}
Worker 3 processing request #2 with config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}

Config updated: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}

Worker 1 processing request #3 with config: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}
Worker 4 processing request #4 with config: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}
Пример ReadMostly
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

// ConcurrentMap реализует потокобезопасную map с помощью atomic.Value
type ConcurrentMap struct {
	data atomic.Value // хранит map[string]string
	mu   sync.Mutex   // только для записи
}

// NewConcurrentMap создает новую потокобезопасную map
func NewConcurrentMap() *ConcurrentMap {
	cm := &ConcurrentMap{}
	cm.data.Store(make(map[string]string))
	return cm
}

// Get безопасно получает значение по ключу
func (cm *ConcurrentMap) Get(key string) (string, bool) {
	currentMap := cm.data.Load().(map[string]string)
	val, ok := currentMap[key]
	return val, ok
}

// Set безопасно устанавливает значение по ключу
func (cm *ConcurrentMap) Set(key, value string) {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	// Копируем текущую map
	oldMap := cm.data.Load().(map[string]string)
	newMap := make(map[string]string, len(oldMap)+1)

	// Копируем все элементы
	for k, v := range oldMap {
		newMap[k] = v
	}

	// Добавляем/обновляем элемент
	newMap[key] = value

	// Атомарно заменяем map
	cm.data.Store(newMap)
}

// Delete безопасно удаляет элемент по ключу
func (cm *ConcurrentMap) Delete(key string) {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	oldMap := cm.data.Load().(map[string]string)
	if _, exists := oldMap[key]; !exists {
		return
	}

	newMap := make(map[string]string, len(oldMap))
	for k, v := range oldMap {
		if k != key {
			newMap[k] = v
		}
	}

	cm.data.Store(newMap)
}

func main() {
	config := NewConcurrentMap()

	// Записываем начальные значения
	config.Set("host", "localhost")
	config.Set("port", "8080")
	config.Set("timeout", "30s")

	// Запускаем 5 читателей
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			for j := 0; j < 3; j++ {
				time.Sleep(time.Millisecond * 100)
				if val, ok := config.Get("host"); ok {
					fmt.Printf("Reader %d: host=%s\n", id, val)
				}
			}
		}(i)
	}

	// Запускаем 2 писателя
	for i := 0; i < 2; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			for j := 0; j < 2; j++ {
				time.Sleep(time.Millisecond * 200)
				newHost := fmt.Sprintf("server-%d-%d", id, j)
				config.Set("host", newHost)
				fmt.Printf("Writer %d updated host to %s\n", id, newHost)
			}
		}(i)
	}

	// Запускаем удаление через некоторое время
	wg.Add(1)
	go func() {
		defer wg.Done()
		time.Sleep(time.Millisecond * 300)
		config.Delete("timeout")
		fmt.Println("Deleted timeout key")
	}()

	wg.Wait()

	// Финальное состояние
	fmt.Println("\nFinal config:")
	if host, ok := config.Get("host"); ok {
		fmt.Println("host:", host)
	}
	if port, ok := config.Get("port"); ok {
		fmt.Println("port:", port)
	}
	if _, ok := config.Get("timeout"); !ok {
		fmt.Println("timeout: (deleted)")
	}
}

Вывод программы:

Reader 4: host=localhost
Reader 0: host=localhost
Reader 2: host=localhost
Reader 1: host=localhost
Writer 0 updated host to server-0-0
Reader 3: host=server-0-0
Reader 0: host=server-0-0
Reader 4: host=server-0-0
Writer 1 updated host to server-1-0
Reader 2: host=server-1-0
Deleted timeout key
Reader 1: host=server-1-0
Writer 0 updated host to server-0-1
Writer 1 updated host to server-1-1
Reader 3: host=server-1-1

Final config:
host: server-1-1
port: 8080
timeout: (deleted)

func (*Value) CompareAndSwap

func (v *Value) CompareAndSwap(old, new any) (swapped bool)

CompareAndSwap выполняет операцию сравнения и замены для Value.

Все вызовы CompareAndSwap для данного значения должны использовать значения одного и того же конкретного типа. CompareAndSwap несовместимого типа вызывает панику, как и CompareAndSwap(old, nil).

func (*Value) Load

func (v *Value) Load() (val any)

Load возвращает значение, установленное последним Store. Он возвращает nil, если для этого значения не было вызова Store.

func (*Value) Store

func (v *Value) Store(val any)

Store устанавливает значение Value v равным val. Все вызовы Store для данного Value должны использовать значения одного и того же конкретного типа. Store несовместимого типа вызывает панику, как и Store(nil).

func (*Value) Swap

func (v *Value) Swap(new any) (old any)

Swap сохраняет new в Value и возвращает предыдущее значение. Он возвращает nil, если Value пуст.

Все вызовы Swap для данного Value должны использовать значения одного и того же конкретного типа. Swap несовместимого типа вызывает панику, как и Swap(nil).

11 - Пакет os языка программирования Go

Пакет os предоставляет платформонезависимый интерфейс к функциональности операционной системы.

Пакет os предоставляет платформонезависимый интерфейс к функциональности операционной системы. Дизайн Unix-подобный, хотя обработка ошибок Go-подобная; неудачные вызовы возвращают значения типа error, а не номера ошибок. Часто в ошибке содержится больше информации. Например, если вызов, принимающий имя файла, не работает, например Open или Stat, ошибка будет включать имя файла, который не работает, и будет иметь тип *PathError, который может быть распакован для получения дополнительной информации.

Интерфейс os должен быть единообразным для всех операционных систем. Функции, которые не являются общедоступными, представлены в специфическом для системы пакете syscall.

Приведем простой пример: открытие файла и чтение его части.

file, err := os.Open("file.go") // For read access.
if err != nil {
	log.Fatal(err)
}

Если открытие завершится неудачей, строка ошибки будет иметь понятный вид, например

open file.go: no such file or directory

Затем данные файла могут быть считаны в виде фрагмента байтов. Команды Read и Write берут количество байтов из длины фрагмента аргумента.

data := make([]byte, 100)
count, err := file.Read(data)
if err != nil {
log.Fatal(err)
}
fmt.Printf("read %d bytes: %q\n", count, data[:count])

Concurrency (Параллелизм)

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

Константы


const (
    // Должно быть указано ровно одно из значений O_RDONLY, O_WRONLY или O_RDWR.
    O_RDONLY int = syscall.O_RDONLY // открыть файл только для чтения.
    O_WRONLY int = syscall.O_WRONLY // открыть файл только для записи.
	O_RDWR   int = syscall.O_RDWR   // открыть файл для чтения и записи.
    // Остальные значения могут быть объединены с помощью оператора OR для управления поведением.
    O_APPEND int = syscall.O_APPEND // добавлять данные в файл при записи.
    O_CREATE int = syscall.O_CREAT  // создать новый файл, если его нет.
	O_EXCL   int = syscall.O_EXCL   // используется с O_CREATE, файл не должен существовать.
    O_SYNC   int = syscall.O_SYNC   // открыть для синхронного ввода-вывода.
    O_TRUNC  int = syscall.O_TRUNC  // усечь обычный файл с правом записи при открытии.
)

Флаги для OpenFile, оборачивающие флаги базовой системы. Не все флаги могут быть реализованы в данной системе.

Просмотреть исходный код const ( PathSeparator = „/“ // разделитель путей, специфичный для ОС PathListSeparator = „:“ // разделитель списка путей, специфичный для ОС)

const (
    // Одиночные буквы являются аббревиатурами,
    // используемыми для форматирования методом String.
	ModeDir        = fs.ModeDir        // d: каталог
    ModeAppend     = fs.ModeAppend     // a: только добавление
    ModeExclusive  = fs.ModeExclusive  // l: исключительное использование
    ModeTemporary  = fs.ModeTemporary  // T: временный файл; только Plan 9
    ModeSymlink    = fs.ModeSymlink    // L: символическая ссылка
	ModeDevice     = fs.ModeDevice     // D: файл устройства
    ModeNamedPipe  = fs.ModeNamedPipe  // p: именованный канал (FIFO)
    ModeSocket     = fs.ModeSocket     // S: сокет домена Unix
    ModeSetuid     = fs.ModeSetuid     // u: setuid
	ModeSetgid     = fs.ModeSetgid     // g: setgid
    ModeCharDevice = fs.ModeCharDevice // c: символьное устройство Unix, когда установлен ModeDevice
    ModeSticky     = fs.ModeSticky     // t: sticky
    ModeIrregular  = fs.ModeIrregular  // ?: нерегулярный файл; ничего больше не известно об этом файле

	// Маска для битов типа. Для обычных файлов не будет установлено ничего.
    ModeType = fs.ModeType

    ModePerm = fs.ModePerm // Биты разрешений Unix, 0o777
	)

Определенные биты режима файла являются наиболее значимыми битами FileMode. Девять наименее значимых битов являются стандартными разрешениями Unix rwxrwxrwx. Значения этих битов следует рассматривать как часть общедоступного API и могут использоваться в протоколах передачи данных или представлениях диска: их нельзя изменять, хотя могут быть добавлены новые биты.

const DevNull = "/dev/null"

DevNull — это имя «нулевого устройства» операционной системы. В Unix-подобных системах оно имеет вид «/dev/null», в Windows — «NUL».

Переменные

var (
    // ErrInvalid указывает на недействительный аргумент.
    // Методы File возвращают эту ошибку, когда получатель равен nil.
    ErrInvalid = fs.ErrInvalid // «недействительный аргумент»

    ErrPermission = fs.ErrPermission // «разрешение отказано»
    ErrExist      = fs.ErrExist      // «файл уже существует»
	ErrNotExist   = fs.ErrNotExist   // «файл не существует»
    ErrClosed     = fs.ErrClosed     // «файл уже закрыт»

    ErrNoDeadline       = errNoDeadline()       // «тип файла не поддерживает срок»
    ErrDeadlineExceeded = errDeadlineExceeded() // «тайм-аут ввода-вывода»
)

Портативные аналоги некоторых распространенных ошибок системных вызовов.

Ошибки, возвращаемые из этого пакета, можно проверить на наличие этих ошибок с помощью errors.Is.

var (
    Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
    Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
	Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr"))

Stdin, Stdout и Stderr — открытые файлы, указывающие на дескрипторы стандартного ввода, стандартного вывода и стандартных ошибок.

Обратите внимание, что среда выполнения Go записывает в стандартный файл ошибок сообщения о паниках и сбоях; закрытие Stderr может привести к тому, что эти сообщения будут отправляться в другое место, например в файл, открытый позже.

var Args []string

Args содержит аргументы командной строки, начиная с имени программы.

var ErrProcessDone = errors.New("os: process already finished")

ErrProcessDone указывает, что процесс завершен.

11.1 - Описание функций пакета os

Основные функции пакета os языка программирования GO

func Chdir

func Chdir(dir string) error

Chdir изменяет текущий рабочий каталог на указанный каталог. Если произошла ошибка, она будет типа *PathError.

func Chmod

func Chmod(name string, mode FileMode) error

Chmod изменяет режим указанного файла на mode. Если файл является символической ссылкой, то изменяется режим цели ссылки. Если произошла ошибка, то она будет типа *PathError.

В зависимости от операционной системы используется разный поднабор битов режима.

В Unix используются биты разрешений режима ModeSetuid, ModeSetgid и ModeSticky.

В Windows используется только бит 0o200 (доступ на запись для владельца) режима; он контролирует, установлен ли или снят атрибут «только для чтения» файла. Остальные биты в настоящее время не используются. Для совместимости с Go 1.12 и более ранними версиями используйте режим, отличный от нуля. Используйте режим 0o400 для файла, доступного только для чтения, и 0o600 для файла, доступного для чтения и записи.

В Plan 9 используются биты разрешений режима ModeAppend, ModeExclusive и ModeTemporary.

Пример
package main

import (
	"log"
	"os"
)

func main() {
	if err := os.Chmod("some-filename", 0644); err != nil {
		log.Fatal(err)
	}
}

func Chown

func Chown(name string, uid, gid int) error

Chown изменяет числовые значения uid и gid указанного файла. Если файл является символической ссылкой, он изменяет uid и gid цели ссылки. uid или gid равные -1 означают, что это значение не изменяется. Если происходит ошибка, она будет типа *PathError.

В Windows или Plan 9 Chown всегда возвращает ошибку syscall.EWINDOWS или EPLAN9, обернутую в *PathError.

func Chtimes

func Chtimes(name string, atime time.Time, mtime time.Time) error

Chtimes изменяет время доступа и изменения указанного файла, аналогично функциям Unix utime() или utimes(). Нулевое значение time.Time оставит время соответствующего файла без изменений.

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

Пример
package main

import (
	"log"
	"os"
	"time"
)

func main() {
	mtime := time.Date(2006, time.February, 1, 3, 4, 5, 0, time.UTC)
	atime := time.Date(2007, time.March, 2, 4, 5, 6, 0, time.UTC)
	if err := os.Chtimes("some-filename", atime, mtime); err != nil {
		log.Fatal(err)
	}
}

func Clearenv

func Clearenv()

Clearenv удаляет все переменные окружения.

func CopyFS

func CopyFS(dir string, fsys fs.FS) error

CopyFS копирует файловую систему fsys в каталог dir, создавая dir при необходимости.

Файлы создаются с режимом 0o666 плюс любые права на выполнение из источника, а каталоги создаются с режимом 0o777 (до umask).

CopyFS не перезаписывает существующие файлы. Если имя файла в fsys уже существует в месте назначения, CopyFS вернет ошибку, так что errors.Is(err, fs.ErrExist) будет true.

Символьные ссылки в fsys не поддерживаются. При копировании из символьной ссылки возвращается *PathError с Err, установленным в ErrInvalid.

Символьные ссылки в dir прослеживаются.

Новые файлы, добавленные в fsys (в том числе, если dir является подкаталогом fsys) во время работы CopyFS, не гарантированно будут скопированы.

Копирование останавливается при возникновении первой ошибки и возвращает ее.

func DirFS

func DirFS(dir string) fs.FS

DirFS возвращает файловую систему (fs.FS) для дерева файлов, корнем которого является каталог dir.

Обратите внимание, что DirFS(«/prefix») гарантирует только то, что вызовы Open, которые он делает в операционной системе, будут начинаться с «/prefix»: DirFS(«/prefix»).Open(„file“) равно os.Open(«/prefix/file»). Поэтому, если /prefix/file является символической ссылкой, указывающей за пределы дерева /prefix, то использование DirFS не останавливает доступ больше, чем использование os.Open. Кроме того, корень fs.FS, возвращаемый для относительного пути, DirFS(«prefix»), будет затронут последующими вызовами Chdir. Поэтому DirFS не является общей заменой механизма безопасности типа chroot, когда дерево каталогов содержит произвольное содержимое.

Используйте Root.FS, чтобы получить fs.FS, который предотвращает выход из дерева через символьные ссылки.

Каталог dir не должен быть «».

Результат реализует io/fs.StatFS, io/fs.ReadFileFS и io/fs.ReadDirFS.

func Environ

func Environ() []string

Environ возвращает копию строк, представляющих среду, в форме «ключ=значение».

func Executable

func Executable() (string, error)

Executable возвращает имя пути к исполняемому файлу, который запустил текущий процесс. Нет гарантии, что путь по-прежнему указывает на правильный исполняемый файл. Если для запуска процесса использовалась символьная ссылка, в зависимости от операционной системы результатом может быть символьная ссылка или путь, на который она указывала. Если требуется стабильный результат, может помочь path/filepath.EvalSymlinks.

Executable возвращает абсолютный путь, если не произошла ошибка.

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

func Exit

func Exit(code int)

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

Для обеспечения переносимости код статуса должен находиться в диапазоне [0, 125].

func Expand

func Expand(s string, mapping func(string) string) string

Expand заменяет ${var} или $var в строке на основе функции сопоставления. Например, os.ExpandEnv(s) эквивалентно os.Expand(s, os.Getenv).

Пример
package main

import (
	"fmt"
	"os"
)

func main() {
	mapper := func(placeholderName string) string {
		switch placeholderName {
		case "DAY_PART":
			return "morning"
		case "NAME":
			return "Gopher"
		}

		return ""
	}

	fmt.Println(os.Expand("Good ${DAY_PART}, $NAME!", mapper))

}
Output:

Good morning, Gopher!

func ExpandEnv

func ExpandEnv(s string) string

ExpandEnv заменяет ${var} или $var в строке в соответствии со значениями текущих переменных окружения. Ссылки на неопределенные переменные заменяются пустой строкой.

Пример
package main

import (
	"fmt"
	"os"
)

func main() {
	os.Setenv("NAME", "gopher")
	os.Setenv("BURROW", "/usr/gopher")

	fmt.Println(os.ExpandEnv("$NAME lives in ${BURROW}."))

}
Output:

gopher lives in /usr/gopher.

func Getegid

func Getegid() int

Getegid возвращает числовой идентификатор эффективной группы вызывающего.

В Windows возвращает -1.

func Getenv

func Getenv(key string) string

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

Пример
package main

import (
	"fmt"
	"os"
)

func main() {
	os.Setenv("NAME", "gopher")
	os.Setenv("BURROW", "/usr/gopher")

	fmt.Printf("%s lives in %s.\n", os.Getenv("NAME"), os.Getenv("BURROW"))

}
Output:

gopher lives in /usr/gopher.

func Geteuid

func Geteuid() int

Geteuid возвращает числовой эффективный идентификатор пользователя вызывающего.

В Windows возвращает -1.

func Getgid

func Getgid() int

Getgid возвращает числовой идентификатор группы вызывающего.

В Windows возвращает -1.

func Getgroups

func Getgroups() ([]int, error)

Getgroups возвращает список числовых идентификаторов групп, к которым принадлежит вызывающий.

В Windows возвращает syscall.EWINDOWS. Возможную альтернативу см. в пакете os/user.

func Getpagesize

func Getpagesize() int

Getpagesize возвращает размер страницы памяти базовой системы.

func Getpid

func Getpid() int

Getpid возвращает идентификатор процесса вызывающего.

func Getppid

func Getppid() int

Getppid возвращает идентификатор процесса родителя вызывающего.

func Getuid

func Getuid() int

Getuid возвращает числовой идентификатор пользователя вызывающего.

В Windows возвращает -1.

func Getwd

func Getwd() (dir string, err error)

Getwd возвращает абсолютное имя пути, соответствующее текущему каталогу. Если к текущему каталогу можно перейти по нескольким путям (из-за символьных ссылок), Getwd может вернуть любой из них.

На платформах Unix, если переменная окружения PWD предоставляет абсолютное имя, и оно является именем текущего каталога, оно возвращается.

func Hostname

func Hostname() (name string, err error)

Hostname возвращает имя хоста, сообщаемое ядром.

func IsExist

func IsExist(err error) bool

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

Эта функция предшествует errors.Is. Она поддерживает только ошибки, возвращаемые пакетом os. В новом коде следует использовать errors.Is(err, fs.ErrExist).

func IsNotExist

func IsNotExist(err error) bool

IsNotExist возвращает булево значение, указывающее, известно ли, что аргумент сообщает о том, что файл или каталог не существует. Это условие выполняется для ErrNotExist, а также для некоторых ошибок системных вызовов.

Эта функция предшествует errors.Is. Она поддерживает только ошибки, возвращаемые пакетом os. В новом коде следует использовать errors.Is(err, fs.ErrNotExist).

func IsPathSeparator

func IsPathSeparator(c uint8) bool

IsPathSeparator сообщает, является ли c символом разделителя каталогов.

func IsPermission

func IsPermission(err error) bool

IsPermission возвращает булево значение, указывающее, известно ли, что аргумент сообщает об отказе в разрешении. Это условие выполняется ErrPermission, а также некоторыми ошибками системных вызовов.

Эта функция предшествует errors.Is. Она поддерживает только ошибки, возвращаемые пакетом os. В новом коде следует использовать errors.Is(err, fs.ErrPermission).

func IsTimeout

func IsTimeout(err error) bool

IsTimeout возвращает булево значение, указывающее, известно ли, что аргумент сообщает о возникновении таймаута.

Эта функция предшествует errors.Is, и понятие о том, указывает ли ошибка на тайм-аут, может быть неоднозначным. Например, ошибка Unix EWOULDBLOCK иногда указывает на тайм-аут, а иногда нет. В новом коде следует использовать errors.Is со значением, соответствующим вызову, возвращающему ошибку, например os.ErrDeadlineExceeded.

func Lchown

func Lchown(name string, uid, gid int) error

Lchown изменяет числовые значения uid и gid указанного файла. Если файл является символической ссылкой, то изменяются значения uid и gid самой ссылки. Если происходит ошибка, то она будет типа *PathError.

В Windows всегда возвращается ошибка syscall.EWINDOWS, обернутая в *PathError.

func Link(oldname, newname string) error

Link создает newname как жесткую ссылку на файл oldname. Если происходит ошибка, она будет типа *LinkError.

func LookupEnv

func LookupEnv(key string) (string, bool)

LookupEnv извлекает значение переменной окружения, указанной ключом. Если переменная присутствует в окружении, возвращается значение (которое может быть пустым) и булево значение true. В противном случае возвращаемое значение будет пустым, а булево значение будет false.

Пример
package main

import (
	"fmt"
	"os"
)

func main() {
	show := func(key string) {
		val, ok := os.LookupEnv(key)
		if !ok {
			fmt.Printf("%s not set\n", key)
		} else {
			fmt.Printf("%s=%s\n", key, val)
		}
	}

	os.Setenv("SOME_KEY", "value")
	os.Setenv("EMPTY_KEY", "")

	show("SOME_KEY")
	show("EMPTY_KEY")
	show("MISSING_KEY")

}
Output:

SOME_KEY=value
EMPTY_KEY=
MISSING_KEY not set

func Mkdir

func Mkdir(name string, perm FileMode) error

Mkdir создает новый каталог с указанным именем и битами разрешений (до umask). Если произошла ошибка, она будет типа *PathError.

Пример
package main

import (
	"log"
	"os"
)

func main() {
	err := os.Mkdir("testdir", 0750)
	if err != nil && !os.IsExist(err) {
		log.Fatal(err)
	}
	err = os.WriteFile("testdir/testfile.txt", []byte("Hello, Gophers!"), 0660)
	if err != nil {
		log.Fatal(err)
	}
}

func MkdirAll

func MkdirAll(path string, perm FileMode) error

MkdirAll создает каталог с именем path, а также все необходимые родительские каталоги, и возвращает nil, или же возвращает ошибку. Биты прав доступа perm (до umask) используются для всех каталогов, которые создает MkdirAll. Если path уже является каталогом, MkdirAll ничего не делает и возвращает nil.

Пример
package main

import (
	"log"
	"os"
)

func main() {
	err := os.MkdirAll("test/subdir", 0750)
	if err != nil {
		log.Fatal(err)
	}
	err = os.WriteFile("test/subdir/testfile.txt", []byte("Hello, Gophers!"), 0660)
	if err != nil {
		log.Fatal(err)
	}
}

func MkdirTemp

func MkdirTemp(dir, pattern string) (string, error)

MkdirTemp создает новый временный каталог в каталоге dir и возвращает путь к новому каталогу. Имя нового каталога генерируется путем добавления случайной строки в конец pattern. Если pattern содержит “*”, случайная строка заменяет последний “*”. Каталог создается с режимом 0o700 (до umask). Если dir — пустая строка, MkdirTemp использует каталог по умолчанию для временных файлов, возвращаемый TempDir. Несколько программ или goroutines, вызывающих MkdirTemp одновременно, не будут выбирать один и тот же каталог. Ответственность за удаление каталога, когда он больше не нужен, лежит на вызывающем.

Пример
package main

import (
	"log"
	"os"
	"path/filepath"
)

func main() {
	dir, err := os.MkdirTemp("", "example")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(dir) // clean up

	file := filepath.Join(dir, "tmpfile")
	if err := os.WriteFile(file, []byte("content"), 0666); err != nil {
		log.Fatal(err)
	}
}
Пример suffix
package main

import (
	"log"
	"os"
	"path/filepath"
)

func main() {
	logsDir, err := os.MkdirTemp("", "*-logs")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(logsDir) // clean up

	// Logs can be cleaned out earlier if needed by searching
	// for all directories whose suffix ends in *-logs.
	globPattern := filepath.Join(os.TempDir(), "*-logs")
	matches, err := filepath.Glob(globPattern)
	if err != nil {
		log.Fatalf("Failed to match %q: %v", globPattern, err)
	}

	for _, match := range matches {
		if err := os.RemoveAll(match); err != nil {
			log.Printf("Failed to remove %q: %v", match, err)
		}
	}
}

func NewSyscallError

func NewSyscallError(syscall string, err error) error

NewSyscallError возвращает в качестве ошибки новый SyscallError с указанным именем системного вызова и подробностями ошибки. Для удобства, если err равен nil, NewSyscallError возвращает nil.

func Pipe

func Pipe() (r *File, w *File, err error)

Pipe возвращает соединенную пару файлов; считывает из r байты, записанные в w. Он возвращает файлы и ошибку, если таковая имеется.

func ReadFile

func ReadFile(name string) ([]byte, error)

ReadFile считывает файл с указанным именем и возвращает его содержимое. Успешный вызов возвращает err == nil, а не err == EOF. Поскольку ReadFile считывает весь файл, он не рассматривает EOF из Read как ошибку, о которой следует сообщать.

Пример
package main

import (
	"log"
	"os"
)

func main() {
	data, err := os.ReadFile("testdata/hello")
	if err != nil {
		log.Fatal(err)
	}
	os.Stdout.Write(data)

}
Output:

Hello, Gophers!
func Readlink(name string) (string, error)

Readlink возвращает место назначения символической ссылки с указанным именем. Если произошла ошибка, она будет типа *PathError.

Если место назначения ссылки является относительным, Readlink возвращает относительный путь, не преобразуя его в абсолютный.

Пример
package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
)

func main() {
	// First, we create a relative symlink to a file.
	d, err := os.MkdirTemp("", "")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(d)
	targetPath := filepath.Join(d, "hello.txt")
	if err := os.WriteFile(targetPath, []byte("Hello, Gophers!"), 0644); err != nil {
		log.Fatal(err)
	}
	linkPath := filepath.Join(d, "hello.link")
	if err := os.Symlink("hello.txt", filepath.Join(d, "hello.link")); err != nil {
		if errors.Is(err, errors.ErrUnsupported) {
			// Allow the example to run on platforms that do not support symbolic links.
			fmt.Printf("%s links to %s\n", filepath.Base(linkPath), "hello.txt")
			return
		}
		log.Fatal(err)
	}

	// Readlink returns the relative path as passed to os.Symlink.
	dst, err := os.Readlink(linkPath)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s links to %s\n", filepath.Base(linkPath), dst)

	var dstAbs string
	if filepath.IsAbs(dst) {
		dstAbs = dst
	} else {
		// Symlink targets are relative to the directory containing the link.
		dstAbs = filepath.Join(filepath.Dir(linkPath), dst)
	}

	// Check that the target is correct by comparing it with os.Stat
	// on the original target path.
	dstInfo, err := os.Stat(dstAbs)
	if err != nil {
		log.Fatal(err)
	}
	targetInfo, err := os.Stat(targetPath)
	if err != nil {
		log.Fatal(err)
	}
	if !os.SameFile(dstInfo, targetInfo) {
		log.Fatalf("link destination (%s) is not the same file as %s", dstAbs, targetPath)
	}

}
Output:

hello.link links to hello.txt

func Remove

func Remove(name string) error

Remove удаляет указанный файл или (пустой) каталог. Если происходит ошибка, она будет типа *PathError.

func RemoveAll

func RemoveAll(path string) error

RemoveAll удаляет путь и все вложенные в него элементы. Он удаляет все, что может, но возвращает первую встреченную ошибку. Если путь не существует, RemoveAll возвращает nil (без ошибки). Если произошла ошибка, она будет типа *PathError.

func Rename

func Rename(oldpath, newpath string) error

Rename переименовывает (перемещает) oldpath в newpath. Если newpath уже существует и не является каталогом, Rename заменяет его. Если newpath уже существует и является каталогом, Rename возвращает ошибку. Ограничения, специфичные для ОС, могут применяться, когда oldpath и newpath находятся в разных каталогах. Даже в пределах одного каталога на платформах, отличных от Unix, Rename не является атомарной операцией. Если произошла ошибка, она будет типа *LinkError.

func SameFile

func SameFile(fi1, fi2 FileInfo) bool

SameFile сообщает, описывают ли fi1 и fi2 один и тот же файл. Например, в Unix это означает, что поля device и inode двух базовых структур идентичны; в других системах решение может основываться на именах путей. SameFile применяется только к результатам, возвращаемым Stat этого пакета. В других случаях она возвращает false.

func Setenv

func Setenv(key, value string) error

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

func Symlink(oldname, newname string) error

Symlink создает newname как символическую ссылку на oldname. В Windows символическая ссылка на несуществующий oldname создает файловую символическую ссылку; если oldname позже будет создан как каталог, символическая ссылка не будет работать. Если произойдет ошибка, она будет типа *LinkError.

func TempDir

func TempDir() string

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

В системах Unix она возвращает $TMPDIR, если он не пуст, в противном случае — /tmp. В Windows она использует GetTempPath, возвращая первое непустое значение из %TMP%, %TEMP%, %USERPROFILE% или каталога Windows. В Plan 9 она возвращает /tmp.

Существование каталога и наличие прав доступа к нему не гарантируются.

func Truncate

func Truncate(name string, size int64) error

Truncate изменяет размер указанного файла. Если файл является символической ссылкой, изменяется размер цели ссылки. Если происходит ошибка, она будет типа *PathError.

func Unsetenv

func Unsetenv(key string) error

Unsetenv снимает установку одной переменной среды.

Пример
package main

import (
	"os"
)

func main() {
	os.Setenv("TMPDIR", "/my/tmp")
	defer os.Unsetenv("TMPDIR")
}

func UserCacheDir

func UserCacheDir() (string, error)

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

В системах Unix она возвращает $XDG_CACHE_HOME, как указано в https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html, если он не пустой, в противном случае — $HOME/.cache. В Darwin она возвращает $HOME/Library/Caches. В Windows она возвращает %LocalAppData%. В Plan 9 она возвращает $home/lib/cache.

Если местоположение не может быть определено (например, $HOME не определено) или путь в $XDG_CACHE_HOME является относительным, то будет возвращена ошибка.

Пример
import (
	"log"
	"os"
	"path/filepath"
	"sync"
)

func main() {
	dir, dirErr := os.UserCacheDir()
	if dirErr == nil {
		dir = filepath.Join(dir, "ExampleUserCacheDir")
	}

	getCache := func(name string) ([]byte, error) {
		if dirErr != nil {
			return nil, &os.PathError{Op: "getCache", Path: name, Err: os.ErrNotExist}
		}
		return os.ReadFile(filepath.Join(dir, name))
	}

	var mkdirOnce sync.Once
	putCache := func(name string, b []byte) error {
		if dirErr != nil {
			return &os.PathError{Op: "putCache", Path: name, Err: dirErr}
		}
		mkdirOnce.Do(func() {
			if err := os.MkdirAll(dir, 0700); err != nil {
				log.Printf("can't create user cache dir: %v", err)
			}
		})
		return os.WriteFile(filepath.Join(dir, name), b, 0600)
	}

	// Read and store cached data.
	// …
	_ = getCache
	_ = putCache

}

func UserConfigDir

func UserConfigDir() (string, error)

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

В системах Unix она возвращает $XDG_CONFIG_HOME, как указано в https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html, если он не пустой, в противном случае — $HOME/.config. В Darwin она возвращает $HOME/Library/Application Support. В Windows она возвращает %AppData%. В Plan 9 она возвращает $home/lib.

Если местоположение не может быть определено (например, $HOME не определено) или путь в $XDG_CONFIG_HOME является относительным, то будет возвращена ошибка.

Пример
package main

import (
	"bytes"
	"log"
	"os"
	"path/filepath"
)

func main() {
	dir, dirErr := os.UserConfigDir()

	var (
		configPath string
		origConfig []byte
	)
	if dirErr == nil {
		configPath = filepath.Join(dir, "ExampleUserConfigDir", "example.conf")
		var err error
		origConfig, err = os.ReadFile(configPath)
		if err != nil && !os.IsNotExist(err) {
			// The user has a config file but we couldn't read it.
			// Report the error instead of ignoring their configuration.
			log.Fatal(err)
		}
	}

	// Use and perhaps make changes to the config.
	config := bytes.Clone(origConfig)
	// …

	// Save changes.
	if !bytes.Equal(config, origConfig) {
		if configPath == "" {
			log.Printf("not saving config changes: %v", dirErr)
		} else {
			err := os.MkdirAll(filepath.Dir(configPath), 0700)
			if err == nil {
				err = os.WriteFile(configPath, config, 0600)
			}
			if err != nil {
				log.Printf("error saving config changes: %v", err)
			}
		}
	}

}

func UserHomeDir

func UserHomeDir() (string, error)

UserHomeDir возвращает домашний каталог текущего пользователя.

В Unix, включая macOS, возвращает переменную среды $HOME. В Windows возвращает %USERPROFILE%. В Plan 9 возвращает переменную среды $home.

Если ожидаемая переменная не задана в среде, UserHomeDir возвращает либо значение по умолчанию, специфичное для платформы, либо ошибку, отличную от nil.

func WriteFile

func WriteFile(name string, data []byte, perm FileMode) error

WriteFile записывает данные в файл с указанным именем, создавая его при необходимости. Если файл не существует, WriteFile создает его с правами доступа perm (до umask); в противном случае WriteFile обрезает его перед записью, не изменяя права доступа. Поскольку WriteFile требует нескольких системных вызовов для завершения, сбой в середине операции может оставить файл в частично записанном состоянии.

Пример
package main

import (
	"log"
	"os"
)

func main() {
	err := os.WriteFile("testdata/hello", []byte("Hello, Gophers!"), 0666)
	if err != nil {
		log.Fatal(err)
	}
}

11.2 - Описание типов пакета os языка программирования Go

Основные типы пакета os языка программирования GO

type DirEntry

type DirEntry = fs.DirEntry

DirEntry — это запись, прочитанная из каталога (с помощью функции ReadDir или метода File.ReadDir).

func ReadDir

func ReadDir(name string) ([]DirEntry, error)

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

Пример
package main

import (
	"fmt"
	"log"
	"os"
)

func main() {
	files, err := os.ReadDir(".")
	if err != nil {
		log.Fatal(err)
	}

	for _, file := range files {
		fmt.Println(file.Name())
	}
}

type File

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

File представляет открытый файловый дескриптор.

Методы File безопасны для одновременного использования.

func Create

func Create(name string) (*File, error)

Create создает или обрезает файл с указанным именем. Если файл уже существует, он обрезается. Если файл не существует, он создается с режимом 0o666 (до umask). В случае успеха методы возвращенного File могут использоваться для ввода-вывода; связанный файловый дескриптор имеет режим O_RDWR. Каталог, содержащий файл, должен уже существовать. Если произошла ошибка, она будет типа *PathError.

func CreateTemp

func CreateTemp(dir, pattern string) (*File, error)

CreateTemp создает новый временный файл в каталоге dir, открывает файл для чтения и записи и возвращает полученный файл. Имя файла генерируется путем добавления случайной строки в конец pattern. Если pattern содержит "*", случайная строка заменяет последний "*". Файл создается с режимом 0o600 (до umask). Если dir — пустая строка, CreateTemp использует каталог по умолчанию для временных файлов, возвращаемый TempDir. Несколько программ или goroutines, вызывающих CreateTemp одновременно, не будут выбирать один и тот же файл. Вызывающий может использовать метод Name файла, чтобы найти путь к файлу. Вызывающий несет ответственность за удаление файла, когда он больше не нужен.

Пример
package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.CreateTemp("", "example")
	if err != nil {
		log.Fatal(err)
	}
	defer os.Remove(f.Name()) // clean up

	if _, err := f.Write([]byte("content")); err != nil {
		log.Fatal(err)
	}
	if err := f.Close(); err != nil {
		log.Fatal(err)
	}
}
Пример suffix
package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.CreateTemp("", "example.*.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer os.Remove(f.Name()) // clean up

	if _, err := f.Write([]byte("content")); err != nil {
		f.Close()
		log.Fatal(err)
	}
	if err := f.Close(); err != nil {
		log.Fatal(err)
	}
}

func NewFile

func NewFile(fd uintptr, name string) *File

NewFile возвращает новый File с заданным файловым дескриптором и именем. Возвращаемое значение будет nil, если fd не является действительным файловым дескриптором. В системах Unix, если файловый дескриптор находится в неблокирующем режиме, NewFile попытается вернуть опрашиваемый File (такой, для которого работают методы SetDeadline).

После передачи в NewFile fd может стать недействительным при тех же условиях, что описаны в комментариях к методу Fd, и применяются те же ограничения.

func Open

func Open(name string) (*File, error)

Open открывает указанный файл для чтения. В случае успеха методы возвращенного файла могут быть использованы для чтения; связанный файловый дескриптор имеет режим O_RDONLY. Если произошла ошибка, она будет типа *PathError.

func OpenFile

func OpenFile(name string, flag int, perm FileMode) (*File, error)

OpenFile — это обобщенный вызов open; большинство пользователей вместо него будут использовать Open или Create. Он открывает файл с указанным флагом (O_RDONLY и т. д.). Если файл не существует и передан флаг O_CREATE, он создается с режимом perm (до umask); содержащий его каталог должен существовать. В случае успеха методы возвращенного File можно использовать для ввода-вывода. Если произошла ошибка, она будет типа *PathError.

Пример
package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.OpenFile("notes.txt", os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		log.Fatal(err)
	}
	if err := f.Close(); err != nil {
		log.Fatal(err)
	}
}
Пример добавление
package main

import (
	"log"
	"os"
)

func main() {
	// If the file doesn't exist, create it, or append to the file
	f, err := os.OpenFile("access.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := f.Write([]byte("appended some data\n")); err != nil {
		f.Close() // ignore error; Write error takes precedence
		log.Fatal(err)
	}
	if err := f.Close(); err != nil {
		log.Fatal(err)
	}
}

func OpenInRoot

func OpenInRoot(dir, name string) (*File, error)

OpenInRoot открывает файл name в каталоге dir. Это эквивалентно OpenRoot(dir) с последующим открытием файла в корневом каталоге.

OpenInRoot возвращает ошибку, если какой-либо компонент имени name ссылается на местоположение за пределами dir.

См. Root для получения подробной информации и ограничений.

func (*File) Chdir

func (f *File) Chdir() error

Chdir изменяет текущий рабочий каталог на файл, который должен быть каталогом. Если произошла ошибка, она будет типа *PathError.

func (*File) Chmod

func (f *File) Chmod(mode FileMode) error

Chmod изменяет режим файла на mode. Если произошла ошибка, она будет типа *PathError.

func (*File) Chown

func (f *File) Chown(uid, gid int) error

Chown изменяет числовые значения uid и gid указанного файла. Если произошла ошибка, она будет типа *PathError.

В Windows всегда возвращается ошибка syscall.EWINDOWS, обернутая в *PathError.

func (*File) Close

func (f *File) Close() error

Close закрывает файл, делая его недоступным для ввода-вывода. Для файлов, поддерживающих File.SetDeadline, все ожидающие операции ввода-вывода будут отменены и немедленно возвращены с ошибкой ErrClosed. Close вернет ошибку, если он уже был вызван.

func (*File) Fd

func (f *File) Fd() uintptr

Fd возвращает целое число Unix-дескриптора файла, ссылающегося на открытый файл. Если f закрыт, дескриптор файла становится недействительным. Если f подвергается сборке мусора, финализатор может закрыть дескриптор файла, сделав его недействительным; см. runtime.SetFinalizer для получения дополнительной информации о том, когда может быть запущен финализатор. В системах Unix это приведет к прекращению работы методов File.SetDeadline. Поскольку файловые дескрипторы могут быть повторно использованы, возвращенный файловый дескриптор может быть закрыт только с помощью метода File.Close f или его финализатором во время сборки мусора. В противном случае во время сборки мусора финализатор может закрыть не связанный файловый дескриптор с тем же (повторно используемым) номером.

В качестве альтернативы см. метод f.SyscallConn.

func (*File) Name

func (f *File) Name() string

Name возвращает имя файла, представленное в Open.

Безопасно вызывать Name после [Close].

func (*File) Read

func (f *File) Read(b []byte) (n int, err error)

Read считывает до len(b) байтов из File и сохраняет их в b. Он возвращает количество прочитанных байтов и любую возникшую ошибку. В конце файла Read возвращает 0, io.EOF.

func (*File) ReadAt

func (f *File) ReadAt(b []byte, off int64) (n int, err error)

ReadAt считывает len(b) байт из файла, начиная с байтового смещения off. Она возвращает количество прочитанных байтов и ошибку, если таковая имеется. ReadAt всегда возвращает ошибку, отличную от nil, когда n < len(b). В конце файла эта ошибка равна io.EOF.

func (*File) ReadDir

func (f *File) ReadDir(n int) ([]DirEntry, error)

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

Если n > 0, ReadDir возвращает не более n записей DirEntry. В этом случае, если ReadDir возвращает пустой срез, он вернет ошибку с объяснением причины. В конце каталога ошибкой является io.EOF.

Если n <= 0, ReadDir возвращает все записи DirEntry, оставшиеся в каталоге. При успешном выполнении он возвращает ошибку nil (не io.EOF).

func (*File) ReadFrom

func (f *File) ReadFrom(r io.Reader) (n int64, err error)

ReadFrom реализует io.ReaderFrom.

func (*File) Readdir

func (f *File) Readdir(n int) ([]FileInfo, error)

Readdir считывает содержимое каталога, связанного с файлом, и возвращает массив из n значений FileInfo, как это было бы возвращено Lstat, в порядке каталога. Последующие вызовы для того же файла будут возвращать дополнительные FileInfos.

Если n > 0, Readdir возвращает не более n структур FileInfo. В этом случае, если Readdir возвращает пустой срез, он вернет не нулевую ошибку с объяснением причины. В конце каталога ошибкой является io.EOF.

Если n <= 0, Readdir возвращает все FileInfo из каталога в одном срезе. В этом случае, если Readdir выполняется успешно (считывает до конца каталога), он возвращает срез и ошибку nil. Если он встречает ошибку до конца каталога, Readdir возвращает FileInfo, прочитанные до этого момента, и ошибку, отличную от nil.

Большинству клиентов лучше подходит более эффективный метод ReadDir.

func (*File) Readdirnames

func (f *File) Readdirnames(n int) (names []string, err error)

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

Если n > 0, Readdirnames возвращает не более n имен. В этом случае, если Readdirnames возвращает пустой срез, он возвращает не нулевую ошибку с объяснением причины. В конце каталога ошибкой является io.EOF.

Если n <= 0, Readdirnames возвращает все имена из каталога в одном фрагменте. В этом случае, если Readdirnames выполняется успешно (считывает до конца каталога), он возвращает фрагмент и ошибку nil. Если перед концом каталога возникает ошибка, Readdirnames возвращает имена, прочитанные до этого момента, и ошибку, отличную от nil.

func (*File) Seek

func (f *File) Seek(offset int64, whence int) (ret int64, err error)

Seek устанавливает смещение для следующего чтения или записи в файл, интерпретируемое в соответствии с whence: 0 означает относительно начала файла, 1 означает относительно текущего смещения, а 2 означает относительно конца. Возвращает новое смещение и ошибку, если она есть. Поведение Seek для файла, открытого с O_APPEND, не определено.

func (*File) SetDeadline

func (f *File) SetDeadline(t time.Time) error

SetDeadline устанавливает сроки чтения и записи для файла. Это эквивалентно вызову SetReadDeadline и SetWriteDeadline.

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

Срок — это абсолютное время, по истечении которого операции ввода-вывода завершаются с ошибкой, а не блокируются. Срок применяется ко всем будущим и ожидающим операциям ввода-вывода, а не только к следующему вызову Read или Write. После превышения срока соединение можно обновить, установив срок в будущем.

Если срок превышен, вызов Read или Write или других методов ввода-вывода вернет ошибку, которая оборачивает ErrDeadlineExceeded. Это можно проверить с помощью errors.Is(err, os.ErrDeadlineExceeded). Эта ошибка реализует метод Timeout, и вызов метода Timeout вернет true, но есть и другие возможные ошибки, для которых Timeout вернет true, даже если срок не был превышен.

Таймаут простоя можно реализовать путем многократного продления срока после успешных вызовов Read или Write.

Нулевое значение t означает, что операции ввода-вывода не будут прерываться по истечении таймаута.

func (*File) SetReadDeadline

func (f *File) SetReadDeadline(t time.Time) error

SetReadDeadline устанавливает срок для будущих вызовов Read и любых заблокированных в данный момент вызовов Read. Нулевое значение t означает, что Read не будет прерываться по истечении времени ожидания. Не все файлы поддерживают установку сроков; см. SetDeadline.

func (*File) SetWriteDeadline

func (f *File) SetWriteDeadline(t time.Time) error

SetWriteDeadline устанавливает срок для любых будущих вызовов Write и любых заблокированных в данный момент вызовов Write. Даже если Write превышает время ожидания, он может вернуть n > 0, указывая, что часть данных была успешно записана. Нулевое значение t означает, что Write не превысит время ожидания. Не все файлы поддерживают установку сроков; см. SetDeadline.

func (*File) Stat

func (f *File) Stat() (FileInfo, error)

Stat возвращает структуру FileInfo, описывающую файл. Если произошла ошибка, она будет иметь тип *PathError.

func (*File) Sync

func (f *File) Sync() error

Sync фиксирует текущее содержимое файла в стабильном хранилище. Обычно это означает сброс в память файловой системы копии недавно записанных данных на диск.

func (*File) SyscallConn

func (f *File) SyscallConn() (syscall.RawConn, error)

SyscallConn возвращает необработанный файл. Это реализует интерфейс syscall.Conn.

func (*File) Truncate

func (f *File) Truncate(size int64) error

Truncate изменяет размер файла. Он не изменяет смещение ввода-вывода. Если происходит ошибка, она будет типа *PathError.

func (*File) Write

func (f *File) Write(b []byte) (n int, err error)

Write записывает len(b) байт из b в File. Он возвращает количество записанных байт и ошибку, если она есть. Write возвращает ошибку, отличную от nil, когда n != len(b).

func (*File) WriteAt

func (f *File) WriteAt(b []byte, off int64) (n int, err error)

WriteAt записывает len(b) байт в файл, начиная с байтового смещения off. Он возвращает количество записанных байтов и ошибку, если она есть. WriteAt возвращает ошибку, отличную от nil, когда n != len(b).

Если файл был открыт с флагом O_APPEND, WriteAt возвращает ошибку.

func (*File) WriteString

func (f *File) WriteString(s string) (n int, err error)

WriteString похож на Write, но записывает содержимое строки s, а не фрагмент байтов.

func (*File) WriteTo

func (f *File) WriteTo(w io.Writer) (n int64, err error)

WriteTo реализует io.WriterTo.

type FileInfo

type FileInfo = fs.FileInfo

FileInfo описывает файл и возвращается Stat и Lstat.

func Lstat

func Lstat(name string) (FileInfo, error)

Lstat возвращает FileInfo, описывающий файл с указанным именем. Если файл является символической ссылкой, возвращаемый FileInfo описывает символическую ссылку. Lstat не пытается следовать по ссылке. Если происходит ошибка, она будет типа *PathError.

В Windows, если файл является точкой повторного анализа, которая является заменителем другого именованного объекта (например, символической ссылки или смонтированной папки), возвращаемый FileInfo описывает точку повторного анализа и не пытается ее разрешить.

func Stat

func Stat(name string) (FileInfo, error)

Stat возвращает FileInfo, описывающий именованный файл. Если произошла ошибка, она будет типа *PathError.

type FileMode

type FileMode = fs.FileMode

FileMode представляет режим файла и биты разрешений. Биты имеют одинаковое определение во всех системах, поэтому информация о файлах может быть перенесена из одной системы в другую. Не все биты применимы ко всем системам. Единственный обязательный бит — ModeDir для каталогов.

Пример ¶

type LinkError

type LinkError struct {
    Op  string
    Old string
    New string
    Err error
}

LinkError записывает ошибку, возникшую во время системного вызова link, symlink или rename, а также пути, которые ее вызвали.

func (*LinkError) Error

func (e *LinkError) Error() string

func (*LinkError) Unwrap

func (e *LinkError) Unwrap() error

type PathError

type PathError = fs.PathError

PathError регистрирует ошибку, а также операцию и путь к файлу, которые ее вызвали.

type ProcAttr

type ProcAttr struct {
    // Если Dir не пустой, дочерний процесс переходит в этот каталог перед
    // созданием процесса.
    Dir string
	// Если Env не равно nil, оно возвращает переменные окружения для
    // нового процесса в форме, возвращаемой Environ.
    // Если оно равно nil, будет использован результат Environ.
    Env []string
    // Files указывает открытые файлы, унаследованные новым процессом.
    // Первые три записи соответствуют стандартному вводу, стандартному выводу и
    // стандартной ошибке. Реализация может поддерживать дополнительные записи,
	// в зависимости от базовой операционной системы. Запись nil соответствует
    // закрытию этого файла при запуске процесса.
    // В системах Unix StartProcess изменит эти значения File
    // в режим блокировки, что означает, что SetDeadline перестанет работать,
    // а вызов Close не прервет Read или Write.
    Files []*File

    // Атрибуты создания процесса, специфичные для операционной системы.
	// Обратите внимание, что установка этого поля означает, что ваша программа
    // может не работать должным образом или даже не компилироваться в некоторых
    // операционных системах.
    Sys *syscall.SysProcAttr
}

ProcAttr содержит атрибуты, которые будут применены к новому процессу, запущенному StartProcess.

type Process

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

Process хранит информацию о процессе, созданном StartProcess.

func FindProcess

func FindProcess(pid int) (*Process, error)

FindProcess ищет запущенный процесс по его pid.

Возвращаемый им Process можно использовать для получения информации о процессе базовой операционной системы.

В системах Unix FindProcess всегда выполняется успешно и возвращает Process для заданного pid, независимо от того, существует ли процесс. Чтобы проверить, действительно ли процесс существует, посмотрите, сообщает ли p.Signal(syscall.Signal(0)) об ошибке.

func StartProcess

func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error)

StartProcess запускает новый процесс с программой, аргументами и атрибутами, указанными в name, argv и attr. Срез argv станет os.Args в новом процессе, поэтому он обычно начинается с имени программы.

Если вызывающая goroutine заблокировала поток операционной системы с помощью runtime.LockOSThread и изменила любое наследуемое состояние потока на уровне ОС (например, пространства имен Linux или Plan 9), новый процесс унаследует состояние потока вызывающего.

StartProcess является низкоуровневым интерфейсом. Пакет os/exec предоставляет интерфейсы более высокого уровня.

Если произошла ошибка, она будет иметь тип *PathError.

func (*Process) Kill

func (p *Process) Kill() error

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

func (*Process) Release

func (p *Process) Release() error

Release освобождает все ресурсы, связанные с процессом p, делая его непригодным для использования в будущем. Release нужно вызывать только в том случае, если Process.Wait не вызывается.

func (*Process) Signal

func (p *Process) Signal(sig Signal) error

Signal посылает сигнал процессу. Отправка Interrupt в Windows не реализована.

func (*Process) Wait

func (p *Process) Wait() (*ProcessState, error)

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

type ProcessState

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

ProcessState хранит информацию о процессе, как сообщается Wait.

func (*ProcessState) ExitCode

func (p *ProcessState) ExitCode() int

ExitCode возвращает код завершения завершившегося процесса или -1, если процесс не завершился или был прерван сигналом.

func (*ProcessState) Exited

func (p *ProcessState) Exited() bool

Exited сообщает, завершилась ли программа. В системах Unix это возвращает true, если программа завершилась из-за вызова exit, но false, если программа была прервана сигналом.

func (*ProcessState) Pid

func (p *ProcessState) Pid() int

Pid возвращает идентификатор завершившегося процесса.

func (*ProcessState) String

func (p *ProcessState) String() string

func (*ProcessState) Success

func (p *ProcessState) Success() bool

Success сообщает, завершилась ли программа успешно, например, со статусом завершения 0 в Unix.

func (*ProcessState) Sys

func (p *ProcessState) Sys() any

Sys возвращает системную информацию о завершении процесса. Преобразуйте ее в соответствующий базовый тип, например syscall.WaitStatus в Unix, чтобы получить доступ к ее содержимому.

func (*ProcessState) SysUsage

func (p *ProcessState) SysUsage() any

SysUsage возвращает системную информацию об использовании ресурсов завершенного процесса. Преобразуйте ее в соответствующий базовый тип, например *syscall.Rusage в Unix, чтобы получить доступ к ее содержимому. (В Unix *syscall.Rusage соответствует struct rusage, как определено в справочной странице getrusage(2).)

func (*ProcessState) SystemTime

func (p *ProcessState) SystemTime() time.Duration

SystemTime возвращает системное время ЦП завершенного процесса и его дочерних процессов.

func (*ProcessState) UserTime

func (p *ProcessState) UserTime() time.Duration

UserTime возвращает пользовательское время ЦП завершенного процесса и его дочерних процессов.

type Root


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

Root может использоваться только для доступа к файлам в пределах одного дерева каталогов.

Методы Root могут обращаться только к файлам и каталогам, расположенным ниже корневого каталога. Если какой-либо компонент имени файла, переданного методу Root, ссылается на местоположение за пределами корня, метод возвращает ошибку. Имена файлов могут ссылаться на сам каталог (.).

Методы Root будут следовать символьным ссылкам, но символьные ссылки не могут ссылаться на местоположение за пределами корня. Символьные ссылки не должны быть абсолютными.

Методы Root не запрещают переход через границы файловой системы, монтирование Linux, специальные файлы /proc или доступ к файлам устройств Unix.

Методы Root можно безопасно использовать одновременно из нескольких goroutines.

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

Поведение Root отличается на некоторых платформах:

  • Когда GOOS=windows, имена файлов не могут ссылаться на зарезервированные Windows имена устройств, такие как NUL и COM1.
  • Когда GOOS=js, Root уязвим для атак TOCTOU (time-of-check-time-of-use) при проверке символьных ссылок и не может гарантировать, что операции не выйдут за пределы корня.
  • Когда GOOS=plan9 или GOOS=js, Root не отслеживает каталоги при переименовании. На этих платформах Root ссылается на имя каталога, а не на файловый дескриптор.

func OpenRoot

func OpenRoot(name string) (*Root, error)

OpenRoot открывает указанный каталог. Если произошла ошибка, она будет типа *PathError.

func (*Root) Close

func (r *Root) Close() error

Close закрывает Root. После вызова Close методы Root возвращают ошибки.

func (*Root) Create

func (r *Root) Create(name string) (*File, error)

Create создает или обрезает указанный файл в корне. Подробнее см. Create.

func (*Root) FS

func (r *Root) FS() fs.FS

FS возвращает файловую систему (fs.FS) для дерева файлов в корне.

Результат реализует io/fs.StatFS, io/fs.ReadFileFS и io/fs.ReadDirFS.

func (*Root) Lstat

func (r *Root) Lstat(name string) (FileInfo, error)

Lstat возвращает FileInfo, описывающий указанный файл в корневом каталоге. Если файл является символической ссылкой, возвращаемый FileInfo описывает символическую ссылку. Подробнее см. Lstat.

func (*Root) Mkdir

func (r *Root) Mkdir(name string, perm FileMode) error

Mkdir создает новый каталог в корне с указанным именем и битами разрешений (до umask). Подробнее см. Mkdir.

Если perm содержит биты, отличные от девяти младших битов (0o777), OpenFile возвращает ошибку.

func (*Root) Name

func (r *Root) Name() string

Name возвращает имя каталога, представленного OpenRoot.

Безопасно вызывать Name после [Close].

func (*Root) Open

func (r *Root) Open(name string) (*File, error)

Open открывает указанный файл в корне для чтения. Подробнее см. Open.

func (*Root) OpenFile

func (r *Root) OpenFile(name string, flag int, perm FileMode) (*File, error)

OpenFile открывает указанный файл в корне. Подробнее см. OpenFile.

Если perm содержит биты, отличные от девяти младших битов (0o777), OpenFile возвращает ошибку.

func (*Root) OpenRoot

func (r *Root) OpenRoot(name string) (*Root, error)

OpenRoot открывает указанный каталог в корневом каталоге. Если произошла ошибка, она будет типа *PathError.

func (*Root) Remove

func (r *Root) Remove(name string) error

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

func (*Root) Stat

func (r *Root) Stat(name string) (FileInfo, error)

Stat возвращает FileInfo, описывающий файл с указанным именем в корне. Подробнее см. Stat.

type Signal

type Signal interface {
    String() string
    Signal() // для отличия от других Stringers
}

Signal представляет сигнал операционной системы. Обычная базовая реализация зависит от операционной системы: в Unix это syscall.Signal.

var (
    Interrupt Signal = syscall.SIGINT
	Kill      Signal = syscall.SIGKILL)

Единственные значения сигналов, которые гарантированно присутствуют в пакете os на всех системах, — это os.Interrupt (отправить процессу прерывание) и os.Kill (принудительно завершить процесс). В Windows отправка os.Interrupt процессу с помощью os.Process.Signal не реализована; вместо отправки сигнала будет возвращена ошибка.

type SyscallError

type SyscallError struct {
    Syscall string
    Err     error
}

SyscallError регистрирует ошибку от конкретного системного вызова.

func (*SyscallError) Error

func (e *SyscallError) Error() string

func (*SyscallError) Timeout

func (e *SyscallError) Timeout() bool

Timeout сообщает, является ли эта ошибка тайм-аутом.

func (*SyscallError) Unwrap

func (e *SyscallError) Unwrap() error

11.3 - Описание функций и типов пакета os/exec языка программирования Go

Пакет exec запускает внешние команды. Он оборачивает os.StartProcess, чтобы упростить перенаправление stdin и stdout, подключение ввода-вывода с помощью труб и другие настройки.

В отличие от вызова библиотеки «system» из C и других языков, пакет os/exec намеренно не вызывает системную оболочку и не расширяет никакие шаблоны glob, а также не обрабатывает другие расширения, конвейеры или перенаправления, которые обычно выполняются оболочками. Пакет ведет себя больше как семейство функций «exec» в C. Чтобы расширить шаблоны glob, либо вызовите оболочку напрямую, позаботившись об экранировании опасных входных данных, либо используйте функцию Glob из пакета path/filepath. Для расширения переменных окружения используйте ExpandEnv из пакета os.

Обратите внимание, что примеры в этом пакете предполагают использование системы Unix. Они могут не работать в Windows и не работают в Go Playground, используемом golang.org и godoc.org.

Исполняемые файлы в текущем каталоге

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

Чтобы избежать этих проблем безопасности, начиная с Go 1.19, этот пакет не будет разрешать программу, используя явную или неявную запись пути относительно текущего каталога. То есть, если вы запустите LookPath(“go”), он не вернет ./go в Unix и .\go.exe в Windows, независимо от того, как настроен путь. Вместо этого, если обычные алгоритмы пути приведут к такому ответу, эти функции возвращают ошибку err, удовлетворяющую errors.Is(err, ErrDot).

Рассмотрим, например, эти два фрагмента программы:

path, err := exec.LookPath("prog")
if err != nil {
	log.Fatal(err)
}
use(path)
cmd := exec.Command("prog")
if err := cmd.Run(); err != nil {
	log.Fatal(err)
}

Они не найдут и не запустят ./prog или .\prog.exe, независимо от того, как настроен текущий путь.

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

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

path, err := exec.LookPath("prog")
if errors.Is(err, exec.ErrDot) {
	err = nil
}
if err != nil {
	log.Fatal(err)
}
use(path)
cmd := exec.Command("prog")
if errors.Is(cmd.Err, exec.ErrDot) {
	cmd.Err = nil
}
if err := cmd.Run(); err != nil {
	log.Fatal(err)
}

Установка переменной окружения GODEBUG=execerrdot=0 полностью отключает генерацию ErrDot, временно восстанавливая поведение, существовавшее до Go 1.19, для программ, которые не могут применить более целевые исправления. В будущих версиях Go поддержка этой переменной может быть удалена.

Прежде чем добавлять такие переопределения, убедитесь, что вы понимаете последствия этого для безопасности. Дополнительные сведения см. на сайте https://go.dev/blog/path-security.

Переменные

var ErrDot = errors.New("cannot run executable found relative to current directory")

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

Обратите внимание, что функции этого пакета не возвращают ErrDot напрямую. Для проверки того, связана ли возвращаемая ошибка err с этим условием, в коде следует использовать errors.Is(err, ErrDot), а не err == ErrDot.

var ErrNotFound = errors.New("исполняемый файл не найден в $PATH")

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

var ErrWaitDelay = errors.New("exec: WaitDelay expired before I/O complete")

ErrWaitDelay возвращается Cmd.Wait, если процесс завершается с успешным кодом состояния, но его выходные трубы не закрыты до истечения WaitDelay команды.

Функции

func LookPath

func LookPath(file string) (string, error)

LookPath ищет исполняемый файл с именем file в каталогах, указанных переменной окружения PATH. Если файл содержит косую черту, то поиск ведется напрямую и PATH не используется. В противном случае, при успехе, результатом будет абсолютный путь.

В старых версиях Go LookPath мог возвращать путь относительно текущего каталога. Начиная с Go 1.19, LookPath будет возвращать этот путь вместе с ошибкой, удовлетворяющей errors.Is(err, ErrDot). Более подробную информацию см. в документации к пакету.

Пример
package main

import (
	"fmt"
	"log"
	"os/exec"
)

func main() {
	path, err := exec.LookPath("fortune")
	if err != nil {
		log.Fatal("installing fortune is in your future")
	}
	fmt.Printf("fortune is available at %s\n", path)
}

Типы

type Cmd

type Cmd struct {
    // Path — путь к выполняемой команде.
    //
    // Это единственное поле, которое должно иметь значение, отличное от нуля.
    // Если Path является относительным, оно оценивается относительно
    // Dir.
    Path string

    // Args содержит аргументы командной строки, включая команду как Args[0].
	// Если поле Args пустое или равно nil, Run использует {Path}.
    //
    // В типичном случае Path и Args задаются вызовом Command.
    Args []string

    // Env задает среду процесса.
    // Каждая запись имеет вид «ключ=значение».
    // Если Env равно nil, новый процесс использует среду текущего процесса.
    //
	// Если Env содержит дубликаты ключей среды, используется только последнее
    // значение в срезе для каждого дубликата ключа.
    // В качестве особого случая в Windows SYSTEMROOT всегда добавляется, если
    // отсутствует и явно не установлен в пустую строку.
    //
    // См. также поле Dir, которое может устанавливать PWD в среде.
    Env []string

    // Dir указывает рабочий каталог команды.
	// Если Dir является пустой строкой, Run запускает команду в
    // текущем каталоге вызывающего процесса.
    //
    // В системах Unix значение Dir также определяет
    // переменную окружения PWD дочернего процесса, если не указано иное
    //. Процесс Unix представляет свой рабочий каталог
    // не по имени, а как неявную ссылку на узел в
	// дереве файлов. Таким образом, если дочерний процесс получает свой рабочий
    // каталог путем вызова функции, такой как getcwd в C, которая
    // вычисляет каноническое имя, проходя по дереву файлов, он
    // не восстановит исходное значение Dir, если это значение
    // было псевдонимом, включающим символьные ссылки. Однако, если
    // дочерний процесс вызывает [os.Getwd] в Go или
	// get_current_dir_name из GNU C, и значение PWD является псевдонимом для
    // текущего каталога, эти функции вернут
    // значение PWD, которое совпадает со значением Dir.
    Dir string

    // Stdin указывает стандартный ввод процесса.
    //
    // Если Stdin равно nil, процесс читает из нулевого устройства (os.DevNull).
    //
	// Если Stdin является *os.File, стандартный ввод процесса подключается
    // непосредственно к этому файлу.
    //
    // В противном случае во время выполнения команды отдельная
    // goroutine считывает данные из Stdin и передает их команде
    // через канал. В этом случае Wait не завершается, пока goroutine
    // не прекратит копирование, либо потому что достиг конца Stdin
	// (EOF или ошибка чтения), либо потому, что запись в канал вернула ошибку,
    // либо потому, что было установлено ненулевое значение WaitDelay, которое истекло.
    Stdin io.Reader

    // Stdout и Stderr указывают стандартный вывод и ошибки процесса.
    //
    // Если любой из них равен nil, Run подключает соответствующий файловый дескриптор
    // к нулевому устройству (os.DevNull).
	//
    // Если любой из них является *os.File, соответствующий вывод из процесса
    // подключается непосредственно к этому файлу.
    //
    // В противном случае во время выполнения команды отдельная goroutine
    // считывает данные из процесса через канал и доставляет их в
    // соответствующий Writer. В этом случае Wait не завершается, пока
    // goroutine не достигнет EOF, не столкнется с ошибкой или не истечет ненулевое значение WaitDelay
	//.
    //
    // Если Stdout и Stderr являются одним и тем же writer и имеют тип, который можно
    // сравнить с помощью ==, то одновременно Write будет вызывать не более одного goroutine.
    Stdout io.Writer
    Stderr io.Writer

    // ExtraFiles указывает дополнительные открытые файлы, которые будут унаследованы
	// новый процесс. Он не включает стандартный ввод, стандартный вывод или
    // стандартную ошибку. Если не равен nil, запись i становится файловым дескриптором 3+i.
    //
    // ExtraFiles не поддерживается в Windows.
    ExtraFiles []*os.File

    // SysProcAttr содержит дополнительные атрибуты, специфичные для операционной системы.
	// Run передает его os.StartProcess как поле Sys os.ProcAttr.
    SysProcAttr *syscall.SysProcAttr

    // Process — это базовый процесс после запуска.
    Process *os.Process

    // ProcessState содержит информацию о завершенном процессе.
    // Если процесс был запущен успешно, Wait или Run
	// заполнят его ProcessState по завершении команды.
    ProcessState *os.ProcessState

    Err error // Ошибка LookPath, если есть.

    // Если Cancel не равен nil, команда должна быть создана с помощью
    // CommandContext, и Cancel будет вызван, когда
    // Context команды будет выполнен. По умолчанию CommandContext устанавливает Cancel в
	// вызывает метод Kill в процессе команды.
    //
    // Обычно пользовательский Cancel посылает сигнал процессу команды
    //, но вместо этого он может предпринять другие действия для инициирования отмены,
    // такие как закрытие канала stdin или stdout или отправка запроса на завершение работы
    // сетевого сокета.
    //
	// Если команда завершается с успешным статусом после вызова Cancel
    //, а Cancel не возвращает ошибку, эквивалентную
    // os.ErrProcessDone, то Wait и подобные методы будут возвращать не нулевую
    // ошибку: либо ошибку, оборачивающую ошибку, возвращенную Cancel,
    // либо ошибку из Context.
    // (Если команда завершается с неуспешным статусом или Cancel
	// возвращает ошибку, которая оборачивает os.ErrProcessDone, Wait и подобные методы
    // продолжают возвращать обычный статус завершения команды.)
    //
    // Если Cancel установлен в nil, ничего не произойдет сразу после завершения
    // Context команды, но WaitDelay, отличное от нуля, все равно будет действовать. Это может
    // быть полезно, например, для обхода тупиковых ситуаций в командах, которые не
	// поддерживают сигналы завершения, но должны всегда завершаться быстро.
    //
    // Cancel не будет вызван, если Start возвращает ошибку, отличную от nil.
    Cancel func() error
    // Если WaitDelay отлично от нуля, оно ограничивает время ожидания двух источников
    // непредвиденной задержки в Wait: дочернего процесса, который не завершается после
    // отмены связанного Context, и дочернего процесса, который завершается, но оставляет
	// свои каналы ввода-вывода незакрытыми.
    //
    // Таймер WaitDelay запускается, когда связанный контекст завершается или
    // вызов Wait обнаруживает, что дочерний процесс завершился, в зависимости от того, что произойдет
    // первым. По истечении задержки команда завершает дочерний процесс
    // и/или его каналы ввода-вывода.
    //
	// Если дочерний процесс не завершился — возможно, потому что он проигнорировал или
    // не получил сигнал о завершении от функции Cancel, или потому что не была
    // установлена функция Cancel — то он будет завершен с помощью os.Process.Kill.
    //
    // Затем, если каналы ввода-вывода, связывающиеся с дочерним процессом, все еще открыты,
    // эти каналы закрываются, чтобы разблокировать все goroutines, которые в данный момент заблокированы
	// вызовами Read или Write.
    //
    // Если трубы закрыты из-за WaitDelay, вызов Cancel не произошел,
    // и команда завершилась с успешным статусом, Wait и
    // подобные методы вернут ErrWaitDelay вместо nil.
    //
    // Если WaitDelay равен нулю (по умолчанию), трубы ввода-вывода будут читаться до EOF,
	// что может не произойти, пока осиротевшие подпроцессы команды
    // также не закроют свои дескрипторы для каналов.
    WaitDelay time.Duration
    // содержит отфильтрованные или неэкспортированные поля
}

Cmd представляет внешнюю команду, которая готовится или запускается.

Cmd не может быть повторно использован после вызова методов Cmd.Run, Cmd.Output или Cmd.CombinedOutput.

func Command

func Command(name string, arg ...string) *Cmd

Command возвращает структуру Cmd для выполнения именованной программы с заданными аргументами.

В возвращаемой структуре задаются только Path и Args.

Если имя не содержит разделителей путей, Command использует LookPath для преобразования имени в полный путь, если это возможно. В противном случае она использует имя непосредственно в качестве Path.

Возвращаемое поле Args Cmd строится из имени команды, за которым следуют элементы arg, поэтому arg не должно включать само имя команды. Например, Command(“echo”, “hello”). Args[0] - это всегда имя, а не возможно разрешенный Path.

В Windows процессы получают всю командную строку как единую строку и выполняют собственный разбор. Command объединяет и заключает Args в кавычки в строку командной строки с помощью алгоритма, совместимого с приложениями, использующими CommandLineToArgvW (это наиболее распространенный способ). Заметными исключениями являются msiexec.exe и cmd.exe (и, соответственно, все пакетные файлы), которые имеют другой алгоритм снятия кавычек. В этих и других подобных случаях вы можете выполнить котирование самостоятельно и указать полную командную строку в SysProcAttr.CmdLine, оставив Args пустым.

Пример
package main

import (
	"fmt"
	"log"
	"os/exec"
	"strings"
)

func main() {
	cmd := exec.Command("tr", "a-z", "A-Z")
	cmd.Stdin = strings.NewReader("some input")
	var out strings.Builder
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("in all caps: %q\n", out.String())
}
Пример с окружением среды
package main

import (
	"log"
	"os"
	"os/exec"
)

func main() {
	cmd := exec.Command("prog")
	cmd.Env = append(os.Environ(),
		"FOO=duplicate_value", // ignored
		"FOO=actual_value",    // this value is used
	)
	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
}

func CommandContext

func CommandContext(ctx context.Context, name string, arg ...string) *Cmd

CommandContext похож на Command, но включает в себя контекст.

Предоставленный контекст используется для прерывания процесса (путем вызова cmd.Cancel или os.Process.Kill), если контекст завершается до того, как команда завершит свою работу.

CommandContext устанавливает функцию Cancel команды для вызова метода Kill в ее Process и оставляет WaitDelay не установленным. Вызывающая сторона может изменить поведение отмены, изменив эти поля перед запуском команды.

Пример
package main

import (
	"context"
	"os/exec"
	"time"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()

	if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
		// This will fail after 100 milliseconds. The 5 second sleep
		// will be interrupted.
	}
}

func (*Cmd) CombinedOutput

func (c *Cmd) CombinedOutput() ([]byte, error)

CombinedOutput запускает команду и возвращает ее объединенный стандартный вывод и стандартную ошибку.

Пример
package main

import (
	"fmt"
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
	stdoutStderr, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", stdoutStderr)
}

func (*Cmd) Environ

func (c *Cmd) Environ() []string

Environ возвращает копию среды, в которой будет запущена команда, в том виде, в котором она настроена в данный момент.

Пример
package main

import (
	"fmt"
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("pwd")

	// Set Dir before calling cmd.Environ so that it will include an
	// updated PWD variable (on platforms where that is used).
	cmd.Dir = ".."
	cmd.Env = append(cmd.Environ(), "POSIXLY_CORRECT=1")

	out, err := cmd.Output()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", out)
}

func (*Cmd) Output

func (c *Cmd) Output() ([]byte, error)

Output запускает команду и возвращает ее стандартный вывод. Любая возвращаемая ошибка обычно будет иметь тип *ExitError. Если c.Stderr было nil, а возвращаемая ошибка имеет тип *ExitError, Output заполняет поле Stderr возвращаемой ошибки.

Пример
package main

import (
	"fmt"
	"log"
	"os/exec"
)

func main() {
	out, err := exec.Command("date").Output()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("The date is %s\n", out)
}

func (*Cmd) Run

func (c *Cmd) Run() error

Run запускает указанную команду и ждет ее завершения.

Возвращаемая ошибка равна nil, если команда запущена, не возникло проблем с копированием stdin, stdout и stderr, и она завершилась с нулевым статусом выхода.

Если команда запущена, но не завершилась успешно, ошибка будет типа *ExitError. В других ситуациях могут возвращаться ошибки других типов.

Если вызывающая goroutine заблокировала поток операционной системы с помощью runtime.LockOSThread и изменила любое наследуемое состояние потока на уровне ОС (например, пространства имен Linux или Plan 9), новый процесс унаследует состояние потока вызывающего.

Пример
package main

import (
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("sleep", "1")
	log.Printf("Running command and waiting for it to finish...")
	err := cmd.Run()
	log.Printf("Command finished with error: %v", err)
}

func (*Cmd) Start

func (c *Cmd) Start() error

Start запускает указанную команду, но не ждет ее завершения.

Если Start возвращается успешно, поле c.Process будет установлено.

После успешного вызова Start необходимо вызвать метод Cmd.Wait, чтобы освободить связанные системные ресурсы.

Пример
package main

import (
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("sleep", "5")
	err := cmd.Start()
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Waiting for command to finish...")
	err = cmd.Wait()
	log.Printf("Command finished with error: %v", err)
}

func (*Cmd) StderrPipe

func (c *Cmd) StderrPipe() (io.ReadCloser, error)

StderrPipe возвращает канал, который будет подключен к стандартной ошибке команды при ее запуске.

Cmd.Wait закроет канал после завершения команды, поэтому большинству вызывающих не нужно закрывать канал самостоятельно. Таким образом, неправильно вызывать Wait до завершения всех операций чтения из канала. По той же причине неправильно использовать Cmd.Run при использовании StderrPipe. Идиоматическое использование см. в примере StdoutPipe.

Пример
package main

import (
	"fmt"
	"io"
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
	stderr, err := cmd.StderrPipe()
	if err != nil {
		log.Fatal(err)
	}

	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}

	slurp, _ := io.ReadAll(stderr)
	fmt.Printf("%s\n", slurp)

	if err := cmd.Wait(); err != nil {
		log.Fatal(err)
	}
}

func (*Cmd) StdinPipe

func (c *Cmd) StdinPipe() (io.WriteCloser, error)

StdinPipe возвращает канал, который будет подключен к стандартному входу команды при ее запуске. Канал будет автоматически закрыт после того, как Cmd.Wait увидит выход команды. Вызывающему достаточно вызвать Close, чтобы заставить канал закрыться раньше. Например, если запускаемая команда не завершится до закрытия стандартного ввода, вызывающий должен закрыть канал.

Пример
package main

import (
	"fmt"
	"io"
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("cat")
	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		defer stdin.Close()
		io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
	}()

	out, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s\n", out)
}

func (*Cmd) StdoutPipe

func (c *Cmd) StdoutPipe() (io.ReadCloser, error)

StdoutPipe возвращает канал, который будет подключен к стандартному выводу команды при ее запуске.

Cmd.Wait закроет канал после завершения команды, поэтому большинству вызывающих не нужно закрывать канал самостоятельно. Таким образом, неправильно вызывать Wait до завершения всех операций чтения из канала. По той же причине неправильно вызывать Cmd.Run при использовании StdoutPipe. См. пример для идиоматического использования.

Пример
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
	var person struct {
		Name string
		Age  int
	}
	if err := json.NewDecoder(stdout).Decode(&person); err != nil {
		log.Fatal(err)
	}
	if err := cmd.Wait(); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s is %d years old\n", person.Name, person.Age)

func (*Cmd) String

func (c *Cmd) String() string

String возвращает удобочитаемое описание c. Предназначено только для отладки. В частности, не подходит для использования в качестве ввода в оболочку. Вывод String может различаться в разных версиях Go.

func (*Cmd) Wait

func (c *Cmd) Wait() error

Wait ожидает завершения команды и завершения копирования в stdin или копирования из stdout или stderr.

Команда должна быть запущена с помощью Cmd.Start.

Возвращаемая ошибка равна nil, если команда запущена, не имеет проблем с копированием stdin, stdout и stderr и завершается с нулевым статусом выхода.

Если команда не запускается или не завершается успешно, ошибка имеет тип *ExitError. Другие типы ошибок могут возвращаться при проблемах с вводом-выводом.

Если c.Stdin, c.Stdout или c.Stderr не являются *os.File, Wait также ожидает завершения соответствующего цикла ввода-вывода в процесс или из процесса.

Wait освобождает все ресурсы, связанные с Cmd.

type Error

type Error struct {
    // Name — имя файла, для которого произошла ошибка.
    Name string
    // Err — базовая ошибка.
    Err error
}

Error возвращается LookPath, когда он не может классифицировать файл как исполняемый.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type ExitError

type ExitError struct {
    *os.ProcessState

    // Stderr содержит подмножество стандартного вывода ошибок из
    // метода Cmd.Output, если стандартные ошибки не были
    // собраны иным способом.
    //
    // Если вывод ошибок длинный, Stderr может содержать только префикс
	// и суффикс вывода, а середина будет заменена
    // текстом о количестве пропущенных байтов.
    //
    // Stderr предоставляется для отладки, для включения в сообщения об ошибках.
    // Пользователи с другими потребностями должны перенаправить Cmd.Stderr по мере необходимости.
    Stderr []byte
}

ExitError сообщает о неудачном завершении команды.

func (*ExitError) Error

func (e *ExitError) Error() string

11.4 - Пакет os/user языка программирования Go

Пакет user позволяет искать учетные записи пользователей по имени или идентификатору.

Для большинства систем Unix этот пакет имеет две внутренние реализации преобразования идентификаторов пользователей и групп в имена, а также вывода списка дополнительных идентификаторов групп. Одна из них написана на чистом Go и анализирует файлы /etc/passwd и /etc/group. Другая основана на cgo и использует стандартные процедуры библиотеки C (libc), такие как getpwuid_r, getgrnam_r и getgrouplist.

Если cgo доступен, а необходимые процедуры реализованы в libc для конкретной платформы, используется код на основе cgo (с поддержкой libc). Это можно переопределить с помощью тега сборки osusergo, который принудительно использует реализацию на чистом Go.

type Group

type Group struct {
    Gid  string // идентификатор группы
    Name string // название группы
}

Group представляет группу пользователей.

В системах POSIX Gid содержит десятичное число, представляющее идентификатор группы.

func LookupGroup

func LookupGroup(name string) (*Group, error)

LookupGroup ищет группу по имени. Если группа не найдена, возвращается ошибка типа UnknownGroupError.

func LookupGroupId

func LookupGroupId(gid string) (*Group, error)

LookupGroupId ищет группу по groupid. Если группа не найдена, возвращается ошибка типа UnknownGroupIdError.

type UnknownGroupError

type UnknownGroupError string

UnknownGroupError возвращается LookupGroup, когда группа не может быть найдена.

func (UnknownGroupError) Error

func (e UnknownGroupError) Error() string

type UnknownGroupIdError

type UnknownGroupIdError string

UnknownGroupIdError возвращается LookupGroupId, когда группа не может быть найдена.

func (UnknownGroupIdError) Error

func (e UnknownGroupIdError) Error() string

type UnknownUserError

type UnknownUserError string

UnknownUserError возвращается Lookup, когда пользователь не может быть найден.

func (UnknownUserError) Error

func (e UnknownUserError) Error() string

type UnknownUserIdError

type UnknownUserIdError int

UnknownUserIdError возвращается LookupId, когда пользователь не может быть найден.

func (UnknownUserIdError) Error

func (e UnknownUserIdError) Error() string

type User ¶

type User struct {
    // Uid — это идентификатор пользователя.
	// В системах POSIX это десятичное число, представляющее uid.
    // В Windows это идентификатор безопасности (SID) в формате строки.
    // В Plan 9 это содержимое /dev/user.
    Uid string
    // Gid — это идентификатор основной группы.
    // В системах POSIX это десятичное число, представляющее gid.
	// В Windows это SID в формате строки.
    // В Plan 9 это содержимое /dev/user.
    Gid string
    // Username — это имя для входа в систему.
    Username string
    // Name — это настоящее или отображаемое имя пользователя.
    // Оно может быть пустым.
    // В системах POSIX это первая (или единственная) запись в поле GECOS
	// списка GECOS.
    // В Windows это отображаемое имя пользователя.
    // В Plan 9 это содержимое /dev/user.
    Строка имени
    // HomeDir — это путь к домашнему каталогу пользователя (если он есть).
    Строка HomeDir
}

User представляет учетную запись пользователя.

func Current

func Current() (*User, error)

Current возвращает текущего пользователя.

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

func Lookup

func Lookup(username string) (*User, error)

Lookup ищет пользователя по имени. Если пользователь не найден, возвращается ошибка типа UnknownUserError.

func LookupId

func LookupId(uid string) (*User, error)

LookupId ищет пользователя по идентификатору. Если пользователь не найден, возвращается ошибка типа UnknownUserIdError.

func (*User) GroupIds

func (u *User) GroupIds() ([]string, error)

GroupIds возвращает список идентификаторов групп, членом которых является пользователь.

11.5 - Пакет os/signal языка программирования Go

Пакет signal реализует доступ к входящим сигналам.

Сигналы в основном используются в Unix-подобных системах. Для использования этого пакета в Windows и Plan 9 см. ниже.

Типы сигналов

Сигналы SIGKILL и SIGSTOP не могут быть перехвачены программой и, следовательно, не могут быть затронуты этим пакетом.

Синхронные сигналы — это сигналы, вызываемые ошибками при выполнении программы: SIGBUS, SIGFPE и SIGSEGV. Они считаются синхронными только в том случае, если вызваны выполнением программы, а не отправлены с помощью os.Process.Kill, программы kill или аналогичного механизма. В общем случае, за исключением описанного ниже, программы Go преобразуют синхронный сигнал в панику во время выполнения.

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

Из асинхронных сигналов сигнал SIGHUP отправляется, когда программа теряет свой управляющий терминал. Сигнал SIGINT отправляется, когда пользователь на управляющем терминале нажимает символ прерывания, который по умолчанию является ^C (Control-C). Сигнал SIGQUIT отправляется, когда пользователь на управляющем терминале нажимает символ выхода, который по умолчанию является ^\ (Control-Backslash). Как правило, вы можете просто завершить программу, нажав ^C, а также завершить ее с дампом стека, нажав ^.

Поведение сигналов по умолчанию в программах Go

По умолчанию синхронный сигнал преобразуется в панику во время выполнения. Сигнал SIGHUP, SIGINT или SIGTERM приводит к завершению программы. Сигнал SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGSTKFLT, SIGEMT или SIGSYS приводит к завершению программы с дампом стека. Сигнал SIGTSTP, SIGTTIN или SIGTTOU получает поведение по умолчанию системы (эти сигналы используются оболочкой для управления задачами). Сигнал SIGPROF обрабатывается непосредственно средой выполнения Go для реализации runtime.CPUProfile. Другие сигналы будут перехвачены, но никаких действий не будет предпринято.

Если программа Go запущена с игнорированием сигналов SIGHUP или SIGINT (обработчик сигналов установлен в SIG_IGN), они будут по-прежнему игнорироваться.

Если программа Go запущена с непустой маской сигналов, она, как правило, будет соблюдаться. Однако некоторые сигналы явно разблокированы: синхронные сигналы, SIGILL, SIGTRAP, SIGSTKFLT, SIGCHLD, SIGPROF, а в Linux — сигналы 32 (SIGCANCEL) и 33 (SIGSETXID) (SIGCANCEL и SIGSETXID используются внутренне glibc). Подпроцессы, запущенные с помощью os.Exec или os/exec, унаследуют измененную маску сигналов.

Изменение поведения сигналов в программах Go

Функции в этом пакете позволяют программе изменять способ обработки сигналов программами Go.

Notify отключает поведение по умолчанию для заданного набора асинхронных сигналов и вместо этого доставляет их по одному или нескольким зарегистрированным каналам. В частности, это относится к сигналам SIGHUP, SIGINT, SIGQUIT, SIGABRT и SIGTERM. Это также относится к сигналам управления задачами SIGTSTP, SIGTTIN и SIGTTOU, в этом случае поведение по умолчанию системы не происходит. Она также применяется к некоторым сигналам, которые в противном случае не вызывают никаких действий: SIGUSR1, SIGUSR2, SIGPIPE, SIGALRM, SIGCHLD, SIGCONT, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGWINCH, SIGIO, SIGPWR, SIGINFO, SIGTHR, SIGWAITING, SIGLWP, SIGFREEZE, SIGTHAW, SIGLOST, SIGXRES, SIGJVM1, SIGJVM2 и любые сигналы реального времени, используемые в системе. Обратите внимание, что не все эти сигналы доступны во всех системах.

Если программа была запущена с игнорированием SIGHUP или SIGINT, и для любого из этих сигналов вызван Notify, для этого сигнала будет установлен обработчик сигналов, и он больше не будет игнорироваться. Если позже для этого сигнала вызван Reset или Ignore, или вызван Stop для всех каналов, переданных Notify для этого сигнала, сигнал снова будет игнорироваться. Reset восстановит поведение системы по умолчанию для сигнала, а Ignore заставит систему полностью игнорировать сигнал.

Если программа запущена с непустой маской сигналов, некоторые сигналы будут явно разблокированы, как описано выше. Если Notify вызывается для заблокированного сигнала, он будет разблокирован. Если позже для этого сигнала вызывается Reset или Stop для всех каналов, переданных Notify для этого сигнала, сигнал снова будет заблокирован.

SIGPIPE

Когда программа на Go записывает в разорванный канал, ядро генерирует сигнал SIGPIPE.

Если программа не вызвала Notify для получения сигналов SIGPIPE, то поведение зависит от номера файлового дескриптора. Запись в разорванный канал на файловых дескрипторах 1 или 2 (стандартный вывод или стандартная ошибка) приведет к завершению программы с сигналом SIGPIPE. Запись в разорванный канал на каком-либо другом файловом дескрипторе не вызовет никаких действий по сигналу SIGPIPE, и запись завершится с ошибкой EPIPE.

Если программа вызвала Notify для получения сигналов SIGPIPE, номер файлового дескриптора не имеет значения. Сигнал SIGPIPE будет доставлен в канал Notify, и запись завершится с ошибкой EPIPE.

Это означает, что по умолчанию программы командной строки будут вести себя как типичные программы командной строки Unix, в то время как другие программы не будут завершаться с SIGPIPE при записи в закрытое сетевое соединение.

Программы Go, использующие cgo или SWIG

В программе Go, которая включает код, не относящийся к Go, обычно код C/C++, доступ к которому осуществляется с помощью cgo или SWIG, сначала запускается код запуска Go. Он настраивает обработчики сигналов в соответствии с ожиданиями среды выполнения Go, прежде чем запускается код запуска, не относящийся к Go. Если код запуска, не относящийся к Go, желает установить свои собственные обработчики сигналов, он должен предпринять определенные шаги, чтобы Go продолжал работать нормально. В этом разделе описаны эти шаги и общие изменения, которые могут быть внесены в настройки обработчиков сигналов кодом, не относящимся к Go, в программах Go. В редких случаях код, не относящийся к Go, может запускаться перед кодом Go, и в этом случае также применяется следующий раздел.

Если код, не относящийся к Go, вызываемый программой Go, не изменяет обработчики сигналов или маски, то поведение будет таким же, как и для чистой программы Go.

Если код, не относящийся к Go, устанавливает какие-либо обработчики сигналов, он должен использовать флаг SA_ONSTACK с sigaction. Невыполнение этого требования может привести к сбою программы при получении сигнала. Программы Go обычно работают с ограниченным стеком, поэтому настраивают альтернативный стек сигналов.

Если код, не относящийся к Go, устанавливает обработчик сигналов для любого из синхронных сигналов (SIGBUS, SIGFPE, SIGSEGV), то он должен записывать существующий обработчик сигналов Go. Если эти сигналы возникают во время выполнения кода Go, он должен вызывать обработчик сигналов Go (возникновение сигнала во время выполнения кода Go можно определить, посмотрев на PC, передаваемый обработчику сигналов). В противном случае некоторые паники времени выполнения Go не будут происходить, как ожидается.

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

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

Если код, не относящийся к Go, запускает новый поток, изменяет маску сигналов, а затем вызывает функцию Go в этом потоке, среда выполнения Go автоматически разблокирует определенные сигналы: синхронные сигналы, SIGILL, SIGTRAP, SIGSTKFLT, SIGCHLD, SIGPROF, SIGCANCEL и SIGSETXID. Когда функция Go возвращается, маска сигналов, не относящаяся к Go, будет восстановлена.

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

Если получен сигнал SIGPIPE, программа Go вызовет специальную обработку, описанную выше, если SIGPIPE получен в потоке Go. Если SIGPIPE получен в потоке, не относящемся к Go, сигнал будет перенаправлен в обработчик, не относящийся к Go, если таковой имеется; если его нет, обработчик по умолчанию системы приведет к завершению программы.

Программы, не написанные на Go, которые вызывают код Go

Когда код Go компилируется с такими опциями, как -buildmode=c-shared, он будет запускаться как часть существующей программы, не написанной на Go. Код, не написанный на Go, может уже иметь установленные обработчики сигналов, когда запускается код Go (это также может произойти в необычных случаях при использовании cgo или SWIG; в этом случае применимо обсуждение, приведенное здесь). Для -buildmode=c-archive среда выполнения Go инициализирует сигналы во время глобального конструктора. Для -buildmode=c-shared среда выполнения Go инициализирует сигналы при загрузке разделяемой библиотеки.

Если среда выполнения Go обнаруживает существующий обработчик сигналов для сигналов SIGCANCEL или SIGSETXID (которые используются только в Linux), она включает флаг SA_ONSTACK и в остальном сохраняет обработчик сигналов.

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

Код Go, скомпилированный с -buildmode=c-archive или -buildmode=c-shared, по умолчанию не будет устанавливать никаких других обработчиков сигналов. Если существующий обработчик сигналов, среда выполнения Go включит флаг SA_ONSTACK и сохранит обработчик сигналов. Если Notify вызывается для асинхронного сигнала, для этого сигнала будет установлен обработчик сигналов Go. Если позже для этого сигнала вызывается Reset, исходная обработка этого сигнала будет переустановлена, восстанавливая обработчик сигналов, не относящийся к Go, если таковой имеется.

Код Go, скомпилированный без -buildmode=c-archive или -buildmode=c-shared, установит обработчик сигналов для асинхронных сигналов, перечисленных выше, и сохранит любой существующий обработчик сигналов. Если сигнал доставляется в поток, не относящийся к Go, он будет действовать, как описано выше, за исключением того, что если существует обработчик сигналов, не относящийся к Go, этот обработчик будет установлен перед генерацией сигнала. Windows ¶

В Windows ^C (Control-C) или ^BREAK (Control-Break) обычно приводят к завершению программы. Если Notify вызывается для os.Interrupt, ^C или ^BREAK приводят к отправке os.Interrupt по каналу, и программа не завершается. Если вызывается Reset или Stop на всех каналах, переданных Notify, то поведение по умолчанию будет восстановлено.

Кроме того, если вызывается Notify, и Windows отправляет CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT или CTRL_SHUTDOWN_EVENT процессу, Notify вернет syscall.SIGTERM. В отличие от Control-C и Control-Break, Notify не изменяет поведение процесса при получении CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT или CTRL_SHUTDOWN_EVENT — процесс все равно будет завершен, если он не завершится самостоятельно. Но получение syscall.SIGTERM даст процессу возможность очиститься перед завершением.

Plan 9

В Plan 9 сигналы имеют тип syscall.Note, который представляет собой строку. Вызов Notify с syscall.Note приведет к отправке этого значения по каналу, когда эта строка будет отправлена в качестве заметки.

Функции

func Ignore

func Ignore(sig ...os.Signal)

Ignore заставляет игнорировать указанные сигналы. Если они будут получены программой, ничего не произойдет. Ignore отменяет действие всех предыдущих вызовов Notify для указанных сигналов. Если сигналы не указаны, все входящие сигналы будут игнорироваться.

func Ignored

func Ignored(sig os.Signal) bool

Ignored сообщает, игнорируется ли sig в данный момент.

func Notify

func Notify(c chan<- os.Signal, sig ...os.Signal)

Notify заставляет пакет signal ретранслировать входящие сигналы в c. Если сигналы не предоставлены, все входящие сигналы будут ретранслированы в c. В противном случае будут ретранслированы только предоставленные сигналы.

Пакет signal не будет блокировать отправку в c: вызывающий должен убедиться, что c имеет достаточное буферное пространство, чтобы справиться с ожидаемой скоростью сигналов. Для канала, используемого для уведомления только об одном значении сигнала, достаточно буфера размером 1.

Разрешается вызывать Notify несколько раз с одним и тем же каналом: каждый вызов расширяет набор сигналов, отправляемых на этот канал. Единственный способ удалить сигналы из набора — вызвать Stop.

Разрешается вызывать Notify несколько раз с разными каналами и одними и теми же сигналами: каждый канал получает копии входящих сигналов независимо.

Пример
package main

import (
	"fmt"
	"os"
	"os/signal"
)

func main() {
	// Set up channel on which to send signal notifications.
	// We must use a buffered channel or risk missing the signal
	// if we're not ready to receive when the signal is sent.
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)

	// Block until a signal is received.
	s := <-c
	fmt.Println("Got signal:", s)
}
Пример AllSignals
package main

import (
	"fmt"
	"os"
	"os/signal"
)

func main() {
	// Set up channel on which to send signal notifications.
	// We must use a buffered channel or risk missing the signal
	// if we're not ready to receive when the signal is sent.
	c := make(chan os.Signal, 1)

	// Passing no signals to Notify means that
	// all signals will be sent to the channel.
	signal.Notify(c)

	// Block until any signal is received.
	s := <-c
	fmt.Println("Got signal:", s)
}

func NotifyContext

func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)

NotifyContext возвращает копию родительского контекста, который помечается как выполненный (его канал Done закрывается), когда поступает один из перечисленных сигналов, когда вызывается возвращаемая функция stop или когда канал Done родительского контекста закрывается, в зависимости от того, что произойдет раньше.

Функция stop отменяет регистрацию поведения сигнала, что, как и signal.Reset, может восстановить поведение по умолчанию для данного сигнала. Например, поведением по умолчанию для программы Go, получающей os.Interrupt, является выход. Вызов NotifyContext(parent, os.Interrupt) изменит поведение на отмену возвращенного контекста. Будущие прерывания не будут вызывать поведение по умолчанию (выход) до тех пор, пока не будет вызвана возвращенная функция stop.

Функция stop освобождает связанные с ней ресурсы, поэтому код должен вызывать stop, как только операции, выполняемые в этом контексте, завершатся и сигналы больше не нужно будет перенаправлять в контекст.

Пример
//go:build unix

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
)

var neverReady = make(chan struct{}) // never closed

// This example passes a context with a signal to tell a blocking function that
// it should abandon its work after a signal is received.
func main() {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()

	p, err := os.FindProcess(os.Getpid())
	if err != nil {
		log.Fatal(err)
	}

	// On a Unix-like system, pressing Ctrl+C on a keyboard sends a
	// SIGINT signal to the process of the program in execution.
	//
	// This example simulates that by sending a SIGINT signal to itself.
	if err := p.Signal(os.Interrupt); err != nil {
		log.Fatal(err)
	}

	select {
	case <-neverReady:
		fmt.Println("ready")
	case <-ctx.Done():
		fmt.Println(ctx.Err()) // prints "context canceled"
		stop()                 // stop receiving signal notifications as soon as possible.
	}

}
Output:

context canceled

func Reset

func Reset(sig ...os.Signal)

Reset отменяет эффект всех предыдущих вызовов Notify для предоставленных сигналов. Если сигналы не указаны, все обработчики сигналов будут сброшены.

func Stop

func Stop(c chan<- os.Signal)

Stop заставляет пакет signal прекратить ретрансляцию входящих сигналов в c. Он отменяет эффект всех предыдущих вызовов Notify с использованием c. Когда Stop возвращается, гарантируется, что c больше не будет получать сигналы.

12 - Описание пакета strconv

Пакет strconv реализует преобразования в строковые представления основных типов данных и обратно.

Числовые преобразования

Наиболее распространенными числовыми преобразованиями являются Atoi (строка в int) и Itoa (int в String).

i, err := strconv.Atoi(«-42»)
s := strconv.Itoa(-42)

Они предполагают десятичную систему счисления и тип Go int.

ParseBool, ParseFloat, ParseInt и ParseUint преобразуют строки в значения:

b, err := strconv.ParseBool(«true»)
f, err := strconv.ParseFloat(«3.1415», 64)
i, err := strconv.ParseInt(«-42», 10, 64)
u, err := strconv.ParseUint(«42», 10, 64)

Функции преобразования возвращают самый широкий тип (float64, int64 и uint64), но если аргумент size указывает более узкую ширину, результат может быть преобразован в этот более узкий тип без потери данных:

s := «2147483647» // самый большой int32
i64, err := strconv.ParseInt(s, 10, 32)
...
i := int32(i64)

FormatBool, FormatFloat, FormatInt и FormatUint преобразуют значения в строки:

s := strconv.FormatBool(true)
s := strconv.FormatFloat(3.1415, E, -1, 64)
s := strconv.FormatInt(-42, 16)
s := strconv.FormatUint(42, 16)

AppendBool, AppendFloat, AppendInt и AppendUint аналогичны, но добавляют отформатированное значение в целевой срез.

Преобразование строк

Quote и QuoteToASCII преобразуют строки в строчные литералы Go в кавычках. Последний гарантирует, что результатом будет строка ASCII, экранируя любой не-ASCII Unicode с помощью \u:

q := strconv.Quote(«Hello, 世界»)
q := strconv.QuoteToASCII(«Hello, 世界»)

QuoteRune и QuoteRuneToASCII похожи, но принимают руны и возвращают зацикленные литералы Go.

Unquote и UnquoteChar разъединяют литералы строк и рун Go.

Константы

const IntSize = intSize

IntSize - это размер в битах значения int или uint.

Переменные

var ErrRange = errors.New("значение вне диапазона")

ErrRange указывает, что значение выходит за пределы диапазона для целевого типа.

var ErrSyntax = errors.New("invalid syntax")

ErrSyntax указывает, что значение не имеет правильного синтаксиса для целевого типа.

12.1 - Описание функций пакета strconv

Описание функция из пакета strconv

func AppendBool

func AppendBool(dst []byte, b bool) []byte

AppendBool добавляет «true» или «false» в зависимости от значения b к dst и возвращает расширенный буфер.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	b := []byte("bool:")
	b = strconv.AppendBool(b, true)
	fmt.Println(string(b))

}

func AppendFloat

func AppendFloat(dst []byte, f float64, fmt byte, prec, bitSize int) []byte

AppendFloat добавляет строковую форму числа с плавающей запятой f, сгенерированную FormatFloat, к dst и возвращает расширенный буфер.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	b32 := []byte("float32:")
	b32 = strconv.AppendFloat(b32, 3.1415926535, 'E', -1, 32)
	fmt.Println(string(b32))

	b64 := []byte("float64:")
	b64 = strconv.AppendFloat(b64, 3.1415926535, 'E', -1, 64)
	fmt.Println(string(b64))

}

func AppendInt

func AppendInt(dst []byte, i int64, base int) []byte

AppendInt добавляет строковую форму целого числа i, сгенерированную FormatInt, к dst и возвращает расширенный буфер.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	b10 := []byte("int (base 10):")
	b10 = strconv.AppendInt(b10, -42, 10)
	fmt.Println(string(b10))

	b16 := []byte("int (base 16):")
	b16 = strconv.AppendInt(b16, -42, 16)
	fmt.Println(string(b16))

}

func AppendQuote

func AppendQuote(dst []byte, s string) []byte

AppendQuote добавляет к dst строковый литерал Go в двойных кавычках, представляющий s, сгенерированный Quote, и возвращает расширенный буфер.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	b := []byte("quote:")
	b = strconv.AppendQuote(b, `"Fran & Freddie's Diner"`)
	fmt.Println(string(b))

}
Output:

quote:"\"Fran & Freddie's Diner\""

func AppendQuoteRune

func AppendQuoteRune(dst []byte, r rune) []byte

AppendQuoteRune добавляет к dst однострочный литерал Go, представляющий руну, сгенерированный QuoteRune, и возвращает расширенный буфер.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	b := []byte("rune:")
	b = strconv.AppendQuoteRune(b, '☺')
	fmt.Println(string(b))

}
Output:

rune:'☺'

func AppendQuoteRuneToASCII

func AppendQuoteRuneToASCII(dst []byte, r rune) []byte

AppendQuoteRuneToASCII добавляет в dst однострочный литерал символа Go, представляющий руну, сгенерированный QuoteRuneToASCII, и возвращает расширенный буфер.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	b := []byte("rune (ascii):")
	b = strconv.AppendQuoteRuneToASCII(b, '☺')
	fmt.Println(string(b))

}
Output:

rune (ascii):'\u263a'

func AppendQuoteRuneToGraphic

func AppendQuoteRuneToGraphic(dst []byte, r rune) []byte

AppendQuoteRuneToGraphic добавляет к dst однострочный литерал символа Go, представляющий руну, сгенерированный QuoteRuneToGraphic, и возвращает расширенный буфер.

func AppendQuoteToASCII

func AppendQuoteToASCII(dst []byte, s string) []byte

AppendQuoteToASCII добавляет к dst строковый литерал Go в двойных кавычках, представляющий s, сгенерированный QuoteToASCII, и возвращает расширенный буфер.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	b := []byte("quote (ascii):")
	b = strconv.AppendQuoteToASCII(b, `"Fran & Freddie's Diner"`)
	fmt.Println(string(b))

}
Output:

quote (ascii):"\"Fran & Freddie's Diner\""

func AppendQuoteToGraphic

func AppendQuoteToGraphic(dst []byte, s string) []byte

AppendQuoteToGraphic добавляет к dst строковый литерал Go в двойных кавычках, представляющий s, сгенерированный QuoteToGraphic, и возвращает расширенный буфер.

func AppendUint

func AppendUint(dst []byte, i uint64, base int) []byte

AppendUint добавляет строковую форму целого числа без знака i, сгенерированную FormatUint, к dst и возвращает расширенный буфер.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	b10 := []byte("uint (base 10):")
	b10 = strconv.AppendUint(b10, 42, 10)
	fmt.Println(string(b10))

	b16 := []byte("uint (base 16):")
	b16 = strconv.AppendUint(b16, 42, 16)
	fmt.Println(string(b16))

}
Output:

uint (base 10):42
uint (base 16):2a
## func Atoi ```go func Atoi(s string) (int, error) ``` Atoi эквивалентно ParseInt(s, 10, 0), преобразованному в тип int.
Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	v := "10"
	if s, err := strconv.Atoi(v); err == nil {
		fmt.Printf("%T, %v", s, s)
	}

}
Output:

int, 10

func CanBackquote

func CanBackquote(s string) bool

CanBackquote сообщает, может ли строка s быть представлена без изменений в виде однострочной строки с обратными кавычками, не содержащей контрольных символов, кроме табуляции.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	fmt.Println(strconv.CanBackquote("Fran & Freddie's Diner ☺"))
	fmt.Println(strconv.CanBackquote("`can't backquote this`"))

}
Output:

true
false

func FormatBool

func FormatBool(b bool) string

FormatBool возвращает «true» или «false» в зависимости от значения b.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	v := true
	s := strconv.FormatBool(v)
	fmt.Printf("%T, %v\n", s, s)

}
Output:

string, true

func FormatComplex

func FormatComplex(c complex128, fmt byte, prec, bitSize int) string

FormatComplex преобразует комплексное число c в строку вида (a+bi), где a и b — действительная и мнимая части, отформатированные в соответствии с форматом fmt и точностью prec.

Формат fmt и точность prec имеют то же значение, что и в FormatFloat. Он округляет результат, предполагая, что исходное значение было получено из комплексного значения bitSize бит, которое должно быть 64 для complex64 и 128 для complex128.

func FormatFloat

func FormatFloat(f float64, fmt byte, prec, bitSize int) string

FormatFloat преобразует число с плавающей запятой f в строку в соответствии с форматом fmt и точностью prec. Оно округляет результат, предполагая, что исходное значение было получено из значения с плавающей запятой bitSize бит (32 для float32, 64 для float64).

Формат fmt может быть одним из следующих

  • “b” (-ddddp±ddd, двоичный экспонента),
  • “e” (-d.dddde±dd, десятичный экспонент),
  • “E” (-d.ddddE±dd, десятичный экспонент),
  • “f” (-ddd.dddd, без экспонента),
  • “g” („e“ для больших экспонентов, „f“ в остальных случаях),
  • “G” („E“ для больших экспонентов, „f“ в остальных случаях),
  • “x” (-0xd.ddddp±ddd, шестнадцатеричная дробь и двоичный показатель), или
  • “X” (-0Xd.ddddP±ddd, шестнадцатеричная дробь и двоичный показатель).

Точность prec контролирует количество цифр (исключая экспоненту), выводимых форматами „e“, „E“, „f“, „g“, „G“, „x“ и „X“. Для „e“, „E“, „f“, „x“ и „X“ это количество цифр после десятичной запятой. Для „g“ и „G“ это максимальное количество значимых цифр (конечные нули удаляются). Специальная точность -1 использует минимальное количество цифр, необходимое для того, чтобы ParseFloat вернул точное значение f. Экспонента записывается в виде десятичного целого числа; для всех форматов, кроме „b“, она будет состоять как минимум из двух цифр.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	v := 3.1415926535

	s32 := strconv.FormatFloat(v, 'E', -1, 32)
	fmt.Printf("%T, %v\n", s32, s32)

	s64 := strconv.FormatFloat(v, 'E', -1, 64)
	fmt.Printf("%T, %v\n", s64, s64)

	// fmt.Println uses these arguments to print floats
	fmt64 := strconv.FormatFloat(v, 'g', -1, 64)
	fmt.Printf("%T, %v\n", fmt64, fmt64)

}
Output:

string, 3.1415927E+00
string, 3.1415926535E+00
string, 3.1415926535

func FormatInt

func FormatInt(i int64, base int) string

FormatInt возвращает строковое представление i в заданной базе, для 2 <= base <= 36. Результат использует строчные буквы от «a» до «z» для значений цифр >= 10.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	v := int64(-42)

	s10 := strconv.FormatInt(v, 10)
	fmt.Printf("%T, %v\n", s10, s10)

	s16 := strconv.FormatInt(v, 16)
	fmt.Printf("%T, %v\n", s16, s16)

}
Output:

string, -42
string, -2a

func FormatUint

func FormatUint(i uint64, base int) string

FormatUint возвращает строковое представление i в заданной базе, для 2 <= base <= 36. В результате для значений цифр >= 10 используются строчные буквы от „a“ до „z“.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	v := uint64(42)

	s10 := strconv.FormatUint(v, 10)
	fmt.Printf("%T, %v\n", s10, s10)

	s16 := strconv.FormatUint(v, 16)
	fmt.Printf("%T, %v\n", s16, s16)

}
Output:

string, 42
string, 2a

func IsGraphic

func IsGraphic(r rune) bool

IsGraphic сообщает, определена ли руна как графический символ в Unicode. К таким символам относятся буквы, знаки, цифры, знаки препинания, символы и пробелы из категорий L, M, N, P, S и Zs.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	shamrock := strconv.IsGraphic('☘')
	fmt.Println(shamrock)

	a := strconv.IsGraphic('a')
	fmt.Println(a)

	bel := strconv.IsGraphic('\007')
	fmt.Println(bel)

}
Output:

true
true
false

func IsPrint

func IsPrint(r rune) bool

IsPrint сообщает, определена ли руна как печатная в Go, с тем же определением, что и unicode.IsPrint: буквы, цифры, знаки препинания, символы и пробел ASCII.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	c := strconv.IsPrint('\u263a')
	fmt.Println(c)

	bel := strconv.IsPrint('\007')
	fmt.Println(bel)

}
Output:

true
false

func Itoa

func Itoa(i int) string

Itoa эквивалентна FormatInt(int64(i), 10).

package main

import (
	"fmt"
	"strconv"
)

func main() {
	i := 10
	s := strconv.Itoa(i)
	fmt.Printf("%T, %v\n", s, s)

}
Output:

string, 10

func ParseBool

func ParseBool(str string) (bool, error)

ParseBool возвращает булево значение, представленное строкой. Принимает 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Любое другое значение возвращает ошибку.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	v := "true"
	if s, err := strconv.ParseBool(v); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}

}
Output:

bool, true

func ParseComplex

func ParseComplex(s string, bitSize int) (complex128, error)

ParseComplex преобразует строку s в комплексное число с точностью, указанной в bitSize: 64 для complex64 или 128 для complex128. Когда bitSize=64, результат по-прежнему имеет тип complex128, но его можно преобразовать в complex64 без изменения его значения.

Число, представленное s, должно иметь вид N, Ni или N±Ni, где N обозначает число с плавающей запятой, распознаваемое ParseFloat, а i — мнимую составляющую. Если второе N не имеет знака, между двумя составляющими требуется знак +, как указано ±. Если второе N равно NaN, допускается только знак +. Форма может быть заключена в скобки и не может содержать пробелов. Результирующее комплексное число состоит из двух компонентов, преобразованных ParseFloat.

Ошибки, возвращаемые ParseComplex, имеют конкретный тип *NumError и включают err.Num = s.

Если s не является синтаксически правильным, ParseComplex возвращает err.Err = ErrSyntax.

Если s является синтаксически правильным, но любой из компонентов находится на расстоянии более 1/2 ULP от наибольшего числа с плавающей запятой заданного размера компонента, ParseComplex возвращает err.Err = ErrRange и c = ±Inf для соответствующего компонента.

func ParseFloat

func ParseFloat(s string, bitSize int) (float64, error)

ParseFloat преобразует строку s в число с плавающей запятой с точностью, указанной в bitSize: 32 для float32 или 64 для float64. Когда bitSize=32, результат по-прежнему имеет тип float64, но его можно преобразовать в float32 без изменения его значения.

ParseFloat принимает десятичные и шестнадцатеричные числа с плавающей запятой, как определено синтаксисом Go для литералов с плавающей запятой. Если s имеет правильную форму и близка к действительному числу с плавающей запятой, ParseFloat возвращает ближайшее число с плавающей запятой, округленное с использованием беспристрастного округления IEEE754. (При разборе шестнадцатеричного числа с плавающей запятой округление происходит только в том случае, если в шестнадцатеричном представлении больше битов, чем может поместиться в мантиссе).

Ошибки, которые возвращает ParseFloat, имеют конкретный тип *NumError и включают err.Num = s.

Если s не является синтаксически правильно сформированным, ParseFloat возвращает err.Err = ErrSyntax.

Если s является синтаксически правильным, но находится на расстоянии более 1/2 ULP от наибольшего числа с плавающей запятой заданного размера, ParseFloat возвращает f = ±Inf, err.Err = ErrRange.

ParseFloat распознает строку «NaN» и строки «Inf» и «Infinity» (возможно со знаком) как соответствующие специальные значения с плавающей запятой. При сопоставлении он игнорирует регистр.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	v := "3.1415926535"
	if s, err := strconv.ParseFloat(v, 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseFloat(v, 64); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseFloat("NaN", 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	// ParseFloat is case insensitive
	if s, err := strconv.ParseFloat("nan", 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseFloat("inf", 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseFloat("+Inf", 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseFloat("-Inf", 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseFloat("-0", 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseFloat("+0", 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}

}
Output:

float64, 3.1415927410125732
float64, 3.1415926535
float64, NaN
float64, NaN
float64, +Inf
float64, +Inf
float64, -Inf
float64, -0
float64, 0

func ParseInt

func ParseInt(s string, base int, bitSize int) (i int64, err error)

ParseInt интерпретирует строку s в заданной базе (от 0 до 36) и размере бита (от 0 до 64) и возвращает соответствующее значение i.

Строка может начинаться с ведущего знака: «+» или «-».

Если аргумент base равен 0, истинная база подразумевается префиксом строки, следующим за знаком (если он есть): 2 для «0b», 8 для «0» или «0o», 16 для «0x» и 10 в остальных случаях. Кроме того, только для аргумента base 0 допускаются символы подчеркивания, как определено синтаксисом Go для целочисленных литералов.

Аргумент bitSize указывает тип целого числа, в который должен помещаться результат. Размеры битов 0, 8, 16, 32 и 64 соответствуют int, int8, int16, int32 и int64. Если bitSize меньше 0 или больше 64, возвращается ошибка.

Ошибки, которые возвращает ParseInt, имеют конкретный тип *NumError и включают err.Num = s. Если s пустое или содержит недопустимые цифры, err.Err = ErrSyntax, и возвращаемое значение равно 0; если значение, соответствующее s, не может быть представлено целым числом с знаком заданного размера, err.Err = ErrRange, и возвращаемое значение является целым числом максимальной величины с соответствующим bitSize и знаком.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	v32 := "-354634382"
	if s, err := strconv.ParseInt(v32, 10, 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseInt(v32, 16, 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}

	v64 := "-3546343826724305832"
	if s, err := strconv.ParseInt(v64, 10, 64); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseInt(v64, 16, 64); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}

}
Output:

int64, -354634382
int64, -3546343826724305832

func ParseUint

func ParseUint(s string, base int, bitSize int) (uint64, error)

ParseUint похож на ParseInt, но для чисел без знака.

Префикс знака не допускается.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	v := "42"
	if s, err := strconv.ParseUint(v, 10, 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseUint(v, 10, 64); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}

}
Output:

uint64, 42
uint64, 42

func Quote

func Quote(s string) string

Quote возвращает строковый литерал Go в двойных кавычках, представляющий s. Возвращаемая строка использует экранирующие последовательности Go (\t, \n, \xFF, \u0100) для управляющих символов и непечатаемых символов, как определено в IsPrint.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// This string literal contains a tab character.
	s := strconv.Quote(`"Fran & Freddie's Diner	☺"`)
	fmt.Println(s)

}
Output:

"\"Fran & Freddie's Diner\t☺\""

func QuoteRune

func QuoteRune(r rune) string

QuoteRune возвращает строковый литерал Go в одинарных кавычках, представляющий руну. Возвращаемая строка использует экранирующие последовательности Go (\t, \n, \xFF, \u0100) для управляющих символов и непечатаемых символов, как определено в IsPrint. Если r не является действительным кодом Unicode, он интерпретируется как символ замены Unicode U+FFFD.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	s := strconv.QuoteRune('☺')
	fmt.Println(s)

}
Output:

'☺'

func QuoteRuneToASCII

func QuoteRuneToASCII(r rune) string

QuoteRuneToASCII возвращает однострочный литерал символа Go, представляющий руну. Возвращаемая строка использует экранирующие последовательности Go (\t, \n, \xFF, \u0100) для не-ASCII символов и непечатаемых символов, как определено в IsPrint. Если r не является действительным кодом Unicode, он интерпретируется как заменяющий символ Unicode U+FFFD.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	s := strconv.QuoteRuneToASCII('☺')
	fmt.Println(s)

}
Output:

'\u263a'

func QuoteRuneToGraphic

func QuoteRuneToGraphic(r rune) string

QuoteRuneToGraphic возвращает однострочный литерал Go, представляющий руну. Если руна не является графическим символом Unicode, как определено IsGraphic, возвращаемая строка будет использовать экранирующие последовательности Go (\t, \n, \xFF, \u0100). Если r не является допустимым кодовым пунктом Unicode, он интерпретируется как символ замены Unicode U+FFFD.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	s := strconv.QuoteRuneToGraphic('☺')
	fmt.Println(s)

	s = strconv.QuoteRuneToGraphic('\u263a')
	fmt.Println(s)

	s = strconv.QuoteRuneToGraphic('\u000a')
	fmt.Println(s)

	s = strconv.QuoteRuneToGraphic('	') // tab character
	fmt.Println(s)

}
Output:

'☺'
'☺'
'\n'
'\t'

func QuoteToASCII

func QuoteToASCII(s string) string

QuoteToASCII возвращает строковый литерал Go в двойных кавычках, представляющий s. Возвращаемая строка использует экранирующие последовательности Go (\t, \n, \xFF, \u0100) для не-ASCII символов и непечатаемых символов, как определено IsPrint.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// This string literal contains a tab character.
	s := strconv.QuoteToASCII(`"Fran & Freddie's Diner	☺"`)
	fmt.Println(s)

}
Output:

"\"Fran & Freddie's Diner\t\u263a\""

func QuoteToGraphic

func QuoteToGraphic(s string) string

QuoteToGraphic возвращает строковый литерал Go в двойных кавычках, представляющий s. Возвращаемая строка оставляет графические символы Unicode, как определено IsGraphic, без изменений и использует экранирующие последовательности Go (\t, \n, \xFF, \u0100) для неграфических символов.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	s := strconv.QuoteToGraphic("☺")
	fmt.Println(s)

	// This string literal contains a tab character.
	s = strconv.QuoteToGraphic("This is a \u263a	\u000a")
	fmt.Println(s)

	s = strconv.QuoteToGraphic(`" This is a ☺ \n "`)
	fmt.Println(s)

}
Output:

"☺"
"This is a ☺\t\n"
"\" This is a ☺ \\n \""

func QuotedPrefix

func QuotedPrefix(s string) (string, error)

QuotedPrefix возвращает строку в кавычках (как понимает Unquote) в префиксе s. Если s не начинается с действительной строки в кавычках, QuotedPrefix возвращает ошибку.

package main

import (
	"fmt"
	"strconv"
)

func main() {
	s, err := strconv.QuotedPrefix("not a quoted string")
	fmt.Printf("%q, %v\n", s, err)
	s, err = strconv.QuotedPrefix("\"double-quoted string\" with trailing text")
	fmt.Printf("%q, %v\n", s, err)
	s, err = strconv.QuotedPrefix("`or backquoted` with more trailing text")
	fmt.Printf("%q, %v\n", s, err)
	s, err = strconv.QuotedPrefix("'\u263a' is also okay")
	fmt.Printf("%q, %v\n", s, err)

}
Output:

"", invalid syntax
"\"double-quoted string\"", <nil>
"`or backquoted`", <nil>
"'☺'", <nil>

func Unquote

func Unquote(s string) (string, error)

Unquote интерпретирует s как строковый литерал Go в одинарных, двойных или обратных кавычках, возвращая значение строки, которое s заключает в кавычки. (Если s заключено в одинарные кавычки, это будет символьный литерал Go; Unquote возвращает соответствующую односимвольную строку. Для пустого символьного литерала Unquote возвращает пустую строку.)

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	s, err := strconv.Unquote("You can't unquote a string without quotes")
	fmt.Printf("%q, %v\n", s, err)
	s, err = strconv.Unquote("\"The string must be either double-quoted\"")
	fmt.Printf("%q, %v\n", s, err)
	s, err = strconv.Unquote("`or backquoted.`")
	fmt.Printf("%q, %v\n", s, err)
	s, err = strconv.Unquote("'\u263a'") // single character only allowed in single quotes
	fmt.Printf("%q, %v\n", s, err)
	s, err = strconv.Unquote("'\u2639\u2639'")
	fmt.Printf("%q, %v\n", s, err)

}
Output:

"", invalid syntax
"The string must be either double-quoted", <nil>
"or backquoted.", <nil>
"☺", <nil>
"", invalid syntax

func UnquoteChar

func UnquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error)

UnquoteChar декодирует первый символ или байт в экранированной строке или символьном литерале, представленном строкой s. Он возвращает четыре значения:

value, декодированный код Unicode или значение байта; multibyte, булево значение, указывающее, требует ли декодированный символ многобайтового представления UTF-8; tail, остаток строки после символа; и ошибка, которая будет nil, если символ синтаксически валиден. Второй аргумент, quote, указывает тип анализируемого литерала и, следовательно, какой экранированный символ кавычки разрешен. Если установлен в одиночную кавычку, он разрешает последовательность \„ и запрещает неэкранированный “. Если установлен в двойную кавычку, он разрешает \« и запрещает неэкранированный ». Если установлен в ноль, он не разрешает ни один из экранированных символов и позволяет обоим символам кавычки появляться неэкранированными.

Пример
package main

import (
	"fmt"
	"log"
	"strconv"
)

func main() {
	v, mb, t, err := strconv.UnquoteChar(`\"Fran & Freddie's Diner\"`, '"')
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("value:", string(v))
	fmt.Println("multibyte:", mb)
	fmt.Println("tail:", t)

}
Output:

value: "
multibyte: false
tail: Fran & Freddie's Diner\"

12.2 - Тип NumError пакета strconv

Ошибка NumError фиксирует неудачное преобразование.

type NumError

type NumError struct {
Func string // сбойная функция (ParseBool, ParseInt, ParseUint, ParseFloat, ParseComplex)
Num string // входные данные
Err error // причина сбоя преобразования (например, ErrRange, ErrSyntax и т. д.)
}

Ошибка NumError фиксирует неудачное преобразование.

Пример
package main

import (
	"fmt"
	"strconv"
)

func main() {
	str := "Not a number"
	if _, err := strconv.ParseFloat(str, 64); err != nil {
		e := err.(*strconv.NumError)
		fmt.Println("Func:", e.Func)
		fmt.Println("Num:", e.Num)
		fmt.Println("Err:", e.Err)
		fmt.Println(err)
	}

}
Output:

Func: ParseFloat
Num: Not a number
Err: invalid syntax
strconv.ParseFloat: parsing "Not a number": invalid syntax

func (*NumError) Error

func (e *NumError) Error() string

func (*NumError) Unwrap

func (e *NumError) Unwrap() error

13 - Описание пакета error языка программирования Go

Пакет errors реализует функции для манипуляции ошибками

Функция New создает ошибки, содержащие только текстовое сообщение.

Ошибка e оборачивает другую ошибку, если тип e имеет один из методов:

Unwrap() error
Unwrap() []error

Если e.Unwrap() возвращает не nil ошибку w или срез, содержащий w, то говорится, что e оборачивает w. Возвращение nil ошибки из e.Unwrap() указывает на то, что e не оборачивает никакую ошибку. Недопустимо, чтобы метод Unwrap() возвращал срез, содержащий nil значение ошибки.

Легкий способ создать обернутые ошибки — вызвать fmt.Errorf и применить шаблон %w к аргументу ошибки:

wrapsErr := fmt.Errorf("... %w ...", ..., err, ...)

Последовательное разворачивание ошибки создает дерево. Функции Is и As исследуют дерево ошибки, проверяя сначала саму ошибку, а затем дерево каждого из ее потомков по очереди (префиксный, глубинный обход).

См. https://go.dev/blog/go1.13-errors для более глубокого обсуждения философии оборачивания и когда следует оборачивать.

Функция Is исследует дерево своего первого аргумента в поисках ошибки, совпадающей со вторым. Она сообщает, найдено ли совпадение. Ее следует использовать вместо простых проверок на равенство:

if errors.Is(err, fs.ErrExist)

лучше, чем

if err == fs.ErrExist

потому что первая будет успешной, если err оборачивает io/fs.ErrExist.

Функция As исследует дерево своего первого аргумента в поисках ошибки, которую можно присвоить второму аргументу, который должен быть указателем. Если она успешна, она выполняет присваивание и возвращает true. В противном случае она возвращает false. Форма

var perr *fs.PathError
if errors.As(err, &perr) {
    fmt.Println(perr.Path)
}

лучше, чем

if perr, ok := err.(*fs.PathError); ok {
    fmt.Println(perr.Path)
}

потому что первая будет успешной, если err оборачивает *io/fs.PathError.

Пример
package main

import (
	"fmt"
	"time"
)

// MyError is an error implementation that includes a time and message.
type MyError struct {
	When time.Time
	What string
}

func (e MyError) Error() string {
	return fmt.Sprintf("%v: %v", e.When, e.What)
}

func oops() error {
	return MyError{
		time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
		"the file system has gone away",
	}
}

func main() {
	if err := oops(); err != nil {
		fmt.Println(err)
	}
}
Output:

1989-03-15 22:30:00 +0000 UTC: the file system has gone away

Переменные

var ErrUnsupported = New("unsupported operation")

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

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

errors.Is(err, errors.ErrUnsupported)

либо путем прямого оборачивания ErrUnsupported, либо путем реализации метода Is.

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

Функции

func As

func As(err error, target any) bool

As находит первую ошибку в дереве err, которая совпадает с target, и если такая найдена, устанавливает target в значение этой ошибки и возвращает true. В противном случае возвращает false.

Дерево состоит из err самой по себе, за которой следуют ошибки, полученные путем многократного вызова ее метода Unwrap() error или Unwrap() []error. Когда err оборачивает несколько ошибок, As проверяет err, за которой следует глубинный обход ее потомков.

Ошибка совпадает с target, если конкретное значение ошибки может быть присвоено значению, на которое указывает target, или если у ошибки есть метод As(any) bool, такой что As(target) возвращает true. В последнем случае метод As отвечает за установку target.

Тип ошибки может предоставить метод As, чтобы его можно было рассматривать как если бы он был другим типом ошибки.

As вызывает панику, если target не является не nil указателем на тип, реализующий error, или на любой интерфейсный тип.

Пример
package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	if _, err := os.Open("non-existing"); err != nil {
		var pathError *fs.PathError
		if errors.As(err, &pathError) {
			fmt.Println("Failed at path:", pathError.Path)
		} else {
			fmt.Println(err)
		}
	}

}
Output:

Failed at path: non-existing

func Is

func Is(err, target error) bool

Is сообщает, совпадает ли какая-либо ошибка в дереве err с target.

Дерево состоит из err самой по себе, за которой следуют ошибки, полученные путем многократного вызова ее метода Unwrap() error или Unwrap() []error. Когда err оборачивает несколько ошибок, Is проверяет err, за которой следует глубинный обход ее потомков.

Ошибка считается совпадающей с целевой, если она равна этой целевой или если она реализует метод Is(error) bool, такой что Is(target) возвращает true.

Тип ошибки может предоставить метод Is, чтобы его можно было рассматривать как эквивалентный существующей ошибке. Например, если MyError определяет

func (m MyError) Is(target error) bool { return target == fs.ErrExist }

то Is(MyError{}, fs.ErrExist) возвращает true. См. syscall.Errno.Is для примера в стандартной библиотеке. Метод Is должен только поверхностно сравнивать err и цель и не вызывать Unwrap для обоих.

Пример
package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	if _, err := os.Open("non-existing"); err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			fmt.Println("file does not exist")
		} else {
			fmt.Println(err)
		}
	}

}
Output:

file does not exist

func Join

func Join(errs ...error) error

Join возвращает ошибку, которая оборачивает переданные ошибки. Любые значения nil ошибки отбрасываются. Join возвращает nil, если все значения в errs равны nil. Ошибка форматируется как конкатенация строк, полученных путем вызова метода Error каждого элемента errs, с новой строкой между каждой строкой.

Не nil ошибка, возвращенная Join, реализует метод Unwrap() []error.

Пример:

package main

import (
	"errors"
	"fmt"
)

func main() {
	err1 := errors.New("first error")
	err2 := errors.New("second error")
	err3 := errors.New("third error")

	combinedErr := errors.Join(err1, err2, err3)
	fmt.Println(combinedErr)
}

func New

func New(text string) error

New возвращает ошибку, которая форматируется как заданный текст. Каждый вызов New возвращает уникальное значение ошибки, даже если текст идентичен.

Пример:

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := errors.New("something went wrong")
	fmt.Println(err)
}
Пример
package main

import (
	"fmt"
)

func main() {
	const name, id = "bimmler", 17
	err := fmt.Errorf("user %q (id %d) not found", name, id)
	if err != nil {
		fmt.Print(err)
	}
}
Output:

user "bimmler" (id 17) not found

func Unwrap

func Unwrap(err error) error

Unwrap возвращает основную ошибку, обернутую в err, если она существует. Если err не оборачивает никакую ошибку, Unwrap возвращает nil.

Пример
package main

import (
	"errors"
	"fmt"
)

func main() {
	err1 := errors.New("error1")
	err2 := fmt.Errorf("error2: [%w]", err1)
	fmt.Println(err2)
	fmt.Println(errors.Unwrap(err2))
}
Output:

error2: [error1]
error1

14 - Описание пакета iter языка программирования Go

Пакет iter предоставляет базовые определения и операции, связанные с итераторами по последовательностям.

Итераторы

type (
	Seq[V any]     func(yield func(V) bool)
	Seq2[K, V any] func(yield func(K, V) bool)
)

Seq2 представляет последовательность пар значений, традиционно ключ-значение или индекс-значение.

Yield возвращает true, если итератор должен продолжить с следующим элементом последовательности, и false, если он должен остановиться.

Например, maps.Keys возвращает итератор, который производит последовательность ключей карты m, реализованную следующим образом:

func Keys[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[K] {
	return func(yield func(K) bool) {
		for k := range m {
			if !yield(k) {
				return
			}
		}
	}
}

Дополнительные примеры можно найти в блоге Go: Range Over Function Types.

Функции итераторов чаще всего вызываются в цикле range, как в следующем примере:

func PrintAll[V any](seq iter.Seq[V]) {
	for v := range seq {
		fmt.Println(v)
	}
}

Конвенции именования

Функции и методы итераторов называются в зависимости от последовательности, по которой они проходят:

// All возвращает итератор по всем элементам в s.
func (s *Set[V]) All() iter.Seq[V]

Метод итератора на типе коллекции традиционно называется All, потому что он итерирует последовательность всех значений в коллекции.

Для типа, содержащего несколько возможных последовательностей, имя итератора может указывать, какая последовательность предоставляется:

// Cities возвращает итератор по основным городам страны.
func (c *Country) Cities() iter.Seq[*City]

// Languages возвращает итератор по официальным языкам страны.
func (c *Country) Languages() iter.Seq[string]

Если итератор требует дополнительной конфигурации, конструкторная функция может принимать дополнительные аргументы конфигурации:

// Scan возвращает итератор по парам ключ-значение с min ≤ key ≤ max.
func (m *Map[K, V]) Scan(min, max K) iter.Seq2[K, V]

// Split возвращает итератор по (возможно, пустым) подстрокам s,
// разделенным sep.
func Split(s, sep string) iter.Seq[string]

Когда существует несколько возможных порядков итерации, имя метода может указывать на этот порядок:

// All возвращает итератор по списку от начала к концу.
func (l *List[V]) All() iter.Seq[V]

// Backward возвращает итератор по списку от конца к началу.
func (l *List[V]) Backward() iter.Seq[V]

// Preorder возвращает итератор по всем узлам синтаксического дерева
// под (и включая) указанный корень, в глубину, в порядке preorder,
// посещая родительский узел перед его дочерними узлами.
func Preorder(root Node) iter.Seq[Node]

Одноразовые итераторы

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

Некоторые итераторы нарушают это правило, предоставляя возможность пройти по последовательности только один раз. Эти “одноразовые итераторы” обычно сообщают значения из потока данных, который нельзя перемотать назад для начала. Повторный вызов итератора после ранней остановки может продолжить поток, но повторный вызов после завершения последовательности не вернет никаких значений. Документационные комментарии для функций или методов, возвращающих одноразовые итераторы, должны документировать этот факт:

// Lines возвращает итератор по строкам, прочитанным из r.
// Он возвращает одноразовый итератор.
func (r *Reader) Lines() iter.Seq[string]

Извлечение значений

Функции и методы, которые принимают или возвращают итераторы, должны использовать стандартные типы Seq или Seq2, чтобы обеспечить совместимость с циклами range и другими адаптерами итераторов. Стандартные итераторы можно рассматривать как “push-итераторы”, которые передают значения в функцию yield.

Иногда цикл range не является наиболее естественным способом потребления значений последовательности. В этом случае Pull преобразует стандартный push-итератор в “pull-итератор”, который можно вызвать для извлечения одного значения за раз из последовательности. Pull запускает итератор и возвращает пару функций — next и stop, которые возвращают следующее значение из итератора и останавливают его соответственно.

Например:

// Pairs возвращает итератор по последовательным парам значений из seq.
func Pairs[V any](seq iter.Seq[V]) iter.Seq2[V, V] {
	return func(yield func(V, V) bool) {
		next, stop := iter.Pull(seq)
		defer stop()
		for {
			v1, ok1 := next()
			if !ok1 {
				return
			}
			v2, ok2 := next()
			// Если ok2 равно false, v2 должно быть
			// нулевым значением; yield одна последняя пара.
			if !yield(v1, v2) {
				return
			}
			if !ok2 {
				return
			}
		}
	}
}

Если клиенты не потребляют последовательность до конца, они должны вызвать stop, что позволяет функции итератора завершить выполнение и вернуться. Как показано в примере, традиционный способ обеспечить это — использовать defer.

Использование стандартной библиотеки

Некоторые пакеты в стандартной библиотеке предоставляют API на основе итераторов, наиболее заметными из которых являются пакеты maps и slices. Например, maps.Keys возвращает итератор по ключам карты, в то время как slices.Sorted собирает значения итератора в срез, сортирует их и возвращает срез. Чтобы итерировать по отсортированным ключам карты, можно использовать следующий код:

for _, key := range slices.Sorted(maps.Keys(m)) {
	...
}

Мутация

Итераторы предоставляют только значения последовательности, но не прямой способ их изменения. Если итератор хочет предоставить механизм для изменения последовательности во время итерации, обычное решение — определить тип позиции с дополнительными операциями и затем предоставить итератор по позициям.

Например, реализация дерева может предоставить:

// Positions возвращает итератор по позициям в последовательности.
func (t *Tree[V]) Positions() iter.Seq[*Pos]

// Pos представляет позицию в последовательности.
// Он действителен только во время вызова yield, в который он передается.
type Pos[V any] struct { ... }

// Value возвращает значение в курсоре.
func (p *Pos[V]) Value() V

// Delete удаляет значение в этой точке итерации.
func (p *Pos[V]) Delete()

// Set изменяет значение v в курсоре.
func (p *Pos[V]) Set(v V)

И затем клиент может удалить boring значения из дерева, используя:

for p := range t.Positions() {
	if boring(p.Value()) {
		p.Delete()
	}
}

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

Функции

func Pull

func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func())

Pull преобразует последовательность итератора в стиле “push” seq в итератор в стиле “pull”, доступный через две функции next и stop.

Next возвращает следующее значение в последовательности и булево значение, указывающее, является ли значение действительным. Когда последовательность завершена, next возвращает нулевое значение V и false. Вызов next после достижения конца последовательности или после вызова stop является допустимым. Эти вызовы будут продолжать возвращать нулевое значение V и false.

Stop завершает итерацию. Его нужно вызвать, когда вызывающий не заинтересован в следующих значениях, и next еще не сигнализировал о завершении последовательности (с возвратом false). Вызов stop несколько раз и после того, как next уже вернул false, является допустимым. Обычно вызывающие должны использовать “defer stop()”.

Ошибкой является вызов next или stop из нескольких горутин одновременно.

Если итератор вызывает панику во время вызова next (или stop), то next (или stop) сам вызывает панику с тем же значением.

func Pull2

func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func())

Pull2 преобразует последовательность итератора в стиле “push” seq в итератор в стиле “pull”, доступный через две функции next и stop.

Next возвращает следующую пару в последовательности и булево значение, указывающее, является ли пара действительной. Когда последовательность завершена, next возвращает пару нулевых значений и false. Вызов next после достижения конца последовательности или после вызова stop является допустимым. Эти вызовы будут продолжать возвращать пару нулевых значений и false.

Stop завершает итерацию. Его нужно вызвать, когда вызывающий не заинтересован в следующих значениях, и next еще не сигнализировал о завершении последовательности (с возвратом false). Вызов stop несколько раз и после того, как next уже вернул false, является допустимым. Обычно вызывающие должны использовать “defer stop()”.

Ошибкой является вызов next или stop из нескольких горутин одновременно.

Если итератор вызывает панику во время вызова next (или stop), то next (или stop) сам вызывает панику с тем же значением.

Типы

type Seq

type Seq[V any] func(yield func(V) bool)

Seq — это итератор по последовательностям отдельных значений. Когда вызывается как seq(yield), seq вызывает yield(v) для каждого значения v в последовательности, останавливаясь раньше, если yield возвращает false. Подробнее см. в документации пакета iter.

type Seq2

type Seq2[K, V any] func(yield func(K, V) bool)

Seq2 — это итератор по последовательностям пар значений, наиболее часто ключ-значение. Когда вызывается как seq(yield), seq вызывает yield(k, v) для каждой пары (k, v) в последовательности, останавливаясь раньше, если yield возвращает false. Подробнее см. в документации пакета iter.

15 - Пакет net встроенных функций и типов языка Go

Пакет net предоставляет переносимый интерфейс для сетевого ввода-вывода, включая TCP/IP, UDP, разрешение доменных имен и сокеты доменов Unix.

Пакет обеспечивает доступ к примитивам низкоуровневых сетей, большинству клиентов понадобится только базовый интерфейс, предоставляемый функциями Dial, Listen и Accept и связанными интерфейсами Conn и Listener. Пакет crypto/tls использует те же интерфейсы и похожие функции Dial и Listen.

Функция Dial подключается к серверу:

conn, err := net.Dial("tcp", "golang.org:80")
if err != nil {
	// handle error
}
fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
status, err := bufio.NewReader(conn).ReadString('\n')
// ...

Функция Listen создает серверы:

ln, err := net.Listen("tcp", ":8080")
if err != nil {
	// handle error
}
for {
	conn, err := ln.Accept()
	if err != nil {
		// handle error
	}
	go handleConnection(conn)
}

Разрешение имен (Name Resolution)

Метод разрешения доменных имен (DNS) в пакете net зависит от операционной системы. Функции, такие как Dial, используют разрешение имен косвенно, а функции вроде LookupHost и LookupAddr делают это напрямую.

На Unix-системах

Ресолвер имеет два варианта работы:

  1. Чистый Go-ресолвер – отправляет DNS-запросы напрямую к серверам, указанным в /etc/resolv.conf.
  2. CGO-ресолвер – использует системные вызовы C (getaddrinfo, getnameinfo).

Чистый Go-ресолвер предпочтительнее, потому что:

  • Заблокированный DNS-запрос потребляет только горутину (легковесный поток Go).
  • Заблокированный вызов C занимает системный поток ОС (более затратно).

Когда используется CGO-ресолвер?

  • На системах, где запрещены прямые DNS-запросы (например, macOS).
  • Если заданы переменные окружения:
    • LOCALDOMAIN (даже пустая),
    • RES_OPTIONS,
    • HOSTALIASES (не пустые),
    • ASR_CONFIG (только OpenBSD).
  • Если /etc/resolv.conf или /etc/nsswitch.conf требуют функциональности, не реализованной в Go.

Лимит на CGO-запросы
Во всех системах (кроме Plan 9) действует ограничение в 500 одновременных CGO-запросов, чтобы избежать исчерпания системных потоков.


Управление выбором ресолвера

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

export GODEBUG=netdns=go    # принудительно использовать чистый Go-ресолвер
export GODEBUG=netdns=cgo   # принудительно использовать нативный (CGO) ресолвер

Также можно задать поведение при сборке через теги:

  • netgoтолько Go-ресолвер (CGO полностью отключен).
  • netcgo – оба ресолвера, но CGO имеет приоритет (можно переопределить через GODEBUG=netdns=go).

Отладочная информация
Если установить GODEBUG=netdns=1, ресолвер будет выводить отладочную информацию. Можно комбинировать:

export GODEBUG=netdns=go+1  # принудительно Go + отладка

EDNS0 и проблемы с роутерами
Go-ресолвер по умолчанию отправляет EDNS0-заголовок, чтобы запрашивать большие DNS-пакеты. Это может вызывать ошибки на некоторых модемах/роутерах. Отключить:

export GODEBUG=netedns0=0

Особенности на разных ОС

  • macOS:
    • Если Go-код собран с -buildmode=c-archive, для линковки в C-программу требуется флаг -lresolv.
  • Plan 9:
    • Ресолвер всегда использует /net/cs и /net/dns.
  • Windows (Go 1.18.x и старше):
    • Всегда используются C-функции (GetAddrInfo, DnsQuery).

Ключевые термины

  • Ресолвер (Resolver) – механизм преобразования доменных имен в IP-адреса.
  • CGO – вызовы C-кода из Go.
  • EDNS0 – расширение DNS, позволяющее увеличить размер пакета.
  • GODEBUG – переменная окружения для настройки поведения Go-рантайма.

15.1 - Функции пакета net языка Go

Функции пакета net

func JoinHostPort

func JoinHostPort(host, port string) string

JoinHostPort объединяет хост и порт в сетевой адрес вида «хост:порт». Если хост содержит двоеточие, как в буквенных IPv6-адресах, то JoinHostPort возвращает «[хост]:порт».

Описание параметров хоста и порта см. в func Dial.

func LookupAddr

func LookupAddr(addr string) (names []string, err error)

LookupAddr выполняет обратный поиск по заданному адресу, возвращая список имен, сопоставленных с этим адресом.

Возвращаемые имена проверяются на правильность форматирования доменных имен в формате представления. Если в ответе содержатся недопустимые имена, эти записи отфильтровываются, и вместе с оставшимися результатами, если таковые имеются, возвращается ошибка.

При использовании хост-резольвера библиотеки C будет возвращено не более одного результата. Чтобы обойти хост-резольвер, используйте собственный резольвер.

LookupAddr внутренне использует context.Background; чтобы указать контекст, используйте Resolver.LookupAddr.

func LookupCNAME

func LookupCNAME(host string) (cname string, err error)

LookupCNAME возвращает каноническое имя для заданного хоста. Те, кому не важно каноническое имя, могут вызвать LookupHost или LookupIP напрямую; оба они позаботятся о разрешении канонического имени в процессе поиска.

Каноническое имя - это конечное имя после следования нуля или более записей CNAME. LookupCNAME не возвращает ошибку, если хост не содержит записей DNS «CNAME», при условии, что хост разрешается в адресные записи.

Возвращаемое каноническое имя проверяется на то, что оно является правильно отформатированным доменным именем формата представления.

LookupCNAME внутренне использует context.Background; чтобы указать контекст, используйте Resolver.LookupCNAME.

func LookupHost

func LookupHost(host string) (addrs []string, err error)

LookupHost ищет заданный хост с помощью локального резольвера. Он возвращает фрагмент адресов этого хоста.

Внутри LookupHost используется context.Background; чтобы указать контекст, используйте Resolver.LookupHost.

func LookupPort

func LookupPort(network, service string) (port int, err error)

LookupPort ищет порт для заданной сети и сервиса.

Внутри LookupPort используется context.Background; чтобы указать контекст, используйте Resolver.LookupPort.

func LookupTXT

func LookupTXT(name string) ([]string, error)

LookupTXT возвращает записи DNS TXT для заданного доменного имени.

Если DNS TXT-запись содержит несколько строк, они объединяются в одну строку.

Внутри LookupTXT используется context.Background; чтобы указать контекст, используйте Resolver.LookupTXT.

func ParseCIDR

func ParseCIDR(s string) (IP, *IPNet, error)

ParseCIDR анализирует s как IP-адрес в нотации CIDR и длину префикса, например «192.0.2.0/24» или «2001:db8::/32», как определено в RFC 4632 и RFC 4291.

Он возвращает IP-адрес и сеть, подразумеваемую IP-адресом и длиной префикса. Например, ParseCIDR(«192.0.2.1/24») возвращает IP-адрес 192.0.2.1 и сеть 192.0.2.0/24.

Пример
package main

import (
	"fmt"
	"log"
	"net"
)

func main() {
	ipv4Addr, ipv4Net, err := net.ParseCIDR("192.0.2.1/24")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(ipv4Addr)
	fmt.Println(ipv4Net)

	ipv6Addr, ipv6Net, err := net.ParseCIDR("2001:db8:a0b:12f0::1/32")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(ipv6Addr)
	fmt.Println(ipv6Net)

}
Output:

192.0.2.1
192.0.2.0/24
2001:db8:a0b:12f0::1
2001:db8::/32

func Pipe ¶

func Pipe() (Conn, Conn)

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

func SplitHostPort

func SplitHostPort(hostport string) (host, port string, err error)

SplitHostPort разделяет сетевой адрес вида «host:port», «host%zone:port», «[host]:port» или «[host%zone]:port» на host или host%zone и port.

Буквальный IPv6-адрес в hostport должен быть заключен в квадратные скобки, как в «[::1]:80», «[::1%lo0]:80».

Описание параметра hostport, а также результатов хоста и порта см. в разделе func Dial.

15.2 - Типы и методы пакета net языка Go

Типы и методы пакета net

type Addr

type Addr интерфейс {
	Network() string // имя сети (например, "tcp", "udp")
	String() string // строковая форма адреса (например, "192.0.2.1:25", "[2001:db8::1]:80")
}

Addr представляет адрес конечной точки сети.

Два метода [Addr.Network] и [Addr.String] условно возвращают строки, которые могут быть переданы в качестве аргументов в Dial, но точная форма и значение строк зависят от реализации.

func InterfaceAddrs

func InterfaceAddrs() ([]Addr, error)

InterfaceAddrs возвращает список адресов одноадресных интерфейсов системы.

Возвращаемый список не идентифицирует связанный с ним интерфейс; для получения более подробной информации используйте Interfaces и Interface.Addrs.

type AddrError

type AddrError struct {
	строка Err
	Addr string
}

func (*AddrError) Error

func (e *AddrError) Error() string

func (*AddrError) Temporary

func (e *AddrError) Temporary() bool

func (*AddrError) Timeout

func (e *AddrError) Timeout() bool

type Buffers

type Buffers [][]byte

Buffers содержит ноль или более байтов для записи.

На некоторых машинах для определенных типов соединений это оптимизируется в специфическую для ОС операцию пакетной записи (например, “writev”).

func (*Buffers) Read

func (v *Буферы) Read(p []byte) (n int, err error)

Чтение из буферов.

Read реализует io.Reader для буферов.

Read изменяет фрагмент v, а также v[i] для 0 <= i < len(v), но не изменяет v[i][j] для любых i, j.

func (*Buffers) WriteTo

func (v *Буферы) WriteTo(w io.Writer) (n int64, err error)

WriteTo записывает содержимое буферов в w.

WriteTo реализует io.WriterTo для буферов.

WriteTo изменяет фрагмент v, а также v[i] для 0 <= i < len(v), но не изменяет v[i][j] для любых i, j.

type Conn ¶

type Conn interface {
	// Read считывает данные из соединения.
	// Чтение можно сделать тайм-аутом и вернуть ошибку по истечении фиксированного
	// времени; см. SetDeadline и SetReadDeadline.
	Read(b []byte) (n int, err error)

	// Write записывает данные в соединение.
	// Запись может быть сделана так, чтобы тайм-аут и возврат ошибки происходили через фиксированное
	// времени; см. SetDeadline и SetWriteDeadline.
	Write(b []byte) (n int, err error)

	// Close закрывает соединение.
	// Любые заблокированные операции чтения или записи будут разблокированы и вернут ошибку.
	Close() error

	// LocalAddr возвращает адрес локальной сети, если он известен.
	LocalAddr() Addr

	// RemoteAddr возвращает адрес удаленной сети, если он известен.
	RemoteAddr() Addr

	// SetDeadline устанавливает крайние сроки чтения и записи, связанные
	// с данным соединением. Это эквивалентно вызову обеих функций.
	// SetReadDeadline и SetWriteDeadline.
	//
	// Крайний срок - это абсолютное время, по истечении которого операции ввода-вывода
	// прекращаются, а не блокируются. Крайний срок применяется ко всем будущим
	// и ожидающим операциям ввода-вывода, а не только к непосредственно следующему вызову
	// чтения или записи. После превышения крайнего срока
	// соединение можно обновить, установив крайний срок в будущем.
	//
	// Если крайний срок превышен, вызов Read или Write или других
	// методы ввода/вывода вернут ошибку, обернутую в os.ErrDeadlineExceeded.
	// Это можно проверить с помощью errors.Is(err, os.ErrDeadlineExceeded).
	// Метод Timeout ошибки вернет true, но обратите внимание, что существуют
	// есть и другие возможные ошибки, для которых метод Timeout
	// вернет true, даже если срок не был превышен.
	//
	// Таймаут простоя может быть реализован путем многократного продления
	// крайнего срока после успешных вызовов чтения или записи.
	//
	// Нулевое значение t означает, что операции ввода-вывода не будут выполняться по тайм-ауту.
	SetDeadline(t time.Time) error

	// SetReadDeadline устанавливает крайний срок для будущих вызовов Read
	// и любого заблокированного в данный момент вызова Read.
	// Нулевое значение t означает, что операции Read не будут завершаться.
	SetReadDeadline(t time.Time) error

	// SetWriteDeadline устанавливает крайний срок для будущих вызовов записи
	// и любого заблокированного в данный момент вызова Write.
	// Даже если запись завершится, она может вернуть n > 0, указывая на то, что
	// часть данных была успешно записана.
	// Нулевое значение t означает, что Write не будет завершаться.
	SetWriteDeadline(t time.Time) error
}

Conn - это общее потоково-ориентированное сетевое соединение.

Несколько горутинов могут вызывать методы на Conn одновременно.

func Dial

func Dial(network, address string) (Conn, error)

Dial подключается к адресу в указанной сети.

Известные сети: “tcp”, “tcp4” (только для IPv4), “tcp6” (только для IPv6), “udp”, “udp4” (только для IPv4), “udp6” (только для IPv6), “ip”, “ip4” (только для IPv4), “ip6” (только для IPv6), “unix”, “unixgram” и “unixpacket”.

Для сетей TCP и UDP адрес имеет вид “хост:порт”. Хост должен быть буквальным IP-адресом или именем хоста, которое может быть преобразовано в IP-адрес. Порт должен быть буквенным номером порта или именем службы. Если хост является литеральным IPv6-адресом, он должен быть заключен в квадратные скобки, как в “[2001:db8::1]:80” или “[fe80::1%zone]:80”. Зона определяет область действия буквального IPv6-адреса, как определено в RFC 4007. Функции JoinHostPort и SplitHostPort манипулируют парой хост-порт в таком виде. При использовании TCP, когда хост разрешается в несколько IP-адресов, Dial будет пробовать каждый IP-адрес по порядку, пока один не будет успешным.

Примеры:

Dial("tcp", "golang.org:http")
Dial("tcp", "192.0.2.1:http")
Dial("tcp", "198.51.100.1:80")
Dial("udp", "[2001:db8::1]:domain")
Dial("udp", "[fe80::1%lo0]:53")
Dial("tcp", ":80")

Для IP-сетей сеть должна быть “ip”, “ip4” или “ip6”, за которой следует двоеточие и литеральный номер протокола или имя протокола, а адрес имеет вид “host”. Хост должен быть буквальным IP-адресом или буквальным IPv6-адресом с зоной. От каждой операционной системы зависит, как она поведет себя с номером протокола, не являющимся общеизвестным, например “0” или “255”.

Примеры:

Dial("ip4:1", "192.0.2.1")
Dial("ip6:ipv6-icmp", "2001:db8::1")
Dial("ip6:58", "fe80::1%lo0")

Для сетей TCP, UDP и IP, если хост пуст или является буквальным неопределенным IP-адресом, как в “:80”, “0.0.0.0:80” или “[::]:80” для TCP и UDP, “”, “0.0.0.0” или “::” для IP, предполагается локальная система.

Для сетей Unix адрес должен быть путем к файловой системе.

func DialTimeout

func DialTimeout(network, address string, timeout time.Duration) (Conn, error)

DialTimeout действует как Dial, но берет таймаут.

Таймаут включает разрешение имен, если это необходимо. Если используется TCP и хост в параметре адреса разрешается в несколько IP-адресов, таймаут распределяется на каждый последовательный набор, так что каждому дается соответствующая доля времени для соединения.

Описание параметров сети и адреса см. в func Dial.

func FileConn

func FileConn(f *os.File) (c Conn, err error)

FileConn возвращает копию сетевого соединения, соответствующего открытому файлу f. Ответственность за закрытие f по завершении работы лежит на вызывающей стороне. Закрытие c не влияет на f, а закрытие f не влияет на c.

type DNSConfigError

type DNSConfigError struct {
	Err error
}

DNSConfigError представляет ошибку чтения конфигурации DNS машины. (Больше не используется; сохранено для совместимости).

func (*DNSConfigError) Error

func (e *DNSConfigError) Error() string

func (*DNSConfigError) Temporary

func (e *DNSConfigError) Temporary() bool

func (*DNSConfigError) Timeout

func (e *DNSConfigError) Timeout() bool

func (*DNSConfigError) Unwrap

func (e *DNSConfigError) Unwrap() error

type DNSError

type DNSError struct {
	UnwrapErr error // ошибка, возвращаемая методом [DNSError.Unwrap], может быть nil
	Err string // описание ошибки
	Name string // искомое имя
	Server string // используемый сервер
	IsTimeout bool // если true, то таймаут истек; не все таймауты устанавливают это значение
	IsTemporary bool // если true, то ошибка временная; не все ошибки устанавливают это

	// IsNotFound устанавливается в true, если запрашиваемое имя не
	// не содержит записей запрашиваемого типа (данные не найдены),
	// или само имя не найдено (NXDOMAIN).
	IsNotFound bool
}

DNSError представляет ошибку поиска DNS.

func (*DNSError) Error

func (e *DNSError) Error() string

func (*DNSError) Temporary

func (e *DNSError) Temporary() bool

Temporary сообщает, является ли ошибка DNS временной. Это не всегда известно; поиск DNS может завершиться неудачей из-за временной ошибки и вернуть DNSError, для которого Temporary возвращает false.

func (*DNSError) Timeout

func (e *DNSError) Timeout() bool

Таймаут сообщает о том, что поиск DNS завершился по таймеру. Это не всегда известно; поиск DNS может завершиться неудачей из-за таймаута и вернуть DNSError, для которого Timeout возвращает false.

func (*DNSError) Unwrap

func (e *DNSError) Unwrap() error

Unwrap возвращает e.UnwrapErr.

type Dialer

type Dialer struct {
	// Timeout - это максимальное количество времени, в течение которого набор будет ждать
	// завершения соединения. Если также задано значение Deadline, то соединение может завершиться
	// раньше.
	//
	// По умолчанию таймаут отсутствует.
	//
	// При использовании TCP и наборе имени хоста с несколькими IP
	// адресов, тайм-аут может быть разделен между ними.
	//
	// С таймаутом или без него операционная система может установить
	// свой собственный более ранний тайм-аут. Например, тайм-ауты TCP
	// часто составляет около 3 минут.
	Timeout time.Duration

	// Крайний срок - это абсолютный момент времени, после которого набор номера
	// завершится неудачей. Если установлен таймаут, то отказ может произойти раньше.
	// Ноль означает отсутствие крайнего срока, или зависит от операционной системы.
	// как в случае с параметром Timeout.
	Deadline time.Time

	// LocalAddr - локальный адрес, который следует использовать при наборе
	// адреса. Адрес должен быть совместимого типа для
	// набираемой сети.
	// Если nil, локальный адрес выбирается автоматически.
	LocalAddr Addr

	// В DualStack ранее была включена поддержка RFC 6555 Fast Fallback
	// поддержка, также известная как "Happy Eyeballs", при которой IPv4
	// пробует вскоре, если IPv6 оказывается неправильно сконфигурированным и
	// зависает.
	//
	// Утративший силу: Fast Fallback включен по умолчанию. Чтобы
	// отключить, установите FallbackDelay в отрицательное значение.
	DualStack bool

	// FallbackDelay задает время ожидания перед тем, как
	// порождения соединения RFC 6555 Fast Fallback. То есть это
	// это время ожидания успеха IPv6, прежде чем
	// предположить, что IPv6 неправильно сконфигурирован, и вернуться к
	// IPv4.
	//
	// Если значение равно нулю, то по умолчанию используется задержка 300 мс.
	// Отрицательное значение отключает поддержку быстрого отката.
	FallbackDelay time.Duration

	// KeepAlive задает интервал между попытками сохранить активное сетевое соединение.
	// зондами активного сетевого соединения.
	//
	// KeepAlive игнорируется, если KeepAliveConfig.Enable равен true.
	//
	// Если значение равно нулю, то зонды keep-alive отправляются со значением по умолчанию.
	// (в настоящее время 15 секунд), если это поддерживается протоколом и операционной // системой.
	// системой. Сетевые протоколы или операционные системы, которые.
	// не поддерживают keep-alive, игнорируют это поле.
	// Если значение отрицательное, то запросы keep-alive отключаются.
	KeepAlive time.Duration

	// KeepAliveConfig определяет конфигурацию зонда keep-alive
	// для активного сетевого соединения, если оно поддерживается
	// протоколом и операционной системой.
	//
	// Если KeepAliveConfig.Enable равен true, то зонды keep-alive включены.
	// Если KeepAliveConfig.Enable ложно и KeepAlive отрицательно,
	// зонды keep-alive отключены.
	KeepAliveConfig KeepAliveConfig

	// Resolver опционально указывает альтернативный резолвер для использования.
	Resolver *Resolver

	// Cancel - необязательный канал, закрытие которого указывает, что
	// набор должен быть отменен. Не все типы циферблатов поддерживают
	// отмену.
	//
	// Утратил актуальность: Вместо этого используйте DialContext.
	Cancel <-chan struct{}

	// Если Control не nil, то вызывается после создания сетевого
	// соединения, но до фактического набора номера.
	//
	// Параметры сети и адреса, передаваемые в функцию Control, не являются
	// обязательно те, которые передаются в Dial. Вызов Dial с сетями TCP
	// приведет к вызову функции Control с параметрами "tcp4" или "tcp6",
	// UDP-сети станут "udp4" или "udp6", IP-сети - "ip4" или "ip6",
	// а другие известные сети передаются как есть.
	//
	// Контроль игнорируется, если ControlContext не равен nil.
	Control func(network, address string, c syscall.RawConn) error

	// Если ControlContext не равен nil, то эта функция вызывается после создания сетевого
	// соединения, но до фактического набора номера.
	//
	// Параметры сети и адреса, передаваемые в функцию ControlContext, не являются
	// обязательно те, что переданы в Dial. Вызов Dial с сетями TCP
	// приведет к вызову функции ControlContext с параметрами "tcp4" или "tcp6",
	// UDP-сети станут "udp4" или "udp6", IP-сети - "ip4" или "ip6",
	// а другие известные сети передаются как есть.
	//
	// Если ControlContext не равен nil, Control игнорируется.
	ControlContext func(ctx context.Context, network, address string, c syscall.RawConn) error
	// содержит отфильтрованные или неэкспонированные поля
}

Dialer содержит опции для соединения с адресом.

Нулевое значение для каждого поля эквивалентно дозвону без этой опции. Поэтому набор номера с нулевым значением Dialer эквивалентен простому вызову функции Dial.

Безопасно вызывать методы Dialer одновременно.

Пример
package main

import (
	"context"
	"log"
	"net"
	"time"
)

func main() {
	var d net.Dialer
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	conn, err := d.DialContext(ctx, "tcp", "localhost:12345")
	if err != nil {
		log.Fatalf("Failed to dial: %v", err)
	}
	defer conn.Close()

	if _, err := conn.Write([]byte("Hello, World!")); err != nil {
		log.Fatal(err)
	}
}
Пример Unix
package main

import (
	"context"
	"log"
	"net"
	"time"
)

func main() {
	// DialUnix does not take a context.Context parameter. This example shows
	// how to dial a Unix socket with a Context. Note that the Context only
	// applies to the dial operation; it does not apply to the connection once
	// it has been established.
	var d net.Dialer
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	d.LocalAddr = nil // if you have a local addr, add it here
	raddr := net.UnixAddr{Name: "/path/to/unix.sock", Net: "unix"}
	conn, err := d.DialContext(ctx, "unix", raddr.String())
	if err != nil {
		log.Fatalf("Failed to dial: %v", err)
	}
	defer conn.Close()
	if _, err := conn.Write([]byte("Hello, socket!")); err != nil {
		log.Fatal(err)
	}
}

func (*Dialer) Dial

func (d *Dialer) Dial(network, address string) (Conn, error)

Dial соединяется с адресом в указанной сети.

Описание параметров сети и адреса см. в func Dial.

Dial внутренне использует context.Background; чтобы указать контекст, используйте Dialer.DialContext.

func (*Dialer) DialContext

func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error)

DialContext соединяется с адресом в названной сети, используя предоставленный контекст.

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

При использовании TCP, когда хост в параметре адреса разрешается в несколько сетевых адресов, таймаут набора номера (из d.Timeout или ctx) распределяется на каждый последовательный набор, так что каждому дается соответствующая доля времени для соединения. Например, если у хоста есть 4 IP-адреса и таймаут составляет 1 минуту, то на соединение с каждым отдельным адресом будет дано 15 секунд, прежде чем будет выполнена попытка соединения со следующим адресом.

Описание параметров сети и адреса см. в func Dial.

func (*Dialer) MultipathTCP

func (d *Dialer) MultipathTCP() bool

MultipathTCP сообщает, будет ли использоваться MPTCP.

Этот метод не проверяет, поддерживается ли MPTCP операционной системой или нет.

func (*Dialer) SetMultipathTCP

func (d *Dialer) SetMultipathTCP(use bool)

SetMultipathTCP направляет методы Dial на использование или неиспользование MPTCP, если это поддерживается операционной системой. Этот метод отменяет системное значение по умолчанию и настройку GODEBUG=multipathtcp=…, если таковая имеется.

Если MPTCP недоступен на хосте или не поддерживается сервером, методы набора будут возвращаться к TCP.

type Error

type Error interface {
	error
	Timeout() bool // Является ли ошибка тайм-аутом?

	// Исправлено: Временные ошибки не имеют четкого определения.
	// Большинство "временных" ошибок - это таймауты, а редкие исключения вызывают удивление.
	// Не используйте этот метод.
	Temporary() bool
}

Error представляет сетевую ошибку.

type Flags

type Flags uint
const (
	FlagUp Flags = 1 << iota // интерфейс административно поднят
	FlagBroadcast // интерфейс поддерживает возможность широковещательного доступа
	FlagLoopback // интерфейс является интерфейсом обратной связи
	FlagPointToPoint // интерфейс принадлежит к каналу "точка-точка
	FlagMulticast // интерфейс поддерживает возможность многоадресного доступа
	FlagRunning // интерфейс находится в рабочем состоянии
)

func (Flags) String

func (f Flags) String() string

type HardwareAddr

type HardwareAddr []byte

HardwareAddr представляет физический аппаратный адрес.

func ParseMAC

func ParseMAC(s string) (hw HardwareAddr, err error)

ParseMAC анализирует s как IEEE 802 MAC-48, EUI-48, EUI-64 или 20-октетный адрес канального уровня IP поверх InfiniBand, используя один из следующих форматов:

00:00:5e:00:53:01
02:00:5e:10:00:00:00:01
00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01
00-00-5e-00-53-01
02-00-5e-10-00-00-00-01
00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01
0000.5e00.5301
0200.5e10.0000.0001
0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001

func (HardwareAddr) String

func (a HardwareAddr) String() string

type IP

type IP []byte

IP - это один IP-адрес, представляющий собой фрагмент байтов. Функции этого пакета принимают на вход 4-байтовые (IPv4) или 16-байтовые (IPv6) фрагменты.

Обратите внимание, что в этой документации обращение к IP-адресу как к IPv4-адресу или IPv6-адресу является семантическим свойством адреса, а не просто длиной байтового фрагмента: 16-байтовый фрагмент все еще может быть IPv4-адресом.

func IPv4

func IPv4(a, b, c, d byte) IP

IPv4 возвращает IP-адрес (в 16-байтовой форме) IPv4-адреса a.b.c.d.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	fmt.Println(net.IPv4(8, 8, 8, 8))

}
Output:

8.8.8.8

func LookupIP

func LookupIP(host string) ([]IP, error)

LookupIP ищет хост с помощью локального резольвера. Он возвращает фрагмент IPv4 и IPv6 адресов этого хоста.

func ParseIP

func ParseIP(s string) IP

ParseIP разбирает s как IP-адрес, возвращая результат. Строка s может быть в десятичном точечном формате IPv4 (“192.0.2.1”), IPv6 (“2001:db8::68”) или IPv4-mapped IPv6 ("::ffff:192.0.2.1"). Если s не является корректным текстовым представлением IP-адреса, ParseIP возвращает nil. Возвращаемый адрес всегда имеет размер 16 байт, IPv4-адреса возвращаются в IPv4-сопоставленной IPv6-форме.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	fmt.Println(net.ParseIP("192.0.2.1"))
	fmt.Println(net.ParseIP("2001:db8::68"))
	fmt.Println(net.ParseIP("192.0.2"))

}
Output:

192.0.2.1
2001:db8::68
<nil>

func (IP) AppendText

func (ip IP) AppendText(b []byte) ([]byte, error)

AppendText реализует интерфейс encoding.TextAppender. Кодировка такая же, как и у IP.String, за одним исключением: Если len(ip) равно нулю, то ничего не добавляется.

func (IP) DefaultMask

func (ip IP) DefaultMask() IPMask

DefaultMask возвращает IP-маску по умолчанию для IP-адреса ip. Маски по умолчанию есть только у IPv4-адресов; DefaultMask возвращает nil, если ip не является действительным IPv4-адресом.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ip := net.ParseIP("192.0.2.1")
	fmt.Println(ip.DefaultMask())

}
Output:

ffffff00

func (IP) Equal

func (ip IP) Equal(x IP) bool

Equal сообщает, являются ли ip и x одним и тем же IP-адресом. IPv4-адрес и тот же адрес в форме IPv6 считаются равными.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv4DNS := net.ParseIP("8.8.8.8")
	ipv4Lo := net.ParseIP("127.0.0.1")
	ipv6DNS := net.ParseIP("0:0:0:0:0:FFFF:0808:0808")

	fmt.Println(ipv4DNS.Equal(ipv4DNS))
	fmt.Println(ipv4DNS.Equal(ipv4Lo))
	fmt.Println(ipv4DNS.Equal(ipv6DNS))

}
Output:

true
false
true

func (IP) IsGlobalUnicast

func (ip IP) IsGlobalUnicast() bool

IsGlobalUnicast сообщает, является ли ip глобальным одноадресным адресом.

Для идентификации глобальных одноадресных адресов используется идентификация типа адреса, как определено в RFC 1122, RFC 4632 и RFC 4291, за исключением адресов направленной широковещательной рассылки IPv4. Возвращает true, даже если ip находится в частном адресном пространстве IPv4 или локальном адресном пространстве IPv6 одноадресной рассылки.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6Global := net.ParseIP("2000::")
	ipv6UniqLocal := net.ParseIP("2000::")
	ipv6Multi := net.ParseIP("FF00::")

	ipv4Private := net.ParseIP("10.255.0.0")
	ipv4Public := net.ParseIP("8.8.8.8")
	ipv4Broadcast := net.ParseIP("255.255.255.255")

	fmt.Println(ipv6Global.IsGlobalUnicast())
	fmt.Println(ipv6UniqLocal.IsGlobalUnicast())
	fmt.Println(ipv6Multi.IsGlobalUnicast())

	fmt.Println(ipv4Private.IsGlobalUnicast())
	fmt.Println(ipv4Public.IsGlobalUnicast())
	fmt.Println(ipv4Broadcast.IsGlobalUnicast())

}
Output:

true
true
false
true
true
false

func (IP) IsInterfaceLocalMulticast

func (ip IP) IsInterfaceLocalMulticast() bool

IsInterfaceLocalMulticast сообщает, является ли ip интерфейсно-локальным многоадресным адресом.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6InterfaceLocalMulti := net.ParseIP("ff01::1")
	ipv6Global := net.ParseIP("2000::")
	ipv4 := net.ParseIP("255.0.0.0")

	fmt.Println(ipv6InterfaceLocalMulti.IsInterfaceLocalMulticast())
	fmt.Println(ipv6Global.IsInterfaceLocalMulticast())
	fmt.Println(ipv4.IsInterfaceLocalMulticast())

}
Output:

true
false
false

func (IP) IsLinkLocalMulticast

func (ip IP) IsLinkLocalMulticast() bool

IsLinkLocalMulticast сообщает, является ли ip адресом link-local multicast.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6LinkLocalMulti := net.ParseIP("ff02::2")
	ipv6LinkLocalUni := net.ParseIP("fe80::")
	ipv4LinkLocalMulti := net.ParseIP("224.0.0.0")
	ipv4LinkLocalUni := net.ParseIP("169.254.0.0")

	fmt.Println(ipv6LinkLocalMulti.IsLinkLocalMulticast())
	fmt.Println(ipv6LinkLocalUni.IsLinkLocalMulticast())
	fmt.Println(ipv4LinkLocalMulti.IsLinkLocalMulticast())
	fmt.Println(ipv4LinkLocalUni.IsLinkLocalMulticast())

}
Output:

true
false
true
false

func (IP) IsLinkLocalUnicast

func (ip IP) IsLinkLocalUnicast() bool

IsLinkLocalUnicast сообщает, является ли ip адресом link-local unicast.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6LinkLocalUni := net.ParseIP("fe80::")
	ipv6Global := net.ParseIP("2000::")
	ipv4LinkLocalUni := net.ParseIP("169.254.0.0")
	ipv4LinkLocalMulti := net.ParseIP("224.0.0.0")

	fmt.Println(ipv6LinkLocalUni.IsLinkLocalUnicast())
	fmt.Println(ipv6Global.IsLinkLocalUnicast())
	fmt.Println(ipv4LinkLocalUni.IsLinkLocalUnicast())
	fmt.Println(ipv4LinkLocalMulti.IsLinkLocalUnicast())

}
Output:

true
false
true
false

func (IP) IsLoopback

func (ip IP) IsLoopback() bool

IsLoopback сообщает, является ли ip адресом loopback.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6Lo := net.ParseIP("::1")
	ipv6 := net.ParseIP("ff02::1")
	ipv4Lo := net.ParseIP("127.0.0.0")
	ipv4 := net.ParseIP("128.0.0.0")

	fmt.Println(ipv6Lo.IsLoopback())
	fmt.Println(ipv6.IsLoopback())
	fmt.Println(ipv4Lo.IsLoopback())
	fmt.Println(ipv4.IsLoopback())

}
Output:

true
false
true
false

func (IP) IsMulticast

func (ip IP) IsMulticast() bool

IsMulticast сообщает, является ли ip адресом многоадресной рассылки.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6Multi := net.ParseIP("FF00::")
	ipv6LinkLocalMulti := net.ParseIP("ff02::1")
	ipv6Lo := net.ParseIP("::1")
	ipv4Multi := net.ParseIP("239.0.0.0")
	ipv4LinkLocalMulti := net.ParseIP("224.0.0.0")
	ipv4Lo := net.ParseIP("127.0.0.0")

	fmt.Println(ipv6Multi.IsMulticast())
	fmt.Println(ipv6LinkLocalMulti.IsMulticast())
	fmt.Println(ipv6Lo.IsMulticast())
	fmt.Println(ipv4Multi.IsMulticast())
	fmt.Println(ipv4LinkLocalMulti.IsMulticast())
	fmt.Println(ipv4Lo.IsMulticast())

}
Output:

true
true
false
true
true
false

func (IP) IsPrivate

func (ip IP) IsPrivate() bool

IsPrivate сообщает, является ли ip частным адресом, в соответствии с RFC 1918 (IPv4-адреса) и RFC 4193 (IPv6-адреса).

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6Private := net.ParseIP("fc00::")
	ipv6Public := net.ParseIP("fe00::")
	ipv4Private := net.ParseIP("10.255.0.0")
	ipv4Public := net.ParseIP("11.0.0.0")

	fmt.Println(ipv6Private.IsPrivate())
	fmt.Println(ipv6Public.IsPrivate())
	fmt.Println(ipv4Private.IsPrivate())
	fmt.Println(ipv4Public.IsPrivate())

}
Output:

true
false
true
false

func (IP) IsUnspecified

func (ip IP) IsUnspecified() bool

IsUnspecified сообщает, является ли ip неопределенным адресом, либо IPv4-адресом “0.0.0.0”, либо IPv6-адресом “::”.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6Unspecified := net.ParseIP("::")
	ipv6Specified := net.ParseIP("fe00::")
	ipv4Unspecified := net.ParseIP("0.0.0.0")
	ipv4Specified := net.ParseIP("8.8.8.8")

	fmt.Println(ipv6Unspecified.IsUnspecified())
	fmt.Println(ipv6Specified.IsUnspecified())
	fmt.Println(ipv4Unspecified.IsUnspecified())
	fmt.Println(ipv4Specified.IsUnspecified())

}
Output:

true
false
true
false

func (IP) MarshalText

func (ip IP) MarshalText() ([]byte, error)

MarshalText реализует интерфейс encoding.TextMarshaler. Кодировка та же, что возвращается IP.String, за одним исключением: Если len(ip) равен нулю, возвращается пустой фрагмент.

func (IP) Mask

func (ip IP) Mask(mask IPMask) IP

Mask возвращает результат маскирования IP-адреса ip с помощью маски.

func (IP) String

func (ip IP) String() string

String возвращает строковую форму IP-адреса ip. Возвращается одна из 4 форм:

  • ”, если ip имеет длину 0
  • десятичная точка (“192.0.2.1”), если ip - это IPv4 или IP4-сопоставленный IPv6-адрес.
  • IPv6 в соответствии с RFC 5952 (“2001:db8::1”), если ip - действительный IPv6-адрес
  • шестнадцатеричная форма ip без знаков препинания, если другие случаи не применимы.
Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6 := net.IP{0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	ipv4 := net.IPv4(10, 255, 0, 0)

	fmt.Println(ipv6.String())
	fmt.Println(ipv4.String())

}
Output:

fc00::
10.255.0.0

func (IP) To16

func (ip IP) To16() IP

To16 преобразует IP-адрес ip в 16-байтовое представление. Если ip не является IP-адресом (имеет неправильную длину), To16 возвращает nil.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6 := net.IP{0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	ipv4 := net.IPv4(10, 255, 0, 0)

	fmt.Println(ipv6.To16())
	fmt.Println(ipv4.To16())

}
Output:

fc00::
10.255.0.0

func (IP) To4

func (ip IP) To4() IP

To4 преобразует IPv4-адрес ip в 4-байтовое представление. Если ip не является IPv4-адресом, To4 возвращает nil.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	ipv6 := net.IP{0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	ipv4 := net.IPv4(10, 255, 0, 0)

	fmt.Println(ipv6.To4())
	fmt.Println(ipv4.To4())

}
Output:

<nil>
10.255.0.0

func (*IP) UnmarshalText

func (ip *IP) UnmarshalText(text []byte) error

UnmarshalText реализует интерфейс encoding.TextUnmarshaler. IP-адрес ожидается в форме, принимаемой ParseIP.

type IPAddr

type IPAddr struct {
	IP-АДРЕС
	Zone string // Зона масштабируемой адресации IPv6
}

IPAddr представляет адрес конечной точки IP.

func ResolveIPAddr

func ResolveIPAddr(network, address string) (*IPAddr, error)

ResolveIPAddr возвращает адрес конечной IP-точки.

Сеть должна быть именем IP-сети.

Если хост в параметре address не является литеральным IP-адресом, ResolveIPAddr преобразует адрес в адрес конечной точки IP. В противном случае он анализирует адрес как литеральный IP-адрес. В качестве параметра адреса может использоваться имя хоста, но это не рекомендуется, так как будет возвращен не более одного из IP-адресов имени хоста.

Описание параметров сети и адреса см. в разделе func Dial.

func (*IPAddr) Сеть

func (a *IPAddr) Network() string

Network возвращает сетевое имя адреса, “ip”.

func (*IPAddr) String

func (a *IPAddr) String() string

type IPConn

type IPConn struct {
	// содержит отфильтрованные или неотправленные поля
}

IPConn - это реализация интерфейсов Conn и PacketConn для сетевых IP-соединений.

func DialIP

func DialIP(network string, laddr, raddr *IPAddr) (*IPConn, error)

DialIP действует как Dial для IP-сетей.

Сеть должна быть именем IP-сети; подробности см. в func Dial.

Если laddr равен nil, автоматически выбирается локальный адрес. Если поле IP в raddr равно nil или является неопределенным IP-адресом, предполагается локальная система.

func ListenIP

func ListenIP(network string, laddr *IPAddr) (*IPConn, error)

ListenIP действует как ListenPacket для IP-сетей.

Сеть должна быть именем IP-сети; подробности см. в func Dial.

Если поле IP в laddr равно nil или неопределенному IP-адресу, ListenIP прослушивает все доступные IP-адреса локальной системы, кроме IP-адресов многоадресной рассылки.

func (*IPConn) Закрыть

func (c *IPConn) Close() error

Close закрывает соединение.

func (*IPConn) File

func (c *IPConn) File() (f *os.File, err error)

File возвращает копию базового файла os.File. Ответственность за закрытие f по завершении работы лежит на вызывающей стороне. Закрытие c не влияет на f, а закрытие f не влияет на c.

Дескриптор файла возвращаемого os.File отличается от дескриптора соединения. Попытка изменить свойства оригинала с помощью этого дубликата может привести к желаемому результату, а может и не привести.

func (*IPConn) LocalAddr

func (c *IPConn) LocalAddr() Addr

LocalAddr возвращает адрес локальной сети. Возвращаемый Addr является общим для всех вызовов LocalAddr, поэтому не следует его изменять.

func (*IPConn) Read

func (c *IPConn) Read(b []byte) (int, error)

Read реализует метод Conn Read.

func (*IPConn) ReadFrom

func (c *IPConn) ReadFrom(b []byte) (int, Addr, error)

ReadFrom реализует метод PacketConn ReadFrom.

func (*IPConn) ReadFromIP

func (c *IPConn) ReadFromIP(b []byte) (int, *IPAddr, error)

ReadFromIP действует как ReadFrom, но возвращает IPAddr.

func (*IPConn) ReadMsgIP

func (c *IPConn) ReadMsgIP(b, oob []byte) (n, oobn, flags int, addr *IPAddr, err error)

ReadMsgIP считывает сообщение из c, копируя полезную нагрузку в b и связанные с ней внеполосные данные в oob. Возвращается количество байт, скопированных в b, количество байт, скопированных в oob, флаги, которые были установлены для сообщения, и адрес источника сообщения.

Пакеты golang.org/x/net/ipv4 и golang.org/x/net/ipv6 могут быть использованы для манипулирования опциями сокетов IP-уровня в oob.

func (*IPConn) RemoteAddr

func (c *IPConn) RemoteAddr() Addr

RemoteAddr возвращает удаленный сетевой адрес. Возвращаемый Addr является общим для всех вызовов RemoteAddr, поэтому не изменяйте его.

func (*IPConn) SetDeadline

func (c *IPConn) SetDeadline(t time.Time) error

SetDeadline реализует метод Conn SetDeadline.

func (*IPConn) SetReadBuffer

func (c *IPConn) SetReadBuffer(bytes int) error

SetReadBuffer устанавливает размер буфера приема операционной системы, связанного с соединением.

func (*IPConn) SetReadDeadline

func (c *IPConn) SetReadDeadline(t time.Time) error

SetReadDeadline реализует метод Conn SetReadDeadline.

func (*IPConn) SetWriteBuffer

func (c *IPConn) SetWriteBuffer(bytes int) error

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

func (*IPConn) SetWriteDeadline

func (c *IPConn) SetWriteDeadline(t time.Time) error

SetWriteDeadline реализует метод Conn SetWriteDeadline.

func (*IPConn) SyscallConn

func (c *IPConn) SyscallConn() (syscall.RawConn, error)

SyscallConn возвращает необработанное сетевое соединение. Оно реализует интерфейс syscall.Conn.

func (*IPConn) Write

func (c *IPConn) Write(b []byte) (int, error)

Write реализует метод Conn Write.

func (*IPConn) WriteMsgIP

func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error)

WriteMsgIP записывает сообщение на addr через c, копируя полезную нагрузку из b и связанные с ней внеполосные данные из oob. Возвращается количество записанных байт полезной нагрузки и внеполосных данных.

Пакеты golang.org/x/net/ipv4 и golang.org/x/net/ipv6 могут быть использованы для манипулирования опциями сокетов IP-уровня в oob.

func (*IPConn) WriteTo

func (c *IPConn) WriteTo(b []byte, addr Addr) (int, error)

WriteTo реализует метод PacketConn WriteTo.

func (*IPConn) WriteToIP

func (c *IPConn) WriteToIP(b []байт, addr *IPAddr) (int, error)

WriteToIP действует подобно IPConn.WriteTo, но принимает IPAddr.

type IPMask

type IPMask []байт

IPMask - это битовая маска, которая может быть использована для манипулирования IP-адресами для IP-адресации и маршрутизации.

Подробнее см. в разделе type IPNet и func ParseCIDR.

func CIDRMask

func CIDRMask(ones, bits int) IPMask

CIDRMask возвращает IPMask, состоящую из битов ‘ones’ 1, за которыми следуют 0 до общей длины ‘bits’ бит. Для маски такой формы CIDRMask является обратной величиной к IPMask.Size.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	// This mask corresponds to a /31 subnet for IPv4.
	fmt.Println(net.CIDRMask(31, 32))

	// This mask corresponds to a /64 subnet for IPv6.
	fmt.Println(net.CIDRMask(64, 128))

}
Output:

fffffffe
ffffffffffffffff0000000000000000

func IPv4Mask

func IPv4Mask(a, b, c, d byte) IPMask

IPv4Mask возвращает IP-маску (в 4-байтовой форме) IPv4-маски a.b.c.d.

Пример
package main

import (
	"fmt"
	"net"
)

func main() {
	fmt.Println(net.IPv4Mask(255, 255, 255, 0))

}
Output:

ffffff00

func (IPMask) Size

func (m IPMask) Size() (ones, bits int)

Size возвращает количество ведущих единиц и общее количество битов в маске. Если маска не имеет канонической формы - за единицами следуют нули - то Size возвращает 0, 0.

func (IPMask) String

func (m IPMask) String() string

String возвращает шестнадцатеричную форму m, без знаков препинания.

type IPNet

type IPNet struct {
	IP IP // номер сети
	Mask IPMask // маска сети
}

IPNet представляет IP-сеть.

func (*IPNet) Contains

func (n *IPNet) Contains(ip IP) bool

Contains сообщает, включает ли сеть ip.

func (*IPNet) Network

func (n *IPNet) Network() string

Network возвращает сетевое имя адреса, “ip+net”.

func (*IPNet) String

func (n *IPNet) String() string

String возвращает CIDR-нотацию n, например “192.0.2.0/24” или “2001:db8::/48”, как определено в RFC 4632 и RFC 4291. Если маска не имеет канонической формы, возвращается строка, состоящая из IP-адреса, за которым следует символ косой черты и маска, выраженная в шестнадцатеричной форме без знаков препинания, например “198.51.100.0/c000ff00”.

type Interface

type Interface struct {
	Index int // положительное целое число, начинающееся с единицы, ноль никогда не используется
	MTU int // максимальная единица передачи
	Name string // например, "en0", "lo0", "eth0.100".
	HardwareAddr HardwareAddr // форма IEEE MAC-48, EUI-48 и EUI-64
	Flags Флаги // например, FlagUp, FlagLoopback, FlagMulticast
}

Интерфейс представляет собой сопоставление между именем и индексом сетевого интерфейса. Он также представляет информацию об объекте сетевого интерфейса.

func InterfaceByIndex

func InterfaceByIndex(index int) (*Interface, error)

InterfaceByIndex возвращает интерфейс, указанный индексом.

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

func InterfaceByName

func InterfaceByName(name string) (*Interface, error)

InterfaceByName возвращает интерфейс, указанный именем.

func Интерфейсы

func Interfaces() ([]Interface, error)

Interfaces возвращает список сетевых интерфейсов системы.

func (*Interface) Addrs

func (ifi *Интерфейс) Addrs() ([]Addr, error)

Addrs возвращает список адресов одноадресных интерфейсов для конкретного интерфейса.

func (*Interface) MulticastAddrs

func (ifi *Интерфейс) MulticastAddrs() ([]Addr, error)

MulticastAddrs возвращает список адресов многоадресной рассылки, объединенных групп для определенного интерфейса.

type InvalidAddrError

type InvalidAddrError string

func (InvalidAddrError) Error

func (e InvalidAddrError) Error() string

func (InvalidAddrError) Temporary

func (e InvalidAddrError) Temporary() bool

func (InvalidAddrError) Timeout

func (e InvalidAddrError) Timeout() bool

type KeepAliveConfig

type KeepAliveConfig struct {
	// Если Enable равно true, то зонды keep-alive включены.
	Enable bool

	// Idle - время, в течение которого соединение должно простаивать, прежде чем
	// отправки первого зонда keep-alive.
	// Если значение равно нулю, то по умолчанию используется значение 15 секунд.
	Idle time.Duration

	// Интервал - время между зондами keep-alive.
	// Если ноль, по умолчанию используется значение 15 секунд.
	Interval time.Duration

	// Count - максимальное количество зондов keep-alive, которые
	// может остаться без ответа до разрыва соединения.
	// Если значение равно нулю, то по умолчанию используется значение 9.
	Count int
}

KeepAliveConfig содержит параметры TCP keep-alive.

Если поля Idle, Interval или Count равны нулю, выбирается значение по умолчанию. Если поле отрицательно, соответствующая опция уровня сокета будет оставлена без изменений.

Обратите внимание, что до версии Windows 10 1709 ни установка Idle и Interval по отдельности, ни изменение Count (которое обычно равно 10) не поддерживаются. Поэтому, если вы хотите настроить параметры TCP keep-alive, рекомендуется установить неотрицательные значения Idle и Interval в сочетании с -1 для Count в старых версиях Windows. Напротив, если только один из Idle и Interval установлен в неотрицательное значение, другой будет установлен в значение по умолчанию, и в конечном итоге установите оба Idle и Interval в отрицательные значения, если вы хотите оставить их без изменений.

Обратите внимание, что Solaris и ее производные не поддерживают установку Interval в неотрицательное значение и Count в отрицательное значение, или наоборот.

type ListenConfig

type ListenConfig struct {
	// Если Control не nil, то вызывается после создания сетевого
	// соединения, но до его привязки к операционной системе.
	//
	// Параметры сети и адреса, передаваемые в функцию Control, не являются
	// обязательно те, которые передаются в функцию Listen. Вызов Listen с сетями TCP
	// приведет к вызову функции Control с параметрами "tcp4" или "tcp6",
	// UDP-сети станут "udp4" или "udp6", IP-сети - "ip4" или "ip6",
	// и другие известные сети передаются как есть.
	Control func(network, address string, c syscall.RawConn) error

	// KeepAlive задает период ожидания для сетевых
	// соединений, принимаемых этим слушателем.
	//
	// KeepAlive игнорируется, если KeepAliveConfig.Enable равен true.
	//
	// Если ноль, то keep-alive включается, если поддерживается протоколом
	// и операционной системой. Сетевые протоколы или операционные системы.
	// которые не поддерживают keep-alive, игнорируют это поле.
	// Если значение отрицательно, keep-alive отключены.
	KeepAlive time.Duration

	// KeepAliveConfig определяет конфигурацию зонда keep-alive
	// для активного сетевого соединения, если оно поддерживается
	// протоколом и операционной системой.
	//
	// Если KeepAliveConfig.Enable равен true, то зонды keep-alive включены.
	// Если KeepAliveConfig.Enable ложно и KeepAlive отрицательно,
	// зонды keep-alive отключены.
	KeepAliveConfig KeepAliveConfig
	// содержит отфильтрованные или неэкспонированные поля
}

ListenConfig содержит опции для прослушивания адреса.

func (*ListenConfig) Listen

func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error)

Listener сообщает об адресе локальной сети.

Описание параметров сети и адреса см. в разделе func Listen.

Аргумент ctx используется при разрешении адреса для прослушивания; он не влияет на возвращаемый error.

func (*ListenConfig) ListenPacket

func (lc *ListenConfig) ListenPacket(ctx context.Context, network, address string) (PacketConn, error)

ListenPacket сообщает об адресе локальной сети.

Описание параметров сети и адреса см. в func ListenPacket.

Аргумент ctx используется при разрешении адреса для прослушивания; он не влияет на возвращаемый слушатель.

func (*ListenConfig) MultipathTCP

func (lc *ListenConfig) MultipathTCP() bool

MultipathTCP сообщает, будет ли использоваться MPTCP.

Этот метод не проверяет, поддерживается ли MPTCP операционной системой или нет.

func (*ListenConfig) SetMultipathTCP

func (lc *ListenConfig) SetMultipathTCP(use bool)

SetMultipathTCP направляет метод Listen на использование или неиспользование MPTCP, если это поддерживается операционной системой. Этот метод отменяет системное значение по умолчанию и настройку GODEBUG=multipathtcp=…, если таковая имеется.

Если MPTCP недоступен на хосте или не поддерживается клиентом, метод Listen вернется к TCP.

type Listener ¶

type Listener interface {
	// Accept ожидает и возвращает следующее соединение со слушателем.
	Accept() (Conn, error)

	// Close закрывает слушателя.
	// Любые заблокированные операции Accept будут разблокированы и вернут ошибки.
	Close() error

	// Addr возвращает сетевой адрес слушателя.
	Addr() Addr
}

Listener - это общий сетевой слушатель для потоково-ориентированных протоколов.

Несколько горутинов могут одновременно вызывать методы одного Listener.

Пример
package main

import (
	"io"
	"log"
	"net"
)

func main() {
	// Listen on TCP port 2000 on all available unicast and
	// anycast IP addresses of the local system.
	l, err := net.Listen("tcp", ":2000")
	if err != nil {
		log.Fatal(err)
	}
	defer l.Close()
	for {
		// Wait for a connection.
		conn, err := l.Accept()
		if err != nil {
			log.Fatal(err)
		}
		// Handle the connection in a new goroutine.
		// The loop then returns to accepting, so that
		// multiple connections may be served concurrently.
		go func(c net.Conn) {
			// Echo all incoming data.
			io.Copy(c, c)
			// Shut down the connection.
			c.Close()
		}(conn)
	}
}

func FileListener

func FileListener(f *os.File) (ln Listener, err error)

FileListener возвращает копию сетевого слушателя, соответствующего открытому файлу f. Ответственность за закрытие ln по завершении работы лежит на вызывающей стороне. Закрытие ln не влияет на f, а закрытие f не влияет на ln.

func Listen

func Listen(network, address string) (Listener, error)

Listener сообщает об адресе локальной сети.

Сеть должна быть “tcp”, “tcp4”, “tcp6”, “unix” или “unixpacket”.

Для TCP-сетей, если параметр host в параметре address пуст или является буквальным неуказанным IP-адресом, Listen прослушивает все доступные одноадресные и одноадресные IP-адреса локальной системы. Чтобы использовать только IPv4, используйте сеть “tcp4”. В качестве адреса может использоваться имя хоста, но это не рекомендуется, поскольку в этом случае будет создан слушатель только для одного из IP-адресов хоста. Если порт в параметре адреса пустой или “0”, как в “127.0.0.1:” или “[::1]:0”, номер порта выбирается автоматически. Для определения выбранного порта можно использовать метод Addr Слушателя.

Описание параметров сети и адреса см. в func Dial.

Listen внутренне использует context.Background; чтобы указать контекст, используйте ListenConfig.Listen.

type MX

type MX struct {
	Host string
	Pref uint16
}

MX представляет собой одну запись DNS MX.

func LookupMX

func LookupMX(name string) ([]*MX, error)

LookupMX возвращает DNS MX-записи для заданного доменного имени, отсортированные по предпочтениям.

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

Внутри LookupMX используется context.Background; чтобы указать контекст, используйте Resolver.LookupMX.

type NS

type NS struct {
	Host string
}

NS представляет собой одну запись DNS NS.

func LookupNS

func LookupNS(name string) ([]*NS, error)

LookupNS возвращает записи DNS NS для заданного доменного имени.

Возвращаемые имена серверов имен проверяются на правильность форматирования доменных имен в формате представления. Если в ответе содержатся недопустимые имена, эти записи отфильтровываются, и вместе с оставшимися результатами возвращается ошибка, если таковая имеется.

Внутри LookupNS используется context.Background; чтобы указать контекст, используйте Resolver.LookupNS.

type OpError

type OpError struct {
	// Op - это операция, вызвавшая ошибку, например.
	// "чтение" или "запись".
	Op string

	// Net - тип сети, в которой произошла ошибка,
	// например, "tcp" или "udp6".
	Net string

	// Для операций, связанных с удаленным сетевым соединением, например.
	// набор, чтение или запись, Source - это соответствующий локальный
	// сетевой адрес.
	Source Addr 

	// Addr - это сетевой адрес, для которого произошла ошибка.
	// Для локальных операций, таких как Listen или SetDeadline, Addr - это
	// адрес локальной конечной точки, с которой производится манипуляция.
	// Для операций с удаленным сетевым соединением, таких как.
	// Dial, Read или Write, Addr - это удаленный адрес этого
	// соединения.
	Addr Addr

	// Err - это ошибка, произошедшая во время операции.
	// Метод Error паникует, если ошибка равна nil.
	Err error
}

OpError - это тип ошибки, обычно возвращаемый функциями пакета net. Он описывает операцию, тип сети и адрес ошибки.

func (*OpError) Error

func (e *OpError) Error() string

func (*OpError) Temporary

func (e *OpError) Temporary() bool

func (*OpError) Timeout

func (e *OpError) Timeout() bool

func (*OpError) Unwrap

func (e *OpError) Unwrap() error

type PacketConn

type PacketConn interface {
	// ReadFrom читает пакет из соединения,
	// копируя полезную нагрузку в p. Возвращается количество
	// байт, скопированных в p, и адрес возврата, который
	// был в пакете.
	// Возвращается количество прочитанных байт (0 <= n <= len(p))
	// и любую возникшую ошибку. Вызывающая сторона всегда должна обрабатывать
	// возвращенные n > 0 байт, прежде чем рассматривать ошибку err.
	// ReadFrom можно сделать так, чтобы тайм-аут и возврат ошибки происходили через
	// фиксированного лимита времени; см. SetDeadline и SetReadDeadline.
	ReadFrom(p []byte) (n int, addr Addr, err error)

	// WriteTo записывает пакет с полезной нагрузкой p в addr.
	// WriteTo можно сделать так, чтобы по истечении фиксированного времени она завершилась и вернула ошибку.
	// фиксированного лимита времени; см. SetDeadline и SetWriteDeadline.
	// На пакетно-ориентированных соединениях таймаут записи случается редко.
	WriteTo(p []byte, addr Addr) (n int, err error)

	// Close закрывает соединение.
	// Любые заблокированные операции ReadFrom или WriteTo будут разблокированы и вернут ошибки.
	Close() error

	// LocalAddr возвращает адрес локальной сети, если он известен.
	LocalAddr() Addr

	// SetDeadline устанавливает крайние сроки чтения и записи, связанные
	// с данным соединением. Это эквивалентно вызову обеих функций.
	// SetReadDeadline и SetWriteDeadline.
	//
	// Крайний срок - это абсолютное время, по истечении которого операции ввода-вывода
	// прекращаются, а не блокируются. Крайний срок применяется ко всем будущим
	// и ожидающим операциям ввода-вывода, а не только к непосредственно следующему вызову
	// чтения или записи. После превышения крайнего срока
	// соединение можно обновить, установив крайний срок в будущем.
	//
	// Если крайний срок превышен, вызов Read или Write или других
	// методы ввода/вывода вернут ошибку, обернутую в os.ErrDeadlineExceeded.
	// Это можно проверить с помощью errors.Is(err, os.ErrDeadlineExceeded).
	// Метод Timeout ошибки вернет true, но обратите внимание, что существуют
	// есть и другие возможные ошибки, для которых метод Timeout
	// вернет true, даже если срок не был превышен.
	//
	// Таймаут ожидания может быть реализован путем многократного продления
	// крайнего срока после успешных вызовов ReadFrom или WriteTo.
	//
	// Нулевое значение t означает, что операции ввода-вывода не будут выполняться по тайм-ауту.
	SetDeadline(t time.Time) error

	// SetReadDeadline устанавливает крайний срок для будущих вызовов ReadFrom
	// и любого заблокированного в данный момент вызова ReadFrom.
	// Нулевое значение t означает, что ReadFrom не будет завершаться по времени.
	SetReadDeadline(t time.Time) error

	// SetWriteDeadline устанавливает крайний срок для будущих вызовов WriteTo
	// и любого текущего заблокированного вызова WriteTo.
	// Даже если запись завершилась, она может вернуть n > 0, указывая на то, что
	// часть данных была успешно записана.
	// Нулевое значение t означает, что WriteTo не прервется.
	SetWriteDeadline(t time.Time) error
}

PacketConn - это общее сетевое соединение, ориентированное на пакеты.

Несколько горутинов могут вызывать методы на PacketConn одновременно.

func FilePacketConn

func FilePacketConn(f *os.File) (c PacketConn, err error)

FilePacketConn возвращает копию пакетного сетевого соединения, соответствующего открытому файлу f. Ответственность за закрытие f по завершении работы лежит на вызывающей стороне. Закрытие c не влияет на f, а закрытие f не влияет на c.

func ListenPacket

func ListenPacket(network, address string) (PacketConn, error)

ListenPacket сообщает адрес локальной сети.

Сеть должна быть “udp”, “udp4”, “udp6”, “unixgram” или IP-транспортом. IP-транспорт - это “ip”, “ip4” или “ip6”, за которым следует двоеточие и буквенный номер протокола или имя протокола, как в “ip:1” или “ip:icmp”.

Для UDP- и IP-сетей, если в параметре address значение host пустое или это буквальный неопределенный IP-адрес, ListenPacket прослушивает все доступные IP-адреса локальной системы, кроме IP-адресов многоадресной рассылки. Чтобы использовать только IPv4, используйте сеть “udp4” или “ip4:proto”. В качестве адреса можно использовать имя хоста, но это не рекомендуется, поскольку в этом случае будет создан слушатель только для одного из IP-адресов хоста. Если порт в параметре адреса пустой или “0”, как в “127.0.0.1:” или “[::1]:0”, номер порта выбирается автоматически. Для обнаружения выбранного порта можно использовать метод LocalAddr программы PacketConn.

Описание параметров сети и адреса см. в func Dial.

ListenPacket внутренне использует context.Background; чтобы указать контекст, используйте ListenConfig.ListenPacket.

type ParseError

type ParseError struct {
	// Тип - это тип ожидаемой строки, например.
	// "IP-адрес", "CIDR-адрес".
	Type string

	// Text - неправильно сформированная текстовая строка.
	Text string
}

ParseError - это тип ошибки синтаксического анализатора сетевых адресов.

func (*ParseError) Error

func (e *ParseError) Error() string

func (*ParseError) Temporary

func (e *ParseError) Temporary() bool

func (*ParseError) Timeout

func (e *ParseError) Timeout() bool

type Resolver

type Resolver struct {
	// PreferGo управляет тем, предпочтителен ли встроенный DNS-резольвер Go.
	// на платформах, где он доступен. Это эквивалентно настройке
	// GODEBUG=netdns=go, но относится только к этому резольверу.
	PreferGo bool

	// StrictErrors управляет поведением временных ошибок
	// (включая таймаут, ошибки сокетов и SERVFAIL) при использовании
	// встроенного в Go резолвера. Для запроса, состоящего из нескольких
	// подзапросов (например, поиск адреса A+AAA или переход по
	// список поиска DNS), эта опция заставляет такие ошибки прерывать
	// весь запрос, а не возвращать частичный результат. Эта опция
	// не включена по умолчанию, поскольку это может повлиять на совместимость
	// с резолверами, которые некорректно обрабатывают запросы AAAA.
	StrictErrors bool

	// Dial опционально задает альтернативный дозвон для использования
	// встроенным DNS-резольвером Go для создания TCP- и UDP-соединений
	// к службам DNS. Хост в параметре address будет
	// всегда будет буквальным IP-адресом, а не именем хоста, а
	// порт в параметре адреса будет буквальным номером порта
	// а не имя службы.
	// Если возвращаемый Conn также является PacketConn, отправленные и полученные DNS
	// сообщения должны соответствовать разделу 4.2.1 RFC 1035, "Использование UDP".
	// В противном случае DNS-сообщения, передаваемые через Conn, должны соответствовать
	// разделу 5 RFC 7766, "Выбор транспортного протокола".
	// Если nil, используется дозвонщик по умолчанию.
	Dial func(ctx context.Context, network, address string) (Conn, error)
	// содержит отфильтрованные или неэкспонированные поля
}

Резольвер ищет имена и номера.

Нулевой *Resolver эквивалентен нулевому Resolver.

func (*Resolver) LookupAddr

func (r *Resolver) LookupAddr(ctx context.Context, addr string) ([]string, error)

LookupAddr выполняет обратный поиск для заданного адреса, возвращая список имен, сопоставленных с этим адресом.

Возвращаемые имена проверяются на правильность форматирования доменных имен в формате представления. Если ответ содержит недопустимые имена, эти записи отфильтровываются, и вместе с оставшимися результатами, если таковые имеются, возвращается ошибка.

func (*Resolver) LookupCNAME

func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, error)

LookupCNAME возвращает каноническое имя для данного хоста. Пользователи, которым не важно каноническое имя, могут вызвать LookupHost или LookupIP напрямую; оба они позаботятся о разрешении канонического имени как части поиска.

Каноническое имя - это окончательное имя после следования нуля или более записей CNAME. LookupCNAME не возвращает ошибку, если хост не содержит записей DNS “CNAME”, при условии, что хост разрешается в адресные записи.

Возвращаемое каноническое имя проверяется на то, что оно является правильно отформатированным доменным именем формата представления.

func (*Resolver) LookupHost

func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error)

LookupHost ищет заданный хост с помощью локального резольвера. Возвращается фрагмент адресов этого хоста.

func (*Resolver) LookupIP

func (r *Resolver) LookupIP(ctx context.Context, network, host string) ([]IP, error)

LookupIP ищет хост для заданной сети с помощью локального резольвера. Возвращается фрагмент IP-адресов этого хоста типа, указанного network. network должен быть одним из “ip”, “ip4” или “ip6”.

func (*Resolver) LookupIPAddr

func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, error)

LookupIPAddr ищет хост с помощью локального резольвера. Возвращается фрагмент IPv4- и IPv6-адресов этого хоста.

func (*Resolver) LookupMX

func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*MX, error)

LookupMX возвращает DNS MX-записи для заданного доменного имени, отсортированные по предпочтению.

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

func (*Resolver) LookupNS

func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*NS, error)

LookupNS возвращает NS-записи DNS для заданного доменного имени.

Возвращаемые имена серверов имен проверяются на правильность форматирования доменных имен в формате представления. Если ответ содержит недопустимые имена, эти записи отфильтровываются, и вместе с оставшимися результатами, если таковые имеются, возвращается ошибка.

func (*Resolver) LookupNetIP

func (r *Resolver) LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)

LookupNetIP ищет хост с помощью локального резольвера. Возвращается фрагмент IP-адресов этого хоста типа, указанного в network. Сеть должна быть одной из “ip”, “ip4” или “ip6”.

func (*Resolver) LookupPort

func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error)

LookupPort ищет порт для заданной сети и сервиса.

Сеть должна быть одной из “tcp”, “tcp4”, “tcp6”, “udp”, “udp4”, “udp6” или “ip”.

func (*Resolver) LookupSRV

func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error)

LookupSRV пытается разрешить SRV-запрос данного сервиса, протокола и доменного имени. Протоколом является “tcp” или “udp”. Возвращаемые записи сортируются по приоритету и рандомизируются по весу в пределах приоритета.

LookupSRV строит DNS-имя для поиска в соответствии с RFC 2782. То есть, он ищет _service._proto.name. Для удобства сервисов, публикующих SRV-записи под нестандартными именами, если service и proto являются пустыми строками, LookupSRV ищет имя напрямую.

Возвращаемые имена сервисов проверяются на правильность форматирования доменных имен в формате представления. Если ответ содержит недопустимые имена, эти записи отфильтровываются и возвращается ошибка вместе с оставшимися результатами, если таковые имеются.

func (*Resolver) LookupTXT

func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error)

LookupTXT возвращает записи DNS TXT для заданного доменного имени.

Если DNS TXT-запись содержит несколько строк, они объединяются в одну строку.

type SRV

type SRV struct {
	Target   string
	Port     uint16
	Priority uint16
	Weight   uint16
}

SRV представляет собой одну запись DNS SRV.

func LookupSRV

func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error)

LookupSRV пытается разрешить SRV-запрос для данного сервиса, протокола и доменного имени. Протоколом является “tcp” или “udp”. Возвращаемые записи сортируются по приоритету и рандомизируются по весу в пределах приоритета.

LookupSRV строит DNS-имя для поиска в соответствии с RFC 2782. То есть, он ищет _service._proto.name. Для удобства сервисов, публикующих SRV-записи под нестандартными именами, если service и proto являются пустыми строками, LookupSRV ищет имя напрямую.

Возвращаемые имена сервисов проверяются на правильность форматирования доменных имен в формате представления. Если ответ содержит недопустимые имена, эти записи отфильтровываются, и вместе с оставшимися результатами, если таковые имеются, возвращается ошибка.

type TCPAddr

type TCPAddr struct {
	IP IP
	Port int
	Zone string // Зона масштабируемой адресации IPv6
}

TCPAddr представляет адрес конечной точки TCP.

func ResolveTCPAddr

func ResolveTCPAddr(network, address string) (*TCPAddr, error)

ResolveTCPAddr возвращает адрес конечной точки TCP.

Сеть должна быть именем TCP-сети.

Если хост в параметре address не является литеральным IP-адресом или порт не является литеральным номером порта, ResolveTCPAddr преобразует адрес в адрес конечной точки TCP. В противном случае он разбирает адрес как пару из буквального IP-адреса и номера порта. В качестве параметра address может использоваться имя хоста, но это не рекомендуется, так как вернется не более одного из IP-адресов имени хоста.

Описание параметров сети и адреса см. в разделе func Dial.

func TCPAddrFromAddrPort

func TCPAddrFromAddrPort(addr netip.AddrPort) *TCPAddr

TCPAddrFromAddrPort возвращает addr как TCPAddr. Если addr.IsValid() равен false, то возвращаемый TCPAddr будет содержать нулевое поле IP, указывающее на неопределенный адрес, не зависящий от семейства адресов.

func (*TCPAddr) AddrPort

func (a *TCPAddr) AddrPort() netip.AddrPort

AddrPort возвращает TCPAddr a в виде netip.AddrPort.

Если a.Port не помещается в uint16, он тихо усекается.

Если a равно nil, возвращается нулевое значение.

func (*TCPAddr) Network

func (a *TCPAddr) Network() string

Network возвращает сетевое имя адреса, “tcp”.

func (*TCPAddr) String

func (a *TCPAddr) String() string

type TCPConn

type TCPConn struct {
	// содержит отфильтрованные или неэкспонированные поля
}

TCPConn - это реализация интерфейса Conn для сетевых соединений TCP.

func DialTCP

func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)

DialTCP действует как Dial для сетей TCP.

Сеть должна быть именем TCP-сети; подробности см. в func Dial.

Если laddr равен nil, автоматически выбирается локальный адрес. Если поле IP в raddr равно nil или является неопределенным IP-адресом, предполагается локальная система.

func (*TCPConn) Закрыть

func (c *TCPConn) Close() error

Close закрывает соединение.

func (*TCPConn) CloseRead

func (c *TCPConn) CloseRead() error

CloseRead закрывает TCP-соединение со стороны чтения. Большинство вызывающих должны просто использовать Close.

func (*TCPConn) CloseWrite

func (c *TCPConn) CloseWrite() error

CloseWrite закрывает пишущую сторону TCP-соединения. Большинству вызывающих следует просто использовать Close.

func (*TCPConn) File

func (c *TCPConn) File() (f *os.File, err error)

File возвращает копию базового файла os.File. Ответственность за закрытие f по завершении работы лежит на вызывающей стороне. Закрытие c не влияет на f, а закрытие f не влияет на c.

Дескриптор файла возвращаемого os.File отличается от дескриптора соединения. Попытка изменить свойства оригинала с помощью этого дубликата может привести к желаемому результату, а может и не привести.

func (*TCPConn) LocalAddr

func (c *TCPConn) LocalAddr() Addr

LocalAddr возвращает адрес локальной сети. Возвращаемый Addr является общим для всех вызовов LocalAddr, поэтому не изменяйте его.

func (*TCPConn) MultipathTCP

func (c *TCPConn) MultipathTCP() (bool, error)

MultipathTCP сообщает, использует ли текущее соединение MPTCP.

Если Multipath TCP не поддерживается хостом, другим пиром или намеренно/случайно отфильтровывается каким-либо устройством между ними, будет выполнен откат к TCP. Этот метод делает все возможное, чтобы проверить, используется ли MPTCP или нет.

В Linux больше условий проверяется на ядрах >= v5.16, что улучшает результаты.

func (*TCPConn) Read

func (c *TCPConn) Read(b []byte) (int, error)

Read реализует метод Conn Read.

func (*TCPConn) ReadFrom

func (c *TCPConn) ReadFrom(r io.Reader) (int64, error)

ReadFrom реализует метод io.ReaderFrom ReadFrom.

func (*TCPConn) RemoteAddr

func (c *TCPConn) RemoteAddr() Addr

RemoteAddr возвращает адрес удаленной сети. Возвращаемый Addr является общим для всех вызовов RemoteAddr, поэтому не изменяйте его.

func (*TCPConn) SetDeadline

func (c *TCPConn) SetDeadline(t time.Time) error

SetDeadline реализует метод Conn SetDeadline.

func (*TCPConn) SetKeepAlive

func (c *TCPConn) SetKeepAlive(keepalive bool) error

SetKeepAlive устанавливает, должна ли операционная система отправлять сообщения keep-alive на соединение.

func (*TCPConn) SetKeepAliveConfig

func (c *TCPConn) SetKeepAliveConfig(config KeepAliveConfig) error

SetKeepAliveConfig настраивает сообщения keep-alive, отправляемые операционной системой.

func (*TCPConn) SetKeepAlivePeriod

func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error

SetKeepAlivePeriod устанавливает время, в течение которого соединение должно простаивать, прежде чем TCP начнет посылать keepalive зонды.

Обратите внимание, что вызов этого метода на Windows до Windows 10 версии 1709 сбросит KeepAliveInterval на системное значение по умолчанию, которое обычно составляет 1 секунду.

func (*TCPConn) SetLinger

func (c *TCPConn) SetLinger(sec int) error

SetLinger задает поведение Close на соединении, которое все еще ожидает отправки или подтверждения данных.

Если sec < 0 (по умолчанию), операционная система завершает отправку данных в фоновом режиме.

Если sec == 0, операционная система отбрасывает все неотправленные или неподтвержденные данные.

Если sec > 0, данные отправляются в фоновом режиме, как и в случае sec < 0. В некоторых операционных системах, включая Linux, это может привести к блокировке Close до тех пор, пока все данные не будут отправлены или отброшены. В некоторых операционных системах по истечении sec все оставшиеся неотправленные данные могут быть отброшены.

func (*TCPConn) SetNoDelay

func (c *TCPConn) SetNoDelay(noDelay bool) error

SetNoDelay управляет тем, должна ли операционная система задерживать передачу пакетов в надежде отправить меньше пакетов (алгоритм Нагла). По умолчанию установлено значение true (без задержки), что означает, что данные отправляются как можно быстрее после записи.

func (*TCPConn) SetReadBuffer

func (c *TCPConn) SetReadBuffer(bytes int) error

SetReadBuffer устанавливает размер буфера приема операционной системы, связанного с соединением.

func (*TCPConn) SetReadDeadline

func (c *TCPConn) SetReadDeadline(t time.Time) error

SetReadDeadline реализует метод Conn SetReadDeadline.

func (*TCPConn) SetWriteBuffer

func (c *TCPConn) SetWriteBuffer(bytes int) error

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

func (*TCPConn) SetWriteDeadline

func (c *TCPConn) SetWriteDeadline(t time.Time) error

SetWriteDeadline реализует метод Conn SetWriteDeadline.

func (*TCPConn) SyscallConn

func (c *TCPConn) SyscallConn() (syscall.RawConn, error)

SyscallConn возвращает необработанное сетевое соединение. Оно реализует интерфейс syscall.Conn.

func (*TCPConn) Write

func (c *TCPConn) Write(b []byte) (int, error)

Write реализует метод записи Conn.

func (*TCPConn) WriteTo

func (c *TCPConn) WriteTo(w io.Writer) (int64, error)

WriteTo реализует метод io.WriterTo WriteTo.

type TCPListener

type TCPListener struct {
	// содержит отфильтрованные или неэкспонированные поля
}

TCPListener - это сетевой слушатель TCP. Клиенты обычно должны использовать переменные типа Listener, а не предполагать, что это TCP.

func ListenTCP

func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error)

ListenTCP действует как Listen для сетей TCP.

Сеть должна быть именем TCP-сети; подробности см. в func Dial.

Если поле IP в laddr равно nil или неопределенному IP-адресу, ListenTCP прослушивает все доступные одноадресные и одноадресные IP-адреса локальной системы. Если поле Port в laddr равно 0, номер порта выбирается автоматически.

func (*TCPListener) Accept

func (l *TCPListener) Accept() (Conn, error)

Accept реализует метод Accept в интерфейсе Listener; он ожидает следующего вызова и возвращает общий Conn.

func (*TCPListener) AcceptTCP

func (l *TCPListener) AcceptTCP() (*TCPConn, error)

AcceptTCP принимает следующий входящий вызов и возвращает новое соединение.

func (*TCPListener) Addr

func (l *TCPListener) Addr() Addr

Addr возвращает сетевой адрес слушателя, *TCPAddr. Возвращаемый Addr является общим для всех вызовов Addr, поэтому не изменяйте его.

func (*TCPListener) Close

func (l *TCPListener) Close() error

Close прекращает прослушивание TCP-адреса. Уже принятые соединения не закрываются.

func (*TCPListener) File

func (l *TCPListener) File() (f *os.File, err error)

File возвращает копию базового файла os.File. Ответственность за закрытие f по завершении работы лежит на вызывающей стороне. Закрытие l не влияет на f, а закрытие f не влияет на l.

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

func (*TCPListener) SetDeadline

func (l *TCPListener) SetDeadline(t time.Time) error

SetDeadline устанавливает крайний срок, связанный со слушателем. Нулевое значение времени отключает дедлайн.

func (*TCPListener) SyscallConn

func (l *TCPListener) SyscallConn() (syscall.RawConn, error)

SyscallConn возвращает необработанное сетевое соединение. Оно реализует интерфейс syscall.Conn.

Возвращаемое RawConn поддерживает только вызов Control. Чтение и запись возвращают ошибку.

type UDPAddr

type UDPAddr struct {
	IP IP
	Порт int
	Zone string // Зона масштабируемой адресации IPv6
}

UDPAddr представляет адрес конечной точки UDP.

func ResolveUDPAddr

func ResolveUDPAddr(network, address string) (*UDPAddr, error)

ResolveUDPAddr возвращает адрес конечной точки UDP.

Сеть должна быть именем UDP-сети.

Если хост в параметре address не является литеральным IP-адресом или порт не является литеральным номером порта, ResolveUDPAddr преобразует адрес в адрес конечной точки UDP. В противном случае он разбирает адрес как пару из буквального IP-адреса и номера порта. В качестве параметра address может использоваться имя хоста, но это не рекомендуется, так как вернется не более одного из IP-адресов имени хоста.

Описание параметров сети и адреса см. в разделе func Dial.

func UDPAddrFromAddrPort

func UDPAddrFromAddrPort(addr netip.AddrPort) *UDPAddr

UDPAddrFromAddrPort возвращает addr как UDPAddr. Если addr.IsValid() равен false, то возвращаемый UDPAddr будет содержать нулевое поле IP, указывающее на неопределенный адрес, не зависящий от семейства адресов.

func (*UDPAddr) AddrPort

func (a *UDPAddr) AddrPort() netip.AddrPort

AddrPort возвращает UDPAddr a в виде netip.AddrPort.

Если a.Port не помещается в uint16, он тихо усекается.

Если a равно nil, возвращается нулевое значение.

func (*UDPAddr) Network

func (a *UDPAddr) Network() string

Network возвращает сетевое имя адреса, “udp”.

func (*UDPAddr) String

func (a *UDPAddr) String() string

type UDPConn

type UDPConn struct {
	// содержит отфильтрованные или неэкспонированные поля
}

UDPConn - это реализация интерфейсов Conn и PacketConn для сетевых соединений UDP.

func DialUDP

func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)

DialUDP действует как Dial для сетей UDP.

Сеть должна быть именем UDP-сети; подробности см. в func Dial.

Если laddr равно nil, автоматически выбирается локальный адрес. Если поле IP в raddr равно nil или является неопределенным IP-адресом, предполагается локальная система.

func ListenMulticastUDP

func ListenMulticastUDP(network string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error)

ListenMulticastUDP действует подобно ListenPacket для UDP сетей, но принимает групповой адрес на определенном сетевом интерфейсе.

Сеть должна быть именем UDP-сети; подробности см. в func Dial.

ListenMulticastUDP прослушивает все доступные IP-адреса локальной системы, включая групповой, многоадресный IP-адрес. Если ifi равно nil, ListenMulticastUDP использует назначенный системой многоадресный интерфейс, хотя это не рекомендуется, поскольку назначение зависит от платформ и иногда может потребовать настройки маршрутизации. Если поле Port в gaddr равно 0, номер порта выбирается автоматически.

ListenMulticastUDP предназначен только для удобства простых, небольших приложений. Для общего назначения существуют пакеты golang.org/x/net/ipv4 и golang.org/x/net/ipv6.

Обратите внимание, что ListenMulticastUDP установит опцию сокета IP_MULTICAST_LOOP в 0 в IPPROTO_IP, чтобы отключить обратную петлю для многоадресных пакетов.

func ListenUDP

func ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error)

ListenUDP действует как ListenPacket для сетей UDP.

Сеть должна быть именем UDP-сети; подробности см. в func Dial.

Если поле IP в laddr равно nil или неопределенному IP-адресу, ListenUDP прослушивает все доступные IP-адреса локальной системы, кроме IP-адресов многоадресной рассылки. Если поле Port в laddr равно 0, номер порта выбирается автоматически.

func (*UDPConn) Close

func (c *UDPConn) Close() error

Close закрывает соединение.

func (*UDPConn) File

func (c *UDPConn) File() (f *os.File, err error)

File возвращает копию базового файла os.File. Ответственность за закрытие f по завершении работы лежит на вызывающей стороне. Закрытие c не влияет на f, а закрытие f не влияет на c.

Дескриптор файла возвращаемого os.File отличается от дескриптора соединения. Попытка изменить свойства оригинала с помощью этого дубликата может привести к желаемому результату, а может и не привести.

func (*UDPConn) LocalAddr

func (c *UDPConn) LocalAddr() Addr

LocalAddr возвращает адрес локальной сети. Возвращаемый Addr является общим для всех вызовов LocalAddr, поэтому не изменяйте его.

func (*UDPConn) Read

func (c *UDPConn) Read(b []byte) (int, error)

Read реализует метод Conn Read.

func (*UDPConn) ReadFrom

func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error)

ReadFrom реализует метод PacketConn ReadFrom.

func (*UDPConn) ReadFromUDP

func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error)

ReadFromUDP действует как UDPConn.ReadFrom, но возвращает UDPAddr.

func (*UDPConn) ReadFromUDPAddrPort

func (c *UDPConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error)

ReadFromUDPAddrPort действует аналогично ReadFrom, но возвращает netip.AddrPort.

Если c привязан к неопределенному адресу, возвращаемый адрес netip.AddrPort может быть IPv4-маппированным IPv6-адресом. Используйте netip.Addr.Unmap, чтобы получить адрес без префикса IPv6.

func (*UDPConn) ReadMsgUDP

func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error)

ReadMsgUDP считывает сообщение из c, копируя полезную нагрузку в b и связанные с ней внеполосные данные в oob. Возвращается количество байт, скопированных в b, количество байт, скопированных в oob, флаги, которые были установлены для сообщения, и адрес источника сообщения.

Пакеты golang.org/x/net/ipv4 и golang.org/x/net/ipv6 могут быть использованы для манипулирования опциями сокетов IP-уровня в oob.

func (*UDPConn) ReadMsgUDPAddrPort

func (c *UDPConn) ReadMsgUDPAddrPort(b, oob []byte) (n, oobn, flags int, addr netip.AddrPort, err error)

ReadMsgUDPAddrPort подобен UDPConn.ReadMsgUDP, но возвращает netip.AddrPort вместо UDPAddr.

func (*UDPConn) RemoteAddr

func (c *UDPConn) RemoteAddr() Addr

RemoteAddr возвращает адрес удаленной сети. Возвращаемый Addr является общим для всех вызовов RemoteAddr, поэтому не изменяйте его.

func (*UDPConn) SetDeadline

func (c *UDPConn) SetDeadline(t time.Time) error

SetDeadline реализует метод Conn SetDeadline.

func (*UDPConn) SetReadBuffer

func (c *UDPConn) SetReadBuffer(bytes int) error

SetReadBuffer устанавливает размер буфера приема операционной системы, связанного с соединением.

func (*UDPConn) SetReadDeadline

func (c *UDPConn) SetReadDeadline(t time.Time) error

SetReadDeadline реализует метод Conn SetReadDeadline.

func (*UDPConn) SetWriteBuffer

func (c *UDPConn) SetWriteBuffer(bytes int) error

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

func (*UDPConn) SetWriteDeadline

func (c *UDPConn) SetWriteDeadline(t time.Time) error

SetWriteDeadline реализует метод Conn SetWriteDeadline.

func (*UDPConn) SyscallConn

func (c *UDPConn) SyscallConn() (syscall.RawConn, error)

SyscallConn возвращает необработанное сетевое соединение. Оно реализует интерфейс syscall.Conn.

func (*UDPConn) Write

func (c *UDPConn) Write(b []byte) (int, error)

Write реализует метод записи Conn.

func (*UDPConn) WriteMsgUDP

func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error)

WriteMsgUDP записывает сообщение на addr через c, если c не подключен, или на удаленный адрес c, если c подключен (в этом случае addr должен быть nil). Полезная нагрузка копируется из b, а связанные с ней внеполосные данные - из oob. Возвращает количество записанных байтов полезной нагрузки и внеполосных данных.

Пакеты golang.org/x/net/ipv4 и golang.org/x/net/ipv6 могут быть использованы для манипулирования опциями сокетов IP-уровня в oob.

func (*UDPConn) WriteMsgUDPAddrPort

func (c *UDPConn) WriteMsgUDPAddrPort(b, oob []byte, addr netip.AddrPort) (n, oobn int, err error)

WriteMsgUDPAddrPort подобен UDPConn.WriteMsgUDP, но принимает netip.AddrPort вместо UDPAddr.

func (*UDPConn) WriteTo

func (c *UDPConn) WriteTo(b []byte, addr Addr) (int, error)

WriteTo реализует метод PacketConn WriteTo.

Пример
package main

import (
	"log"
	"net"
)

func main() {
	// Unlike Dial, ListenPacket creates a connection without any
	// association with peers.
	conn, err := net.ListenPacket("udp", ":0")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	dst, err := net.ResolveUDPAddr("udp", "192.0.2.1:2000")
	if err != nil {
		log.Fatal(err)
	}

	// The connection can write data to the desired address.
	_, err = conn.WriteTo([]byte("data"), dst)
	if err != nil {
		log.Fatal(err)
	}
}

func (*UDPConn) WriteToUDP

func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error)

WriteToUDP действует как UDPConn.WriteTo, но принимает UDPAddr.

func (*UDPConn) WriteToUDPAddrPort

func (c *UDPConn) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error)

WriteToUDPAddrPort действует как UDPConn.WriteTo, но принимает netip.AddrPort.

type UnixAddr

type UnixAddr struct {
	строка имени
	Net string
}

UnixAddr представляет адрес конечной точки сокета домена Unix.

func ResolveUnixAddr

func ResolveUnixAddr(network, address string) (*UnixAddr, error)

ResolveUnixAddr возвращает адрес конечной точки сокета домена Unix.

Сеть должна быть именем сети Unix.

Описание параметров сети и адреса см. в разделе func Dial.

func (*UnixAddr) Network

func (a *UnixAddr) Network() string

Network возвращает сетевое имя адреса, “unix”, “unixgram” или “unixpacket”.

func (*UnixAddr) String

func (a *UnixAddr) String() string

type UnixConn

type UnixConn struct {
	// содержит отфильтрованные или неэкспонированные поля
}

UnixConn - это реализация интерфейса Conn для соединений с сокетами домена Unix.

func DialUnix

func DialUnix(network string, laddr, raddr *UnixAddr) (*UnixConn, error)

DialUnix действует как Dial для сетей Unix.

Сеть должна быть именем сети Unix; подробности см. в func Dial.

Если laddr не равен nil, он используется в качестве локального адреса для соединения.

func ListenUnixgram

func ListenUnixgram(network string, laddr *UnixAddr) (*UnixConn, error)

ListenUnixgram действует как ListenPacket для сетей Unix.

Сеть должна быть “unixgram”.

func (*UnixConn) Закрыть

func (c *UnixConn) Close() error

Close закрывает соединение.

func (*UnixConn) CloseRead

func (c *UnixConn) CloseRead() error

CloseRead закрывает сторону чтения соединения с доменом Unix. Большинство вызывающих должны просто использовать Close.

func (*UnixConn) CloseWrite

func (c *UnixConn) CloseWrite() error

CloseWrite закрывает пишущую сторону соединения с доменом Unix. Большинство вызывающих должны просто использовать Close.

func (*UnixConn) File

func (c *UnixConn) File() (f *os.File, err error)

File возвращает копию базового файла os.File. Ответственность за закрытие f по завершении работы лежит на вызывающей стороне. Закрытие c не влияет на f, а закрытие f не влияет на c.

Дескриптор файла возвращаемого os.File отличается от дескриптора соединения. Попытка изменить свойства оригинала с помощью этого дубликата может привести к желаемому результату, а может и не привести.

func (*UnixConn) LocalAddr

func (c *UnixConn) LocalAddr() Addr

LocalAddr возвращает адрес локальной сети. Возвращаемый Addr является общим для всех вызовов LocalAddr, поэтому не изменяйте его.

func (*UnixConn) Read

func (c *UnixConn) Read(b []byte) (int, error)

Read реализует метод Conn Read.

func (*UnixConn) ReadFrom

func (c *UnixConn) ReadFrom(b []byte) (int, Addr, error)

ReadFrom реализует метод PacketConn ReadFrom.

func (*UnixConn) ReadFromUnix

func (c *UnixConn) ReadFromUnix(b []byte) (int, *UnixAddr, error)

ReadFromUnix действует как UnixConn.ReadFrom, но возвращает UnixAddr.

func (*UnixConn) ReadMsgUnix

func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAddr, err error)

ReadMsgUnix считывает сообщение из c, копируя полезную нагрузку в b и связанные с ней внеполосные данные в oob. Возвращается количество байт, скопированных в b, количество байт, скопированных в oob, флаги, которые были установлены для сообщения, и адрес источника сообщения.

Обратите внимание, что если len(b) == 0 и len(oob) > 0, эта функция все равно прочитает (и отбросит) 1 байт из соединения.

func (*UnixConn) RemoteAddr

func (c *UnixConn) RemoteAddr() Addr

RemoteAddr возвращает адрес удаленной сети. Возвращаемый Addr является общим для всех вызовов RemoteAddr, поэтому не изменяйте его.

func (*UnixConn) SetDeadline

func (c *UnixConn) SetDeadline(t time.Time) error

SetDeadline реализует метод Conn SetDeadline.

func (*UnixConn) SetReadBuffer

func (c *UnixConn) SetReadBuffer(bytes int) error

SetReadBuffer устанавливает размер буфера приема операционной системы, связанного с соединением.

func (*UnixConn) SetReadDeadline

func (c *UnixConn) SetReadDeadline(t time.Time) error

SetReadDeadline реализует метод Conn SetReadDeadline.

func (*UnixConn) SetWriteBuffer

func (c *UnixConn) SetWriteBuffer(bytes int) error

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

func (*UnixConn) SetWriteDeadline

func (c *UnixConn) SetWriteDeadline(t time.Time) error

SetWriteDeadline реализует метод Conn SetWriteDeadline.

func (*UnixConn) SyscallConn

func (c *UnixConn) SyscallConn() (syscall.RawConn, error)

SyscallConn возвращает необработанное сетевое соединение. Оно реализует интерфейс syscall.Conn.

func (*UnixConn) Write

func (c *UnixConn) Write(b []byte) (int, error)

Write реализует метод Conn Write.

func (*UnixConn) WriteMsgUnix

func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err error)

WriteMsgUnix записывает сообщение в addr через c, копируя полезную нагрузку из b и связанные с ней внеполосные данные из oob. Возвращается количество записанных байт полезной нагрузки и внеполосных данных.

Обратите внимание, что если len(b) == 0 и len(oob) > 0, эта функция все равно запишет 1 байт в соединение.

func (*UnixConn) WriteTo

func (c *UnixConn) WriteTo(b []byte, addr Addr) (int, error)

WriteTo реализует метод PacketConn WriteTo.

func (*UnixConn) WriteToUnix

func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (int, error)

WriteToUnix действует как UnixConn.WriteTo, но принимает UnixAddr.

type UnixListener

type UnixListener struct {
	// содержит отфильтрованные или неэкспонированные поля
}

UnixListener - это слушатель сокетов домена Unix. Клиенты обычно должны использовать переменные типа Listener вместо того, чтобы предполагать сокеты домена Unix.

func ListenUnix

func ListenUnix(network string, laddr *UnixAddr) (*UnixListener, error)

ListenUnix действует как Listen для сетей Unix.

Сеть должна быть “unix” или “unixpacket”.

func (*UnixListener) Принять

func (l *UnixListener) Accept() (Conn, error)

Accept реализует метод Accept в интерфейсе Listener. Возвращаемые соединения будут иметь тип *UnixConn.

func (*UnixListener) AcceptUnix

func (l *UnixListener) AcceptUnix() (*UnixConn, error)

AcceptUnix принимает следующий входящий вызов и возвращает новое соединение.

func (*UnixListener) Addr

func (l *UnixListener) Addr() Addr

Addr возвращает сетевой адрес слушателя. Возвращаемый Addr является общим для всех вызовов Addr, поэтому не изменяйте его.

func (*UnixListener) Close

func (l *UnixListener) Close() error

Close прекращает прослушивание Unix-адреса. Уже принятые соединения не закрываются.

func (*UnixListener) File

func (l *UnixListener) File() (f *os.File, err error)

File возвращает копию базового файла os.File. Ответственность за закрытие f по завершении работы лежит на вызывающей стороне. Закрытие l не влияет на f, а закрытие f не влияет на l.

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

func (*UnixListener) SetDeadline

func (l *UnixListener) SetDeadline(t time.Time) error

SetDeadline устанавливает крайний срок, связанный со слушателем. Нулевое значение времени отключает дедлайн.

func (*UnixListener) SetUnlinkOnClose

func (l *UnixListener) SetUnlinkOnClose(unlink bool)

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

Поведение по умолчанию заключается в том, что файл сокета отсоединяется только тогда, когда пакет net создал его. То есть, если слушатель и базовый файл сокета были созданы вызовом Listen или ListenUnix, то по умолчанию закрытие слушателя приведет к удалению файла сокета. Но если слушатель был создан вызовом FileListener для использования уже существующего файла сокета, то по умолчанию закрытие слушателя не приведет к удалению файла сокета.

func (*UnixListener) SyscallConn

func (l *UnixListener) SyscallConn() (syscall.RawConn, error)

SyscallConn возвращает необработанное сетевое соединение. Оно реализует интерфейс syscall.Conn.

Возвращаемое RawConn поддерживает только вызов Control. Чтение и запись возвращают ошибку.

type UnknownNetworkError

type UnknownNetworkError string

func (UnknownNetworkError) Error

func (e UnknownNetworkError) Error() string

func (UnknownNetworkError) Temporary

func (e UnknownNetworkError) Temporary() bool

func (UnknownNetworkError) Timeout

func (e UnknownNetworkError) Timeout() bool

Примечания

Ошибки

  • В JS и Windows функции FileConn, FileListener и FilePacketConn не реализованы.
  • В JS методы и функции, связанные с Interface, не реализованы.
  • В AIX, DragonFly BSD, NetBSD, OpenBSD, Plan 9 и Solaris метод MulticastAddrs интерфейса Interface не реализован.
  • На всех платформах POSIX чтение из сети “ip4” с помощью метода ReadFrom или ReadFromIP может не возвращать полный пакет IPv4, включая его заголовок, даже если есть свободное место. Это может произойти даже в тех случаях, когда Read или ReadMsgIP могут вернуть полный пакет. По этой причине рекомендуется не использовать эти методы, если важно получить полный пакет.
  • Рекомендации по совместимости Go 1 не позволяют нам изменить поведение этих методов; вместо них используйте Read или ReadMsgIP.
  • На JS и Plan 9 методы и функции, связанные с IPConn, не реализованы.
  • На Windows метод File интерфейса IPConn не реализован.
  • В DragonFly BSD и OpenBSD прослушивание сетей “tcp” и “udp” не прослушивает соединения IPv4 и IPv6. Это связано с тем, что трафик IPv4 не будет маршрутизироваться в сокет IPv6 — для поддержки обеих семейств адресов требуются два отдельных сокета. Подробности см. в inet6(4).
  • В Windows метод Write syscall.RawConn не интегрирован с сетевым опросником среды выполнения. Он не может ждать, пока соединение станет доступным для записи, и не учитывает deadli

15.3 - Пакет net/http встроенных функций и типов языка Go

Пакет 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, когда закончит с ним работать:

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

Клиенты и транспорты (Clients and Transports)

Для управления HTTP-заголовками, политикой перенаправлений (redirects) и другими настройками клиента, создайте 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, сжатие и др.) настройте http.Transport:

tr := &http.Transport{
    MaxIdleConns:       10,               // макс. число бездействующих соединений
    IdleConnTimeout:    30 * time.Second, // таймаут для idle-соединений
    DisableCompression: true,             // отключение сжатия (gzip)
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")

Важно:

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

Серверы (Servers)

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

http.Handle("/foo", fooHandler)  // регистрация обработчика для пути "/foo"

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)) // запуск сервера на :8080

Для тонкой настройки сервера создайте http.Server:

s := &http.Server{
    Addr:           ":8080",         // адрес сервера
    Handler:        myHandler,       // кастомный обработчик
    ReadTimeout:    10 * time.Second, // таймаут на чтение запроса
    WriteTimeout:   10 * time.Second, // таймаут на запись ответа
    MaxHeaderBytes: 1 << 20,         // макс. размер заголовков (1 MB)
}
log.Fatal(s.ListenAndServe())

Поддержка HTTP/2

С Go 1.6+ HTTP/2 поддерживается автоматически при использовании HTTPS.

Как отключить HTTP/2?

  • Для клиента: задать Transport.TLSNextProto = make(map[string]func(...)) (пустой map).
  • Для сервера: задать Server.TLSNextProto = make(map[string]func(...)).

Через переменную GODEBUG:

GODEBUG=http2client=0  # отключить HTTP/2 для клиентов  
GODEBUG=http2server=0  # отключить HTTP/2 для серверов  
GODEBUG=http2debug=1   # логировать отладочную информацию  
GODEBUG=http2debug=2   # расширенные логи (дампы фреймов)  

Перед отключением HTTP/2 сообщите о проблеме: golang.org/s/http2bug.

Расширенная настройка HTTP/2
Для сложных сценариев (например, ручная конфигурация HTTP/2) используйте пакет golang.org/x/net/http2:

import "golang.org/x/net/http2"

// Для клиента:
http2.ConfigureTransport(tr) // tr — ваш *http.Transport

// Для сервера:
http2.ConfigureServer(s, nil) // s — ваш *http.Server

Ручная настройка через golang.org/x/net/http2 имеет приоритет над встроенной поддержкой HTTP/2 в net/http.


Ключевые термины

  • DefaultServeMux – стандартный муксер (роутер) HTTP-запросов.
  • Transport – управление низкоуровневыми параметрами HTTP-соединений.
  • TLSNextProto – настройка протоколов, работающих поверх TLS (например, HTTP/2).
  • GODEBUG – переменная окружения для отладки поведения Go-программ.

Константы

const (
	MethodGet     = "GET"
	MethodHead    = "HEAD"
	MethodPost    = "POST"
	MethodPut     = "PUT"
	MethodPatch   = "PATCH" // RFC 5789
	MethodDelete  = "DELETE"
	MethodConnect = "CONNECT"
	MethodOptions = "OPTIONS"
	MethodTrace   = "TRACE"
)

Общие методы HTTP. Если не указано иное, они определены в разделе 4.3 RFC 7231.

const (
	StatusContinue           = 100 // RFC 9110, 15.2.1
	StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2
	StatusProcessing         = 102 // RFC 2518, 10.1
	StatusEarlyHints         = 103 // RFC 8297

	StatusOK                   = 200 // RFC 9110, 15.3.1
	StatusCreated              = 201 // RFC 9110, 15.3.2
	StatusAccepted             = 202 // RFC 9110, 15.3.3
	StatusNonAuthoritativeInfo = 203 // RFC 9110, 15.3.4
	StatusNoContent            = 204 // RFC 9110, 15.3.5
	StatusResetContent         = 205 // RFC 9110, 15.3.6
	StatusPartialContent       = 206 // RFC 9110, 15.3.7
	StatusMultiStatus          = 207 // RFC 4918, 11.1
	StatusAlreadyReported      = 208 // RFC 5842, 7.1
	StatusIMUsed               = 226 // RFC 3229, 10.4.1

	StatusMultipleChoices  = 300 // RFC 9110, 15.4.1
	StatusMovedPermanently = 301 // RFC 9110, 15.4.2
	StatusFound            = 302 // RFC 9110, 15.4.3
	StatusSeeOther         = 303 // RFC 9110, 15.4.4
	StatusNotModified      = 304 // RFC 9110, 15.4.5
	StatusUseProxy         = 305 // RFC 9110, 15.4.6

	StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8
	StatusPermanentRedirect = 308 // RFC 9110, 15.4.9

	StatusBadRequest                   = 400 // RFC 9110, 15.5.1
	StatusUnauthorized                 = 401 // RFC 9110, 15.5.2
	StatusPaymentRequired              = 402 // RFC 9110, 15.5.3
	StatusForbidden                    = 403 // RFC 9110, 15.5.4
	StatusNotFound                     = 404 // RFC 9110, 15.5.5
	StatusMethodNotAllowed             = 405 // RFC 9110, 15.5.6
	StatusNotAcceptable                = 406 // RFC 9110, 15.5.7
	StatusProxyAuthRequired            = 407 // RFC 9110, 15.5.8
	StatusRequestTimeout               = 408 // RFC 9110, 15.5.9
	StatusConflict                     = 409 // RFC 9110, 15.5.10
	StatusGone                         = 410 // RFC 9110, 15.5.11
	StatusLengthRequired               = 411 // RFC 9110, 15.5.12
	StatusPreconditionFailed           = 412 // RFC 9110, 15.5.13
	StatusRequestEntityTooLarge        = 413 // RFC 9110, 15.5.14
	StatusRequestURITooLong            = 414 // RFC 9110, 15.5.15
	StatusUnsupportedMediaType         = 415 // RFC 9110, 15.5.16
	StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17
	StatusExpectationFailed            = 417 // RFC 9110, 15.5.18
	StatusTeapot                       = 418 // RFC 9110, 15.5.19 (Unused)
	StatusMisdirectedRequest           = 421 // RFC 9110, 15.5.20
	StatusUnprocessableEntity          = 422 // RFC 9110, 15.5.21
	StatusLocked                       = 423 // RFC 4918, 11.3
	StatusFailedDependency             = 424 // RFC 4918, 11.4
	StatusTooEarly                     = 425 // RFC 8470, 5.2.
	StatusUpgradeRequired              = 426 // RFC 9110, 15.5.22
	StatusPreconditionRequired         = 428 // RFC 6585, 3
	StatusTooManyRequests              = 429 // RFC 6585, 4
	StatusRequestHeaderFieldsTooLarge  = 431 // RFC 6585, 5
	StatusUnavailableForLegalReasons   = 451 // RFC 7725, 3

	StatusInternalServerError           = 500 // RFC 9110, 15.6.1
	StatusNotImplemented                = 501 // RFC 9110, 15.6.2
	StatusBadGateway                    = 502 // RFC 9110, 15.6.3
	StatusServiceUnavailable            = 503 // RFC 9110, 15.6.4
	StatusGatewayTimeout                = 504 // RFC 9110, 15.6.5
	StatusHTTPVersionNotSupported       = 505 // RFC 9110, 15.6.6
	StatusVariantAlsoNegotiates         = 506 // RFC 2295, 8.1
	StatusInsufficientStorage           = 507 // RFC 4918, 11.5
	StatusLoopDetected                  = 508 // RFC 5842, 7.2
	StatusNotExtended                   = 510 // RFC 2774, 7
	StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)

HTTP коды состояния (HTTP status codes)

Коды состояния HTTP зарегистрированы в IANA. См.: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml

Константы

const DefaultMaxHeaderBytes = 1 << 20 // 1 МБ

DefaultMaxHeaderBytes - максимально допустимый размер заголовков в HTTP-запросе. Можно переопределить через [Server.MaxHeaderBytes].

const DefaultMaxIdleConnsPerHost = 2

DefaultMaxIdleConnsPerHost - значение по умолчанию для Transport.MaxIdleConnsPerHost.

const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"

TimeFormat - формат времени для генерации временных меток в HTTP-заголовках. Аналогичен time.RFC1123, но использует GMT. Время должно быть в UTC.

const TrailerPrefix = "Trailer:"

TrailerPrefix - префикс для ключей [ResponseWriter.Header], указывающий, что значение относится к трейлерам ответа, а не к заголовкам.

Переменные

var (
    ErrNotSupported = &ProtocolError{"feature not supported"}
    ErrMissingBoundary = &ProtocolError{"no multipart boundary param in Content-Type"}
    ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"}
    // Устаревшие ошибки...
)
var (
    ErrBodyNotAllowed = errors.New("http: request method or response status code does not allow body")
    ErrHijacked = errors.New("http: connection has been hijacked")
    ErrContentLength = errors.New("http: wrote more than the declared Content-Length")
    // Устаревшие ошибки...
)

Ошибки, используемые HTTP-сервером.

var (
    ServerContextKey = &contextKey{"http-server"}
    LocalAddrContextKey = &contextKey{"local-addr"}
)

Ключи контекста для доступа к серверу и локальному адресу.

var DefaultClient = &Client{}

DefaultClient - клиент по умолчанию, используемый Get, Head и Post.

var DefaultServeMux = &defaultServeMux

DefaultServeMux - ServeMux по умолчанию, используемый Serve.

var ErrAbortHandler = errors.New("net/http: abort Handler")

ErrAbortHandler - значение для прерывания обработчика без логирования стека.

var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body")

Ошибка при чтении закрытого тела запроса/ответа.

var NoBody = noBody{}

NoBody - пустое тело запроса (io.ReadCloser), всегда возвращает EOF.

15.3.1 - Функции пакета net/http языка Go

Функции пакета http для реализации HTTP-клиента и сервера.

func CanonicalHeaderKey

func CanonicalHeaderKey(s string) string

CanonicalHeaderKey возвращает канонический формат заголовка. Преобразует первую букву и буквы после дефиса в верхний регистр, остальные - в нижний. Например, “accept-encoding” → “Accept-Encoding”. Если s содержит пробелы или недопустимые символы, возвращается без изменений.

func DetectContentType

func DetectContentType(data []byte) string

DetectContentType определяет Content-Type данных по алгоритму https://mimesniff.spec.whatwg.org/. Анализирует первые 512 байт. Всегда возвращает валидный MIME-тип, по умолчанию “application/octet-stream”.

func Error

func Error(w ResponseWriter, error string, code int)

Error отправляет ответ с указанным HTTP-кодом и текстом ошибки. Устанавливает заголовки:

  • Удаляет Content-Length
  • Content-Type: “text/plain; charset=utf-8”
  • X-Content-Type-Options: “nosniff”

func Handle

func Handle(pattern string, handler Handler)

Регистрирует обработчик для шаблона в DefaultServeMux.

Пример
package main

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

type countHandler struct {
	mu sync.Mutex // guards n
	n  int
}

func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.mu.Lock()
	defer h.mu.Unlock()
	h.n++
	fmt.Fprintf(w, "count is %d\n", h.n)
}

func main() {
	http.Handle("/count", new(countHandler))
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func HandleFunc

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

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

Пример
package main

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

func main() {
	h1 := func(w http.ResponseWriter, _ *http.Request) {
		io.WriteString(w, "Hello from a HandleFunc #1!\n")
	}
	h2 := func(w http.ResponseWriter, _ *http.Request) {
		io.WriteString(w, "Hello from a HandleFunc #2!\n")
	}

	http.HandleFunc("/", h1)
	http.HandleFunc("/endpoint", h2)

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

func ListenAndServe

func ListenAndServe(addr string, handler Handler) error

Запускает HTTP-сервер на указанном адресе. Если handler=nil, используется DefaultServeMux.

Пример
package main

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

func main() {
	// Hello world, the web server

	helloHandler := func(w http.ResponseWriter, req *http.Request) {
		io.WriteString(w, "Hello, world!\n")
	}

	http.HandleFunc("/hello", helloHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func ListenAndServeTLS

func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error

Аналогично ListenAndServe, но для HTTPS-соединений. Требует сертификат и приватный ключ.

Пример
package main

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

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		io.WriteString(w, "Hello, TLS!\n")
	})

	// One can use generate_cert.go in crypto/tls to generate cert.pem and key.pem.
	log.Printf("About to listen on 8443. Go to https://127.0.0.1:8443/")
	err := http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", nil)
	log.Fatal(err)
}

func MaxBytesReader

func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser

Ограничивает размер тела запроса. В отличие от io.LimitReader:

  • Возвращает ReadCloser
  • При превышении лимита возвращает ошибку *MaxBytesError
  • Закрывает исходный reader при вызове Close

func NotFound

func NotFound(w ResponseWriter, r *Request)

Отправляет ответ с HTTP 404.

func ParseHTTPVersion

func ParseHTTPVersion(vers string) (major, minor int, ok bool)

Разбирает строку версии HTTP (например, “HTTP/1.1” → (1, 1, true)).

func ParseTime

func ParseTime(text string) (t time.Time, err error)

Разбирает временную метку из заголовка, поддерживая три формата: TimeFormat, RFC850 и ANSIC.

func ProxyFromEnvironment

func ProxyFromEnvironment(req *Request) (*url.URL, error)

ProxyFromEnvironment возвращает URL прокси-сервера для указанного запроса на основе переменных окружения:

  • HTTP_PROXY/https_proxy - для HTTP-запросов
  • HTTPS_PROXY/https_proxy - для HTTPS-запросов
  • NO_PROXY/no_proxy - исключения из проксирования

Форматы значений:

  • Полный URL (http://proxy.example.com:8080)
  • host:port (подразумевается схема http)

Возвращаемые значения:

  • Если прокси не задан или запрос исключен через NO_PROXYnil, nil
  • При некорректном формате → ошибка
  • Для localhost (с портом или без) → nil, nil (специальный случай)

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

proxyUrl, err := http.ProxyFromEnvironment(req)
if err != nil {
    // обработка ошибки конфигурации прокси
}
if proxyUrl != nil {
    // настроить транспорт с этим прокси
}

Особенности:

  1. Автоматически выбирает переменную окружения по схеме запроса
  2. Учитывает исключения в NO_PROXY
  3. Всегда пропускает запросы к localhost
  4. Поддерживает оба регистра переменных (UPPER и lower case)

func ProxyURL

func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error)

Возвращает функцию прокси, всегда возвращающую указанный URL.

func Redirect

func Redirect(w ResponseWriter, r *Request, url string, code int)

Перенаправляет запрос на указанный URL (может быть относительным путем). Код ответа должен быть в диапазоне 3xx (обычно 301, 302 или 303). Если Content-Type не установлен, автоматически добавляет “text/html; charset=utf-8” и простой HTML-ответ. Установка любого значения Content-Type отключает это поведение.

func Serve

func Serve(l net.Listener, handler Handler) error

Принимает входящие HTTP-соединения через listener, создавая новую горутину для каждого соединения. Если handler=nil, используется DefaultServeMux. Поддержка HTTP/2 требует TLS-соединений с “h2” в Config.NextProtos. Всегда возвращает non-nil ошибку.

func ServeContent

func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker)

Отвечает на запрос содержимым из ReadSeeker с поддержкой:

  • Range-запросов
  • Заголовков If-Match/If-None-Match
  • If-Modified-Since/If-Unmodified-Since

Автоматически определяет Content-Type (по расширению файла или через DetectContentType). Использует modtime для Last-Modified и проверки If-Modified-Since. Требует работающий Seek() для определения размера.

func ServeFile

func ServeFile(w ResponseWriter, r *Request, name string)

Отправляет содержимое файла или директории. Особенности:

  • Отклоняет пути с “..” (защита от traversal-атак)
  • Перенаправляет запросы к “/index.html” на родительский каталог
  • Использует только параметр name (игнорирует r.URL.Path)
  • Относительные пути разрешаются от текущего каталога

func ServeFileFS

func ServeFileFS(w ResponseWriter, r *Request, fsys fs.FS, name string)

Отправляет содержимое указанного файла или директории из файловой системы fsys в ответ на запрос. Файлы в fsys должны реализовывать интерфейс io.Seeker.

Меры предосторожности:

  1. Если имя (name) формируется из пользовательского ввода, его необходимо санировать перед вызовом
  2. Автоматически отклоняет запросы с “..” в r.URL.Path (защита от path traversal)
  3. Специальный случай: перенаправляет запросы, оканчивающиеся на “/index.html”, на путь без этого суффикса

Важно: выбор файла/директории осуществляется только по параметру name, r.URL.Path игнорируется (кроме двух указанных случаев).

func ServeTLS

func ServeTLS(l net.Listener, handler Handler, certFile, keyFile string) error

Принимает входящие HTTPS-соединения через listener l, создавая новую горутину для обработки каждого соединения. Требования:

  • Если handler не указан (nil), используется DefaultServeMux
  • Необходимо указать файлы сертификата (certFile) и приватного ключа (keyFile)
  • Для цепочки сертификатов certFile должен содержать: серверный сертификат, промежуточные и корневой CA

Особенности:

  • Всегда возвращает non-nil ошибку
  • Для корректной работы требуется правильная настройка TLS

Технические детали:

  1. Для ServeFileFS:

    • Работает с абстрактной файловой системой (fs.FS)
    • Требуется поддержка Seek() для обработки Range-запросов
    • Поведение с “index.html” аналогично классическим веб-серверам
  2. Для ServeTLS:

    • Автоматически активирует HTTP/2 при поддержке
    • Рекомендуется использовать современные параметры шифрования
    • Для production-среды следует использовать полную цепочку сертификатов

func SetCookie

func SetCookie(w ResponseWriter, cookie *Cookie)

Добавляет Set-Cookie заголовок в ответ. Некорректные cookie могут игнорироваться.

func StatusText

func StatusText(code int) string

Возвращает текстовое описание HTTP-статуса. Для неизвестных кодов возвращает пустую строку.

15.3.2 - Типы пакета net/http языка Go

Типы и методы пакета http для реализации HTTP-клиента и сервера.

type Client

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

    // CheckRedirect определяет политику обработки редиректов.
    // Если задан, вызывается перед каждым редиректом.
    // Принимает новый запрос (req) и цепочку выполненных запросов (via).
    // Если возвращает ошибку, клиент прекращает выполнение.
    // Особый случай: ErrUseLastResponse возвращает последний Response без закрытия тела.
    // Если nil, используется политика по умолчанию (максимум 10 редиректов).
    CheckRedirect func(req *Request, via []*Request) error

    // Jar хранит куки для автоматической подстановки в запросы.
    // Обновляется при получении ответов с Set-Cookie.
    // Если nil, куки отправляются только если явно заданы в Request.
    Jar CookieJar

    // Timeout устанавливает общий таймаут для запроса:
    // включает подключение, редиректы и чтение тела ответа.
    // Таймер продолжает работать после вызова Get/Post/Do.
    // Нулевое значение отключает таймаут.
    // Для отмены используется контекст запроса.
    Timeout time.Duration
}

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

Особенности реализации:

  1. Transport обычно содержит состояние (кешированные TCP-соединения), поэтому клиенты должны переиспользоваться
  2. Потокобезопасен для использования из нескольких горутин
  3. Работает на более высоком уровне, чем RoundTripper, обрабатывая:
    • Куки (через Jar)
    • Редиректы (через CheckRedirect)
    • Таймауты

Политика редиректов:

При перенаправлениях клиент:

  1. Не пересылает sensitive-заголовки (Authorization, WWW-Authenticate, Cookie) на:
    • Домены, не являющиеся поддоменами исходного (с foo.com на bar.com)
  2. Особенности Cookie:
    • При наличии Jar: мутировавшие куки исключаются (Jar сам добавит обновленные)
    • Без Jar: исходные куки пересылаются без изменений

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

client := &http.Client{
    Timeout: 10 * time.Second,
    Jar:     cookieJar,
}
resp, err := client.Get("https://example.com")

func (*Client) CloseIdleConnections

(добавлено в Go 1.12)

func (c *Client) CloseIdleConnections()

Закрывает все бездействующие keep-alive соединения в Transport. Не прерывает активные соединения. Если Transport не поддерживает этот метод, операция не выполняется.

func (*Client) Do

func (c *Client) Do(req *Request) (*Response, error)

Выполняет HTTP-запрос с учетом настроек клиента (редиректы, куки, аутентификация). Особенности:

  • Возвращает ошибку только при проблемах сети или срабатывании политик (CheckRedirect)
  • Не считает HTTP-коды ≠ 2xx ошибкой
  • Тело ответа (Body) всегда требует закрытия, даже при ошибках
  • При редиректах:
    • 301/302/303 → GET/HEAD (тело не сохраняется)
    • 307/308 → сохраняет метод и тело (если определен Request.GetBody)
  • Ошибки имеют тип *url.Error с флагом Timeout()

Пример:

req, _ := http.NewRequest("GET", url, nil)
resp, err := client.Do(req)
if err != nil { /* обработка */ }
defer resp.Body.Close()

func (*Client) Get

func (c *Client) Get(url string) (*Response, error)

Выполняет GET-запрос с автоматическим следованием редиректам (301, 302, 303, 307, 308). Особенности:

  • Не возвращает ошибку при кодах ≠ 2xx
  • Тело ответа требует закрытия
  • Для кастомных заголовков используйте NewRequest + Do

func (*Client) Head

func (c *Client) Head(url string) (*Response, error)

Аналогично Get, но отправляет HEAD-запрос. Также обрабатывает редиректы.

func (*Client) Post

func (c *Client) Post(url, contentType string, body io.Reader) (*Response, error)

Выполняет POST-запрос с указанным Content-Type. Особенности:

  • Если body реализует io.Closer, оно закрывается автоматически
  • Для сложных запросов используйте NewRequest + Do

func (*Client) PostForm

func (c *Client) PostForm(url string, data url.Values) (*Response, error)

Отправляет POST с x-www-form-urlencoded данными. Автоматически:

  • Кодирует данные
  • Устанавливает Content-Type
  • Обрабатывает редиректы

type ConnState

type ConnState int

ConnState представляет состояние клиентского соединения с сервером. Используется в хуке Server.ConnState для отслеживания жизненного цикла соединений.

Состояния соединения:

const (
    // StateNew - новое соединение, ожидается отправка запроса
    StateNew ConnState = iota
    
    // StateActive - соединение получило данные запроса (1+ байт)
    StateActive
    
    // StateIdle - соединение в режиме keep-alive между запросами
    StateIdle
    
    // StateHijacked - соединение было захвачено (hijacked)
    StateHijacked
    
    // StateClosed - соединение закрыто
    StateClosed
)

Особенности:

  1. Для HTTP/2:

    • StateActive активируется при переходе от 0 к 1 активному запросу
    • Переход в другое состояние только после завершения всех запросов
  2. Терминальные состояния:

    • StateHijacked - не переходит в StateClosed
    • StateClosed - конечное состояние

func (ConnState) String

func (c ConnState) String() string

Возвращает строковое представление состояния соединения.

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

  1. Базовый мониторинг соединений:
server := &http.Server{
    ConnState: func(conn net.Conn, state http.ConnState) {
        log.Printf("Соединение %v: %v -> %v", 
            conn.RemoteAddr(), 
            conn.LocalAddr(), 
            state.String())
    },
}
  1. Ограничение активных соединений:
var activeConns int32

server := &http.Server{
    ConnState: func(conn net.Conn, state http.ConnState) {
        switch state {
        case http.StateActive:
            if atomic.AddInt32(&activeConns, 1) > 100 {
                conn.Close() // закрываем при превышении лимита
            }
        case http.StateClosed, http.StateHijacked:
            atomic.AddInt32(&activeConns, -1)
        }
    },
}
  1. Логирование времени жизни соединения:
type connInfo struct {
    start time.Time
}

server := &http.Server{
    ConnState: func(conn net.Conn, state http.ConnState) {
        ctx := conn.Context()
        if state == http.StateNew {
            ctx = context.WithValue(ctx, "connInfo", &connInfo{
                start: time.Now(),
            })
            conn = conn.WithContext(ctx)
            return
        }
        
        if state == http.StateClosed {
            if info, ok := ctx.Value("connInfo").(*connInfo); ok {
                duration := time.Since(info.start)
                log.Printf("Соединение жило %v", duration)
            }
        }
    },
}

Типичный жизненный цикл соединения:

StateNew → StateActive → StateIdle → StateActive → ... → StateClosed
                    \           /
                     → StateHijacked

Для HTTP/2 состояния меняются реже, так как одно соединение обслуживает множество запросов параллельно.

Вот профессиональный перевод документации типа Cookie с примерами использования:

type Cookie struct {
    Name   string    // Название cookie (обязательное)
    Value  string    // Значение cookie
    Quoted bool      // Флаг, было ли значение в кавычках

    // Опциональные атрибуты:
    Path       string    // Путь действия cookie
    Domain     string    // Домен действия
    Expires    time.Time // Время истечения
    RawExpires string    // Оригинальное значение Expires (только для чтения)

    // MaxAge определяет срок жизни в секундах:
    //  0 - без Max-Age
    // <0 - немедленное удаление (аналог Max-Age: 0)
    // >0 - срок жизни в секундах
    MaxAge      int
    Secure      bool     // Только для HTTPS
    HttpOnly    bool     // Недоступно для JavaScript
    SameSite    SameSite // Политика SameSite
    Partitioned bool     // Разделенные cookie (CHIPS)
    Raw         string   // Оригинальное значение заголовка
    Unparsed    []string // Неразобранные атрибуты
}

Тип Cookie представляет HTTP-куки, используемые в заголовках:

  • Set-Cookie - при установке сервером
  • Cookie - при отправке клиентом

Соответствует спецификации RFC 6265.

// Простая cookie
sessionCookie := &http.Cookie{
    Name:  "session_id",
    Value: "abc123",
}

// Защищенная cookie с атрибутами
secureCookie := &http.Cookie{
    Name:     "prefs",
    Value:    "dark_theme=true",
    Path:     "/",
    Domain:   "example.com",
    MaxAge:   3600, // 1 час
    Secure:   true,
    HttpOnly: true,
    SameSite: http.SameSiteLaxMode,
}

func ParseCookie

(добавлено в Go 1.23)

func ParseCookie(line string) ([]*Cookie, error)

Разбирает заголовок Cookie (клиентские куки), возвращая все найденные куки. Одно имя может встречаться несколько раз.

Пример:

cookies, err := http.ParseCookie("name1=val1; name2=val2; name1=val3")
if err != nil { /* обработка ошибки */ }
for _, c := range cookies {
    fmt.Printf("%s: %s\n", c.Name, c.Value)
}
// Вывод:
// name1: val1
// name2: val2
// name1: val3

func ParseSetCookie

(добавлено в Go 1.23)

func ParseSetCookie(line string) (*Cookie, error)

Разбирает заголовок Set-Cookie (серверные куки), возвращая одну куку.

Пример:

cookie, err := http.ParseSetCookie("session=abc123; Path=/; HttpOnly; Max-Age=3600")
if err != nil { /* обработка ошибки */ }
fmt.Printf("Cookie: %s=%s, Path=%s\n", cookie.Name, cookie.Value, cookie.Path)
func (c *Cookie) String() string

Форматирует куку для заголовка:

  • Для Cookie: только имя и значение (name=value)
  • Для Set-Cookie: со всеми атрибутами

Пример:

c := &http.Cookie{Name: "test", Value: "123"}
fmt.Println(c.String()) // "test=123"

c.Path = "/"
c.HttpOnly = true
fmt.Println(c.String()) // "test=123; Path=/; HttpOnly"
func (c *Cookie) Valid() error

Проверяет валидность куки (корректность имени и других атрибутов).

Пример:

err := (&http.Cookie{Name: "invalid name", Value: "test"}).Valid()
if err != nil {
    fmt.Println("Invalid cookie:", err)
}

Практические примеры:

  1. Установка cookie в ResponseWriter:
func handler(w http.ResponseWriter, r *http.Request) {
    cookie := &http.Cookie{
        Name:  "user",
        Value: "john",
        Path:  "/",
    }
    http.SetCookie(w, cookie)
}
  1. Чтение cookie из Request:
func handler(w http.ResponseWriter, r *http.Request) {
    cookie, err := r.Cookie("session_id")
    if err != nil {
        // Обработка отсутствия cookie
    }
    fmt.Fprintf(w, "Value: %s", cookie.Value)
}
  1. Удаление cookie:
func logoutHandler(w http.ResponseWriter, r *http.Request) {
    http.SetCookie(w, &http.Cookie{
        Name:   "session",
        Value:  "",
        Path:   "/",
        MaxAge: -1, // Удалить cookie
    })
}
  1. Проверка SameSite политики:
cookie := &http.Cookie{
    Name:     "csrf_token",
    Value:    generateToken(),
    SameSite: http.SameSiteStrictMode,
    Secure:   true,
}

Вот перевод документации с примерами:

type CookieJar

type CookieJar interface {
    // SetCookies сохраняет полученные куки для указанного URL
    SetCookies(u *url.URL, cookies []*Cookie)
    
    // Cookies возвращает куки, которые должны быть отправлены для URL
    Cookies(u *url.URL) []*Cookie
}

CookieJar управляет хранением и использованием кук в HTTP-запросах. Реализации должны быть потокобезопасными.

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

jar, _ := cookiejar.New(nil)
client := &http.Client{
    Jar: jar,
}

// Первый запрос (получаем куки)
resp, _ := client.Get("https://example.com/login")

// Последующие запросы автоматически включают куки
resp, _ = client.Get("https://example.com/dashboard")

type Dir

type Dir string

Dir реализует FileSystem для работы с файловой системой в пределах указанной директории.

Важные предупреждения:

  1. Может предоставлять доступ к чувствительным файлам
  2. Следует по символическим ссылкам за пределы директории
  3. Не фильтрует файлы, начинающиеся с точки (например, .git)

Пример:

fs := http.Dir("/var/www")
http.Handle("/static/", http.FileServer(fs))

func (Dir) Open

func (d Dir) Open(name string) (File, error)

Открывает файл для чтения, используя os.Open. Пути должны использовать / как разделитель.

Пример:

fs := http.Dir("public")
file, err := fs.Open("css/styles.css")
if err != nil {
    // обработка ошибки
}
defer file.Close()

type File

type File interface {
    io.Closer
    io.Reader
    io.Seeker
    Readdir(count int) ([]fs.FileInfo, error)
    Stat() (fs.FileInfo, error)
}

Интерфейс файла, возвращаемый FileSystem.Open. Аналогичен *os.File.

type FileSystem

type FileSystem interface {
    Open(name string) (File, error)
}

Интерфейс для работы с коллекцией файлов. Использует / как разделитель путей.

Пример реализации:

type CustomFS struct {
    base string
}

func (c CustomFS) Open(name string) (http.File, error) {
    return os.Open(filepath.Join(c.base, name))
}

func FS

func FS(fsys fs.FS) FileSystem

Конвертирует fs.FS в FileSystem. Файлы должны реализовывать io.Seeker.

Пример с embed:

//go:embed static/*
var staticFiles embed.FS

func main() {
    http.Handle("/", http.FileServer(http.FS(staticFiles)))
    http.ListenAndServe(":8080", nil)
}

Дополнительные примеры:

  1. Защищенная FileSystem (без доступа к скрытым файлам):
type SafeFS struct {
    http.Dir
}

func (s SafeFS) Open(name string) (http.File, error) {
    if strings.Contains(name, "..") || strings.HasPrefix(filepath.Base(name), ".") {
        return nil, os.ErrNotExist
    }
    return s.Dir.Open(name)
}
  1. Кастомный CookieJar:
type MemoryJar struct {
    cookies map[string][]*http.Cookie
    sync.Mutex
}

func (j *MemoryJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
    j.Lock()
    defer j.Unlock()
    j.cookies[u.Host] = cookies
}

func (j *MemoryJar) Cookies(u *url.URL) []*http.Cookie {
    j.Lock()
    defer j.Unlock()
    return j.cookies[u.Host]
}

type Flusher

type Flusher interface {
	// Flush sends any buffered data to the client.
	Flush()
}

Интерфейс Flusher реализуется в ResponseWriter, которые позволяют HTTP-обработчику отправлять буферизованные данные клиенту.

Реализации ResponseWriter по умолчанию HTTP/1.x и HTTP/2 поддерживают Flusher, но обертки ResponseWriter могут этого не делать. Обработчики всегда должны проверять эту возможность во время выполнения.

Обратите внимание, что даже для ResponseWriter, поддерживающих Flush, если клиент подключен через HTTP-прокси, буферизованные данные могут не дойти до клиента до завершения ответа.

type HTTP2Config

type HTTP2Config struct {
	// MaxConcurrentStreams опционально задает количество
	// параллельных потоков, которые могут быть открыты у пира одновременно.
	// Если значение равно нулю, то по умолчанию MaxConcurrentStreams равно как минимум 100.
	MaxConcurrentStreams int

	// MaxDecoderHeaderTableSize опционально задает верхний предел для
	// размера таблицы сжатия заголовков, используемой для декодирования заголовков, отправленных
	// пиром.
	// Допустимое значение - менее 4 Мбайт.
	// Если значение равно нулю или недействительно, используется значение по умолчанию.
	MaxDecoderHeaderTableSize int

	// MaxEncoderHeaderTableSize опционально задает верхний предел для
	// таблицы сжатия заголовков, используемой для отправки заголовков сверстнику.
	// Допустимое значение - менее 4 Мбайт.
	// Если значение равно нулю или недействительно, используется значение по умолчанию.
	MaxEncoderHeaderTableSize int

	// MaxReadFrameSize опционально указывает самый большой кадр.
	// который эта конечная точка готова прочитать.
	// Допустимое значение - от 16КиБ до 16МиБ, включительно.
	// Если значение равно нулю или недействительно, используется значение по умолчанию.
	MaxReadFrameSize int

	// MaxReceiveBufferPerConnection - это максимальный размер
	// окна управления потоком для данных, получаемых по соединению.
	// Допустимое значение - не менее 64КиБ и не более 4МиБ.
	// Если значение недействительно, используется значение по умолчанию.
	MaxReceiveBufferPerConnection int

	// MaxReceiveBufferPerStream - это максимальный размер
	// окна управления потоком для данных, получаемых в потоке (запросе).
	// Допустимое значение - менее 4 Мбайт.
	// Если значение равно нулю или недействительно, используется значение по умолчанию.
	MaxReceiveBufferPerStream int

	// SendPingTimeout - таймаут, по истечении которого проверка работоспособности с помощью ping
	// кадра будет проведена, если на соединении не будет получено ни одного кадра.
	// Если ноль, проверка работоспособности не выполняется.
	SendPingTimeout time.Duration

	// PingTimeout - таймаут, по истечении которого соединение будет закрыто.
	// если не будет получен ответ на пинг.
	// Если значение равно нулю, используется значение по умолчанию 15 секунд.
	PingTimeout time.Duration

	// WriteByteTimeout - таймаут, по истечении которого соединение будет // закрыто, если не удастся записать данные.
	// закрыто, если в него не могут быть записаны данные. Таймаут начинается, когда данные // доступны для записи.
	// доступны для записи, и продлевается каждый раз, когда записываются какие-либо байты.
	WriteByteTimeout time.Duration

	// PermitProhibitedCipherSuites, если true, разрешает использование
	// наборов шифров, запрещенных спецификацией HTTP/2.
	PermitProhibitedCipherSuites bool

	// CountError, если не равен nil, вызывается при ошибках HTTP/2.
	// Она предназначена для инкрементирования метрики для мониторинга.
	// Тип errType содержит только строчные буквы, цифры и символы подчеркивания.
	// (a-z, 0-9, _).
	CountError func(errType string)
}

HTTP2Config определяет параметры конфигурации HTTP/2, общие для транспорта и сервера.

type Handler

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

Обработчик отвечает на HTTP-запрос.

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

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

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

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

Обработчик отвечает на HTTP-запрос.

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

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

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

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

func AllowQuerySemicolons

func AllowQuerySemicolons(h Handler) Handler

AllowQuerySemicolons возвращает обработчик, который обслуживает запросы, преобразуя любые неэскэпированные точки с запятой в URL-запросе в амперсанд и вызывая обработчик h.

Это восстанавливает поведение, существовавшее до версии Go 1.17, когда параметры запроса разделялись как на точки с запятой, так и на амперсанд. (См. golang.org/issue/25192). Обратите внимание, что такое поведение не соответствует поведению многих прокси-серверов, и это несоответствие может привести к проблемам безопасности.

AllowQuerySemicolons должен быть вызван до вызова Request.ParseForm.

func FileServer

func FileServer(root FileSystem) Handler

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

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

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

http.Handle(«/», http.FileServer(http.Dir(«/tmp»)))

Чтобы использовать реализацию fs.FS, используйте http.FileServerFS.

Пример
package main

import (
	"log"
	"net/http"
)

func main() {
	// Simple static webserver:
	log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
}
Пример DotFileHiding
пакет main

импорт (
	«io»
	«io/fs»
	«log»
	«net/http»
	«strings»
)

// containsDotFile сообщает, содержит ли имя элемент пути, начинающийся с точки.
// Предполагается, что имя представляет собой файл, разделенный прямыми косыми чертами, как это гарантируется
// интерфейсом http.FileSystem.
func containsDotFile(name string) bool {
	parts := strings.Split(name, «/»)
	for _, part := range parts {
		if strings.HasPrefix(part, «.») {
			return true
		}
	}
	return false
}

// dotFileHidingFile - это http.File, используемый в dotFileHidingFileSystem.
// Он используется для обертывания метода Readdir из http.File, чтобы мы могли
// удалять из его вывода файлы и каталоги, начинающиеся с точки.
тип dotFileHidingFile struct {
	http.File
}

// Readdir - это обертка вокруг метода Readdir встроенного File.
// которая отфильтровывает все файлы, имя которых начинается с точки.
func (f dotFileHidingFile) Readdir(n int) (fis []fs.FileInfo, err error) {
	files, err := f.File.Readdir(n)
	for _, file := range files { // Отфильтровываем файлы с точками
		if !strings.HasPrefix(file.Name(), «.») {
			fis = append(fis, file)
		}
	}
	if err == nil && n > 0 && len(fis) == 0 {
		err = io.EOF
	}
	return
}

// dotFileHidingFileSystem - это http.FileSystem, которая скрывает
// скрытые «dot-файлы» от обслуживания.
тип dotFileHidingFileSystem struct {
	http.FileSystem
}

// Open - это обертка вокруг метода Open встроенной файловой системы.
// который выдает ошибку разрешения 403, если в имени есть файл или каталог
// в пути которого имя начинается с точки.
func (fsys dotFileHidingFileSystem) Open(name string) (http.File, error) {
	if containsDotFile(name) { // Если файл с точкой, возвращаем ответ 403
		return nil, fs.ErrPermission
	}

	file, err := fsys.FileSystem.Open(name)
	if err != nil {
		return nil, err
	}
	return dotFileHidingFile{file}, err
}

func main() {
	fsys := dotFileHidingFileSystem{http.Dir(«.»)}
	http.Handle(«/», http.FileServer(fsys))
	log.Fatal(http.ListenAndServe(«:8080», nil))
}
Пример StripPrefix
пакет main

импорт (
	«net/http»
)

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

func FileServerFS

func FileServerFS(root fs.FS) Обработчик

FileServerFS возвращает обработчик, который обслуживает HTTP-запросы с содержимым файловой системы fsys. Файлы, предоставляемые fsys, должны реализовывать io.Seeker.

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

http.Handle(«/», http.FileServerFS(fsys))

func MaxBytesHandler

func MaxBytesHandler(h Handler, n int64) Handler

MaxBytesHandler возвращает обработчик, который запускает h с его ResponseWriter и [Request.Body], обернутыми MaxBytesReader.

func NotFoundHandler

func NotFoundHandler() Handler

NotFoundHandler возвращает простой обработчик запросов, который отвечает на каждый запрос ответом «404 страница не найдена».

Пример
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()

	// Create sample handler to returns 404
	mux.Handle("/resources", http.NotFoundHandler())

	// Create sample handler that returns 200
	mux.Handle("/resources/people/", newPeopleHandler())

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

func RedirectHandler

func RedirectHandler(url string, code int) Обработчик

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

Заданный код должен быть в диапазоне 3xx и обычно является StatusMovedPermanently, StatusFound или StatusSeeOther.

func StripPrefix

func StripPrefix(prefix string, h Handler) Handler

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

Пример
package main

import (
	"net/http"
)

func main() {
	// To serve a directory on disk (/tmp) under an alternate URL
	// path (/tmpfiles/), use StripPrefix to modify the request
	// URL's path before the FileServer sees it:
	http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
}

func TimeoutHandler

func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler

TimeoutHandler возвращает обработчик, который запускает h с заданным лимитом времени.

Новый обработчик вызывает h.ServeHTTP для обработки каждого запроса, но если вызов выполняется дольше установленного лимита времени, обработчик отвечает ошибкой 503 Service Unavailable с заданным сообщением в теле. (Если msg пустое, будет отправлено подходящее сообщение по умолчанию). После такого таймаута запись h в свой ResponseWriter будет возвращать ErrHandlerTimeout.

TimeoutHandler поддерживает интерфейс Pusher, но не поддерживает интерфейсы Hijacker и Flusher.

type HandlerFunc

type HandlerFunc func(ResponseWriter, *Request)

Тип HandlerFunc - это адаптер, позволяющий использовать обычные функции в качестве HTTP-обработчиков. Если f - функция с соответствующей сигнатурой, то HandlerFunc(f) - это обработчик, вызывающий f.

func (HandlerFunc) ServeHTTP

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)

ServeHTTP вызывает f(w, r).

type Header

type Header map[string][]string

Header представляет пары ключ-значение в HTTP-заголовке.

Ключи должны быть в канонической форме, как возвращает CanonicalHeaderKey.

func (Header) Add

func (h Header) Add(key, value string)

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

func (Header) Clone

func (h Header) Clone() Header

Clone возвращает копию h или nil, если h - nil.

func (Header) Del

func (h Header) Del(key string)

Del удаляет значения, связанные с ключом. Ключ не чувствителен к регистру; он канонизируется с помощью CanonicalHeaderKey.

func (Header) Get

func (h Header) Get(key string) string

Get получает первое значение, связанное с заданным ключом. Если значений, связанных с ключом, нет, Get возвращает “”. Нечувствительно к регистру; textproto.CanonicalMIMEHeaderKey используется для канонизации предоставленного ключа. Get предполагает, что все ключи хранятся в канонической форме. Чтобы использовать неканонические ключи, обращайтесь к карте напрямую.

func (Header) Set

func (h Header) Set(key, value string)

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

func (Header) Values

func (h Header) Values(key string) []string

Values возвращает все значения, связанные с заданным ключом. Нечувствительно к регистру; textproto.CanonicalMIMEHeaderKey используется для канонизации заданного ключа. Чтобы использовать неканонические ключи, обращайтесь к карте напрямую. Возвращаемый фрагмент не является копией.

func (Header) Write

func (h Header) Write(w io.Writer) error

Write записывает заголовок в формате wire.

func (Header) WriteSubset

func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error

WriteSubset записывает заголовок в формате wire. Если exclude не равно nil, ключи, для которых exclude[key] == true, не записываются. Ключи не канонизируются перед проверкой карты exclude.

type Hijacker

type Hijacker interface
	// Hijack позволяет вызывающей стороне взять на себя управление соединением.
	// После вызова Hijack библиотека HTTP-сервера
	// больше ничего не будет делать с соединением.
	//
	// Ответственность за управление // и закрытие соединения ложится на вызывающую сторону.
	// и закрывать соединение.
	//
	// Возвращаемый net.Conn может иметь дедлайны чтения или записи.
	// уже установленными, в зависимости от конфигурации
	// сервера. Вызывающая сторона несет ответственность за установку
	// или очистить эти сроки по мере необходимости.
	//
	// Возвращаемый файл bufio.Reader может содержать необработанные буферизованные
	// данные от клиента.
	//
	// После вызова Hijack исходное Request.Body не должно
	// использоваться. Контекст исходного запроса остается действительным и
	// не отменяется до тех пор, пока метод ServeHTTP запроса
	// возвращается.
	Hijack() (net.Conn, *bufio.ReadWriter, error)
}

Интерфейс Hijacker реализуется в ResponseWriter’ах, которые позволяют HTTP-обработчику взять на себя управление соединением.

ResponseWriter по умолчанию для соединений HTTP/1.x поддерживает Hijacker, но соединения HTTP/2 намеренно не поддерживают его. Обертки ResponseWriter также могут не поддерживать Hijacker. Обработчики всегда должны проверять эту возможность во время выполнения.

Пример
package main

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

func main() {
	http.HandleFunc("/hijack", func(w http.ResponseWriter, r *http.Request) {
		hj, ok := w.(http.Hijacker)
		if !ok {
			http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
			return
		}
		conn, bufrw, err := hj.Hijack()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		// Don't forget to close the connection:
		defer conn.Close()
		bufrw.WriteString("Now we're speaking raw TCP. Say hi: ")
		bufrw.Flush()
		s, err := bufrw.ReadString('\n')
		if err != nil {
			log.Printf("error reading string: %v", err)
			return
		}
		fmt.Fprintf(bufrw, "You said: %q\nBye.\n", s)
		bufrw.Flush()
	})
}

type MaxBytesError

type MaxBytesError struct {
	Limit int64
}

MaxBytesError возвращается MaxBytesReader, когда его лимит чтения превышен.

func (*MaxBytesError) Error

func (e *MaxBytesError) Error() string

type ProtocolError

устаревший

type Protocols

type Protocols struct {
	// содержит отфильтрованные или неэкспонированные поля
}

Protocols - это набор протоколов HTTP. Нулевое значение - это пустой набор протоколов.

Поддерживаются следующие протоколы:

  • HTTP1 - протоколы HTTP/1.0 и HTTP/1.1. HTTP1 поддерживается как на незащищенных TCP, так и на защищенных TLS соединениях.
  • HTTP2 - протокол HTTP/2 через TLS-соединение.
  • UnencryptedHTTP2 - протокол HTTP/2 через незащищенное TCP-соединение.
Пример Http1
package main

import (
	"log"
	"net/http"
)

func main() {
	srv := http.Server{
		Addr: ":8443",
	}

	// Serve only HTTP/1.
	srv.Protocols = new(http.Protocols)
	srv.Protocols.SetHTTP1(true)

	log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem"))
}
Пример Http1or2
package main

import (
	"log"
	"net/http"
)

func main() {
	t := http.DefaultTransport.(*http.Transport).Clone()

	// Use either HTTP/1 and HTTP/2.
	t.Protocols = new(http.Protocols)
	t.Protocols.SetHTTP1(true)
	t.Protocols.SetHTTP2(true)

	cli := &http.Client{Transport: t}
	res, err := cli.Get("http://www.google.com/robots.txt")
	if err != nil {
		log.Fatal(err)
	}
	res.Body.Close()
}

func (Protocols) HTTP1

func (p Protocols) HTTP1() bool

HTTP1 сообщает, включает ли p протокол HTTP/1.

func (Протоколы) HTTP2

func (p Protocols) HTTP2() bool

HTTP2 сообщает, включает ли p протокол HTTP/2.

func (*Protocols) SetHTTP1

func (p *Protocols) SetHTTP1(ok bool)

SetHTTP1 добавляет или удаляет HTTP/1 из p.

func (*Protocols) SetHTTP2

func (p *Protocols) SetHTTP2(ok bool)

SetHTTP2 добавляет или удаляет HTTP/2 из p.

func (*Protocols) SetUnencryptedHTTP2

func (p *Protocols) SetUnencryptedHTTP2(ok bool)

SetUnencryptedHTTP2 добавляет или удаляет незашифрованный HTTP/2 из p.

func (Protocols) String

func (p Protocols) String() string

func (Protocols) UnencryptedHTTP2

func (p Protocols) UnencryptedHTTP2() bool

UnencryptedHTTP2 сообщает, включает ли p незашифрованный HTTP/2.

type PushOptions

type PushOptions struct {
	// Method указывает HTTP-метод для обещанного запроса.
	// Если установлен, он должен быть «GET» или «HEAD». Пустое значение означает «GET».
	Method string

	// Header указывает дополнительные заголовки обещанного запроса. Они не могут
	// включать поля псевдозаголовков HTTP/2, такие как «:path» и «:scheme»,
	// которые будут добавлены автоматически.
	Заголовок Header
}

PushOptions описывает опции для [Pusher.Push].

type Pusher

type Pusher interface {
	// Push инициирует HTTP/2 серверный push. При этом создается синтетический
	// запрос, используя заданную цель и опции, сериализует этот запрос
	// в фрейм PUSH_PROMISE, затем отправляет этот запрос с помощью
	// обработчика запроса сервера. Если opts равно nil, используются параметры по умолчанию.
	//
	// Цель должна быть либо абсолютным путем (например, «/path»), либо абсолютным
	// URL, содержащий действительный хост и ту же схему, что и родительский запрос.
	// Если цель - это путь, то она наследует схему и хост // родительского запроса.
	// родительского запроса.
	//
	// Спецификация HTTP/2 запрещает рекурсивные и кросс-авторитарные проталкивания.
	// Push может обнаружить или не обнаружить эти недействительные проталкивания; однако недействительные
	// push будут обнаружены и отменены соответствующими клиентами.
	//
	// Обработчики, желающие передать URL X, должны вызывать Push перед отправкой любых
	// данные, которые могут вызвать запрос на URL X. Это позволяет избежать гонки, когда
	// клиент отправляет запрос на X до получения PUSH_PROMISE для X.
	//
	// Push будет выполняться в отдельной горутине, что делает порядок прибытия
	// недетерминированным. Любая требуемая синхронизация должна быть реализована
	// вызывающей стороной.
	//
	// Push возвращает ErrNotSupported, если клиент отключил push или если push
	// не поддерживается на базовом соединении.
	Ошибка Push(target string, opts *PushOptions)
}

Pusher - это интерфейс, реализованный в ResponseWriters, которые поддерживают HTTP/2 server push. Более подробную информацию можно найти на сайте https://tools.ietf.org/html/rfc7540#section-8.2.

type Request

type Request struct {
	// Method задает метод HTTP (GET, POST, PUT и т.д.).
	// Для клиентских запросов пустая строка означает GET.
	Строка Method

	// URL указывает либо запрашиваемый URI (для серверных
	// запросов), либо URL для доступа (для клиентских запросов).
	//
	// Для серверных запросов URL анализируется из URI.
	// предоставленного в строке запроса и хранящегося в RequestURI.  Для
	// большинства запросов поля, кроме Path и RawQuery, будут // пустыми.
	// пустыми. (См. RFC 7230, раздел 5.3)
	//
	// Для клиентских запросов URL's Host указывает сервер, к которому нужно // подключиться.
	// соединиться с ним, а поле Request's Host опционально
	// определяет значение заголовка Host для отправки в HTTP
	// запросе.
	URL *url.URL

	// Версия протокола для входящих запросов сервера.
	//
	// Для клиентских запросов эти поля игнорируются. HTTP
	// клиентский код всегда использует либо HTTP/1.1, либо HTTP/2.
	// Подробнее см. документацию по транспорту.
	Proto string // «HTTP/1.0»
	ProtoMajor int // 1
	ProtoMinor int // 0

	// Заголовок содержит поля заголовка запроса, которые либо получены
	// сервером, либо должны быть отправлены клиентом.
	//
	// Если сервер получил запрос со строками заголовка,
	//
	// Host: example.com
	// accept-encoding: gzip, deflate
	// Accept-Language: en-us
	// fOO: Bar
	// foo: two
	//
	// тогда
	//
	// Header = map[string][]string{
	// «Accept-Encoding»: { «gzip», «deflate» }
	// «Accept-Language»: {«en-us»},
	// «Foo»: { «Bar», «2»},
	// }
	//
	// Для входящих запросов заголовок Host переводится в поле
	// поле Request.Host и удаляется из карты Header.
	//
	// HTTP определяет, что имена заголовков не чувствительны к регистру. В
	// парсер запросов реализует это с помощью CanonicalHeaderKey,
	// делая первый символ и все символы, следующие за
	// дефисом прописными, а остальные - строчными.
	//
	// Для клиентских запросов некоторые заголовки, такие как Content-Length
	// и Connection, записываются автоматически, когда это необходимо, и
	// значения в Header могут игнорироваться. См. документацию
	// по методу Request.Write.
	Request Header

	// Body - это тело запроса.
	//
	// Для клиентских запросов нулевое тело означает, что запрос не имеет
	// тело, например, запрос GET. Транспорт HTTP-клиента
	// отвечает за вызов метода Close.
	//
	// Для серверных запросов тело запроса всегда не является nil
	// но при отсутствии тела немедленно возвращается EOF.
	// Сервер закроет тело запроса. Обработчику ServeHTTP
	// обработчику не нужно.
	//
	// Тело должно позволять вызывать Read одновременно с Close.
	// В частности, вызов Close должен разблокировать Read, ожидающий
	// ввода.
	Body io.ReadCloser

	// GetBody определяет необязательную функцию для возврата новой копии
	// Body. Она используется для клиентских запросов, когда перенаправление требует.
	// чтения тела более одного раза. Использование GetBody по-прежнему
	// требует установки Body.
	//
	// Для серверных запросов он не используется.
	GetBody func() (io.ReadCloser, error)

	// ContentLength записывает длину связанного содержимого.
	// Значение -1 указывает на то, что длина неизвестна.
	// Значения >= 0 указывают, что данное количество байт может быть
	// быть прочитано из Body.
	//
	// Для клиентских запросов значение 0 с ненулевым Body
	// также рассматривается как неизвестное.
	ContentLength int64

	// TransferEncoding перечисляет кодировки передачи данных от крайних к
	// внутренней. Пустой список обозначает «идентичную» кодировку.
	// TransferEncoding обычно можно игнорировать; кодировка chunked
	// автоматически добавляется и удаляется по мере необходимости при отправке и
	// получении запросов.
	TransferEncoding []string

	// Close указывает, закрывать ли соединение после
	// ответа на этот запрос (для серверов) или после отправки этого
	// запроса и чтения его ответа (для клиентов).
	//
	// Для серверных запросов HTTP-сервер обрабатывает это автоматически.
	// и это поле не нужно обработчикам.
	//
	// Для клиентских запросов установка этого поля предотвращает повторное использование
	// TCP-соединений между запросами к одним и тем же хостам, как если бы
	// Transport.DisableKeepAlives был установлен.
	Close bool

	// Для серверных запросов поле Host указывает хост, на котором
	// URL-адрес. Для HTTP/1 (согласно RFC 7230, раздел 5.4), это
	// это либо значение заголовка «Host», либо имя хоста.
	// указанное в самом URL. Для HTTP/2 это значение заголовка
	// поля псевдозаголовка «:authority».
	// Оно может иметь вид «хост:порт». Для международных доменных
	// имен, Host может быть в форме Punycode или Unicode. Используйте
	// golang.org/x/net/idna для преобразования его в любой формат, если
	// необходимо.
	// Для предотвращения атак перепривязки DNS обработчики сервера должны
	// проверять, что заголовок Host имеет значение, для которого обработчик считает себя авторитетным.
	// обработчик считает себя авторитетным. Включенный
	// ServeMux поддерживает шаблоны, зарегистрированные на определенные хосты.

	// называет и таким образом защищает свои зарегистрированные обработчики.
	//
	// Для клиентских запросов Host опционально переопределяет заголовок Host
	// заголовок для отправки. Если он пуст, метод Request.Write использует
	// значение URL.Host. Host может содержать международное
	// доменное имя.
	Host String

	// Form содержит разобранные данные формы, включая параметры запроса поля URL
	// параметры запроса поля, так и данные формы PATCH, POST или PUT.
	// Это поле доступно только после вызова ParseForm.
	// HTTP-клиент игнорирует Form и использует вместо нее Body.
	Form url.Values

	// PostForm содержит разобранные данные формы из параметров тела PATCH, POST
	// или параметров тела PUT.
	//
	// Это поле доступно только после вызова ParseForm.
	// HTTP-клиент игнорирует PostForm и использует вместо него Body.
	PostForm url.Values

	// MultipartForm - это разобранная многочастная форма, включая загрузку файлов.
	// Это поле доступно только после вызова ParseMultipartForm.
	// HTTP-клиент игнорирует MultipartForm и использует вместо нее Body.
	MultipartForm *multipart.Form

	// Trailer определяет дополнительные заголовки, которые отправляются после запроса
	// body.
	//
	// Для серверных запросов карта Trailer изначально содержит только
	// ключи трейлеров, со значениями nil. (Клиент объявляет, какие трейлеры он
	// будет отправлять позже.)  Пока обработчик читает из Body, он не должен // ссылаться на Trailer.
	// не ссылаться на Trailer. После того как чтение из Body возвращает EOF, Trailer
	// может быть прочитан снова и будет содержать значения non-nil, если они были отправлены
	// клиентом.
	//
	// Для клиентских запросов Trailer должен быть инициализирован картой, содержащей
	// ключи трейлера для последующей отправки. Значения могут быть nil или их конечные
	// значениями. ContentLength должен быть 0 или -1, чтобы отправить чанкированный запрос.
	// После отправки HTTP-запроса значения карты могут быть обновлены, пока
	// пока читается тело запроса. Как только тело вернется в EOF, вызывающая сторона должна.
	// не мутировать Trailer.
	//
	// Немногие HTTP-клиенты, серверы или прокси поддерживают HTTP-трейлеры.
	Trailer Header

	// RemoteAddr позволяет HTTP-серверам и другому программному обеспечению записывать.
	// сетевой адрес, отправивший запрос, обычно для
	// протоколирования. Это поле не заполняется ReadRequest и // не имеет определенного формата.
	// не имеет определенного формата. HTTP-сервер в этом пакете
	// устанавливает RemoteAddr в адрес «IP:порт» перед вызовом
	// обработчика.
	// Это поле игнорируется HTTP-клиентом.
	RemoteAddr String

	// RequestURI - это немодифицированный запрос-цель в
	// Request-Line (RFC 7230, раздел 3.1.1), отправленный клиентом
	// на сервер. Обычно вместо этого поля следует использовать поле URL.
	// Ошибкой является установка этого поля в HTTP-запросе клиента.
	RequestURI String

	// TLS позволяет HTTP-серверам и другому программному обеспечению записывать
	// информацию о TLS-соединении, по которому был получен запрос
	// был получен. Это поле не заполняется ReadRequest.
	// HTTP-сервер в этом пакете устанавливает поле для
	// соединений с поддержкой TLS перед вызовом обработчика;
	// в противном случае он оставляет поле нулевым.
	// Это поле игнорируется HTTP-клиентом.
	TLS *tls.ConnectionState

	// Cancel - необязательный канал, закрытие которого указывает на то, что клиентский
	// запрос следует считать отмененным. Не все реализации
	// RoundTripper могут поддерживать Cancel.
	//
	// Для серверных запросов это поле неприменимо.
	//
	// Исправлено: Установите контекст запроса с помощью NewRequestWithContext
	// вместо этого. Если поле Cancel и контекст запроса одновременно // установлены.
	// установлены, не определено, соблюдается ли Cancel.
	Cancel <-chan struct{}

	// Response - это ответ перенаправления, который вызвал этот запрос.
	// быть создан. Это поле заполняется только во время клиентских
	// перенаправления.
	Response *Response

	// Pattern - шаблон [ServeMux], который соответствует запросу.
	// Оно пустое, если запрос не был сопоставлен с шаблоном.
	Строка Pattern
	// содержит отфильтрованные или неотфильтрованные поля
}

Запрос представляет собой HTTP-запрос, полученный сервером или отправляемый клиентом.

Семантика полей немного отличается при использовании клиентом и сервером. В дополнение к примечаниям к полям, приведенным ниже, смотрите документацию по Request.Write и RoundTripper.

func NewRequest

func NewRequest(method, url string, body io.Reader) (*Request, error)

NewRequest оборачивает NewRequestWithContext, используя context.Background.

func NewRequestWithContext

func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error)

NewRequestWithContext возвращает новый запрос, заданный методом, URL и необязательным телом.

Если предоставленное тело также является io.Closer, возвращаемое [Request.Body] устанавливается в body и будет закрыто (возможно, асинхронно) методами клиента Do, Post и PostForm, а также Transport.RoundTrip.

NewRequestWithContext возвращает запрос, подходящий для использования с Client.Do или Transport.RoundTrip. Чтобы создать запрос для использования при тестировании обработчика сервера, используйте функцию net/http/httptest.NewRequest, ReadRequest или вручную обновите поля Request. Для исходящего клиентского запроса контекст контролирует все время жизни запроса и его ответа: получение соединения, отправку запроса, чтение заголовков и тела ответа. Разницу между полями входящего и исходящего запроса смотрите в документации к типу Request.

Если body имеет тип *bytes.Buffer, *bytes.Reader или *strings.Reader, то ContentLength возвращаемого запроса устанавливается в точное значение (вместо -1), GetBody заполняется (чтобы 307 и 308 редиректы могли воспроизвести тело), а Body устанавливается в NoBody, если ContentLength равен 0.

func ReadRequest

func ReadRequest(b *bufio.Reader) (*Request, error)

ReadRequest читает и разбирает входящий запрос от b.

ReadRequest является низкоуровневой функцией и должна использоваться только в специализированных приложениях; большинство кода должно использовать сервер для чтения запросов и обрабатывать их через интерфейс Handler. ReadRequest поддерживает только запросы HTTP/1.x. Для HTTP/2 используйте golang.org/x/net/http2.

func (*Request) AddCookie

func (r *Request) AddCookie(c *Cookie)

AddCookie добавляет cookie в запрос. Согласно разделу 5.4 RFC 6265, AddCookie не добавляет более одного поля заголовка Cookie. Это означает, что все куки, если они есть, записываются в одну строку, разделенную точкой с запятой. AddCookie дезинфицирует только имя и значение c, и не дезинфицирует заголовок Cookie, уже присутствующий в запросе.

func (*Request) BasicAuth

func (r *Request) BasicAuth() (имя пользователя, строка пароля, ok bool)

BasicAuth возвращает имя пользователя и пароль, указанные в заголовке авторизации запроса, если запрос использует базовую аутентификацию HTTP. См. RFC 2617, раздел 2.

func (*Request) Clone

func (r *Request) Clone(ctx context.Context) *Request

Clone возвращает глубокую копию r с измененным контекстом на ctx. Предоставленный ctx должен быть ненулевым.

Clone делает неглубокую копию только поля Body.

Для исходящего клиентского запроса контекст контролирует все время жизни запроса и его ответа: получение соединения, отправку запроса, чтение заголовков и тела ответа.

func (*Request) Context

func (r *Request) Context() context.Context

Context возвращает контекст запроса. Чтобы изменить контекст, используйте Request.Clone или Request.WithContext.

Возвращаемый контекст всегда не имеет значения nil; по умолчанию это фоновый контекст.

Для исходящих клиентских запросов контекст управляет отменой.

Для входящих запросов сервера контекст отменяется при закрытии соединения клиента, отмене запроса (при использовании HTTP/2) или при возврате метода ServeHTTP.

func (r *Request) Cookie(name string) (*Cookie, error)

Cookie возвращает именованный cookie, указанный в запросе, или ErrNoCookie, если он не найден. Если несколько cookie соответствуют заданному имени, будет возвращен только один cookie.

func (*Request) Cookies

func (r *Request) Cookies() []*Cookie

Cookies разбирает и возвращает HTTP-куки, отправленные вместе с запросом.

func (*Request) CookiesNamed

func (r *Request) CookiesNamed(name string) []*Cookie

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

func (*Request) FormFile

func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)

FormFile возвращает первый файл для предоставленного ключа формы. При необходимости FormFile вызывает Request.ParseMultipartForm и Request.ParseForm.

func (*Request) FormValue

func (r *Request) FormValue(key string) string

FormValue возвращает первое значение для именованного компонента запроса. Порядок старшинства:

  • тело формы в формате application/x-www-form-urlencoded (только POST, PUT, PATCH)
  • параметры запроса (всегда)
  • тело формы multipart/form-data (всегда)

FormValue вызывает Request.ParseMultipartForm и Request.ParseForm, если это необходимо, и игнорирует любые ошибки, возвращаемые этими функциями. Если ключ отсутствует, FormValue возвращает пустую строку. Чтобы получить доступ к нескольким значениям одного и того же ключа, вызовите ParseForm, а затем проверьте [Request.Form] напрямую.

func (*Request) MultipartReader

func (r *Request) MultipartReader() (*multipart.Reader, error)

MultipartReader возвращает читателя MIME multipart, если это запрос multipart/form-data или multipart/mixed POST, иначе возвращает nil и ошибку. Используйте эту функцию вместо Request.ParseMultipartForm для обработки тела запроса в виде потока.

func (*Request) ParseForm

func (r *Request) ParseForm() error

ParseForm заполняет r.Form и r.PostForm.

Для всех запросов ParseForm разбирает необработанный запрос из URL и обновляет r.Form.

Для запросов POST, PUT и PATCH он также считывает тело запроса, разбирает его как форму и помещает результаты в r.PostForm и r.Form. Параметры тела запроса имеют приоритет перед значениями строки запроса URL в r.Form.

Если размер тела запроса еще не был ограничен MaxBytesReader, его размер ограничивается 10 МБ.

Для других методов HTTP или если Content-Type не является application/x-www-form-urlencoded, тело запроса не считывается, а r.PostForm инициализируется не нулевым, пустым значением.

Request.ParseMultipartForm автоматически вызывает ParseForm. ParseForm является идемпотентным.

func (*Request) ParseMultipartForm

func (r *Request) ParseMultipartForm(maxMemory int64) error

ParseMultipartForm разбирает тело запроса как multipart/form-data. Разбирается все тело запроса, и не более maxMemory байт его файловых частей сохраняется в памяти, а остальная часть хранится на диске во временных файлах. При необходимости ParseMultipartForm вызывает Request.ParseForm. Если ParseForm возвращает ошибку, ParseMultipartForm возвращает ее, но при этом продолжает разбирать тело запроса. После одного вызова ParseMultipartForm последующие вызовы не имеют эффекта.

func (*Request) PathValue

func (r *Request) PathValue(name string) string

PathValue возвращает значение именованного подстановочного знака пути в шаблоне ServeMux, который соответствует запросу. Возвращается пустая строка, если запрос не был сопоставлен с шаблоном или в шаблоне нет такого подстановочного знака.

func (*Request) PostFormValue

func (r *Request) PostFormValue(key string) string

PostFormValue возвращает первое значение для именованного компонента в теле запроса POST, PUT или PATCH. Параметры запроса URL игнорируются. При необходимости PostFormValue вызывает Request.ParseMultipartForm и Request.ParseForm и игнорирует любые ошибки, возвращаемые этими функциями. Если ключ отсутствует, PostFormValue возвращает пустую строку.

func (*Request) ProtoAtLeast

func (r *Request) ProtoAtLeast(major, minor int) bool

ProtoAtLeast сообщает, является ли протокол HTTP, используемый в запросе, хотя бы мажорным и минорным.

func (*Request) Referer

func (r *Request) Referer() string

Referer возвращает ссылающийся URL, если он был отправлен в запросе.

Referer пишется неправильно, как и в самом запросе, что является ошибкой с самых ранних дней существования HTTP. Это значение также может быть получено из карты Header как Header[«Referer»]; преимущество предоставления его в виде метода заключается в том, что компилятор может диагностировать программы, использующие альтернативное (правильное английское) написание req.Referrer(), но не может диагностировать программы, использующие Header[«Referrer»].

func (*Request) SetBasicAuth

func (r *Request) SetBasicAuth(имя пользователя, пароль string)

SetBasicAuth устанавливает заголовок авторизации запроса на использование базовой аутентификации HTTP с указанными именем пользователя и паролем.

При использовании HTTP Basic Authentication предоставленные имя пользователя и пароль не шифруются. Обычно их следует использовать только в запросах HTTPS.

Имя пользователя не должно содержать двоеточие. Некоторые протоколы могут предъявлять дополнительные требования к предварительному введению имени пользователя и пароля. Например, при использовании OAuth2 оба аргумента должны быть сначала закодированы в URL с помощью url.QueryEscape.

func (*Request) SetPathValue

func (r *Request) SetPathValue(name, value string)

SetPathValue устанавливает имя в значение, так что последующие вызовы r.PathValue(name) возвращают значение.

func (*Request) UserAgent

func (r *Request) UserAgent() string

UserAgent возвращает User-Agent клиента, если он был отправлен в запросе.

func (*Request) WithContext

func (r *Request) WithContext(ctx context.Context) *Request

WithContext возвращает неглубокую копию r с измененным контекстом на ctx. Предоставленный ctx должен быть ненулевым.

Для исходящего клиентского запроса контекст контролирует все время жизни запроса и его ответа: получение соединения, отправку запроса, чтение заголовков и тела ответа.

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

func (*Request) Write

func (r *Request) Write(w io.Writer) error

Write записывает HTTP/1.1 запрос, который представляет собой заголовок и тело, в формате wire. Этот метод обрабатывает следующие поля запроса:

Host
URL
Method (defaults to "GET")
Header
ContentLength
TransferEncoding
Body

Если присутствует Body, Content-Length <= 0 и для [Request.TransferEncoding] не установлено значение «identity», Write добавляет в заголовок «Transfer-Encoding: chunked». Тело закрывается после отправки.

func (*Request) WriteProxy

func (r *Request) WriteProxy(w io.Writer) error

WriteProxy похож на Request.Write, но записывает запрос в форме, ожидаемой HTTP-прокси. В частности, Request.WriteProxy записывает начальную строку Request-URI запроса с абсолютным URI, согласно разделу 5.3 RFC 7230, включая схему и хост. В любом случае WriteProxy также записывает заголовок Host, используя либо r.Host, либо r.URL.Host.

type Response

type Response struct {
	Status string // например, «200 OK»
	StatusCode int // например, 200
	Proto string // например, «HTTP/1.0»
	ProtoMajor int // например, 1
	ProtoMinor int // например, 0

	// Header сопоставляет ключи заголовков со значениями. Если в ответе было несколько
	// заголовков с одним и тем же ключом, они могут быть объединены с помощью запятой
	// разделителями.  (RFC 7230, раздел 3.2.2 требует, чтобы несколько заголовков
	// были семантически эквивалентны последовательности, разделенной запятыми.) Когда
	// значения заголовков дублируются другими полями в этой структуре (например,
	// ContentLength, TransferEncoding, Trailer), значения полей являются
	// авторитетное.
	//
	// Ключи в карте канонизированы (см. CanonicalHeaderKey).
	Header Header

    // Body представляет собой тело ответа.
	//
	// Тело ответа передается по требованию, когда поле Body
	// считывается. Если сетевое соединение обрывается или сервер
	// завершает ответ, вызов Body.Read возвращает ошибку.
	//
	// Клиент и транспорт http гарантируют, что Body всегда
	// non-nil, даже в ответах без тела или в ответах с
	// телом нулевой длины. Вызывающая сторона обязана // закрыть Body.
	// закрыть Body. Транспорт HTTP-клиента по умолчанию не может
	// повторно использовать TCP-соединения HTTP/1.x «keep-alive», если тело
	// не прочитано до конца и не закрыто.
	//
	// Тело автоматически дешанкируется, если сервер ответил
	// с «chunked» Transfer-Encoding.
	//
	// Начиная с Go 1.12, тело также будет реализовывать io.Writer
	// при успешном ответе «101 Switching Protocols»,
	// как это используется в WebSockets и режиме «h2c» HTTP/2.
	Body io.ReadCloser

	// ContentLength записывает длину связанного содержимого. Значение
	// значение -1 указывает на то, что длина неизвестна. Если Request.Method
	// является «HEAD», значения >= 0 указывают, что данное количество байт может быть
	// быть прочитано из Body.
	ContentLength int64

	// Содержит кодировки передачи данных от крайнего внешнего к крайнему внутреннему. Значение
	// nil, означает, что используется кодировка «identity».
	TransferEncoding []string

	// Close записывает, указывал ли заголовок, что соединение должно быть
	// закрыть после прочтения Body. Значение является рекомендательным для клиентов: ни
	// ни ReadResponse, ни Response.Write никогда не закрывают соединение.
	Close bool

	// Uncompressed сообщает, был ли ответ отправлен в сжатом виде, но
	// был распакован пакетом http. Если значение равно true, чтение из
	// Body выдает несжатое содержимое вместо сжатого
	// содержимое, фактически переданное с сервера, ContentLength устанавливается в -1,
	// а поля «Content-Length» и «Content-Encoding» удаляются
	// из responseHeader. Чтобы получить оригинальный ответ от
	// сервера, установите Transport.DisableCompression в true.
	Uncompressed bool

	// Трейлер сопоставляет ключи трейлера со значениями в том же
	// формате, что и Header.
	//
	// Изначально трейлер содержит только значения nil, по одному для
	// каждого ключа, указанного в заголовке «Trailer» сервера.
	// значение. Эти значения не добавляются в Header.
	//
	// К трейлеру нельзя обращаться одновременно с вызовами Read
	// на Body.
	//
	// После того как Body.Read вернет io.EOF, Trailer будет содержать.
	// любые значения трейлера, отправленные сервером.
	Trailer Header

	// Request - это запрос, который был отправлен для получения данного Response.
	// Тело запроса равно нулю (оно уже было использовано).
	// Это значение заполняется только для клиентских запросов.
	Request *Request

	// TLS содержит информацию о TLS-соединении, по которому был получен
	// был получен ответ. Он равен nil для незашифрованных ответов.
	// Указатель разделяется между ответами и не должен быть // изменен.
	// изменяться.
	TLS *tls.ConnectionState
}

Response представляет собой ответ на HTTP-запрос.

Клиент и транспорт возвращают ответы от серверов после получения заголовков ответа. Тело ответа передается по запросу по мере считывания поля Body.

func Get

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

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

301 (Moved Permanently)
302 (найдено)
303 (См. другое)
307 (Временное перенаправление)
308 (постоянное перенаправление)

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

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

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

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

Чтобы сделать запрос с заданным контекстом (context.Context), используйте NewRequestWithContext и DefaultClient.Do.

Пример
package main

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

func main() {
	res, err := http.Get("http://www.google.com/robots.txt")
	if err != nil {
		log.Fatal(err)
	}
	body, err := io.ReadAll(res.Body)
	res.Body.Close()
	if res.StatusCode > 299 {
		log.Fatalf("Response failed with status code: %d and\nbody: %s\n", res.StatusCode, body)
	}
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s", body)
}

func Head

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

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

301 (Moved Permanently)
302 (найдено)
303 (См. другое)
307 (Временное перенаправление)
308 (постоянный редирект)

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

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

func 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.

Подробнее о том, как обрабатываются перенаправления, смотрите документацию по методу Client.Do.

Чтобы выполнить запрос с указанным контекстом (context.Context), используйте NewRequestWithContext и DefaultClient.Do.

func PostForm

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

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

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

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

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

Подробнее о том, как обрабатываются перенаправления, смотрите документацию метода Client.Do.

Чтобы выполнить запрос с указанным контекстом (context.Context), используйте NewRequestWithContext и DefaultClient.Do.

func ReadResponse

func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)

ReadResponse считывает и возвращает HTTP-ответ от r. Параметр req опционально указывает запрос, который соответствует этому ответу. Если параметр nil, предполагается, что это GET-запрос. Клиенты должны вызвать resp.Body.Close после завершения чтения resp.Body. После этого вызова клиенты могут просмотреть resp.Trailer, чтобы найти пары ключ/значение, включенные в трейлер ответа.

func (*Response) Cookies

func (r *Response) Cookies() []*Cookie

Cookies разбирает и возвращает cookie, установленные в заголовках Set-Cookie.

func (*Response) Location

func (r *Response) Location() (*url.URL, error)

Location возвращает URL заголовка «Location» ответа, если он присутствует. Относительные перенаправления разрешаются относительно [Response.Request]. Возвращается ErrNoLocation, если заголовок Location отсутствует.

func (*Response) ProtoAtLeast

func (r *Response) ProtoAtLeast(major, minor int) bool

ProtoAtLeast сообщает, является ли протокол HTTP, используемый в ответе, хотя бы мажорным и минорным.

func (*Response) Write

func (r *Response) Write(w io.Writer) error

Write записывает r в w в формате ответа сервера HTTP/1.x, включая строку состояния, заголовки, тело и необязательный трейлер.

Этот метод обрабатывает следующие поля ответа r:

StatusCode
ProtoMajor
ProtoMinor
Request.Method
TransferEncoding
Trailer
Body
ContentLength
Header, значения для неканонических ключей будут иметь непредсказуемое поведение

Тело ответа закрывается после отправки.

type ResponseController

type ResponseController struct {
	// содержит отфильтрованные или неэкспонированные поля
}

ResponseController используется HTTP-обработчиком для управления ответом.

ResponseController не может быть использован после возврата метода [Handler.ServeHTTP].

func NewResponseController

func NewResponseController(rw ResponseWriter) *ResponseController

NewResponseController создает ResponseController для запроса.

ResponseWriter должен быть исходным значением, переданным в метод [Handler.ServeHTTP], или иметь метод Unwrap, возвращающий исходный ResponseWriter.

Если ResponseWriter реализует любой из следующих методов, ResponseController будет вызывать их по мере необходимости:

Flush()
FlushError() error // альтернативный вариант Flush, возвращающий ошибку
Hijack() (net.Conn, *bufio.ReadWriter, error)
SetReadDeadline(deadline time.Time) error
SetWriteDeadline(deadline time.Time) ошибка
EnableFullDuplex() error

Если ResponseWriter не поддерживает метод, ResponseController возвращает ошибку, соответствующую ErrNotSupported.

func (*ResponseController) EnableFullDuplex

func (c *ResponseController) EnableFullDuplex() error

EnableFullDuplex указывает, что обработчик запроса будет чередовать чтение из [Request.Body] с записью в ResponseWriter.

Для запросов HTTP/1 HTTP-сервер Go по умолчанию потребляет всю непрочитанную часть тела запроса перед началом записи ответа, что не позволяет обработчикам одновременно читать из запроса и записывать ответ. Вызов EnableFullDuplex отключает это поведение и позволяет обработчикам продолжать читать из запроса, одновременно записывая ответ.

Для запросов HTTP/2 HTTP-сервер Go всегда разрешает одновременное чтение и запись ответа.

func (*ResponseController) Flush

func (c *ResponseController) Flush() error

Flush сбрасывает буферизованные данные клиенту.

func (*ResponseController) Hijack

func (c *ResponseController) Hijack() (net.Conn, *bufio.ReadWriter, error)

Hijack позволяет вызывающей стороне взять на себя управление соединением. Подробности см. в интерфейсе Hijacker.

func (*ResponseController) SetReadDeadline

func (c *ResponseController) SetReadDeadline(deadline time.Time) error

SetReadDeadline устанавливает крайний срок для чтения всего запроса, включая тело. Чтение из тела запроса после превышения установленного срока вернет ошибку. Нулевое значение означает отсутствие дедлайна.

Установка крайнего срока чтения после того, как он был превышен, не продлевает его.

func (*ResponseController) SetWriteDeadline

func (c *ResponseController) SetWriteDeadline(deadline time.Time) error

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

Установка крайнего срока записи после его превышения не приведет к его продлению.

type ResponseWriter

type ResponseWriter interface {
	// Header возвращает карту заголовков, которая будет отправлена
	// [ResponseWriter.WriteHeader]. Карта [Header] также является механизмом, с помощью которого
	// реализации [Handler] могут устанавливать HTTP-трейлеры.
	//
	// Изменение карты заголовков после вызова [ResponseWriter.WriteHeader] (или
	// [ResponseWriter.Write]) не имеет никакого эффекта, если только код состояния HTTP не был класса
	// 1xx или измененные заголовки являются трейлерами.
	//
	// Существует два способа установки трейлеров. Предпочтительным является.
	// заранее объявить в заголовках, какие трейлеры вы будете впоследствии
	// отправлять, устанавливая в заголовке «Trailer» имена
	// ключей трейлеров, которые появятся позже. В этом случае эти
	// ключи карты заголовков рассматриваются так, как если бы они были
	// трейлерами. См. пример. Второй способ - для трейлеров.
	// ключей, не известных [Обработчику] до первого [ResponseWriter.Write],
	// является префикс ключей карты [Header] с помощью константного значения [TrailerPrefix]
	// константным значением.
	//
	// Чтобы подавить автоматические заголовки ответа (например, «Дата»), установите
	// их значение в nil.
	Header() Header

	// Write записывает данные в соединение как часть HTTP-ответа.
	//
	// Если функция [ResponseWriter.WriteHeader] еще не была вызвана, Write вызывает
	// WriteHeader(http.StatusOK) перед записью данных. Если заголовок
	// не содержит строки Content-Type, Write добавляет набор Content-Type
	// к результату передачи начальных 512 байт записанных данных в
	// [DetectContentType]. Кроме того, если общий размер всех записанных
	// данных меньше нескольких КБ и нет вызовов Flush, то
	// заголовок Content-Length добавляется автоматически.
	//
	// В зависимости от версии протокола HTTP и клиента, вызов
	// Write или WriteHeader может предотвратить последующее чтение // Request.Body.
	// Request.Body. Для запросов HTTP/1.x обработчики должны считывать любые // необходимые данные тела запроса перед записью ответа.
	// необходимые данные тела запроса перед записью ответа. После того как
	// заголовки будут смыты (либо из-за явного вызова Flusher.Flush
	// вызова или записи достаточного количества данных, чтобы вызвать флэш), тело запроса
	// может быть недоступно. Для запросов HTTP/2 HTTP-сервер Go разрешает // обработчикам продолжать считывать // тело запроса.
	// обработчикам продолжать читать тело запроса, одновременно // записывая ответ.
	// записи ответа. Однако такое поведение может поддерживаться не // всеми клиентами HTTP/2.
	// всеми клиентами HTTP/2. Обработчики должны читать перед записью, если
	// по возможности, чтобы обеспечить максимальную совместимость.
	Write([]byte) (int, error)

	// WriteHeader отправляет заголовок ответа HTTP с указанным
	// кодом состояния.
	//
	// Если WriteHeader не вызывается явно, то первый вызов Write
	// вызовет неявный WriteHeader(http.StatusOK).
	// Таким образом, явные вызовы WriteHeader используются в основном для.
	// отправки кодов ошибок или информационных ответов 1xx.
	//
	// Предоставленный код должен быть действительным кодом состояния HTTP 1xx-5xx.
	// Может быть записано любое количество заголовков 1xx, за которыми следует не более
	// один заголовок 2xx-5xx. Заголовки 1xx отправляются немедленно, но заголовки 2xx-5xx
	// заголовки могут буферизироваться. Используйте интерфейс Flusher для отправки
	// буферизованных данных. Карта заголовков очищается при отправке заголовков 2xx-5xx.
	// при отправке заголовков 2xx-5xx, но не при отправке заголовков 1xx.
	//
	// Сервер автоматически отправляет заголовок 100 (Continue)
	// при первом чтении из тела запроса, если в запросе есть
	// заголовок «Expect: 100-continue».
	WriteHeader(statusCode int)
}

Интерфейс ResponseWriter используется HTTP-обработчиком для создания HTTP-ответа.

ResponseWriter не может быть использован после возврата [Handler.ServeHTTP].

Пример трейлеры

HTTP-трейлеры — это набор пар «ключ/значение», подобных заголовкам, которые следуют после HTTP-ответа, а не перед ним.

package main

import (
	"io"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/sendstrailers", func(w http.ResponseWriter, req *http.Request) {
		// Before any call to WriteHeader or Write, declare
		// the trailers you will set during the HTTP
		// response. These three headers are actually sent in
		// the trailer.
		w.Header().Set("Trailer", "AtEnd1, AtEnd2")
		w.Header().Add("Trailer", "AtEnd3")

		w.Header().Set("Content-Type", "text/plain; charset=utf-8") // normal header
		w.WriteHeader(http.StatusOK)

		w.Header().Set("AtEnd1", "value 1")
		io.WriteString(w, "This HTTP response has both headers before this text and trailers at the end.\n")
		w.Header().Set("AtEnd2", "value 2")
		w.Header().Set("AtEnd3", "value 3") // These will appear as trailers.
	})
}

type RoundTripper

type RoundTripper interface {
	// RoundTrip выполняет одну HTTP-транзакцию, возвращая
	// ответ на предоставленный запрос.
	//
	// RoundTrip не должен пытаться интерпретировать ответ. В
	// частности, RoundTrip должен вернуть err == nil, если он получил
	// ответ, независимо от кода состояния ответа HTTP.
	// Ненулевое значение err должно быть зарезервировано на случай неудачи в получении
	// ответа. Аналогично, RoundTrip не должен пытаться.
	// обрабатывать детали протокола более высокого уровня, такие как перенаправления,
	// аутентификация или cookies.
	//
	// RoundTrip не должен модифицировать запрос, кроме как.
	// потребления и закрытия тела запроса. RoundTrip может.
	// читать поля запроса в отдельной горутине. Вызывающие стороны
	// не должны мутировать или повторно использовать запрос до тех пор, пока не будет закрыто тело ответа
	// Body не будет закрыто.
	//
	// RoundTrip должен всегда закрывать тело, в том числе при ошибках,
	// но в зависимости от реализации может делать это в отдельной
	// горутине даже после возвращения RoundTrip. Это означает, что
	// вызывающие стороны, желающие повторно использовать тело для последующих запросов
	// должны дождаться вызова Close, прежде чем сделать это.
	//
	// Поля URL и Header запроса должны быть инициализированы.
	RoundTrip(*Request) (*Response, error)
}

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

RoundTripper должен быть безопасным для одновременного использования несколькими горутинами.

var DefaultTransport RoundTripper = &Transport{
	Proxy: ProxyFromEnvironment,
	DialContext: defaultTransportDialContext(&net.Dialer{
		Таймаут:   30 * time.Second,
		KeepAlive: 30 * time.Second,
	}),
	ForceAttemptHTTP2: true,
	MaxIdleConns:          100,
	IdleConnTimeout: 90 * time.Second,
	TLSHandshakeTimeout:   10 * time.Second,
	ExpectContinueTimeout: 1 * time.Second,
}

DefaultTransport является реализацией Transport по умолчанию и используется DefaultClient. Он устанавливает сетевые соединения по мере необходимости и кэширует их для повторного использования при последующих вызовах. Он использует HTTP-прокси по указанию переменных окружения HTTP_PROXY, HTTPS_PROXY и NO_PROXY (или их строчных версий).

func NewFileTransport

func NewFileTransport(fs FileSystem) RoundTripper

NewFileTransport возвращает новый RoundTripper, обслуживающий предоставленную FileSystem. Возвращаемый RoundTripper игнорирует URL host в своих входящих запросах, а также большинство других свойств запроса.

Типичным случаем использования NewFileTransport является регистрация «файлового» протокола на транспорте, как показано ниже:

t := &http.Transport{}
t.RegisterProtocol(«file», http.NewFileTransport(http.Dir(«/»)))
c := &http.Client{Transport: t}
res, err := c.Get(«file:///etc/passwd»)
...

func NewFileTransportFS

func NewFileTransportFS(fsys fs.FS) RoundTripper

NewFileTransportFS возвращает новый RoundTripper, обслуживающий предоставленную файловую систему fsys. Возвращаемый RoundTripper игнорирует URL host в своих входящих запросах, а также большинство других свойств запроса. Файлы, предоставляемые fsys, должны реализовывать io.Seeker.

Типичным случаем использования NewFileTransportFS является регистрация «файлового» протокола в транспорте, как показано ниже:

fsys := os.DirFS(«/»)
t := &http.Transport{}
t.RegisterProtocol(«file», http.NewFileTransportFS(fsys))
c := &http.Client{Transport: t}
res, err := c.Get(«file:///etc/passwd»)
...

type SameSite

type SameSite int

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

Подробности см. на сайте https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00.

const (
	SameSiteDefaultMode SameSite = iota + 1
	SameSiteLaxMode
	SameSiteStrictMode
	SameSiteNoneMode
)

type ServeMux

type ServeMux struct {
	// содержит отфильтрованные или неотфильтрованные поля
}

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

Шаблоны

Шаблоны могут соответствовать методу, хосту и пути запроса. Некоторые примеры:

- «/index.html» соответствует пути „/index.html“ для любого хоста и метода.
- «GET /static/» соответствует GET-запросу, путь которого начинается с „/static/“.
- «example.com/» соответствует любому запросу к хосту „example.com“.
- «example.com/{$}» соответствует запросам с хостом „example.com“ и путем „/“.
- «/b/{bucket}/o/{objectname...}» соответствует путям, первый сегмент которых - „b“, а третий - „o“. Имя «bucket» обозначает второй сегмент, а «objectname» - оставшуюся часть пути.

В общем случае шаблон выглядит следующим образом

[METHOD ][HOST]/[PATH]

Все три части необязательны; «/» является допустимым шаблоном. Если присутствует METHOD, за ним должен следовать хотя бы один пробел или табуляция.

Буквальные (т. е. не содержащие символы дикого символа) части шаблона соответствуют соответствующим частям запроса с учетом регистра.

Шаблон без метода соответствует любому методу. Шаблон с методом GET соответствует запросам GET и HEAD. В противном случае метод должен совпадать в точности.

Шаблон без хоста соответствует всем хостам. Шаблон с указанием хоста соответствует URL-адресам только на этом хосте.

Путь может включать сегменты подстановочных знаков вида {NAME} или {NAME…}. Например, «/b/{bucket}/o/{objectname…}». Имя подстановочного знака должно быть действительным идентификатором Go. Подстановочные знаки должны быть полными сегментами пути: им должна предшествовать косая черта, а за ней следовать либо косая черта, либо конец строки. Например, «/b_{bucket}» не является правильным шаблоном.

Обычно подстановочный знак соответствует только одному сегменту пути, заканчивающемуся следующим буквенным слешем (не %2F) в URL-адресе запроса. Но если присутствует «…», то подстановочный знак соответствует оставшейся части пути URL, включая косые черты. (Поэтому подстановочный знак «…» не должен появляться нигде, кроме конца шаблона). Соответствие для подстановочного знака можно получить, вызвав Request.PathValue с именем подстановочного знака. Косая черта в пути действует как анонимный подстановочный знак «…».

Специальный подстановочный знак {$} совпадает только с концом URL. Например, шаблон «/{$}» соответствует только пути «/», в то время как шаблон «/» соответствует всем путям.

Для сопоставления пути шаблонов и пути входящих запросов сегментно разгруппировываются. Так, например, путь «/a%2Fb/100%25» рассматривается как состоящий из двух сегментов, «a/b» и «100%». Шаблон «/a%2fb/» соответствует ему, а шаблон «/a/b/» - нет.

Приоритет

Если запросу соответствуют два или более шаблонов, то приоритет имеет наиболее специфичный шаблон. Шаблон P1 более специфичен, чем P2, если P1 соответствует строгому подмножеству запросов P2; то есть, если P2 соответствует всем запросам P1 и более. Если ни один из шаблонов не является более специфичным, то они конфликтуют. Из этого правила есть одно исключение для обратной совместимости: если два шаблона в противном случае будут конфликтовать, но один из них имеет хост, а другой - нет, то приоритет имеет шаблон с хостом. Если шаблон, переданный в ServeMux.Handle или ServeMux.HandleFunc, конфликтует с другим шаблоном, который уже зарегистрирован, эти функции паникуют.

В качестве примера общего правила можно привести «/images/thumbnails/», который является более конкретным, чем «/images/», поэтому оба шаблона могут быть зарегистрированы. Первый соответствует путям, начинающимся с «/images/thumbnails/», а второй будет соответствовать любому другому пути в поддереве «/images/».

В качестве другого примера рассмотрим шаблоны «GET /» и «/index.html»: оба соответствуют GET-запросу на «/index.html», но первый шаблон соответствует всем остальным GET- и HEAD-запросам, а второй - любому запросу на «/index.html», который использует другой метод. Шаблоны конфликтуют.

Перенаправление по следам косой черты

Рассмотрим ServeMux с обработчиком для поддерева, зарегистрированного с использованием слэша в конце или подстановочного символа «…». Если ServeMux получает запрос на корень поддерева без скрепки, он перенаправляет запрос, добавляя скрепку. Это поведение можно отменить, зарегистрировав отдельный путь без косой черты или подстановочного знака «…». Например, регистрация «/images/» заставляет ServeMux перенаправлять запрос «/images» на «/images/», если только «/images» не был зарегистрирован отдельно.

Санирование запросов

ServeMux также заботится о дезинфекции пути запроса URL и заголовка Host, удаляя номер порта и перенаправляя любой запрос, содержащий сегменты . или … или повторяющиеся косые черты, на эквивалентный, более чистый URL. Сбегающие элементы пути, такие как «%2e» для «.» и «%2f» для «/», сохраняются и не считаются разделителями при маршрутизации запроса.

Совместимость

Синтаксис шаблонов и поведение ServeMux при сопоставлении значительно изменились в Go 1.22. Чтобы восстановить старое поведение, установите переменную окружения GODEBUG в значение «httpmuxgo121=1». Эта настройка считывается один раз, при запуске программы; изменения во время выполнения будут проигнорированы.

Изменения, несовместимые с предыдущими, включают:

  • Дикие символы в 1.21 - это обычные литеральные сегменты пути. Например, шаблон «/{x}» будет соответствовать только этому пути в 1.21, но будет соответствовать любому односегментному пути в 1.22.
  • В 1.21 ни один шаблон не отклонялся, если он не был пустым или не конфликтовал с существующим шаблоном. В 1.22 синтаксически некорректные шаблоны приводят к панике ServeMux.Handle и ServeMux.HandleFunc. Например, в 1.21 шаблоны «/{» и «/a{x}» совпадают сами по себе, но в 1.22 они недействительны и при регистрации вызовут панику.
  • В 1.22 каждый сегмент шаблона разгруппировывается; в 1.21 этого не делалось. Например, в 1.22 шаблон «/%61» соответствует пути «/a» («%61» - управляющая последовательность URL для «a»), а в 1.21 он соответствовал бы только пути «/%2561» (где «%25» - управляющая последовательность для знака процента).
  • При сопоставлении шаблонов с путями в версии 1.22 каждый сегмент пути раскрывается, а в версии 1.21 раскрывается весь путь. Это изменение в основном влияет на то, как обрабатываются пути с эскейпами %2F, расположенными рядом со слешами. Подробности см. на https://go.dev/issue/21955.

func NewServeMux

func NewServeMux() *ServeMux

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

func (*ServeMux) Handle

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

Handle регистрирует обработчик для заданного шаблона. Если заданный шаблон конфликтует с уже зарегистрированным, 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) {
		// The "/" pattern matches everything, so we need to check
		// that we're at the root here.
		if req.URL.Path != "/" {
			http.NotFound(w, req)
			return
		}
		fmt.Fprintf(w, "Welcome to the home page!")
	})
}

func (*ServeMux) HandleFunc

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

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

func (*ServeMux) Handler

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

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

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

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

Если для запроса не существует зарегистрированного обработчика, Handler возвращает обработчик «страница не найдена» и пустой шаблон.

func (*ServeMux) ServeHTTP

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

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

type Server

type Server struct {
	// Addr опционально указывает TCP-адрес сервера для прослушивания,
	// в форме «host:port». Если он пуст, то используется «:http» (порт 80).
	// Имена служб определены в RFC 6335 и назначены IANA.
	// Подробнее о формате адресов см. в разделе net.Dial.
	Addr string

	Handler Handler // обработчик для вызова, http.DefaultServeMux, если nil

	// DisableGeneralOptionsHandler, если true, передает запросы «OPTIONS *» обработчику,
	// в противном случае отвечает 200 OK и Content-Length: 0.
	DisableGeneralOptionsHandler bool

	// TLSConfig опционально предоставляет конфигурацию TLS для использования
	// функциями ServeTLS и ListenAndServeTLS. Обратите внимание, что это значение
	// клонируется ServeTLS и ListenAndServeTLS, поэтому не
	// невозможно изменить конфигурацию с помощью методов типа
	// tls.Config.SetSessionTicketKeys. Чтобы использовать
	// SetSessionTicketKeys, используйте Server.Serve с TLS-слушателем
	// вместо него.
	TLSConfig *tls.Config

	// ReadTimeout - максимальная продолжительность чтения всего // запроса.
	// запроса, включая тело. Нулевое или отрицательное значение означает.
	// что таймаута не будет.
	//
	// Поскольку ReadTimeout не позволяет обработчикам принимать по каждому запросу
	// решения о допустимом сроке выполнения или // скорости загрузки каждого тела запроса, большинство пользователей предпочтут использовать ReadTimeout.
	// скорости загрузки, большинство пользователей предпочтут использовать
	// ReadHeaderTimeout. Вполне допустимо использовать их оба.
	ReadTimeout time.Duration

	// ReadHeaderTimeout - это количество времени, разрешенное для чтения
	// заголовков запросов. Срок чтения соединения сбрасывается.
	// после чтения заголовков, и обработчик может решить, что
	// считается слишком медленным для тела запроса. Если значение равно нулю, используется значение
	// ReadTimeout используется. Если отрицательно, или если ноль и ReadTimeout
	// равно нулю или отрицательно, то таймаут не используется.
	ReadHeaderTimeout time.Duration

	// WriteTimeout - максимальная продолжительность до завершения тайминга
	// записи ответа. Она сбрасывается каждый раз, когда считывается новый
	// чтении заголовка запроса. Как и ReadTimeout, он не // позволяет обработчикам принимать решения.
	// позволяет обработчикам принимать решения на основе каждого запроса.
	// Нулевое или отрицательное значение означает, что таймаута не будет.
	WriteTimeout time.Duration

	// IdleTimeout - это максимальное время ожидания
	// следующего запроса, когда включены keep-alives. Если значение равно нулю, то используется значение
	// из ReadTimeout используется. Если отрицательно, или если ноль и ReadTimeout
	// равно нулю или отрицательно, таймаут не используется.
	IdleTimeout time.Duration

	// MaxHeaderBytes управляет максимальным количеством байтов, которые
	// сервер будет читать, разбирая ключи и // значения заголовка запроса, включая строку запроса.
	// значения, включая строку запроса. Оно не ограничивает
	// размер тела запроса.
	// Если значение равно нулю, используется значение DefaultMaxHeaderBytes.
	MaxHeaderBytes int

	// TLSNextProto опционально указывает функцию, которая принимает на себя
	// владение предоставленным TLS-соединением, когда происходит обновление протокола ALPN
	// произошло обновление протокола. Ключом карты является протокол.
	// согласованное имя. Аргумент Handler должен использоваться для.
	// обработки HTTP-запросов и инициализирует TLS запроса
	// и RemoteAddr, если они еще не заданы. Соединение
	// автоматически закрывается при возврате функции.
	// Если TLSNextProto не равно nil, поддержка HTTP/2 не включается
	// автоматически.
	TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

	// ConnState задает необязательную функцию обратного вызова, которая
	// вызывается при изменении состояния клиентского соединения. См.
	// Тип ConnState и связанные с ним константы для получения подробной информации.
	ConnState func(net.Conn, ConnState)

	// ErrorLog задает необязательный регистратор ошибок при принятии // соединения, неожиданного поведения обработчиков.
	// соединения, неожиданного поведения обработчиков и
	// базовых ошибок файловой системы.
	// Если nil, то протоколирование осуществляется через стандартный логгер пакета log.
	ErrorLog *log.Logger

	// BaseContext опционально указывает функцию, которая возвращает
	// базовый контекст для входящих запросов на этом сервере.
	// Предоставленный слушатель - это конкретный слушатель, который
	// собирается начать принимать запросы.
	// Если BaseContext равен nil, по умолчанию используется context.Background().
	// Если не nil, то должен быть возвращен не nil контекст.
	BaseContext func(net.Listener) context.Context

	// ConnContext опционально задает функцию, которая изменяет
	// контекст, используемый для нового соединения c. Предоставляемый ctx
	// является производным от базового контекста и имеет ServerContextKey
	// значение.
	ConnContext func(ctx context.Context, c net.Conn) context.Context

	// HTTP2 настраивает HTTP/2 соединения.
	//
	// Это поле пока не имеет никакого эффекта.
	// См. https://go.dev/issue/67813.
	HTTP2 *HTTP2Config

	// Протоколы - это набор протоколов, принимаемых сервером.
	//
	// Если Protocols включает UnencryptedHTTP2, сервер будет принимать
	// незашифрованные соединения HTTP/2. Сервер может обслуживать как
	// HTTP/1 и незашифрованный HTTP/2 на одном и том же адресе и порту.
	//
	// Если значение Protocols равно nil, по умолчанию обычно используются HTTP/1 и HTTP/2.
	// Если TLSNextProto не имеет значения nil и не содержит записи «h2»,
	// по умолчанию используется только HTTP/1.
	Protocols *Protocols
	// содержит отфильтрованные или неэкспонированные поля
}

Server определяет параметры для запуска HTTP-сервера. Нулевое значение для Server является допустимой конфигурацией.

func (*Server) Close

func (s *Server) Close() error

Close немедленно закрывает все активные net.Listeners и все соединения в состоянии StateNew, StateActive или StateIdle. Для изящного завершения работы используйте Server.Shutdown.

Close не пытается закрыть (и даже не знает об этом) любые перехваченные соединения, такие как WebSockets.

Close возвращает любую ошибку, возникшую при закрытии базового слушателя(ей) сервера.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

ListenAndServe прослушивает сетевой TCP-адрес s.Addr и затем вызывает Serve для обработки запросов на входящих соединениях. Принимаемые соединения настроены на включение TCP keep-alives.

Если s.Addr пуст, используется «:http».

ListenAndServe всегда возвращает ошибку, отличную от нуля. После Server.Shutdown или Server.Close возвращаемая ошибка будет ErrServerClosed.

func (*Server) ListenAndServeTLS

func (s *Server) ListenAndServeTLS(certFile, keyFile string) error

ListenAndServeTLS прослушивает сетевой TCP-адрес s.Addr и затем вызывает ServeTLS для обработки запросов на входящих TLS-соединениях. Принимаемые соединения настроены на включение TCP keep-alives.

Если не заполнены ни TLSConfig.Certificates, ни TLSConfig.GetCertificate сервера, необходимо предоставить файлы, содержащие сертификат и соответствующий закрытый ключ для сервера. Если сертификат подписан центром сертификации, certFile должен представлять собой объединение сертификата сервера, всех промежуточных сертификатов и сертификата центра сертификации.

Если s.Addr пуст, используется «:https».

ListenAndServeTLS всегда возвращает ошибку, отличную от нуля. После Server.Shutdown или Server.Close возвращаемая ошибка - ErrServerClosed.

func (*Server) RegisterOnShutdown

func (s *Server) RegisterOnShutdown(f func())

RegisterOnShutdown регистрирует функцию для вызова на Server.Shutdown. Она может использоваться для изящного завершения соединений, которые подверглись обновлению протокола ALPN или были перехвачены. Эта функция должна запускать специфическое для данного протокола изящное отключение, но не должна ждать завершения отключения.

func (*Server) Serve

func (s *Server) Serve(l net.Listener) error

Serve принимает входящие соединения на слушателе l, создавая для каждого новую сервисную горутину. Сервисные программы читают запросы и затем вызывают s.Handler для ответа на них.

Поддержка HTTP/2 включена только в том случае, если Слушатель возвращает соединения *tls.Conn и они были настроены на «h2» в TLS Config.NextProtos.

Serve всегда возвращает ненулевую ошибку и закрывает l. После Server.Shutdown или Server.Close возвращаемая ошибка будет ErrServerClosed.

func (*Server) ServeTLS

func (s *Server) ServeTLS(l net.Listener, certFile, keyFile string) error

ServeTLS принимает входящие соединения на слушателе l, создавая для каждого новую сервисную горутину. Сервисные программы выполняют настройку TLS, а затем считывают запросы, вызывая s.Handler для ответа на них.

Если не заполнены ни TLSConfig.Certificates, ни TLSConfig.GetCertificate, ни config.GetConfigForClient сервера, необходимо предоставить файлы, содержащие сертификат и соответствующий закрытый ключ для сервера. Если сертификат подписан центром сертификации, файл certFile должен быть конкатенацией сертификата сервера, всех промежуточных сертификатов и сертификата центра сертификации.

ServeTLS всегда возвращает ошибку, отличную от нуля. После Server.Shutdown или Server.Close возвращаемая ошибка будет ErrServerClosed.

func (*Server) SetKeepAlivesEnabled

func (s *Server) SetKeepAlivesEnabled(v bool)

SetKeepAlivesEnabled управляет тем, включены ли HTTP keep-alives. По умолчанию keep-alives всегда включены. Только очень ограниченные ресурсы или серверы, находящиеся в процессе выключения, должны отключать их.

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Выключение изящно отключает сервер, не прерывая активных соединений. Shutdown работает, сначала закрывая все открытые слушатели, затем закрывая все простаивающие соединения, а затем ожидая неопределенное время, пока соединения не вернутся в состояние простоя, и затем выключается. Если предоставленный контекст истекает до завершения выключения, Shutdown возвращает ошибку контекста, в противном случае возвращается любая ошибка, возникшая при закрытии базового слушателя (слушателей) сервера.

Когда вызывается Shutdown, Serve, ListenAndServe и ListenAndServeTLS немедленно возвращают ErrServerClosed. Убедитесь, что программа не завершается, а ждет возврата Shutdown.

Shutdown не пытается закрыть или дождаться перехваченных соединений, таких как WebSockets. Вызывающая Shutdown программа должна отдельно уведомить такие долгоживущие соединения о закрытии и дождаться их закрытия, если это необходимо. Способ регистрации функций уведомления о выключении см. в Server.RegisterOnShutdown.

После вызова Shutdown на сервере его нельзя использовать повторно; будущие вызовы таких методов, как Serve, будут возвращать ErrServerClosed.

Пример
package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
)

func main() {
	var srv http.Server

	idleConnsClosed := make(chan struct{})
	go func() {
		sigint := make(chan os.Signal, 1)
		signal.Notify(sigint, os.Interrupt)
		<-sigint

		// We received an interrupt signal, shut down.
		if err := srv.Shutdown(context.Background()); err != nil {
			// Error from closing listeners, or context timeout:
			log.Printf("HTTP server Shutdown: %v", err)
		}
		close(idleConnsClosed)
	}()

	if err := srv.ListenAndServe(); err != http.ErrServerClosed {
		// Error starting or closing listener:
		log.Fatalf("HTTP server ListenAndServe: %v", err)
	}

	<-idleConnsClosed
}

type Transport

type Transport struct {

	// Proxy определяет функцию для возврата прокси для данного
	// запроса. Если функция возвращает ошибку, отличную от нуля, то
	// запрос будет прерван с указанной ошибкой.
	//
	// Тип прокси определяется схемой URL. «http»,
	// «https», «socks5» и «socks5h» поддерживаются. Если схема пуста,
	// предполагается «http».
	// «socks5» обрабатывается так же, как и «socks5h».
	//
	// Если URL прокси содержит подкомпонент userinfo,
	// в прокси-запросе будут переданы имя пользователя и пароль
	// в заголовке Proxy-Authorization.
	//
	// Если Proxy равно nil или возвращает nil *URL, прокси не используется.
	Proxy func(*Request) (*url.URL, error)

	// OnProxyConnectResponse вызывается, когда транспорт получает HTTP-ответ от
	// прокси для запроса CONNECT. Он вызывается перед проверкой ответа 200 OK.
	// Если он возвращает ошибку, запрос завершается с этой ошибкой.
	OnProxyConnectResponse func(ctx context.Context, proxyURL *url.URL, connectReq *Request, connectRes *Response) error

	// DialContext задает функцию dial для создания незашифрованных TCP-соединений.
	// Если DialContext равен nil (и устаревшая Dial ниже также равна nil),
	// то транспорт набирает номер, используя пакет net.
	//
	// DialContext работает параллельно с вызовами RoundTrip.
	// Вызов RoundTrip, инициирующий набор номера, может в конечном итоге использовать
	// соединение, набранное ранее, когда предыдущее соединение
	// становится нерабочим до завершения последующего DialContext.
	DialContext func(ctx context.Context, network, addr string) (net.Conn, error)

	// Dial определяет функцию dial для создания незашифрованных TCP-соединений.
	//
	// Dial выполняется одновременно с вызовами RoundTrip.
	// Вызов RoundTrip, инициирующий набор, может в конечном итоге использовать
	// соединение, набранное ранее, если предыдущее соединение
	// становится нерабочим до завершения последующего Dial.
	//
	// Исправлено: Вместо этого используйте DialContext, который позволяет транспорту
	// отменять дозвоны, как только они больше не нужны.
	// Если оба значения установлены, приоритет имеет DialContext.
	Dial func(network, addr string) (net.Conn, error)

	// DialTLSContext задает необязательную функцию набора номера для создания
	// TLS-соединений для непроксированных HTTPS-запросов.
	//
	// Если DialTLSContext равен nil (и устаревшая DialTLS ниже также равна nil),
	// используются DialContext и TLSClientConfig.
	//
	// Если DialTLSContext установлен, крючки Dial и DialContext не используются для HTTPS
	// запросы, а параметры TLSClientConfig и TLSHandshakeTimeout
	// игнорируются. Предполагается, что возвращаемый net.Conn уже // прошел квитирование TLS.
	// прошло рукопожатие TLS.
	DialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)

	// DialTLS определяет необязательную функцию набора номера для создания
	// TLS-соединений для непроксированных HTTPS-запросов.
	//
	// Исправлено: Вместо этого используйте DialTLSContext, который позволяет транспорту
	// отменять циклы, как только они больше не нужны.
	// Если оба значения установлены, приоритет имеет DialTLSContext.
	DialTLS func(network, addr string) (net.Conn, error)

	// TLSClientConfig задает конфигурацию TLS для использования с
	// tls.Client.
	// Если nil, используется конфигурация по умолчанию.
	// Если не nil, то поддержка HTTP/2 может быть не включена по умолчанию.
	TLSClientConfig *tls.Config

	// TLSHandshakeTimeout задает максимальное количество времени.
	// ожидание рукопожатия TLS. Ноль означает отсутствие таймаута.
	TLSHandshakeTimeout time.Duration

	// DisableKeepAlives, если true, отключает HTTP keep-alives и
	// будет использовать соединение с сервером только для одного
	// HTTP-запроса.
	//
	// Это не связано с аналогичным названием TCP keep-alives.
	DisableKeepAlives bool

	// DisableCompression, если значение равно true, запрещает транспорту
	// запрашивать сжатие с помощью заголовка запроса «Accept-Encoding: gzip»
	// заголовком запроса, если запрос не содержит существующего
	// значение Accept-Encoding. Если Транспорт запрашивает gzip самостоятельно
	// самостоятельно и получает gzip-ответ, то он прозрачно
	// декодируется в Response.Body. Однако если пользователь
	// явно запросил gzip, он не будет автоматически // распакован.
	// не сжимается.
	DisableCompression bool

	// MaxIdleConns контролирует максимальное количество простаивающих (keep-alive)
	// соединений на всех хостах. Ноль означает отсутствие ограничения.
	MaxIdleConns int

	// MaxIdleConnsPerHost, если ненулевое значение, контролирует максимальное количество простаивающих
	// (keep-alive) соединений для каждого хоста. Если ноль,
	// используется значение по умолчаниюMaxIdleConnsPerHost.
	MaxIdleConnsPerHost int

	// MaxConnsPerHost опционально ограничивает общее количество
	// соединений на хост, включая соединения в состоянии дозвона,
	// активном и неактивном состояниях. При нарушении лимита соединения будут блокироваться.
	//
	// Ноль означает отсутствие ограничения.
	MaxConnsPerHost int

	// IdleConnTimeout - это максимальное количество времени, в течение которого неработающее
	// (keep-alive) соединение будет простаивать перед закрытием
	// само.
	// Ноль означает отсутствие ограничений.
	IdleConnTimeout time.Duration

	// ResponseHeaderTimeout, если ненулевое значение, определяет количество
	// времени для ожидания заголовков ответа сервера после полной
	// записи запроса (включая его тело, если таковое имеется). Это
	// время не включает время на чтение тела ответа.
	ResponseHeaderTimeout time.Duration

	// ExpectContinueTimeout, если ненулевое значение, определяет количество
	// времени для ожидания первых заголовков ответа сервера после полной
	// записи заголовков запроса, если запрос содержит
	// заголовок «Expect: 100-continue». Ноль означает отсутствие таймаута и
	// приводит к тому, что тело запроса отправляется немедленно, без
	// ожидания одобрения сервера.
	// Это время не включает время отправки заголовка запроса.
	ExpectContinueTimeout time.Duration

	// TLSNextProto определяет, как транспорт переключается на.
	// альтернативному протоколу (например, HTTP/2) после TLS ALPN
	// согласования протокола. Если транспорт набирает TLS-соединение
	// с непустым именем протокола и TLSNextProto содержит
	// запись в карте для этого ключа (например, «h2»), то функция
	// вызывается с полномочиями запроса (например, «example.com»
	// или «example.com:1234») и TLS-соединением. Функция
	// должна возвращать RoundTripper, который затем обрабатывает запрос.
	// Если TLSNextProto не равно nil, поддержка HTTP/2 не включается
	// автоматически.
	TLSNextProto map[string]func(authority string, c *tls.Conn) RoundTripper

	// ProxyConnectHeader опционально указывает заголовки, которые следует отправлять на
	// прокси во время запросов CONNECT.
	// Чтобы динамически установить заголовок, смотрите GetProxyConnectHeader.
	ProxyConnectHeader Header

	// GetProxyConnectHeader опционально указывает функцию для возврата
	// заголовки для отправки в proxyURL во время запроса CONNECT к
	// ip:port target.
	// Если она возвращает ошибку, то раундтрип транспорта завершается с ошибкой.
	// этой ошибкой. Он может вернуть (nil, nil), чтобы не добавлять заголовки.
	// Если GetProxyConnectHeader не равен nil, ProxyConnectHeader
	// игнорируется.
	GetProxyConnectHeader func(ctx context.Context, proxyURL *url.URL, target string) (Header, error)

	// MaxResponseHeaderBytes задает ограничение на то, сколько
	// байтов в ответе сервера
	// заголовок.
	//
	// Ноль означает использование ограничения по умолчанию.
	MaxResponseHeaderBytes int64

	// WriteBufferSize определяет размер буфера записи, используемого
	// при записи на транспорт.
	// Если ноль, то используется значение по умолчанию (в настоящее время 4 КБ).
	WriteBufferSize int

	// ReadBufferSize задает размер буфера чтения, используемого
	// при чтении с транспорта.
	// Если ноль, то используется значение по умолчанию (в настоящее время 4 КБ).
	ReadBufferSize int

	// ForceAttemptHTTP2 определяет, будет ли включен HTTP/2 при ненулевом значении
	// Dial, DialTLS, или DialContext func или TLSClientConfig.
	// По умолчанию использование любого из этих полей консервативно отключает HTTP/2.
	// Чтобы использовать пользовательские конфигурации дозвона или TLS и при этом пытаться HTTP/2
	// обновления, установите это значение в true.
	ForceAttemptHTTP2 bool

	// HTTP2 настраивает HTTP/2 соединения.
	//
	// Это поле пока не имеет никакого эффекта.
	// См. https://go.dev/issue/67813.
	HTTP2 *HTTP2Config

	// Протоколы - это набор протоколов, поддерживаемых транспортом.
	//
	// Если Protocols включает UnencryptedHTTP2 и не включает HTTP1,
	// транспорт будет использовать незашифрованный HTTP/2 для запросов к URL http://.
	//
	// Если Protocols равно nil, то по умолчанию обычно используется только HTTP/1.
	// Если ForceAttemptHTTP2 равен true, или если TLSNextProto содержит запись «h2»,
	// по умолчанию используются HTTP/1 и HTTP/2.
	Protocols *Protocols
	// содержит отфильтрованные или неэкспонированные поля
}

Transport - это реализация RoundTripper, поддерживающая HTTP, HTTPS и HTTP-прокси (для HTTP или HTTPS с CONNECT).

По умолчанию Transport кэширует соединения для последующего повторного использования. Это может оставить много открытых соединений при доступе ко многим хостам. Таким поведением можно управлять с помощью метода Transport.CloseIdleConnections и полей [Transport.MaxIdleConnsPerHost] и [Transport.DisableKeepAlives].

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

Транспорт - это низкоуровневый примитив для выполнения HTTP- и HTTPS-запросов. Для высокоуровневой функциональности, такой как куки и редиректы, смотрите раздел Клиент.

Транспорт использует HTTP/1.1 для HTTP URL и либо HTTP/1.1, либо HTTP/2 для HTTPS URL, в зависимости от того, поддерживает ли сервер HTTP/2, и как настроен транспорт. DefaultTransport поддерживает HTTP/2. Чтобы явно включить HTTP/2 на транспорте, установите [Transport.Protocols].

Ответы с кодами состояния в диапазоне 1xx либо обрабатываются автоматически (100 expect-continue), либо игнорируются. Исключением является код состояния HTTP 101 (Switching Protocols), который считается терминальным статусом и возвращается Transport.RoundTrip. Чтобы увидеть проигнорированные ответы 1xx, используйте ClientTrace.Got1xxResponse пакета трассировки httptrace.

Transport повторяет запрос при возникновении сетевой ошибки только в том случае, если соединение уже было успешно использовано и если запрос является идемпотентным и либо не имеет тела, либо определено его [Request.GetBody]. HTTP-запросы считаются идемпотентными, если они имеют HTTP-методы GET, HEAD, OPTIONS или TRACE; или если их карта заголовков содержит запись «Idempotency-Key» или «X-Idempotency-Key». Если значение ключа idempotency является фрагментом нулевой длины, запрос рассматривается как idempotent, но заголовок не передается по проводам.

func (*Transport) CancelRequest deprecated

func (*Transport) Clone

func (t *Transport) Clone() *Transport

Clone возвращает глубокую копию экспортированных полей t.

func (*Transport) CloseIdleConnections

func (t *Транспорт) CloseIdleConnections()

CloseIdleConnections закрывает все соединения, которые были ранее подключены в результате предыдущих запросов, но теперь простаивают в состоянии «keep-alive». Она не прерывает используемые в данный момент соединения.

func (*Transport) RegisterProtocol

func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper)

RegisterProtocol регистрирует новый протокол с помощью схемы. Транспорт будет передавать запросы, использующие заданную схему, в rt. Ответственность rt заключается в имитации семантики HTTP-запросов.

RegisterProtocol может быть использован другими пакетами для обеспечения реализаций схем протоколов типа «ftp» или «file».

Если rt.RoundTrip возвращает ErrSkipAltProtocol, транспорт будет сам обрабатывать Transport.RoundTrip для этого одного запроса, как если бы протокол не был зарегистрирован.

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *Request) (*Response, error)

RoundTrip реализует интерфейс RoundTripper.

Для более высокоуровневой поддержки HTTP-клиентов (например, обработка cookies и перенаправлений) смотрите Get, Post и тип Client.

Как и в интерфейсе RoundTripper, типы ошибок, возвращаемых RoundTrip, не определены.

15.4 - Описание пакета для управления маршрутизацией github.com/julienschmidt/httprouter для GO

HttpRouter — это легкий высокопроизводительный маршрутизатор HTTP-запросов (также называемый мультиплексором или просто мультиплексором) для Go.

HttpRouter

HttpRouter — это легковесный высокопроизводительный маршрутизатор HTTP-запросов (также называемый мультиплексором или просто “mux”) для Go.

В отличие от стандартного мультиплексора пакета net/http в Go, этот роутер поддерживает:

  • Переменные в шаблонах маршрутов
  • Соответствие HTTP-методам запроса
  • Лучшую масштабируемость

Роутер оптимизирован для высокой производительности и малого потребления памяти. Эффективно работает даже с очень длинными путями и большим количеством маршрутов благодаря использованию сжимающей динамической trie-структуры (радиксное дерево).


Ключевые особенности

1. Только точные совпадения
В отличие от других роутеров (например, http.ServeMux), где URL может соответствовать нескольким шаблонам (с приоритетами типа “самое длинное совпадение” или “первый зарегистрированный — первый обработанный”), здесь запрос может соответствовать только одному маршруту или ни одному. Это исключает неожиданные совпадения, что полезно для SEO и UX.

2. Автоматическая обработка слэшей
Роутер автоматически перенаправляет при отсутствии или избытке trailing slash (косой черты в конце пути), если для нового пути есть обработчик. Можно отключить.

3. Коррекция пути
Дополнительные возможности:

  • Исправление регистра символов (например, для CAPTAIN CAPS LOCK)
  • Удаление избыточных элементов (../, //)
  • Case-insensitive поиск с редиректом

4. Параметры в путях
Динамические сегменты пути (например, /user/:id) извлекаются без ручного парсинга URL. Реализовано с минимальными накладными расходами.

5. Zero Garbage
Процесс сопоставления и диспетчеризации не создает мусора (zero bytes allocation). Единственные выделения памяти:

  • Создание slice для параметров пути
  • Создание контекста и объекта запроса (только в стандартном Handler API)
  • В 3-аргументном API при отсутствии параметров — аллокаций нет вообще.

6. Максимальная производительность
Реализация оптимизирована (см. бенчмарки). Используется радиксное дерево для эффективного сопоставления длинных путей.

7. Обработка паник
Можно установить PanicHandler для перехвата паник во время обработки запроса. Роутер восстановит работу и отправит клиенту ошибку.

8. Идеально для API

  • Поощряет построение RESTful API с иерархической структурой
  • Нативная поддержка OPTIONS-запросов
  • Автоматические ответы 405 Method Not Allowed
  • Возможность кастомизации NotFound и MethodNotAllowed обработчиков
  • Поддержка статических файлов

Технические детали

Используется радиксное дерево (trie) с компрессией для:

  • Эффективного хранения длинных путей
  • Быстрого поиска даже при тысячах маршрутов
  • Минимизации использования памяти

Возможности

Именованные параметры

Как видно из примеров, :name представляет собой именованный параметр. Его значения доступны через httprouter.Params — это срез (slice) параметров. Получить значение можно двумя способами:

  1. По индексу в срезе
  2. Через метод ByName(name): например, параметр :name извлекается вызовом ByName("name")

Важно:
При использовании стандартного http.Handler (через router.Handler или http.HandlerFunc) вместо 3-аргументного API HttpRouter, именованные параметры хранятся в контексте запроса (request.Context). Подробнее см. раздел «Почему это не работает с http.Handler?».

Особенности именованных параметров:

  • Соответствуют только одному сегменту пути
    Пример шаблона: /user/:user
Путь Совпадение
/user/gordon
/user/you
/user/gordon/profile
/user/

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

  • /user/new (статический)
  • /user/:user (параметр)

При этом маршрутизация для разных методов запроса (GET, POST и т.д.) обрабатывается независимо.


Параметры Catch-All (перехват всего)

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

Пример шаблона: /src/*filepath

Путь Совпадение
/src/
/src/somefile.go
/src/subdir/somefile.go

Особенности:

  • Перехватывают все оставшиеся сегменты пути, включая слэши
  • Полезны для реализации «обработчиков-прокси» (например, статических файлов в подкаталогах)

Как это работает?

Роутер использует древовидную структуру, активно использующую общие префиксы — по сути, это компактное префиксное дерево (радиксное дерево). Узлы с общим префиксом имеют общего родителя. Вот пример структуры дерева для GET-запросов:

Приоритет   Путь             Обработчик
9          \                *<1>
3          ├s               nil
2          |├earch\         *<2>
1          |└upport\        *<3>
2          ├blog\           *<4>
1          |    └:post      nil
1          |\     *<5>
2          ├about-us\       *<6>
1          |        └team\  *<7>
1          └contact\        *<8>

Здесь каждый *<num> представляет адрес обработчика (указатель на функцию). При прохождении пути от корня до листа формируется полный маршрут, например \blog\:post\, где :post — это параметр-заполнитель. В отличие от хэш-таблиц, дерево позволяет работать с динамическими параметрами, поскольку сопоставление происходит по шаблонам, а не через сравнение хэшей. Бенчмарки подтверждают эффективность этого подхода.

Оптимизации структуры:

  1. Иерархичность URL:
    Ограниченный набор символов в путях URL создает множество общих префиксов, что позволяет декомпозировать задачу маршрутизации на меньшие подзадачи.
  2. Раздельные деревья для методов:
    Для каждого HTTP-метода (GET, POST и т.д.) строится отдельное дерево. Это:
    • Экономит память (не требует хранения map[метод]->обработчик в каждом узле).
    • Сокращает пространство поиска до актуального метода.
  3. Приоритизация узлов:
    Дочерние узлы сортируются по приоритету — количеству обработчиков в поддеревьях. Это дает:
    • Быстрый доступ к популярным маршрутам.
    • Приоритетную обработку длинных путей (с максимальной «стоимостью»).

Визуализация приоритетов:

├------------
├---------
├-----
├----
├--
├--
└-

Почему это не работает с http.Handler?

Работает! Роутер сам реализует интерфейс http.Handler и предоставляет адаптеры для интеграции стандартных http.Handler и http.HandlerFunc в качестве httprouter.Handle.

Доступ к параметрам:

Для http.Handler именованные параметры доступны через контекст запроса:

func Hello(w http.ResponseWriter, r *http.Request) {
    params := httprouter.ParamsFromContext(r.Context()) 
    fmt.Fprintf(w, "hello, %s!\n", params.ByName("name"))
}

Альтернативный вариант:

params := r.Context().Value(httprouter.ParamsKey)

Автоматические OPTIONS-ответы и CORS

Для кастомизации автоматических ответов на OPTIONS-запросы (например, для поддержки CORS preflight или добавления заголовков) используйте обработчик Router.GlobalOPTIONS:

router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Если это CORS preflight запрос
    if r.Header.Get("Access-Control-Request-Method") != "" {
        header := w.Header()
        // Разрешаем методы из заголовка Allow
        header.Set("Access-Control-Allow-Methods", r.Header.Get("Allow")) 
        // Открываем доступ для всех доменов
        header.Set("Access-Control-Allow-Origin", "*")  
    }
    // Возвращаем статус 204 No Content
    w.WriteHeader(http.StatusNoContent)  
})

Где найти Middleware X?

HttpRouter — это исключительно высокопроизводительный роутер с минималистичным функционалом. Поскольку он реализует интерфейс http.Handler, вы можете:

  1. Цеплять любые совместимые middleware перед роутером (например, из библиотеки Gorilla).
  2. Создавать собственные middleware — это достаточно просто.
  3. Использовать фреймворки на основе HttpRouter.

Мультидомены и поддомены

Пример реализации маршрутизации для разных доменов/поддоменов:

// HostSwitch - карта для хранения обработчиков по доменам
type HostSwitch map[string]http.Handler

// Реализуем интерфейс http.Handler
func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // Ищем обработчик для текущего хоста
    if handler := hs[r.Host]; handler != nil {
        handler.ServeHTTP(w, r)
    } else {
        http.Error(w, "Forbidden", http.StatusForbidden) // Или редирект
    }
}

func main() {
    // Инициализация роутера
    router := httprouter.New()
    router.GET("/", Index)
    router.GET("/hello/:name", Hello)

    // Настройка HostSwitch
    hs := make(HostSwitch)
    hs["example.com:12345"] = router  // Для основного домена
    hs["api.example.com:12345"] = anotherRouter  // Для поддомена

    // Запуск сервера
    log.Fatal(http.ListenAndServe(":12345", hs))
}

Ключевые моменты:

  • Каждый домен/поддомен может иметь собственный роутер
  • Порт указывается вместе с доменом (example.com:12345)
  • Для необрабатываемых доменов возвращается 403 Forbidden (можно заменить на редирект)

Базовая аутентификация (Basic Auth)

Пример реализации HTTP Basic Authentication (RFC 2617) для обработчиков:

package main

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

	"github.com/julienschmidt/httprouter"
)

// BasicAuth - middleware для проверки учетных данных
func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		// Получаем учетные данные из заголовка
		user, password, hasAuth := r.BasicAuth()

		if hasAuth && user == requiredUser && password == requiredPassword {
			// Если аутентификация успешна - передаем запрос обработчику
			h(w, r, ps)
		} else {
			// Иначе запрашиваем аутентификацию
			w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		}
	}
}

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Открытая зона!\n")
}

func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Защищенная зона!\n")
}

func main() {
	user := "gordon"
	pass := "secret!"

	router := httprouter.New()
	router.GET("/", Index)
	router.GET("/protected/", BasicAuth(Protected, user, pass))

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

Цепочка обработчиков через NotFound

Примечание: Для корректной работы может потребоваться отключить Router.HandleMethodNotAllowed.

Вы можете использовать другой http.Handler (например, дополнительный роутер) для обработки запросов, которые не были найдены основным роутером:

router.NotFound = anotherRouter

Обслуживание статических файлов

Обработчик NotFound можно использовать для раздачи статических файлов из корневого пути /:

// Раздаем файлы из директории ./public
router.NotFound = http.FileServer(http.Dir("public"))

Однако такой подход нарушает строгие правила маршрутизации. Рекомендуется:

  1. Использовать выделенные подпути:
    router.ServeFiles("/static/*filepath", http.Dir("public"))
    
  2. Или явно задавать маршруты:
    router.GET("/files/*filepath", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
        http.ServeFile(w, r, path.Join("public", ps.ByName("filepath")))
    })
    

Веб-фреймворки на основе HttpRouter

Если HttpRouter кажется вам слишком минималистичным, рассмотрите эти высокоуровневые сторонние фреймворки, построенные на его основе:

Популярные решения

  1. Gin
    API в стиле Martini с значительно лучшей производительностью.
    Особенности: Middleware-цепочки, JSON-валидация, маршрутизация без рефлексии.

  2. Ace (устаревший, но исторически значимый)
    Один из первых быстрых фреймворков для Go.
    Преемник: Перешел в Gin.

  3. api2go
    Полноценная реализация JSON API с поддержкой JSON:API спецификации.
    Использование: Создание RESTful API с CRUD-операциями.

Для специфичных задач

  1. Goat
    Минималистичный REST API сервер.
    Философия: “Меньше кода — больше производительности”.

  2. Hitch
    Интегрирует HttpRouter с контекстом и middleware.
    Ключевая особенность: Простота связывания компонентов.

  3. Kami
    Работает через x/net/context.
    Для чего: Создание масштабируемых приложений с пробросом контекста.

  4. Siesta
    Композиция HTTP-обработчиков с поддержкой контекста.
    Паттерн: “Middleware как сервисы”.

Специализированные

  1. Medeina
    Вдохновлен Ruby-фреймворками Roda и Cuba.
    Подход: Древовидная маршрутизация.

  2. pbgo
    Мини-фреймворк для RPC/REST на основе Protobuf.
    Сценарии: Микросервисы с gRPC-like API.

  3. xmux
    Форк HttpRouter с поддержкой net/context.
    Отличие: Наследует производительность с расширенным API.

Для продакшена

  1. Hikaru
    Поддержка standalone-режима и Google App Engine.
    Плюсы: Кроссплатформенность.

  2. River
    Упрощенный REST-сервер для быстрого прототипирования.
    Фишка: Нулевая настройка для базовых сценариев.

  3. httpway
    Добавляет middleware-цепочки и graceful shutdown.
    Особенность: Совместимость с native HTTP-пакетами.


Как выбрать?

  • Для API: Gin, api2go, pbgo
  • Микросервисы: Siesta, xmux
  • Минимализм: Goat, River
  • Унаследованные проекты: Ace (переход на Gin)

Использование

Это всего лишь краткое введение, подробности смотрите в GoDoc. Давайте начнем с простого примера:

package main

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

    "github.com/julienschmidt/httprouter"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
    router := httprouter.New()
    router.GET("/", Index)
    router.GET("/hello/:name", Hello)

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

Принцип работы маршрутизации

Роутер сопоставляет входящие запросы по HTTP-методу и пути. Если для комбинации метод-путь зарегистрирован обработчик, запрос передается соответствующей функции. Для стандартных методов существуют сокращенные функции регистрации:

router.GET("/path", handler)    // GET
router.POST("/path", handler)   // POST 
router.PUT("/path", handler)    // PUT
router.PATCH("/path", handler)  // PATCH
router.DELETE("/path", handler) // DELETE

Для других методов используйте универсальный метод:

router.Handle("OPTIONS", "/path", handler)

Типы параметров пути

Синтаксис Тип
:name Именованный параметр
*name Catch-all параметр

1. Именованные параметры

Динамические сегменты пути. Соответствуют любому значению до следующего / или конца пути.

Шаблон:

/blog/:category/:post

Примеры соответствия:

Запрос Совпадение Параметры
/blog/go/request-routers category="go", post="request-routers"
/blog/go/request-routers/ ❌ (редирект) -
/blog/go/ -
/blog/go/request-routers/comments -

2. Catch-all параметры

Соответствуют любой части пути до конца, включая /. Всегда должны быть последним элементом.

Шаблон:

/files/*filepath

Примеры соответствия:

Запрос Совпадение Параметры
/files/ filepath="/"
/files/LICENSE filepath="/LICENSE"
/files/templates/article.html filepath="/templates/article.html"
/files ❌ (редирект) -

Работа с параметрами

Параметры хранятся в срезе структур Param (ключ-значение) и передаются обработчику третьим аргументом:

func Handler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    // Получение значения по имени
    user := ps.ByName("user") // для :user или *user

    // Получение по индексу (с доступом к имени параметра)
    param := ps[2]
    key   := param.Key   // имя 3-го параметра
    value := param.Value // значение 3-го параметра
}

Особенности:

  • Доступ к параметрам за O(1) по имени (использует внутреннюю оптимизацию)
  • Индексный доступ полезен при обработке неизвестных параметров
  • Параметры автоматически URL-decoded

Важные нюансы

  1. Редиректы:

    • Для путей с / в конце роутер автоматически делает редирект на каноничную версию
    • Можно отключить через router.RedirectTrailingSlash = false
  2. Кодировка:

    // Пример значения с спецсимволами
    // Запрос: /search/%D1%82%D0%B5%D1%81%D1%82
    query := ps.ByName("query") // автоматически декодируется в "тест"
    
  3. Производительность:

    • Параметры не аллоцируют память при отсутствии в пути
    • Используется slice pooling для повторного использования структур Param

Переменные

var ParamsKey = paramsKey{}

ParamsKey — это ключ контекста запроса, под которым хранятся параметры URL.


Функции

CleanPath

func CleanPath(p string) string

CleanPath — это URL-версия функции path.Clean. Она возвращает канонизированный путь, удаляя элементы . и ...

Правила обработки (применяются итеративно до полной обработки):

  1. Слэши
    Заменяет множественные слэши на один: ////

  2. Текущая директория (.)
    Удаляет элементы .:
    /././path/path

  3. Родительская директория (..)
    Удаляет комбинации ../ с предыдущим не-.. элементом:
    /a/b/../c/a/c
    /a/b/../../c/c

  4. Корневые ..
    Заменяет /.. в начале пути на /:
    /../a/a

  5. Пустой результат
    Если после обработки путь пуст, возвращает /:
    `` → /

Примеры:

CleanPath("//foo///bar")   // "/foo/bar"
CleanPath("/./foo/../bar") // "/bar"
CleanPath("/../")          // "/"
CleanPath("")              // "/"

Особенности:

  • Не выполняет URL-декодирование (работает с уже декодированным путем)
  • Сохраняет регистр символов
  • Полезен для нормализации путей перед маршрутизацией

Технические детали

  1. Использование в роутере:
    HttpRouter автоматически применяет CleanPath к входящим запросам перед сопоставлением маршрутов.

  2. Безопасность:
    Защищает от path traversal атак:

    // Запрос "/secret/../../etc/passwd" будет преобразован в "/etc/passwd",
    // но роутер обработает его только если есть явный маршрут
    
  3. Производительность:
    Реализация использует zero-allocation алгоритм для минимизации нагрузк

Типы

type Handle

type Handle func(http.ResponseWriter, *http.Request, Params)

Handle — функция, которая может быть зарегистрирована для обработки HTTP-запросов. Аналог http.HandlerFunc, но с третьим параметром для значений параметров URL.


type Param (добавлено в v1.1.0)

type Param struct {
    Key   string // Ключ параметра
    Value string // Значение параметра
}

Param представляет отдельный параметр URL, состоящий из ключа и значения.


type Params (добавлено в v1.1.0)

type Params []Param

Params — это срез параметров Param, возвращаемый роутером. Срез упорядочен: первый параметр URL соответствует первому элементу среза. Безопасен для доступа по индексу.

Методы:

  1. ParamsFromContext (добавлено в v1.2.0)
func ParamsFromContext(ctx context.Context) Params

Извлекает параметры URL из контекста запроса. Возвращает nil, если параметров нет.

  1. ByName (добавлено в v1.1.0)
func (ps Params) ByName(name string) string

Возвращает значение первого параметра с указанным ключом. Если параметр не найден, возвращает пустую строку.


type Router

type Router struct {
    // Автоматический редирект при несовпадении пути, 
    // но наличии обработчика для пути с/без завершающего слэша.
    // Пример: /foo/ → /foo (код 301 для GET, 307 для других методов)
    RedirectTrailingSlash bool

    // Автокоррекция пути при отсутствии обработчика:
    // 1. Удаляет избыточные элементы (../, //)
    // 2. Поиск без учета регистра
    // 3. Редирект на исправленный путь (301/307)
    RedirectFixedPath bool

    // Проверка допустимых методов при неудачной маршрутизации.
    // Если включено, отправляет 405 Method Not Allowed с заголовком Allow.
    HandleMethodNotAllowed bool

    // Автоматическая обработка OPTIONS-запросов.
    // Пользовательские обработчики OPTIONS имеют приоритет.
    HandleOPTIONS bool

    // Глобальный обработчик для автоматических OPTIONS-запросов.
    // Вызывается только если HandleOPTIONS=true и нет специфичного обработчика.
    GlobalOPTIONS http.Handler

    // Обработчик для ненайденных маршрутов (по умолчанию — http.NotFound).
    NotFound http.Handler

    // Обработчик для недопустимых методов (код 405).
    // По умолчанию — http.Error с StatusMethodNotAllowed.
    MethodNotAllowed http.Handler

    // Обработчик паник (код 500).
    // Предотвращает аварийное завершение сервера.
    PanicHandler func(http.ResponseWriter, *http.Request, interface{})

    // содержит скрытые или неэкспортируемые поля
}

Router реализует интерфейс http.Handler и предоставляет конфигурируемую систему маршрутизации.


Ключевые особенности

  1. Гибкость обработчиков:

    • Handle поддерживает параметры через Params
    • Совместимость со стандартными http.Handler/http.HandlerFunc
  2. Безопасность:

    • ParamsFromContext для безопасного доступа к параметрам из middleware
    • PanicHandler для обработки критических ошибок
  3. Производительность:

    • Срезы Params избегают аллокаций памяти
    • Прямой доступ к параметрам по индексу (O(1))

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

router.GET("/user/:id", func(w http.ResponseWriter, r *http.Request, ps Params) {
    id := ps.ByName("id") // или ps[0].Value
})

Методы типа Router

func New() *Router

func New() *Router

Создает и возвращает новый инициализированный роутер. Автокоррекция путей (включая обработку trailing slashes) включена по умолчанию.


Методы-сокращения для HTTP-методов

Метод Описание
DELETE(path string, handle Handle) Сокращение для router.Handle(http.MethodDelete, path, handle)
GET(path string, handle Handle) Сокращение для GET-запросов
HEAD(path string, handle Handle) (v1.1.0+) Сокращение для HEAD-запросов
OPTIONS(path string, handle Handle) (v1.1.0+) Сокращение для OPTIONS-запросов
PATCH(path string, handle Handle) Сокращение для PATCH-запросов
POST(path string, handle Handle) Сокращение для POST-запросов
PUT(path string, handle Handle) Сокращение для PUT-запросов
func (r *Router) DELETE(path string, handle Handle)
func (r *Router) GET(path string, handle Handle)

Пример:

router := httprouter.New()
router.GET("/users", listUsers)
router.POST("/users", createUser)

func (*Router) Handle(method, path string, handle Handle)

Регистрирует обработчик для указанного HTTP-метода и пути.

router.Handle("PROPFIND", "/resource", handleResource)

func (*Router) Handler(method, path string, handler http.Handler)

Адаптер для использования стандартного http.Handler. Параметры доступны через контекст:

router.Handler("GET", "/user/:id", customHandler)
// В обработчике:
params := httprouter.ParamsFromContext(r.Context())

func (*Router) HandlerFunc(method, path string, handler http.HandlerFunc)

Адаптер для http.HandlerFunc:

router.HandlerFunc("GET", "/", indexHandler)

func (*Router) Lookup(method, path string) (Handle, Params, bool)

Ручной поиск обработчика для комбинации метод+путь. Возвращает:

  • Обработчик
  • Параметры пути
  • Флаг необходимости редиректа (добавить/убрать trailing slash)

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

if handle, ps, _ := router.Lookup(r.Method, r.URL.Path); handle != nil {
    // Кастомная обработка
}

func (*Router) ServeFiles(path string, root http.FileSystem)

Обслуживает статические файлы из указанной файловой системы. Путь должен содержать /*filepath в конце:

// Доступ к /var/www/file.txt по URL /src/file.txt
router.ServeFiles("/src/*filepath", http.Dir("/var/www"))

Особенности:

  • Использует стандартный http.FileServer
  • Для 404 ошибок применяется http.NotFound, а не роутерский NotFound-обработчик

func (*Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

Позволяет роутеру удовлетворять интерфейсу http.Handler:

http.ListenAndServe(":8080", router)

Особенности использования

  1. Порядок регистрации:
    Нет приоритета “первый зарегистрированный — первый обработанный”. Каждый путь+метод соответствует ровно одному обработчику.

  2. Производительность:
    Все методы регистрации используют единую оптимизированную логику:

    // Эти вызовы эквивалентны по производительности
    router.GET("/path", handler)
    router.Handle("GET", "/path", handler)
    
  3. Безопасность:
    ServeFiles автоматически защищает от directory traversal атак:

    // Запрос `/src/../../../etc/passwd` будет отклонен
    

16 - Пакет OAuth2 языка программирования Go

Пакет oauth2 содержит реализацию клиента для спецификации OAuth 2.0.
sequenceDiagram
    participant ClientApp
    participant User
    participant AuthServer
    participant ResourceServer

    %% Регистрация нового пользователя
    Note over ClientApp: 1. Создание нового пользователя
    ClientApp->>AuthServer: POST /register (не часть OAuth)
    AuthServer-->>ClientApp: client_id, client_secret

    %% Авторизация (Authorization Code Flow)
    Note over ClientApp,User: 2. Авторизация пользователя
    ClientApp->>User: Redirect to AuthURL (config.AuthCodeURL())
    User->>AuthServer: Авторизация/согласие
    AuthServer->>User: Redirect с code и state
    User->>ClientApp: Передача code
    ClientApp->>AuthServer: Exchange code на token (config.Exchange())
    AuthServer-->>ClientApp: access_token, refresh_token

    %% Использование токена
    Note over ClientApp,ResourceServer: 3. Доступ к ресурсам
    ClientApp->>ResourceServer: Запрос с токеном (transport)
    ResourceServer-->>ClientApp: Данные пользователя

    %% Другие поддерживаемые события
    Note over ClientApp,AuthServer: 4. Другие сценарии

    %% Refresh Token Flow
    ClientApp->>AuthServer: Запрос нового токена (TokenSource.Token())
    AuthServer-->>ClientApp: Новый access_token

    %% Client Credentials Flow
    ClientApp->>AuthServer: Запрос токена (clientcredentials.Config)
    AuthServer-->>ClientApp: Сервисный токен

    %% Device Flow
    ClientApp->>AuthServer: Запрос device code (DeviceAuth())
    AuthServer-->>ClientApp: user_code, verification_uri
    User->>AuthServer: Ввод user_code
    ClientApp->>AuthServer: Polling для токена (DeviceAccessToken())
    AuthServer-->>ClientApp: Устройственный токен

Ключевые элементы модели:

  1. Типы пакета OAuth2:

    • Config - основной объект конфигурации
    • Endpoint - URLs сервера авторизации
    • Token - содержит access/refresh токены
    • TokenSource - механизм обновления токенов
    • Transport - HTTP-транспорт с авторизацией
  2. Основные методы:

    • AuthCodeURL() - генерация URL авторизации
    • Exchange() - обмен code на токен
    • Client() - создание авторизованного HTTP-клиента
    • TokenSource() - автоматическое обновление токенов
  3. Поддерживаемые потоки:

    • Authorization Code Flow (основной)
    • Client Credentials Flow (для сервисов)
    • Device Flow (для ТВ/IoT)
    • Refresh Token Flow
  4. Дополнительные компоненты:

    • PKCE (защита от CSRF)
    • Различные AuthStyle (методы аутентификации)
    • Кастомные AuthCodeOption

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

Переменные

var HTTPClient internal.ContextKey

HTTPClient - это ключ контекста, используемый с context.WithValue для ассоциации *http.Client с контекстом.

var NoContext = context.TODO()

NoContext - контекст по умолчанию, который следует использовать, если не применяется собственный context.Context.

Устарело: Вместо этого используйте context.Background или context.TODO.


Функции

func GenerateVerifier

func GenerateVerifier() string

GenerateVerifier генерирует верификатор кода PKCE со случайными 32 октетами. Соответствует рекомендациям RFC 7636.

Новый верификатор должен генерироваться для каждой авторизации. Полученный верификатор следует передавать в Config.AuthCodeURL или Config.DeviceAuth с S256ChallengeOption, а в Config.Exchange или Config.DeviceAccessToken - с VerifierOption.

func NewClient

func NewClient(ctx context.Context, src TokenSource) *http.Client

NewClient создает *http.Client из context.Context и TokenSource. Возвращенный клиент действителен только в течение времени жизни контекста.

Примечание: если пользовательский *http.Client предоставлен через context.Context, он используется только для получения токена и не влияет на *http.Client, возвращаемый из NewClient.

Особый случай: если src равен nil, возвращается не-OAuth2 клиент с использованием предоставленного контекста.

func RegisterBrokenAuthHeaderProvider

Устарело.

func S256ChallengeFromVerifier

func S256ChallengeFromVerifier(verifier string) string

S256ChallengeFromVerifier возвращает challenge-код PKCE, полученный из верификатора методом S256.

Предпочтительнее использовать S256ChallengeOption, где это возможно.


Типы

type AuthCodeOption

type AuthCodeOption interface {
	// содержит неэкспортируемые методы
}

AuthCodeOption передается в Config.AuthCodeURL.

Варианты:

var (
	// AccessTypeOnline и AccessTypeOffline - параметры для Options.AuthCodeURL.
	// Они изменяют поле "access_type" в URL, возвращаемом AuthCodeURL.
	AccessTypeOnline  AuthCodeOption = SetAuthURLParam("access_type", "online")
	AccessTypeOffline AuthCodeOption = SetAuthURLParam("access_type", "offline")

	// ApprovalForce требует от пользователя подтверждения запроса разрешений
	ApprovalForce AuthCodeOption = SetAuthURLParam("prompt", "consent")
)

func S256ChallengeOption

func S256ChallengeOption(verifier string) AuthCodeOption

S256ChallengeOption создает PKCE challenge из верификатора методом S256. Должен передаваться только в Config.AuthCodeURL или Config.DeviceAuth.

func SetAuthURLParam

func SetAuthURLParam(key, value string) AuthCodeOption

SetAuthURLParam создает AuthCodeOption для передачи параметров key/value на endpoint авторизации провайдера.

func VerifierOption

func VerifierOption(verifier string) AuthCodeOption

VerifierOption возвращает AuthCodeOption с верификатором кода PKCE. Должен передаваться только в Config.Exchange или Config.DeviceAccessToken.


type AuthStyle

type AuthStyle int

AuthStyle определяет способ аутентификации запросов токенов.

Варианты:

const (
	// Автоопределение стиля аутентификации
	AuthStyleAutoDetect AuthStyle = 0
	
	// Передача client_id и client_secret в теле POST
	AuthStyleInParams AuthStyle = 1
	
	// Использование HTTP Basic Authorization OAuth2 RFC 6749 section 2.3.1.
	AuthStyleInHeader AuthStyle = 2
)

type Config

type Config struct {
    // ClientID - публичный идентификатор вашего приложения,
    // выдается провайдером при регистрации OAuth-клиента
    ClientID     string
    
    // ClientSecret - секретный ключ вашего приложения,
    // должен храниться безопасно (не в клиентском коде)
    ClientSecret string
    
    // Endpoint - содержит URL-адреса сервера авторизации:
    //   AuthURL - endpoint для получения authorization code
    //   TokenURL - endpoint для обмена code на access token
    Endpoint     Endpoint
    
    // RedirectURL - URL, на который провайдер перенаправит
    // пользователя после авторизации. Должен точно совпадать
    // с URL, зарегистрированным у провайдера
    RedirectURL  string
    
    // Scopes - запрашиваемые области доступа (разрешения),
    // определяют, к каким ресурсам будет доступ у приложения
    Scopes       []string
}

Config описывает стандартный 3-этапный OAuth2-поток с информацией о клиентском приложении и URL endpoint’ов сервера.

Пример
package main

import (
	"context"
	"fmt"
	"log"

	"golang.org/x/oauth2"
)

func main() {
	ctx := context.Background()
	conf := &oauth2.Config{
		ClientID:     "ВАШ_CLIENT_ID", 
		ClientSecret: "ВАШ_CLIENT_SECRET",
		Scopes:       []string{"ДОСТУП1", "ДОСТУП2"},
		Endpoint: oauth2.Endpoint{
			AuthURL:  "https://provider.com/o/oauth2/auth",  // URL авторизации
			TokenURL: "https://provider.com/o/oauth2/token", // URL получения токена
		},
	}

	// Используем PKCE для защиты от CSRF атак
	// Спецификация: https://www.ietf.org/archive/id/draft-ietf-oauth-security-topics-22.html#name-countermeasures-6
	verifier := oauth2.GenerateVerifier() // Генерируем верификатор для PKCE

	// Перенаправляем пользователя на страницу согласия для запроса разрешений
	// на указанные выше области доступа (scopes)
	url := conf.AuthCodeURL(
		"state",                          // Уникальный state для защиты
		oauth2.AccessTypeOffline,          // Запрашиваем refresh-токен  
		oauth2.S256ChallengeOption(verifier), // Добавляем PKCE challenge
	)
	fmt.Printf("Перейдите по URL для авторизации: %v", url)

	// Получаем authorization code из redirect URL.
	// Exchange выполнит обмен кода на токен доступа.
	// HTTP клиент от conf.Client будет автоматически обновлять токен.
	var code string
	if _, err := fmt.Scan(&code); err != nil {
		log.Fatal(err)
	}
	
	// Обмениваем код на токен, передавая верификатор PKCE
	tok, err := conf.Exchange(ctx, code, oauth2.VerifierOption(verifier))
	if err != nil {
		log.Fatal(err)
	}

	// Создаем HTTP клиент с автоматическим обновлением токенов
	client := conf.Client(ctx, tok)
	client.Get("...") // Делаем авторизованные запросы
}
Пример CustomHTTP
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"

	"golang.org/x/oauth2"
)

func main() {
	ctx := context.Background()

	// Конфигурация OAuth2 клиента
	conf := &oauth2.Config{
		ClientID:     "ВАШ_CLIENT_ID",      // Идентификатор клиента
		ClientSecret: "ВАШ_CLIENT_SECRET",  // Секретный ключ клиента
		Scopes:       []string{"ДОСТУП1", "ДОСТУП2"},  // Запрашиваемые разрешения
		Endpoint: oauth2.Endpoint{
			TokenURL: "https://provider.com/o/oauth2/token",  // URL для получения токена
			AuthURL:  "https://provider.com/o/oauth2/auth",   // URL для авторизации
		},
	}

	// Перенаправляем пользователя на страницу авторизации для получения разрешений
	// на указанные области доступа (scopes)
	url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)  // "state" для защиты от CSRF
	fmt.Printf("Перейдите по ссылке для авторизации: %v", url)

	// Получаем код авторизации из redirect URL.
	// Exchange выполняет обмен кода на токен доступа.
	// HTTP клиент, возвращаемый conf.Client, будет автоматически обновлять токен.
	var code string
	if _, err := fmt.Scan(&code); err != nil {
		log.Fatal(err)
	}

	// Используем кастомный HTTP клиент с таймаутом 2 секунды для запроса токена
	httpClient := &http.Client{Timeout: 2 * time.Second}
	ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)

	// Получаем токен доступа
	tok, err := conf.Exchange(ctx, code)
	if err != nil {
		log.Fatal(err)
	}

	// Создаем HTTP клиент с автоматической авторизацией
	client := conf.Client(ctx, tok)
	_ = client  // Используйте client для авторизованных запросов
}

func (*Config) AuthCodeURL

func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string

AuthCodeURL возвращает URL страницы согласия OAuth 2.0 провайдера, которая явно запрашивает разрешения для требуемых областей доступа (scopes).

Параметры:

  1. state
    Случайное значение (опaque), используемое клиентом для поддержания состояния между запросом и callback-вызовом. Сервер авторизации включает это значение при перенаправлении пользователя обратно в клиентское приложение.

  2. opts (дополнительные опции)
    Может включать:

    • AccessTypeOnline или AccessTypeOffline - тип доступа
    • ApprovalForce - принудительное подтверждение разрешений

Защита от CSRF-атак:

  1. Рекомендуемый способ:
    Включите PKCE challenge через S256ChallengeOption.
    Примечание: Не все серверы поддерживают PKCE.

  2. Альтернативный способ:
    Генерация случайного параметра state с последующей проверкой после обмена токена.

Ссылки на стандарты:

func (*Config) Client

func (c *Config) Client(ctx context.Context, t *Token) *http.Client

Клиент возвращает HTTP-клиента, используя предоставленный токен. Токен будет автоматически обновляться по мере необходимости. Базовый HTTP-транспорт будет получен с использованием предоставленного контекста. Возвращенный клиент и его Transport не должны быть изменены.

func (*Config) DeviceAccessToken

func (c *Config) DeviceAccessToken(ctx context.Context, da *DeviceAuthResponse, opts ...AuthCodeOption) (*Token, error)

DeviceAccessToken опрашивает сервер для обмена device code на токен.

func (*Config) DeviceAuth

func (c *Config) DeviceAuth(ctx context.Context, opts ...AuthCodeOption) (*DeviceAuthResponse, error)

DeviceAuth возвращает структуру с device code для авторизации на другом устройстве.

Пример
var config Config
ctx := context.Background()
response, err := config.DeviceAuth(ctx)
if err != nil {
	panic(err)
}
fmt.Printf("please enter code %s at %s\n", response.UserCode, response.VerificationURI)
token, err := config.DeviceAccessToken(ctx, response)
if err != nil {
	panic(err)
}
fmt.Println(token)

func (*Config) Exchange

func (c *Config) Exchange(ctx context.Context, code string, opts ...AuthCodeOption) (*Token, error)

Выполняет обмен authorization code на токен доступа.

Использование: Вызывается после перенаправления пользователя обратно на Redirect URI (URL, полученный из AuthCodeURL).

Параметры:

  • ctx - контекст, может содержать кастомный HTTP-клиент (см. переменную HTTPClient)
  • code - authorization code из параметра http.Request.FormValue("code")
  • opts - опции, при использовании PKCE должен включать VerifierOption

Безопасность:

  1. Обязательно проверяйте параметр state (из http.Request.FormValue("state")) перед вызовом для защиты от CSRF
  2. Для PKCE передавайте верификатор через VerifierOption

func (*Config) PasswordCredentialsToken

func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error)

Получает токен по связке username/password (Resource Owner Password Credentials flow).

Рекомендации RFC 6749: Следует использовать ТОЛЬКО при:

  • Высокой степени доверия между клиентом и владельцем ресурса
  • Когда клиент является частью ОС или привилегированного приложения
  • При отсутствии других доступных способов авторизации

Ссылка: RFC 6749 Section 4.3

Параметры:

  • ctx - может содержать кастомный HTTP-клиент
  • username, password - учетные данные владельца ресурса

Примечание: Этот grant type считается менее безопасным и должен применяться в исключительных случаях.

func (*Config) TokenSource

func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource

TokenSource возвращает источник токенов с автоматическим обновлением.


type DeviceAuthResponse

type DeviceAuthResponse struct {
    // DeviceCode - код устройства для OAuth-потока
    DeviceCode string `json:"device_code"`
    
    // UserCode - код, который пользователь должен ввести
    // на странице верификации
    UserCode string `json:"user_code"`
    
    // VerificationURI - URL страницы, куда пользователь
    // должен ввести user code
    VerificationURI string `json:"verification_uri"`
    
    // VerificationURIComplete (опционально) - полный URL верификации
    // с уже подставленным user code. Обычно отображается пользователю
    // в графической форме (например, QR-код)
    VerificationURIComplete string `json:"verification_uri_complete,omitempty"`
    
    // Expiry - время истечения срока действия 
    // device code и user code
    Expiry time.Time `json:"expires_in,omitempty"`
    
    // Interval - интервал в секундах между запросами
    // на проверку авторизации (polling)
    Interval int64 `json:"interval,omitempty"`
}

DeviceAuthResponse описывает успешный ответ Device Authorization по RFC 8628.

func (DeviceAuthResponse) MarshalJSON

func (d DeviceAuthResponse) MarshalJSON() ([]byte, error)

func (*DeviceAuthResponse) UnmarshalJSON

func (c *DeviceAuthResponse) UnmarshalJSON(data []byte) error

type Endpoint

type Endpoint struct {
    AuthURL       string    // URL endpoint'а авторизации (для получения authorization code)
    DeviceAuthURL string    // URL для Device Flow авторизации (RFC 8628)
    TokenURL      string    // URL endpoint'а получения токенов (для обмена code на token)
    AuthStyle     AuthStyle // Предпочтительный метод аутентификации клиента
}

Предназначение: Содержит все необходимые URL для OAuth 2.0 flow.

Использование: Конфигурация клиента для взаимодействия с OAuth провайдером.

type RetrieveError

type RetrieveError struct {
    Response         *http.Response // HTTP-ответ сервера
    Body             []byte         // Тело ответа (может быть усечено)
    ErrorCode        string         // Код ошибки OAuth (RFC 6749 Section 5.2)
    ErrorDescription string         // Человекочитаемое описание ошибки
    ErrorURI         string         // Ссылка на документацию по ошибке
}

RetrieveError - ошибка, возвращаемая при неверном статусе HTTP или ошибке OAuth2.

Предназначение: Ошибки, возвращаемые OAuth сервером.

Особенности:

  • Соответствует стандарту OAuth 2.0 (RFC 6749)
  • Включает как HTTP-детали, так и специфичные OAuth ошибки

type Token

type Token struct {
    AccessToken  string    // Основной токен доступа
    TokenType    string    // Тип токена (обычно "Bearer")
    RefreshToken string    // Токен для обновления (опционально)
    Expiry       time.Time // Время истечения срока действия
    ExpiresIn    int64     // Срок жизни токена в секундах
}

Token содержит учетные данные для авторизации запросов.

Предназначение: Хранит OAuth 2.0 токены и метаданные.

  • AccessToken всегда обязателен
  • RefreshToken может отсутствовать в некоторых flow
  • Expiry вычисляется из ExpiresIn при получении токена

type TokenSource

type TokenSource interface {
	Token() (*Token, error) // Возвращает текущий/новый токен
}

TokenSource - любой источник, который может возвращать токен.

Предназначение: Абстракция для источников токенов.

Реализации:

  • StaticTokenSource (фиксированный токен)
  • ReuseTokenSource (с автоматическим обновлением)
  • Config.TokenSource (получение токенов из Config)

type Transport

type Transport struct {
    Source TokenSource         // Источник токенов для авторизации
    Base   http.RoundTripper   // Базовый RoundTripper (по умолчанию http.DefaultTransport)
}

Transport - это http.RoundTripper для OAuth 2.0 запросов.

Предназначение: Добавляет OAuth-авторизацию к HTTP-запросам.

Принцип работы:

  • Получает токен из Source
  • Добавляет Authorization header
  • Делегирует запрос базовому RoundTripper

16.1 - Пакет jwt (JSON Web Token)

Пакет jwt реализует поток OAuth 2.0 с использованием JSON Web Token, известный как “двухногая OAuth 2.0” (two-legged OAuth 2.0).

Спецификация: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12

Типы

type Config

type Config struct {
    // Email - идентификатор OAuth-клиента
    Email string

    // PrivateKey содержит содержимое RSA-ключа или PEM-файла
    PrivateKey []byte

    // PrivateKeyID - необязательный идентификатор ключа
    PrivateKeyID string

    // Subject - необязательный пользователь для имперсонации
    Subject string

    // Scopes - запрашиваемые области доступа
    Scopes []string

    // TokenURL - endpoint для JWT-потока
    TokenURL string

    // Expires - срок действия токена
    Expires time.Duration

    // Audience - целевая аудитория запроса
    Audience string

    // PrivateClaims - кастомные JWT-claims
    PrivateClaims map[string]any

    // UseIDToken - использовать ID-токен вместо access-токена
    UseIDToken bool
}

Config содержит конфигурацию для получения токенов через JWT (“двухногая OAuth 2.0”).

Пример
package main

import (
	"context"

	"golang.org/x/oauth2/jwt"
)

func main() {
	ctx := context.Background()
	conf := &jwt.Config{
		Email: "xxx@developer.com",
		// The contents of your RSA private key or your PEM file
		// that contains a private key.
		// If you have a p12 file instead, you
		// can use `openssl` to export the private key into a pem file.
		//
		//    $ openssl pkcs12 -in key.p12 -out key.pem -nodes
		//
		// It only supports PEM containers with no passphrase.
		PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."),
		Subject:    "user@example.com",
		TokenURL:   "https://provider.com/o/oauth2/token",
	}
	// Initiate an http.Client, the following GET request will be
	// authorized and authenticated on the behalf of user@example.com.
	client := conf.Client(ctx)
	client.Get("...")
}

Методы Config

func (*Config) Client

func (c *Config) Client(ctx context.Context) *http.Client

Client возвращает HTTP-клиент, который автоматически добавляет Authorization-заголовки с токенами, полученными из конфигурации.

Возвращенный клиент и его Transport не должны изменяться.

func (*Config) TokenSource

func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource

TokenSource возвращает источник токенов JWT, используя конфигурацию и HTTP-клиент из переданного контекста.

Особенности реализации

  1. Формат ключей:

    • Поддерживаются RSA-ключи и PEM-файлы без пароля
    • Для конвертации PKCS#12 в PEM используйте:
      openssl pkcs12 -in key.p12 -out key.pem -nodes
      
  2. Кастомные claims:

  3. Аудитория:

    • Если Audience не указан, используется TokenURL
  4. Тип токена:

    • При UseIDToken=true будет использоваться ID-токен вместо access-токена

16.2 - Пакет jws (JSON Web Signature)

Пакет jws предоставляет частичную реализацию кодирования и декодирования JSON Web Signature. Существует для поддержки пакета golang.org/x/oauth2.

Устарело: этот пакет не предназначен для публичного использования и может быть удален в будущем. Существует только для внутреннего использования. Рекомендуется использовать другой JWS-пакет или скопировать этот пакет в свой исходный код.

Функции

func Encode

func Encode(header *Header, c *ClaimSet, key *rsa.PrivateKey) (string, error)

Encode кодирует подписанный JWS с предоставленными заголовком и набором claims. Использует crypto/rsa.SignPKCS1v15 с указанным RSA-приватным ключом.

func EncodeWithSigner

func EncodeWithSigner(header *Header, c *ClaimSet, sg Signer) (string, error)

EncodeWithSigner кодирует заголовок и набор claims с использованием предоставленного подписывающего устройства.

func Verify

func Verify(token string, key *rsa.PublicKey) error

Verify проверяет, была ли подпись JWT-токена создана приватным ключом, соответствующим предоставленному публичному ключу.

Типы

type ClaimSet

type ClaimSet struct {
    Iss   string `json:"iss"`             // email клиентского приложения
    Scope string `json:"scope,omitempty"` // запрашиваемые разрешения
    Aud   string `json:"aud"`             // целевая аудитория (опционально)
    Exp   int64  `json:"exp"`             // время истечения (Unix epoch)
    Iat   int64  `json:"iat"`             // время выдачи (Unix epoch)
    Typ   string `json:"typ,omitempty"`   // тип токена (опционально)
    Sub   string `json:"sub,omitempty"`   // email для делегированного доступа
    Prn   string `json:"prn,omitempty"`   // устаревший аналог Sub
    PrivateClaims map[string]any `json:"-"` // кастомные claims
}

ClaimSet содержит информацию о JWT-подписи, включая запрашиваемые разрешения, цель токена, издателя, время выдачи и срок действия.

func Decode

func Decode(payload string) (*ClaimSet, error)

Decode декодирует набор claims из JWS-полезной нагрузки.

type Header

type Header struct {
    Algorithm string `json:"alg"`  // алгоритм подписи
    Typ       string `json:"typ"`  // тип токена
    KeyID     string `json:"kid,omitempty"` // идентификатор ключа (опционально)
}

Header представляет заголовок для подписанных JWS-полезных нагрузок.

type Signer

type Signer func(data []byte) (sig []byte, err error)

Signer возвращает подпись для предоставленных данных.

Особенности реализации

  1. Поддержка алгоритмов:

    • Основная реализация использует RSA с PKCS1v15
    • Возможность подключения кастомных подписывающих устройств
  2. Устаревшие поля:

    • Поле Prn сохраняется для обратной совместимости
    • Рекомендуется использовать Sub вместо Prn
  3. Кастомные claims:

    • Поддерживаются через поле PrivateClaims
    • Не включаются в стандартную JSON-маршализацию
  4. Безопасность:

    • Пакет помечен как устаревший для публичного использования
    • Рекомендуется использовать более полные реализации JWS

16.3 - Пакет authhandler (Three-Legged OAuth 2.0)

Пакет authhandler реализует TokenSource для поддержки “трехногового OAuth 2.0” через кастомный AuthorizationHandler.

Функции

func TokenSource

func TokenSource(
    ctx context.Context, 
    config *oauth2.Config, 
    state string, 
    authHandler AuthorizationHandler,
) oauth2.TokenSource

TokenSource возвращает oauth2.TokenSource, который получает access-токены, используя трехноговый OAuth-поток.

Параметры:

  • ctx - контекст для операции Exchange
  • config - полная конфигурация OAuth (AuthURL, TokenURL, Scope)
  • state - уникальная строка состояния для защиты от CSRF
  • authHandler - обработчик авторизации для получения согласия пользователя

Особенности:

  • Проверяет соответствие параметра state в запросе и ответе
  • Обменивает auth-код на OAuth-токен после проверки

func TokenSourceWithPKCE

func TokenSourceWithPKCE(
    ctx context.Context,
    config *oauth2.Config,
    state string,
    authHandler AuthorizationHandler, 
    pkce *PKCEParams,
) oauth2.TokenSource

TokenSourceWithPKCE - расширенная версия с поддержкой PKCE (Proof Key for Code Exchange).

Дополнительный параметр:

  • pkce - параметры для защиты от CSRF через code challenge и code verifier

Рекомендации:

Типы

type AuthorizationHandler

type AuthorizationHandler func(authCodeURL string) (code string, state string, err error)

AuthorizationHandler - обработчик для трехногового OAuth, который:

  1. Перенаправляет пользователя по URL для получения согласия
  2. Возвращает auth-код и состояние после подтверждения

type PKCEParams

type PKCEParams struct {
    Challenge       string // Зашифрованный code verifier (base64-url)
    ChallengeMethod string // Метод шифрования (напр. S256)
    Verifier        string // Оригинальный секрет (незашифрованный)
}

PKCEParams содержит параметры для защиты потока PKCE.

Особенности реализации

  1. Безопасность:

    • Обязательная проверка параметра state
    • Поддержка современных методов защиты PKCE
  2. Гибкость:

    • Возможность кастомной обработки авторизации
    • Поддержка различных методов шифрования PKCE
  3. Рекомендации:

    • Всегда использовать уникальный state для каждого запроса
    • Для мобильных приложений предпочтительнее TokenSourceWithPKCE

16.4 - Пакет clientcredentials OAuth 2.0 Client Credentials Flow

Пакет реализует поток OAuth 2.0 учетные данные клиента client credentials, также известный как двухногая OAuth 2.0

Используется, когда:

  • Клиент действует от своего имени
  • Клиент является владельцем ресурса
  • Запрашивается доступ к защищенным ресурсам на основе предварительной авторизации

Спецификация: https://tools.ietf.org/html/rfc6749#section-4.4

Тип Config

type Config struct {
    // ClientID - идентификатор приложения
    ClientID string
    
    // ClientSecret - секрет приложения  
    ClientSecret string
    
    // TokenURL - endpoint сервера для получения токенов
    TokenURL string
    
    // Scopes - запрашиваемые разрешения (опционально)
    Scopes []string
    
    // EndpointParams - дополнительные параметры запроса
    EndpointParams url.Values
    
    // AuthStyle - способ аутентификации клиента
    AuthStyle oauth2.AuthStyle
}

Config описывает двухноговый OAuth2 поток, содержащий информацию о клиентском приложении и URL endpoint’ов сервера.

Методы Config

func (*Config) Client

func (c *Config) Client(ctx context.Context) *http.Client

Возвращает HTTP-клиент, который:

  • Автоматически добавляет токены авторизации
  • Обновляет токены по истечении срока
  • Использует HTTP-клиент из контекста (через oauth2.HTTPClient)

Важно: возвращенный клиент и его Transport не должны изменяться.

func (*Config) Token

func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) 

Получает токен, используя учетные данные клиента (client credentials).

Может использовать кастомный HTTP-клиент из контекста.

func (*Config) TokenSource

func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource

Возвращает источник токенов, который:

  • Возвращает текущий токен, пока он действителен
  • Автоматически обновляет токен при истечении срока
  • Использует client ID и client secret для обновления

Рекомендация: большинству пользователей следует использовать Config.Client вместо прямого использования TokenSource.

Особенности реализации

  1. Сценарии использования:

    • Сервер-серверное взаимодействие
    • Микросервисная архитектура
    • Фоновые процессы без участия пользователя
  2. Безопасность:

    • ClientSecret должен храниться защищенно
    • Рекомендуется использовать HTTPS для всех запросов
  3. Гибкость:

    • Поддержка различных методов аутентификации (AuthStyle)
    • Возможность передачи дополнительных параметров (EndpointParams)

17 - Описание пакета string языка программирования Go

Пакет strings реализует простые функции для работы со строками в кодировке UTF-8.

Информацию о строках UTF-8 в Go см. на сайте https://blog.golang.org/strings.

17.1 - Функции пакета string языка программирования Go

Подробное описание функций пакета string языка программирования Go

func Clone

func Clone(s string) string

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

Использование Clone может помочь таким программам использовать меньше памяти.

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

Пример
package main

import (
	"fmt"
	"strings"
	"unsafe"
)

func main() {
	s := "abc"
	clone := strings.Clone(s)
	fmt.Println(s == clone)
	fmt.Println(unsafe.StringData(s) == unsafe.StringData(clone))
}
Output:

true
false

func Compare

func Compare(a, b string) int

Compare возвращает целое число, сравнивающее две строки лексикографически. Результат будет 0, если a == b, -1, если a < b, и +1, если a > b.

Используйте Compare, когда вам нужно выполнить трехстороннее сравнение (например, с помощью slices.SortFunc). Обычно более понятно и всегда быстрее использовать встроенные операторы сравнения строк ==, <, > и т. д.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Compare("a", "b"))
	fmt.Println(strings.Compare("a", "a"))
	fmt.Println(strings.Compare("b", "a"))
}
Output:

-1
0
1

func Contains

func Contains(s, substr string) bool

Contains сообщает, находится ли substr в s.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Contains("seafood", "foo"))
	fmt.Println(strings.Contains("seafood", "bar"))
	fmt.Println(strings.Contains("seafood", ""))
	fmt.Println(strings.Contains("", ""))
}
Output:

true
false
true
true

func ContainsAny

func ContainsAny(s, chars string) bool

ContainsAny сообщает, находятся ли какие-либо кодовые точки Unicode в chars в s.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.ContainsAny("team", "i"))
	fmt.Println(strings.ContainsAny("fail", "ui"))
	fmt.Println(strings.ContainsAny("ure", "ui"))
	fmt.Println(strings.ContainsAny("failure", "ui"))
	fmt.Println(strings.ContainsAny("foo", ""))
	fmt.Println(strings.ContainsAny("", ""))
}
Output:

false
true
true
true
false
false

func ContainsFunc

func ContainsFunc(s string, f func(rune) bool) bool

ContainsFunc сообщает, удовлетворяют ли какие-либо кодовые точки Unicode r в s условию f(r).

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	f := func(r rune) bool {
		return r == 'a' || r == 'e' || r == 'i' || r == 'o' || r == 'u'
	}
	fmt.Println(strings.ContainsFunc("hello", f))
	fmt.Println(strings.ContainsFunc("rhythms", f))
}
Output:

true
false

func ContainsRune

func ContainsRune(s string, r rune) bool

ContainsRune сообщает, находится ли кодовая точка Unicode r в s.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	// Finds whether a string contains a particular Unicode code point.
	// The code point for the lowercase letter "a", for example, is 97.
	fmt.Println(strings.ContainsRune("aardvark", 97))
	fmt.Println(strings.ContainsRune("timeout", 97))
}
Output:

true
false

func Count

func Count(s, substr string) int

Count подсчитывает количество непересекающихся вхождений substr в s. Если substr является пустой строкой, Count возвращает 1 + количество кодовых точек Unicode в s.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Count("cheese", "e"))
	fmt.Println(strings.Count("five", "")) // before & after each rune
}
Output:

3
5

func Cut

func Cut(s, sep string) (before, after string, found bool)

Cut разрезает s вокруг первого вхождения sep, возвращая текст до и после sep. Результат found сообщает, встречается ли sep в s. Если sep не встречается в s, cut возвращает s, «», false.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	show := func(s, sep string) {
		before, after, found := strings.Cut(s, sep)
		fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
	}
	show("Gopher", "Go")
	show("Gopher", "ph")
	show("Gopher", "er")
	show("Gopher", "Badger")
}
Output:

Cut("Gopher", "Go") = "", "pher", true
Cut("Gopher", "ph") = "Go", "er", true
Cut("Gopher", "er") = "Goph", "", true
Cut("Gopher", "Badger") = "Gopher", "", false

func CutPrefix

func CutPrefix(s, prefix string) (after string, found bool)

CutPrefix возвращает s без указанной начальной префиксной строки и сообщает, был ли найден префикс. Если s не начинается с prefix, CutPrefix возвращает s, false. Если prefix является пустой строкой, CutPrefix возвращает s, true.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	show := func(s, sep string) {
		after, found := strings.CutPrefix(s, sep)
		fmt.Printf("CutPrefix(%q, %q) = %q, %v\n", s, sep, after, found)
	}
	show("Gopher", "Go")
	show("Gopher", "ph")
}
Output:

CutPrefix("Gopher", "Go") = "pher", true
CutPrefix("Gopher", "ph") = "Gopher", false

func CutSuffix

func CutSuffix(s, suffix string) (before string, found bool)

CutSuffix возвращает s без указанной конечной строки суффикса и сообщает, был ли найден суффикс. Если s не заканчивается суффиксом, CutSuffix возвращает s, false. Если суффикс является пустой строкой, CutSuffix возвращает s, true.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	show := func(s, sep string) {
		before, found := strings.CutSuffix(s, sep)
		fmt.Printf("CutSuffix(%q, %q) = %q, %v\n", s, sep, before, found)
	}
	show("Gopher", "Go")
	show("Gopher", "er")
}
Output:

CutSuffix("Gopher", "Go") = "Gopher", false
CutSuffix("Gopher", "er") = "Goph", true

func EqualFold

func EqualFold(s, t string) bool

EqualFold сообщает, равны ли s и t, интерпретируемые как строки UTF-8, при простом преобразовании регистра Unicode, которое является более общей формой нечувствительности к регистру.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.EqualFold("Go", "go"))
	fmt.Println(strings.EqualFold("AB", "ab")) // true because comparison uses simple case-folding
	fmt.Println(strings.EqualFold("ß", "ss"))  // false because comparison does not use full case-folding
}
Output:

true
true
false

func Fields

func Fields(s string) []string

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

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Printf("Fields are: %q", strings.Fields("  foo bar  baz   "))
}
Output:

Fields are: ["foo" "bar" "baz"]

func FieldsFunc

func FieldsFunc(s string, f func(rune) bool) []string

FieldsFunc разбивает строку s на каждом проходе кодовых точек Unicode c, удовлетворяющих f(c), и возвращает массив фрагментов s. Если все кодовые точки в s удовлетворяют f(c) или строка пуста, возвращается пустой фрагмент.

FieldsFunc не дает никаких гарантий относительно порядка вызова f(c) и предполагает, что f всегда возвращает одно и то же значение для данного c.

Пример
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	f := func(c rune) bool {
		return !unicode.IsLetter(c) && !unicode.IsNumber(c)
	}
	fmt.Printf("Fields are: %q", strings.FieldsFunc("  foo1;bar2,baz3...", f))
}
Output:

Fields are: ["foo1" "bar2" "baz3"]

func FieldsFuncSeq

func FieldsFuncSeq(s string, f func(rune) bool) iter.Seq[string]

FieldsFuncSeq возвращает итератор над подстроками s, разбитыми по последовательностям кодовых точек Unicode, удовлетворяющих f(c). Итератор возвращает те же строки, что и FieldsFunc(s), но без построения среза.

func FieldsSeq

func FieldsSeq(s string) iter.Seq[string]

FieldsSeq возвращает итератор над подстроками s, разбитыми по последовательностям пробельных символов, как определено в unicode.IsSpace. Итератор возвращает те же строки, что и Fields(s), но без построения среза.

func HasPrefix

func HasPrefix(s, prefix string) bool

HasPrefix сообщает, начинается ли строка s с префикса.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.HasPrefix("Gopher", "Go"))
	fmt.Println(strings.HasPrefix("Gopher", "C"))
	fmt.Println(strings.HasPrefix("Gopher", ""))
}
Output:

true
false
true

func HasSuffix

func HasSuffix(s, suffix string) bool

HasSuffix сообщает, заканчивается ли строка s суффиксом.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.HasSuffix("Amigo", "go"))
	fmt.Println(strings.HasSuffix("Amigo", "O"))
	fmt.Println(strings.HasSuffix("Amigo", "Ami"))
	fmt.Println(strings.HasSuffix("Amigo", ""))
}
Output:

true
false
false
true

func Index

func Index(s, substr string) int

Index возвращает индекс первого вхождения substr в s или -1, если substr отсутствует в s.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Index("chicken", "ken"))
	fmt.Println(strings.Index("chicken", "dmr"))
}
Output:

4
-1

func IndexAny

func IndexAny(s, chars string) int

IndexAny возвращает индекс первого вхождения любого кодового пункта Unicode из chars в s, или -1, если ни один кодовый пункт Unicode из chars не присутствует в s.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.IndexAny("chicken", "aeiouy"))
	fmt.Println(strings.IndexAny("crwth", "aeiouy"))
}
Output:

2
-1

func IndexByte

func IndexByte(s string, c byte) int

IndexByte возвращает индекс первого вхождения c в s, или -1, если c отсутствует в s.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.IndexByte("golang", 'g'))
	fmt.Println(strings.IndexByte("gophers", 'h'))
	fmt.Println(strings.IndexByte("golang", 'x'))
}
Output:

0
3
-1

func IndexFunc

func IndexFunc(s string, f func(rune) bool) int

IndexFunc возвращает индекс в s первого кодового пункта Unicode, удовлетворяющего f(c), или -1, если такового нет.

Пример
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	f := func(c rune) bool {
		return unicode.Is(unicode.Han, c)
	}
	fmt.Println(strings.IndexFunc("Hello, 世界", f))
	fmt.Println(strings.IndexFunc("Hello, world", f))
}
Output:

7
-1

func IndexRune

func IndexRune(s string, r rune) int

IndexRune возвращает индекс первого вхождения кодовой точки Unicode r, или -1, если rune отсутствует в s. Если r является utf8.RuneError, то возвращается первое вхождение любой недопустимой последовательности байтов UTF-8.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.IndexRune("chicken", 'k'))
	fmt.Println(strings.IndexRune("chicken", 'd'))
}
Output:

4
-1

func Join

func Join(elems []string, sep string) string

Join объединяет элементы своего первого аргумента, создавая одну строку. Разделитель sep помещается между элементами в результирующей строке.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	s := []string{"foo", "bar", "baz"}
	fmt.Println(strings.Join(s, ", "))
}
Output:

foo, bar, baz

func LastIndex

func LastIndex(s, substr string) int

LastIndex возвращает индекс последнего вхождения substr в s, или -1, если substr отсутствует в s.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Index("go gopher", "go"))
	fmt.Println(strings.LastIndex("go gopher", "go"))
	fmt.Println(strings.LastIndex("go gopher", "rodent"))
}
Output:

0
3
-1

func LastIndexAny

func LastIndexAny(s, chars string) int

LastIndexAny возвращает индекс последнего вхождения любого кодового пункта Unicode из chars в s, или -1, если в s нет кодовых пунктов Unicode из chars.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.LastIndexAny("go gopher", "go"))
	fmt.Println(strings.LastIndexAny("go gopher", "rodent"))
	fmt.Println(strings.LastIndexAny("go gopher", "fail"))
}
Output:

4
8
-1

func LastIndexByte

func LastIndexByte(s string, c byte) int

LastIndexByte возвращает индекс последнего вхождения c в s, или -1, если c отсутствует в s.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.LastIndexByte("Hello, world", 'l'))
	fmt.Println(strings.LastIndexByte("Hello, world", 'o'))
	fmt.Println(strings.LastIndexByte("Hello, world", 'x'))
}
Output:

10
8
-1

func LastIndexFunc

func LastIndexFunc(s string, f func(rune) bool) int

LastIndexFunc возвращает индекс в s последней кодовой точки Unicode, удовлетворяющей f(c), или -1, если таковой нет.

Пример
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	fmt.Println(strings.LastIndexFunc("go 123", unicode.IsNumber))
	fmt.Println(strings.LastIndexFunc("123 go", unicode.IsNumber))
	fmt.Println(strings.LastIndexFunc("go", unicode.IsNumber))
}
Output:

5
2
-1

func Lines

func Lines(s string) iter.Seq[string]

Lines возвращает итератор по строкам, отделенным символом новой строки, в строке s. Строки, возвращаемые итератором, включают в себя символы новой строки, отделяющие их. Если s пуста, итератор не возвращает никаких строк. Если s не заканчивается символом новой строки, последняя возвращаемая строка не будет заканчиваться символом новой строки. Возвращает итератор однократного использования.

func Map

func Map(mapping func(rune) rune, s string) string

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

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	rot13 := func(r rune) rune {
		switch {
		case r >= 'A' && r <= 'Z':
			return 'A' + (r-'A'+13)%26
		case r >= 'a' && r <= 'z':
			return 'a' + (r-'a'+13)%26
		}
		return r
	}
	fmt.Println(strings.Map(rot13, "'Twas brillig and the slithy gopher..."))
}
Output:

'Gjnf oevyyvt naq gur fyvgul tbcure...

func Repeat

func Repeat(s string, count int) string

Repeat возвращает новую строку, состоящую из count копий строки s.

Функция выдает ошибку, если count отрицательно или если результат (len(s) * count) переполняет буфер.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println("ba" + strings.Repeat("na", 2))
}
Output:

banana

func Replace

func Replace(s, old, new string, n int) string

Replace возвращает копию строки s, в которой первые n неперекрывающихся экземпляров old заменены на new. Если old пустое, оно совпадает в начале строки и после каждой последовательности UTF-8, давая до k+1 замен для строки k-рун. Если n < 0, количество замен не ограничено.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
	fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
}
Output:

oinky oinky oink
moo moo moo

func ReplaceAll

func ReplaceAll(s, old, new string) string

ReplaceAll возвращает копию строки s со всеми неперекрывающимися вхождениями old, замененными на new. Если old пустое, оно совпадает с началом строки и после каждой последовательности UTF-8, давая до k+1 замен для строки из k рун.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.ReplaceAll("oink oink oink", "oink", "moo"))
}
Output:

moo moo moo

func Split

func Split(s, sep string) []string

Split разделяет s на все подстроки, разделенные sep, и возвращает набор подстрок между этими разделителями.

Если s не содержит sep, а sep не пустой, Split возвращает набор длиной 1, единственным элементом которого является s.

Если sep пустой, Split разделяет после каждой последовательности UTF-8. Если и s, и sep пустые, Split возвращает пустой набор.

Это эквивалентно SplitN с количеством -1.

Чтобы разделить вокруг первого вхождения разделителя, см. Cut.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Printf("%q\n", strings.Split("a,b,c", ","))
	fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
	fmt.Printf("%q\n", strings.Split(" xyz ", ""))
	fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
}
Output:

["a" "b" "c"]
["" "man " "plan " "canal panama"]
[" " "x" "y" "z" " "]
[""]

func SplitAfter

func SplitAfter(s, sep string) []string

SplitAfter разделяет s на все подстроки после каждого вхождения sep и возвращает срез этих подстрок.

Если s не содержит sep, а sep не пустой, SplitAfter возвращает срез длиной 1, единственным элементом которого является s.

Если sep пусто, SplitAfter разделяет после каждой последовательности UTF-8. Если и s, и sep пусты, SplitAfter возвращает пустой срез.

Это эквивалентно SplitAfterN с количеством -1.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Printf("%q\n", strings.SplitAfter("a,b,c", ","))
}

Output:

["a," "b," "c"]

func SplitAfterN

func SplitAfterN(s, sep string, n int) []string

SplitAfterN разделяет s на подстроки после каждого вхождения sep и возвращает срез этих подстрок.

Число определяет количество подстрок, которые будут возвращены:

n > 0: не более n подстрок; последняя подстрока будет неразделенным остатком; n == 0: результат равен nil (ноль подстрок); n < 0: все подстроки. Крайние случаи для s и sep (например, пустые строки) обрабатываются, как описано в документации для SplitAfter.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Printf("%q\n", strings.SplitAfterN("a,b,c", ",", 2))
}
Output:

["a," "b,c"]

func SplitAfterSeq

func SplitAfterSeq(s, sep string) iter.Seq[string]

SplitAfterSeq возвращает итератор над подстроками s, разделенными после каждого вхождения sep. Итератор возвращает те же строки, которые были бы возвращены SplitAfter(s, sep), но без построения слайса. Он возвращает итератор однократного использования.

func SplitN

func SplitN(s, sep string, n int) []string

SplitN разделяет s на подстроки, разделенные sep, и возвращает срез подстрок между этими разделителями.

Число определяет количество подстрок, которые будут возвращены:

n > 0: не более n подстрок; последняя подстрока будет неразделенной остаточной частью; n == 0: результат равен nil (ноль подстрок); n < 0: все подстроки. Крайние случаи для s и sep (например, пустые строки) обрабатываются, как описано в документации по Split.

Чтобы разделить вокруг первого вхождения разделителя, см. Cut.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Printf("%q\n", strings.SplitN("a,b,c", ",", 2))
	z := strings.SplitN("a,b,c", ",", 0)
	fmt.Printf("%q (nil = %v)\n", z, z == nil)
}
Output:

["a" "b,c"]
[] (nil = true)

func SplitSeq

func SplitSeq(s, sep string) iter.Seq[string]

SplitSeq возвращает итератор по всем подстрокам s, разделенным sep. Итератор возвращает те же строки, что и Split(s, sep), но без создания слайса. Он возвращает итератор однократного использования.

func Title (устарело)

func ToLower

func ToLower(s string) string

ToLower возвращает s со всеми буквами Unicode, преобразованными в нижний регистр.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.ToLower("Gopher"))
}
Output:

gopher

func ToLowerSpecial

func ToLowerSpecial(c unicode.SpecialCase, s string) string

ToLowerSpecial возвращает копию строки s со всеми буквами Unicode, преобразованными в нижний регистр с использованием преобразования регистра, указанного c.

Пример
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "Örnek İş"))
}
Output:

örnek iş

func ToTitle

func ToTitle(s string) string

ToTitle возвращает копию строки s, в которой все буквы Unicode преобразованы в заглавные.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	// Compare this example to the Title example.
	fmt.Println(strings.ToTitle("her royal highness"))
	fmt.Println(strings.ToTitle("loud noises"))
	fmt.Println(strings.ToTitle("брат"))
}
HER ROYAL HIGHNESS
LOUD NOISES
БРАТ

func ToTitleSpecial

func ToTitleSpecial(c unicode.SpecialCase, s string) string

ToTitleSpecial возвращает копию строки s, в которой все буквы Unicode преобразованы в заглавные, с приоритетом специальных правил преобразования регистра.

Пример
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	fmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "dünyanın ilk borsa yapısı Aizonai kabul edilir"))
}
Output:

DÜNYANIN İLK BORSA YAPISI AİZONAİ KABUL EDİLİR

func ToUpper

func ToUpper(s string) string

ToUpper возвращает s, в которой все буквы Unicode преобразованы в заглавные.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.ToUpper("Gopher"))
}
Output:

GOPHER

func ToUpperSpecial

func ToUpperSpecial(c unicode.SpecialCase, s string) string

ToUpperSpecial возвращает копию строки s, в которой все буквы Unicode преобразованы в верхний регистр с использованием преобразования регистра, указанного в c.

Пример
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "örnek iş"))
}
Output:

ÖRNEK İŞ

func ToValidUTF8

func ToValidUTF8(s, replacement string) string

ToValidUTF8 возвращает копию строки s, в которой каждая последовательность недопустимых байтов UTF-8 заменена строкой replacement, которая может быть пустой.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Printf("%s\n", strings.ToValidUTF8("abc", "\uFFFD"))
	fmt.Printf("%s\n", strings.ToValidUTF8("a\xffb\xC0\xAFc\xff", ""))
	fmt.Printf("%s\n", strings.ToValidUTF8("\xed\xa0\x80", "abc"))
}
Output:

abc
abc
abc

func Trim

func Trim(s, cutset string) string

Trim возвращает фрагмент строки s, из которого удалены все начальные и конечные кодовые точки Unicode, содержащиеся в cutset.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Print(strings.Trim("¡¡¡Hello, Gophers!!!", "!¡"))
}
Output:

Hello, Gophers

func TrimFunc

func TrimFunc(s string, f func(rune) bool) string

TrimFunc возвращает фрагмент строки s, из которого удалены все начальные и конечные кодовые точки Unicode c, удовлетворяющие f(c).

Пример
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	fmt.Print(strings.TrimFunc("¡¡¡Hello, Gophers!!!", func(r rune) bool {
		return !unicode.IsLetter(r) && !unicode.IsNumber(r)
	}))
}
Output:

Hello, Gophers

func TrimLeft

func TrimLeft(s, cutset string) string

TrimLeft возвращает фрагмент строки s, из которого удалены все начальные кодовые точки Unicode, содержащиеся в cutset.

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

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Print(strings.TrimLeft("¡¡¡Hello, Gophers!!!", "!¡"))
}
Output:

Hello, Gophers!!!

func TrimLeftFunc

func TrimLeftFunc(s string, f func(rune) bool) string

TrimLeftFunc возвращает фрагмент строки s, из которого удалены все начальные кодовые точки Unicode c, удовлетворяющие f(c).

Пример
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	fmt.Print(strings.TrimLeftFunc("¡¡¡Hello, Gophers!!!", func(r rune) bool {
		return !unicode.IsLetter(r) && !unicode.IsNumber(r)
	}))
}
Output:

Hello, Gophers!!!

func TrimPrefix

func TrimPrefix(s, prefix string) string

TrimPrefix возвращает s без указанного начального префикса string. Если s не начинается с префикса, s возвращается без изменений.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	var s = "¡¡¡Hello, Gophers!!!"
	s = strings.TrimPrefix(s, "¡¡¡Hello, ")
	s = strings.TrimPrefix(s, "¡¡¡Howdy, ")
	fmt.Print(s)
}
Output:

Gophers!!!

func TrimRight

func TrimRight(s, cutset string) string

TrimRight возвращает фрагмент строки s, из которого удалены все конечные кодовые точки Unicode, содержащиеся в cutset.

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

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Print(strings.TrimRight("¡¡¡Hello, Gophers!!!", "!¡"))
}
Output:

¡¡¡Hello, Gophers

func TrimRightFunc

func TrimRightFunc(s string, f func(rune) bool) string

TrimRightFunc возвращает фрагмент строки s, из которого удалены все конечные кодовые точки Unicode c, удовлетворяющие f(c).

Пример
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	fmt.Print(strings.TrimRightFunc("¡¡¡Hello, Gophers!!!", func(r rune) bool {
		return !unicode.IsLetter(r) && !unicode.IsNumber(r)
	}))
}
Output:

¡¡¡Hello, Gophers

func TrimSpace

func TrimSpace(s string) string

TrimSpace возвращает фрагмент строки s, из которого удалены все ведущие и конечные пробелы, как определено в Unicode.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.TrimSpace(" \t\n Hello, Gophers \n\t\r\n"))
}
Output:

Hello, Gophers

func TrimSuffixy

func TrimSuffix(s, suffix string) string

TrimSuffix возвращает s без указанного конечного суффикса string. Если s не заканчивается суффиксом, s возвращается без изменений.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	var s = "¡¡¡Hello, Gophers!!!"
	s = strings.TrimSuffix(s, ", Gophers!!!")
	s = strings.TrimSuffix(s, ", Marmots!!!")
	fmt.Print(s)
}
Output:

¡¡¡Hello

17.2 - Описание типов пакета string языка программирования Go

Описание типов и их функций из пакета string для языка Go с примерами

type Builder


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

Builder используется для эффективного построения строки с помощью методов Builder.Write. Он минимизирует копирование памяти. Нулевое значение готово к использованию. Не копируйте Builder, отличное от нуля.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	var b strings.Builder
	for i := 3; i >= 1; i-- {
		fmt.Fprintf(&b, "%d...", i)
	}
	b.WriteString("ignition")
	fmt.Println(b.String())

}

Output:

3...2...1...ignition

func (*Builder) Cap

func (b *Builder) Cap() int

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

func (*Builder) Grow

func (b *Builder) Grow(n int)

Grow увеличивает емкость b, если это необходимо, чтобы гарантировать пространство для еще n байтов. После Grow(n) в b можно записать как минимум n байтов без дополнительного выделения памяти. Если n отрицательно, Grow вызывает панику.

func (*Builder) Len

func (b *Builder) Len() int

Len возвращает количество накопленных байтов; b.Len() == len(b.String()).

func (*Builder) Reset

func (b *Builder) Reset()

Reset сбрасывает Builder в пустое состояние.

func (*Builder) String

func (b *Builder) String() string

String возвращает накопленную строку.

func (*Builder) Write

func (b *Builder) Write(p []byte) (int, error)

Write добавляет содержимое p в буфер b. Write всегда возвращает len(p), nil.

func (*Builder) WriteByte

func (b *Builder) WriteByte(c byte) error

WriteByte добавляет байт c в буфер b. Возвращаемая ошибка всегда равна nil.

func (*Builder) WriteRune

func (b *Builder) WriteRune(r rune) (int, error)

WriteRune добавляет UTF-8-кодировку кодовой точки Unicode r в буфер b. Возвращает длину r и ошибку nil.

func (*Builder) WriteString

func (b *Builder) WriteString(s string) (int, error)

WriteString добавляет содержимое s в буфер b. Возвращает длину s и ошибку nil.

type Reader

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

Reader реализует интерфейсы io.Reader, io.ReaderAt, io.ByteReader, io.ByteScanner, io.RuneReader, io.RuneScanner, io.Seeker и io.WriterTo, читая из строки. Нулевое значение для Reader работает как Reader пустой строки.

func NewReader

func NewReader(s string) *Reader

NewReader возвращает новый Reader, читающий из s. Он похож на bytes.NewBufferString, но более эффективен и не поддается записи.

func (*Reader) Len

func (r *Reader) Len() int

Len возвращает количество байтов непрочитанной части строки.

func (*Reader) Read

func (r *Reader) Read(b []byte) (n int, err error)

Read реализует интерфейс io.Reader.

func (*Reader) ReadAt

func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)

ReadAt реализует интерфейс io.ReaderAt.

func (*Reader) ReadByte

func (r *Reader) ReadByte() (byte, error)

ReadByte реализует интерфейс io.ByteReader.

func (*Reader) ReadRune

func (r *Reader) ReadRune() (ch rune, size int, err error)

ReadRune реализует интерфейс io.RuneReader.

func (*Reader) Reset

func (r *Reader) Reset(s string)

Reset сбрасывает Reader для чтения из s.

func (*Reader) Seek

func (r *Reader) Seek(offset int64, whence int) (int64, error)

Seek реализует интерфейс io.Seeker.

func (*Reader) Size

func (r *Reader) Size() int64

Size возвращает исходную длину базовой строки. Size — это количество байтов, доступных для чтения с помощью Reader.ReadAt. Возвращаемое значение всегда одинаково и не зависит от вызовов других методов.

func (*Reader) UnreadByte

func (r *Reader) UnreadByte() error

UnreadByte реализует интерфейс io.ByteScanner.

func (*Reader) UnreadRune

func (r *Reader) UnreadRune() error

UnreadRune реализует интерфейс io.RuneScanner.

func (*Reader) WriteTo

func (r *Reader) WriteTo(w io.Writer) (n int64, err error)

WriteTo реализует интерфейс io.WriterTo.

type Replacer

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

Replacer заменяет список строк на замены. Он безопасен для одновременного использования несколькими goroutines.

func NewReplacer

func NewReplacer(oldnew ...string) *Replacer

NewReplacer возвращает новый Replacer из списка пар старых и новых строк. Замены выполняются в том порядке, в котором они появляются в целевой строке, без перекрывающихся совпадений. Сравнение старых строк выполняется в порядке аргументов.

NewReplacer вызывает панику, если ему передано нечетное количество аргументов.

Пример
package main

import (
	"fmt"
	"strings"
)

func main() {
	r := strings.NewReplacer("<", "<", ">", ">")
	fmt.Println(r.Replace("This is <b>HTML</b>!"))
}
Output:

This is <b>HTML</b>!

func (*Replacer) Replace

func (r *Replacer) Replace(s string) string

Replace возвращает копию s со всеми выполненными заменами.

func (*Replacer) WriteString

func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)

WriteString записывает s в w со всеми выполненными заменами.

18 - Полное описание пакета log в Go

Полное описание пакета log в Go с примерами применения и разными потоками вывода

Пакет log в Go предоставляет простые и эффективные инструменты для логирования. Он поддерживает:

  • Вывод логов в стандартный вывод (stdout/stderr).
  • Настройку формата вывода (префиксы, дата/время, уровень логирования).
  • Запись логов в файлы.
  • Гибкую настройку через log.Logger.

1. Основные функции пакета log

1.1. Простое логирование

Пакет log предоставляет стандартные функции:

log.Print("Обычное сообщение")       // Вывод без формата
log.Printf("Формат: %d", 123)       // Форматированный вывод
log.Println("Сообщение с новой строкой") // Вывод с \n

1.2. Логирование с уровнем FATAL (завершение программы)

log.Fatal("Сообщение и выход с os.Exit(1)")    // Вывод + exit(1)
log.Fatalf("Формат: %s", "ошибка")            // Форматированный вывод + exit(1)
log.Fatalln("Фатальная ошибка")                // Вывод + exit(1)

1.3. Логирование с уровнем PANIC (вызов паники)

log.Panic("Паника с выводом стека")    // Вывод + panic()
log.Panicf("Ошибка: %v", err)         // Форматированный вывод + panic()
log.Panicln("Критическая ошибка")     // Вывод + panic()

2. Настройка формата вывода

2.1. Добавление префикса (дата, время, уровень)

log.SetPrefix("ERROR: ") // Установка префикса
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) // Формат
log.Println("Сообщение с префиксом")

Вывод:

ERROR: 2024/03/15 14:20:10 main.go:10: Сообщение с префиксом

2.2. Доступные флаги для log.SetFlags

Флаг Описание
log.Ldate Дата (2006/01/02)
log.Ltime Время (15:04:05)
log.Lmicroseconds Микросекунды (15:04:05.000000)
log.Llongfile Полный путь к файлу (/a/b/c.go:10)
log.Lshortfile Короткое имя файла (c.go:10)
log.LUTC Вывод времени в UTC
log.Lmsgprefix Префикс перед сообщением (Go 1.14+)

Пример комбинации флагов:

log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile | log.Lmsgprefix)

3. Запись логов в файл

file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
    log.Fatal("Ошибка открытия файла:", err)
}
defer file.Close()

log.SetOutput(file) // Перенаправляем вывод в файл
log.Println("Сообщение записано в app.log")

4. Создание нескольких логгеров

Можно создавать отдельные логгеры для разных целей:

logger := log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime)
errorLogger := log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)

logger.Println("Информационное сообщение")
errorLogger.Println("Ошибка в программе")

Вывод:

INFO: 2024/03/15 14:20:10 Информационное сообщение
ERROR: 2024/03/15 14:20:10 main.go:15: Ошибка в программе

5. Продвинутое использование (JSON-логирование, уровни)

Стандартный log не поддерживает уровни (DEBUG, INFO, WARN, ERROR), но их можно эмулировать:

5.1. Логирование в JSON

type LogEntry struct {
    Level   string `json:"level"`
    Message string `json:"message"`
    Time    string `json:"time"`
}

func JSONLog(level, message string) {
    entry := LogEntry{
        Level:   level,
        Message: message,
        Time:    time.Now().Format(time.RFC3339),
    }
    data, _ := json.Marshal(entry)
    log.Println(string(data))
}

JSONLog("INFO", "Запуск сервера")

Вывод:

{"level":"INFO","message":"Запуск сервера","time":"2024-03-15T14:20:10Z"}

5.2. Уровни логирования

const (
    LevelDebug = "DEBUG"
    LevelInfo  = "INFO"
    LevelWarn  = "WARN"
    LevelError = "ERROR"
)

func Log(level, message string) {
    log.Printf("[%s] %s", level, message)
}

Log(LevelError, "Сервер не отвечает")

Вывод:

[ERROR] Сервер не отвечает

6. Когда использовать log, а когда другие пакеты?

  • log — для простых задач (CLI-утилиты, маленькие сервисы).
  • slog (Go 1.21+) — структурированное логирование с уровнями.
  • zap/zerolog — для высоконагруженных приложений (минимальные аллокации).

Вывод

  • Пакет log прост, но гибок (префиксы, файлы, форматирование).
  • Подходит для базового логирования.
  • Для сложных сценариев лучше использовать slog, zap или zerolog.
  • Можно комбинировать с io.MultiWriter для вывода в несколько мест.

Пример MultiWriter:

file, _ := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
multiWriter := io.MultiWriter(os.Stdout, file)
log.SetOutput(multiWriter)
log.Println("Сообщение в консоль и файл")

19 - Описание пакета unsafe языка программирования Go

Пакет unsafe содержит операции, которые обходят типовую безопасность программ Go.

Пакеты, которые импортируют unsafe, могут быть непереносимыми и не защищены правилами совместимости Go 1.

Функции

func Alignof

func Alignof(x ArbitraryType) uintptr

Alignof принимает выражение x любого типа и возвращает требуемое выравнивание гипотетической переменной v, как если бы v была объявлена с помощью var v = x. Это наибольшее значение m, такое что адрес v всегда равен нулю по модулю m. Оно совпадает со значением, возвращаемым reflect.TypeOf(x).Align(). В качестве особого случая, если переменная s имеет тип struct, а f является полем в этой структуре, то Alignof(s.f) вернет требуемое выравнивание поля этого типа в структуре. Этот случай аналогичен значению, возвращаемому reflect.TypeOf(s.f).FieldAlign(). Возвращаемое значение Alignof является константой Go, если тип аргумента не имеет переменного размера. (Определение типов переменного размера см. в описании Sizeof.)

func Offsetof

func Offsetof(x ArbitraryType) uintptr

Offsetof возвращает смещение в структуре поля, представленного x, которое должно иметь форму structValue.field. Другими словами, она возвращает количество байтов между началом структуры и началом поля. Возвращаемое значение Offsetof является константой Go, если тип аргумента x не имеет переменного размера. (Определение типов переменного размера см. в описании Sizeof.)

func Sizeof

func Sizeof(x ArbitraryType) uintptr

Sizeof принимает выражение x любого типа и возвращает размер в байтах гипотетической переменной v, как если бы v была объявлена с помощью var v = x. Размер не включает в себя память, на которую может ссылаться x. Например, если x является срезом, Sizeof возвращает размер дескриптора среза, а не размер памяти, на которую ссылается срез; если x является интерфейсом, Sizeof возвращает размер самого значения интерфейса, а не размер значения, хранящегося в интерфейсе. Для структуры размер включает в себя любую заполняющую пробел, введенную выравниванием полей. Возвращаемое значение Sizeof является константой Go, если тип аргумента x не имеет переменного размера. (Тип имеет переменный размер, если он является параметром типа или если он является типом массива или структуры с элементами переменного размера).

func String

func String(ptr *byte, len IntegerType) string

String возвращает строковое значение, байты которого начинаются с ptr и длина которого равна len.

Аргумент len должен быть целочисленного типа или нетипизированной константой. Константа len должна быть неотрицательной и представляться значением типа int; если это константа без типа, ей присваивается тип int. Во время выполнения, если len отрицательна или если ptr равна nil, а len не равна нулю, возникает паника во время выполнения.

Поскольку строки Go являются неизменяемыми, байты, переданные в String, не должны изменяться, пока существует возвращаемое строковое значение. 6

func StringData

func StringData(str string) *byte

StringData возвращает указатель на базовые байты str. Для пустой строки возвращаемое значение не определено и может быть nil.

Поскольку строки Go являются неизменяемыми, байты, возвращаемые StringData, не должны изменяться.

type ArbitraryType

type ArbitraryType int

ArbitraryType используется здесь только в целях документирования и на самом деле не входит в состав пакета unsafe. Он представляет тип произвольного выражения Go.

func Slice

func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

Функция Slice возвращает срез, базовый массив которого начинается с ptr, а длина и емкость — len. Slice(ptr, len) эквивалентно

(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:] за исключением того, что в особом случае, если ptr равно nil, а len равно нулю, Slice возвращает nil.

Аргумент len должен быть целочисленного типа или нетипизированной константой. Константа len должна быть неотрицательной и представляться значением типа int; если это нетипизированная константа, ей присваивается тип int. Во время выполнения, если len отрицательно, или если ptr равно nil, а len не равно нулю, возникает паника во время выполнения.

func SliceData

func SliceData(slice []ArbitraryType) *ArbitraryType

SliceData возвращает указатель на базовый массив аргумента slice.

Если cap(slice) > 0, SliceData возвращает &slice[:1][0]. Если slice == nil, SliceData возвращает nil. В противном случае SliceData возвращает не нулевой указатель на неуказанный адрес памяти.

type IntegerType

type IntegerType int

IntegerType используется здесь только для целей документирования и на самом деле не является частью пакета unsafe. Он представляет любой произвольный целочисленный тип.

type Pointer

type Pointer *ArbitraryType

Pointer представляет указатель на произвольный тип. Для типа Pointer доступны четыре специальные операции, которые недоступны для других типов:

  • Значение указателя любого типа может быть преобразовано в Pointer.
  • Pointer может быть преобразован в значение указателя любого типа.
  • uintptr может быть преобразован в Pointer.
  • Pointer может быть преобразован в uintptr.

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

Следующие шаблоны, связанные с Pointer, являются действительными. Код, не использующий эти шаблоны, вероятно, является недействительным сегодня или станет недействительным в будущем. Даже действительные шаблоны, приведенные ниже, сопровождаются важными предостережениями.

Запуск «go vet» может помочь найти использования Pointer, которые не соответствуют этим шаблонам, но отсутствие сообщений от «go vet» не является гарантией того, что код является действительным.

1. Преобразование *T1 в Pointer в *T2.

При условии, что T2 не больше T1 и что оба имеют одинаковую структуру памяти, это преобразование позволяет переосмыслить данные одного типа как данные другого типа. Примером является реализация math.Float64bits:

func Float64bits(f float64) uint64
    return *(*uint64)(unsafe.Pointer(&f))
}

2. Преобразование указателя в uintptr (но не обратно в указатель).

Преобразование указателя в uintptr дает адрес памяти указанного значения в виде целого числа. Обычно uintptr используется для вывода на печать.

Преобразование uintptr обратно в указатель в целом не допускается.

uintptr — это целое число, а не ссылка. Преобразование указателя в uintptr создает целое значение без семантики указателя. Даже если uintptr содержит адрес какого-либо объекта, сборщик мусора не обновит значение uintptr, если объект переместится, и uintptr не помешает объекту быть восстановленным.

Остальные шаблоны перечисляют единственные допустимые преобразования из uintptr в Pointer.

3. Преобразование Pointer в uintptr и обратно с помощью арифметики.

Если p указывает на выделенный объект, его можно продвинуть по объекту путем преобразования в uintptr, добавления смещения и преобразования обратно в Pointer.

p = unsafe.Pointer(uintptr(p) + offset)

Наиболее распространенное использование этого шаблона — доступ к полям в структуре или элементам массива:

// эквивалентно f := unsafe.Pointer(&s.f)
f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))

// эквивалентно e := unsafe.Pointer(&x[i])
e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))

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

В отличие от C, не допускается перемещение указателя за пределы его исходного выделения:

// НЕПРАВИЛЬНО: конечная точка находится за пределами выделенного пространства.
var s thing
end = unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))

// НЕПРАВИЛЬНО: конец указывает за пределы выделенного пространства.
b := make([]byte, n)
end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))

Обратите внимание, что оба преобразования должны появляться в одном выражении, с арифметическими операциями между ними:

// НЕПРАВИЛЬНО: uintptr не может быть сохранен в переменной
// до преобразования обратно в Pointer.
u := uintptr(p)
p = unsafe.Pointer(u + offset)

Обратите внимание, что указатель должен указывать на выделенный объект, поэтому он не может быть nil.

// НЕПРАВИЛЬНО: преобразование указателя nil
u := unsafe.Pointer(nil)
p := unsafe.Pointer(uintptr(u) + offset)

4. Преобразование указателя в uintptr при вызове функций типа syscall.Syscall.

Функции Syscall в пакете syscall передают свои аргументы uintptr непосредственно операционной системе, которая затем, в зависимости от деталей вызова, может переинтерпретировать некоторые из них как указатели. То есть реализация системного вызова неявно преобразует определенные аргументы обратно из uintptr в указатель.

Если аргумент-указатель должен быть преобразован в uintptr для использования в качестве аргумента, это преобразование должно появиться в самом выражении вызова:

syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))

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

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

// НЕПРАВИЛЬНО: uintptr не может быть сохранен в переменной
// до неявного преобразования обратно в Pointer во время системного вызова.
u := uintptr(unsafe.Pointer(p))
syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))

5. Преобразование результата reflect.Value.Pointer или reflect.Value.UnsafeAddr из uintptr в Pointer.

Методы Value пакета reflect с именами Pointer и UnsafeAddr возвращают тип uintptr вместо unsafe.Pointer, чтобы вызывающие функции не могли изменить результат на произвольный тип без предварительного импорта «unsafe». Однако это означает, что результат является неустойчивым и должен быть преобразован в Pointer сразу после вызова, в том же выражении:

p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))

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

// НЕПРАВИЛЬНО: uintptr не может быть сохранен в переменной
// до преобразования обратно в Pointer.
u := reflect.ValueOf(new(int)).Pointer()
p := (*int)(unsafe.Pointer(u))

6. Преобразование поля данных reflect.SliceHeader или reflect.StringHeader в Pointer или из Pointer.

Как и в предыдущем случае, структуры данных reflect SliceHeader и StringHeader объявляют поле Data как uintptr, чтобы вызывающие функции не могли изменить результат на произвольный тип без предварительного импорта «unsafe». Однако это означает, что SliceHeader и StringHeader действительны только при интерпретации содержимого фактического значения slice или string.

var s string
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // случай 1
hdr.Data = uintptr(unsafe.Pointer(p))              // случай 6 (этот случай)
hdr.Len = n

В этом случае hdr.Data на самом деле является альтернативным способом ссылки на базовый указатель в заголовке строки, а не самой переменной uintptr.

В общем случае reflect.SliceHeader и reflect.StringHeader следует использовать только как *reflect.SliceHeader и *reflect.StringHeader, указывающие на фактические срезы или строки, но никогда как простые структуры. Программа не должна объявлять или выделять переменные этих типов структур.

// НЕПРАВИЛЬНО: непосредственно объявленный заголовок не будет содержать Data в качестве ссылки.
var hdr reflect.StringHeader
hdr.Data = uintptr(unsafe.Pointer(p))
hdr.Len = n
s := *(*string)(unsafe.Pointer(&hdr)) // p, возможно, уже утрачен

func Add

func Add(ptr Pointer, len IntegerType) Pointer

Функция Add добавляет len к ptr и возвращает обновленный указатель Pointer(uintptr(ptr) + uintptr(len)). Аргумент len должен быть целочисленного типа или нетипизированной константой. Константный аргумент len должен быть представлен значением типа int; если он является нетипизированной константой, ему присваивается тип int. Правила допустимого использования Pointer по-прежнему применяются.

20 - Пакет time языка программирования Go

Пакет time предоставляет функции для измерения и отображения времени.

Календарные вычисления всегда основаны на григорианском календаре без учета високосных секунд.

Монотонные часы

Операционные системы предоставляют как “системные часы” (которые могут корректироваться для синхронизации времени), так и “монотонные часы” (которые не подвержены таким изменениям). Общее правило гласит: системные часы используются для определения текущего времени, а монотонные часы - для измерения временных интервалов. Вместо разделения API, в данном пакете Time, возвращаемый функцией time.Now, содержит показания как системных, так и монотонных часов. Последующие операции определения времени используют показания системных часов, тогда как операции измерения времени (в частности, сравнения и вычитания) используют показания монотонных часов.

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

start := time.Now()
... operation that takes 20 milliseconds ...
t := time.Now()
elapsed := t.Sub(start)

Другие идиомы, такие как time.Since(start), time.Until(deadline) и time.Now().Before(deadline), также устойчивы к сбросам системных часов.

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

Особенности работы с монотонными часами

  1. Функция time.Now()
    Возвращаемое значение Time содержит показания монотонных часов. Если время t включает монотонные часы:

    • t.Add добавляет длительность как к системным, так и к монотонным часам
    • t.AddDate, t.Round и t.Truncate работают только с системным временем и удаляют монотонные показания
    • t.In, t.Local и t.UTC также удаляют монотонные показания
    • Стандартный способ удалить монотонные показания: t = t.Round(0)
  2. Сравнение времен
    Если оба значения t и u содержат монотонные показания:

    • t.After(u), t.Before(u), t.Equal(u), t.Compare(u) и t.Sub(u) используют только монотонные часы
    • Если одно из значений не имеет монотонных показаний, используются системные часы
  3. Особые случаи

    • На некоторых системах монотонные часы останавливаются при переходе в спящий режим
    • В таких случаях t.Sub(u) и аналогичные операции могут давать неточные результаты
    • Иногда требуется удалить монотонные показания для получения точных данных
  4. Сериализация и форматирование
    Монотонные показания:

    • Не включаются при сериализации (GobEncode, MarshalJSON и др.)
    • Не имеют формата вывода в t.Format
    • Не создаются конструкторами (Date, Parse, Unix и др.)
  5. Особенности реализации

    • Монотонные показания существуют только в значениях Time
    • Не входят в Duration или Unix-время (t.Unix)
    • Оператор == сравнивает Location и монотонные показания
  6. Отладка

    • t.String() показывает монотонные показания при их наличии
    • Различия в монотонных показаниях видны при выводе

Разрешение таймеров ¶

Разрешение таймеров зависит от:

  • Версии Go
  • Операционной системы
  • Аппаратного обеспечения

Типичные значения:

  • Unix: ~1 мс
  • Windows 1803+: ~0.5 мс
  • Старые версии Windows: ~16 мс (можно улучшить через windows.TimeBeginPeriod)

Константы

const (
	Layout = «01/02 03:04:05PM '06 -0700» // Время отсчета, в числовом порядке.
	ANSIC = «Mon Jan _2 15:04:05 2006»
	UnixDate = «Mon Jan _2 15:04:05 MST 2006»
	RubyDate = «Mon Jan 02 15:04:05 -0700 2006»
	RFC822 = «02 Jan 06 15:04 MST»
	RFC822Z = «02 Jan 06 15:04 -0700» // RFC822 с числовой зоной
	RFC850 = «Monday, 02-Jan-06 15:04:05 MST»
	RFC1123 = «Mon, 02 Jan 2006 15:04:05 MST»
	RFC1123Z = «Mon, 02 Jan 2006 15:04:05 -0700» // RFC1123 с числовой зоной
	RFC3339 = «2006-01-02T15:04:05Z07:00»
	RFC3339Nano = «2006-01-02T15:04:05.9999999Z07:00»
	Kitchen = «3:04PM»
	// Удобные временные метки.
	Stamp = «Jan _2 15:04:05»
	StampMilli = «Jan _2 15:04:05.000»
	StampMicro = «Jan _2 15:04:05.000000»
	StampNano = «Jan _2 15:04:05.000000000»
	DateTime = «2006-01-02 15:04:05»
	DateOnly = «2006-01-02»
	TimeOnly = «15:04:05»
)

Это предопределенные макеты для использования в Time.Format и time.Parse. В качестве опорного времени в этих макетах используется конкретная временная метка:

01/02 03:04:05PM '06 -0700

(2 января, 15:04:05, 2006, в часовом поясе на семь часов западнее GMT). Это значение записывается как константа с именем Layout, приведенным ниже. В качестве времени Unix это 1136239445. Поскольку MST - это GMT-0700, команда Unix date выведет ссылку в виде:

Mon Jan 2 15:04:05 MST 2006.

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

Пример Time.Format подробно демонстрирует работу со строкой компоновки и является хорошей ссылкой.

Обратите внимание, что форматы RFC822, RFC850 и RFC1123 следует применять только к местному времени. При их использовании для времени UTC в качестве сокращения часового пояса будет использоваться «UTC», в то время как, строго говоря, эти RFC требуют использовать «GMT» в этом случае. При использовании форматов RFC1123 или RFC1123Z для разбора, обратите внимание, что эти форматы определяют ведущий ноль для части «день-месяц», что строго не разрешено RFC 1123. Это приведет к ошибке при разборе строк дат, которые приходятся на первые 9 дней данного месяца. В общем случае RFC1123Z следует использовать вместо RFC1123 для серверов, которые настаивают на этом формате, а RFC3339 следует предпочесть для новых протоколов. RFC3339, RFC822, RFC822Z, RFC1123 и RFC1123Z полезны для форматирования; при использовании с time.Parse они принимают не все форматы времени, разрешенные RFC, и принимают форматы времени, не определенные формально. Формат RFC3339Nano удаляет нули в конце поля секунд, поэтому после форматирования он может сортироваться некорректно.

Большинство программ могут использовать одну из определенных констант в качестве макета, передаваемого в Format или Parse. Остальную часть этого комментария можно игнорировать, если только вы не создаете собственную строку макета.

Чтобы определить свой собственный формат, запишите, как будет выглядеть эталонное время, отформатированное вашим способом; в качестве примера можно посмотреть значения таких констант, как ANSIC, StampMicro или Kitchen. Модель призвана продемонстрировать, как выглядит эталонное время, чтобы методы Format и Parse могли применить то же преобразование к общему значению времени.

Ниже приводится краткое описание компонентов строки макета. Каждый элемент показывает на примере форматирование элемента эталонного времени. Распознаются только эти значения. Текст в компоновочной строке, который не распознается как часть эталонного времени, дословно повторяется во время Format и, как ожидается, дословно отображается на входе в Parse.

Year: "2006" "06"
Month: "Jan" "January" "01" "1"
Day of the week: "Mon" "Monday"
Day of the month: "2" "_2" "02"
Day of the year: "__2" "002"
Hour: "15" "3" "03" (PM or AM)
Minute: "4" "04"
Second: "5" "05"
AM/PM mark: "PM"

Числовые смещения часовых поясов форматируются следующим образом:

"-0700"     ±hhmm
"-07:00"    ±hh:mm
"-07"       ±hh
"-070000"   ±hhmmss
"-07:00:00" ±hh:mm:ss

Если в формате заменить знак на Z, это включит режим ISO 8601, где для UTC отображается Z вместо указания смещения. Например:

"Z0700"      Z or ±hhmm
"Z07:00"     Z or ±hh:mm
"Z07"        Z or ±hh
"Z070000"    Z or ±hhmmss
"Z07:00:00"  Z or ±hh:mm:ss

В строке формата подчёркивания в "_2" и "__2" обозначают пробелы, которые могут заменяться цифрами, если следующее число состоит из нескольких цифр, для совместимости с фиксированными форматами времени Unix. Ноль в начале означает дополнение нулями.

Форматы __2 и 002 обозначают день года, дополненный пробелами или нулями до трёх символов; формата без дополнения для дня года не существует.

Запятая или точка, за которыми следуют один или несколько нулей, обозначают долю секунды, выводимую с указанным количеством знаков после запятой. Запятая или точка, за которыми следуют одна или несколько девяток, обозначают долю секунды, выводимую с указанным количеством знаков после запятой, но с удалением завершающих нулей. Например, форматы "15:04:05,000" или "15:04:05.000" форматируют или парсят время с миллисекундной точностью.

Некоторые допустимые форматы являются недопустимыми значениями времени для time.Parse, из-за элементов вроде _ (пробельного дополнения) или Z (информации о временной зоне).

const (
	Nanosecond  Duration = 1
	Microsecond          = 1000 * Nanosecond
	Millisecond          = 1000 * Microsecond
	Second               = 1000 * Millisecond
	Minute               = 60 * Second
	Hour                 = 60 * Minute
)

Стандартные длительности (Common durations)

Не существует определения для единиц измерения “День” или больше, чтобы избежать путаницы из-за перехода на летнее/зимнее время в разных часовых поясах.

Чтобы подсчитать количество единиц в Duration (длительности), выполните деление:

second := time.Second
fmt.Print(int64(second/time.Millisecond)) // prints 1000

Чтобы преобразовать целое количество единиц в Duration (длительность), выполните умножение:

seconds := 10
fmt.Print(time.Duration(seconds)*time.Second) // prints 10s

func After

func After(d Duration) <-chan Time

After ожидает истечения указанной длительности d, после чего отправляет текущее время в возвращаемом канале. Эквивалентно NewTimer(d).C.

Изменения в Go 1.23:
В ранних версиях документация предупреждала, что таймер не освобождался сборщиком мусора до срабатывания, и рекомендовала использовать NewTimer с ручным вызовом Timer.Stop для оптимизации. Начиная с Go 1.23, сборщик мусора может освобождать неиспользуемые таймеры, даже если они не остановлены. Нет необходимости использовать NewTimer вместо After.

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

package main

import (
	"fmt"
	"time"
)

var c chan int

func handle(int) {}

func main() {
	select {
	case m := <-c:
		handle(m)
	case <-time.After(10 * time.Second):
		fmt.Println("timed out")
	}
}

func Sleep

func Sleep(d Duration)

Sleep приостанавливает выполнение текущей горутины как минимум на указанную длительность d. Отрицательное или нулевое значение приводит к немедленному возврату.

Пример:

package main

import (
    "time"
)

func main() {
    time.Sleep(100 * time.Millisecond) // Пауза на 100 мс
}

func Tick

func Tick(d Duration) <-chan Time

Tick — это удобная обёртка над NewTicker, предоставляющая только канал с тиками. В отличие от NewTicker, возвращает nil при d <= 0.

Изменения в Go 1.23:
Ранее документация указывала, что сборщик мусора не освобождает неостановленные тикеры, и рекомендовала использовать NewTicker с Ticker.Stop. В Go 1.23 сборщик может освобождать неиспользуемые тикеры, даже без вызова Stop. Нет причин предпочитать NewTicker, если достаточно Tick.

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

package main

import (
    "fmt"
    "time"
)

func statusUpdate() string { return "" }

func main() {
    c := time.Tick(5 * time.Second) // Тикер каждые 5 секунд
    for next := range c {
        fmt.Printf("%v %s\n", next, statusUpdate())
    }
}

Вот перевод технической документации пакета time на русский язык:

Типы

type Duration

type Duration int64

Тип Duration представляет промежуток времени между двумя моментами в виде количества наносекунд int64. Представление ограничивает максимальную продолжительность примерно 290 годами.

Пример
package main

import (
	"fmt"
	"time"
)

func expensiveCall() {}

func main() {
	t0 := time.Now()
	expensiveCall()
	t1 := time.Now()
	fmt.Printf("The call took %v to run.\n", t1.Sub(t0))
}

func ParseDuration

func ParseDuration(s string) (Duration, error)

ParseDuration разбирает строку продолжительности. Строка продолжительности - это возможно знаковая последовательность десятичных чисел, каждое с необязательной дробной частью и суффиксом единицы, например “300ms”, “-1.5h” или “2h45m”. Допустимые единицы времени: “ns”, “us” (или “µs”), “ms”, “s”, “m”, “h”.

Пример
import (
	"fmt"
	"time"
)

func main() {
	hours, _ := time.ParseDuration("10h")
	complex, _ := time.ParseDuration("1h10m10s")
	micro, _ := time.ParseDuration("1µs")
	// The package also accepts the incorrect but common prefix u for micro.
	micro2, _ := time.ParseDuration("1us")

	fmt.Println(hours)
	fmt.Println(complex)
	fmt.Printf("There are %.0f seconds in %v.\n", complex.Seconds(), complex)
	fmt.Printf("There are %d nanoseconds in %v.\n", micro.Nanoseconds(), micro)
	fmt.Printf("There are %6.2e seconds in %v.\n", micro2.Seconds(), micro2)
}
Output:

10h0m0s
1h10m10s
There are 4210 seconds in 1h10m10s.
There are 1000 nanoseconds in 1µs.
There are 1.00e-06 seconds in 1µs.

func Since

func Since(t Time) Duration

Since возвращает время, прошедшее с момента t. Это сокращение для time.Now().Sub(t).

Пример
package main

import (
	"fmt"
	"time"
)

func expensiveCall() {}

func main() {
	start := time.Now()
	expensiveCall()
	elapsed := time.Since(start)
	fmt.Printf("The call took %v to run.\n", elapsed)
}

func Until

func Until(t Time) Duration

Until возвращает продолжительность до момента t. Это сокращение для t.Sub(time.Now()).

Пример
package main

import (
	"fmt"
	"math"
	"time"
)

func main() {
	futureTime := time.Now().Add(5 * time.Second)
	durationUntil := time.Until(futureTime)
	fmt.Printf("Duration until future time: %.0f seconds", math.Ceil(durationUntil.Seconds()))
}
Output:

Duration until future time: 5 seconds

func (Duration) Abs

func (d Duration) Abs() Duration

Abs возвращает абсолютное значение d. В особом случае Duration(math.MinInt64) преобразуется в Duration(math.MaxInt64), уменьшая его величину на 1 наносекунду.

Пример
package main

import (
	"fmt"
	"math"
	"time"
)

func main() {
	positiveDuration := 5 * time.Second
	negativeDuration := -3 * time.Second
	minInt64CaseDuration := time.Duration(math.MinInt64)

	absPositive := positiveDuration.Abs()
	absNegative := negativeDuration.Abs()
	absSpecial := minInt64CaseDuration.Abs() == time.Duration(math.MaxInt64)

	fmt.Printf("Absolute value of positive duration: %v\n", absPositive)
	fmt.Printf("Absolute value of negative duration: %v\n", absNegative)
	fmt.Printf("Absolute value of MinInt64 equal to MaxInt64: %t\n", absSpecial)

}
Output:

Absolute value of positive duration: 5s
Absolute value of negative duration: 3s
Absolute value of MinInt64 equal to MaxInt64: true

func (Duration) Hours

func (d Duration) Hours() float64

Hours возвращает продолжительность как число часов с плавающей точкой.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	h, _ := time.ParseDuration("4h30m")
	fmt.Printf("I've got %.1f hours of work left.", h.Hours())
}
Output:

I've got 4.5 hours of work left.

func (Duration) Microseconds

func (d Duration) Microseconds() int64

Microseconds возвращает продолжительность как целое количество микросекунд.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	u, _ := time.ParseDuration("1s")
	fmt.Printf("One second is %d microseconds.\n", u.Microseconds())
}
Output:

One second is 1000000 microseconds.

func (Duration) Milliseconds

func (d Duration) Milliseconds() int64

Milliseconds возвращает продолжительность как целое количество миллисекунд.

func (Duration) Minutes

func (d Duration) Minutes() float64

Minutes возвращает продолжительность как число минут с плавающей точкой.

func (Duration) Nanoseconds

func (d Duration) Nanoseconds() int64

Nanoseconds возвращает продолжительность как целое количество наносекунд.

func (Duration) Round

func (d Duration) Round(m Duration) Duration

Round возвращает результат округления d до ближайшего кратного m. При округлении половинных значений округление выполняется от нуля. Если результат превышает максимальное (или минимальное) значение, которое может храниться в Duration, возвращается максимальная (или минимальная) продолжительность. Если m <= 0, Round возвращает d без изменений.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	d, err := time.ParseDuration("1h15m30.918273645s")
	if err != nil {
		panic(err)
	}

	round := []time.Duration{
		time.Nanosecond,
		time.Microsecond,
		time.Millisecond,
		time.Second,
		2 * time.Second,
		time.Minute,
		10 * time.Minute,
		time.Hour,
	}

	for _, r := range round {
		fmt.Printf("d.Round(%6s) = %s\n", r, d.Round(r).String())
	}
}
d.Round(   1ns) = 1h15m30.918273645s
d.Round(   1µs) = 1h15m30.918274s
d.Round(   1ms) = 1h15m30.918s
d.Round(    1s) = 1h15m31s
d.Round(    2s) = 1h15m30s
d.Round(  1m0s) = 1h16m0s
d.Round( 10m0s) = 1h20m0s
d.Round(1h0m0s) = 1h0m0s

func (Duration) Seconds

func (d Duration) Seconds() float64

Seconds возвращает продолжительность как число секунд с плавающей точкой.

func (Duration) String

func (d Duration) String() string

String возвращает строковое представление продолжительности в виде “72h3m0.5s”. Ведущие нулевые единицы опускаются. В особом случае продолжительности менее одной секунды используют меньшие единицы (милли-, микро- или наносекунды), чтобы ведущая цифра была не нулевой. Нулевая продолжительность форматируется как “0s”.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println(1*time.Hour + 2*time.Minute + 300*time.Millisecond)
	fmt.Println(300 * time.Millisecond)
}
Output:

1h2m0.3s
300ms

func (Duration) Truncate

func (d Duration) Truncate(m Duration) Duration

Truncate возвращает результат округления d к нулю до кратного m. Если m <= 0, Truncate возвращает d без изменений.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	d, err := time.ParseDuration("1h15m30.918273645s")
	if err != nil {
		panic(err)
	}

	trunc := []time.Duration{
		time.Nanosecond,
		time.Microsecond,
		time.Millisecond,
		time.Second,
		2 * time.Second,
		time.Minute,
		10 * time.Minute,
		time.Hour,
	}

	for _, t := range trunc {
		fmt.Printf("d.Truncate(%6s) = %s\n", t, d.Truncate(t).String())
	}
}
Output:

d.Truncate(   1ns) = 1h15m30.918273645s
d.Truncate(   1µs) = 1h15m30.918273s
d.Truncate(   1ms) = 1h15m30.918s
d.Truncate(    1s) = 1h15m30s
d.Truncate(    2s) = 1h15m30s
d.Truncate(  1m0s) = 1h15m0s
d.Truncate( 10m0s) = 1h10m0s
d.Truncate(1h0m0s) = 1h0m0s

type Location

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

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

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

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	// China doesn't have daylight saving. It uses a fixed 8 hour offset from UTC.
	secondsEastOfUTC := int((8 * time.Hour).Seconds())
	beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)

	// If the system has a timezone database present, it's possible to load a location
	// from that, e.g.:
	//    newYork, err := time.LoadLocation("America/New_York")

	// Creating a time requires a location. Common locations are time.Local and time.UTC.
	timeInUTC := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
	sameTimeInBeijing := time.Date(2009, 1, 1, 20, 0, 0, 0, beijing)

	// Although the UTC clock time is 1200 and the Beijing clock time is 2000, Beijing is
	// 8 hours ahead so the two dates actually represent the same instant.
	timesAreEqual := timeInUTC.Equal(sameTimeInBeijing)
	fmt.Println(timesAreEqual)

}
Output:

true
var Local *Location = &localLoc

Local представляет локальный часовой пояс системы. В Unix-системах Local обращается к переменной окружения TZ для определения используемого часового пояса. Отсутствие TZ означает использование системного значения по умолчанию /etc/localtime. TZ="" означает использование UTC. TZ=“foo” означает использование файла foo в системном каталоге часовых поясов.

var UTC *Location = &utcLoc

UTC представляет Универсальное Координированное Время (UTC).

func FixedZone

func FixedZone(name string, offset int) *Location

FixedZone возвращает Location, который всегда использует заданное имя пояса и смещение (в секундах к востоку от UTC).

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	loc := time.FixedZone("UTC-8", -8*60*60)
	t := time.Date(2009, time.November, 10, 23, 0, 0, 0, loc)
	fmt.Println("The time is:", t.Format(time.RFC822))
}
Output:

The time is: 10 Nov 09 23:00 UTC-8

func LoadLocation

func LoadLocation(name string) (*Location, error)

LoadLocation возвращает Location с заданным именем.

Если имя "" или “UTC”, LoadLocation возвращает UTC. Если имя “Local”, LoadLocation возвращает Local.

В противном случае имя считается именем местоположения, соответствующим файлу в базе данных часовых поясов IANA, например “America/New_York”.

LoadLocation ищет базу данных часовых поясов IANA в следующих местах по порядку:

  1. Каталог или распакованный zip-файл, указанный переменной окружения ZONEINFO
  2. В Unix-системах - стандартное системное местоположение установки
  3. $GOROOT/lib/time/zoneinfo.zip
  4. Пакет time/tzdata, если он был импортирован
Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	location, err := time.LoadLocation("America/Los_Angeles")
	if err != nil {
		panic(err)
	}

	timeInUTC := time.Date(2018, 8, 30, 12, 0, 0, 0, time.UTC)
	fmt.Println(timeInUTC.In(location))
}
Output:

2018-08-30 05:00:00 -0700 PDT

func LoadLocationFromTZData

func LoadLocationFromTZData(name string, data []byte) (*Location, error)

LoadLocationFromTZData возвращает Location с заданным именем, инициализированный из данных в формате базы данных часовых поясов IANA. Данные должны быть в формате стандартного файла часового пояса IANA (например, содержимое /etc/localtime в Unix-системах).

func (*Location) String

func (l *Location) String() string

String возвращает описательное имя для информации о часовом поясе, соответствующее аргументу name для LoadLocation или FixedZone.

type Month

type Month int

Month определяет месяц года (January = 1, …).

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	_, month, day := time.Now().Date()
	if month == time.November && day == 10 {
		fmt.Println("Happy Go day!")
	}
}

Константы:

const (
	January Month = 1 + iota
	February
	March
	April
	May
	June
	July
	August
	September
	October
	November
	December
)

func (Month) String

func (m Month) String() string

String возвращает английское название месяца (“January”, “February”, …).

type ParseError

type ParseError struct {
	Layout     string
	Value      string
	LayoutElem string
	ValueElem  string
	Message    string
}

ParseError описывает проблему при разборе строки времени.

func (*ParseError) Error

func (e *ParseError) Error() string

Error возвращает строковое представление ParseError.

type Ticker

type Ticker struct {
	C <-chan Time // Канал, по которому доставляются "тики"
	// содержит неэкспортируемые поля
}

Ticker содержит канал, который доставляет “тики” часов с заданными интервалами.

func NewTicker

func NewTicker(d Duration) *Ticker

NewTicker создает новый Ticker, содержащий канал, который будет отправлять текущее время на канал после каждого тика. Период тиков задается аргументом duration. Ticker будет корректировать интервал времени или пропускать тики для компенсации медленных получателей. Длительность d должна быть больше нуля; в противном случае NewTicker вызовет панику.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()
	done := make(chan bool)
	go func() {
		time.Sleep(10 * time.Second)
		done <- true
	}()
	for {
		select {
		case <-done:
			fmt.Println("Done!")
			return
		case t := <-ticker.C:
			fmt.Println("Current time: ", t)
		}
	}
}

func (*Ticker) Reset

func (t *Ticker) Reset(d Duration)

Reset останавливает тикер и устанавливает его период в заданную продолжительность. Следующий тик придет после истечения нового периода. Длительность d должна быть больше нуля; в противном случае Reset вызовет панику.

func (*Ticker) Stop

func (t *Ticker) Stop()

Stop выключает тикер. После Stop больше не будет отправляться тиков. Stop не закрывает канал, чтобы предотвратить ошибочное чтение “тика” параллельной горутиной.

type Time

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

Time представляет момент времени с наносекундной точностью.

Программы, использующие время, обычно должны хранить и передавать его как значения, а не указатели. То есть переменные времени и поля структур должны быть типа time.Time, а не *time.Time.

Значение Time может использоваться несколькими горутинами одновременно, за исключением того, что методы Time.GobDecode, Time.UnmarshalBinary, Time.UnmarshalJSON и Time.UnmarshalText не являются безопасными для конкурентного использования.

Моменты времени можно сравнивать с помощью методов Time.Before, Time.After и Time.Equal. Метод Time.Sub вычитает два момента, производя Duration. Метод Time.Add добавляет Time и Duration, производя Time.

Нулевое значение типа Time - January 1, year 1, 00:00:00.000000000 UTC. Поскольку это время вряд ли встретится на практике, метод Time.IsZero дает простой способ обнаружения времени, которое не было инициализировано явно.

Каждое время имеет связанный Location. Методы Time.Local, Time.UTC и Time.In возвращают Time с конкретным Location. Изменение Location значения Time с помощью этих методов не изменяет фактический момент, который оно представляет, только часовой пояс, в котором оно интерпретируется.

func Date

func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time

Date возвращает Time, соответствующее

yyyy-mm-dd hh:mm:ss + nsec наносекунд

в соответствующем часовом поясе для этого времени в заданном местоположении.

Значения month, day, hour, min, sec и nsec могут быть вне их обычных диапазонов и будут нормализованы во время преобразования. Например, 32 октября преобразуется в 1 ноября.

Переход на летнее время пропускает или повторяет моменты времени. Например, в США March 13, 2011 2:15am никогда не происходило, а November 6, 2011 1:15am произошло дважды. В таких случаях выбор часового пояса, а следовательно и времени, не определен однозначно. Date возвращает время, которое корректно в одном из двух участвующих в переходе часовых поясов, но не гарантирует, в каком именно.

Date вызывает панику, если loc равен nil.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
	fmt.Printf("Go launched at %s\n", t.Local())
}
Output:

Go launched at 2009-11-10 15:00:00 -0800 PST

func Now

func Now() Time

Now возвращает текущее локальное время.

func Parse

func Parse(layout, value string) (Time, error)

Parse разбирает форматированную строку и возвращает значение времени, которое она представляет. Смотрите документацию для константы Layout, чтобы понять, как представить формат. Второй аргумент должен быть разбираемым с использованием строки формата (layout), предоставленной в качестве первого аргумента.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	// Пример с Time.Format содержит подробное описание того, как
	// определять строку формата для разбора значения time.Time;
	// Parse и Format используют одну и ту же модель для описания
	// входных и выходных данных.

	// longForm демонстрирует на примере, как эталонное время будет представлено
	// в желаемом формате.
	const longForm = "Jan 2, 2006 at 3:04pm (MST)"
	t, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")
	fmt.Println(t)

	// shortForm - это альтернативный способ представления эталонного времени
	// в желаемом формате; он не содержит информации о часовом поясе.
	// Примечание: без явного указания пояса возвращает время в UTC.
	const shortForm = "2006-Jan-02"
	t, _ = time.Parse(shortForm, "2013-Feb-03")
	fmt.Println(t)

	// Некоторые допустимые форматы могут быть недопустимыми значениями времени,
	// из-за спецификаторов формата, таких как _ для заполнения пробелами
	// и Z для информации о часовом поясе.
	// Например, формат RFC3339 2006-01-02T15:04:05Z07:00
	// содержит как Z, так и смещение часового пояса, чтобы обрабатывать оба варианта:
	// 2006-01-02T15:04:05Z
	// 2006-01-02T15:04:05+07:00
	t, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
	fmt.Println(t)
	t, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
	fmt.Println(t)
	_, err := time.Parse(time.RFC3339, time.RFC3339)
	fmt.Println("ошибка", err) // Возвращает ошибку, так как формат не является допустимым значением времени
}
Output:

2013-02-03 19:54:00 -0800 PST
2013-02-03 00:00:00 +0000 UTC
2006-01-02 15:04:05 +0000 UTC
2006-01-02 15:04:05 +0700 +0700
error parsing time "2006-01-02T15:04:05Z07:00": extra text: "07:00"

func ParseInLocation

func ParseInLocation(layout, value string, loc *Location) (Time, error)

ParseInLocation похож на Parse, но отличается в двух важных аспектах. Во-первых, при отсутствии информации о часовом поясе Parse интерпретирует время как UTC; ParseInLocation интерпретирует время как в заданном местоположении. Во-вторых, при заданном смещении или аббревиатуре часового пояса Parse пытается сопоставить его с местоположением Local; ParseInLocation использует заданное местоположение.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	loc, _ := time.LoadLocation("Europe/Berlin")

	// This will look for the name CEST in the Europe/Berlin time zone.
	const longForm = "Jan 2, 2006 at 3:04pm (MST)"
	t, _ := time.ParseInLocation(longForm, "Jul 9, 2012 at 5:02am (CEST)", loc)
	fmt.Println(t)

	// Note: without explicit zone, returns time in given location.
	const shortForm = "2006-Jan-02"
	t, _ = time.ParseInLocation(shortForm, "2012-Jul-09", loc)
	fmt.Println(t)

}
Output:

2012-07-09 05:02:00 +0200 CEST
2012-07-09 00:00:00 +0200 CEST

func Unix

func Unix(sec int64, nsec int64) Time

Unix возвращает локальное Time, соответствующее заданному Unix-времени, sec секунд и nsec наносекунд с 1 января 1970 UTC. Допустимо передавать nsec вне диапазона [0, 999999999]. Не все значения sec имеют соответствующее значение времени. Одним из таких значений является 1«63-1 (максимальное значение int64).

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	unixTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
	fmt.Println(unixTime.Unix())
	t := time.Unix(unixTime.Unix(), 0).UTC()
	fmt.Println(t)

}
Output:

1257894000
2009-11-10 23:00:00 +0000 UTC

func UnixMicro

func UnixMicro(usec int64) Time

UnixMicro возвращает локальное Time, соответствующее заданному Unix-времени, usec микросекунд с 1 января 1970 UTC.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	umt := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
	fmt.Println(umt.UnixMicro())
	t := time.UnixMicro(umt.UnixMicro()).UTC()
	fmt.Println(t)

}
Output:

1257894000000000
2009-11-10 23:00:00 +0000 UTC

func UnixMilli

func UnixMilli(msec int64) Time

UnixMilli возвращает локальное Time, соответствующее заданному Unix-времени, msec миллисекунд с 1 января 1970 UTC.

func (Time) Add

func (t Time) Add(d Duration) Time

Add возвращает время t+d.

Пример
import (
	"fmt"
	"time"
)

func main() {
	start := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
	afterTenSeconds := start.Add(time.Second * 10)
	afterTenMinutes := start.Add(time.Minute * 10)
	afterTenHours := start.Add(time.Hour * 10)
	afterTenDays := start.Add(time.Hour * 24 * 10)

	fmt.Printf("start = %v\n", start)
	fmt.Printf("start.Add(time.Second * 10) = %v\n", afterTenSeconds)
	fmt.Printf("start.Add(time.Minute * 10) = %v\n", afterTenMinutes)
	fmt.Printf("start.Add(time.Hour * 10) = %v\n", afterTenHours)
	fmt.Printf("start.Add(time.Hour * 24 * 10) = %v\n", afterTenDays)

}
Output:

start = 2009-01-01 12:00:00 +0000 UTC
start.Add(time.Second * 10) = 2009-01-01 12:00:10 +0000 UTC
start.Add(time.Minute * 10) = 2009-01-01 12:10:00 +0000 UTC
start.Add(time.Hour * 10) = 2009-01-01 22:00:00 +0000 UTC
start.Add(time.Hour * 24 * 10) = 2009-01-11 12:00:00 +0000 UTC

func (Time) AddDate

func (t Time) AddDate(years int, months int, days int) Time

AddDate возвращает время, соответствующее добавлению заданного количества лет, месяцев и дней к t. Например, AddDate(-1, 2, 3), примененное к 1 января 2011, возвращает 4 марта 2010.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	start := time.Date(2023, 03, 25, 12, 0, 0, 0, time.UTC)
	oneDayLater := start.AddDate(0, 0, 1)
	dayDuration := oneDayLater.Sub(start)
	oneMonthLater := start.AddDate(0, 1, 0)
	oneYearLater := start.AddDate(1, 0, 0)

	zurich, err := time.LoadLocation("Europe/Zurich")
	if err != nil {
		panic(err)
	}
	// This was the day before a daylight saving time transition in Zürich.
	startZurich := time.Date(2023, 03, 25, 12, 0, 0, 0, zurich)
	oneDayLaterZurich := startZurich.AddDate(0, 0, 1)
	dayDurationZurich := oneDayLaterZurich.Sub(startZurich)

	fmt.Printf("oneDayLater: start.AddDate(0, 0, 1) = %v\n", oneDayLater)
	fmt.Printf("oneMonthLater: start.AddDate(0, 1, 0) = %v\n", oneMonthLater)
	fmt.Printf("oneYearLater: start.AddDate(1, 0, 0) = %v\n", oneYearLater)
	fmt.Printf("oneDayLaterZurich: startZurich.AddDate(0, 0, 1) = %v\n", oneDayLaterZurich)
	fmt.Printf("Day duration in UTC: %v | Day duration in Zürich: %v\n", dayDuration, dayDurationZurich)

}

Output:

oneDayLater: start.AddDate(0, 0, 1) = 2023-03-26 12:00:00 +0000 UTC
oneMonthLater: start.AddDate(0, 1, 0) = 2023-04-25 12:00:00 +0000 UTC
oneYearLater: start.AddDate(1, 0, 0) = 2024-03-25 12:00:00 +0000 UTC
oneDayLaterZurich: startZurich.AddDate(0, 0, 1) = 2023-03-26 12:00:00 +0200 CEST
Day duration in UTC: 24h0m0s | Day duration in Zürich: 23h0m0s

func (Time) After

func (t Time) After(u Time) bool

After сообщает, является ли момент времени t после u.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	year2000 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
	year3000 := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)

	isYear3000AfterYear2000 := year3000.After(year2000) // True
	isYear2000AfterYear3000 := year2000.After(year3000) // False

	fmt.Printf("year3000.After(year2000) = %v\n", isYear3000AfterYear2000)
	fmt.Printf("year2000.After(year3000) = %v\n", isYear2000AfterYear3000)

}
Output:

year3000.After(year2000) = true
year2000.After(year3000) = false

func (Time) AppendBinary

func (t Time) AppendBinary(b []byte) ([]byte, error)

AppendBinary реализует интерфейс encoding.BinaryAppender. Добавляет бинарное представление времени в срез байт b и возвращает расширенный буфер.

func (Time) AppendFormat

func (t Time) AppendFormat(b []byte, layout string) []byte

AppendFormat аналогичен Time.Format, но добавляет текстовое представление времени в буфер b и возвращает расширенный буфер. Формат определяется параметром layout.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Date(2017, time.November, 4, 11, 0, 0, 0, time.UTC)
	text := []byte("Time: ")

	text = t.AppendFormat(text, time.Kitchen)
	fmt.Println(string(text))

}
Output:

Time: 11:00AM

func (Time) AppendText

func (t Time) AppendText(b []byte) ([]byte, error)

AppendText реализует интерфейс encoding.TextAppender. Форматирует время в формате RFC 3339 с субсекундной точностью и добавляет в буфер b. Возвращает ошибку, если время невозможно представить в корректном формате RFC 3339 (например, при выходе года за допустимый диапазон).

func (Time) Before

func (t Time) Before(u Time) bool

Before сообщает, является ли момент времени t до u.

Пример
ackage main

import (
	"fmt"
	"time"
)

func main() {
	year2000 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
	year3000 := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)

	isYear2000BeforeYear3000 := year2000.Before(year3000) // True
	isYear3000BeforeYear2000 := year3000.Before(year2000) // False

	fmt.Printf("year2000.Before(year3000) = %v\n", isYear2000BeforeYear3000)
	fmt.Printf("year3000.Before(year2000) = %v\n", isYear3000BeforeYear2000)

}
Output:

year2000.Before(year3000) = true
year3000.Before(year2000) = false

func (Time) Clock

func (t Time) Clock() (hour, min, sec int)

Clock возвращает час, минуту и секунду в пределах дня, указанного t.

func (Time) Compare

func (t Time) Compare(u Time) int

Compare сравнивает момент времени t с u. Если t до u, возвращает -1; если t после u, возвращает +1; если они одинаковы, возвращает 0.

func (Time) Date

func (t Time) Date() (year int, month Month, day int)

Date возвращает год, месяц и день, в которые происходит t.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	d := time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC)
	year, month, day := d.Date()

	fmt.Printf("year = %v\n", year)
	fmt.Printf("month = %v\n", month)
	fmt.Printf("day = %v\n", day)

}
Output:

year = 2000
month = February
day = 1

func (Time) Day

func (t Time) Day() int

Day возвращает день месяца, указанный t.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	d := time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC)
	day := d.Day()

	fmt.Printf("day = %v\n", day)

}
Output:

day = 1

func (Time) Equal

func (t Time) Equal(u Time) bool

Equal сообщает, представляют ли t и u один и тот же момент времени. Два времени могут быть равны, даже если они находятся в разных местоположениях. Например, 6:00 +0200 и 4:00 UTC равны.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	secondsEastOfUTC := int((8 * time.Hour).Seconds())
	beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)

	// Unlike the equal operator, Equal is aware that d1 and d2 are the
	// same instant but in different time zones.
	d1 := time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC)
	d2 := time.Date(2000, 2, 1, 20, 30, 0, 0, beijing)

	datesEqualUsingEqualOperator := d1 == d2
	datesEqualUsingFunction := d1.Equal(d2)

	fmt.Printf("datesEqualUsingEqualOperator = %v\n", datesEqualUsingEqualOperator)
	fmt.Printf("datesEqualUsingFunction = %v\n", datesEqualUsingFunction)

}
Output:

datesEqualUsingEqualOperator = false
datesEqualUsingFunction = true

func (Time) Format

func (t Time) Format(layout string) string

Format возвращает текстовое представление значения времени, отформатированное согласно layout. Смотрите документацию для константы Layout, чтобы понять, как представить формат.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	// Разбираем строку времени в стандартном Unix формате
	t, err := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 2015")
	if err != nil { // Всегда проверяем ошибки, даже если они не должны возникать
		panic(err)
	}

	tz, err := time.LoadLocation("Asia/Shanghai")
	if err != nil { // Всегда проверяем ошибки
		panic(err)
	}

	// Метод Stringer типа time.Time удобен для вывода без указания формата
	fmt.Println("формат по умолчанию:", t)

	// Предопределенные константы пакета реализуют распространенные форматы
	fmt.Println("Unix формат:", t.Format(time.UnixDate))

	// Часовой пояс, связанный со значением времени, влияет на его вывод
	fmt.Println("То же время в UTC:", t.UTC().Format(time.UnixDate))

	fmt.Println("в Шанхае с секундами:", t.In(tz).Format("2006-01-02T15:04:05 -070000"))

	fmt.Println("в Шанхае с секундами и двоеточиями:", t.In(tz).Format("2006-01-02T15:04:05 -07:00:00"))

	// Оставшаяся часть функции демонстрирует свойства
	// строки формата, используемой в Format

	// Строка формата, используемая функциями Parse и Format,
	// на примере показывает, как должно быть представлено эталонное время.
	// Важно подчеркнуть, что нужно показывать, как форматируется именно эталонное время,
	// а не произвольное время пользователя. Таким образом, каждая строка формата -
	// это представление временной метки:
	//	Jan 2 15:04:05 2006 MST
	// Простой способ запомнить это значение - заметить, что при таком порядке
	// оно содержит следующие значения (соответствующие элементам выше):
	//	  1 2  3  4  5    6  -7
	// Ниже показаны некоторые особенности.

	// Большинство использований Format и Parse применяют константные строки формата,
	// такие как определенные в этом пакете, но интерфейс гибкий,
	// как показывают следующие примеры.

	// Вспомогательная функция для красивого вывода примеров
	do := func(name, layout, want string) {
		got := t.Format(layout)
		if want != got {
			fmt.Printf("ошибка: для %q получено %q; ожидалось %q\n", layout, got, want)
			return
		}
		fmt.Printf("%-16s %q дает %q\n", name, layout, got)
	}

	// Заголовок для вывода
	fmt.Printf("\nФорматы:\n\n")

	// Простые начальные примеры
	do("Полная дата", "Mon Jan 2 15:04:05 MST 2006", "Wed Feb 25 11:06:39 PST 2015")
	do("Краткая дата", "2006/01/02", "2015/02/25")

	// Час эталонного времени - 15 (3PM). Формат может выражать это
	// любым способом, и так как наше значение утреннее, мы должны видеть его
	// в AM-формате. Покажем оба варианта в одной строке формата. И в нижнем регистре.
	do("AM/PM", "3PM==3pm==15h", "11AM==11am==11h")

	// При разборе, если за секундами следует точка
	// и цифры, это воспринимается как доля секунды, даже если
	// строка формата не включает представление долей секунды.
	// Добавим доли секунды к нашему значению времени
	t, err = time.Parse(time.UnixDate, "Wed Feb 25 11:06:39.1234 PST 2015")
	if err != nil {
		panic(err)
	}
	// Доли секунды не отображаются, если строка формата не содержит
	// их представления
	do("Без долей секунды", time.UnixDate, "Wed Feb 25 11:06:39 PST 2015")

	// Доли секунды можно вывести, добавив последовательность 0 или 9
	// после точки в значении секунд в строке формата.
	// Если используются 0, выводится указанное количество знаков.
	// Обратите внимание, что вывод содержит завершающий ноль.
	do("0 для долей секунды", "15:04:05.00000", "11:06:39.12340")

	// Если используются 9, завершающие нули отбрасываются.
	do("9 для долей секунды", "15:04:05.99999999", "11:06:39.1234")
}
формат по умолчанию: 2015-02-25 11:06:39 -0800 PST
Unix формат: Wed Feb 25 11:06:39 PST 2015
То же время в UTC: Wed Feb 25 19:06:39 UTC 2015
в Шанхае с секундами: 2015-02-26T03:06:39 +080000
в Шанхае с секундами и двоеточиями: 2015-02-26T03:06:39 +08:00:00

Форматы:

Полная дата       "Mon Jan 2 15:04:05 MST 2006" дает "Wed Feb 25 11:06:39 PST 2015"
Краткая дата      "2006/01/02" дает "2015/02/25"
AM/PM             "3PM==3pm==15h" дает "11AM==11am==11h"
Без долей секунды "Mon Jan _2 15:04:05 MST 2006" дает "Wed Feb 25 11:06:39 PST 2015"
0 для долей секунды "15:04:05.00000" дает "11:06:39.12340"
9 для долей секунды "15:04:05.99999999" дает "11:06:39.1234"
Пример (Pad)
package main

import (
	"fmt"
	"time"
)

func main() {
	// Разбор строки времени в стандартном Unix формате
	t, err := time.Parse(time.UnixDate, "Sat Mar 7 11:06:39 PST 2015")
	if err != nil { // Всегда проверяем ошибки, даже если они маловероятны
		panic(err)
	}

	// Вспомогательная функция для форматированного вывода примеров
	do := func(name, layout, want string) {
		got := t.Format(layout)
		if want != got {
			fmt.Printf("ошибка: для формата %q получено %q; ожидалось %q\n", layout, got, want)
			return
		}
		fmt.Printf("%-16s %q → %q\n", name, layout, got)
	}

	// Предопределенная константа Unix использует подчеркивание для выравнивания дня
	do("Unix формат", time.UnixDate, "Sat Mar  7 11:06:39 PST 2015")

	// Для выравнивания значений переменной ширины (например, дня месяца)
	// используйте _ вместо пробела в строке формата
	do("Без выравнивания", "<2>", "<7>")

	// Подчеркивание добавляет пробел для однозначных чисел
	do("С пробелом", "<_2>", "< 7>")

	// "0" добавляет нулевое заполнение для однозначных чисел
	do("С нулями", "<02>", "<07>")

	// Если значение уже имеет нужную ширину, заполнение не применяется
	// Например, секунды (39) не требуют заполнения, а минуты (06) - требуют
	do("Пропуск заполнения", "04:05", "06:39")
}
Unix формат       "Mon Jan _2 15:04:05 MST 2006""Sat Mar  7 11:06:39 PST 2015"
Без выравнивания "<2>""<7>"
С пробелом       "<_2>""< 7>"
С нулями         "<02>""<07>"
Пропуск заполнения "04:05""06:39"

func (Time) GoString

func (t Time) GoString() string

GoString реализует fmt.GoStringer и форматирует t для вывода в исходном коде Go.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
	fmt.Println(t.GoString())
	t = t.Add(1 * time.Minute)
	fmt.Println(t.GoString())
	t = t.AddDate(0, 1, 0)
	fmt.Println(t.GoString())
	t, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Feb 3, 2013 at 7:54pm (UTC)")
	fmt.Println(t.GoString())

}
Output:

time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
time.Date(2009, time.November, 10, 23, 1, 0, 0, time.UTC)
time.Date(2009, time.December, 10, 23, 1, 0, 0, time.UTC)
time.Date(2013, time.February, 3, 19, 54, 0, 0, time.UTC)

func (*Time) GobDecode

func (t *Time) GobDecode(data []byte) error

GobDecode реализует интерфейс gob.GobDecoder.

func (Time) GobEncode

func (t Time) GobEncode() ([]byte, error)

GobEncode реализует интерфейс gob.GobEncoder.

func (Time) Hour

func (t Time) Hour() int

Hour возвращает час в пределах дня, указанного t, в диапазоне [0, 23].

func (Time) ISOWeek

func (t Time) ISOWeek() (year, week int)

ISOWeek возвращает год и номер недели ISO 8601, в которые происходит t. Неделя находится в диапазоне от 1 до 53. 1-3 января года n могут принадлежать 52 или 53 неделе года n-1, а 29-31 декабря могут принадлежать 1 неделе года n+1.

func (Time) In

func (t Time) In(loc *Location) Time

In возвращает копию t, представляющую тот же момент времени, но с информацией о местоположении, установленной в loc для целей отображения.

In вызывает панику, если loc равен nil.

func (Time) IsDST

func (t Time) IsDST() bool

IsDST сообщает, находится ли время в настроенном местоположении в летнем времени.

func (Time) IsZero

func (t Time) IsZero() bool

IsZero сообщает, представляет ли t нулевой момент времени, 1 января, год 1, 00:00:00 UTC.

func (Time) Local

func (t Time) Local() Time

Local возвращает t с местоположением, установленным в локальное время.

func (Time) Location

func (t Time) Location() *Location

Location возвращает информацию о часовом поясе, связанную с t.

func (Time) MarshalBinary

func (t Time) MarshalBinary() ([]byte, error)

MarshalBinary реализует интерфейс encoding.BinaryMarshaler.

func (Time) MarshalJSON

func (t Time) MarshalJSON() ([]byte, error)

MarshalJSON реализует интерфейс encoding/json.Marshaler. Время — это строка в формате RFC 3339 с точностью до секунды. Если временная метка не может быть представлена ​​как допустимая RFC 3339 (например, год выходит за пределы диапазона), то выдается сообщение об ошибке.

func (Time) MarshalText

func (t Time) MarshalText() ([]byte, error)

MarshalText реализует интерфейс encoding.TextMarshaler. Вывод соответствует вызову метода Time.AppendText.

См. Time.AppendText для получения дополнительной информации.

func (Time) Minute

func (t Time) Minute() int

Minute возвращает минуту в пределах часа, указанного t, в диапазоне [0, 59].

func (Time) Month

func (t Time) Month() Month

Month возвращает месяц года, указанный t.

func (Time) Nanosecond

func (t Time) Nanosecond() int

Nanosecond возвращает наносекунду в пределах секунды, указанной t, в диапазоне [0, 999999999].

func (Time) Round

func (t Time) Round(d Duration) Time

Round возвращает результат округления t до ближайшего кратного d (с нулевого времени). При округлении половинных значений округление выполняется от нуля. Если d <= 0, Round возвращает t, лишенное любого монотонного показания часов, но в остальном неизменное.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Date(0, 0, 0, 12, 15, 30, 918273645, time.UTC)
	round := []time.Duration{
		time.Nanosecond,
		time.Microsecond,
		time.Millisecond,
		time.Second,
		2 * time.Second,
		time.Minute,
		10 * time.Minute,
		time.Hour,
	}

	for _, d := range round {
		fmt.Printf("t.Round(%6s) = %s\n", d, t.Round(d).Format("15:04:05.999999999"))
	}
}
Output:

t.Round(   1ns) = 12:15:30.918273645
t.Round(   1µs) = 12:15:30.918274
t.Round(   1ms) = 12:15:30.918
t.Round(    1s) = 12:15:31
t.Round(    2s) = 12:15:30
t.Round(  1m0s) = 12:16:00
t.Round( 10m0s) = 12:20:00
t.Round(1h0m0s) = 12:00:00

func (Time) Second

func (t Time) Second() int

Second возвращает секунду в пределах минуты, указанной t, в диапазоне [0, 59].

func (Time) String

func (t Time) String() string

String возвращает время, отформатированное с использованием строки формата

"2006-01-02 15:04:05.999999999 -0700 MST"

Если время имеет монотонное показание часов, возвращаемая строка включает конечное поле “m=±”, где value - монотонное показание часов, отформатированное как десятичное число секунд.

Пример
import (
	"fmt"
	"time"
)

func main() {
	timeWithNanoseconds := time.Date(2000, 2, 1, 12, 13, 14, 15, time.UTC)
	withNanoseconds := timeWithNanoseconds.String()

	timeWithoutNanoseconds := time.Date(2000, 2, 1, 12, 13, 14, 0, time.UTC)
	withoutNanoseconds := timeWithoutNanoseconds.String()

	fmt.Printf("withNanoseconds = %v\n", string(withNanoseconds))
	fmt.Printf("withoutNanoseconds = %v\n", string(withoutNanoseconds))

}
Output:

withNanoseconds = 2000-02-01 12:13:14.000000015 +0000 UTC
withoutNanoseconds = 2000-02-01 12:13:14 +0000 UTC

func (Time) Sub

func (t Time) Sub(u Time) Duration

Sub возвращает продолжительность t-u. Если результат превышает максимальное (или минимальное) значение, которое может храниться в Duration, будет возвращена максимальная (или минимальная) продолжительность.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	start := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
	end := time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC)

	difference := end.Sub(start)
	fmt.Printf("difference = %v\n", difference)

}
Output:

difference = 12h0m0s

func (Time) Truncate

func (t Time) Truncate(d Duration) Time

Truncate возвращает результат округления t вниз до кратного d (с нулевого времени). Если d <= 0, Truncate возвращает t, лишенное любого монотонного показания часов, но в остальном неизменное.

Пример
import (
	"fmt"
	"time"
)

func main() {
	t, _ := time.Parse("2006 Jan 02 15:04:05", "2012 Dec 07 12:15:30.918273645")
	trunc := []time.Duration{
		time.Nanosecond,
		time.Microsecond,
		time.Millisecond,
		time.Second,
		2 * time.Second,
		time.Minute,
		10 * time.Minute,
	}

	for _, d := range trunc {
		fmt.Printf("t.Truncate(%5s) = %s\n", d, t.Truncate(d).Format("15:04:05.999999999"))
	}
	// To round to the last midnight in the local timezone, create a new Date.
	midnight := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.Local)
	_ = midnight

}
Output:

t.Truncate(  1ns) = 12:15:30.918273645
t.Truncate(  1µs) = 12:15:30.918273
t.Truncate(  1ms) = 12:15:30.918
t.Truncate(   1s) = 12:15:30
t.Truncate(   2s) = 12:15:30
t.Truncate( 1m0s) = 12:15:00
t.Truncate(10m0s) = 12:10:00

func (Time) UTC

func (t Time) UTC() Time

UTC возвращает t с местоположением, установленным в UTC.

func (Time) Unix

func (t Time) Unix() int64

Unix возвращает t как Unix-время, количество секунд, прошедших с 1 января 1970 UTC. Результат не зависит от местоположения, связанного с t.

Пример
package main

import (
	"fmt"
	"time"
)

func main() {
	// 1 billion seconds of Unix, three ways.
	fmt.Println(time.Unix(1e9, 0).UTC())     // 1e9 seconds
	fmt.Println(time.Unix(0, 1e18).UTC())    // 1e18 nanoseconds
	fmt.Println(time.Unix(2e9, -1e18).UTC()) // 2e9 seconds - 1e18 nanoseconds

	t := time.Date(2001, time.September, 9, 1, 46, 40, 0, time.UTC)
	fmt.Println(t.Unix())     // seconds since 1970
	fmt.Println(t.UnixNano()) // nanoseconds since 1970

}
Output:

2001-09-09 01:46:40 +0000 UTC
2001-09-09 01:46:40 +0000 UTC
2001-09-09 01:46:40 +0000 UTC
1000000000
1000000000000000000

func (Time) UnixMicro

func (t Time) UnixMicro() int64

UnixMicro возвращает t как Unix-время, количество микросекунд, прошедших с 1 января 1970 UTC.

func (Time) UnixMilli

func (t Time) UnixMilli() int64

UnixMilli возвращает t как Unix-время, количество миллисекунд, прошедших с 1 января 1970 UTC.

func (Time) UnixNano

func (t Time) UnixNano() int64

UnixNano возвращает t как Unix-время, количество наносекунд, прошедших с 1 января 1970 UTC.

func (*Time) UnmarshalBinary

func (t *Time) UnmarshalBinary(data []byte) error

UnmarshalBinary реализует интерфейс encoding.BinaryUnmarshaler.

func (*Time) UnmarshalJSON

func (t *Time) UnmarshalJSON(data []byte) error

UnmarshalJSON реализует интерфейс encoding/json.Unmarshaler. Время должно быть строкой в ​​кавычках в формате RFC 3339.

func (*Time) UnmarshalText

func (t *Time) UnmarshalText(data []byte) error

UnmarshalText реализует интерфейс encoding.TextUnmarshaler. Время должно быть в формате RFC 3339.

func (Time) Weekday

func (t Time) Weekday() Weekday

Weekday возвращает день недели, указанный t.

func (Time) Year

func (t Time) Year() int

Year возвращает год, в который происходит t.

func (Time) YearDay

func (t Time) YearDay() int

YearDay возвращает день года, указанный t, в диапазоне [1,365] для невисокосных лет и [1,366] в високосных годах.

func (Time) Zone

func (t Time) Zone() (name string, offset int)

Zone вычисляет часовой пояс, действующий в момент времени t, возвращая сокращенное название пояса (например, “CET”) и его смещение в секундах к востоку от UTC.

func (Time) ZoneBounds

func (t Time) ZoneBounds() (start, end Time)

ZoneBounds возвращает границы часового пояса, действующего в момент времени t. Пояс начинается в start, а следующий пояс начинается в end. Если пояс начинается в начале времен, start будет возвращен как нулевое Time. Если пояс продолжается вечно, end будет возвращен как нулевое Time. Местоположение возвращаемых времен будет таким же, как у t.

type Timer

type Timer struct {
	C <-chan Time
	// содержит неэкспортируемые поля
}

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

func AfterFunc

func AfterFunc(d Duration, f func()) *Timer

AfterFunc ждет истечения продолжительности, а затем вызывает f в своей собственной горутине. Возвращает Timer, который можно использовать для отмены вызова с помощью его метода Stop. Поле C возвращаемого Timer не используется и будет nil.

func NewTimer

func NewTimer(d Duration) *Timer

NewTimer создает новый Timer, который отправит текущее время на свой канал после как минимум продолжительности d.

func (*Timer) Reset

func (t *Timer) Reset(d Duration) bool

Reset изменяет таймер на истечение после продолжительности d. Возвращает true, если таймер был активен, false, если таймер уже истек или был остановлен.

func (t *Timer) Reset(d Duration) bool

Reset изменяет таймер для срабатывания через интервал d. Возвращает true, если таймер был активен, и false, если таймер уже сработал или был остановлен.

Для функционального таймера, созданного через AfterFunc(d, f):

  • Reset либо переносит время выполнения f (возвращает true)
  • либо планирует новое выполнение f (возвращает false)

При возврате false:

  • Reset не ожидает завершения предыдущего выполнения f
  • Не гарантируется отсутствие конкурентного выполнения предыдущей и новой версий f
  • Для контроля завершения нужно реализовать собственную синхронизацию

Для канального таймера (создан через NewTimer), начиная с Go 1.23:

  • Гарантируется, что после Reset канал t.C не получит значений от предыдущих настроек
  • Если программа еще не читала из t.C и таймер активен, Reset гарантированно вернет true

До Go 1.23 безопасное использование требовало:

  1. Вызова Timer.Stop()
  2. Явного чтения из канала (если Stop вернула false)

func (*Timer) Stop

func (t *Timer) Stop() bool

Stop предотвращает срабатывание таймера. Возвращает:

  • true - если таймер был успешно остановлен
  • false - если таймер уже сработал или был остановлен

Для функционального таймера (AfterFunc):

  • При false функция f уже запущена в отдельной горутине
  • Stop не ожидает завершения f
  • Для контроля завершения нужна дополнительная синхронизация

Для канального таймера (Go 1.23+):

  • После Stop чтение из t.C гарантированно блокируется
  • Не будет получено “устаревших” значений времени
  • Если таймер активен и канал не читался, Stop гарантированно вернет true

До Go 1.23 требовалось:

  • При false выполнять дополнительное чтение <-t.C для очистки канала

type Weekday

type Weekday int

Weekday определяет день недели (Sunday = 0, …).

Константы:

const (
	Sunday Weekday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
)

func (Weekday) String

func (d Weekday) String() string

String возвращает английское название дня (“Sunday”, “Monday”, …).

Пакет time/tzdata

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

Импорт этого пакета увеличит размер программы примерно на 450 КБ.

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

Этот пакет будет автоматически импортирован, если вы построите с -tags timetzdata.

21 - SCS: Управление HTTP-сессиями для Go

Менеджер сессий для GO

Возможности

  • Автоматическая загрузка и сохранение данных сессии через middleware

  • Выбор из 19 серверных хранилищ для сессий, включая:

    • Базы данных: PostgreSQL, MySQL, MSSQL, SQLite
    • Кэш-системы: Redis
    • Поддержка пользовательских хранилищ
  • Расширенные функции:

    • Несколько сессий на один запрос
    • “Flash”-сообщения (одноразовые уведомления)
    • Регенерация токенов сессии
    • Таймауты: по бездействию и абсолютные
    • Функция “запомнить меня”
  • Гибкость:

    • Простота расширения и кастомизации
    • Передача токенов сессии через HTTP-заголовки или тела запросов/ответов
  • Производительность:

    • Оптимизированная архитектура
    • Меньший размер, выше скорость и меньше потребление памяти по сравнению с gorilla/sessions

    Установка

Этот пакет требует Go версии 1.12 или новее.

go get github.com/alexedwards/scs/v2

Примечание: Если вы используете традиционный механизм GOPATH для управления зависимостями (вместо модулей), вам нужно использовать go get и импортировать github.com/alexedwards/scs без суффикса v2.

Рекомендуется использовать версионные релизы. Код в ветке tip может содержать экспериментальные функции, которые могут измениться.

Базовое использование

SCS реализует механизм управления сессиями в соответствии с рекомендациями по безопасности OWASP. Данные сессии хранятся на сервере, а случайно сгенерированный уникальный токен сессии (или ID сессии) передается клиенту и обратно через cookie сессии.

package main

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

	"github.com/alexedwards/scs/v2"
)

var sessionManager *scs.SessionManager

func main() {
	// Инициализация нового менеджера сессий и настройка времени жизни сессии
	sessionManager = scs.New()
	sessionManager.Lifetime = 24 * time.Hour

	mux := http.NewServeMux()
	mux.HandleFunc("/put", putHandler)
	mux.HandleFunc("/get", getHandler)

	// Оберните ваши обработчики middleware LoadAndSave()
	http.ListenAndServe(":4000", sessionManager.LoadAndSave(mux))
}

func putHandler(w http.ResponseWriter, r *http.Request) {
	// Сохранение нового ключа и значения в данных сессии
	sessionManager.Put(r.Context(), "message", "Hello from a session!")
}

func getHandler(w http.ResponseWriter, r *http.Request) {
	// Использование вспомогательной функции GetString для получения строкового значения
	// по ключу. Возвращается нулевое значение, если ключ не существует.
	msg := sessionManager.GetString(r.Context(), "message")
	io.WriteString(w, msg)
}

Пример работы с curl:

$ curl -i --cookie-jar cj --cookie cj localhost:4000/put
HTTP/1.1 200 OK
Cache-Control: no-cache="Set-Cookie"
Set-Cookie: session=lHqcPNiQp_5diPxumzOklsSdE-MJ7zyU6kjch1Ee0UM; Path=/; Expires=Sat, 27 Apr 2019 10:28:20 GMT; Max-Age=86400; HttpOnly; SameSite=Lax
Vary: Cookie
Date: Fri, 26 Apr 2019 10:28:19 GMT
Content-Length: 0

$ curl -i --cookie-jar cj --cookie cj localhost:4000/get
HTTP/1.1 200 OK
Date: Fri, 26 Apr 2019 10:28:24 GMT
Content-Length: 21
Content-Type: text/plain; charset=utf-8

Hello from a session!

Настройка поведения сессии

Поведение сессии можно настроить через поля SessionManager:

sessionManager = scs.New()
sessionManager.Lifetime = 3 * time.Hour          // Общее время жизни сессии
sessionManager.IdleTimeout = 20 * time.Minute    // Таймаут бездействия
sessionManager.Cookie.Name = "session_id"        // Имя cookie
sessionManager.Cookie.Domain = "example.com"     // Домен cookie
sessionManager.Cookie.HttpOnly = true            // Доступ только через HTTP
sessionManager.Cookie.Path = "/example/"         // Путь cookie
sessionManager.Cookie.Persist = true             // Сохранение после закрытия браузера
sessionManager.Cookie.SameSite = http.SameSiteStrictMode // Политика SameSite
sessionManager.Cookie.Secure = true              // Только HTTPS
sessionManager.Cookie.Partitioned = true         // Partitioned cookies

Документация по всем доступным настройкам и их значениям по умолчанию доступна [здесь](ссылка на документацию).

Работа с данными сессии

Установка и получение данных

Данные можно устанавливать с помощью метода Put() и получать с помощью Get(). Для распространённых типов данных предусмотрены вспомогательные методы, такие как GetString(), GetInt() и GetBytes(). Полный список вспомогательных методов доступен в документации.

Одноразовое получение данных

Метод Pop() (и соответствующие вспомогательные методы для распространённых типов данных) работает как одноразовый Get() - извлекает данные и сразу удаляет их из сессии. Это полезно для реализации функционала “flash”-сообщений, которые показываются пользователю только один раз.

Дополнительные функции

  • Exists() - возвращает bool, указывающий на наличие ключа в данных сессии
  • Keys() - возвращает отсортированный срез всех ключей в данных сессии

Удаление данных

Отдельные элементы можно удалить с помощью метода Remove(). Для полной очистки данных сессии используйте метод Destroy(). После вызова Destroy() любые последующие операции в рамках того же цикла запроса приведут к созданию новой сессии - с новым токеном и временем жизни.

Хранение пользовательских типов

SCS использует кодировку gob для хранения данных сессии. Пользовательские типы должны быть зарегистрированы в пакете encoding/gob. Поля структур пользовательских типов должны быть экспортируемыми (начинаться с заглавной буквы), чтобы быть видимыми для пакета encoding/gob. Пример работы можно посмотреть [здесь](ссылка на пример).

Загрузка и сохранение сессий

Стандартное использование

Большинство приложений используют middleware LoadAndSave(). Этот middleware:

  • Загружает и сохраняет данные сессии в хранилище
  • Управляет передачей токена сессии клиенту через cookie

Кастомизация поведения

Если требуется изменить стандартное поведение (например, передавать токен сессии через HTTP-заголовки или устанавливать распределённую блокировку токена сессии на время запроса), можно создать собственный middleware, используя код LoadAndSave() в качестве шаблона. Пример доступен [здесь](ссылка на пример).

Точный контроль

Для более точного управления можно загружать и сохранять сессии непосредственно в обработчиках запросов или в любом другом месте приложения. Пример такого подхода приведён [здесь](ссылка на пример).

Настройка хранилища сессий

Хранилище по умолчанию

По умолчанию SCS использует in-memory хранилище для данных сессии. Это:

  • Удобно (не требует настройки)
  • Очень быстро
  • Но все данные теряются при остановке или перезапуске приложения

Это решение подходит для:

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

Продукционные решения

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

  • PostgreSQL
  • MySQL
  • Другие поддерживаемые системы (список ниже)

Полный список поддерживаемых хранилищ сессий с инструкциями по использованию доступен в таблице (ссылки в оригинальной документации).

Поддерживаемые хранилища сессий

Вот полный список доступных хранилищ сессий в SCS с их характеристиками:

Пакет Бэкенд Встроенный In-Memory Мультипроцессорный
badgerstore BadgerDB Да Нет Нет
boltstore BBolt Да Нет Нет
bunstore Bun ORM (PostgreSQL/MySQL/MSSQL/SQLite) Нет Нет Да
buntdbstore BuntDB Да Да Нет
cockroachdbstore CockroachDB Нет Нет Да
consulstore Consul Нет Да Да
etcdstore Etcd Нет Нет Да
firestore Google Cloud Firestore Нет ? Да
gormstore GORM ORM (PostgreSQL/MySQL/SQLite/MSSQL/TiDB) Нет Нет Да
leveldbstore LevelDB Да Нет Нет
memstore In-memory (по умолчанию) Да Да Нет
mongodbstore MongoDB Нет Нет Да
mssqlstore Microsoft SQL Server Нет Нет Да
mysqlstore MySQL Нет Нет Да
pgxstore PostgreSQL (драйвер pgx) Нет Нет Да
postgresstore PostgreSQL (драйвер pq) Нет Нет Да
redisstore Redis Нет Да Да
sqlite3store SQLite3 (CGO-драйвер mattn/go-sqlite3) Да Нет Да

Ключевые характеристики:

  • Встроенный - Не требует отдельного сервера/процесса
  • In-Memory - Хранит данные в оперативной памяти
  • Мультипроцессорный - Поддерживает работу в распределённой среде

Пользовательские хранилища

SCS также поддерживает создание собственных хранилищ сессий. Подробная информация доступна [здесь](ссылка на документацию).

Выбор хранилища зависит от требований вашего приложения:

  • Для тестирования/разработки - memstore или buntdbstore
  • Для продакшена - postgresstore, mysqlstore или redisstore
  • Для встраиваемых решений - badgerstore или boltstore

Использование пользовательских хранилищ сессий

Базовый интерфейс хранилища

Интерфейс scs.Store определяет контракт для пользовательских хранилищ сессий. Любой объект, реализующий этот интерфейс, может быть установлен как хранилище при настройке сессии.

type Store interface {
    // Delete удаляет токен сессии и соответствующие данные из хранилища.
    // Если токен не существует, Delete должен завершиться без ошибки.
    Delete(token string) (err error)

    // Find возвращает данные для указанного токена сессии.
    // Если токен не найден или истек, возвращает found=false.
    // Для повреждённых токенов также возвращает found=false.
    // Ошибка возвращается только для системных сбоев.
    Find(token string) (b []byte, found bool, err error)

    // Commit добавляет или обновляет данные сессии с указанным сроком действия.
    Commit(token string, b []byte, expiry time.Time) (err error)
}

type IterableStore interface {
    // All возвращает map со всеми активными (не истекшими) сессиями.
    // Ключ - токен сессии, значение - данные сессии.
    // Если активных сессий нет, возвращает пустую (не nil) map.
    All() (map[string][]byte, error)
}

Интерфейс с поддержкой контекста

scs.CtxStore расширяет базовый интерфейс, добавляя методы с поддержкой context.Context:

type CtxStore interface {
    Store

    // Версии методов с контекстом
    DeleteCtx(ctx context.Context, token string) (err error)
    FindCtx(ctx context.Context, token string) (b []byte, found bool, err error)
    CommitCtx(ctx context.Context, token string, b []byte, expiry time.Time) (err error)
}

type IterableCtxStore interface {
    // AllCtx - версия All с поддержкой контекста
    AllCtx(ctx context.Context) (map[string][]byte, error)
}

Защита от фиксации сессии

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

func loginHandler(w http.ResponseWriter, r *http.Request) {
    userID := 123

    // 1. Сначала обновляем токен сессии
    err := sessionManager.RenewToken(r.Context())
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    // 2. Затем изменяем уровень привилегий
    sessionManager.Put(r.Context(), "userID", userID)
}

Важные моменты:

  1. RenewToken() должен вызываться ДО изменения данных сессии
  2. При ошибке обновления токена следует прервать операцию
  3. Такой же подход применяется при выходе пользователя

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

Расширенные возможности SCS

Несколько сессий в одном запросе

SCS позволяет использовать несколько параллельных сессий в рамках одного запроса с разными:

  • Временем жизни
  • Хранилищами данных

Пример реализации доступен [здесь](ссылка на пример).

Перебор всех сессий

Для работы со всеми активными сессиями SCS предоставляет метод Iterate(), который принимает функцию-обработчик:

err := sessionManager.Iterate(r.Context(), func(ctx context.Context) error {
    userID := sessionManager.GetInt(ctx, "userID")
    
    if userID == 4 {
        // Удаляем сессию для пользователя с ID=4
        return sessionManager.Destroy(ctx)
    }
    
    return nil
})
if err != nil {
    log.Fatal(err)
}

Ключевые особенности:

  • Обработчик получает контекст каждой сессии
  • Можно выполнять любые операции с сессией
  • Возврат ошибки прерывает процесс итерации

Потоковая передача ответов (Flushing)

Поддержка потоковой передачи реализована через http.NewResponseController (доступно в Go ≥1.20):

func flushingHandler(w http.ResponseWriter, r *http.Request) {
    sessionManager.Put(r.Context(), "message", "Hello from flushing handler!")

    rc := http.NewResponseController(w)

    for i := 0; i < 5; i++ {
        fmt.Fprintf(w, "Write %d\n", i)
        
        err := rc.Flush()
        if err != nil {
            log.Println(err)
            return
        }

        time.Sleep(time.Second)
    }
}

Важно:

  • Стандартная реализация middleware LoadAndSave() не поддерживает интерфейс http.Flusher
  • Потоковая передача работает только в Go версии 1.20 и выше

Полный рабочий пример можно найти [здесь](ссылка на пример).

Совместимость с фреймворками

Возможны проблемы при интеграции с фреймворками, которые не передают контекст запроса через стандартные middleware:

  • Для Echo рекомендуется использовать пакет echo-scs-session
  • Fiber и другие нестандартные фреймворки могут требовать дополнительной адаптации

Вклад в разработку

Приветствуются:

  • Исправления ошибок
  • Улучшения документации

Для новых функций или изменений поведения:

  1. Создайте issue для обсуждения
  2. После согласования - отправляйте PR

Для новых реализаций хранилищ:

  • Как правило, не добавляются в основной репозиторий
  • Можно создать отдельный репозиторий и добавить ссылку в README через PR

21.1 - Официальное API SCS: Управление HTTP-сессиями для Go

Менеджер сессий для GO

Использование пользовательских хранилищ сеансов scs.Store определяет интерфейс для пользовательских хранилищ сеансов. Любой объект, реализующий этот интерфейс, может быть установлен как хранилище при настройке сеанса.

Работа с пользовательскими хранилищами сессий

Интерфейсы хранилищ

Базовый интерфейс Store

Определяет контракт для реализации пользовательских хранилищ:

type Store interface {
    // Delete удаляет токен и данные сессии из хранилища
    // Если токен не существует - операция должна завершиться успешно
    Delete(token string) error

    // Find ищет данные по токену сессии
    // Возвращает:
    // - данные (если найдены)
    // - флаг found (найдено/не найдено)
    // - ошибку (только для системных сбоев)
    Find(token string) ([]byte, bool, error)

    // Commit сохраняет или обновляет данные сессии
    Commit(token string, data []byte, expiry time.Time) error
}

Интерфейс IterableStore

Добавляет возможность перебора всех сессий:

type IterableStore interface {
    // All возвращает все активные (не истекшие) сессии
    // В формате map[токен]данные
    All() (map[string][]byte, error)
}

Версии с поддержкой контекста

CtxStore

Расширяет базовый интерфейс с добавлением context.Context:

type CtxStore interface {
    Store
    
    // Версии методов с поддержкой контекста
    DeleteCtx(ctx context.Context, token string) error
    FindCtx(ctx context.Context, token string) ([]byte, bool, error) 
    CommitCtx(ctx context.Context, token string, data []byte, expiry time.Time) error
}

IterableCtxStore

Аналогично для перебора сессий:

type IterableCtxStore interface {
    AllCtx(ctx context.Context) (map[string][]byte, error)
}

Защита от фиксации сессии

Для предотвращения атак фиксации сессии необходимо:

func loginHandler(w http.ResponseWriter, r *http.Request) {
    userID := 123

    // 1. Сначала обновляем токен сессии
    if err := sessionManager.RenewToken(r.Context()); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 2. Затем сохраняем данные пользователя
    sessionManager.Put(r.Context(), "userID", userID)
}

Ключевые моменты:

  1. Всегда обновляйте токен при изменении прав доступа
  2. Порядок операций критически важен
  3. Такой же подход применяйте при выходе пользователя

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

Типы данных в SCS

Интерфейс Codec

Добавлен в v2.2.0

type Codec interface {
    // Encode преобразует срок действия и значения сессии в байтовый срез
    Encode(deadline time.Time, values map[string]interface{}) ([]byte, error)
    
    // Decode восстанавливает срок действия и значения сессии из байтового среза
    Decode([]byte) (deadline time.Time, values map[string]interface{}, err error)
}

Интерфейс для кодирования/декодирования данных сессии при работе с хранилищем.

Интерфейс CtxStore

Добавлен в v2.5.0

type CtxStore interface {
    Store
    
    // Версии методов с поддержкой контекста:
    DeleteCtx(ctx context.Context, token string) error
    FindCtx(ctx context.Context, token string) ([]byte, bool, error)
    CommitCtx(ctx context.Context, token string, b []byte, expiry time.Time) error
}

Расширенный интерфейс хранилища с поддержкой context.Context.

Структура GobCodec

Добавлена в v2.3.1

type GobCodec struct{}

Реализация Codec с использованием encoding/gob для сериализации данных.

Методы GobCodec:

// Decode преобразует байты в срок действия и данные сессии
func (GobCodec) Decode(b []byte) (time.Time, map[string]interface{}, error)

// Encode сериализует данные сессии в байтовый срез  
func (GobCodec) Encode(deadline time.Time, values map[string]interface{}) ([]byte, error)

Интерфейс IterableCtxStore

Добавлен в v2.5.0

type IterableCtxStore interface {
    // AllCtx возвращает все активные сессии (с поддержкой контекста)
    AllCtx(ctx context.Context) (map[string][]byte, error)
}

Интерфейс для перечислимых хранилищ с поддержкой context.Context.

Интерфейс IterableStore

Добавлен в v2.5.0

type IterableStore interface {
    // All возвращает все активные (не истекшие) сессии
    All() (map[string][]byte, error)
}

Базовый интерфейс для хранилищ с возможностью перебора сессий.

Ключевые особенности:

  • Codec - абстракция для сериализации данных
  • GobCodec - стандартная реализация через encoding/gob
  • CtxStore добавляет поддержку контекста к базовому хранилищу
  • Интерфейсы Iterable позволяют работать со всеми сессиями

Конфигурация сессий в SCS

Структура SessionCookie

type SessionCookie struct {
    // Имя cookie сессии (не должно содержать спецсимволы)
    // По умолчанию: "session"
    Name string

    // Домен cookie (по умолчанию - текущий домен)
    Domain string

    // HttpOnly флаг (по умолчанию true)
    HttpOnly bool

    // Путь cookie (по умолчанию "/")
    Path string

    // Сохранять cookie после закрытия браузера (по умолчанию true)
    Persist bool

    // Политика SameSite (по умолчанию Lax)
    SameSite http.SameSite

    // Secure флаг (только HTTPS, по умолчанию false)
    Secure bool
}

Настройки cookie для управления сессиями.

Структура SessionManager

Добавлена в v2.1.0

type SessionManager struct {
    // Таймаут бездействия (опционально)
    IdleTimeout time.Duration

    // Максимальное время жизни сессии (по умолчанию 24 часа)
    Lifetime time.Duration

    // Хранилище сессий
    Store Store

    // Настройки cookie
    Cookie SessionCookie

    // Кодек для сериализации данных (по умолчанию GobCodec)
    Codec Codec

    // Обработчик ошибок (по умолчанию HTTP 500)
    ErrorFunc func(http.ResponseWriter, *http.Request, error)

    // Хэшировать токен в хранилище
    HashTokenInStore bool
    // содержит скрытые поля
}

Основные настройки:

  • IdleTimeout: автоматическое завершение неактивных сессий
  • Lifetime: абсолютное время жизни сессии
  • Store: подключение к Redis, PostgreSQL и другим хранилищам
  • Cookie: тонкая настройка параметров безопасности cookie
  • Codec: кастомная сериализация данных
  • ErrorFunc: обработка ошибок на уровне middleware

Рекомендации по безопасности:

  1. Всегда устанавливайте Secure=true в production
  2. Используйте SameSite=Strict для критичных операций
  3. Для API можно отключить HttpOnly и работать через заголовки
  4. Регулируйте Lifetime в соответствии с требованиями приложения

Пример инициализации:

manager := scs.New()
manager.Lifetime = 12 * time.Hour
manager.Cookie.Secure = true
manager.Cookie.SameSite = http.SameSiteStrictMode
manager.Store = redisstore.New(redisPool)

Методы SessionManager

Основные функции

New

Добавлено в v2.1.0

func New() *SessionManager

Создает новый менеджер сессий с настройками по умолчанию. Потокобезопасен.

Clear (устаревший)

func (s *SessionManager) Clear(ctx context.Context) error

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

Управление сессиями

Commit

Добавлено в v2.1.0

func (s *SessionManager) Commit(ctx context.Context) (string, time.Time, error)

Сохраняет данные сессии в хранилище и возвращает:

  • Токен сессии
  • Время истечения
  • Ошибку (если есть)

Обычно используется автоматически middleware LoadAndSave().

Deadline

Добавлено в v2.5.0

func (s *SessionManager) Deadline(ctx context.Context) time.Time

Возвращает абсолютное время истечения сессии. При использовании IdleTimeout сессия может завершиться раньше из-за неактивности.

Destroy

Добавлено в v2.1.0

func (s *SessionManager) Destroy(ctx context.Context) error

Полностью удаляет сессию:

  1. Удаляет данные из хранилища
  2. Помечает сессию как уничтоженную
  3. Новые операции создадут свежую сессию

Работа с данными

Exists

Добавлено в v2.1.0

func (s *SessionManager) Exists(ctx context.Context, key string) bool

Проверяет наличие ключа в данных сессии.

Get

Добавлено в v2.1.0

func (s *SessionManager) Get(ctx context.Context, key string) interface{}

Возвращает значение по ключу как interface{}, требующее приведения типа:

foo, ok := session.Get(ctx, "foo").(string)
if !ok {
    return errors.New("ошибка приведения типа")
}

GetBool

Добавлено в v2.1.0

func (s *SessionManager) GetBool(ctx context.Context, key string) bool

Возвращает значение как bool (по умолчанию false если ключ отсутствует или тип не совпадает).

GetBytes

Добавлено в v2.1.0

func (s *SessionManager) GetBytes(ctx context.Context, key string) []byte

Возвращает значение как []byte (по умолчанию nil).

Методы работы с данными сессии

Получение типизированных значений

GetFloat

func (s *SessionManager) GetFloat(ctx context.Context, key string) float64

Возвращает значение как float64 (0 если ключ отсутствует или тип не совпадает)

GetInt

func (s *SessionManager) GetInt(ctx context.Context, key string) int  

Возвращает значение как int (0 если ключ отсутствует)

GetInt32

func (s *SessionManager) GetInt32(ctx context.Context, key string) int32

Возвращает значение как int32 (0 по умолчанию)

GetInt64

func (s *SessionManager) GetInt64(ctx context.Context, key string) int64

Возвращает значение как int64 (0 по умолчанию)

GetString

func (s *SessionManager) GetString(ctx context.Context, key string) string

Возвращает строковое значение (пустая строка "" если ключ отсутствует)

GetTime

func (s *SessionManager) GetTime(ctx context.Context, key string) time.Time

Возвращает значение времени (time.IsZero() == true если ключ отсутствует)

Управление сессиями

Iterate

func (s *SessionManager) Iterate(ctx context.Context, fn func(context.Context) error) error

Итерируется по всем активным сессиям и выполняет функцию fn для каждой. Требует поддержки итерации в хранилище.

Keys

func (s *SessionManager) Keys(ctx context.Context) []string

Возвращает отсортированный список всех ключей сессии (пустой слайс если данных нет)

Middleware и загрузка данных

Load

func (s *SessionManager) Load(ctx context.Context, token string) (context.Context, error)

Загружает данные сессии по токену и возвращает новый контекст. Создает новую сессию если токен не найден.

LoadAndSave

func (s *SessionManager) LoadAndSave(next http.Handler) http.Handler

Middleware для автоматической загрузки/сохранения сессии и управления cookie.

Пример:

router := http.NewServeMux()
router.HandleFunc("/", handler)

// Обертывание роутера middleware
app := sessionManager.LoadAndSave(router)

http.ListenAndServe(":4000", app)

Методы управления сессиями в SCS

Работа с данными сессии

MergeSession (v2.5.0+)

func (s *SessionManager) MergeSession(ctx context.Context, token string) error

Объединяет данные из другой сессии (полезно при OAuth-редиректах). Для полной замены данных используйте Clear().

Pop-методы

Одноразовое получение данных с удалением ключа:

func (s *SessionManager) Pop(ctx context.Context, key string) interface{}
func (s *SessionManager) PopBool(ctx context.Context, key string) bool
func (s *SessionManager) PopBytes(ctx context.Context, key string) []byte  
func (s *SessionManager) PopFloat(ctx context.Context, key string) float64
func (s *SessionManager) PopInt(ctx context.Context, key string) int
func (s *SessionManager) PopString(ctx context.Context, key string) string
func (s *SessionManager) PopTime(ctx context.Context, key string) time.Time

Пример для flash-сообщений:

// Установка
sessionManager.Put(ctx, "flash", "Сообщение")

// Получение с удалением  
msg := sessionManager.PopString(ctx, "flash")

Основные операции

Put (v2.1.0+)

func (s *SessionManager) Put(ctx context.Context, key string, val interface{})

Добавляет или обновляет значение в сессии. Автоматически помечает сессию как измененную.

Remove (v2.1.0+)

func (s *SessionManager) Remove(ctx context.Context, key string)

Удаляет ключ из сессии. Если ключ не существует - операция игнорируется.

Управление токенами и временем жизни

RenewToken (v2.1.0+)

func (s *SessionManager) RenewToken(ctx context.Context) error

Генерирует новый токен сессии, сохраняя данные. Критично для:

  • Входа/выхода пользователей
  • Изменения прав доступа
  • Защиты от фиксации сессии

RememberMe (v2.5.0+)

func (s *SessionManager) RememberMe(ctx context.Context, val bool)

Управляет постоянством сессии (работает только при Cookie.Persist = false).

SetDeadline (v2.7.0+)

func (s *SessionManager) SetDeadline(ctx context.Context, expire time.Time)

Устанавливает абсолютное время истечения сессии (может быть перекрыто IdleTimeout).

Информационные методы

Status (v2.1.0+)

func (s *SessionManager) Status(ctx context.Context) Status

Возвращает текущее состояние сессии (новые/измененные/уничтоженные).

Token (v2.5.0+)

func (s *SessionManager) Token(ctx context.Context) string

Возвращает токен сессии (пустую строку до коммита).

WriteSessionCookie (v2.3.0+)

func (s *SessionManager) WriteSessionCookie(ctx context.Context, w http.ResponseWriter, token string, expiry time.Time)

Низкоуровневый метод записи cookie (обычно используется через middleware).

Типы данных и интерфейсы SCS

Тип Status

type Status int

Статус представляет состояние данных сессии в течение цикла запроса.

Константы статусов:

const (
    // Unmodified - данные сессии не изменялись
    Unmodified Status = iota
    
    // Modified - данные были изменены
    Modified
    
    // Destroyed - сессия была уничтожена
    Destroyed
)

Использование:

status := sessionManager.Status(ctx)
switch status {
case scs.Unmodified:
    // Сессия не изменялась
case scs.Modified:
    // Требуется сохранение изменений  
case scs.Destroyed:
    // Сессия закрыта
}

Интерфейс Store

type Store interface {
    // Delete удаляет токен и данные сессии
    // Если токен не существует - операция игнорируется
    Delete(token string) error

    // Find ищет данные по токену сессии
    // Возвращает:
    // - данные (если найдены)
    // - флаг существования
    // - ошибку (только для системных сбоев)
    Find(token string) ([]byte, bool, error)

    // Commit сохраняет/обновляет данные сессии
    Commit(token string, data []byte, expiry time.Time) error
}

Базовый интерфейс для реализации хранилищ сессий.

Особенности реализации:

  1. Delete должен быть идемпотентным
  2. Find возвращает found=false для:
    • Несуществующих токенов
    • Истекших сессий
    • Поврежденных данных
  3. Commit должен перезаписывать существующие данные

Пример проверки сессии:

data, found, err := store.Find(token)
if err != nil {
    // Обработка системной ошибки
}
if !found {
    // Сессия не существует/истекла
}

Этот интерфейс позволяет интегрировать SCS с любыми системами хранения данных.