среда, 29 сентября 2021 г.

Пакет net/url в Golang

Пакет url анализирует URL-адреса и реализует экранирование запросов.

Функция PathEscape

func PathEscape(s string) string

PathEscape экранирует строку, чтобы ее можно было безопасно разместить внутри сегмента пути URL, заменяя при необходимости специальные символы (включая /) последовательностями %XX.

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

package main

import (
    "fmt"
    "net/url"
)

func main() {
    space := url.PathEscape("one two")
    fmt.Println(space)
    
    percent := url.PathEscape("10%")
    fmt.Println(percent)
    
    symbols := url.PathEscape(" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;")
    fmt.Println(symbols)
}

Вывод:

one%20two
10%25
%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B

Пример в песочнице https://play.golang.org/p/LNmUJmU8uaq

Функция PathUnescape

func PathUnescape(s string) (string, error)

PathUnescape выполняет обратное преобразование PathEscape, преобразуя каждую 3-байтовую закодированную подстроку формы "%AB" в шестнадцатеричный байт 0xAB. Он возвращает ошибку, если за любым % не следуют две шестнадцатеричные цифры.

PathUnescape идентичен QueryUnescape, за исключением того, что он не отменяет экранирование '+' на ' ' (пробел).

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

package main

import (
	"fmt"
	"net/url"
)

func main() {
	space, err := url.PathUnescape("one%20two")
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(space)
	
	percent, err := url.PathUnescape("10%25")
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(percent)
	
	symbols, err := url.PathUnescape("%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B")
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(symbols)
}

Вывод:

one two
10%
 ?&=#+%!<>#"{}|\^[]`☺	:/@$'()*,;

Пример в песочнице https://play.golang.org/p/RevOg4UxSG7

Функция QueryEscape

func QueryEscape(s string) string

QueryEscape экранирует строку, чтобы ее можно было безопасно поместить в URL-запрос.

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

package main

import (
    "fmt"
    "net/url"
)

func main() {
    space := url.QueryEscape("one two")
    fmt.Println(space)
    
    percent := url.QueryEscape("10%")
    fmt.Println(percent)
    
    symbols := url.QueryEscape(" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;")
    fmt.Println(symbols)
}

Вывод:

one+two
10%25
+%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A%2F%40%24%27%28%29%2A%2C%3B

Пример в песочнице https://play.golang.org/p/hfKAuFsV00i

Функция QueryUnescape

func QueryUnescape(s string) (string, error)

QueryUnescape выполняет обратное преобразование QueryEscape, преобразуя каждую 3-байтовую закодированную подстроку формы "%AB" в шестнадцатеричный байт 0xAB. Он возвращает ошибку, если за любым % не следуют две шестнадцатеричные цифры.

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

package main

import (
    "fmt"
    "net/url"
)

func main() {
    space, err := url.QueryUnescape("one+two")
    if err != nil {
        fmt.Println(err.Error())
    }
    fmt.Println(space)
    
    percent, err := url.QueryUnescape("10%25")
    if err != nil {
        fmt.Println(err.Error())
    }
    fmt.Println(percent)
    
    symbols, err := url.QueryUnescape("+%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A%2F%40%24%27%28%29%2A%2C%3B")
    if err != nil {
        fmt.Println(err.Error())
    }
    fmt.Println(symbols)
}

Вывод:

one two
10%
 ?&=#+%!<>#"{}|\^[]`☺	:/@$'()*,;

Пример в песочнице https://play.golang.org/p/cIDrkclH5Qa

Тип URL

type URL struct {
    Scheme      string
    Opaque      string    // закодированные непрозрачные данные
    User        *Userinfo // username и password информация
    Host        string    // host или host:port
    Path        string    // путь (в относительных путях может отсутствовать начальная косая черта)
    RawPath     string    // закодированный путь (EscapedPath)
    ForceQuery  bool      // добавить запрос ('?') даже если RawQuery пуст
    RawQuery    string    // закодированные значения запроса, без '?'
    Fragment    string    // фрагмент для ссылок, без '#'
    RawFragment string    // закодированный фрагмент (EscapedFragment)
}

URL представляет собой проанализированный URL-адрес (технически ссылка на URI).

Общая форма представленна как:

[scheme:][//[userinfo@]host][/]path[?query][#fragment]

URL-адреса, которые не начинаются с косой черты после схемы, интерпретируются как:

scheme:opaque[?query][#fragment]

Обратите внимание, что поле Path хранится в декодированной форме: /%47%6f%2f становится /Go/. Как следствие, невозможно определить, какие косые черты в пути были косыми чертами в необработанном URL, а какие были %2f. Это различие редко бывает важным, но когда это так, код должен использовать RawPath, необязательное поле, которое устанавливается только в том случае, если кодировка по умолчанию отличается от Path.

Метод URL String использует метод EscapedPath для получения пути.

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

package main

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

func main() {
    u, err := url.Parse("http://bing.com/search?q=dotnet")
    if err != nil {
        log.Fatal(err)
    }
    u.Scheme = "https"
    u.Host = "google.com"
    q := u.Query()
    q.Set("q", "golang")
    u.RawQuery = q.Encode()
    fmt.Println(u)
}

Вывод

https://google.com/search?q=golang

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

package main

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

func main() {
    // Parse + String сохраняют первоначальное кодирование.
    u, err := url.Parse("https://example.com/foo%2fbar")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.Path)
    fmt.Println(u.RawPath)
    fmt.Println(u.String())
}

Вывод

/foo/bar
/foo%2fbar
https://example.com/foo%2fbar

Функция Parse

func Parse(rawURL string) (*URL, error)

Parse анализирует необработанный URL-адрес в структуру URL.

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

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

package main

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

func main() {
    u, err := url.Parse("http://bing.com/search?q=dotnet")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Scheme: ", u.Scheme)
    fmt.Println("Host: ", u.Host)
    fmt.Println("Path: ", u.Path)
    fmt.Println("RawQuery: ", u.RawQuery)
}

Вывод

Scheme:  http
Host:  bing.com
Path:  /search
RawQuery:  q=dotnet

Пример в песочнице https://play.golang.org/p/o5gLj5_fteS

Функция ParseRequestURI

func ParseRequestURI(rawURL string) (*URL, error)

ParseRequestURI анализирует необработанный URL-адрес в структуру URL. Предполагается, что URL-адрес был получен в HTTP-запросе, поэтому URL-адрес интерпретируется только как абсолютный URI или абсолютный путь. Предполагается, что строковый url не имеет суффикса #fragment. (Веб-браузеры удаляют #fragment перед отправкой URL-адреса на веб-сервер.)

Метод EscapedFragment

func (u *URL) EscapedFragment() string

EscapedFragment возвращает экранированную форму u.Fragment. В общем, существует несколько возможных экранированных форм любого фрагмента. EscapedFragment возвращает u.RawFragment, если это допустимое экранирование u.Fragment. В противном случае EscapedFragment игнорирует u.RawFragment и самостоятельно вычисляет экранированную форму. Метод String использует EscapedFragment для построения своего результата. В общем, код должен вызывать EscapedFragment вместо непосредственного чтения u.RawFragment.

Пример использования метода EscapedFragment

package main

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

func main() {
    u, err := url.Parse("http://example.com/#x/y%2Fz")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Fragment:", u.Fragment)
    fmt.Println("RawFragment:", u.RawFragment)
    fmt.Println("EscapedFragment:", u.EscapedFragment())
}

Вывод

Fragment: x/y/z
RawFragment: x/y%2Fz
EscapedFragment: x/y%2Fz

Пример в песочнице https://play.golang.org/p/Q-I-8bfoXFO

Метод EscapedPath

func (u *URL) EscapedPath() string

EscapedPath возвращает экранированную форму u.Path. Как правило, у любого пути есть несколько возможных экранированных форм. EscapedPath возвращает u.RawPath, если это действительное экранирование u.Path. В противном случае EscapedPath игнорирует u.RawPath и вычисляет экранированную форму самостоятельно. Методы String и RequestURI используют EscapedPath для построения своих результатов. В общем, код должен вызывать EscapedPath вместо непосредственного чтения u.RawPath.

Пример использования метода EscapedPath

package main

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

func main() {
    u, err := url.Parse("http://example.com/x/y%2Fz")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Path:", u.Path)
    fmt.Println("RawPath:", u.RawPath)
    fmt.Println("EscapedPath:", u.EscapedPath())
}

Вывод

Path: /x/y/z
RawPath: /x/y%2Fz
EscapedPath: /x/y%2Fz

Пример в песочнице https://play.golang.org/p/xvc45jZkqq6

Метод Hostname

func (u *URL) Hostname() string

Hostname возвращает u.Host, удаляя любой допустимый номер порта, если он присутствует.

Если результат заключен в квадратные скобки, как буквальные адреса IPv6, квадратные скобки удаляются из результата.

Пример использования метода Hostname

package main

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

func main() {
    u, err := url.Parse("https://example.org:8000/path")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.Hostname())
    u, err = url.Parse("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:17000")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.Hostname())
}

