<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Яков Захаров</title><generator>teletype.in</generator><description><![CDATA[Системный администратор Linux]]></description><link>https://yashumitsu.com/?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=yashumitsu</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/yashumitsu?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/yashumitsu?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Fri, 10 Apr 2026 21:04:18 GMT</pubDate><lastBuildDate>Fri, 10 Apr 2026 21:04:18 GMT</lastBuildDate><item><guid isPermaLink="true">https://yashumitsu.com/cLNAkVtW4i3</guid><link>https://yashumitsu.com/cLNAkVtW4i3?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=yashumitsu</link><comments>https://yashumitsu.com/cLNAkVtW4i3?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=yashumitsu#comments</comments><dc:creator>yashumitsu</dc:creator><title>Кеширующий прокси-сервер для geoipupdate</title><pubDate>Mon, 15 Dec 2025 06:45:41 GMT</pubDate><category>Admin</category><description><![CDATA[Потребность в актуализации GeoIP-баз, как правило, реализуется через установку на каждом сервере утилиты geoipupdate.]]></description><content:encoded><![CDATA[
  <p id="N9sy">Потребность в актуализации GeoIP-баз, как правило, реализуется через установку на каждом сервере утилиты <a href="https://github.com/maxmind/geoipupdate" target="_blank">geoipupdate</a>.</p>
  <p id="TmH6">Но оказывается, при таком подходе легко столкнуться с суточными ограничениями на скачивание бесплатной версии GeoLite:</p>
  <ul id="7LY0">
    <li id="oVpD"><a href="https://www.maxmind.com/en/geolite-free-ip-geolocation-data" target="_blank">https://www.maxmind.com/en/geolite-free-ip-geolocation-data</a></li>
  </ul>
  <p id="BdHU">Можно изменить архитектуру, выделив под обновления отдельный сервер и  распространять базы с него. Однако, такое решение требует самописной обвязки, для доставки обновлений на конечные хосты.</p>
  <p id="Raap">Обратим внимание, что <em>geoipupdate</em> поддерживает переопределение сервера обновлений:</p>
  <pre id="aT9p"># The server to use. Defaults to &quot;https://updates.maxmind.com&quot;.
# Host https://updates.maxmind.com</pre>
  <p id="WQdh">и работу через прокси-сервер:</p>
  <pre id="aT9p"># The proxy host name or IP address. You may optionally specify a
# port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080http,
# will be used.
# Proxy 127.0.0.1:8888</pre>
  <p id="dNAw">Развёртывание полноценного прокси-сервера (например Squid) затруднительно в Kubernetes, из-за метода CONNECT, которые стандартные Ingress не поддерживают.</p>
  <p id="xK3N">Зато, имитация API сервиса обновлений MaxMind, выглядит подходящим решением.</p>
  <p id="VEdZ">Разберём алгоритм работы <em>geoipupdate</em>:</p>
  <p id="LYL6">    1. <em>geoipupdate</em> считает контрольную сумму локальной версии базы (допустим Country) и запрашивает у <em>updates.maxmind.com</em> информацию об актуальной версии:</p>
  <pre id="rv7v">&gt; Host: updates.maxmind.com
&gt; GET /geoip/updates/metadata?edition_id=GeoLite2-Country HTTP/2
&gt; Authorization: Basic &lt;лицензионный ключ&gt;</pre>
  <p id="7wLc">    2.<em> updates.maxmind.com</em> отвечает в следующем формате:</p>
  <pre id="mHOs">{
  &quot;databases&quot;: [
    {
      &quot;date&quot;: &quot;2025-11-25&quot;,
      &quot;edition_id&quot;: &quot;GeoLite2-Country&quot;,
      &quot;md5&quot;: &quot;7f97170bfe5f384a14ec6cb49a538d33&quot;
    }
  ]
}</pre>
  <p id="jH0V">    3. Если хеш не совпадает — требуется обновление, тогда geoipupdate  обращается к <em>download.maxmind.com</em>:</p>
  <pre id="yao9">&gt; Host: download.maxmind.com
