# Go lang

# Доступ к K8S CRD из go-клиента

Kubernetes API сервер легко расширяется с помощью Custom Resource Defenition. Однако, доступ к этом ресурсу из популярных библиотек go-клиентов сложна и плохо задокументированна. Эта статья содержит маленькую инструкцию как получить доступ к такому ресурсу из вашего кода Go. 

# Цель
Я пришел к этой задаче, когда хотел синтегрировать внешнее хранилище в кубернетес кластер. План был использовать CRD, что бы определять резервы систем хранения данных. Потом, самодельный оператор может слушать все эти ресурсы чтобы создавать и удалять и управлять текущим состоянием этих ресурсов.

# Определим и создадим CRD
Для этой статьи будем работать над простым примером: CRD может быть леко создан используя `kubectl` для этого примера, мы начнем с одиночного простого определения ресурса:
```yaml
apiVersion: "apiextensions.k8s.io/v1beta1"
kind: "CustomResourceDefinition"
metadata:
  name: "projects.example.martin-helmich.de"
spec:
  group: "example.martin-helmich.de"
  version: "v1alpha1"
  scope: "Namespaced"
  names:
    plural: "projects"
    singular: "project"
    kind: "Project"
  validation:
    openAPIV3Schema:
      required: ["spec"]
      properties:
        spec:
          required: ["replicas"]
          properties:
            replicas:
              type: "integer"
              minimum: 1
```
Для определения CRD, нам понадобиться озаботиться об API Group Name( в этом случае, `example.martin-helmich.de`). По соглашению, это обычно доменное, которым вы владеете(например домер организации), чтобы предотвратить конфликты наименования. CRD именя обычно выглядят так: `<plural-resource-name>.<api-group-name>`, в нашем примере: `projects.example.martin-helmich.de`.

Так же, будьте внимательны когда выбираете версию CRD(`spec.version` в примере выше). Пока еще рабоатает над CRD, то будет хорошей идей поместить CRD в группу alpha версии API. Для пользователей вашего самодельного ресурса, это будет значить, что что-то может измениться.

Часто, нужно проверить что данные которые хранит пользователень в вашем CRD содержит опредленные схемы. За это отвечает `spec.validation.openAPIV3Schema`. Она содержит JSON схему которая описывает формат который должны иметь CRD.

После сохранения CRD в файл, применим его в кластере:

```bash
> kubectl apply -f projects-crd.yaml
customresourcedefinition "projects.example.martin-helmich.de" created
```

После создания CRD вы можете создать объект этого типа. Работает это так же как с обычными Kubernetes Объектами(pods, deploymens и так далее). Отличается только `kind` и `apiVersion`:

```yaml
apiVersion: "example.martin-helmich.de/v1alpha1"
kind: "Project"
metadata:
  name: "example-project"
  namespace: "default"
spec:
  replicas: 1
```
Можно создать CRD как любой другой объект через `kubectl`
```bash
> kubectl apply -f project.yaml
project "example-project" created
```
Можно даже использовать `kubectl` чтобы получить самодельный ресурс обратно из K8S. 
```bash
> kubectl get projects
NAME               AGE
example-project    2m
```
# Cоздание Golang клиента
Теперь, будем использовать пакет go-клиента, для доступа к этим CRD. Для примера, я буду считать, что мы работаем над Go проектом, с названием `github.com/martin-helmich/kubernetes-crd-example`(репозиторий существует) и у него есть go-клиент и apimachinery установленну библиотеку в качестве модуля Go.
```bash
go mod init github.com/martin-helmich/kubernetes-crd-example
go get k8s.io/client-go@v0.17.0
go get k8s.io/apimachinery@v0.17.0
```
	Множество документаций работая с CRD предполагают, что вы работаете с некоторым типом генерации кода, чтобы собрать клиентскую библиотеку автоматически. Однако, этот процесс задокументирован редко, и после прочтения нескольких ненавистных дискуссий на github, создалось впечатление, что всё ещё в "прогрессе". В общем, я встял в самостоятельную реализацую клиентат.


## Шаг 1: Определение типов
Начав с определения типов для самодельного ресурса. Я нашел, что это хорошая практика, организовывать эти типа группируя по версии API. Для примера, можно создать файл `api/types/v1alpha1/project.go` содержащий следующее:
```go
package v1alpha1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

type ProjectSpec struct {
    Replicas int `json:"replicas"`
}

type Project struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec ProjectSpec `json:"spec"`
}

type ProjectList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`

	Items []Project `json:"items"`
}
```
Тип `metav1.ObjectMeta` содержит типичные свойства метадаты, которые вы можете найти в любом K8s ресурсе.


## Шаг 2: Определим метод DeepCopy
Каждый тип, который будет обслуживаться K8S API(в нашем случае, Project и ProjectList) требует реализацию `k8s.io/apimachinery/pkg/runtime.Object` интерфейса. Этот интерфейс определяет 2 метода `GetObjectKind()` и `DeepCopyObject()`. Первый метод уже предоставлен встронной структурой `metav1.TypeMeta`, второй нужно реализовать самостоятельно. 

Метод `DeepCopyObject` предназначен для создания полной копии объекта. Так как это требует шаблонного кода, этот метод часто генерируется автоматически. Для этой статьи мы сделаем это ручками. Продолжим с добавления второго файла `deepcopy.go` в тот же покет:
```go
package v1alpha1

import "k8s.io/apimachinery/pkg/runtime"

// DeepCopyInto copies all properties of this object into another object of the
// same type that is provided as a pointer.
func (in *Project) DeepCopyInto(out *Project) {
    out.TypeMeta = in.TypeMeta
    out.ObjectMeta = in.ObjectMeta
    out.Spec = ProjectSpec{
        Replicas: in.Spec.Replicas,
    }
}

// DeepCopyObject returns a generically typed copy of an object
func (in *Project) DeepCopyObject() runtime.Object {
    out := Project{}
    in.DeepCopyInto(&out)

    return &out
}

// DeepCopyObject returns a generically typed copy of an object
func (in *ProjectList) DeepCopyObject() runtime.Object {
    out := ProjectList{}
    out.TypeMeta = in.TypeMeta
    out.ListMeta = in.ListMeta

    if in.Items != nil {
        out.Items = make([]Project, len(in.Items))
        for i := range in.Items {
            in.Items[i].DeepCopyInto(&out.Items[i])
        }
    }

    return &out
}
```
## Интерлюдия: Автоматическое создание DeepCopy метода 
Так, мы могли заметить, что определение всех этих различных DeepCopy методов вовсе не веселое занятие. Есть множество различных инструментов и фреймворков около автогенерации этих методов(все зависит от уровня документации и в целом зрелости). То что нашел я, работает отлично в инструменте `controller-gen`, что является частью файмеворка `Kuberbuilder`:
```bash
$ go get -u github.com/kubernetes-sigs/controller-tools/cmd/controller-gen
```
Чтобы использовать `controller-gen`, опишите ваш CRD тип через `+k8s:deepcopy-gen annotation`
```go
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type Project struct {
    // ...
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ProjectList struct {
    // ...
}
```
Затем, выполните команду, для автоматического создания метода deepcopy
```bash
controller-gen object paths=./api/types/v1alpha1/project.go
``` 

Можно еще проще, вы можете добавить `go:generate` выражение в целый файл:
```go
package v1alpha1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

//go:generate controller-gen object paths=$GOFILE

// ...
```
И чтобы сгенерировать код, нужно выполнить команду в корневой папке:
```bash
go generate ./... 
```

## Шаг 3: Зарегистрируем типы на схеме компоновщика 
Теперь, нам нужно сделать новый тип известным для библиотеки клиента. Это позволить клиенту(более или менее) автоматически обрабатывать ваши новые типы после подключения к API серверу.

Для этого, добавим новый файл `register.go` в наш пакет:
```go
package v1alpha1

import (
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/schema"
)

const GroupName = "example.martin-helmich.de"
const GroupVersion = "v1alpha1"

var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion}

var (
    SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
    AddToScheme   = SchemeBuilder.AddToScheme
)

func addKnownTypes(scheme *runtime.Scheme) error {
    scheme.AddKnownTypes(SchemeGroupVersion,
        &Project{},
        &ProjectList{},
    )

    metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
    return nil
}
```
Как можно заметить, этот код не делает что-то реальное, пока еще(за исключением создания нового `runtime.SchemeBuilder`). Важная часть в том. что `AddToScheme` функция(строка 16)б которая экспортирует членов структуры созданых с типом `runtime.SchemeBuilder` в строке 15. Вы можете вызвать эту функцию позже, из любой части вашего клиентского кода как только клиент K8S будет готов к регистрации вашего определенного типа. 

## Шаг 4: создание HTTP клиента
После определения типов и добавления метода для регистрации их в глобальной схеме компоновщика, вы можете создат HTTP клиента, который может загружать ваши собственные ресурсы.

Для этого, добавим следующий код в ваш `main.go` вашего пакета
```go
package main

import (
    "flag"
    "log"
    "time"

    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/apimachinery/pkg/runtime/serializer"

    "github.com/martin-helmich/kubernetes-crd-example/api/types/v1alpha1"
    "k8s.io/client-go/kubernetes/scheme"
    "k8s.io/client-go/rest"
    "k8s.io/client-go/tools/clientcmd"
)

var kubeconfig string

func init() {
    flag.StringVar(&kubeconfig, "kubeconfig", "", "path to Kubernetes config file")
    flag.Parse()
}

func main() {
    var config *rest.Config
    var err error

    if kubeconfig == "" {
        log.Printf("using in-cluster configuration")
        config, err = rest.InClusterConfig()
    } else {
        log.Printf("using configuration from '%s'", kubeconfig)
        config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
    }

    if err != nil {
        panic(err)
    }

    v1alpha1.AddToScheme(scheme.Scheme)

    crdConfig := *config
    crdConfig.ContentConfig.GroupVersion = &schema.GroupVersion{Group: v1alpha1.GroupName, Version: v1alpha1.GroupVersion}
    crdConfig.APIPath = "/apis"
    crdConfig.NegotiatedSerializer = serializer.NewCodecFactory(scheme.Scheme)
    crdConfig.UserAgent = rest.DefaultKubernetesUserAgent()

    exampleRestClient, err := rest.UnversionedRESTClientFor(&crdConfig)
    if err != nil {
        panic(err)
    }
}
```
Теперь можно использовать `exampleRestClient` созданный в строке 48, для запроса всех самостоятельных ресурсов внутри `example.martin-helmich.de/v1alpha1` API группы. Пример может выглядить сдедующим образом: 
```go
result := v1alpha1.ProjectList{}
err := exampleRestClient.
    Get().
    Resource("projects").
    Do().
    Into(&result)
```
Чтобы использовать API типобезопасным способом, обычно хорошая идея обернуть эти операции внутри своего клиентского набора. Для этого, создаём новый подпакет `clientset/v1alpha1`. Для начала, реализуем интерфейс который определяет типы для вашей группы API. И Переносим настройки конфигурации из вашего главного метода в эту функцию коснтуктора клиенсткого набора(`NewForConfig` для примера ниже):
```bash
package v1alpha1

import (
    "github.com/martin-helmich/kubernetes-crd-example/api/types/v1alpha1"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/apimachinery/pkg/runtime/serializer"
    "k8s.io/client-go/kubernetes/scheme"
    "k8s.io/client-go/rest"
)

type ExampleV1Alpha1Interface interface {
    Projects(namespace string) ProjectInterface 
}

type ExampleV1Alpha1Client struct {
    restClient rest.Interface
}

func NewForConfig(c *rest.Config) (*ExampleV1Alpha1Client, error) {
    config := *c
    config.ContentConfig.GroupVersion = &schema.GroupVersion{Group: v1alpha1.GroupName, Version: v1alpha1.GroupVersion}
    config.APIPath = "/apis"
    config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
    config.UserAgent = rest.DefaultKubernetesUserAgent()

    client, err := rest.RESTClientFor(&config)
    if err != nil {
        return nil, err
    }

    return &ExampleV1Alpha1Client{restClient: client}, nil
}

func (c *ExampleV1Alpha1Client) Projects(namespace string) ProjectInterface {
    return &projectClient{
        restClient: c.restClient,
        ns: namespace,
    }
}
```
Код ниже, всё еще, не будет компилироваться, так как в нем всё еще отсутствуют `ProjectInterface` и `projectClient` типы. Мы сейчас до них доберемся.

`ExampleV1Alpha1Interface` и его реализация, `ExampleV1Alpha1Client` структура это главная точка входа для доступа к самодельным ресурсам. Вы можете легко создать новый клиентский набор в вашем `main.go`, просто вызывая `clientset, err := v1alpha1.NewForConfig(config)`.

Дальше, вам нужно реализовать определенный клиентский набор для доступа к самодельному ресурсу `Project`(пример выше уже использует `ProjectInterface` и `projectClient` типы которые всё еще нужно поддерживать). Создадим втрой файл в том же пакете `prjects.go`:
```go
package v1alpha1

import (
    "github.com/martin-helmich/kubernetes-crd-example/api/types/v1alpha1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/watch"
    "k8s.io/client-go/kubernetes/scheme"
    "k8s.io/client-go/rest"
)

type ProjectInterface interface {
    List(opts metav1.ListOptions) (*v1alpha1.ProjectList, error)
    Get(name string, options metav1.GetOptions) (*v1alpha1.Project, error)
    Create(*v1alpha1.Project) (*v1alpha1.Project, error)
    Watch(opts metav1.ListOptions) (watch.Interface, error)
    // ...
}

type projectClient struct {
    restClient rest.Interface
    ns         string
}

func (c *projectClient) List(opts metav1.ListOptions) (*v1alpha1.ProjectList, error) {
    result := v1alpha1.ProjectList{}
    err := c.restClient.
        Get().
        Namespace(c.ns).
        Resource("projects").
        VersionedParams(&opts, scheme.ParameterCodec).
        Do().
        Into(&result)

    return &result, err
}

func (c *projectClient) Get(name string, opts metav1.GetOptions) (*v1alpha1.Project, error) {
    result := v1alpha1.Project{}
    err := c.restClient.
        Get().
        Namespace(c.ns).
        Resource("projects").
        Name(name).
        VersionedParams(&opts, scheme.ParameterCodec).
        Do().
        Into(&result)

    return &result, err
}

func (c *projectClient) Create(project *v1alpha1.Project) (*v1alpha1.Project, error) {
    result := v1alpha1.Project{}
    err := c.restClient.
        Post().
        Namespace(c.ns).
        Resource("projects").
        Body(project).
        Do().
        Into(&result)

    return &result, err
}

func (c *projectClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
    opts.Watch = true
    return c.restClient.
        Get().
        Namespace(c.ns).
        Resource("projects").
        VersionedParams(&opts, scheme.ParameterCodec).
        Watch()
}
```
Этот клиент очевидно еще не закончен и не имеет методы типа `Delete`, `Update` и другие. Однако, это можно реализовать похожим на существующий метод образом. Посмотрите на существующий клиенсткий набор(для примера `Pod client set`) для вдохновения.

После создания вашего клиентского набора и используя его, вывести список существующих ресурсов становится доволно легко. 
```go
import clientV1alpha1 "github.com/martin-helmich/kubernetes-crd-example/clientset/v1alpha1"
// ...

func main() {
    // ...

    clientSet, err := clientV1alpha1.NewForConfig(config)
    if err != nil {
        panic(err)
    }

    projects, err := clientSet.Projects("default").List(metav1.ListOptions{})
    if err != nil {
        panic(err)
    }

    fmt.Printf("projects found: %+v\n", projects)
}
```
## Шаг 5: Созадем оповещятель
При создании оператора Kubernetes. Вы обычно хотите иметь возможность реагировать на вновь созданные или обновленные ресурсы. В теории, вы можете просто переодически вызывать `List()` метод и проверять добавленны ли новые ресусры. На практике, это не оптимальное решение, особенно когда у вас есть множество подобных ресурсов. 

Большинство операторов работает изначала загрузив все актуальные экземпляры ресурсов используя начальный  вызов `List()`, и затем подписываясь на обновления с помощью `Watch()` вызова. Начальный список объетов и обновления полученные от `Watch()` далее используются для создания локального кэша, что позволяет иметь быстрый доступ к любому самодельному ресурсу без надобности хождения к API серверу каждый раз. 

Этот шаблон широко распространнен, что библиотеки go-клиентов предлагают готовое решение: пакет k8s.io/client-go/tools/cache.  Вы можете создать новый оповещатель для ваших ресурсов:
```go
package main

import (
    "time"

    "github.com/martin-helmich/kubernetes-crd-example/api/types/v1alpha1"
    client_v1alpha1 "github.com/martin-helmich/kubernetes-crd-example/clientset/v1alpha1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/util/wait"
    "k8s.io/apimachinery/pkg/watch"
    "k8s.io/client-go/tools/cache"
)

func WatchResources(clientSet client_v1alpha1.ExampleV1Alpha1Interface) cache.Store {
    projectStore, projectController := cache.NewInformer(
        &cache.ListWatch{
            ListFunc: func(lo metav1.ListOptions) (result runtime.Object, err error) {
                return clientSet.Projects("some-namespace").List(lo)
            },
            WatchFunc: func(lo metav1.ListOptions) (watch.Interface, error) {
                return clientSet.Projects("some-namespace").Watch(lo)
            },
        },
        &v1alpha1.Project{},
        1*time.Minute,
        cache.ResourceEventHandlerFuncs{},
    )

    go projectController.Run(wait.NeverStop)
    return projectStore
}
```
Метод NewInformer возвращает два объекта: второй - значение, `controller` - управляет `List()` и `Watch()` вызывает и наполняет первое значение, храня некоторое количество кэшированных ресурсов с API сервера(в нашем случае CRD).


Теперь можно использовать хранилище, для легкого доступа к вашему CRD, либо слушая их или иметь доступ к ним по именам. Помните, что функции хранения возвращаяют `interface` типа, поэтому вам нужно будет самостоятельно приводить их к CRD типам.
```go
store := WatchResource(clientSet)