Вывод

example.org
2001:0db8:85a3:0000:0000:8a2e:0370:7334

Пример в песочнице https://play.golang.org/p/0MV8vkqssmO

Метод IsAbs

func (u *URL) IsAbs() bool

IsAbs сообщает, является ли URL абсолютным. Абсолютное означает, что у него непустая схема.

Пример использования метода IsAbs

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u := url.URL{Host: "example.com", Path: "foo"}
    fmt.Println(u.IsAbs())
    u.Scheme = "http"
    fmt.Println(u.IsAbs())
}

Вывод

false
true

Пример в песочнице https://play.golang.org/p/UfOBOdHopyF

Метод MarshalBinary

func (u *URL) MarshalBinary() (text []byte, err error)

Преобразует URL в срез байтов, выдает ошибку если она возникает при преобразовании.

Пример использования метода MarshalBinary

package main

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

func main() {
    u, _ := url.Parse("https://example.org")
    b, err := u.MarshalBinary()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s\n", b)
}

Вывод

https://example.org

Пример в песочнице https://play.golang.org/p/L2uSp9WIN1Y

Метод Parse

func (u *URL) Parse(ref string) (*URL, error)

Parse анализирует URL-адрес в контексте получателя. Предоставленный URL-адрес может быть относительным или абсолютным. Parse возвращает nil, ошибку при сбое синтаксического анализа, в противном случае его возвращаемое значение такое же, как ResolveReference.

Пример использования метода Parse

package main

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

func main() {
    u, err := url.Parse("https://example.org")
    if err != nil {
        log.Fatal(err)
    }
    rel, err := u.Parse("/foo")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(rel)
    _, err = u.Parse(":foo")
    if _, ok := err.(*url.Error); !ok {
        log.Fatal(err)
    }
}

Вывод

https://example.org/foo

Пример в песочнице https://play.golang.org/p/lyPqaigN1pw

Метод Port

func (u *URL) Port() string

Port возвращает часть порта u.Host без начального двоеточия.

Если u.Host не содержит допустимого числового порта, Port возвращает пустую строку.

Пример использования метода Port

package main

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

func main() {
    u, err := url.Parse("https://example.org")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.Port())
    u, err = url.Parse("https://example.org:8080")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.Port())
}

Вывод

8080

Пример в песочнице https://play.golang.org/p/xcP1_ozPxfG

Метод Query

func (u *URL) Query() Values

Query анализирует RawQuery и возвращает соответствующие значения. Он молча отбрасывает искаженные пары значений. Для проверки ошибок используйте ParseQuery.

Пример использования метода Query

package main

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

func main() {
    u, err := url.Parse("https://example.org/?a=1&a=2&b=&=3&&&&")
    if err != nil {
        log.Fatal(err)
    }
    q := u.Query()
    fmt.Println(q["a"])
    fmt.Println(q.Get("b"))
    fmt.Println(q.Get(""))
}

Вывод

[1 2]

3

Пример в песочнице https://play.golang.org/p/MJrTerqSaqd

Метод Redacted

func (u *URL) Redacted() string

Redacted аналогично String, но заменяет любой пароль на "xxxxx". Удаляется только пароль в u.URL.

Пример использования метода Redacted

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u := &url.URL{
        Scheme: "https",
        User:   url.UserPassword("user", "password"),
        Host:   "example.com",
        Path:   "foo/bar",
    }
    fmt.Println(u.Redacted())
    u.User = url.UserPassword("me", "newerPassword")
    fmt.Println(u.Redacted())
}

Вывод

https://user:xxxxx@example.com/foo/bar
https://me:xxxxx@example.com/foo/bar

Пример в песочнице https://play.golang.org/p/FPsxk_4bbCV

Метод RequestURI

func (u *URL) RequestURI() string

RequestURI возвращает закодированный path?query или opaque?query строку, которая будет использоваться в HTTP-запросе для u.

Пример использования метода RequestURI

package main

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

func main() {
    u, err := url.Parse("https://example.org/path?foo=bar")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.RequestURI())
}

Вывод

/path?foo=bar

Пример в песочнице https://play.golang.org/p/QK-lScEgjou

Метод ResolveReference

func (u *URL) ResolveReference(ref *URL) *URL

ResolveReference разрешает ссылку URI на абсолютный URI из абсолютного базового URI u, согласно RFC 3986. Ссылка URI может быть относительной или абсолютной. ResolveReference всегда возвращает новый экземпляр URL, даже если возвращенный URL идентичен базовому или ref. Если ref является абсолютным URL, ResolveReference игнорирует base и возвращает копию ref.

Пример использования метода ResolveReference

package main

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

func main() {
    u, err := url.Parse("../../..//search?q=dotnet")
    if err != nil {
        log.Fatal(err)
    }
    base, err := url.Parse("http://example.com/directory/")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(base.ResolveReference(u))
}

Вывод

http://example.com/search?q=dotnet

Пример в песочнице https://play.golang.org/p/WGNUEkswSmY

Метод String

func (u *URL) String() string

String повторно собирает URL в допустимую строку URL-адреса. Общий вид результата - один из:

scheme:opaque?query#fragment
scheme://userinfo@host/path?query#fragment

Если u.Opaque не пусто, String использует первую форму; в противном случае используется вторая форма. Любые символы, отличные от ASCII, в host экранируются. Чтобы получить путь (path), String использует u.EscapedPath().

Во второй форме действуют следующие правила:

- если u.Scheme пуст, scheme: не отображается.
- если u.User равен nil, userinfo@ опускается.
- если u.Host пуст, host/ не указывается.
- если u.Scheme и u.Host пусты, а u.User - nil,
    вся часть scheme://userinfo@host/ опускается.
- если u.Host не пуст и u.Path начинается с /,
    форма host/path не добавляет свой /.
- если u.RawQuery пуст, ?query опускается.
- если u.Fragment пуст, #fragment опускается.

Пример использования метода String

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u := &url.URL{
        Scheme:   "https",
        User:     url.UserPassword("me", "pass"),
        Host:     "example.com",
        Path:     "foo/bar",
        RawQuery: "x=1&y=2",
        Fragment: "anchor",
    }
    fmt.Println(u.String())
    u.Opaque = "opaque"
    fmt.Println(u.String())
}

Вывод

https://me:pass@example.com/foo/bar?x=1&y=2#anchor
https:opaque?x=1&y=2#anchor

Пример в песочнице https://play.golang.org/p/trgWI4TlgAP

Метод UnmarshalBinary

func (u *URL) UnmarshalBinary(text []byte) error

Преобразует срез байтов и наполняет поля структуры URL, на которой был вызван. Возвращает ошибку в случае ее возникновения при анализе среза байтов.

