golang

Можем ли мы сохранить животных с помощью Golang?

  • суббота, 12 августа 2023 г. в 00:00:12
https://habr.com/ru/articles/753966/

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

Идентифицируем проблему

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

Я разработчик и я предпочитаю проводить время со своим компьютером. Но все же иногда мне нужно выходить из дома, вы знаете, делать все те вещи, что делают другие люди. И вот однажды, возвращаясь домой со своей сумкой продуктов я увидел некоторую картину: группа людей с плакатами на которых написано “Save animals”. Как я уже говорил, я разработчик и прекрасно понимаю английский. Они, очевидно, хотели сохранить животных. И раз столько людей собрались вместе - это очень серьезная проблема, подумал я.

У меня нет фото с стого протеста, но есть одно фото с протеста против использования javascript на бэкэнде.
У меня нет фото с стого протеста, но есть одно фото с протеста против использования javascript на бэкэнде.

Эти люди не знали кто только что прошел мимо них. Я разработчик - один из тех людей которые делают мир лучше. И я намерен решить их проблему.

Не могу удержаться от проэктирования своего кода на стену. Но вот IDE хочу поменять, может что-то от JetBrains попробую.
Не могу удержаться от проэктирования своего кода на стену. Но вот IDE хочу поменять, может что-то от JetBrains попробую.

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

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

ChatGPT

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

Ну что же, проблема идентифицирована и мы наконец-то можем приступить к ее решению.

Реализуем решение

Давайте определимся с требованиями к нашему приложению. Наш сервис сохранения животных будет доступен по HTTP. Он будет принимать POST запрос с одним или несколькими файлами изображений животных. Также мы будем использовать путь нашего URL для целей категоризации изображений животных. 

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

Синие коробки, стрелочки и облачные бумажные шредеры.
Синие коробки, стрелочки и облачные бумажные шредеры.

Первое чем было решено заняться - это процессинг файлов. Естественно, я имею вполне нормальное желание сделать все быстро. Всю работу связанную с файлам я скину на небольшой сервис под названием Capyfile, код которого можно найти на GitHub. Ему нужно вскормить небольшой конфигурационный файл, которых скажет что я хочу делать с загружаемыми файлам. Мой конфиг выглядит так:

{
  "version": "1.0",
  "name": "upload",
  "processors": [
    {
      "name": "animal",
      "operations": [
        {
          "name": "file_size_validate",
          "params": {
            "maxFileSize": {
              "sourceType": "http_header",
              "source": "X-Capyfile-FileSizeValidate-MaxFileSize"
            }
          }
        },
        {
          "name": "file_type_validate",
          "params": {
            "allowedMimeTypes": {
              "sourceType": "http_header",
              "source": "X-Capyfile-FileTypeValidate-AllowedMimeTypes"
            }
          }
        },
        {
          "name": "metadata_cleanup"
        },
        {
          "name": "image_convert",
          "params": {
            "toMimeType": {
              "sourceType": "value",
              "source": "image/jpeg"
            },
            "quality": {
              "sourceType": "value",
              "source": "high"
            }
          }
        },
        {
          "name": "s3_upload",
          "params": {
            "accessKeyId": {
              "sourceType": "secret",
              "source": "aws_access_key_id"
            },
            "secretAccessKey": {
              "sourceType": "secret",
              "source": "aws_secret_access_key"
            },
            "endpoint": {
              "sourceType": "env_var",
              "source": "AWS_ENDPOINT"
            },
            "region": {
              "sourceType": "env_var",
              "source": "AWS_REGION"
            },
            "bucket": {
              "sourceType": "http_header",
              "source": "X-Capyfile-S3Upload-Bucket"
            }
          }
        }
      ]
    }
  ]
}

Из него ясно, что я хочу сделать с каждым загруженным файлом:

  • Проверить размер файла

  • Проверить MIME-тип файла

  • Очистить метаданные файла

  • Конвертировать файл в JPEG формат

  • Сохранить файл в совместимое с S3 хранилище

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

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

package main

import (
    "net/http"
    "net/http/httputil"
    "net/url"
    "strconv"
    "strings"
)

const (
    Byte = 1 << (iota * 10)
    KiByte
    MiByte
    GiByte
    TiByte
    PiByte
    EiByte
)

func main() {
    http.HandleFunc("/", capyfileProxyHandler)

    http.ListenAndServe(":8080", nil)
}

func capyfileProxyHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        w.WriteHeader(http.StatusMethodNotAllowed)
        return
    }

    path := strings.Split(
        strings.TrimLeft(r.URL.Path, "/"),
        "/")

    if len(path) != 1 {
        w.WriteHeader(http.StatusNotFound)
        return
    }

    animal := path[0]

    if animal == "seagull" {
        w.WriteHeader(http.StatusForbidden)
        return
    }

    if animal == "capybara" {
        r.Header.Set(
            "X-Capyfile-FileSizeValidate-MaxFileSize",
            strconv.Itoa(100*MiByte))
        r.Header.Set(
            "X-Capyfile-FileTypeValidate-AllowedMimeTypes",
            `["image/jpeg", "image/png", "image/gif", "image/bmp", "image/webp", "image/heic", "image/heif"]`)
    } else {
        r.Header.Set(
            "X-Capyfile-FileSizeValidate-MaxFileSize",
            strconv.Itoa(1*MiByte))
        r.Header.Set(
            "X-Capyfile-FileTypeValidate-AllowedMimeTypes",
            `["image/jpeg", "image/png"]`)
    }
    // We assume that all the necessary buckets are already created 
    // or can be created on the fly.
    r.Header.Set("X-Capyfile-S3Upload-Bucket", animal)

    r.URL.Path = "/upload/animal"

    proxy := httputil.NewSingleHostReverseProxy(&url.URL{
        Scheme: "http",
        Host:   "localhost:8024",
    })
    proxy.ServeHTTP(w, r)
}

Запускаем и тестируем

Первым делом, мы запустим Capyfile сервер:

docker run --rm \
    --name capyfile_server \
    --mount type=bind,source=./service-definition.json,target=/etc/capyfile/service-definition.json \
    --env CAPYFILE_SERVICE_DEFINITION_FILE=/etc/capyfile/service-definition.json \
    --env AWS_ENDPOINT=s3.amazonaws.com \
    --env AWS_REGION=us-west-1 \
    --secret aws_access_key_id \
    --secret aws_secret_access_key \
    -p 8024:8024 \
    capyfile/capysvr:latest

Затем запускаем наше приложение:

go run animal_saver.go

Теперь все готово для сохранения животных в S3 хранилище.

Сохранил одну капибару.
Сохранил одну капибару.

Заключение

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

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