&gt; GET /geoip/databases/GeoLite2-Country/download?date=20251125&amp;suffix=tar.gz HTTP/2
&gt; Authorization: Basic &lt;лицензионный ключ&gt;</pre>
  <p id="bVZk">    4. <em>download.maxmind.com</em> отвечает редиректом, на скачивание файла напрямую из S3</p>
  <p id="ynB4">    5. <em>geoipupdate</em> следует по ссылке и таким образом начинается загрузка файла</p>
  <p id="n5Vz">Всё что нам нужно, это спроксировать запросы от <em>geoipupdate</em> к сервисам <em>updates</em> и <em>download.maxmind.com</em> и закешировать их ответы.</p>
  <p id="VG1Q">Решение этой задачи уже реализовано в виде сервиса на Go:</p>
  <ul id="3DPu">
    <li id="uxdl"><a href="https://github.com/gabe565/geoip-cache-proxy" target="_blank">https://github.com/gabe565/geoip-cache-proxy</a></li>
  </ul>
  <p id="kE6B">однако оставалось предчувствие, что для реализации похожей функциональности достаточно базового Nginx.</p>
  <p id="phnx">Так получилась следующая конфигурация:</p>
  <pre id="QDz1"># Указание резолвера является требованием Nginx при использовании
# переменной ($follow_upstream_http_location) в proxy_pass
resolver ${NGINX_LOCAL_RESOLVERS};

# Логируем был ли использован кеш ($upstream_cache_status) 
log_format geoipupdate &#x27;$remote_addr - $remote_user [$time_local] &#x27;
                    &#x27;&quot;$request&quot; $status $body_bytes_sent &#x27;
                    &#x27;&quot;$http_referer&quot; &quot;$http_user_agent&quot; &quot;$upstream_cache_status&quot;&#x27;;

proxy_cache_path /var/cache/nginx/geoipupdate levels=1:2
    keys_zone=geoipupdate_cache:1m
    max_size=500M
    inactive=14d
    use_temp_path=off;

server {
    listen       8080;
    server_name  _;

    access_log  /var/log/nginx/access.log  geoipupdate;

    # По умолчанию, proxy_cache_key основывается на $proxy_host,
    # т.е. upstream-домене.
    # В результате кеш записывается, но фактически не используется.
    # Запросы продолжают доходить до proxy_pass и проксироваться в maxmind.com.
    # Поэтому, в составе ключа кеширования используется
    # переменная $host (домен самого прокси-сервера).
    # Это позволяет проверить кеш на попадание, до обращения к proxy_pass.
    proxy_cache_key $host$uri$is_args$args;

    # (1) Проксируем запрос на получение метаданных, в сервис updates.maxmind.com.
    #
    # Кешируем его ответ не непродолжительное время.
    location /geoip/updates/ {
        proxy_pass             https://updates.maxmind.com;
        proxy_ssl_server_name  on;

        proxy_cache                    geoipupdate_cache;
        proxy_cache_valid              200 1d;
        proxy_cache_use_stale          error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update  on;
        proxy_cache_lock               on;
        proxy_cache_lock_timeout       30s;

        # Игнорируем заголовки запрещающие кеширование.
        proxy_ignore_headers Cache-Control Expires Set-Cookie;
        add_header X-Proxy-Cache $upstream_cache_status;
    }

    # (2) Проксируем запрос на скачивание определённой версии базы,
    # в сервис download.maxmind.com.
    #
    # Данный ресурс отвечает 302-редиректом,
    # содержащим динамическую ссылку на S3-хранилище.
    # Nginx перехватывает редирект и передаёт
    # его обработку в internal-location @follow_redirect.
    #
    # То есть, вместо отдачи редиректа как есть, Nginx переходит по нему
    # за нас, чтобы закешировался итоговый файл, а не ссылка на него. 
    location /geoip/databases/ {
        # Отвечаем из кеша при наличии в нём запрашиваемой версии БД.
        proxy_cache geoipupdate_cache;

        proxy_pass             https://download.maxmind.com;
        proxy_ssl_server_name  on;

        # Перехватываем редирект в ответе бекенда и передаём его в internal-location.
        proxy_intercept_errors on;
        error_page 302 = @follow_redirect;
    }

    # (3) Обработчик перехода по ссылке из заголовка Location ответа upstream.
    location @follow_redirect {
        internal;

        # При входе в location, сохраняем текущее
        # значение переменной $upstream_http_location из шага 2.
        # Поскольку после вызова proxy_pass оно будет перезаписано.
        set $follow_upstream_http_location $upstream_http_location;

        # Переходим в S3 по ссылке из редиректа.
        proxy_pass $follow_upstream_http_location;
        proxy_ssl_server_name on;

        # Исключаем проксирование заголовка Authorization в Amazon S3,
        # поскольку в обратном случае он отвечает ошибкой:
        #   &gt; Missing x-amz-content-sha256
        proxy_set_header Authorization &quot;&quot;;

        # Сохраняем ответ S3 в кеш.
        proxy_cache                    geoipupdate_cache;
        proxy_cache_valid              200 7d;
        proxy_cache_use_stale          error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update  on;
        proxy_cache_lock               on;
        proxy_cache_lock_timeout       30s;

        proxy_ignore_headers Cache-Control Expires Set-Cookie;
        add_header X-Proxy-Cache $upstream_cache_status;
    }
}</pre>
  <p id="ztG8">Остаётся прописать URL-нашего прокси, в параметре Host конфигурации GeoIP.conf.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://yashumitsu.com/TdkOVlpkhL6</guid><link>https://yashumitsu.com/TdkOVlpkhL6?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=yashumitsu</link><comments>https://yashumitsu.com/TdkOVlpkhL6?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=yashumitsu#comments</comments><dc:creator>yashumitsu</dc:creator><title>Обзор реализаций конструкторов объектов в Go</title><pubDate>Mon, 07 Jul 2025 03:47:50 GMT</pubDate><category>Go</category><description><![CDATA[В поисках идеального конструктора: с поддержкой обязательных и опциональные аргументов, дефолтных значений, возврата ошибок и простотой кода.]]></description><content:encoded><![CDATA[
  <h3 id="sPFy">Functional Options</h3>
  <blockquote id="HpjD">Источники:<br />  1. <a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis" target="_blank">https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis</a><br />  2. <a href="https://rednafi.com/go/configure_options/" target="_blank">https://rednafi.com/go/configure_options/</a></blockquote>
  <p id="JCW6">Начнём с наиболее распространённого в Go подхода — <strong>Functional Options</strong>.</p>
  <pre id="0LEX" data-lang="go">type User struct {
	// Обязательный параметр
	Email string

	// Опциональные параметры
	Name   string
	Public bool
}