project := store.GetByKey("some-namespace/some-project").(*v1alpha1.Project)
```
# Вывод
Создание клиентов для CRD - это что-то что мало задокументированно(на данный момент) и подчас может быть довольно сложным. 

Клиентская библиотека для CRD, что показана в статье, вместе с оповещятелем это отличный старт для создания вашего собственного K8S оператора который реагирует на изменения который делают CRD.

# Как мокать? Go способ.

[![](https://notepad.gasick.ru/uploads/images/gallery/2022-01/scaled-1680-/image-1642406073578.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-01/image-1642406073578.png)

У Go есть встроенный фреймворк тестирования предоставленный `testing` пакетом, это позволяет писать тесты проще, но тогда как мы пишем более сложные тесты которые требуют моков?
В этой статье, мы изучим как взять преимущества структур и интерфейсов в Go, чтобы смокать любой сервис или библиотеку которую используем, без использования сторонних инструментов и библиотек.
Начнем с определения нашей системы для понимания того. что мы будем делать с тестом и моком.

# Система.
[![](https://notepad.gasick.ru/uploads/images/gallery/2022-01/scaled-1680-/image-1642406082469.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-01/image-1642406082469.png)

Наша система имеет 2 компонента:
* "Наш сервис" который у нас есть и мы создаем
* "Сторонние сервисы и библиотеки" которые взаимодействуют с базой данных и мы используем для работоспособности нашего сервиса.
Теперь так как мы строим "Наш сервис", мы хотим написать независимую единицу для "Нашего сервиса" но так как мы используем функционал стороннего сервиса или библиотеки в нашем сервисе, если мы тестируем без моков, мы будет производить интеграционное тестирование, что иногда сложно и более временно затратно.
Для демонстрации, мы напишем простую библиотеку которая проверяет существует ли пользователь внутри map. Наш сервис будет нужен для хранения бизнес логики, и это станет сторонней библиотекой внутри нашей стистемы.
# Код сторонней библиотеки
```go
package userdb

// db act as a dummy package level database.
var db map[string]bool

// init initialize a dummy db with some data
func init() {
	db = make(map[string]bool)
	db["ankuranand@dummy.org"] = true
	db["anand@example.com"] = true
}

// UserExists check if the User is registered with the provided email.
func UserExists(email string) bool {
	if _, ok := db[email]; !ok {
		return false
	}
	return true
}
```
# Сервис
Регистрация пользователя использует сторонний код, для проверки наличия пользователя. Если пользователь не существует, то сервис просто возвращает ошибку,и в обратном случае выполняет обычную логику.
```go
package simpleservice

import (
	"fmt"
	"log"

	"github.com/ankur-anand/mocking-demo/userdb"
)

// User encapsulate a user in the system.
type User struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	UserName string `json:"user_name"`
}

// RegisterUser will register a User if only User has not been previously
// registered.
func RegisterUser(user User) error {
	// check if user is already registered
	found := userdb.UserExists(user.Email)
	if found {
		return fmt.Errorf("email '%s' already registered", user.Email)
	}
	// carry business logic and Register the user in the system
	log.Println(user)
	return nil
}
```

```go
package simpleservice

import "testing"

func TestCheckUserExist(t *testing.T) {
	user := User{
		Name:     "Ankur Anand",
		Email:    "anand@example.com",
		UserName: "anand",
	}

	err := RegisterUser(user)
	if err == nil {
		t.Error("Expected Register User to throw and error got nil")
	}
}
```
Если мы посмотрим на функцию `theRegisterUser`. Она вызывает Функцию `userdb.UserExist`которая является для нас стороннеё библиотекой и мы не можем протестировать нашу `RegisterUser` функцию  без её вызова.

# Моки
Попробуем это исправить моками.

	Мок-объекты соответствуют требованиям к интерфейсу и заменяют более сложные настоящие объекты.

## Мок-объекты соответствуют требованиям к интерфейсу.
Для этого нам нужно переделать код нашего сервиса. Первое, мы должны определить требования интерфейса, для того чтобы реализовать наш мок. В нашем случае, нам нужен интерфейс который внутренний для пакета который предоставляет тестирование существует пользователь или нет.
```go
// registrationPreChecker validates if user is allowed to register.
type registrationPreChecker interface {
    userExists(string) bool
}
```
## Реализация интерфейса.
`userExists`  функция нашего нового интерфейса просто оборачивает  вызова настоящего вызова к стороннему сервису.

```go
type regPreCheck struct {}
func (r regPreCheck) userExists(email string) bool {
    return userdb.UserExist(email)
}
```
Теперь создадим, переменную на уровне пакета типа `registrationPreChecker` и присвоим экземпляр `regPreCheck` внутри `init` функции.
```go
var regPreCond registrationPreChecker

func init() {
   regPreCond = regPreCheck{}
}
```
Так как `regPreCond` является типом `registrationPreChecker` который проверяет существования пользователя, мы можем использовать `RegisterUser` фнукцию. Поэтому вместо  прямого вызова функции `userdb.UserExist` внутри функции `RegisterUser` мы вызовем через реализацию интерфейса.
```go
// check if user is already registered
found := regPreCond.userExist(user.Email)
```
Изменный код:

```go
package unitservice

import (
	"fmt"
	"log"

	"github.com/ankur-anand/mocking-demo/userdb"
)

// User encapsulate a user in the system.
type User struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	UserName string `json:"user_name"`
}

// Mock objects meet the interface requirements of,
// and stand in for, more complex real ones
type registrationPreChecker interface {
	userExists(string) bool
}

type regPreCheck struct{}

func (r regPreCheck) userExists(email string) bool {
	return userdb.UserExist(email)
}

var regPreCond registrationPreChecker

func init() {
	regPreCond = regPreCheck{}
}

// RegisterUser will register a User if only User has not been previously
// registered.
func RegisterUser(user User) error {
	// check if user is already registered
	found := regPreCond.userExist(user.Email)
	if found {
		return fmt.Errorf("email '%s' already registered", user.Email)
	}
	// carry business logic and Register the user in the system
	log.Println(user)
	return nil
}
```

Если мы запустим test опять он пройет так как мы не трогали поведение нашей функции. Теперь давайте посмотрим как заставить наше тестирование проходить с помощью моков.
# Написание моков
Для начала посмотрим на полный код:
```go
package unitservice

import "testing"

// This helps in assigning mock at the runtime instead of compile time
var userExistsMock func(email string) bool

type preCheckMock struct{}

func (u preCheckMock) userExists(email string) bool {
	return userExistsMock(email)
}

func TestRegisterUser(t *testing.T) {
	user := User{
		Name:     "Ankur Anand",
		Email:    "anand@example.com",
		UserName: "anand",
	}

	regPreCond = preCheckMock{}
	userExistsMock = func(email string) bool {
		return false
	}

	err := RegisterUser(user)
	if err != nil {
		t.Fatal(err)
	}

	userExistsMock = func(email string) bool {
		return true
	}
	err = RegisterUser(user)
	if err == nil {
		t.Error("Expected Register User to throw and error got nil")
	}
}
```
Мок объекты отвечают требованиям интерфейса.
Тут наш мок объект реализует `registrationPreChecker` интерфейс.

```go
type preCheckMock struct{}

func (u preCheckMock) userExists(email string) bool {
   return userExistsMock(email)
}
```
Реализация мока возвращает `userExistMock` тип функции вместо прямого возвращения `true` или `false`. Это помогает в назначении мока во время работы вместо во время компиляции.  Вы можете увидеть это в  `TestRegisterUser` фукнции.
##  Их заменяют более сложные настоящие объекты
```go
regPreCond = preCheckMock{}
```
Мы просто назначили нашему `regPreCond` тип `registrationPreChecker`, который проверяет есть ли пользователь или нет в нашей реализации мока во времы выполнения теста. Это можно увидеть в `TestRegisterUser` функции.
```go
func TestRegisterUser(t *testing.T) {
   user := User{
      Name:     "Ankur Anand",
      Email:    "anand@example.com",
      UserName: "anand",
   }

   regPreCond = preCheckMock{}
   userExistsMock = func(email string) bool {
      return false
   }

   err := RegisterUser(user)
   if err != nil {
      t.Fatal(err)
   }

   userExistsMock = func(email string) bool {
      return true
   }
   err = RegisterUser(user)
   if err == nil {
      t.Error("Expected Register User to throw and error got nil")
   }
}
```
# Но мы еще не закончиил!
Мы сказали что будет делать через "Способ GO", да, но пока есть проблема, для этого нужно провести рефакторинг.

Мы использовали глобальную переменную и события, которые мы не обновляем переменную во время реального выполнения, это должно сломать паралельный тест.
Есть несколько способов, чтобы это исправить.  В нашем случае, мы собираемся передать `registrationPreCheker` как зависимость нашей функции и представим новую функцию конструктора которая будет создавать по умолчанию `regitrationPreCheck` тип, который может быть использован во время настоящей работы, и так как мы передаем его как зависимость, мы можем передать нашу реализацию мок как параметр, в этом случае.
```go
func NewRegistrationPreChecker() RegistrationPreChecker {
   return regPreCheck{}
}

// RegisterUser will register a User if only User has not been previously
// registered.
func RegisterUser(user User, regPreCond RegistrationPreChecker) error {
   // check if user is already registered
   found := regPreCond.userExists(user.Email)
   if found {
      return fmt.Errorf("email '%s' already registered", user.Email)
   }
   // carry business logic and Register the user in the system
   log.Println(user)
   return nil
}
```
Let’s modify our test code too. So instead of modifying the package level variable, we are now explicitly passing it as a dependency inside our `RegisterUser` function.
```go
func TestRegisterUser(t *testing.T) {
   user := User{
      Name:     "Ankur Anand",
      Email:    "anand@example.com",
      UserName: "anand",
   }

   regPreCond := preCheckMock{}
   userExistsMock = func(email string) bool {
      return false
   }

   err := RegisterUser(user, regPreCond)
   if err != nil {
      t.Fatal(err)
   }

   userExistsMock = func(email string) bool {
      return true
   }
   err = RegisterUser(user, regPreCond)
   if err == nil {
      t.Error("Expected Register User to throw and error got nil")
   }
}
```

### Выводы
Чтож. так как этоо не единственный способ написания моков в GO, но с этим я надуюсь у вас появиться общее понимание моков и как использовать структуры и интерфейсы в GO чтобы мокать любые артефакты которые вам понадобятся, без внешних блиблиотек.

# Злоупотребление синтаксисом Go для создания DSL(предметно-ориентированного языка)

Go частый выбор для создания внутреностей высокопроизводительных систем, но так он так же разработан с некоторым количеством функций которые хороши для создания высокоуровневых абстракций. Вам не нужно переключаться на динамические языки такие как Ruby или Python, чтобы получить удовольствие от API или объяснительный синтакс.

Довольно часто для выражения API используется DSL(Предметно ориентированный язык). DSL это язык внутри языка который компилируется или интерпритируется внутри языка, в нашем случае Go. Если API хорошо спроектирован, DSL выглядит как его собственная спецификация специально созданная для конкретной задачи. DSL такие как CSS, SQL созданы как отдельный язык с их собственными анализаторами, но на данный момент мы сосредоточимся на одном, который построим с помощью компилятора Go, и используем внутри Go кода.

DSL используется для инфраструктурной автоматизации, модель объявления данных, построения запросов и тонны других приложений. Он может быть приятен для написания DSL для задач типа "подключил и настроил" потому, что они предлагают декларативный синтакс. Чаще чем инперативное описание, вся логика и операции нужны чтобы создать у приложения определенное состояние. DSL позволяет вам объявить желаемое состояние,параметры этого состояния и их реализацию ниже, всего лишь в шаге друг от друга. Конечный код тоже будет старатся выглядеть проще для понимания.

Мы собираемся посмотреть на то, как построить API, которое будет понятно для Go комплиятора, но выглядеть оно будет как отдельный язык. Название этой статьи выбрано потому, что мы собираемся нарушить дух Go только слегка, чтобы поправить букву закона так как нам хочется. Я надеюсь вы собираетесь быть рассудительны при применении сомнительных практик обсуждаемых далее. Я так же надеюсь изучая их, вы будете вдохновлены думать креативно о том, как писать выразительные Go API с которыми будет приятно работать и легко понимать.

# Пример использования.

Мы собираемся построить просто DSL для создания HTTP посредника. Эта область отличный кандидат для DSL потому что оно наполнено общими, хорошо понимаемыми и часто переиспользуемыми шаблонами как ограничени доступа, частого ограничения, обработки сессии и так далее. Лучше всего для читалей и писателей кода который реализует эти шаблоны, если код будет читаться как декларативный файл настроек, чем как императивное изобретение колеса.

# Личные типы.

Один из тонких но мощных моментов написания Go кода с образным чувством личности типов. Большую часть времени когда мы объявляем свой тип в Go, мы объявляем структуру или интерфейс. Мы так же можем объявить новый личный тип для существующих, ссылаясь на них с именем которое мы выбрали.

Это может быть чем-то простым как создание нового имени простопу типу, такому как `string` для названий хостов:
```go
type Host string
```
Мы также можем создать тип для набора:
```go
type HostList []Host
type HostSet map[Host]interface{}
```

Теперь где нибудь в нашем коде, переменная типа `HostList` будет `[]Host`, или на самом деле `[]string` под капотом, но с более понятным имененм.

Выгоду от таких типов, кроме как внешнего вида и сохраненных нажатий клавиш, это то что эти типы могут быть расширены с помощью их собственных типов. На пример:
```go
func (s HostSet) Add(n Host) {
	s[n] = struct{}{}
}

func (s HostSet) Remove(n Host) {
	delete(s, n)
}

func (s HostSet) Contains(n Host) bool {
	_, found := s[n]
	return found
}
```

Как мы можем использовать `HostSet` как если бы это был более сложная структура контейнера доступная через методы:
```go
func main() {
	s := make(HostSet)
	s.Add("golang.org")
	s.Add("google.com")
	s.Add("gopheracademy.org")
	s.Remove("google.com")

	hostnames := HostList{
		"golang.org",
		"google.com",
		"gopheracademy.org",
	}
	for _, n := range hostnames {
		fmt.Printf("%s? %v\n", n, s.Contains(n))
	}
}
```

Вот что мы получим на выходе:
```bash
golang.org? true
google.com? false
gopheracademy.org? true
```
Что мы тут имеем? Мы созадли абстракцию над простой мапой, мы можем использовать её как набор - у нее есть `Add` и  `Remove` операции, и у нее есть проверка `Contains` - созданные нами обороты речи, которые могут быть использованны нами в коде. Он лучше изолирован, чем простая передача `map[string]interface{}` и надеясь что значение "набор имен хостов"  соблюдается при доступе к map. Это решение более гибкое и явное, чем скажем:
```go
func SetContains(s map[string]interface{}, hostname string) bool {
	_, found := s[hostname]
	return found
}

func main() {
	s := make(map[string]interface{})
	if SetContains(s, hostname) {
        // do stuff
	}
}
```

Поэкспериментируем чуток с созданием новых типов, частично для различных типов срезов и мапов, и даже для каналов. Какие обороты вы можете создать чтобы заставить работать эти типы проще и понятнее?

# Высокоуровневые функции.

Go включает некоторые идеи из функционального программирования которое бесценно для создания выразительных и декларативных API. Go предлагает возможность присваивать функции переменных, для передачи функций как аргумент в другую функцию и для создания анонимных функций и закрытий. Используя высокоуровневые функции которые создают, изменяют, или строят поведение других функций, вы можете легко объединять кусочки логики  и функциональности во что-то более сложное целое используя несколько выражений, чаще чем повторение или создание клубка условной логики.

Давайте построим наш пример выше по-новому. Добавим метод в  `HostList` который принимает функцию как входной параметр и возвращает новый `HostList`:
```go
func (l HostList) Select(f func(Host) bool) HostList {
	result := make(HostList, 0, len(l))
	for _, h := range l {
		if f(h) {
			result = append(result, h)
		}
	}
	return result
}
```

Этот метод `HostList` имеет эффективность создания нового `HostList` для которого предоставленное условие (func f) верно. Создадим простое выражение функции для вставки в `f`:
```go
// import “strings” 
func IsDotOrg(h Host) bool {
	return strings.HasSuffix(string(h), ".org")
}
```
И используем его в наше новом методе `HostList`
```go
myHosts := HostList{"golang.org", "google.com", "gopheracademy.org"}
fmt.Printf("%v\n", myHosts.Select(IsDotOrg))
```
Вывод будет таким:
```bash
[golang.org gopheracademy.org]
```

`Select` возвращает только те элементы `myHosts` для которых переданная функция `IsDotOrg` будет возвращать `true`, то есть для тех имен у которых есть ".org".

`func(Host) bool` немного кривовата как параметричечкий тип и создает подпись метода `Select` сложно для чтения, поэтому давайте используем наш трюк для типов, чтобы сделать его аккуратным.
```go
type HostFilter func(Host) bool
```

Он делает `Select` более читаемым:
```go
func (l HostList) Select(f HostFilter) HostList {
        //...
}
```

и добавляет выгоду с которой мы можем объявить некоторые методы `HostFilter`:
```go
func (f HostFilter) Or(g HostFilter) HostFilter {
    return func(h Host) bool {
        return f(h) || g(h)
    }
}

func (f HostFilter) And(g HostFilter) HostFilter {
    return func(h Host) bool {
        return f(h) && g(h)
    }
}
```

Если мы хотим объявить функцию которая может использовать `HostFilter` метода, к сожалению нам нжуно пойти другим путём, чтобы это сделать. Чтобы функция имела верный приемни `HostFilter` метода, недостаточно совпадать описанию `HostFilter`, нам нужно объявить функцию как `HostFilter` явно.
```go
var IsDotOrg HostFilter = func(h Host) bool {
	return strings.HasSuffix(string(h), ".org")
}
```

Но теперь стало ясно что мы начали делать хорошо для нашей угрозы злоупотребления синтаксисом Go. Объявление функции с помощью передачи анонимной функции в переменную дает неясное ощущение. Отметим что это не требование использовать высокоуровневую функцию или преимущество типа для подписи функции - любоая `func(Host) bool` может быть назначения переменной `HostFilter` или параметру. Это безрассудное объявление нужно только чтобы можно было использовать функции  такие как `IsDotOrg` как приемник `HostFilter` метода

Однако выгода, заключается в том, что использование методов в `HostFilter` функции позволяет получить нам интересный синтаксис:
```go
var HasGo HostFilter = func (h Host) bool {
    return strings.Contains(string(h), "go")
}

var IsAcademic HostFilter = func(h Host) bool {
    return strings.Contains(string(h), "academy")
}