Пример использования метода UnmarshalBinary

package main

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

func main() {
    u := &url.URL{}
    err := u.UnmarshalBinary([]byte("https://example.org/foo"))
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s\n", u)
}

Вывод

https://example.org/foo

Пример в песочнице https://play.golang.org/p/SyeYQ-6_Vy4

Тип Values

type Values map[string][]string

Values сопоставляет строковый ключ со списком значений. Обычно он используется для параметров запроса и значений формы. В отличие от карты http.Header, ключи в карте Values чувствительны к регистру.

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

package main

import (
    "fmt"
    "net/url"
)

func main() {
    v := url.Values{}
    v.Set("name", "Ava")
    v.Add("friend", "Jess")
    v.Add("friend", "Sarah")
    v.Add("friend", "Zoe")
    fmt.Println(v.Encode())
    fmt.Println(v.Get("name"))
    fmt.Println(v.Get("friend"))
    fmt.Println(v["friend"])
    fmt.Println(v.Has("name"))
    v.Del("name")
    fmt.Println(v.Encode())
}

Вывод

friend=Jess&friend=Sarah&friend=Zoe&name=Ava
Ava
Jess
[Jess Sarah Zoe]
true
friend=Jess&friend=Sarah&friend=Zoe

Пример в песочнице https://play.golang.org/p/9Gvyq0Rnlx7

Функция ParseQuery

func ParseQuery(query string) (Values, error)

ParseQuery анализирует строку запроса в кодировке URL и возвращает карту, в которой перечислены значения, указанные для каждого ключа. ParseQuery всегда возвращает ненулевую карту, содержащую все найденные допустимые параметры запроса; err описывает первую обнаруженную ошибку декодирования, если таковая имеется.

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

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

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/url"
    "strings"
)

func main() {
    m, err := url.ParseQuery(`x=1&y=2&y=3`)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(toJSON(m))
}

func toJSON(m interface{}) string {
    js, err := json.Marshal(m)
    if err != nil {
        log.Fatal(err)
    }
    return strings.ReplaceAll(string(js), ",", ", ")
}

Вывод

{"x":["1"], "y":["2", "3"]}

Пример в песочнице https://play.golang.org/p/3ytJDMl5P5y

Методы типа Values

func (v Values) Add(key, value string)

Add добавляет значение к ключу. Он добавляется к любым существующим значениям, связанным с ключом.

func (v Values) Del(key string)

Del удаляет значения, связанные с ключом.

func (v Values) Encode() string

Encode кодирует значения в форму "закодированный URL" ("bar=baz&foo=quux"), отсортированные по ключу.

func (v Values) Get(key string) string

Get получает первое значение, связанное с данным ключом. Если с ключом не связаны никакие значения, Get возвращает пустую строку. Чтобы получить доступ к нескольким значениям, используйте карту напрямую.

func (v Values) Has(key string) bool

Has проверяет, установлен ли данный ключ.

func (v Values) Set(key, value string)

Set устанавливает значение ключа. Он заменяет любые существующие значения.


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


суббота, 11 сентября 2021 г.

Разработка RESTful API с помощью Go и Gin

В этом посте представлены основы написания API RESTful веб-сервиса с помощью Go и Gin Web Framework (Gin).

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

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

В этом руководстве вы создадите RESTful API сервер с двумя конечными точками. Ваш примерный проект будет репозиторием данных о винтажных джазовых записях.

Предпосылки

1. Установка Go 1.16 или новее. Инструкции по установке см. Установка Go.

2. Инструмент для редактирования вашего кода. Любой текстовый редактор, который у вас есть, будет работать нормально.

3. Командный терминал. Go хорошо работает с любым терминалом в Linux и Mac, а также с PowerShell или cmd в Windows.

4. Инструмент curl. В Linux и Mac это уже должно быть установлено. В Windows он входит в состав Windows 10 . Для более ранних версий Windows может потребоваться его установка.

Проектирование конечных точек API

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

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

Вот конечные точки, которые вы создадите в этом руководстве.

/albums

  • GET - получить список всех альбомов в формате JSON.
  • POST - добавить новый альбом из данных запроса, отправленных в формате JSON.

/albums/:id

  • GET - получить альбом по его идентификатору, вернув данные альбома в формате JSON.

Далее вы создадите папку для своего кода.

Создайте папку для вашего кода

Для начала создайте проект для кода, который вы напишете.

1. Откройте командную строку и перейдите в свой домашний каталог.

В Linux или Mac:

$ cd

В Windows:

C:\> cd %HOMEPATH%

2. Используя командную строку, создайте каталог для вашего кода с именем web-service-gin.

$ mkdir web-service-gin
$ cd web-service-gin

3. Создайте модуль, в котором вы сможете управлять зависимостями.

Запустите команду go mod init, указав ей путь к модулю, в котором будет находиться ваш код.

$ go mod init example/web-service-gin
go: creating new go.mod: module example/web-service-gin

Эта команда создает файл go.mod, в котором будут перечислены добавленные вами зависимости для отслеживания. Дополнительные сведения об именовании модуля с помощью пути к модулю см. в посте Управление зависимостями.

Далее вы создадите структуры данных для обработки данных.

Создайте данные

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

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

Напишите код

Используя текстовый редактор, создайте файл с именем main.go в каталоге web-service-gin. Вы напишете свой код Go в этом файле.

В main.go в верхней части файла вставьте следующее объявление пакета.

package main

Автономная программа (в отличие от библиотеки) всегда находится в пакете main.

Под объявлением пакета вставьте следующее объявление структуры album. Вы будете использовать ее для хранения данных альбома в памяти.

Теги структуры, такие как json:"artist", определяют, каким должно быть имя поля, когда содержимое структуры сериализуется в JSON. Без них JSON использовал бы имена полей структуры с заглавной буквы - стиль, который не так распространен в JSON.

// album представляет данные об альбоме.
type album struct {
    ID     string  `json:"id"`
    Title  string  `json:"title"`
    Artist string  `json:"artist"`
    Price  float64 `json:"price"`
}

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

// Срез albums для заполнения данных об альбомах.
var albums = []album{
    {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

Далее вы напишете код для реализации вашей первой конечной точки.

Напишите обработчик для возврата всех товаров

Когда клиент делает запрос в GET /albums, вы хотите вернуть все альбомы в формате JSON.

Для этого вам нужно написать следующее:

  • Логика для подготовки ответа
  • Код для сопоставления пути запроса с вашей логикой

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

Напишите код

Под кодом структур, который вы добавили в предыдущем разделе, вставьте следующий код, чтобы получить список альбомов.

Эта функция getAlbums создает JSON из среза album структур, записывая JSON в ответ.

// getAlbums возвращает список всех альбомов в формате JSON.
func getAlbums(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, albums)
}

В этом коде вы:

  • Напишете функцию getAlbums, которая принимает параметр gin.Context. Обратите внимание, что вы могли дать этой функции любое имя - ни Gin, ни Go не требуют определенного формата имени функции.
    gin.Context - самая важная часть Gin. Он передает детали запроса, проверяет и сериализует JSON и многое другое. (Несмотря на похожее название, он отличается от встроенного в Go пакета context.)
  • Вызовете Context.IndentedJSON, чтобы сериализовать структуру в JSON и добавить ее в ответ.
    Первый аргумент функции - это код состояния HTTP, который вы хотите отправить клиенту. Здесь вы передаете константу StatusOK из пакета net/http, чтобы указать 200 OK.
    Обратите внимание, что вы можете заменить Context.IndentedJSON вызовом Context.JSON для отправки более компактного JSON. На практике с формой с отступом намного легче работать при отладке, и разница в размере обычно небольшая.

В верхней части main.go, сразу под объявлением среза albums, вставьте приведенный ниже код, чтобы назначить функцию-обработчик пути к конечной точке.

Это устанавливает связь, в которой getAlbums обрабатывает запросы к пути конечной точки /albums.

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)

    router.Run("localhost:8080")
}