type UserOption func(*User) error

func WithName(name string) UserOption {
	return func(u *User) error {
		u.Name = name
		return nil
	}
}

func WithPublicFlag(u *User) error {
	u.Public = true
	return nil
}

func NewUser(email string, options ...UserOption) (*User, error) {
	// Инициализируем объект с обязательными полями.
	// Здесь же можно задать дефолтные значения для опциональных полей.
	u := &amp;User{
		Email: email,
	}

	for _, option := range options {
		if err := option(u); err != nil {
			return nil, err
		}
	}

	return u, nil
}

func main() {
	user, _ := NewUser(&quot;ivan@example.com&quot;)
	fmt.Printf(&quot;%+v\n&quot;, user)
	// &amp;{Email:ivan@example.com Name: Public:false}

	user, _ = NewUser(&quot;ivan@example.com&quot;, WithName(&quot;Ivan Ivanich&quot;), WithPublicFlag)
	fmt.Printf(&quot;%+v\n&quot;, user)
	// &amp;{Email:ivan@example.com Name:Ivan Ivanich Public:true}
}</pre>
  <p id="avXX">Преимущества:</p>
  <ul id="0xAT">
    <li id="yiAh">Валидация ввода (поддержка возврата ошибки)</li>
  </ul>
  <p id="aZmY">Недостатки:</p>
  <ul id="FEwY">
    <li id="Cq4N">IDE не показывает доступные опции конструктора, нужно явно просматривать структуру <em>UserOption</em></li>
    <li id="O0X9">На практике, With-опций  вызываются с префиксом названия пакета, что может выглядеть чуждо</li>
  </ul>
  <h3 id="TwtZ">Configurable Object</h3>
  <blockquote id="8ucF">Источники:<br />  1. <a href="https://rednafi.com/go/dysfunctional_options_pattern/" target="_blank">https://rednafi.com/go/dysfunctional_options_pattern/</a></blockquote>
  <p id="gAXD">Реализация с отдельной структурой для хранения опциональных параметров:</p>
  <pre id="XeaT" data-lang="go">type User struct {
	// Обязательный параметр
	Email string

	UserOptions
}

// Опциональные параметры
type UserOptions struct {
	Name   string
	Public bool
}

func (userOption UserOptions) WithName(name string) UserOptions {
	userOption.Name = name
	return userOption
}

func (userOption UserOptions) WithPublic() UserOptions {
	userOption.Public = true
	return userOption
}

func NewUser(email string, options UserOptions) *User {
	// Описываем объект с обязательными полями.
	// Здесь же можно задать дефолтные значения для опциональных полей.
	u := &amp;User{
		Email:       email,
		UserOptions: options,
	}

	return u
}