func main() {
    myHosts := HostList{"golang.org", "google.com", "gopheracademy.org"}
    goHosts := myHosts.Select(IsDotOrg.Or(HasGo))
    academies := myHosts.Select(IsDotOrg.And(IsAcademic))

    fmt.Printf("Go sites: %v\n", goHosts)
    fmt.Printf("Academies: %v\n", academies)
}
```

Запустим:
```bash
Go sites: [golang.org google.com gopheracademy.org]
Academies: [gopheracademy.org]
```

Мы можем увидеть язык приобретает форму выражений типа `myHosts.Select(IsDotOrg.Or(HasGo))`. Он читается как английский, что-то подобное вы можете услышать на болотах Дагобы. Декларативный синтаксис, начинает появляться - выражения говорять больше о желаемом результате(“select the elements of myHosts that are .orgs or contain ‘Go’) нежели шаги которые требуется чтобы до этого добраться. Мы использовали высокоуровневые функции, `Select`, `And` и `Or`, для построения поведения от этих трех различных кусоков кода в полностью динамичном виде.

Это очень могущественный вид выражения поведения, но все эти методы очереди могут стать запутанными.
```go
// etc.
myHosts.Select(IsDotOrg.Or(HasGo).Or(IsAcademic).Or(WelcomesGophers).And(UsesSSL)
```
Поэтому возмож мы должны очитсть вещи используя вариативные метод:
```go
var HostFilter Or = func (clauses ...HostFilter) HostFilter {
    var f HostFilter = nil
    for _, c := range clauses {
        f = f.Or(c)
    }
    return f
}
```
И затем переписать очередь вызовов выше таким образом:
```go
myHosts.Select(Or(IsDotOrg, HasGo, IsAcademic, WelcomesGophers).And(UsesSSL))
```

	Предупредение: эти функциональные сущности самые опасные свойства Go - любой Go код который я читал или писал должен злоупотреблять этими возможностями, создание анонимных функци и передача их через слой за слой с косвенным обращением.

Одноко, динамика функционального стиля программирования бесценна, когда строишь свой язык внутри Go. Высокоуровневые функции, функции которые управляют другими функциями и возвращают целую новую фунцкию, дают возможно сочинять или параметризовать поведение. Цель создания DSL - упростить решение в классе проблем выставив пользоватлю DSL несколько ползеных идей для решения этих проблем, расширив их и объединив эти идеи в какой-то смысл. Созданное динамическое поведение созднное с помощью высокоуровневых функций это одна из возможностей доставить эту функциональность.

# Пример по серьёзнее

Мы построим нашу работу над именами хостов так, чтобы сделать что-то ближе к тому, что мы возможно используем в реальном приложении. Ипмортируем пакет `net/http`, и давайте создадим другой тип:
```go
type RequestFilter func(*http.Request) bool
```

Мы можем использовать `RequestFilter` в простом HTTP сервере для вычисления удовлетворяет `http.Request` услови, как мы это делали с `HostFilter` выше. Мы можем использовать эти условия, чтобы определить обработать или отбросить запрос.

Мы перейдем от имен хосто к работе с набором ip адресов. Будем использовать CIDR блоки типа "192.168.0.0/16", который определяет набор ip адресов, в данном случае, от  192.168.0.0 до 192.168.255.255. Создадим `RequestFilter` который фильтрует запросы основанные на ip.

Из `net` пакета, будем использовать `ParseCIDR` функцию, и `PaeseIP` чтобы анализировать входящие запросы. Значения которые возвращает `ParseCIDR` - `IPNet` который имеет удобный метод `Contains`, он будет нам говорить входит ли ip  в наш список CIDR блоков.

Давайте импортируем `net` пакет и напишем `RequestFilter` который принимает вариативный набор CIDR блоков в формате `string`:
```go
func CIDR(cidrs ...string) RequestFilter {
	nets := make([]*net.IPNet, len(cidrs))
	for i, cidr := range cidrs {
        // TODO: handle err
		_, nets[i], _ = net.ParseCIDR(cidr)
	}
	return func(r *http.Request) bool {
        // TODO: handle err
		host, _, _ := net.SplitHostPort(r.RemoteAddr)
		ip := net.ParseIP(host)
		for _, net := range nets {
			if net.Contains(ip) {
				return true
			}
		}
		return false
	}
}
```

Заметим, что `net/http` пакет уже содержит тип HTTP обработчика, `HandlerFunc`:
```go
type HandlerFunc func(ResponseWriter, *Request)
```
А мы будем использовать высокоуровневую функцию и наш `RequestFilter` для изменения `http.HandlerFuncs`, объявим тип для функции которая обрабатывает `http.HandlerFuncs`:
```go
type Middleware func(http.HandlerFunc) http.HandlerFunc
```
И давайте сделаем несколько функций чтобы построить промежуточный слой используя `RequestFilter`:
 ```go
 func Allow(f RequestFilter) Middleware {
	return func(h http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			if f(r) {
				h(w, r)
			} else {
				// TODO
				w.WriteHeader(http.StatusForbidden)
			}
		}
	}
}
```

Теперь для примера вы можете изменить http обработчик `MyHandler` так, чтобы он прининмал только запросы от `127.0.0.1`, следующим образом:
```go
filteredHandler := Allow(CIDR("127.0.0.1/32"))(MyHandler)
```
Давайте попробуем запустить простой сервер:
```go
func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello\n")
}