В этом коде вы:

  • Инициализируете роутер Gin, используя Default.
  • Используете функцию GET, чтобы связать метод GET HTTP и путь /albums с функцией обработчика.
    Обратите внимание, что вы передаете имя функции getAlbums. Это отличается от передачи результата функции, которую вы бы сделали, передав getAlbums() (обратите внимание на круглые скобки).
  • Используйте функцию Run, чтобы подключить маршрутизатор к http.Server и запустить сервер.

В верхней части main.go, сразу под объявлением пакета, импортируйте пакеты, которые вам понадобятся для поддержки кода, который вы только что написали.

Первые строки кода должны выглядеть так:

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

Сохраните main.go.

Запустите код

Начните отслеживать модуль Gin как зависимость.

В командной строке используйте go get, чтобы добавить модуль github.com/gin-gonic/gin в качестве зависимости для вашего модуля. Используйте аргумент с точкой для обозначения "получить зависимости для кода в текущем каталоге".

$ go get .
go get: added github.com/gin-gonic/gin v1.7.2

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

Из командной строки в каталоге, содержащем main.go, запустите код. Используйте аргумент с точкой для обозначения "запустить код в текущем каталоге".

$ go run .

После запуска кода у вас есть работающий HTTP-сервер, на который вы можете отправлять запросы.

В новом окне командной строки используйте curl, чтобы сделать запрос к работающему веб-сервису.

$ curl http://localhost:8080/albums

Команда должна отображать данные, которыми вы заполнили сервис.

[
        {
                "id": "1",
                "title": "Blue Train",
                "artist": "John Coltrane",
                "price": 56.99
        },
        {
                "id": "2",
                "title": "Jeru",
                "artist": "Gerry Mulligan",
                "price": 17.99
        },
        {
                "id": "3",
                "title": "Sarah Vaughan and Clifford Brown",
                "artist": "Sarah Vaughan",
                "price": 39.99
        }
]

Вы запустили API! В следующем разделе вы создадите еще одну конечную точку с кодом для обработки POST запроса для добавления элемента.

Напишите обработчик для добавления нового элемента

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

Для этого вам нужно написать следующее:

  • Логика добавления нового альбома в существующий список.
  • Немного кода для маршрутизации запроса POST к вашей логике.

Напишите код

Добавьте код для добавления данных альбомов в список альбомов.

Где-нибудь после операторов импорта вставьте следующий код. (Конец файла - хорошее место для этого кода, но Go не обеспечивает соблюдение порядка, в котором вы объявляете функции.)

// postAlbums добавляет альбом из JSON, 
// полученного в теле запроса.
func postAlbums(c *gin.Context) {
    var newAlbum album

    // Вызов BindJSON для привязки 
    // полученного JSON к newAlbum
    if err := c.BindJSON(&newAlbum); err != nil {
        return
    }

    // Добавляем в срез новый альбом.
    albums = append(albums, newAlbum)
    c.IndentedJSON(http.StatusCreated, newAlbum)
}

В этом коде вы:

  • Используйте Context.BindJSON для привязки тела запроса к newAlbum.
  • Добавляете структуру album, инициализированную из JSON, в срез albums.
  • Добавляете в ответ код состояния 201 вместе с JSON, представляющим добавленный вами альбом.

Измените main функцию так, чтобы она включала функцию router.POST, как показано ниже.

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)
    router.POST("/albums", postAlbums)

    router.Run("localhost:8080")
}

В этом коде вы:

  • Связываете метод POST по пути /albums с функцией postAlbums.
    С помощью Gin вы можете связать обработчик с комбинацией HTTP-метода и пути. Таким образом, вы можете отдельно маршрутизировать запросы, отправленные по одному пути, в зависимости от метода, который использует клиент.

Запустите код

Если сервер все еще работает с последнего раздела, остановите его.

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .

Из другого окна командной строки используйте curl, чтобы сделать запрос к работающему веб-сервису.

$ curl http://localhost:8080/albums \
    --include \
    --header "Content-Type: application/json" \
    --request "POST" \
    --data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'

Команда должна отображать заголовки и JSON для добавленного альбома.

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Wed, 02 Jun 2021 00:34:12 GMT
Content-Length: 116

{
    "id": "4",
    "title": "The Modern Sound of Betty Carter",
    "artist": "Betty Carter",
    "price": 49.99
}

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

$ curl http://localhost:8080/albums \
    --header "Content-Type: application/json" \
    --request "GET"

Команда должна отобразить список альбомов.

[
        {
                "id": "1",
                "title": "Blue Train",
                "artist": "John Coltrane",
                "price": 56.99
        },
        {
                "id": "2",
                "title": "Jeru",
                "artist": "Gerry Mulligan",
                "price": 17.99
        },
        {
                "id": "3",
                "title": "Sarah Vaughan and Clifford Brown",
                "artist": "Sarah Vaughan",
                "price": 39.99
        },
        {
                "id": "4",
                "title": "The Modern Sound of Betty Carter",
                "artist": "Betty Carter",
                "price": 49.99
        }
]

В следующем разделе вы добавите код для обработки GET для определенного элемента.

Напишите обработчик для получения определенного элемента

Когда клиент делает запрос к GET /album/[id], вы хотите вернуть альбом, идентификатор которого совпадает с параметром пути id.

Для этого вы:

  • Добавите логику для получения запрошенного альбома.
  • Укажите путь к логике.

Напишите код

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

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

// getAlbumByID находит альбом, 
// значение идентификатора которого совпадает с 
// параметром id, отправленным клиентом,
// затем возвращает этот альбом в качестве ответа
func getAlbumByID(c *gin.Context) {
    id := c.Param("id")

    // Перебираем список альбомов в поисках альбома,
    // значение идентификатора которого соответствует параметру.
    for _, a := range albums {
        if a.ID == id {
            c.IndentedJSON(http.StatusOK, a)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}

В этом коде вы:

  • Используете Context.Param, чтобы получить параметр пути id из URL-адреса. Когда вы сопоставляете этот обработчик с путем, вы включаете в путь заполнитель для параметра.
  • Проходите в цикле по структурам album в срезе, ища ту, значение поля ID которой совпадает со значением параметра id. Если он найден, вы сериализуете эту album структуру в JSON и возвращаете ее в качестве ответа с HTTP-кодом 200 OK.
    Как упоминалось выше, реальный сервис, скорее всего, будет использовать запрос к базе данных для выполнения этого поиска.
  • Вернуть ошибку HTTP 404 с http.StatusNotFound, если альбом не найден.

Наконец, измените свой main так, чтобы он включал новый вызов router.GET, где теперь путь - /albums/:id, как показано в следующем примере.

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)
    router.GET("/albums/:id", getAlbumByID)
    router.POST("/albums", postAlbums)

    router.Run("localhost:8080")
}

В этом коде вы:

  • Связываете путь /albums/:id с функцией getAlbumByID. В Gin двоеточие перед элементом в пути означает, что этот элемент является параметром пути.

Запустите код

Если сервер все еще работает с последнего раздела, остановите его.

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

$ go run .

Из другого окна командной строки используйте curl, чтобы сделать запрос к работающему веб-сервису.

$ curl http://localhost:8080/albums/2

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

{
        "id": "2",
        "title": "Jeru",
        "artist": "Gerry Mulligan",
        "price": 17.99
}

Заключение

Вы только что использовали Go и Gin для написания простого RESTful веб-сервиса.

Итоговый код

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

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

// album представляет данные об альбоме.
type album struct {
    ID     string  `json:"id"`
    Title  string  `json:"title"`
    Artist string  `json:"artist"`
    Price  float64 `json:"price"`
}