func main() {
	user := NewUser(&quot;ivan@example.com&quot;, UserOptions{})
	fmt.Printf(&quot;%+v\n&quot;, user)
	// &amp;{Email:ivan@example.com UserOptions:{Name: Public:false}}

	user = NewUser(&quot;ivan@example.com&quot;, UserOptions{}.WithName(&quot;Ivan Ivanich&quot;).WithPublic())
	fmt.Printf(&quot;%+v\n&quot;, user)
	// &amp;{Email:ivan@example.com UserOptions:{Name:Ivan Ivanich Public:true}}
}</pre>
  <p id="mL2f">Выявляется менее наглядная работа с ошибками, валидацию в <em>With</em>-опции встроить не получится, только описать на уровне <em>NewUser()</em>.</p>
  <p id="i4NY">Также сигнатура конструктора обязывает передавать <em>options</em>, даже в неиспользуемом случае. Здесь можно упомянуть альтернативу — <strong>Builder pattern</strong>, реализующую цепочку методов, мутирующих основную структуру:</p>
  <pre id="HmBz" data-lang="go">type User struct {
	// Обязательный параметр
	Email string

	// Опциональные параметры
	Name   string
	Public bool
}

func (u *User) WithName(name string) *User {
	u.Name = name
	return u
}

func (u *User) WithPublicFlag() *User {
	u.Public = true
	return u
}

func NewUser(email string) *User {
	return &amp;User{
		Email: email,
	}
}

func main() {
	user := NewUser(&quot;ivan@example.com&quot;)
	fmt.Printf(&quot;%+v\n&quot;, user)
	// &amp;{Email:ivan@example.com Name: Public:false}

	user = NewUser(&quot;ivan@example.com&quot;).WithName(&quot;Ivan Ivanich&quot;).WithPublicFlag()
	fmt.Printf(&quot;%+v\n&quot;, user)
	// &amp;{Email:ivan@example.com Name:Ivan Ivanich Public:true}
}</pre>
  <p id="LRhd">Однако в ней полностью исключается возврат ошибок. И допускается модификация приватных полей, после создания объекта.</p>
  <p id="Jlul">Также важным недостатком, объединяющим оба примера, является смешивание методов конструктора и основной структуры в автодополнении IDE.</p>
  <h3 id="A7hA">Структура Options</h3>
  <blockquote id="sbi3">Источники:<br />  1. <a href="https://asankov.dev/post/different-ways-to-initialize-go-structs" target="_blank">https://asankov.dev/post/different-ways-to-initialize-go-structs</a></blockquote>
  <p id="AcYl">Вместо дополнительного кода ради <strong>Configurable Object</strong>, напрашивается использование простой структуры:</p>
  <pre id="9Fz5" data-lang="go">type User struct {
	// Обязательный параметр
	Email string

	UserOptions
}

// Опциональные параметры
type UserOptions struct {
	Name   string
	Public bool
}

func NewUser(email string, options UserOptions) *User {
	u := &amp;User{
		Email:       email,
		UserOptions: options,
	}

	return u
}

func main() {
	user := NewUser(&quot;ivan@example.com&quot;, UserOptions{})
	fmt.Printf(&quot;%+v\n&quot;, user)
	// &amp;{Email:ivan@example.com UserOptions:{Name: Public:false}}

	user = NewUser(&quot;ivan@example.com&quot;, UserOptions{
		Name:   &quot;Ivan Ivanich&quot;,
		Public: true,
	})
	fmt.Printf(&quot;%+v\n&quot;, user)
	// &amp;{Email:ivan@example.com UserOptions:{Name:Ivan Ivanich Public:true}}
}</pre>
  <p id="I1qX">Если мы имеем дело с приватными полями, поможет прокси-структура:</p>
  <pre id="geTr" data-lang="go">type User struct {
	email, name string
	public      bool
}

// Опциональные параметры
type UserOptions struct {
	Name   string
	Public bool
}

func NewUser(email string, options UserOptions) *User {
	u := &amp;User{
		email: email,
	}

	// Такой метод сравнения работает если структура состоит только из comparable-полей
	//   - https://stackoverflow.com/questions/28447297/how-to-check-for-an-empty-struct
	if options == (UserOptions{}) {
		return u
	}

	if options.Name != &quot;&quot; {
		u.name = options.Name
	}

	if options.Public != false {
		u.public = options.Public
	}

	return u
}

func main() {
	user := NewUser(&quot;ivan@example.com&quot;, UserOptions{})
	fmt.Printf(&quot;%+v\n&quot;, user)
	// &amp;{Email:ivan@example.com UserOptions:{Name: Public:false}}

	user = NewUser(&quot;ivan@example.com&quot;, UserOptions{
		Name:   &quot;Ivan Ivanich&quot;,
		Public: true,
	})
	fmt.Printf(&quot;%+v\n&quot;, user)
	// &amp;{Email:ivan@example.com UserOptions:{Name:Ivan Ivanich Public:true}}
}</pre>
  <p id="aKKG">Из недостатков, сохраняется обязательность аргумента <em>options</em> в конструкторе.</p>

]]></content:encoded></item></channel></rss>