func main() {
    http.HandleFunc("/hello", Allow(CIDR("127.0.0.1/32")(hello))
	log.Fatal(http.ListenAndServe(":1217", nil))
}
```
Если мы перейдем по ссылке с локальной машины: `http://0.0.0.0:1217/hello`, то вы должны увидеть "Hello" в ответ, если вы зайдете на эту ссылку с другого ip, то увидите ошибку `403 Forbidden error`.

Для прикола, давайте добавим другой вид `RequestFilter` который реализует реально просто механизм аутентификации
```go
func PasswordHeader(password string) RequestFilter {
	return func(r *http.Request) bool {
		return r.Header.Get("X-Password") == password
	}
}
```

И один на основе HTTP метода:
```go
func Method(methods ...string) RequestFilter {
	return func(r *http.Request) bool {
		for _, m := range methods {
			if r.Method == m {
				return true
			}
		}
		return false
	}
}
```
Ну и промежуточный слой который что-то просто логирует
```go
func Logging(f http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Printf("[%v] - %s %s\n", time.Now(), r.Method, r.RequestURI)
		f(w, r)
	}
}
```
Который мы можем попробовать обновив наш сервер:
```go
func main() {
    http.HandleFunc("/hello", Logging(Allow(CIDR("127.0.0.1/32")(hello)))
	log.Fatal(http.ListenAndServe(":1217", nil))
}
```
Запустите сервер и посетите страницу `http://localhost:1217/hello` несколько раз в браузере и в консоли сервера вы увидите:
```bash
[2016-12-14 07:42:12.022266374 -0500 EST] - GET /hello
[2016-12-14 07:42:14.537985456 -0500 EST] - GET /hello
[2016-12-14 07:42:24.220089221 -0500 EST] - GET /hello
```
Синтаксис декларативный как есть, но метод цепочки может быть немного неуклюж. Метод должен быть упорядочем в правильном порядке чтобы вести себя правильно, а результат может быть слолжно читать.

Мы можем использовать структуры для дальнейшего раскрыватя DSL и дать нашим пользвателям даже более ясную возможность объявить их конфигурацию промежуточного слоя.
```go
type Filters []RequestFilters
type Stack []Middleware
type Endpoint struct {
	Handler    http.HandlerFunc
	Allow      Filters
	Middleware Stack
}
```
Затем, мы можем выразить эндпоинт выше тем же ограничением как:
```go
var MyEndpoint = Endpoint{
	Handler: hello,
	Allow: Filters{
		CIDR("127.0.0.1/32"),
	},
	Middleware: Stack{
		Logging,
	},
}
```

Который в разы проще писать, читать и изменять. Нам просто нужно добавить несколько методов в нашу структуру и типы превращаются из декларативных в удобные `http.HandlerFunc`
```go
// Combine creates a RequestFilter that is the conjunction
// of all the RequestFilters in f.
func (f Filters) Combine() RequestFilter {
	return func(r *http.Request) bool {
		for _, filter := range f {
			if !filter(r) {
				return false
			}
		}
		return true
	}
}

// Apply returns an http.Handlerfunc that has had all of the
// Middleware functions in s, if any, to f.
func (s Stack) Apply(f http.HandlerFunc) http.HandlerFunc {
	g := f
	for _, middleware := range s {
		g = middleware(g)
	}
	return g
}

// Builds the endpoint described by e, by applying
// access restrictions and other middleware.
func (e Endpoint) Build() http.HandlerFunc {
	allowFilter := e.Allow.Combine()
	restricted := Allow(allowFilter)(e.Handler)

	return e.Middleware.Apply(restricted)
}
```
И наконец, изменим сервер чтобы использовать новый сервер:
```go
func main() {
	http.HandleFunc("/hello", mw.MyEndpoint.Build())
	log.Fatal(http.ListenAndServe(":1217", nil))
}
```
Чтобы увидеть выходу этого миниDSL что мы создали, добавим еще один промежуточный слой:
```go
func SetHeader(key, value string) Middleware {
    return func(f http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *RequestFilter) {
            w.Header().Set(key, value)
            f(w, r)
        }
    }
}
```
И затем добавим его, вместе с другим `RequestFilter` в наш эндпоинт:
```go
var MyEndpoint = Endpoint{
	Handler: hello,
	Allow: Filters{
		CIDR("127.0.0.1/32"),
		PasswordHeader("opensesame"), // added
		Method("GET"), // added
	},
	Middleware: Stack{
		Logging,
		SetHeader("X-Foo", "Bar"), // added
	},
}
```
МЫ добавили существенности в сложность `MyEndpoint` без добавления множества сложности в его объявление.

Этот полезный DLS удобен для построения одного HTTP эндпоинт, но часто мы хотим больше чем просто один сервис. Мы добавим еще один элемент в наше DSL, способ создать несколько маршрутов и их ендпоинты за раз:
```go
type Routes map[string]Endpoint

func (r Routes) Serve(addr string) error {
	mux := http.NewServeMux()
	for pattern, endpoint := range r {
		mux.Handle(pattern, endpoint.Build())
	}

	return http.ListenAndServe(addr, mux)
}
```
И наш сервис превращается в:
```go
func main() {
	routes := Routes{
		"/hello": {
			Handler: hello,
			Middleware: Stack{
				Logging,
			},
		},
		"/private": {
			Handler: hello,
			Allow: Filters{
				CIDR("127.0.0.1/32"),
				PasswordHeader("opensesame"),
			},
			Middleware: Stack{
				Logging,
			},
		},
		"/test": {
			Handler: hello,
			Middleware: Stack{
				Logging,
				SetHeader("X-Foo", "Bar"),
			},
		},
	}
	log.Fatal(routes.Serve(":1217"))
}
```

Обратите внимание, что Go автоматически определяет тип структурных литералов конечной точки в карте маршрутов, избавляя нас от лишнего набора текста и беспорядка. 

HTTP промежуточный слой DSL показывает как много может удаваться в относительно маленьком наборе Go, но это простой пример. Вот несколько задания для раширения DSL и для того чтобы сделать его более мощным.

* Реализовать дополнительный `ReqeustFilters` как ограничитель частоты, возможно использовать golang.org/x/time/rate или juju/ratelimit, или более сложный механизм аутентификации
* Реазиловать другой промежуточный слой
* Изменить структуру эндпоинта чтобы включить полу `Deny` для типа `Filters`, это будет отвергать запрос если одно из полей `RequestFiltesr` - `true`
* Каждый эндпоинт в конечном примере включает логирование в промежуточном слое, добавьте в DSL средство для применения набора общих ограничений или промежуточный слой для всех энпоинтов.
* Создать способ для стека промежуточного слоя, чтобы создать `context.Context` и работать с обработчиками которые их принимают.

Резюмируем, мы использовать типы для создания абстрактнций поверх наборов простых типов и функций отдельных подписей,  и мы берем преимущество Go свойств синтаксиса такие как вариативные функции и приведение типов для написания спокойного и ненагроможденного синтаксиса. Тяжелый подъем в создании DSL был произведен с помощью функций высшего порядка который позволили параметризовать поведения объединеные и настроены во время работы. Мы использовали несколько опасных практик написания кода, но чем больше мы применяем их только когда сокращаем сложность для конечного пользователя, тем крепче мы можем спать ночью.

Go, который вы получаете из коробки ориентирован на детали, минималистичный, и может быть довольно подробным. Go дает инструменты, однако, чтобы построить вашу собственную абстракцию, ваш собственный высокоуровневый язык для написания кода который содержательный, элегантный, и выразительный как и любой другой который вы находите в динамичном или чистом функциональном языке, но это дает нам доступ ко всем свойствам который мы любим в Go.

# Первые шаги на Go — Построение простого веб приложения с Neo4j

### Цель:
Цель этого поста создать простое веб приложение которое снимает структуру вашего сайта получая все ссылки которые содержит и сохраняет их в neo4j базу данных.
Идея проста - и в ней следующие шаги:
* Делаем запрос на URL
* парсим ответ
* Извлекаем ссылки из ответа
* Сохраняем извлеченные ссылки в  neo4j
* Повторяем 1 шаг с полученными ссылками пока не исследуем весь сайт
* Наконец используем Neo4j веб интерфейс чтобы посмотреть на структуру.
### Требования:
Эта статья подойдет начинающим. Будут приведены ссылки, каждый раз, когда будет преставленна новая идея. Для Neo4j, базовое знание графово ориентированной базы данных будет к месту. Предполагается что Go  и  Neo4j уже установленны на машинет.
## Создаем ползунок:
Теперь, когда у нас все есть. Начнем.
### Получение одной страницы из интеренета:
Время написать сожный код, которы поможет нам получить определенную страницу из интернета.
```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

type responseWriter struct{}

func main() {

	resp, err := http.Get("http://www.sfeir.com")

	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	rw := responseWriter{}
	io.Copy(rw, resp.Body)
}

func (responseWriter) Write(bs []byte) (int, error) {
	fmt.Printf(string(bs))

	return len(bs), nil
}
```

Мы начали с объявления главного пакета и импорта требуемых пакетов. Дальше, мы объявили структуру которая будет реализовывать `Writer` интерфейс. В `main` функции, мы собираемся присвоить множествую переменных значения. В основном. `http.Get` будет возвращать значения с  ответом и некоторой ошибкой, если что-то пойдет не так. Это общий способ обработки ошибок в Go программах.

Если вы посмотрите на документацию, вы найдете `Writer` интерфейс с одной функцией. Для того, чтобы реализовать этот интерфейс, нам нужно добавить получателя функции к нашей `responseWriter` структуре которая совпадает с функцией `Writer`.  Если вы пришли из Java вы должны ожидать синтаксис типа `реалзиация Writer`. Ну чтож, это не тот случай для Go, так как тут реализация происходит неявно.

Наконец, мы используем `io.Copy` для записи тела ответа в нашу переменную ответа. Следующий шаг это модификация нашего кода для извлечения ссылок из данного адреса страницы. После некоторого рефакторинга, у нас будет два фала.
Это `main.go`:
```go
package main

import (
	"fmt"
	"os"
)

func main() {

	if len(os.Args) < 2 {
		fmt.Println("Web site url is missing")
		os.Exit(1)
	}

	url := os.Args[1]

	retreive(url)
}
```

И этот `retreiver.go`:
```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

type responseWriter struct{}

func (responseWriter) Write(bs []byte) (int, error) {
	fmt.Printf(string(bs))

	return len(bs), nil
}

func retreive(uri string) {
	resp, err := http.Get(uri)

	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	rw := responseWriter{}
	io.Copy(rw, resp.Body)
}
```

Мы можем запустить это для небольшого вебсайта:
```bash
go run main.go retreiver.go http://www.sfeir.com
```
Теперь мы сделали наш первый шаг для создание ползунка. Есть возможность загрузить и спарсить данный адрес, открыть подключение прям к удлённому хосту, и получить html содержание.


### Создадим все гиперссыылки для одной страницы
Теперь начинается часть где нам нужно извлечь все ссылки из  html  документа. К сожалению, нет доступного для этого помошника для обработки  HTML в Go API. Поэтому мы должны посмотреть на  стороннюю  API. Давайте рассмотрим  `goquery`. Как вы можете догадаться, она  похоже на `jquery` только для Go. 

Пакет `goquery`  легко получается командой:
```bash
go get github.com/PuerkitoBio/goquery
```

```go
package main

import (
	"fmt"
	"os"
)

func main() {

	if len(os.Args) < 2 {
		fmt.Println("Web site url is missing")
		os.Exit(1)
	}

	url := os.Args[1]

	links, err := retrieve(url)

	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	for _, link := range links {
		fmt.Println(link)
	}
}
```

Я изменил нашу `retrieve` функцию, таким образом, чтобы она возвращала ссылки данные на странице.
```go
package main

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

func retrieve(uri string) ([]string, error) {
	resp, err := http.Get(uri)
	if err != nil {
		fmt.Println("Error:", err)
		return nil, err
	}

	doc, readerErr := goquery.NewDocumentFromReader(resp.Body)
	if readerErr != nil {
		fmt.Println("Error:", readerErr)
		return nil, readerErr
	}
	u, parseErr := url.Parse(uri)
	if parseErr != nil {
		fmt.Println("Error:", parseErr)
		return nil, parseErr
	}
	host := u.Host

	links := []string{}
	doc.Find("a[href]").Each(func(index int, item *goquery.Selection) {
		href, _ := item.Attr("href")
		lu, err := url.Parse(href)
		if err != nil {
			fmt.Println("Error:", err)
			return
		}
		if isInternalURL(host, lu) {
			links = append(links, u.ResolveReference(lu).String())
		}

	})

	return unique(links), nil
}

// insures that the link is internal
func isInternalURL(host string, lu *url.URL) bool {

	if lu.IsAbs() {
		return strings.EqualFold(host, lu.Host)
	}
	return len(lu.Host) == 0
}

// insures that there is no repetition
func unique(s []string) []string {
	keys := make(map[string]bool)
	list := []string{}
	for _, entry := range s {
		if _, value := keys[entry]; !value {
			keys[entry] = true
			list = append(list, entry)
		}
	}
	return list
}
```
Как можно увидеть, наша `retrieve` функция стала существенно улучшена. Я убрал  `responseWriter` стуктуру  так как она больше не нужна из-за `goqeury` имеет свою реализацию интерфейса `Writer`. 
Я так же добавил две функции помошников. Первая - определяет  URL указывающий на внутреннюю страницу. Вторая - проверяет, что список не содержит дубликатов ссылок.

Вновь запустим программу для простого сайта:
```bash
go run main.go retreiver.go http://www.sfeir.com
```

### Получаем все гиперссылки для всего сайта.
Ура! Мы сделали большую работу. Следующее, мы собираемся посмотре, как улучшить `retrieve` функцию для того, чтобы получить ссылки с других страниц, в том числе. Я предлагю рассмотреть использование рекурсии. Мы создадим другую функцию под названием `crawl` и эта функция будет вызывать себя рекурсивно, с каждой следующей полученой ссылкой. Так же, нам нужно отслеживать посещенные страницы, чтоби избежать повторных переходов по ссылкам.
Прроверим:
```go
// part of retreiver.go
var visited = make(map[string]bool)

func crawl(uri string) {

	links, _ := retrieve(uri)

	for _, l := range links {
		if !visited[l] {
			fmt.Println("Fetching", l)
			visited[uri] = true
			crawl(l)
		}
	}
}
```
Теперь можно вызывать  `crawl` вместо `retrieve` функции в  `main.go`. Код будет следующим.
```go
package main

import (
	"fmt"
	"os"
)

func main() {

	if len(os.Args) < 2 {
		fmt.Println("Web site url is missing")
		os.Exit(1)
	}

	url := os.Args[1]

	crawl(url)

}
```
Запустим нашу программу:
```bash
go run main.go retreiver.go http://www.sfeir.com
```
### Реализуем слушателя событий через каналы. 
В прошлой части, мы увидели как полученные URL отображаются внутри `crawl` функции. Это не лучшее решение особенно когда вам нужно делать больеш чем просто вывод на экране. Чтобы это исправить, в основном, нам нужно реализовать слушателя событий для получения URL через каналы. 
Давайте посмотрим на это:
```go
// same imports
type link struct {
	source string
	target string
}

type retriever struct {
	events  map[string][]chan link
	visited map[string]bool
}

func (b *retriever) addEvent(e string, ch chan link) {
	if b.events == nil {
		b.events = make(map[string][]chan link)
	}
	if _, ok := b.events[e]; ok {
		b.events[e] = append(b.events[e], ch)
	} else {
		b.events[e] = []chan link{ch}
	}
}

func (b *retriever) removeEvent(e string, ch chan link) {
	if _, ok := b.events[e]; ok {
		for i := range b.events[e] {
			if b.events[e][i] == ch {
				b.events[e] = append(b.events[e][:i], b.events[e][i+1:]...)
				break
			}
		}
	}
}

func (b *retriever) emit(e string, response link) {
	if _, ok := b.events[e]; ok {
		for _, handler := range b.events[e] {
			go func(handler chan link) {
				handler <- response
			}(handler)
		}
	}
}

func (b *retriever) crawl(uri string) {

	links, _ := b.retrieve(uri)

	for _, l := range links {
		if !b.visited[l] {
			b.emit("newLink", link{
				source: uri,
				target: l,
			})
			b.visited[uri] = true
			b.crawl(l)
		}
	}
}

func (b *retriever) retrieve(uri string) ([]string, error) {
	resp, err := http.Get(uri)
	if err != nil {
		fmt.Println("Error:", err)
		return nil, err
	}

	doc, readerErr := goquery.NewDocumentFromReader(resp.Body)
	if readerErr != nil {
		fmt.Println("Error:", readerErr)
		return nil, readerErr
	}
	u, parseErr := url.Parse(uri)
	if parseErr != nil {
		fmt.Println("Error:", parseErr)
		return nil, parseErr
	}
	host := u.Host

	links := []string{}
	doc.Find("a[href]").Each(func(index int, item *goquery.Selection) {
		href, _ := item.Attr("href")
		lu, err := url.Parse(href)
		if err != nil {
			fmt.Println("Error:", err)
			return
		}
		if isInternalURL(host, lu) {
			links = append(links, u.ResolveReference(lu).String())
		}

	})

	return unique(links), nil
}

// same helper functions
```
Как можно увидеть, у нас есть дополнительные функции, для управлеия событиями для данного `retriever`. Для этого кода я использовал  `go` ключевое слово. В основном, написание `go foo()` запустит функцию `foo` запуститься асинхронно. В нашем случае мы использовали `go` которая является анонимной функцией, чтобы послать парамметр события(ссылку) всем слушателям через канал.
	Я указал тип канала данных как `link`, который содержит источник и целевую страницу. 

Теперь давайте взглянем на `main` функцию:
```go
package main

import (
	"fmt"
	"os"
)

func main() {

	if len(os.Args) < 2 {
		fmt.Println("Web site url is missing")
		os.Exit(1)
	}

	url := os.Args[1]
	ev := make(chan link)
	r := retriever{visited: make(map[string]bool)}
	r.addEvent("newLink", ev)

	go func() {
		for {
			l := <-ev
			fmt.Println(l.source + " -> " + l.target)
		}
	}()

	r.crawl(url)

}
```
Вновь используя `go` ключевое слово, в этот раз чтобы получать параметры события отправленные  `crawl` функцией. Если мы запустим нашу программу, теперь мы должны увидеть все внутренние ссылки для данного сайта.

Этого достаточно для ползунка.

## Интеграция с  Neo4j
Мы закончили с ползунком, давайте перейдем к части с Neo4j. Первая вещь, которую  мы собираемся сделать это установить драйвер.
```bash
go get github.com/neo4j/neo4j-go-driver/neo4j
```
После установки драйвера, нам нужно создать некую базовую функцию которая позволит нам работать с  Neo4j.
Создадим файл под названием  `neo4j.go`:
```go
package main

import (
	"github.com/neo4j/neo4j-go-driver/neo4j"
)

func connectToNeo4j() (neo4j.Driver, neo4j.Session, error) {

	configForNeo4j40 := func(conf *neo4j.Config) { conf.Encrypted = false }
	
	driver, err := neo4j.NewDriver("bolt://localhost:7687", neo4j.BasicAuth(
		"neo4j", "alice!in!wonderland", ""), configForNeo4j40)

	if err != nil {
		return nil, nil, err
	}

	sessionConfig := neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}
	session, err := driver.NewSession(sessionConfig)
	if err != nil {
		return nil, nil, err
	}

	return driver, session, nil
}

func createNode(session *neo4j.Session, l *link) (neo4j.Result, error) {
	r, err := (*session).Run("CREATE (:WebLink{source: $source, target: $target}) ", map[string]interface{}{
		"source": l.source,
		"target": l.target,
	})

	if err != nil {
		return nil, err
	}

	return r, err
}

func createNodesRelationship(session *neo4j.Session) (neo4j.Result, error) {
	r, err := (*session).Run("MATCH (a:WebLink),(b:WebLink) WHERE a.target = b.source CREATE (a)-[r:point_to]->(b)", map[string]interface{}{})

	if err != nil {
		return nil, err
	}

	return r, err
}
```
В основе, мы имеет три функции отвечающие за инициализацию подключения  Neo4j с базовым запросом. 
	Вам нужно поменять Neo4j конфигурацию для работы с локальной установкой.
    
Чтобы создать `WebLink` ноду нам просто нужно запустить следующий запрос:
```sql
CREATE (:WebLink{source: "http://www.sfeir.com/", target: "http://www.sfeir.com/en/services"})
```
Как только нода будет создана, нам нужно создать отношение между ними запустив следующий запрос:
```sql
MATCH (a:WebLink),(b:WebLink) 
WHERE a.target = b.source 
CREATE (a)-[r:point_to]->(b)
```
Давайте обновим нашу `main` функцию.
```go
package main

import (
	"fmt"
	"os"

	"github.com/neo4j/neo4j-go-driver/neo4j"
)

func main() {

	if len(os.Args) < 2 {
		fmt.Println("Web site url is missing")
		os.Exit(1)
	}

	driver, session, connErr := connectToNeo4j()

	if connErr != nil {
		fmt.Println("Error connecting to Database:", connErr)
		os.Exit(1)
	}

	defer driver.Close()

	defer session.Close()

	url := os.Args[1]
	ev := make(chan link)
	r := retriever{visited: make(map[string]bool)}
	r.addEvent("newLink", ev)

	go func(session *neo4j.Session) {
		for {
			l := <-ev
			fmt.Println(l.source + " -> " + l.target)
			_, err := createNode(session, &l)

			if err != nil {
				fmt.Println("Failed to create node:", err)
			}

		}
	}(&session)

	r.crawl(url)

	fmt.Println("Creation of relationship between nodes.. ")
	_, qErr := createNodesRelationship(&session)

	if qErr == nil {
		fmt.Println("Nodes updated")
	} else {
		fmt.Println("Error while updating nodes:", qErr)
	}

}
```
Использую три функциюю объявленные в `neo4j.go` наша программа создаст подключение к neo4j, подпишется на `newLink` событие для вставки нод и наконец обновит связи нод.
Я использовал `defer` ключево слово, чтобы сослаться на исполнение функции до тех пор пока не завершиштся `main` функция.
Давайте запустим в последний раз:
```bash
go run main.go retreiver.go neo4j.go http://www.sfeir.com
```
Чтобы проверить результат в  Neo4j вы можете запустить следующий запрос в вашем  Neo4j браузере.
```sql
MATCH (n:WebLink) RETURN count(n) AS count
```
Или этот запрос отобразит все ноды:
```sql
MATCH (n:WebLink) RETURN n
```

### Выводы
В этом посте, мы изучили множество свойств языка Golang включая множественные присвоения переменным, реализацию интерфейсов и каналов и горутин. Так же мы использовали стандартную библиотеку. Спасибо за чтение.

# Пишем REST API клиента на Go

Api клиенты, очень полезны когда вы выставляете ваш REST API на публику. И go делает это проще для вас, как для разработчика, так же как и для ваших пользователей, спасибо его структуре и типу систем. Но как определить хороший Api клиент?

В этой инструкции, мы собираемся обсудить несколько хороших практик написания SDK на Go.

Мы будем исполльзовть Facest.io  Api как пример. 

Прежде чем начнем писать какой-то код, мы одлжны изучить API чтобы понять главные его вещи, такие как:
* Что такое "Base url", и можно ли его поменять?
* Поддерживает ли он версионирование?
* Какие ошибки можно встретить?
* Каким образом аутентифицируются клиенты?

Ответы на эти вопросы помогут вам создать правильную структуру. 

Начнем с основ. Создадим репозиторй, выберем название, в идеаеле одно должно совпадать с именем API сервиса. Инициализирум go модули. Создадим нашу главную структуру для хранения пользовательской информации. Это структура, в последствии, будет содержать точки доступа API как функции.

Структуры дложны быть гибкими, но так же ограниченными. чтобы пользователь не могу увидеть внутренние поля.

Создаем поля `BaseURL` и `HTTPClient` экспортируемыми, чтобы пользователь мог использовать их в своём HTTP клиенте, если это нужно.
```go
package facest

import (
    "net/http"
    "time"
)

const (
    BaseURLV1 = "https://api.facest.io/v1"
)

type Client struct {
    BaseURL    string
    apiKey     string
    HTTPClient *http.Client
}

func NewClient(apiKey string) *Client {
    return &Client{
        BaseURL: BaseURLV1,
        apiKey:  apiKey,
        HTTPClient: &http.Client{
            Timeout: time.Minute,
        },
    }
}
```
Продолжаем: реализуем "Get Faces" точку доступа, которая возвращает список результатов и поддерживает постраничный вывод, что говорит о том, что постраничный вывод - это опция ввода.

Как я указал про API, любой ответ должен всегда иметь одну и ту же структуру, чтобы мы могли определить и отделить успешный ли был ответит или нет, от данных что пришли, чтобы пользователь видел только нужную ему информацию.
```go
type errorResponse struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
}

type successResponse struct {
    Code int         `json:"code"`
    Data interface{} `json:"data"`
}
```
Убедитесь, что вы не пишете все точки доступа в одном .go файле, но сгрупируйте их используя отдельные файлы, для примера вы можете сгрупировать их по типу, всё что начинается с  `/v1/faces` идет в `fasec.go` файл.

Я обычно начинаю с определения типаов, вы можете это сделать вручную сконвертировав JSON  в JSON-to-Go инструменте.
```go
package facest

import "time"

type FacesList struct {
    Count      int    `json:"count"`
    PagesCount int    `json:"pages_count"`
    Faces      []Face `json:"faces"`
}

type Face struct {
    FaceToken  string      `json:"face_token"`
    FaceID     string      `json:"face_id"`
    FaceImages []FaceImage `json:"face_images"`
    CreatedAt  time.Time   `json:"created_at"`
}

type FaceImage struct {
    ImageToken string    `json:"image_token"`
    ImageURL   string    `json:"image_url"`
    CreatedAt  time.Time `json:"created_at"`
}
```

Функция `GetFaces` дожна поддерживать постраничный вывод а мы должны делать это добавив аргументы функции, но эти аргументы не обязательны и они могту быть изменены в будущем. Так, что стоит группировать их в специальную структуру:
```go
type FacesListOptions struct {
    Limit int `json:"limit"`
    Page  int `json:"page"`
}
```
Еще один аргумент, нашей функции должен поддерживать, и его контекст, который позволит пользователю вызывать API. Пользователи могут создавать `Context`, передавая его в нашу функцию. Пример использования: отмена API вызова если он длится больше 5 секунд.

Теперь шаблон нашей функции выглядит следующим образом:
```go
func (c *Client) GetFaces(ctx context.Context, options *FacesListOptions) (*FacesList, error) {
    return nil, nil
}
```
Время сделать сам API:
```go
func (c *Client) GetFaces(ctx context.Context, options *FacesListOptions) (*FacesList, error) {
    limit := 100
    page := 1
    if options != nil {
        limit = options.Limit
        page = options.Page
    }

    req, err := http.NewRequest("GET", fmt.Sprintf("%s/faces?limit=%d&page=%d", c.BaseURL, limit, page), nil)
    if err != nil {
        return nil, err
    }

    req = req.WithContext(ctx)

    res := FacesList{}
    if err := c.sendRequest(req, &res); err != nil {
        return nil, err
    }

    return &res, nil
}
func (c *Client) sendRequest(req *http.Request, v interface{}) error {
    req.Header.Set("Content-Type", "application/json; charset=utf-8")
    req.Header.Set("Accept", "application/json; charset=utf-8")
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))

    res, err := c.HTTPClient.Do(req)
    if err != nil {
        return err
    }

    defer res.Body.Close()

    if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
        var errRes errorResponse
        if err = json.NewDecoder(res.Body).Decode(&errRes); err == nil {
            return errors.New(errRes.Message)
        }

        return fmt.Errorf("unknown error, status code: %d", res.StatusCode)
    }

    fullResponse := successResponse{
        Data: v,
    }
    if err = json.NewDecoder(res.Body).Decode(&fullResponse); err != nil {
        return err
    }

    return nil
}
```
Так как все точки доступа API работают одинаково, функция помошника `sendRequest` создана, чтобы избежать повторения в коде. Он задает большиство заголовков(content type, auth header), создает запрос, проверяет на ошибки, парсит ответ.

Отменит, что мы предполагаем ответы < 200 и >= 400, как ошибки, в этом случае парсится ответ из `errorResponse`. Понятно, что это зависит от архитектуры API, ваша Api может по разному обрабатывать ошибки.

# Тестирование
Теперь у нас есть SDK с  покрытием одной точки доступа API, что достаточно для этого примера, но этого достаточно чтобы передать это пользователю? Возможно да, но давайте посмотрим еще на несколько вещей.

Тесты востребованны в данном месте, и есть возможность двух типов: unit тесты, и интеграционные тесты. Для второго будем вызвать настоящий API. Напишем простой тест:

```go
// +build integration

package facest

import (
    "os"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestGetFaces(t *testing.T) {
    c := NewClient(os.Getenv("FACEST_INTEGRATION_API_KEY"))

    ctx := context.Background()
    res, err := c.GetFaces(nil)

    assert.Nil(t, err, "expecting nil error")
    assert.NotNil(t, res, "expecting non-nil result")

    assert.Equal(t, 1, res.Count, "expecting 1 face found")
    assert.Equal(t, 1, res.PagesCount, "expecting 1 PAGE found")

    assert.Equal(t, "integration_face_id", res.Faces[0].FaceID, "expecting correct face_id")
    assert.NotEmpty(t, res.Faces[0].FaceToken, "expecting non-empty face_token")
    assert.Greater(t, len(res.Faces[0].FaceImages), 0, "expecting non-empty face_images")
}
```
Этот тест использует `env` переменную где указывается API ключ. Поступая таким образом, мы убеждаемся, что они не публичны. Позже мы можем сконфигурировать эту переменную с помощью окружения используемого в CI\CD.

Эти тесты, отьделены от юнит тестов(так как они выполняются гораздо дольше): execute):
```bash
go test -v -tags=integration
```

## Документация.
Делайте ваш SDK не требующий описания с понятными типами и абстракциями. не выставляйте много информации. Обычно достаточно предоставить `godoc` ссылку как главную документацию.

## Свместимость и версионирование.
Версионирование вашего SDK зависит от вашего репозитория. Но всегда убеждайтесь, что вы не сломали ничего в различных патчах или минорных выпусках. Обычно ваша SDK библиотека должна следовать API обновлениям, поэтому если релизитсся API v2, тогда должен быть и релиз SDK v2.

# Пример использования Jenkins REST API на Golang

Я собираюсь описать как вызывать Jenkins REST API используя GO. Создадим три Go файла:
1. packages/helpers/helpers.go
2. packages/jenkins/jenkins.go
3. main.go

Исходный код доступен по адресу: https://github.com/mohdnaim/jenkins_rest_api

`helpers.go` содержит функции помощники. В нем будут расположены функции для чистоты и организации кода.

`jenkins.go` содержит функции связанные c Jenkins. Это функции `IsJobExist()` для проверки существования конвеера задачи или билда, `CopyJenkinsJob()` для копирования Jenkins задания и `DownloadConfigXML()` для скачивания конфигурации задачи в формате XML.

`main.go` это главный файл, точка старта нашей программы. 
```go
//main.go
package main

import (
	"fmt"
	"log"
	"strings"
	helpers "./packages/helpers"
	jenkins "./packages/jenkins"
)

func main() {
	// Обязательные настройки
	jenkinsURL := "АДРЕС СЕРВЕРА"
	jenkinsUsername := "ИМЯ ПОЛЬЗОВАТЕЛЯ"
	jenkinsAPIToken := "API ТОКЕР"
	jenkins.JenkinsDetails = jenkins.Details{jenkinsURL, jenkinsUsername, jenkinsAPIToken}
	xmlFolder := "xml"
	// 1. Получаем все доступные провекты\задачи 
	allProjectNames := jenkins.GetAllProjectNames()
	// 2. Фильтруем наши проекты которые мы ищем 
	filteredProjectNames := make([]string, 0)
	for _, projectName := range allProjectNames {
		// Что-то делаем 
		// Добавляем в другой срез основываясь на условии
		if strings.HasPrefix(projectName, "prefix") {
			filteredProjectNames = append(filteredProjectNames, projectName)
		}
	}
	// 3. Для каждого проекта получаем его  config.xml
	for _, projectName := range filteredProjectNames {
		xmlPath := fmt.Sprintf("%s/%s.xml", xmlFolder, projectName)
		if err := jenkins.DownloadConfigXML(projectName, xmlPath); err != nil {
			log.Println("error download config.xml for project:", projectName)
			continue // пропускаем
		}
	}
	// 4. Изменяем config.xml
	files := helpers.GetFilenamesRecursively(xmlFolder)
	for _, xmlFile := range files {
		log.Println(xmlFile)
	}
	// 4b. Переписываем  config.xml
	// 5. http POST запрос обновления config.xml
	for _, xmlFile := range files {
		tmpSlice := strings.Split(xmlFile, "/")
		projectName := tmpSlice[len(tmpSlice)-1]
		log.Println(projectName)
		if err := jenkins.PostConfigXML(projectName, xmlFile); err != nil {
			log.Println("error postconfigxml:", projectName)
		}
	}
}
```