// Срез albums для заполнения данных об альбомах.
var albums = []album{
    {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)
    router.GET("/albums/:id", getAlbumByID)
    router.POST("/albums", postAlbums)

    router.Run("localhost:8080")
}

// getAlbums возвращает список всех альбомов в формате JSON.
func getAlbums(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, albums)
}

// postAlbums добавляет альбом из JSON, полученный в теле запроса.
func postAlbums(c *gin.Context) {
    var newAlbum album

    // Вызов BindJSON для привязки 
    // полученного JSON к newAlbum
    if err := c.BindJSON(&newAlbum); err != nil {
        return
    }

    // Добавляем в срез новый альбом.
    albums = append(albums, newAlbum)
    c.IndentedJSON(http.StatusCreated, newAlbum)
}

// getAlbumByID находит альбом, 
// значение идентификатора которого совпадает с 
// параметром id, отправленным клиентом,
// затем возвращает этот альбом в качестве ответа
func getAlbumByID(c *gin.Context) {
    id := c.Param("id")

    // Перебираем список альбомов в поисках альбома,
    // значение идентификатора которого соответствует параметру.
    for _, a := range albums {
        if a.ID == id {
            c.IndentedJSON(http.StatusOK, a)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}


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


четверг, 9 сентября 2021 г.

Как избежать риска внедрения SQL-кода с пакетом database/sql в Golang

Вы можете избежать риска внедрения SQL-кода, указав значения параметров SQL в качестве аргументов функции пакета sql. Многие функции в пакете sql предоставляют параметры для оператора SQL и значений, которые будут использоваться в параметрах этого оператора (другие предоставляют параметр для подготовленного оператора и параметров).

Код в следующем примере использует символ ? символ в качестве заполнителя для параметра id, который предоставляется как аргумент функции:

// Правильный формат для выполнения оператора SQL с параметрами.
rows, err := db.Query("SELECT * FROM user WHERE id = ?", id)

Функции пакета sql, выполняющие операции с базой данных, создают подготовленные операторы из предоставленных вами аргументов. Во время выполнения пакет sql превращает инструкцию SQL в подготовленную инструкцию и отправляет ее вместе с отдельным параметром.

Примечание. Заполнители параметров различаются в зависимости от СУБД и используемого драйвера. Например, драйвер pq для Postgres принимает форму-заполнитель, такую ​​как $1 вместо ?.

У вас может возникнуть соблазн использовать функцию из пакета fmt для сборки оператора SQL в виде строки с включенными параметрами - например:

// SECURITY RISK!
rows, err := db.Query(fmt.Sprintf("SELECT * FROM user WHERE id = %s", id))

Это небезопасно! Когда вы это делаете, Go собирает весь оператор SQL, заменяя глагол формата %s значением параметра перед отправкой полного оператора в СУБД. Это создает риск внедрения SQL-кода, поскольку вызывающий код может отправить неожиданный фрагмент SQL в качестве аргумента id. Этот фрагмент может завершить инструкцию SQL непредсказуемым образом, что опасно для вашего приложения.

Например, передав определенное значение %s, вы можете получить что-то вроде следующего, которое может вернуть все пользовательские записи в вашей базе данных:

SELECT * FROM user WHERE id = 1 OR 1=1;


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


среда, 8 сентября 2021 г.

Управление подключениями к базе данных с пакетом database/sql в Golang

Для подавляющего большинства программ вам не нужно настраивать параметры пула соединений sql.DB по умолчанию. Но для некоторых продвинутых программ вам может потребоваться настроить параметры пула соединений или явно работать с соединениями. В этом посте объясняется, как это сделать.

Дескриптор базы данных sql.DB безопасен для одновременного использования несколькими горутинами (это означает, что дескриптор - это то, что другие языки могут назвать "потокобезопасным"). Некоторые другие библиотеки доступа к базе данных основаны на соединениях, которые могут использоваться только для одной операции за раз. Чтобы восполнить этот пробел, каждый sql.DB управляет пулом активных подключений к базе данных, создавая новые по мере необходимости для параллелизма в вашей программе Go.

Пул соединений подходит для большинства нужд доступа к данным. Когда вы вызываете метод sql.DB Query или Exec, реализация sql.DB извлекает доступное соединение из пула или, при необходимости, создает его. Пакет возвращает соединение с пулом, когда оно больше не нужно. Это поддерживает высокий уровень параллелизма для доступа к базе данных.

Настройка свойств пула соединений

Вы можете установить свойства, которые определяют, как пакет sql управляет пулом соединений. Чтобы получить статистику о влиянии этих свойств, используйте DB.Stats.

Установка максимального количества открытых подключений

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

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

Установка максимального количества неактивных подключений

DB.SetMaxIdleConns изменяет ограничение на максимальное количество неактивных соединений, поддерживаемых sql.DB.

Когда операция SQL завершается с заданным подключением к базе данных, она обычно не завершается немедленно: приложение может вскоре снова потребоваться, и поддержание открытого подключения позволяет избежать повторного подключения к базе данных для следующей операции. По умолчанию sql.DB поддерживает два незанятых соединения в любой момент времени. Повышение предела позволяет избежать частых повторных подключений в программах со значительным параллелизмом.

Установка максимального количества времени, в течение которого соединение может простаивать

DB.SetConnMaxIdleTime устанавливает максимальное время простоя соединения перед его закрытием. Это приводит к тому, что sql.DB закрывает соединения, которые не использовались дольше заданного времени.

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

Установка максимального срока службы подключений

Использование DB.SetConnMaxLifetime устанавливает максимальное время, в течение которого соединение может оставаться открытым перед закрытием.

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

Использование выделенных соединений

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

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

Для других случаев использования, когда последовательность отдельных операций должна выполняться в одном и том же соединении, пакет sql предоставляет выделенные соединения. DB.Conn получает выделенное соединение sql.Conn. В sql.Conn есть методы BeginTx, ExecContext, PingContext, PrepareContext, QueryContext и QueryRowContext, которые ведут себя как эквивалентные методы в БД, но используют только выделенное соединение. По завершении выделенного соединения ваш код должен освободить его с помощью Conn.Close.


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


Купить gopher

вторник, 7 сентября 2021 г.

Отмена незавершенных операций с базой данных в Golang

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

Например, вы можете захотеть:

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

Многие API-интерфейсы для разработчиков Go включают методы, которые принимают аргумент Context, что упрощает использование контекста во всем приложении.

Отмена операций с базой данных после таймаута

Вы можете использовать контекст, чтобы установить таймаут или крайний срок, по истечении которого операция будет отменена. Чтобы получить контекст с таймаутом или крайним сроком, вызовите context.WithTimeout или context.WithDeadline.

Код в следующем примере таймаута извлекает контекст и передает его в метод sql.DB QueryContext.

func QueryWithTimeout(ctx context.Context) {
    // Создаем Context с таймаутом.
    queryCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    // Передаем Context с таймаутом с запросом.
    rows, err := db.QueryContext(queryCtx, "SELECT * FROM album")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    // Обрабатываем возвращенные строки.
}

Когда один контекст является производным от внешнего контекста, поскольку queryCtx является производным от ctx в этом примере, если внешний контекст отменяется, то производный контекст также автоматически отменяется. Например, в HTTP-серверах метод http.Request.Context возвращает контекст, связанный с запросом. Этот контекст отменяется, если HTTP-клиент отключает или отменяет HTTP-запрос (возможно в HTTP/2). Передача контекста HTTP-запроса в QueryWithTimeout выше приведет к преждевременной остановке запроса к базе данных либо в том случае, если общий HTTP-запрос был отменен, либо если запрос занял более пяти секунд.

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


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


Купить gopher

понедельник, 6 сентября 2021 г.

Выполнение транзакций с пакетом database/sql в Golang

Вы можете выполнять транзакции базы данных, используя sql.Tx, который представляет транзакцию. Помимо методов Commit и Rollback, представляющих семантику конкретной транзакции, sql.Tx имеет все методы, которые вы можете использовать для выполнения общих операций с базой данных. Чтобы получить sql.Tx, вызовите DB.Begin или DB.BeginTx.

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

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

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

Начните транзакцию.

DB.Begin или DB.BeginTx начинают новую транзакцию базы данных, возвращая sql.Tx, который ее представляет.

Выполните операции с базой данных.

Используя sql.Tx, вы можете запрашивать или обновлять базу данных в серии операций, использующих одно соединение. Для поддержки этого Tx экспортирует следующие методы:

  • Exec и ExecContext для внесения изменений в базу данных с помощью операторов SQL, таких как INSERT, UPDATE и DELETE.
  • Query, QueryContext, QueryRow и QueryRowContext для операций, возвращающих строки.
  • Prepare, PrepareContext, Stmt и StmtContext для предварительного определения подготовленных операторов.

Завершите транзакцию одним из следующих способов:

  • Зафиксируйте транзакцию с помощью Tx.Commit.
    Если фиксация завершается успешно (возвращает nil ошибку), то все результаты запроса подтверждаются как действительные, и все выполненные обновления применяются к базе данных как одно атомарное изменение. В случае сбоя фиксации все результаты Query и Exec на Tx должны быть отклонены как недопустимые.
  • Откатите транзакцию с помощью Tx.Rollback.
    Даже если Tx.Rollback завершится неудачно, транзакция больше не будет действительной и не будет зафиксирована в базе данных.

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

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

  • Используйте API, описанные в этом разделе, для управления транзакциями. Не используйте операторы SQL, связанные с транзакциями, такие как BEGIN и COMMIT, напрямую - это может привести к непредсказуемому состоянию вашей базы данных, особенно в конкурентных программах.
  • При использовании транзакции старайтесь не вызывать напрямую методы sql.DB, не связанные с транзакцией, так как они будут выполняться вне транзакции, давая вашему коду непоследовательное представление о состоянии базы данных или даже вызывая взаимоблокировки.

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

Код в следующем примере использует транзакцию для создания нового заказа клиента на альбом. В ходе этого код будет:

  • Начинать транзакцию.
  • Откладывать (defer) откат транзакции. Если транзакция завершится успешно, она будет зафиксирована до выхода из функции, что сделает отложенный откатный вызов бездействующим. Если транзакция завершится неудачно, она не будет зафиксирована, а это означает, что откат будет вызываться при выходе из функции.
  • Убедится, что для альбома, который заказывает клиент, имеется достаточное количество штук.
  • Если товара достаточно, обновит запас, уменьшив его на количество заказанных альбомов.
  • Создаст новый заказ и получит сгенерированный идентификатор нового заказа для клиента.
  • Зафиксирует транзакцию и вернет идентификатор.

В этом примере используются методы Tx, которые принимают аргумент context.Context. Это позволяет отменить выполнение функции, включая операции с базой данных, если она выполняется слишком долго или закрывается клиентское соединение.

// CreateOrder создает заказ для альбома 
// и возвращает идентификатор нового заказа.
func CreateOrder(ctx context.Context, albumID, quantity, custID int) (orderID int64, err error) {

    // Создаем вспомогательную функцию 
    // для подготовки результатов сбоя.
    fail := func(err error) (int64, error) {
        return fmt.Errorf("CreateOrder: %v", err)
    }

    // Получаем Tx для выполнения запросов на транзакцию.
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return fail(err)
    }
    // Откладываем откат на случай, если что-то не удастся.
    defer tx.Rollback()

    // Подтверждаем, что количество копий альбома 
    // достаточно для заказа.
    var enough bool
    if err = tx.QueryRowContext(ctx, "SELECT (quantity >= ?) from album where id = ?",
        quantity, albumID).Scan(&enough); err != nil {
        if err == sql.ErrNoRows {
            return fail(fmt.Errorf("no such album"))
        }
        return fail(err)
    }
    if !enough {
        return fail(fmt.Errorf("not enough inventory"))
    }

    // Обновляем запас альбома, чтобы удалить количество в заказе.
    _, err = tx.ExecContext(ctx, "UPDATE album SET quantity = quantity - ? WHERE id = ?",
        quantity, albumID)
    if err != nil {
        return fail(err)
    }

    // Создаем новую строку в таблице album_order.
    result, err := tx.ExecContext(ctx, "INSERT INTO album_order (album_id, cust_id, quantity, date) VALUES (?, ?, ?, ?)",
        albumID, custID, quantity, time.Now())
    if err != nil {
        return fail(err)
    }
    // Получаем идентификатор только что созданной позиции заказа.
    orderID, err := result.LastInsertId()
    if err != nil {
        return fail(err)
    }

    // Фиксируем транзакцию.
    if err = tx.Commit(); err != nil {
        return fail(err)
    }

    // Возвращаем идентификатор заказа.
    return orderID, nil
}


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


Купить gopher

воскресенье, 5 сентября 2021 г.

Использование подготовленных операторов с пакетом database/sql в Golang

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

Примечание. Заполнители параметров в подготовленных операторах различаются в зависимости от СУБД и драйвера, который вы используете. Например, драйвер pq для Postgres требует заполнителя типа $1 вместо ?.

Что такое подготовленный оператор?

Подготовленный оператор - это SQL, который анализируется и сохраняется СУБД, обычно содержащий заполнители, но без фактических значений параметров. Позже оператор может быть выполнен с набором значений параметров.

Как использовать подготовленный оператор

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

В следующем примере создается подготовленный оператор, который выбирает конкретный альбом из базы данных. DB.Prepare возвращает sql.Stmt, представляющий подготовленный оператор для данного текста SQL. Вы можете передать параметры для оператора SQL в Stmt.Exec, Stmt.QueryRow или Stmt.Query для запуска оператора.

// AlbumByID получает указанный альбом.
func AlbumByID(id int) (Album, error) {
    // Определяем подготовленный оператор. 
    // Обычно стоит определять оператор
    // в другом месте и сохранять его 
    // для использования в таких функциях, как эта.
    stmt, err := db.Prepare("SELECT * FROM album WHERE id = ?")
    if err != nil {
        log.Fatal(err)
    }

    var album Album

    // Выполняем подготовленный оператор, 
    // передавая значение id для параметра, 
    // заполнитель которого - ?
    err := stmt.QueryRow(id).Scan(&album.ID, &album.Title, 
        &album.Artist, &album.Price, &album.Quantity)
    if err != nil {
        if err == sql.ErrNoRows {
            // Обработка случая отсутствия возвращенных строк.
        }
        return album, err
    }
    return album, nil
}

Поведение подготовленного оператора

Подготовленный sql.Stmt предоставляет обычные методы Exec, QueryRow и Query для вызова оператора.

Однако, поскольку sql.Stmt уже представляет предустановленный оператор SQL, его методы Exec, QueryRow и Query принимают только значения параметров SQL, соответствующие заполнителям, опуская текст SQL.

Вы можете определить новый sql.Stmt по-разному, в зависимости от того, как вы его будете использовать.

  • DB.Prepare и DB.PrepareContext создают подготовленный оператор, который может выполняться изолированно, вне транзакции, как это делают DB.Exec и DB.Query.
  • Tx.Prepare, Tx.PrepareContext, Tx.Stmt и Tx.StmtContext создают подготовленный оператор для использования в конкретной транзакции. Prepare и PrepareContext используют текст SQL для определения оператора. Stmt и StmtContext используют результат DB.Prepare или DB.PrepareContext. То есть они преобразуют sql.Stmt не для транзакций в sql.Stmt для этой транзакции.
  • Conn.PrepareContext создает подготовленный оператор из sql.Conn, который представляет зарезервированное соединение.

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

Функции для создания подготовленного оператора

DB.Prepare, DB.PrepareContext - Подготовливает оператор для изолированного выполнения, иначе он будет преобразован в подготовленный оператор внутри транзакции с помощью Tx.Stmt.

Tx.Prepare, Tx.PrepareContext, Tx.Stmt, Tx.StmtContext - Подготавливает оператор для использования в конкретной транзакции.

Conn.PrepareContext - Для использования с зарезервированными соединениями.


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


Купить gopher

суббота, 4 сентября 2021 г.

Запрос данных из базы данных с пакетом database/sql в Golang

При выполнении оператора SQL, который возвращает данные, используйте один из Query методов, предоставленных в пакете database/sql. Каждый из них возвращает Row или Rows, данные которых можно скопировать в переменные с помощью Scan метода. Вы можете использовать эти методы, например, для выполнения операторов SELECT.

При выполнении оператора, который не возвращает данные, вместо этого можно использовать метод Exec или ExecContext.

Пакет database/sql предоставляет два способа выполнить запрос, возвращающий данные.

  • Запрос одной строки - QueryRow возвращает не более одной строки из базы данных.
  • Запрос нескольких строк - Query возвращает все соответствующие запросу строки в виде структуры Rows, которую ваш код может пройти в цикле.

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

Внимание: не используйте функции форматирования строк, такие как fmt.Sprintf, для сборки оператора SQL! Вы можете создать риск внедрения SQL-кода.

Запрос одной строки

QueryRow извлекает не более одной строки базы данных, например, когда вы хотите найти данные по уникальному идентификатору. Если запрос возвращает несколько строк, метод Scan отбрасывает все, кроме первой.

QueryRowContext работает как QueryRow, но с аргументом context.Context.

В следующем примере используется запрос, чтобы узнать, присуствует ли в наличии достаточное количество товара для совершения покупки. Оператор SQL возвращает true, если товара достаточно, и false, если нет. Row.Scan копирует логическое возвращаемое значение в переменную enough через указатель.

func canPurchase(id int, quantity int) (bool, error) {
    var enough bool
    // Запрос значения на основе одной строки.
    if err := db.QueryRow("SELECT (quantity >= ?) from album where id = ?",
        quantity, id).Scan(&enough); err != nil {
        if err == sql.ErrNoRows {
            return false, fmt.Errorf("canPurchase %d: unknown album", id)
        }
        return false, fmt.Errorf("canPurchase %d: %v", id)
    }
    return enough, nil
}

Примечание. Заполнители параметров в подготовленных операторах различаются в зависимости от используемой СУБД и драйвера. Например, драйвер pq для Postgres требует заполнителя типа $1 вместо ?.

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

Сам QueryRow не возвращает ошибок. Вместо этого Scan сообщает о любых ошибках при комбинированном поиске и сканировании. Он возвращает sql.ErrNoRows, если запрос не находит строк.

Функции для возврата одной строки

DB.QueryRow, DB.QueryRowContext - Выполнение однострочного запроса изолированно.

Tx.QueryRow, Tx.QueryRowContext - Выполнение однострочного запроса внутри более крупной транзакции.

Stmt.QueryRow, Stmt.QueryRowContext - Выполнить однострочный запрос с использованием уже подготовленного оператора.

Conn.QueryRowContext - Для использования с зарезервированными соединениями.

Запрос нескольких строк

Вы можете запросить несколько строк с помощью Query или QueryContext, которые возвращают Rows, представляющие результаты запроса. Ваш код выполняет итерацию по возвращенным строкам, используя Rows.Next. Каждая итерация вызывает Scan для копирования значений столбцов в переменные.

QueryContext работает как Query, но с аргументом context.Context.

В следующем примере выполняется запрос для возврата альбомов указанного исполнителя. Альбомы возвращаются в виде sql.Rows. Код использует Rows.Scan для копирования значений столбцов в переменные, представленные указателями.

func albumsByArtist(artist string) ([]Album, error) {
    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    // Срез альбомов для хранения данных из возвращенных строк.
    var albums []Album

    // Цикл по строкам, 
    // используя Scan для назначения данных столбца полям структуры.
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist,
            &alb.Price, &alb.Quantity); err != nil {
            return albums, err
        }
        albums = append(albums, album)
    }
    if err = rows.Err(); err != nil {
        return albums, err
    }
    return albums, nil
}

