Небольшая история про tls.Dial и Qrator
При работе с https в Go обычно используется стандартный пакет net/http, но иногда нужно поработать с tls.Conn.
Небольшая история про tls.Dial и Qrator
При работе с https в Go обычно используется стандартный пакет net/http, но иногда нужно поработать с tls.Conn.
В одном из своих проектов я использую прямой конекшн и отправляю сырой текст HTTP запроса в него. Реализация простая и выглядит примерно так:
package main
import (
"crypto/tls"
"fmt"
)
func main() {
req := "GET / HTTP/1.1\r\nHost: www.google.com\r\nUser-Agent: Firefox\r\n\r\n"
config := tls.Config{InsecureSkipVerify: true}
c, _ := tls.Dial("tcp", "www.google.com:443", &config)
c.Write([]byte(req))
buf := make([]byte, 1024)
n, _ := c.Read(buf)
fmt.Printf("%s", buf[:n])
}
Всё это работало прекрасно, до одного момента.

Успешный ответ от сервера google
Появилась необходимость общаться в таком режиме с avito и тут появилась проблема.

Код ответа — 403. Плохо!
Начал разбираться в чём дело. Авито работает в связке с Qrator. Это платформа для защиты от DDoS. Похоже, что нас не пускают на сайт по каким-то критериям.
Для эксперимента отправил запрос через net/http. И получил 200 код и нормальное тело ответа.
Становится понятнее… В чём могут быть различия? Переносы строк с \r\n, \n, какие-то заголовки добавляются?. Поднял быстренько Vikingo Satellite и проинспектировал запросы. Никаких дополнительных заголовков не нашлось. Значит нужно копать в сторону TLS.
TLS
Отснифал через Wireshark TLS хендшейки в двух разных сценариях и начал изучать различия.

Размер рукопожатий
По размеру можно понять, что они всё-таки есть. Листаю дальше и нахожу поле, которое есть при работе с net/http, но которое не выставляется при tls.Dial:

Слева net/http, справа tls
Расширение Application-Layer Protocol Negotiation (ALPN). Вот он, виновник.

Выбор протокола внутри TLS соединения
При использовании стандартного tls-конфига, который будет использоваться в tls.Dial нет списка протоколов, которые будут использоваться в рамках соединения. Похоже, Qrator не очень любит гостей, которые пользуются не браузерами =)
Для исправления ситуации достаточно заполнить поле NextProtos нужным списком протоколов.
config := tls.Config{InsecureSkipVerify: true, NextProtos: []string{"http/1.1"}}
c, _ := tls.Dial("tcp", "host", &config)
В результате всё заработает.

Успешный код и тело ответа
메타데이터
- post_id
- 511bec86e666
- slug
- небольшая-история-про-tls-dial-и-qrator-511bec86e666
- url
- https://medium.com/kiv-blog/%D0%BD%D0%B5%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B0%D1%8F-%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D1%8F-%D0%BF%D1%80%D0%BE-tls-dial-%D0%B8-qrator-511bec86e666
- canonical_url
- https://medium.com/kiv-blog/%D0%BD%D0%B5%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B0%D1%8F-%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D1%8F-%D0%BF%D1%80%D0%BE-tls-dial-%D0%B8-qrator-511bec86e666
- author_url
- https://medium.com/@kivim
- status
- ok
- fetched_at
- 2026-07-27 18:16:53