Нам нужно настроить детали Jenkins объекта. Укажем корректное значение для следующих переменных:
```go
	jenkinsURL := "АДРЕС СЕРВЕРА"
	jenkinsUsername := "ИМЯ ПОЛЬЗОВАТЕЛЯ"
	jenkinsAPIToken := "API ТОКЕР"
```
Строка #18 
Создаем структуру содержащую три переменных выше. Структура используется функции в `jenkins.go` поэтому перед вызовом любой функции из `jenkins.go` модуля, нам нужно настроить эти значения.

Строка #19 
Укажем папку в которой будем хранить XML файл, который является конфигурацией Jenkins задачи.

Строка #22
Получаем имя всех проектов существующих в Jenkins. Если мы посмотрим на реализацию функции `GetAllProjectNames()` в `jenkins.go`, функция вызывает `DownloadFileToBytes()`. Она делаеет HTTP запрос на `/api/json` используя имя пользователя и пароль в заголовке запроса. Точка доступа вернет имя проекта в JSON формате. Затем преобразуется JSON в `result` словарь. Любое имя в словаре добавляет имя в `allProjectNames` массив. 

Строка #24 
Отсортируем наши проекты, так как мы хотим. Пройдемся по `allProjectNames` массива и если проект встречает определенный критерий, мы добавляем имя проекта в `filteredProjectNames` массив который мы будем использовать вместо `allProjectNames`.

Строка #35 
Для каждого имени проекта в `filteredProjectNames` мы получаем его конфигурационный файл config.xml.

Строка #45 
Вызываем фукнкцию `GetFileNamesRecursively()` в `helpers.go` для чтения всех имен файлов в `xmlFolder`.

Строка #50 
Мы делаем всё что хотим с `config.xml` файлом, например переименовываем имя проекта, добавляем параметры сборки, изменяем права и т.д.

Я написал скрипт на Python вместо Go для управления `config.xml` файлом, так как Pyehon имеет больше библиотек чем Go, которые могут помочь нам для управления строками и файлами. 

Строка #52 
Наконец, после того, как мы создали изменения в строке 50, мы отправляем изменения выполняя POST запрос в Jenkins.

```go
//helpers.go
package helpers

import (
	"os"
	"path/filepath"
)
// StringInSlice ...
func StringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}
// GetFilenamesRecursively ...
func GetFilenamesRecursively(path string) []string {
	var files []string
	err := filepath.Walk("xml", func(path string, info os.FileInfo, err error) error {
		files = append(files, path)
		return nil
	})
	if err != nil {
		panic(err)
	}
	return files
}
```

```go
//jenkins.go
package jenkins
import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"reflect"
)
// Details ...
type Details struct {
	URL      string
	Username string
	APIToken string
}
// JenkinsDetails ...
var JenkinsDetails = Details{}
// IsJobExist ...
func IsJobExist(projectName string) bool {
	fullURL := fmt.Sprintf("%sjob/%s", JenkinsDetails.URL, projectName)
	request, err := http.NewRequest("GET", fullURL, nil)
	request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		log.Println("project doesn't exist:", projectName)
		return false
	}
	if resp.StatusCode == 200 {
		// log.Println("project exists:", projectName)
		return true
	}
	log.Println("project doesn't exist:", projectName)
	return false
}
// CopyJenkinsJob ...
func CopyJenkinsJob(srcJob string, dstJob string) error {
	fullURL := fmt.Sprintf("%s%s?name=%s&mode=copy&from=%s", JenkinsDetails.URL, "createItem", dstJob, srcJob)
	request, err := http.NewRequest("POST", fullURL, nil)
	request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return err
	}
	if resp.StatusCode == 200 {
		return nil
	}
	return fmt.Errorf("error copying job: %s", srcJob)
}
// DownloadConfigXML ...
func DownloadConfigXML(projectName string, dstFilename string) error {
	fullURL := fmt.Sprintf("%sjob/%s/config.xml", JenkinsDetails.URL, projectName)
	if DownloadFile(fullURL, dstFilename) == nil {
		return nil
	}
	return fmt.Errorf("error downloading file %s", fullURL)
}
// PostConfigXML ...
func PostConfigXML(projectName string, filename string) error {
	// read content of file
	data, err := os.Open(filename)
	if err != nil {
		log.Fatal(err)
		return err
	}
	fullURL := fmt.Sprintf("%sjob/%s/config.xml", JenkinsDetails.URL, projectName)
	request, err := http.NewRequest("POST", fullURL, data)
	request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
	client := &http.Client{}
	// perform the request
	resp, err := client.Do(request)
	if err != nil {
		return err
	}
	if resp.StatusCode == 200 {
		// log.Printf("success postConfigXML: %s %s %s", projectName, filename, resp.Status)
		return nil
	}
	return fmt.Errorf("error postConfigXML: %s %s %s", projectName, filename, resp.Status)
}
// DownloadFile ...
func DownloadFile(url string, filepath string) error {
	// Create the file
	out, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer out.Close()
	// Get the data
	request, err := http.NewRequest("GET", url, nil)
	request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	// Write the body to file
	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return err
	}
	return nil
}
// DownloadFileToBytes ...
func DownloadFileToBytes(url string) ([]byte, error) {
	request, err := http.NewRequest("GET", url, nil)
	request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err) // writes to standard error
	}
	return bodyBytes, nil
}
// GetAllProjectNames ...
func GetAllProjectNames() []string {
	allProjectNames := make([]string, 0)
	allProjectsURL := fmt.Sprintf("%sapi/json?pretty=true", JenkinsDetails.URL)
	if respBytes, err := DownloadFileToBytes(allProjectsURL); err == nil {
		// json --> map
		var result map[string]interface{}
		json.Unmarshal([]byte(respBytes), &result)
		// if map has slice 'jobs', iterate over it
		if jobs, keyIsPresent := result["jobs"]; keyIsPresent && reflect.TypeOf(jobs).Kind() == reflect.Slice {
			jobs2 := result["jobs"].([]interface{})
			for _, job := range jobs2 {
				// log.Println(job)
				_map, _ := job.(map[string]interface{}) // assert to map
				allProjectNames = append(allProjectNames, _map["name"].(string))
			}
		}
	}
	return allProjectNames
}
```

# Простая инструкция изучения Цепочи обязаностей в Golang

Что такое цепочка обязаностей(Chain of Responsibility)?

* Поведенческий шаблон проектирования который позволяет вам передавать запрос через цепочку обработчиков.
* При получении запроса, каждый обработчик решает сам обрабатывать запрос или передать его дальше по цепочке.

# Проблема

[![](https://notepad.gasick.ru/uploads/images/gallery/2021-09/scaled-1680-/image-1631168606391.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-09/image-1631168606391.png)

Представим, вы рабоаете над приложениме онлаин курсов. Вот список проверок перед тем как можно использовать курс.

* Один из ваших коллег, Джимм, предположил, что это не безопасно передавать чистые данные в приложении. Поэтому вы добавли дополнительную проверку шага для обработки данных в запросе.
* One of your colleagues, Tommy, noticed that the system is vulnerable to brute force password cracking. To negate this, you promptly added a check that filters repeated failed requests coming from the same IP address.
* One of your colleagues, Daniel, suggested that you could speed up the system by returning cached results on repeated requests containing the same data. Hence, you added another check which lets the request pass through to the system only if there’s no suitable cached response.

Having a big headache

* The code of the checks, which had already looked like a mess, became more and more bloated as you added each new feature
* Changing one check sometimes affected the others
* Worst of all, when you tried to reuse the checks to protect other components of the system, you had to duplicate some of the code since those components required some of the checks, but not all of them.

# Solution

[![](https://notepad.gasick.ru/uploads/images/gallery/2021-09/scaled-1680-/image-1631168616743.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-09/image-1631168616743.png)

* CoR relies on transforming particular behaviors into stand-alone objects called handlers
* The pattern suggests that you link these handlers into a chain
* Each linked handler has a field for storing a reference to the next handler in the chain. In addition to processing a request, handlers pass the request further along the chain
* The request travels along the chain until all handlers have had a chance to process it.

Diagram

[![](https://notepad.gasick.ru/uploads/images/gallery/2021-09/scaled-1680-/image-1631168630393.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-09/image-1631168630393.png)

Handler

* declares the interface common to all concrete handlers.
* usually contains just 1 method for handling requests and another method to set the next handler in the chain

Base handler

* optional class to put the boilerplate code that’s common to all handler class
* this class defines a field for storing a reference to the next handler
* the main class build a chain by passing a handler to the constructor or setter of the previous handler

Main

* compose chains just once or compose them dynamically depending on the app logic

Concrete Handlers

* contain actual logic for processing requests
* upon receiving a request, each handler decide whether to process it and whether to pass it along the chain
* usually self contained and immutable, accepting all necessary data just one via the constructor

Pros and Cons
Pros

* control the order of request handling
* Fulfills *Single Responsibility Princple*
* Fulfills *Open/Closed Principle*

Cons

* some request may end up unhandled

# How to code a simple Chain of Responsibility in Golang?
## Section
```go
package main

type section interface {
	execute(*task)
	setNext(section)
}
```
## Material
```go
package main

import (
	"fmt"
)

type material struct {
	next section
}

func (m *material) execute(t *task) {
	if t.materialCollected {
		fmt.Println("Material already collected")
		m.next.execute(t)
		return
	}
	fmt.Println("Material section gathering materials")
	t.materialCollected = true
	m.next.execute(t)
}

func (m *material) setNext(next section) {
	m.next = next
}
```
## Assembly
```go
package main

import (
	"fmt"
)

type assembly struct {
	next section
}

func (a *assembly) execute(t *task) {
	if t.assemblyExecuted {
		fmt.Println("Assembly already done")
		a.next.execute(t)
		return
	}
	fmt.Println("Assembly section assembling...")
	t.assemblyExecuted = true
	a.next.execute(t)
}

func (a *assembly) setNext(next section) {
	a.next = next
}
view raw

```
## Packaging
```go
package main

import (
	"fmt"
)

type packaging struct {
	next section
}

func (p *packaging) execute(t *task) {
	if t.packagingExecuted {
		fmt.Println("Packaging already done")
		p.next.execute(t)
		return
	}
	fmt.Println("Packaging section doing packaging")
}

func (p *packaging) setNext(next section) {
	p.next = next
}
```
## Task
```go
package main

type task struct {
	name				string
	materialCollected	bool
	assemblyExecuted	bool
	packagingExecuted	bool
}
```
## Main
```go
package main

func main() {
	packaging := &packaging{}

	// set next for assembly section
	assembly := &assembly{}
	assembly.setNext(packaging)

	material := &material{}
	material.setNext(assembly)

	task := &task{name: "truck_toy"}
	material.execute(task)
}
```
# Explanation

`section.go` is a Handler interface. This interface handling requests and sets the next handler on the chain.

`material.go` is a Concrete handler. It decides if the request to collect material should be processed and can move up the chain.

`assembly.go` is a Concrete handler. It decides if the request to perform the assembly work should be processed and can move up the chain.

`packaging.go` is a Concrete handler. It decides if the request to perform the packaging work should be processed and can move up the chain.

`main.go` is a client. Initializes up the chain of handlers.


# How to run

Command: 
```bash
go run .

Material section gathering materials
Assembly section assembling…
Packaging section doing packaging
```

# Takeaways

I hope you understand how to code a simple abstract factory in Golang and more importantly understand how a chain of responsibility design pattern can help you to design your code better and to ensure maintainability.

Here the link to my github page for all source code on chain of responsibility design pattern in Golang: https://github.com/leonardyeoxl/go-patterns/tree/master/behavioral/chain_of_responsibility

# Реализация RSA шифрования и подписи на Golang

Эта статья описывает как работает RSA алгоритм, и как его можно реалоизоват на Go.

RSA (Rivest–Shamir–Adleman) шифрование наиболее широко распространнено для безопасного шифрования данных.

Это асиметрический алгорит шифрования, который по простому можно назвать "в одну сторону". В данном случае, легко для кого угодно зашифровать кусочек данных, но расшифровать его может только тот у кого есть настоящий ключ.

# RSA Шифрование изнутри

RSA работает генерируя публичный и приватный ключ. `public` и `private` ключи создаются одновременно и формируют пару ключей. 

Публичный ключ может быть использовать для шифрования произвольной части кода, но не может его расшифровать.

[![encryption](https://notepad.gasick.ru/uploads/images/gallery/2022-08/scaled-1680-/image-1660594505658.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-08/image-1660594505658.png)

Приватный ключ может быть использовать для расшифровки любой части данных которая была зашифрованна соотвесттвующим ключем. 

[![decryption](https://notepad.gasick.ru/uploads/images/gallery/2022-08/scaled-1680-/image-1660594517663.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-08/image-1660594517663.png)

Это значит, что мы даем наш публичный ключ, кому угодно. Они могут затем зашифровать любую информацию, которую они хотят нам послать, и единственный способ получить к ней доступ можно  только с помощью приватного ключа. 

[![key distribution](https://notepad.gasick.ru/uploads/images/gallery/2022-08/scaled-1680-/image-1660594544318.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-08/image-1660594544318.png)

# Генерация ключа
Первое, что мы хотим сделать, это создать пару ключей: `public` и `private`. Эти ключи случайным образом генерируются, и будут использоваться вдальнейшем. 

Мы используем `crypto/rsa` стандартную библиотеку для генерации ключа и `crypto/rand` библиотеку для генерации случайных чисел. 

```go
// Метод GenerateKey принимает reader, который возвращает случайные биты, и количество бит. 
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
	panic(err)
}

//  Публичный ключ это часть *rsa.PrivateKey структуры
publicKey := privateKey.PublicKey

// Используем приватный и публичные ключи 
// ...
```
Переменные `publicKey` и `privateKey` будут использоваться шифрования и расшифровки соответственно.

# Шифрование
Мы буддем использовать метод [EncryptOAEP](https://pkg.go.dev/crypto/rsa?tab=doc#EncryptOAEP) для шифрования случайного сообщения. Мы должны предоставить этому методу:

1) Хэширующую функцию, выбранную таким образом, что даже если ввод изменится едва ли, вывод должен менятся совершенно. Алгоритм SHA256 подходит для этого.
2) Случайный reader используемый для создания случаный битов, таким образом, чтобы каждый одинаковый ввод будет отдавать тот же вывод. 
3) Публичный ключ ранее сгенерированный. 
4) Сообщение которое мы шифруем. 
5) Дополнительные параметры, которые мы опустим в этот раз. 
```go
encryptedBytes, err := rsa.EncryptOAEP(
	sha256.New(),
	rand.Reader,
	&publicKey,
	[]byte("super secret message"),
	nil)
if err != nil {
	panic(err)
}

fmt.Println("encrypted bytes: ", encryptedBytes)
```
Функция выведет зашифрованные байты, которые выглядят как шум.

# Расшифровка
Чтобы получить доступ к содержанию информации в зашифрованных битах, её нужно расшифровать. 

Единственный способ расшифровать их, использовать приватный ключ соотвествующий публичному ключу, с помощью которого мы зашифровали. 


Структура `*rsa.PrivateKey` имеет метод `Decrypt` который мы будем использовать для получения оригинальной информации обратно из зашифрованной. 

Данные необходимые для расшифровки:
1) Зашифрованные данные(называемые cipher text)
2) Хэш который мы использовать для шафрования данных
```go
// Первый аргумент это генератор случайных данных( rand.Reader который мы использовали ранее)
// Мы можем указать это значение как nil
// OAEPOptions в конце означают, что мы шифруем данные используя OAEP, и то мы используем SHA256 в качестве входного хэша.
decryptedBytes, err := privateKey.Decrypt(nil, encryptedBytes, &rsa.OAEPOptions{Hash: crypto.SHA256})
if err != nil {
	panic(err)
}

// Мы возвращаем информацию в оригинальной форме байт, которые мы приводим к строке и выводим. 
fmt.Println("decrypted message: ", string(decryptedBytes))
```
# Подпись и проверка
RSA ключ используется так же для подписи и проверки. Подпись отличается от шифрования, что позволяет нам осуществить аутентификацию и не нарушить конфиденциальность. 

Что это значит? Что вместо маскирования содержания оригинального сообщения(как это было сделано ранее), часть данных созданная из сообщения называется `signature` сигнатурой. 

[![signing](https://notepad.gasick.ru/uploads/images/gallery/2022-08/scaled-1680-/image-1660594692439.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-08/image-1660594692439.png)

У кого есть сигнатура, сообщение и публичный ключ, могут использвать RSA для проверки, того, что сообщение действительно пришло от того, чей публичный ключ используется. Если данные или подпись не совпадают, проверка завершается неудачей.

[![verification](https://notepad.gasick.ru/uploads/images/gallery/2022-08/scaled-1680-/image-1660594699632.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-08/image-1660594699632.png)

Только часть с приватным ключем может подписать сообщение, но кто угодно с публичным ключем могут проверить его.
```go
msg := []byte("verifiable message")

// Перед попдписью, на мнужен хэш сообщения
// Хэш - то что мы подписали. 
msgHash := sha256.New()
_, err = msgHash.Write(msg)
if err != nil {
	panic(err)
}
msgHashSum := msgHash.Sum(nil)

// Чтобы сгенерировать подпись, нам нужен генератор случайных чисел. 
// наш приватный ключ, алгоритм хеширования, который мы использовали и хэш сумму нашего сообщения

signature, err := rsa.SignPSS(rand.Reader, privateKey, crypto.SHA256, msgHashSum, nil)
if err != nil {
	panic(err)
}

// Для проверки подписи, мы предоставляем публичный ключ, алгоритм хэширования,
// хэш сумму нашего сообщения и подпись которую мы ранее сгенеерировали. 
// А так же "options" параметры которые мы опустим
err = rsa.VerifyPSS(&publicKey, crypto.SHA256, msgHashSum, signature, nil)
if err != nil {
	fmt.Println("could not verify signature: ", err)
	return
}
// Если не получили ошибки от метода `VerifyPSS`, значит наша подпись верна.
fmt.Println("signature verified")
```
# Выводы
В этой статье мы разобрали, как сгенерировать RSA публичный и приватные ключи и как использовать их для шифрования, подписи и проверки данных.

Есть ограничение, о которых стоит знать прежде чем использовать алгоритм. 
1) Данные, которые вы пытаетесь зашифровать должны быть гораздо короче чем сила вашего ключа. Для примера: EncryptOAEP документация говорит, что сообщение не должно быть больше, чем длинна публичного модуля минус двойная длинна хэша, и дальше минус 2.
2) Используемый алгоритм хэширования должен быть подобран к случаю. SHA256(который мы используем) является достаточным для большинства случаем, но вы можете захотет использовать что-то по серьезнее типа SHA512 для критически важных данных приложения.

# Строим свой первый Rest API с помощью GO

У нас есть три необходимые части для построения.

* API
* Rest API
* Rest API на GO
# API
Если вы близки к компьютерам достаточно давно, вы возможно слышали об этих вещах. Что такое API?

API значит программный интерфейс приложения. Как и большинство вещей в информатике абревиатура не сильно помогает.

На самом деле это значит, что он выставялет функциональность без выставления внутренностй. Если ваша программа написана на языке который поддерживает функции или методы(большинство языков) вы должны понимать, о чём идет речь.

```go
func addNumber(a, b int) int {
    // DO AMAZING MATH HERE
    // and return the result
}
```
Даже если вы совсем новичок в go, вы можете сказать, что функция возвращает результат сложения двху чисел. 

Как пользователь функции вы просто вызываете функцию и не беспокоитесь о том, функция выполняет свою работу(не доверяйтся всем функциям подряд)

Это всё что такое API. API может быть функция которую вы написали, или функция из библиотеки или метод фреймворка, или http точка доступа.

# Rest API
Большинство API написаные в наши дни это web api. Только не цитируйте меня, так как я не делал никаких исследований чтобы получить настоящее число. Но учитывая количество web  сервисов и прилжений я не думаю, что я далеко от правды.

## What is REST
REST - это акроним для REpresentational State Transfer(передача репрезентативного состояния). Это архитекторный стиль распределнной гипермедия системы и была впервые представлена Роем Филдингом в 2000 году в его известной диссертации.

Как любые другие архитектурные стили, REST так же имеет свои 6 формирующих его ограничений, которые должны быть удовлетворены и если интерфейс хочет называться RESTful. Эти принципы приведены ниже.



### Ведущие принципы REST
1) Клиент-сервер - Разделяя проблемы пользовательских интерфейсов от проблем хранения данных, вы улучшаем  переносимость пользовательского интерфейса на многие платформы и улучшаем масштабируемость упрощением сервреных компонентов.
2) Не хранит состояние - каждый запрос от клиента к серверу должен содержать всю необходимую информацию для понимания запросов, и не может воспользоваться любых сохраненным содеражением на сервере. Состояние сессии отсюда хранится полностью на клиенте.
3) Кешируемость - Ограничения кэша требуют. чтобы данные в ответе на запрос были явно или не явно обозначены как кэшируемые или не кешуруемые. Если ответ кэшируемый, тогда право кэша клиента для переиспользования данные ответа в дальнейшем, похожем запросе. 
4) Универсальный интерфейс - Применя принцыпи програмной инженерии в целом к компонентам интерфейса, общая архитектура системы упрощена и видимость взаимодействий улучшена. Для того, чтобы получить универсальный интерфейс, множество универсальных ограничений нужны для указания поведения компонентов. REST определяется четырьмя ограничивающими интерфейсами: 
	* Определение ресурса
    * Упралвение ресурсами через репрезентацию
    * Самоописываемое сообщение
    * Гипермедия - как движок состояния приложения.
5) Слоёная система - Стиль слоёной системы позволяет архитектуре быть составленной из иерархических слоёв ограниченными поведением компонентов, так, что каждый компоненто не может "смотреть сковозь" слой с которым происходит взаимодействие. 
6) Код по запросу(по желанию) - REST позволяет расширить функциональность клиенту скачать и выполнив код в форме аплетов или скриптов. Это урощает клиентов с помощью сокращения количества особенностей требуемых для погдготовки реализации.

### HTTP глаголы
Вот несколько условностей соблюдаемы HTTP API. Это не часть спецификации REST. Но нам нужно это понять, что бы использовать REST API по-понной.

HTTP определяет набор методов-запросов чтобы указать желаемое действие для данного ресурса. Так же они могут быть существительными, эти методы-запросы иногда называются HTTP глаголы. Каждый из них реализует различную семантику, но которые общие особенности поделены на группы: например запрос может быть безопасный, неизменяемый или кэшируемый.



`GET` - метод запрашивает представление указанного ресурка. Запросы используещие `GET` должны только получать данные. 

`HEAD` - метод просит ответ идентичный `GET` запросу, но опускает `body`.

`POST` - методв используется для отправки сущности указанному ресурсу, часто является причиной изменения изменения состояния или имеет побочный эффект на сервер. 

`PUT` - метод заменяет все текущее представление целевого ресурса загруженным в запросе. 

`DELETE` - метод удаляет указанный ресурс. 

`CONNECT` - метод устанавливает тоннель к серверу указанному как целевой.

`OPTIONS` - метод используется для описания возможностей подключения к удаленному ресурсу.

`TRACE` - метод производит сообщение петлевого контроля на пути к ресурсу цели.

`PATCH` - метод используется для применения частичных изменений ресурса.

ЭТО ВСЁ ВРАНЬЕ.

### Статус коды
1xx Информация

100 Continue
101 Switching Protocols
102 Processing
2xx Success

200 OK
201 Created
202 Accepted
203 Non-authoritative Information
204 No Content
205 Reset Content
206 Partial Content
207 Multi-Status
208 Already Reported
226 IM Used
3xx Redirects

300 Multiple Choices
301 Moved Permanently
302 Found
303 See Other
304 Not Modified
305 Use Proxy
307 Temporary Redirect
308 Permanent Redirect
4xx Client Error

400 Bad Request
401 Unauthorized
402 Payment Required
403 Forbidden
404 Not Found
405 Method Not Allowed
406 Not Acceptable
407 Proxy Authentication Required
408 Request Timeout
409 Conflict
410 Gone
411 Length Required
412 Precondition Failed
413 Payload Too Large
414 Request-URI Too Long
415 Unsupported Media Type
416 Requested Range Not Satisfiable
417 Expectation Failed
418 I'm a teapot
421 Misdirected Request
422 Unprocessable Entity
423 Locked
424 Failed Dependency
426 Upgrade Required
428 Precondition Required
429 Too Many Requests
431 Request Header Fields Too Large
444 Connection Closed Without Response
451 Unavailable For Legal Reasons
499 Client Closed Request
5xx Server Error

500 Internal Server Error
501 Not Implemented
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout
505 HTTP Version Not Supported
506 Variant Also Negotiates
507 Insufficient Storage
508 Loop Detected
510 Not Extended
511 Network Authentication Required
599 Network Connect Timeout Error
This also has no actual meaning.

## Терминология
Ниже - самые важные понятия связанные с REST API.

* Ресурс - объект или представление чего-то, что имеет некоторую ассоциацию данных с ним и может иметь набор методов для обработки оных. К примеру: Живтоные, школы или работники ресурсы, а `delete`, `add`, `update` - операции для обработки этих данных.
* Коллекции - наборы ресурсов: Компании это наборы ресурсов компаний.
* URL - это путь по которому ресурс может быть определен и действия которые необходимо с ним произвести.

### API Endpoint
Вот как выглдяит пример такой точки:
	
    https://www.github.com/golang/go/search?q=http&type=Commits
    
Разделим URL на части:
```
protocol	subdomain	domain	path	Port	query
http/https	subdomain	base-url	resource/some-other-resource	some-port	key value pair
https	www	github.com	golang/go/search	80	?q=http&type=Commits
```
#### Протоколы
Как браузер или клиент должен взаимодействовать с сервером.

#### Поддомен
Подраздел главного домена.

#### Порт
Порт сервера на котором запущено приложения. По умолчанию это 80. Но в большинстве случаем вы его не видим.

#### Петь
Пусть - параметры REST API отражающие ресурсы.

```
https://jsonplaceholder.typicode.com/posts/1/comments
posts/1/comments
```
Этот пусть отражает коментарии 1го поста ресурса.

Базовой структурой является
```
top-level-resource/<some-identifier>/secondary-resource/<some-identifier>/...
```
#### Запрос
Запросы - пары ключ-значение информации, используемый в основном для целей фильтрования. 
```
https://jsonplaceholder.typicode.com/posts?userId=1
```
Часть после `?` это параметры запроса. У нас есть только один запрос тут: `userID=1`.

#### Заголовки
Это не часть самого URL, но заголовки это часть сетевого компонента посылаемые клиентом или сервером. В зависимости от того, кто послал его. Есть 1 типа заголовков.

1) Заголовок запроса (client -> server)
2) Заголовок ответа (server -> client)
#### Тело
Вы можете добавить дополнительную инфомацию в обза запроса к серверу и к ответу от сервера. 