Обратите внимание на отложенный вызов rows.Close. Он освобождает любые ресурсы, удерживаемые rows, независимо от того, что функция возвращает. Цикл по всем строкам также неявно закрывает rows, но лучше использовать defer, чтобы убедиться, что rows закрыты, несмотря ни на что.

Примечание. Заполнители параметров в подготовленных операторах различаются в зависимости от СУБД и драйвера, который вы используете. Например, драйвер pq для Postgres требует заполнителя типа $1 вместо ?.

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

Обязательно проверьте наличие ошибки в sql.Rows проходя в цикле по результатам запроса. Если запрос не удался, ваш код узнает об этом именно так.

Функции для возврата нескольких строк

DB.Query, DB.QueryContext - Выполнение запроса изолированно.

Tx.Query, Tx.QueryContext - Выполнить запрос внутри более крупной транзакции.

Stmt.Query, Stmt.QueryContext - Выполнить запрос, используя уже подготовленный оператор.

Conn.QueryContext - Для использования с зарезервированными соединениями.

Обработка значений столбца, допускающего значение NULL

Пакет database/sql предоставляет несколько специальных типов, которые вы можете использовать в качестве аргументов для функции Scan, когда значение столбца может быть null. Каждый из них включает поле Valid, которое сообщает, является ли значение ненулевым, и поле, содержащее значение, если это так.

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

