HTTP(S) прокси на Go в 100 строчек кода
В этой статье я опишу реализацию HTTP и HTTPS прокси сервера. С HTTP все просто: сначала парсим запрос от клиента, передаем этот запрос дальше на сервер, получаем ответ от сервера и передаем его обратно клиенту. Нам достаточно использовать HTTP сервер и клиент из пакета net/http . С HTTPS все несколько сложнее. Технически это будет туннелирование HTTP с использованием метода CONNECT. Клиент отправляет запрос, указав метод CONNECT, с помощью которого устанавливается соединение между клиентом и удаленным сервером. Как только наш туннель из 2х TCP соединений готов, клиент обменивается TLS рукопожатием с сервером, посылает запрос и ждет ответ.
Наш прокси будет работать как HTTPS сервер(если используется параметр —-proto https ), а это значит нам нужны сертификаты и приватные ключи. В качестве примера будем использовать самоподписанные сертификаты, которые можно сгенерировать вот таким скриптом:
Необходимо убедить вашу операционную систему доверять получившимся сертификатам. Для этого в OS X можно использовать Keychain Access.
Для работы с HTTP будем использовать встроенный клиент и сервер. Прокся будет обрабатывать полученный запрос, передавать его нужному серверу и возвращать ответ клиенту.
HTTP туннелирование с использованием CONNECT
Если мы хотим использовать HTTPS или WebSockets, то придется поменять тактику. Нам нужен метод HTTP CONNECT. Этот метод работает как приказ серверу установить TCP соединение с необходимым сервером и рулить TCP стримом между сервером и клиентом. В таком случае SSL не будет разрываться и все данные будут передаваться по этому своеобразному туннелю.
Предупреждаю, что это не готовый к продакшену код. Это только пример. В этом коде не хватает передачи необходимых hop-by-hop заголовков и правильной настройки таймаутов(об этом можно почитать в прекрасной статье “Руководство по net/http таймаутам в Go”
Наша прокся будет поддерживать оба способа. По умолчанию будем работать по простой схеме, но создадим туннель если указан метод CONNECT
Функция handleHTTP очень простая, поэтому сконцентрируемся на handleTunneling . Все начинается с установки соединения:
Затем используем интерфейс Hijacker чтобы получить соединение с которым работает наш http сервер.
Если мы перехватываем соединение, то и обслуживать его дальше должны сами.
Теперь мы можем передавать данные напрямую между двумя TCP соединениями. Собственно, это и будет тем самым туннелем.
В этих рутинах данные передаются от клиента к серверу и обратно.
Чтобы проверить как все это работает можно использовать хром:
Go get proxy windows
A minimalist Go module proxy handler.
Goproxy has fully implemented the GOPROXY protocol. Our goal is to find the most dead simple way to provide a minimalist handler that can act as a full-featured Go module proxy for those who want to build their own proxies. Yeah, there is no Makefile , no configuration files, no crazy file organization, no lengthy documentation, no annoying stuff, just a goproxy.Goproxy that implements the http.Handler .
- Extremely easy to use
- One function: goproxy.New
- One struct: goproxy.Goproxy
- Two interfaces: goproxy.Cacher and goproxy.Cache
- Built-in GOPROXY support
- Defaulted to https://proxy.golang.org,direct (just like what Go is doing right now)
- Built-in GONOPROXY support
- Built-in GOSUMDB support
- Defaulted to sum.golang.org (just like what Go is doing right now)
- Built-in GONOSUMDB support
- Built-in GOPRIVATE support
- Supports serving under other Go module proxies by setting GOPROXY
- Supports proxying checksum databases
- Supports multiple mainstream implementations of the goproxy.Cacher
- Disk: cacher.Disk
- MinIO: cacher.MinIO
- Google Cloud Storage: cacher.GCS
- Amazon Simple Storage Service: cacher.S3
- Microsoft Azure Blob Storage: cacher.MABS
- DigitalOcean Spaces: cacher.DOS
- Alibaba Cloud Object Storage Service: cacher.OSS
- Qiniu Cloud Kodo: cacher.Kodo
Open your terminal and execute
The only requirement is the Go, at least v1.13.
Golang
Блог о языке программирования Go
пятница, 5 июля 2019 г.
Вспомогательные темы инструмента go: протокол прокси модуля
Команда go по умолчанию загружает модули из систем управления версиями напрямую, как это всегда бывает с go get. Переменная среды GOPROXY позволяет дополнительно контролировать источник загрузки. Если GOPROXY не установлен, является пустой строкой или строкой «direct», при загрузке используется прямое подключение по умолчанию к системам контроля версий. Отключение GOPROXY запрещает загрузку модулей из любого источника. В противном случае ожидается, что GOPROXY будет URL-адресом прокси модуля, и в этом случае команда go извлечет все модули из этого прокси. Независимо от источника модулей, загруженные модули должны соответствовать существующим записям в go.sum.
Прокси модуля Go — это любой веб-сервер, который может отвечать на запросы GET для URL-адресов заданной формы. Запросы не имеют параметров запроса, поэтому даже сайт, обслуживающий фиксированную файловую систему (включая file:/// URL), может быть прокси модуля.
GET-запросы, отправленные на прокси Go модуля:
GET $GOPROXY/ /@v/list возвращает список всех известных версий данного модуля, по одной на строку.
GET $GOPROXY/ /@v/ .info возвращает метаданные в формате JSON об этой версии данного модуля.
GET $GOPROXY/ /@v/ .mod возвращает файл go.mod для этой версии данного модуля.
GET $GOPROXY/ /@v/ .zip возвращает zip-архив для этой версии данного модуля.
Чтобы избежать проблем при обслуживании из файловых систем с учетом регистра, элементы и кодируются регистром, заменяя каждую заглавную букву восклицательным знаком, за которым следует соответствующая строчная буква: github.com/Azure кодируется как github.com/!azure.
Метаданные в формате JSON о данном модуле соответствуют этой структуре данных Go, которая может быть расширена в будущем:
Zip-архив для конкретной версии данного модуля — это стандартный zip-файл, содержащий дерево файлов, соответствующее исходному коду модуля и связанным файлам. В архиве используются разделенные косой чертой пути, и каждый путь к файлу в архиве должен начинаться с @ /, где модуль и версия подставляются напрямую, а не в регистре. Корень дерева файлов модулей соответствует префиксу @ / в архиве.
How to use Golang’s «go get» from behind a proxy.
In a perfect world employers would trust their employees, governments would care about the average person, and cats and dogs would get along together. Unfortunately the reality is that many organizations must use traffic proxies for both security and compliance with federal regulation. Luckily there is a way for Golang’s package manager «get» to work in such an environment. In this article we will look into how to clone a Golang based program from behind a corporate network proxy.
My Situation
This process is executed on a Windows 10 (build 1709) on a HP notebook. Inside of this is running Ubuntu 18.04 via the Windows Subsystem for Linux (WSL) feature. All of this on a corporate network, connects to a client’s network via VPN. The network chain looks like this (even before the ‘internet’): local machine -> corporate network -> VPN tunnel connection -> client corporate network.
Process
Opening Ubuntu WSL I received the typical command prompt and entered the user password. Once log in completed I changed directories (cd) to the user’s home directory.
WSL is not 100% Linux, but it works, kinda, sorta.
The first thing I tried was the recommended command as documented in the README file.
But that just got me an error, not surprising really.
Interesting. GiT is not able to access the URL. So somewhere between my machine and the GitHub servers the communication routing fails. I wanted to trace the traffic to see where the requests started to fail. So I installed and ran traceroute to github.com
It would seem traceroute fails. Next I tried a curl request:
Ah! «302 not allowed». And it looks like the Location cURL was redirected to is the ProxyWarning.html page from the organizations proxy gateway.
This means we need to set the proxy setting for GiT. I knew that weeks back I have set the proxy value for Ubuntu’s APT program, so I tail’d APT»s config file for the value. To finish up I ran a git list command to ensure the value was saved.
With the GiT proxy value now set I tried the following command.
After what seemed like hours ( Super Green!
Conclusion
With a little troubleshooting, knowledge share, and documentation reading I was able to get leverage the configuration ability of GiT to continue using «go get» to install applications. It would seem any program that downloads on a port other that 80 will need to be configured. May I should look into http_proxt global usage.
Do you have any knowledge about configuring programs to use proxy pass-throughs? Do you think corporate proxies are useless; or a necessity for the internet of today? Comment below and let me know.
TCP/IP proxy на Go
Я снова вернулся к любимой задаче для освоения новых языков. После написания движка для блога на Go, захотелось снова поразмять пальцы, болезный TCP/IP proxy/debugger теперь написан на Go.
Вкратце, TCP/IP proxy — это программа, которая умеет принимать соединения и «пробрасывать» их на указанный адрес. Попутно ведутся логи переданных данных. Это очень удобно при отладке различных самодельных сетевых протоколов.
В плане функциональности версия на Go, как и эрланговская, ведет три лога: двунаправленный шестнадцатеричный дамп и бинарные логи в обоих направлениях, «от» и «к» удаленному хосту. Питоновская версия бинарные логи не ведет.
Конечно, все многопоточно. И так как в Go параллельное программирование настолько просто (и безопасно), количество параллельных активностей для каждого соединения даже больше, чем в версии на Эрланге.
На Эрланге для каждого соединения работали следующие четыре потока:
- двунаправленный дамп-логгер
- два потока для двоичных логов принимаемых и посылаемых данных
- главный поток, мультиплексирующий чтение из локального и удаленного сокетов
В версии на Go немного иначе:
- двунаправленный дамп-логгер
- два потока для двоичных логов принимаемых и посылаемых данных
- два независимых потока для чтения из локального и удаленного сокета
Итого, 5.
В обоих случаях потоки чтения логируют данные, посылая сообщения потокам-логгерам. Конечно, нет никаких глупостей типа мьютексов или условных переменных. Проблемы согласования элегантно решаются через каналы Go.
Ниже привожу исходник. Он отличается от того, что в репозитории, наличием обильных комментариев. Для людей, не очень знакомых с Go, могут быть интересны некоторые моменты.
Повторюсь, каждое соединения обслуживается пятью потоками. И сделал я это не ради прикола. Просто мне показалось, что логически есть явно независимые подзадачи, которые было бы логично запустить параллельно. Если б я писал все на C++/boost, я б скорее всего замутил все одном потоке для каждого соединения (а может быть и вся программа была бы чисто однопотоковой через какие-нибудь изощренные библиотеки мультиплексирования), и не исключено, что на C++ в итоге еще и работало бы быстрее, несмотря на один поток. Но я хочу сказать не об этом. Go подталкивает на многопоточное программирование (а не отталкивает, как C++, даже на стероидах нового стандарта). Так или иначе, будут задачи, где удобная многопоточность станет ключевым фактором.
Запустить можно так (требуется как минимум Go релиз 1):
Затем, если в другом окне ввести:
и ввести, например, » USER test » » ENTER » и » PASS none » » ENTER «, то будут созданы три лога (дата в имени будет, конечно, другая).
Общий лог log-2012.04.20-19.55.17-0001-192.168.1.41-49544-213.180.204.37-110.log :
Двоичный лог исходящих данных log-binary-2012.04.20-19.55.17-0001-192.168.1.41-49544.log :
Двоичный лог входящих данных log-binary-2012.04.20-19.55.17-0001-213.180.204.37-110.log :
Теперь измерим производительность. Прокачаем файл напрямую, а потом через эту программу.
Качаем напрямую (файл размером около 72MB):
Теперь закачаем через программу, предварительно запустив ее:
На всякий случай, можно сравнить результаты:
У меня файлы одинаковые, значит все работает верно.
Теперь время. Я повторял эксперимент несколько раз (на Mac Air), и, что удивительно, закачка через программу всегда была не то, чтобы медленнее, а даже немного быстрее. Например, напрямую — 1m2.819s, через программу — 0m56.209s. Единственное объяснение, что wget возможно работает в один поток, а программа принимает данные из локального и удаленного сокета в два потока, что может давать небольшое ускорение. Но, разница все равно минимальна, и возможно на другой машине или сети ее будет не видно, но главное, что работает как минимум не медленнее, чем напрямую, несмотря на создание в процессе передачи весьма массивных логов.
Итак, пока среди трех вариантов такой программы, на Питоне, Эрланге и Go, последняя мне нравится больше всего.
Как мне показалось, неплохой эксперимент с параллельностью в Go.