### Тип ответа
Обычно JSON или XML.

В наши дни это обычно  JSON.

## Rest API на GO
Это то почему вы тут. Ну или я, по крайней, мере я надеюсь на это.

Если вы пишите REST API, почему вы должны выбрать Go?

* Он компилируем. Вы получаете маленькие бинарники.
* Он быстр. Медленнее чем c/c++ или rust, но быстрее чем большинство других языков web программирования.
* Он легок в изучении.
* Он работает отлично в мире микросервисов - это причини номер 1.

### net/http
Стандартная библиотека в go идущая с `net/http` пакетом, что является отличной точкой отсчета для построения REST API. И большинство других библиотек добавлюят особенности тоже взаимодействуют с `net/http` пакетом, поэтому понимание пакется является критичным для использования golang в качестве REST API.
```
net/http
```
Нам, возможно, не нужно знать всё в пакете `net/http`. но есть несколько вещей, который мы должны знать для начала.

### Интерфейс обработчика
нам нужно помнить интерфейс обработчика:
```go
type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}
```
Ну вот и он.

У него есть тольк один метод и только.

Структура или объект будет обработан если он имеет один метод `ServeHTTP` который принимает `ResponseWriter` и указатель на `Request`.

Теперь, с этим знанием, мы готовы к некоторым увечьям.

# Начнем
Я думаю мы готовы начать.

Мы узнали много теории. Я обещал что вы создадите свой первый RestAPI.

## Простой Rest API
В папке где вы хотите писать ваш код выполните команду в терминале:
```bash
go mod init api-test
```
Созайте новый файл, можно назвать его как угодно.

Я назвал свой main.go
```go
package main

import (
    "log"
    "net/http"
)

type server struct{}

func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"message": "hello world"}`))
}

func main() {
    s := &server{}
    http.Handle("/", s)
    log.Fatal(http.ListenAndServe(":8080", nil))
}
```
Пройдемся по коду.

В самом начала, у нас стоит строка `package main`, все go исполняемые файлы должны иметь `main` пакет.
Дальше идут иморты: 
* `log` для логирования ошибок, если случаются. 
* `net/http` потому что мы пишем rest api.

Далее идет структура под названием `server`. Она не имеет полей. Добавим метод `ServerHTTP` этому `server`, чтобы удовлетворить обработчик интерфейс. Одна вещь, которую вы можете заметить в go, нам не нужно явно говорить какой из интерфейсов мы реализуем. Компилятор достаточно умен, чтобы выяснить это самостоятельно. В `ServerHTTP` методе мы настраиваем `httpStatus` 200, чтобы обозначить, что запрост прошел успешно. Мы видим тип содержания `application/json` так, что клиент понимает когда мы отправляет ему что-то полезное. В мы пишем `{"message": "hello world"}` в ответ.

Запустим наш сервер.
```bash
go run main.go
```
Для проверки выполняем команду из соседнего терминала:
```bash
curl localhost:8000
```
В ответ получае json описанный выше.
Отличная работа!

Но подождите.

Давайте посмотрим какие другие HTTP глаголы обрабатывает наше приложение.

Теперь попробуем выполлнить POST запрос:
```bash
curl localhost:8000 -X POST
```
Когда мы выполняем запрос, то получаем тот же самый результат.

Это вовсе не баг. Но часто мы будем хотеть чтобы выполнялись разные задачи в зависимости от типа запроса.

Посмотрим, что мы тут можем сделать.

Изменим наш `ServerHTTP` метод следующим образом:

```go
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    switch r.Method {
    case "GET":
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"message": "get called"}`))
    case "POST":
        w.WriteHeader(http.StatusCreated)
        w.Write([]byte(`{"message": "post called"}`))
    case "PUT":
        w.WriteHeader(http.StatusAccepted)
        w.Write([]byte(`{"message": "put called"}`))
    case "DELETE":
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"message": "delete called"}`))
    default:
        w.WriteHeader(http.StatusNotFound)
        w.Write([]byte(`{"message": "not found"}`))
    }
}
```
Если наш сервер уже запущен, останавливаем его нажимая CTRL-C.

И заново запускаем, чтобы изменения из файла применились.
```bash
go run main.go
```
Проверяем, как теперь себя ведет reas api, в зависимости от типа запроса.
```bash
$ curl localhost:8000
{"message": "get called"}
$ curl localhost:8000 -X POST
{"message": "post called"}
$ curl localhost:8000 -X PUT
{"message": "put called"}
$ curl localhost:8000 -X DELETE
{"message": "delete called"}
$ curl localhost:8000/test
{"message": "not found"}
```
Можно заметить, что мы используем структуру нашего сервера в том числе с помощью метода.

Go команда знала что это будет не удобно, и дала нам `HandleFunc` метод в `http` пакете который позволяет нам передавать функцию которая имеет ту же подпись что и `ServerHTTP` и может обслуживать маршруты.

Немного упростим наш код.
```go
package main

import (
    "log"
    "net/http"
)

func home(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    switch r.Method {
    case "GET":
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"message": "get called"}`))
    case "POST":
        w.WriteHeader(http.StatusCreated)
        w.Write([]byte(`{"message": "post called"}`))
    case "PUT":
        w.WriteHeader(http.StatusAccepted)
        w.Write([]byte(`{"message": "put called"}`))
    case "DELETE":
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"message": "delete called"}`))
    default:
        w.WriteHeader(http.StatusNotFound)
        w.Write([]byte(`{"message": "not found"}`))
    }
}

func main() {
    http.HandleFunc("/", home)
    log.Fatal(http.ListenAndServe(":8080", nil))
}
```
Функциональность не должна измениться.


## Gorilla Mux
`net/http` со встроенными методами - это отлично. Мы можем написать сервер без внешних библиотек. Но `net/http` имеет ограничения. Нет прямого пути взаимодейстия с параметрами. Так же как и с запросами, нам нужно обрабатывать и параметры запроса в ручную.

Gorilla Mux очень популярная библиотека которая работает отлично по сравнению с `net/http` пакетом и помогает нам с некоторыми вещями при создании api.

### Использование Gorilla Mux
Чтобы установить этот пакет будем использовать `get`.
Под капотом `go get` использует `git`.
В папке где лежит `go.mod` и `main.go`, запустите:
```bash
go get github.com/gorilla/mux
```

Изменим наш код следующим образом.

```go
package main

import (
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

func home(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    switch r.Method {
    case "GET":
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"message": "get called"}`))
    case "POST":
        w.WriteHeader(http.StatusCreated)
        w.Write([]byte(`{"message": "post called"}`))
    case "PUT":
        w.WriteHeader(http.StatusAccepted)
        w.Write([]byte(`{"message": "put called"}`))
    case "DELETE":
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"message": "delete called"}`))
    default:
        w.WriteHeader(http.StatusNotFound)
        w.Write([]byte(`{"message": "not found"}`))
    }
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", home)
    log.Fatal(http.ListenAndServe(":8080", r))
}
```
Выглядит, так как-будто ничего не изменилось за исключением строки с импортом и строки под номером 32.

### HandleFunc HTTP Методы
Но теперь мы можем делать немного больше с помощью `HandleFunc`, к примеру создание каждого обработчика функции для определенного метода HTTP.
Выглядить это будет следующим образом.
```go
package main

import (
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

func get(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"message": "get called"}`))
}

func post(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    w.Write([]byte(`{"message": "post called"}`))
}

func put(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusAccepted)
    w.Write([]byte(`{"message": "put called"}`))
}

func delete(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"message": "delete called"}`))
}

func notFound(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusNotFound)
    w.Write([]byte(`{"message": "not found"}`))
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", get).Methods(http.MethodGet)
    r.HandleFunc("/", post).Methods(http.MethodPost)
    r.HandleFunc("/", put).Methods(http.MethodPut)
    r.HandleFunc("/", delete).Methods(http.MethodDelete)
    r.HandleFunc("/", notFound)
    log.Fatal(http.ListenAndServe(":8080", r))
}
```
Если вы запустите, то программа должна всё еще делать всё тоже самое.
Теперь вы гадаете, что почему этот вариант лучше, елси строчек кода получилось больле?
Но подумайте так: Наш код стал гораздо чище, и еще большее понятен.

	Лучше чище, чем умнее.

### Подмаршруты
```go
func main() {
    r := mux.NewRouter()
    api := r.PathPrefix("/api/v1").Subrouter()
    api.HandleFunc("", get).Methods(http.MethodGet)
    api.HandleFunc("", post).Methods(http.MethodPost)
    api.HandleFunc("", put).Methods(http.MethodPut)
    api.HandleFunc("", delete).Methods(http.MethodDelete)
    api.HandleFunc("", notFound)
    log.Fatal(http.ListenAndServe(":8080", r))
}
```
Всё остается тем же самым за исплючением создания `sub-router`(подмаршрут). Подмаршрут очень полезен, когда нам нужно поддерживать большое количество ресурсов. Он помогает нам группировать содержание, а так же защищает нас от перенабора одного и того же префикса пути.

Пенесем наше `api` в `api/va`. Таким образом мы можем создавать v2 и так далее.

###  Параметры пути и запроса
```go
package main

import (
    "fmt"
    "log"
    "net/http"
    "strconv"

    "github.com/gorilla/mux"
)

func get(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"message": "get called"}`))
}

func post(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    w.Write([]byte(`{"message": "post called"}`))
}

func put(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusAccepted)
    w.Write([]byte(`{"message": "put called"}`))
}

func delete(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"message": "delete called"}`))
}

func params(w http.ResponseWriter, r *http.Request) {
    pathParams := mux.Vars(r)
    w.Header().Set("Content-Type", "application/json")

    userID := -1
    var err error
    if val, ok := pathParams["userID"]; ok {
        userID, err = strconv.Atoi(val)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(`{"message": "need a number"}`))
            return
        }
    }

    commentID := -1
    if val, ok := pathParams["commentID"]; ok {
        commentID, err = strconv.Atoi(val)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(`{"message": "need a number"}`))
            return
        }
    }

    query := r.URL.Query()
    location := query.Get("location")

    w.Write([]byte(fmt.Sprintf(`{"userID": %d, "commentID": %d, "location": "%s" }`, userID, commentID, location)))
}