var s sql.NullString
err := db.QueryRow("SELECT name FROM customer WHERE id = ?", id).Scan(&s)
if err != nil {
    log.Fatal(err)
}

// Находим имя клиента, используя заполнитель, если он отсутствует.
name := "Valued Customer"
if s.Valid {
    name = s.String
}

Вот некоторые типы, предназначенные для полей, имеющих null значения:

  • NullBool
  • NullFloat64
  • NullInt32
  • NullInt64
  • NullString
  • NullTime

Получение данных из столбцов

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

Существует базовый набор преобразований данных, поддерживаемых всеми драйверами, например преобразование SQL INT в Go int.

Как и следовало ожидать, Scan преобразует типы столбцов в похожие типы Go. Например, Scan преобразует из SQL CHAR, VARCHAR и TEXT в строку Go. Однако Scan также выполнит преобразование в другой тип Go, который хорошо подходит для значения столбца. Например, если столбец представляет собой VARCHAR, который всегда будет содержать число, вы можете указать числовой тип Go, например int, для получения значения, и Scan преобразует его с помощью strconv.Atoi за вас.

Обработка нескольких наборов результатов

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

Rows.NextResultSet подготавливает следующий набор результатов, так что вызов Rows.Next извлекает первую строку из этого следующего набора. Он возвращает логическое значение, указывающее, существует ли вообще следующий набор результатов.

Код в следующем примере использует DB.Query для выполнения двух операторов SQL. Первый набор результатов получается из первого запроса в процедуре, извлекающего все строки в таблице album. Следующий набор результатов - из второго запроса, извлекающего строки из таблицы song.

rows, err := db.Query("SELECT * from album; SELECT * from song;")
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

// Проходим в цикле по первому набору результатов.
for rows.Next() {
    // Обрабатываем набор результатов.
}

// Переход к следующему набору результатов.
rows.NextResultSet()

// Проходим в цикле по второму набору результатов.
for rows.Next() {
    // Обрабатываем второй набор результатов.
}

// Проверяем наличие ошибок в любом наборе результатов.
if err := rows.Err(); err != nil {
    log.Fatal(err)
}


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


Купить gopher

четверг, 2 сентября 2021 г.

Выполнение операторов SQL, которые не возвращают данные, с пакетом database/sql в Golang

Когда вы выполняете действия с базой данных, которые не возвращают данные, используйте метод Exec или ExecContext из пакета database/sql. Операторы SQL, которые вы выполняете таким образом, включают INSERT, DELETE и UPDATE.

Если ваш запрос может возвращать строки, используйте вместо этого метод Query или QueryContext.

Метод ExecContext работает так же, как метод Exec, но с дополнительным аргументом context.Context.

Код в следующем примере использует DB.Exec для выполнения инструкции по добавлению нового альбома в таблицу album.

func AddAlbum(alb Album) (int64, error) {
    result, err := db.Exec("INSERT INTO album (title, artist) VALUES (?, ?)", alb.Title, alb.Artist)
    if err != nil {
        return 0, fmt.Errorf("AddAlbum: %v", err)
    }

    // Получить сгенерированный идентификатор нового альбома для клиента.
    id, err := result.LastInsertId()
    if err != nil {
        return 0, fmt.Errorf("AddAlbum: %v", err)
    }
    // Возвращаем ID нового альбома.
    return id, nil
}

DB.Exec возвращает значения: sql.Result и ошибку. Когда ошибка равна nil, вы можете использовать Result для получения идентификатора последнего вставленного элемента (как в примере) или для получения количества строк, затронутых операцией.

Примечание. Заполнители параметров в подготовленных операторах различаются в зависимости от СУБД и драйвера, который вы используете. Например, драйвер pq для Postgres требует заполнителя типа $1 вместо ?.

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

Внимание: не используйте функции форматирования строк, такие как fmt.Sprintf, для сборки оператора SQL! Вы можете создать риск внедрения SQL-кода (SQL-injection).

Функции для выполнения операторов SQL, которые не возвращают строки

DB.Exec, DB.ExecContext - Выполнение одного оператора SQL изолированно.

Tx.Exec, Tx.ExecContext - Выполнение инструкции SQL в более крупной транзакции.

Stmt.Exec, Stmt.ExecContext - Выполнение уже подготовленного оператора SQL.

Conn.ExecContext - Для использования с зарезервированными соединениями.


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


Купить gopher

среда, 1 сентября 2021 г.

Открытие дескриптора базы данных с Golang

Пакет database/sql упрощает доступ к базе данных, уменьшая потребность в управлении соединениями. В отличие от многих API доступа к данным, с database/sql вы не открываете соединение явно, выполняете работу, а затем закрываете соединение. Вместо этого ваш код открывает дескриптор базы данных, который представляет пул соединений, а затем выполняет операции доступа к данным с помощью этого дескриптора, вызывая метод Close только тогда, когда это необходимо для освобождения ресурсов, таких как те, которые удерживаются извлеченными строками или подготовленным оператором.

Другими словами, это дескриптор базы данных, представленный sql.DB, обрабатывает соединения, открывая и закрывая их от имени вашего кода. Поскольку ваш код использует дескриптор для выполнения операций с базой данных, эти операции имеют конкурентный доступ к базе данных.

В дополнение к API, доступным в пакете database/sql, сообщество Go разработало драйверы для всех наиболее распространенных (и многих необычных) систем управления базами данных (СУБД).

При открытии дескриптора базы данных вы выполняете следующие высокоуровневые шаги:

  1. Находите драйвер.
    Драйвер переводит запросы и ответы между вашим кодом Go и базой данных.
  2. Открываете дескриптор базы данных.
    После того, как вы импортировали драйвер, вы можете открыть дескриптор конкретной базы данных.
  3. Подтверждаете соединение.
    После того, как вы открыли дескриптор базы данных, ваш код может проверить, доступно ли соединение.

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

Поиск и импорт драйвера базы данных

Вам понадобится драйвер базы данных, поддерживающий СУБД, которую вы используете. Чтобы найти драйвер для своей базы данных, см. SQLDrivers.

Чтобы сделать драйвер доступным для вашего кода, вы импортируете его, как любой другой пакет Go. Например:

import "github.com/go-sql-driver/mysql"

Обратите внимание, что если вы не вызываете какие-либо функции непосредственно из пакета драйвера - например, когда он неявно используется пакетом sql - вам нужно будет использовать пустой импорт с префиксом пути импорта с подчеркиванием:

import _ "github.com/go-sql-driver/mysql"

Примечание. Рекомендуется избегать использования собственного API драйвера базы данных для операций с базой данных. Вместо этого используйте функции из пакета database/sql. Это поможет сохранить слабую связанность вашего кода с СУБД, облегчая при необходимости переключение на другую СУБД.

Открытие дескриптора базы данных

Дескриптор базы данных sql.DB обеспечивает возможность чтения и записи в базу данных по отдельности или в транзакции.

Вы можете получить дескриптор базы данных, вызвав либо sql.Open (который принимает строку подключения), либо sql.OpenDB (который принимает driver.Connector). Оба возвращают указатель на sql.DB.

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

Открытие с помощью строки подключения

Используйте функцию sql.Open, если вы хотите подключиться с помощью строки подключения. Формат строки зависит от используемого драйвера.

Вот пример для MySQL:

db, err = sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/jazzrecords")
if err != nil {
    log.Fatal(err)
}

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

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

// Указываем параметры подключения.
cfg := mysql.Config{
    User:   username,
    Passwd: password,
    Net:    "tcp",
    Addr:   "127.0.0.1:3306",
    DBName: "jazzrecords",
}

// Получаем дескриптор базы данных.
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
    log.Fatal(err)
}

Открытие с помощью коннектора

Используйте функцию sql.OpenDB, если вы хотите воспользоваться специфичными для драйвера функциями подключения, которые недоступны в строке подключения. Каждый драйвер поддерживает свой собственный набор параметров подключения, часто предоставляя способы настройки запроса подключения, специфичного для СУБД.

Адаптировав предыдущий пример sql.Open для использования sql.OpenDB, вы можете создать дескриптор с таким кодом, как следующий:

// Указываем параметры подключения.
cfg := mysql.Config{
    User:   username,
    Passwd: password,
    Net:    "tcp",
    Addr:   "127.0.0.1:3306",
    DBName: "jazzrecords",
}

// Получаем коннектор для конкретного драйвера.
connector, err := mysql.NewConnector(&cfg)
if err != nil {
    log.Fatal(err)
}

// Получаем дескриптор базы данных.
db = sql.OpenDB(connector)

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

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

Подтверждение подключения

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

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

db, err = sql.Open("mysql", connString)

// Подтверждаем успешное соединение.
if err := db.Ping(); err != nil {
    log.Fatal(err)
}

Хранение учетных данных базы данных

Избегайте хранения учетных данных базы данных в исходном Go коде, так как это может открыть доступ к содержимому вашей базы данных другим пользователям. Вместо этого найдите способ хранить их за пределами вашего кода, но доступными для него. Например, рассмотрим приложение-хранитель секретов, которое хранит учетные данные и предоставляет API, который ваш код может использовать для получения учетных данных для аутентификации в вашей СУБД.

Один из популярных подходов - хранить секреты в среде до запуска программы, возможно, загруженные из секретного менеджера, а затем ваша Go программа может прочитать их с помощью os.Getenv:

username := os.Getenv("DB_USER")
password := os.Getenv("DB_PASS")

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

Освобождение ресурсов

Хотя вы не управляете и не закрываете соединения явно с помощью пакета database/sql, ваш код должен освобождать ресурсы, которые он получил, когда они больше не нужны. Они могут включать ресурсы, удерживаемые sql.Rows, представляющие данные, возвращенные из запроса, или sql.Stmt, представляющие подготовленный оператор.

Обычно вы закрываете ресурсы, откладывая вызов функции Close, чтобы ресурсы высвобождались до выхода из включающей функции.

Код в следующем примере откладывает Close, чтобы освободить ресурс, удерживаемый sql.Rows.

rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

// Цикл по возвращенным rows.


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


Купить gopher