func main() {
    r := mux.NewRouter()

    api := r.PathPrefix("/api/v1").Subrouter()
    api.HandleFunc("", get).Methods(http.MethodGet)
    api.HandleFunc("", post).Methods(http.MethodPost)
    api.HandleFunc("", put).Methods(http.MethodPut)
    api.HandleFunc("", delete).Methods(http.MethodDelete)

    api.HandleFunc("/user/{userID}/comment/{commentID}", params).Methods(http.MethodGet)

    log.Fatal(http.ListenAndServe(":8080", r))
}
```
Давайте посмотрим на параметры функции в строке 36. Мы обрабатываем оба параметра: путь и запрос.

Теперь вы знаете достаточно, чтобы быть "опасным".


## Книжное API
В Kaggle есть набор данных по книгам. Это csv файл с 13000 книгами. Мы будем использовать его для создания нешго api.

[books.csv](https://raw.githubusercontent.com/moficodes/rest-api-with-go/master/gitbook/.gitbook/assets/books.csv])

Файл можно найти выше.

### Склонируем репозиторий
В отдельной папке.
```bash
git clone https://github.com/moficodes/bookdata-api.git
```
### Пройдемся по коду
Есть ва пакета внутри кода. Один называется `datastore`, другой - `loader`.
* `lader` - конвертирует csv данные в массив объектов с данными о книге. 
* `datastore` работает с доступом к масиву. Обычно это интерфейс который имеет метод.

### un app
Из корня репозитория запустите:
```bash
go run .
```
### Точки доступа
У приложения есть несколько точек доступа. 
Все точки доступа api имеют префикс `/api/v1`.
Чтобы достичь какую либо из них, нужно использовать базовый адрес: `baseurl:8080/api/v1/{endpoint}`

Получить книги для автора:
	
    "/books/authors/{author}" 

Можно указать параметр запроса для пределов `ratingAbove` и `ratingBelow`

---
Получить книги по названию.
	
    "/books/book-name/{bookName}"

Можно указать параметр запроса для пределов `ratingAbove` и `ratingBelow`

---
Получить книгу по ISBN
	
    "/book/isbn/{isbn}"

---
Удалить книгу по ISBN

	"/book/isbn/{isbn}"

---
Создать новую книгу

	"/book"

# Echo framework + GORM = Огненно быстрое Golang приложение на стороне сервера. Пример аутентификации.

В статье, я хочу показать пример реализации входа, выхода и регистрации используя Go фреймворк Echo и Gorm для postgreSQL. Для аутентификации пользователей будет использоваться JWT(Json web token).

Для начала создадим `main.go`:

```go
package main

import (
	"app1/helper"
	"app1/models"
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

func main() {
	configuration := helper.GetConfig()

	gormParameters := fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=disable", configuration.DbHost, configuration.DbPort, configuration.DbName, configuration.DbUsername, configuration.DbPassword)
	gormDB, err := gorm.Open("postgres", gormParameters)
	if err != nil {
		panic("failed to connect database")
	}
	helper.GormDB = gormDB

	// Migrate the schema (tables): User, Authentication
	helper.GormDB.AutoMigrate(&helper.User{})
	helper.GormDB.AutoMigrate(&helper.Authentication{})
	helper.GormDB.Model(&helper.Authentication{}).AddForeignKey("user_id", "users(id)", "CASCADE", "CASCADE")

	echoFramework := echo.New()
	echoFramework.Use(middleware.Logger()) // log
	echoFramework.Use(middleware.CORS())   // CORS from Any Origin, Any Method

	echoGroupUseJWT := echoFramework.Group("/api/v1")
echoGroupUseJWT.Use(middleware.JWT([]byte(configuration.EncryptionKey)))
	echoGroupNoJWT := echoFramework.Group("/api/v1")

	// /api/v1/users : logged in users
	echoGroupUseJWT.POST("/users/logout", models.Logout)

	// /api/v1/users : public accessible
	echoGroupNoJWT.POST("/users", models.CreateUser)
	echoGroupNoJWT.POST("/users/login", models.Login)

	defer helper.GormDB.Close()
	echoFramework.Logger.Fatal(echoFramework.Start(":1323"))
}
```
Второе - создадим `globals.go`:
```go
package helper

import (
	"regexp"
	"time"
	"github.com/jinzhu/gorm"

)

type Configuration struct {
	EncryptionKey string
	DbHost        string
	DbPort        string
	DbName        string
	DbUsername    string
	DbPassword    string
}

var configuration Configuration
type ModelBase struct {
	ID        int       `gorm:"primary_key"`
	CreatedAt time.Time `json:"-"`
	UpdatedAt time.Time `json:"-"`
}

type User struct {
	ModelBase        // replaces gorm.Model
	Email     string `gorm:"not null; unique"`
	Password  string `gorm:"not null" json:"-"`
	Name      string `gorm:"not null; type:varchar(100)"` // unique_index
}

type Authentication struct {
	ModelBase
	User   User `gorm:"foreignkey:UserID; not null"`
	UserID int
	Token  string `gorm:"type:varchar(200); not null"`
}

type CustomHTTPSuccess struct {
	Data string `json:"data"`
}

type ErrorType struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

type CustomHTTPError struct {
	Error ErrorType `json:"error"`
}

var GormDB *gorm.DB
func init() {
	configuration = Configuration{
		EncryptionKey: "F61L8L7CUCGN0NK6336I8TFP9Y2ZOS43",
		DbHost:        "localhost",
		DbPort:        "5432",
		DbName:        "postgres",
		DbUsername:    "postgres",
		DbPassword:    "postgres",
	}
}

func GetConfig() Configuration {
	return configuration
}

func ValidateEmail(email string) (matchedString bool) {
	re := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
	matchedString = re.MatchString(email)
	return
}
```
Теперь реализуем метод аутентификации в `user.go`:

```go
package models

import (
	"app1/helper"
	"fmt"
	"net/http"
	"strconv"
	jwt "github.com/dgrijalva/jwt-go"
	"github.com/labstack/echo"
)

func CreateUser(c echo.Context) error {
	m := echo.Map{}
	if err := c.Bind(&m); err != nil {
		// return err
	}

	name := m["name"].(string)
	email := m["email"].(string)
	password := m["password"].(string)
	confirmPassword := m["confirm_password"].(string)
	if password == "" || confirmPassword == "" || name == "" || email == "" {
		return echo.NewHTTPError(http.StatusBadRequest, "Please enter name, email and password")
	}
	if password != confirmPassword {
		return echo.NewHTTPError(http.StatusBadRequest, "Confirm password is not same to password provided")
	}
	if helper.ValidateEmail(email) == false {
		return echo.NewHTTPError(http.StatusBadRequest, "Please enter valid email")
	}
	if bCheckUserExists(email) == true {
		return echo.NewHTTPError(http.StatusBadRequest, "Email provided already exists")
	}
	configuration := helper.GetConfig()
	enc, _ := helper.EncryptString(password, configuration.EncryptionKey)
	user1 := helper.User{Name: name, Email: email, Password: enc}
  
	// globals.GormDB.NewRecord(user) // => returns `true` as primary key is blank
	helper.GormDB.Create(&user1)
	token := jwt.New(jwt.SigningMethodHS256)
	claims := token.Claims.(jwt.MapClaims)
	claims["name"] = user1.Name
	claims["email"] = user1.Email
	t, err := token.SignedString([]byte(configuration.EncryptionKey)) // "secret" >> EncryptionKey
	if err != nil {
		return err
	}
	authentication := helper.Authentication{}
	if helper.GormDB.First(&authentication, "user_id =?", user1.ID).RecordNotFound() {
		// insert
		helper.GormDB.Create(&helper.Authentication{User: user1, Token: t})
	} else {
		authentication.User = user1
		authentication.Token = t
		helper.GormDB.Save(&authentication)
	}
	return c.JSON(http.StatusOK, map[string]string{
		"token": t,
	})
}
func bCheckUserExists(email string) bool {
	user1 := helper.User{}
	if helper.GormDB.Where(&helper.User{Email: email}).First(&user1).RecordNotFound() {
		return false
	}
	return true
}
func ValidateUser(email, password string, c echo.Context) (bool, error) {
	fmt.Println("validate")
	var user1 helper.User
	if helper.GormDB.First(&user1, "email =?", email).RecordNotFound() {
		return false, nil
	}
	configuration := helper.GetConfig()
	decrypted, _ := helper.DecryptString(user1.Password, configuration.EncryptionKey)
	if password == decrypted {
		return true, nil
	}
	return false, nil
}

func Login(c echo.Context) error {
	m := echo.Map{}
	if err := c.Bind(&m); err != nil {
		// return err
	}
	email := m["email"].(string)
	password := m["password"].(string)
	var user1 helper.User
	if helper.GormDB.First(&user1, "email =?", email).RecordNotFound() {
		_error := helper.CustomHTTPError{
			Error: helper.ErrorType{
				Code:    http.StatusBadRequest,
				Message: "Invalid email & password",
			},
		}
		return c.JSONPretty(http.StatusBadGateway, _error, "  ")
	}
	configuration := helper.GetConfig()
	decrypted, _ := helper.DecryptString(user1.Password, configuration.EncryptionKey)
	if password == decrypted {
		token := jwt.New(jwt.SigningMethodHS256)
		claims := token.Claims.(jwt.MapClaims)
		claims["name"] = user1.Name
		claims["email"] = user1.Email
		claims["id"] = user1.ModelBase.ID
		t, err := token.SignedString([]byte(configuration.EncryptionKey)) // "secret" >> EncryptionKey
		if err != nil {
			return err
		}
		authentication := helper.Authentication{}
		if helper.GormDB.First(&authentication, "user_id =?", user1.ID).RecordNotFound() {
			// insert
			helper.GormDB.Create(&helper.Authentication{User: user1, Token: t})
		} else {
			// update
			authentication.User = user1
			authentication.Token = t
			helper.GormDB.Save(&authentication)
		}
		return c.JSON(http.StatusOK, map[string]string{
			"token": t,
		})
	} else {
		_error := helper.CustomHTTPError{
			Error: helper.ErrorType{
				Code:    http.StatusBadRequest,
				Message: "Invalid email & password",
			},
		}
		return c.JSONPretty(http.StatusBadGateway, _error, "  ")
	}
}
func Logout(c echo.Context) error {
	tokenRequester := c.Get("user").(*jwt.Token)
	claims := tokenRequester.Claims.(jwt.MapClaims)
	fRequesterID := claims["id"].(float64)
	iRequesterID := int(fRequesterID)
	sRequesterID := strconv.Itoa(iRequesterID)
	requester := helper.User{}
	if helper.GormDB.First(&requester, "id =?", sRequesterID).RecordNotFound() {
		return echo.ErrUnauthorized
	}
	authentication := helper.Authentication{}
	if helper.GormDB.First(&authentication, "user_id =?", requester.ModelBase.ID).RecordNotFound() {
		return echo.ErrUnauthorized
	}
	helper.GormDB.Delete(&authentication)
	return c.String(http.StatusAccepted, "")
}
```

Пропущенный функции шифроки\расшифровки. Напишите мне чтобы их увидеть.

# Go — Raspberry Pi GPIO "Привет мир" руководство

Для относительно нового языка, к которым относится  Go.  Я использовать его с тех годов, по нескольким причинам, так как он полон сюрпризов.

Поэтому недавно, я купил новую  pi3 и в основном использовал C  для прошлой pi2. Однако, отметим, множество руководств использует питон, а я не большой фанат этого языка. Поэтому я случайным образом поискал библиотеке  Go и различные руководства для Pi GPIO,  и поигрался чуток с проводульками у меня дома.

В этой статье мы собираемся построить очень простую  LED переключалку используя  Raspberry Pi  и язык  Golang.

Мы можем установить целое Go окружение внутри  Raspberry Pi, однако, это займет много ресурсов внутри маленькой машинки. Поэтому, я предпочитаю разрабатывать и строить программу удаленно с моего рабочего места.

# 1. Используем GPIO
Мы собираемся создать очень простую мерцающую программу, которую усстановим и запустим на  Raspberry PI. Поэтому. перед этим, нужно понять базу, как работать с  GPIO.

Самый простой способ считать  GPIO интерфейсом, который лежит между системой Pi и внешней системой. IO модуль будет использовать GPIO пины, таким образом, чтобы raspberry могла читать ввод или показывать вывод от девайса или сенсора.

[![FIg 2. Raspberry Pi 2 GPIO Map](https://notepad.gasick.ru/uploads/images/gallery/2021-10/scaled-1680-/image-1634898347231.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-10/image-1634898347231.png) 

Как показано на рисунке, устройство имеет несколько типов пинов, такие как GPIO, земля, 3.3 вольтовые и 5 вольтовые пины. Для нашей инструкции мы собираемся играться с  GPIO 18 пином и землей.

Мы так же можем получить доступ к  GPIO информации через термина исопльзуются команду `pinout`.

# 2. Собираем LED устройство 
Для этой инструкции, мы собираемся подготовить несколько вещей:
1. USB кабель и зарядник для питания Raspberry PI,
2. 2 коротких переключателя проводов,
3. Макет,
4. LED, and
5. 330Ω резистор

Я не буду рассказывать глубого процесс установки, так как это легко можно найти в интернете.

[![Figure 3. Raspberry Pi 3 with LED](https://notepad.gasick.ru/uploads/images/gallery/2021-10/scaled-1680-/image-1634898380275.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-10/image-1634898380275.png)
# 3. Разработка Go  кода 
Наконец, мы собираемся разработать код используя Go. Перво-наперво, мы будем использовать доступный такой пакет в интернете, как:

https://github.com/stianeikeland/go-rpio, and http://gobot.io/documentation/platforms/raspi/


Go-rpio поддерживает операции для GPIO используя Golang, поэтому я верю, что он достаточно подходит для наших целей. В то время как Gobot предоставялет целую SDK библиотеку для доступа к GPIO пинам и специфические драйвера как: LED, мотор, реле, сервопривод и т.д.

В этом случае, я собираюсь использовать Go-rpio для базового LED примера, так как мы не собираемся использовать эти дравйвера на данный момент.

Как я упоминал в самом начале, мы собираемся разрабатывать удалеенно. Поэтому сперва, скачаем пакет который будем использовать.
```bash
go get github.com/stianeikeland/go-rpio
```

Это пример кода для создания мерцающего приложения используя Go  на raspberry pi:

```go
package main

import (
	"fmt"
	"time"

	"github.com/stianeikeland/go-rpio"
)

func main() {
	fmt.Println("opening gpio")
	err := rpio.Open()
	if err != nil {
		panic(fmt.Sprint("unable to open gpio", err.Error()))
	}

	defer rpio.Close()

	pin := rpio.Pin(18)
	pin.Output()

	for x := 0; x < 20; x++ {
		pin.Toggle()
		time.Sleep(time.Second / 5)
	}
}
```
Объяснение:
Этот код открывает GPIO и устанавливает режим приема на пине 18. Пин будет настроен для вывода(Ввода для чтения, типа сенсора и т.д.). 20 переключит режим пина(Low > High > Low) и установит задержку 200ms. Как только операция выполнится, программа будет завершена. 
# 4. Сборка и загрузка программы. 
Для того, чтобы безопасно собрать программу мы будем использовать команду:
```bash
env GOOS=linux GOARCH=arm GOARM=5 go build
```
Команда выше говорит компилятору. что мы собираем программу для Linux и  ARM  архитектуру. На данный момент, есть поддержка нескольких версий ARM компилятором Go, но мы ищем версию 5. 
Затем мы собираемся загрузитьь \ скопировать файл на подключенную raspberry pi. Для этой цели, мы собираемся использовать scp команду.
```bash
scp [go binary] pi@192.168.43.208:[remote dir]
```
# 5. Запуск программы
Как только бинарник был загружен(для примера, мы загрузили его по пути /home/pi/go/gopio), то чтобы его запустить нужно выполнить команду:
```bash
./gopio
```
Если программа верно собрана, и нет ошибок в коде, LED будет мерцать и остановится после 20 переключений.

[![Figure 4. Raspberry Pi LED Blink](https://notepad.gasick.ru/uploads/images/gallery/2021-10/scaled-1680-/image-1634898394920.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-10/image-1634898394920.png)
# 6. Выводы 
Можно использовать Raspberry-Pi для разработки программ внутри Raspberry-Pi. В этой статье, я показал вам простой пример, как создать мерцающий LED используя Raspberry Pi, на языке Golang.
# 7. Image Link and Resources
* [turning-on-an-led-with-your-raspberry](https://thepihut.com/blogs/raspberry-pi-tutorials/27968772-turning-on-an-led-with-your-raspberry-pis-gpio-pins)
* [cross-compiling-golang-applications-raspberry-pi](https://www.thepolyglotdeveloper.com/2017/04/cross-compiling-golang-applications-raspberry-pi/)
* [go-rpio](https://github.com/stianeikeland/go-rpio)
* [gobot](https://gobot.io/documentation/platforms/raspi/)
* [Raspberry Pi icon in Color Style](https://icons8.com/icon/13443/raspberry-pi)

# Go: Ввдение в сокеты межпроцессного взаимодействия(unixsocket)

Сокеты межпроцессного взаимодействия предлагают эффективное,безопасное двухсторонее подключение между процессами на Unix/Linux машинах. В то время как каналы отлично используются для подключения между горутинами приложения, и HTTP вездесущь, при подключении между Go приложениями(межпроцессороное взаимодействие) запущенные на той же машине каналы не помогают, а подключление к сокету межпроцессного взаимодействия гораздо проще, эффективнее, и более безопасно чем HTTP или другие интернет протоколы подключений.

Всё что вам нужно это только пакет `net` для запуска подключения:
* Пакет `net` предоставляет перносимый интерфейс для сети I/O, включая Unix сокеты.
* Так же пакет предоставляет доступ к низкоуровневым примитивам сети, большинству клиентов требуедсят только базовый интерфейс п редоставляемый `Dial`, `Listen` и `Accept` функциями и связанные `Conn` и `Listener` интферфейсы.

К сожалению эти фукнции и интерфейсы редко документированны(в частности сокеты межпроцессного взаимодействия), так же нет официального Go блога о том, как с чего начать при работе с сокетами. Похоже что недостаток хорошего ввдения на StackOverflow и Go блогах. Большинство статей о сокетах показывают C реализацию; где я сосредоточусь на том как начать Работать с Go.

Первое, сокерт представлен специальным фалом. Ваш сервер слушает через файл, принимае подключения и читает данные через это подключение. Ваш клиент использует файл, чтобы создать подключение и затем пишет данные в это самое подключение.

Вы возможно думаете, что вам нужно создать этот специальный файл используя пакет `os`. В нем вы можете найти постоянную `FileMode`, `ModeSocket`, которая может взывать к вам. Но это не поможет: Незадокументированный функцонионал функции `Listen` заключается в том, что (это должно) создать файл для вас, и он будет существовать с ошибкой вводящей в заблуждение: `“bind: address already in use”`, если файл уже существует. Отсюда дла начала, нелогичный шаг в создании сервера это удаление файла который вы собираетесь слушать, и только помто его можно слушать:
```go
    os.Remove(cfg.SocketFile)
    listener, err := net.Listen("unix", cfg.SocketFile)
    if err != nil {
        log.Fatalf("Unable to listen on socket file %s: %s", cfg.SocketFile, err)
    }
    defer listener.Close()
```
Для сервера который управляет постоянно и обрабатывает множество похожих подключений, вы захотите использовать бесконечный цикл, в котором вы примете подключение в `listener` и начнете новую горутину для его обработки:
```go
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Fatalf("Error on accept: %s", err)
        }
        go message.HandleConn(conn)
    }
```
Ваш обработчик должен создать буффер любого желаемого размера, и прочитать в него бесконечный цыкл который остановится когда `Read` выдаст ошибку. `Read` едва документирован в `net` пакете. Странным образом, вы должны знать что дальше нужно смотреть в документацию `io` пакета для `Reader` интерфейса:
```go
    type Reader interface {
            Read(p []byte) (n int, err error)
    }
```
... чтобы узнать важную информацию про поведение `Read` в других пакетах стандартной библиотеки:
	
* `Reader` - интерфейс который оборачивает базовый метод `Read`
* `Read` читает `len(p)` байтов в  `p`. Он возвращяет количество прочитанных байтов(0<= n <= len(p)) в том числе любую ошибку. Даже если `Read` вернет n < len(p), он может использовать всё из `p`  как испорченное пространство в время вызова. Если какие-то данные доступны, но длинной не равной `len(p)` байт, `Read` вернеть то что доступно, вместо того, чтобы чего-то ждать.
* Когда `Read` встречает ошибку или `EOF` условние после успешного чтения n > 0 байтов, он возвращает количество прочитанных байтов. Может вернуть `non-nil` ошибку из того же вызова или вернуть ошибку `n == 0` из подпоследовательности вызовов. Экземпляр данного конкретного случая это то что `Reader` возвращает `non-zero` число байтов в конце потока ввода, может так же вернуть или `err == EOF` или `err == nil`. Следующий `Read` должен вернуть `0`, `EOF`.
* Функции должны всегда обрабатывать `n>0` байтов возвращенные перед учитываением ошибки `err`. Выполнение этого условия обрабатывает `I/O` ошибки, которые случаются после чтения неких байтов и так же обоим позволено `EOF` поведение.
* Реализация Read обескураживает от возвращения `0` байтов с `nil` ошибкой, за исключением когда `len(p) == 0`.
Функция должна отнестись к возврату `0` или `nil`, как к индикатору что ничег не случилось, в частности это не говорит об `EOF`(конце файла)
* Реализация не должна удерживать `p`:

Как при загадочном старте, буфер который вы передаете `Read` в форме среза байтов, который должен иметь длинну больше чем нуль, для того, чтобы в него что-то прочитать. Это совмещается с передачей среза больше чем указателя на срез, потому что любое увеличение длинны среза внутри `Read` не будет видно в вызове контекста без использования указателя. Относительно общий баг в использовании `Read` это буфер с нулевой длинной.

Другой распространненый баг это игнорирвание предостережения выше, обрабатывать возвращенный байты до обработки ошибок. Это контрастирует с советом общей обработки ошибок, в большинстве программных контекстов на Go, и очень важно исправить реализацию основанных на `net` поключений вообще.

Ниже пример обработчика который решает эти проблемы. Он читает в буфер с длинной больше чем ноль внутри бесконечного цикла, и прервыается только при ошибке. После каждого `Read`, первый счетчик байтов буфера поглащается перед обработкой ошибок:
```go
func HandleConn(c net.Conn) {
    received := make([]byte, 0)
    for {
        buf := make([]byte, 512)
        count, err := c.Read(buf)
        received = append(received, buf[:count]...)
        if err != nil {
            ProcessMessage(received)
            if err != io.EOF {
                log.Errorf("Error on read: %s", err)
            }
            break
        }        
    }
}
```
Этим методом, все данные отправленные подключением воспринимаются сервером как одно сообщение. Клиент должен закрыть подключение сигналом о конце сообщения. Чтение закрытого подключения вренуть ошибку `io.EOF`, которая не должна быть обработа как обыная ошибка. Это просто сигнал о том, что сообщение закончилось, часто подсказка к началу обработки сообщения так как оно закончено.

Что происходит в `ProcessMessage`, конечно, зависит от вашего приложения. Так как строка это по-факту срез байтов, только для чтения, это маленькая попытка для связи текстовых данных таким образом. Но байтовый срез так же распространненая валюта в стандартной библиотеке Go, и любые данные могут быть зашифрованы как срез байтов.

Всё что нам осталось - это сделать клиента. Клиент - это просто функция которая поднимает сокет файл для того чтобы создать подключение, откладывает закрытие подключения, и пишет байтовое сообщение в подключение. Не нужно беспокоится о размере сообщения, оно может быть очень большое, но код не изменится. Ниже пример с логированием ошибок:
```go
type Sender struct {
    Logger     *log.Logger
    SocketFile string
}

func (s *Sender) SendMessage(message []byte) {
    c, err := net.Dial("unix", s.SocketFile)
    if err != nil {
        s.Logger.Errorf("Failed to dial: %s", err)
    }
    defer c.Close()
    count, err := c.Write(message)
    if err != nil {
        s.Logger.Errorf("Write error: %s", err)
    }
    s.Logger.Infof("Wrote %d bytes", count)
}
```
Следующим шагом может быть добавление ответа с подтверждением полученного. Для множества приложений, выше приведенная инструкция это всё что нужно для начала связи между GO процессами использующими Unix сокеты.

# Go: горутины , потоки ОС и управление ЦПУ

Создание потоков ОС или переключение из одного в другой может стоить вашей программе память и производительность. Целью go является получение преимущества от ядра настолько, насколько это возможно. Он был создан уже с конкурентностью с самого начала.

# M, P, G оркестрация
Чтобы решить эту проблему, Go имеет своё расписание для распределения горутин через различные потоки. Это расписание определяет три главных идеи, как сказано в самом коде:

Главные идеи:
* G - горутины
* M - рабочий поток или машина
* P - процессор, ресурс для выполнения Go кода

	M должен иметь связанный P, чтобы выполнять код Go.
    
Вот диаграмма этих трех моделей.

[![](https://notepad.gasick.ru/uploads/images/gallery/2022-02/scaled-1680-/image-1644781906660.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-02/image-1644781906660.png)

Каждая горутина (G) запускается в потоке ОС (M) которая назначена логическому ЦПУ (P). Давайте возьмем простой пример, чтобы посмотреть как Go управляет ими:
```go
func main() {
   var wg sync.WaitGroup
   wg.Add(2)

   go func() {
      println(`hello`)
      wg.Done()
   }()

   go func() {
      println(`world`)
      wg.Done()
   }()

   wg.Wait()
}
```
Сначала Go создаст различные P с номерами логических ЦПУ присутствующих в машине и сохранит их в качестве списка "холостых" P:
[![P initialization](https://notepad.gasick.ru/uploads/images/gallery/2022-02/scaled-1680-/image-1644781941382.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-02/image-1644781941382.png)

Затем, новая горутина или горутины готовые к запуску будут будить P для назначения на них работ. В этом случае P создаст M и свяжет их с ОС потоками:

[![OS thread creation](https://notepad.gasick.ru/uploads/images/gallery/2022-02/scaled-1680-/image-1644781954050.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-02/image-1644781954050.png)

Однако, как и P, M без работы - то есть не имеющая работающей горутины ожидающей запуска, возвращается из `syscall` или принудительно завершенная сборщиком мусора, попадет в "холостой" список:

[![M and P idle list](https://notepad.gasick.ru/uploads/images/gallery/2022-02/scaled-1680-/image-1644781965983.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-02/image-1644781965983.png)

Во время загрузки программы, Go уже создает потоки ОС и связывает их с M. Для нашего примера, первая горутина которая выводит "привет" будет использовать главную горутину, в то время как вторая получит M и P из "холостого" списка:

[![M and P pulled from the idle list](https://notepad.gasick.ru/uploads/images/gallery/2022-02/scaled-1680-/image-1644781980342.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-02/image-1644781980342.png)

Теперь у нас есть общая картина упрвления горутинами и потоками, давайте посмотрим в какомслучае Go станет использовать M чаще чем P и как горутины управляются в случае системых вызовов.
# Системные вызовы
Go оптимизирует системные вызовы, вне зависимости от блокировки, с помощью оборачивания их во время исполнения. Эта обертка будет автоматически отделять P от треда M и позволять другим тредам запускать его. Давайте посмотрим на пример чтения файла:
```go
func main() {
   buf := make([]byte, 0, 2)

   fd, _ := os.Open("number.txt")
   fd.Read(buf)
   fd.Close()

   println(string(buf)) // 42
}
```
Вот рабочий процесс открытия файла:

[![Syscall handoffs P](https://notepad.gasick.ru/uploads/images/gallery/2022-02/scaled-1680-/image-1644782002972.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-02/image-1644782002972.png)

P0 в данный момент простаивает, и потенциально доступен. Затем, как только систенмый вызов завершен, Go применяет следующий набор правил пока одно из правил не будет удовлетворено:
* Попытатся завладеть тем же P, в нашем случае это P0, и вернутся к выполнению.
* Попытаться получить P из списка "холостых" и вернуться к выполнению
* Поместить горутину в общую очередь, а связанный с ним M вернуть обратно в "холостую" очередь.

Однакой, Go, так же обрабатывает ситуации когда ресурсы еще не готовы, на случай не блокируемых I/O, например http вызовы. Тогда, первый системый вызов, который следуюет представленному выше рабочему процессу, упадет, так как нет готовых ресурсов, заставляет Go использовать сетевой опросник и остановливает горутину. Вот пример:
```go
func main() {
   http.Get(`https://httpstat.us/200`)
}
```
Как первый системный вызов отработает и явно скажет, что ресуср не готов, горутина остановится до тех пор пока сетевой опросник не скажет, что ресурс готов. В этом случае тред M будет разблокирован:

[![Network poller waiting for the resource](https://notepad.gasick.ru/uploads/images/gallery/2022-02/scaled-1680-/image-1644782025643.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-02/image-1644782025643.png)

Горутина заново запустится когда Go планировщик начнет искать работу. Планировщик затем будет передавать сетевому опроснику, что горутина ожидает запуска в случае успешного получения информации которая ожидается:

[![](https://notepad.gasick.ru/uploads/images/gallery/2022-02/scaled-1680-/image-1644782048991.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-02/image-1644782048991.png)

Если больше, чем одна горутина готова, то дополнительная будет отправлена в запускаемую глобальную очреедь и будет запущена планировщиком позже.
# Ограничения в рамках потоков ОС
Когда системные вызовы использутся, Go не ограничивает количество потоков ОС, которые могут быть заблокированны, как указано в коде:
	
    GOMAXPROCS переменная ограничивает количество рабочих системных потоков, которые могут быть запущены пользвателем одновременно. Нет ограничений по количеству потоков которые могут быть заблокированны системными вызовами при выполнении от имени Go кода, отсюда, количество GOMAXPROCS ограниченно. Эта функция пакета GOMAXPROCS опрашивает и меняет количество. 
    
Вот пример ситуации:
```go
func main() {
   var wg sync.WaitGroup

   for i := 0;i < 100 ;i++  {
      wg.Add(1)

      go func() {
         http.Get(`https://httpstat.us/200?sleep=10000`)

         wg.Done()
      }()
   }

   wg.Wait()
}
```
Вот количество потоков созданных с помощью инструментов слежения:

[![](https://notepad.gasick.ru/uploads/images/gallery/2022-02/scaled-1680-/image-1644782072403.png)](https://notepad.gasick.ru/uploads/images/gallery/2022-02/image-1644782072403.png)

Так как Go оптимизирован для использования потоков, он может быть многократно использован в то время, как горутины заблокированны. Это объясняет почему это число не совпадает с числом циклов.

# Raspberry Pi и программирование на Go

Go или GoLang - компилируемый язык программирования разработанный Google.

Go широко используется для web разработки и на сегодня это самый быстро растущий язык. В отличии от Java который запускается в  JVM, Go собирается прямо в Windows, OS X or Linux исполняемые файлы.

В этой статье мы взглянем на создание двух програм, которые разговаривают с  Raspberry Pi GPIO. Первая будет просто клавиатурный ввод программы, а вторая будет отдельным web приложением управляюищим пинами GPIO.

# Raspberry Pi GPIO
Есть можество различных способов производить подключение к GPIO. Для нашего примера мы собираемся посмотреть на вывод gpio терминальной утилиты, так же будем использовать go-rpi библиотеку.

Для тестирования мне нравится использовать gpio утилиту так как она предлагает хороший выбор команд и я могу в ручном режиме протестировать и проверить команды, прежде, чем я буду использовать их в коде. Для помощи можно воспользоваться ключем `-h`.

Аппаратное обеспечение Raspberry PI настраивается используя резистор на физическом пине 7(BCM пин 4)

[![Led_setu](https://notepad.gasick.ru/uploads/images/gallery/2021-10/scaled-1680-/image-1634905026104.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-10/image-1634905026104.png)

Наша первая программа(keyin.go) будет читать ввод клавиатуры и затем направлять в gpio дважды, первый раз, чтобы записать значение, второй раз чтобы прочитать значение обратно.
```go
package main

import (
    "bufio"
    "fmt"
    "os/exec"
    "os"
)

func main() {
    // Get keyboard input
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("Enter value for GPIO pin 7 : ")
    pinval, _ := reader.ReadString('\n')

    // Write to GPIO pin 7 using keyboard input 
    testCmd := exec.Command("gpio","write", "7", pinval)
    testOut, err := testCmd.Output()
    if err != nil {
        println(err)
    }
    // Read back the GPIO pin 7 status
    testCmd = exec.Command("gpio","read", "7")
    testOut, err = testCmd.Output()
    if err != nil {
        println(err)
    } else {
      fmt.Print("GPIO Pin 4 value : ")
      fmt.Println(string(testOut))
    }
}
```
Скомпилируем и запустим keyin.go программу:
```bash
 $ go build keyin.go
 $ ./keyin
Enter value for GPIO pin 7 : 1
GPIO Pin 4 value : 1
```
# Простое Go веб-приложение
Для начального примера мы сделаем веб-приложение(web_static.go) которое показывает страничку `web_static.html`.


web_static.html выглядит следующим образом:
```html
<html>
  <head>
    <title>GO PI Static Page</title>
  </head>
  <body>
    <h1>GO PI Static Page</h1>
    <hr>
    This is a static test page
  </body>
</html>
```
`web_static.go` программе необходимо импортировать `net/http` библиотеку. `http.HandleFunc` вызов используется для стандартного адреса “/” и раздачи с помощью сервера нашего `web_static.html` файла.  `http.ListenAndServe` фукнция слушает web запросы на порту 8081.

```go
package main
 
import (
    "log"
    "net/http"
)
 
func main() {
    // Create default web handler, and call a starting web page
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        http.ServeFile(w, r, "web_static.html")
        println("Default Web Page")
    })
    // start a listening on port 8081
    log.Fatal(http.ListenAndServe("8081", nil))
 
}
```
Go код может быть скомпилирован и запущен.
```bash 
$ go build web_static.go
$ ./web_static
Default Web Page
```
Из web бразуера указывающего на  Raspberry Pi и порт 8081,наше приложение будет выглядит так:

[![web_static](https://notepad.gasick.ru/uploads/images/gallery/2021-10/scaled-1680-/image-1634904998928.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-10/image-1634904998928.png)

# Go Web приложение с Pi GPIO
Следующий шаг - создание web страницы которая может передавать параметры. Для этогоп риложения мы переключим GPIO вывод с `on`  на `off`.

Новая web страница(go_buttons.html) создается с двумя кнопками. Html якорь используется для передачи `/on`  и `/off` параметров в наше web приложение.

`CACHE-CONTROL` тег устанавливается в  `NO-CACHE` для того, чтобы мы точно знали, что страница перезагружается. Я так же включил `EXPIRES` тег равным нулю, чтобы браузер всегда видел страничку "испорченной". Если вы не включаете их то страница может быт загружена только один раз.
```html
<html>
  <head>
    <title>GO PI GPIO</title>
    <META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
    <META HTTP-EQUIV="EXPIRES" CONTENT="0">
  </head>
  <body>
    <h1>Go Raspberry Pi GPIO Test</h1>
    <a href="/on"><button>Set LED ON</button></a><br>
    <a href="/off"><button>Set LED OFF</button></a>
  </body>
</html>
```
Наше новое web приложение(go_buttons.go) теперь включает еще две `http.HandleFunc` функции обработчика, одну для `/on` и одну для `/off`. Эти обработчики вызывают функцию которая вызывает gpio, что используется для написания вывода и чтения обратно статуса ввода.

Наша новоиспеченная gpio функция выполняет ходит 2 раза к  gpio командной утилите, первый раз записать значение, второй - прочитать значение обратно.
```go
package main

import (
	"log"
	"net/http"
	"os/exec"
)
func gpio( pinval string) {
    testCmd := exec.Command("gpio","write", "7", pinval)  
    testOut, err := testCmd.Output()      
    if err != nil {
        println(err)
    }
    testCmd = exec.Command("gpio","read", "7") 
    testOut, err = testCmd.Output()
    if err != nil {
        println(err)
    } else { 
      print("GPIO Pin 4 value : ")  
      println(string(testOut))
    }
}

func main() {

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "go_buttons.html")
		println("Default Web Page")
	})

	http.HandleFunc("/on", func(w http.ResponseWriter, r *http.Request) {
                 http.ServeFile(w, r, "go_buttons.html")
		 println("Web Page sent : ON")
		 gpio("1")

	})

	http.HandleFunc("/off", func(w http.ResponseWriter, r *http.Request) {
                 http.ServeFile(w, r, "go_buttons.html")
		 println("Web Page sent : OFF")
		 gpio("0")
	})

	log.Fatal(http.ListenAndServe(":8081", nil))

}
```
Скомпилируем и запустим go_buttons:
```bash
$ go build go_buttons.go
$ ./go_buttons


Default Web Page
Web Page sent : ON
GPIO Pin 4 value : 1

Default Web Page
Web Page sent : OFF
GPIO Pin 4 value : 0
```
The web page should look something like:

[![](https://notepad.gasick.ru/uploads/images/gallery/2021-10/scaled-1680-/image-1634904977872.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-10/image-1634904977872.png)

# Выводы
Для конечного приложения я бы предпочёл использовать Go нативную библиотеку для вызовов GPIO, но для прототипирования я нашел, что командная утилита проще в решении проблем.