# HTTP туннель

Начиная с версии **0.36.0** tuna показывает в терминале [полноэкранный интерфейс](https://tuna.am/docs/tunnels/tui.md) с лентой запросов, их деталями и повтором.

HTTP туннель открывает доступ из интернета к сайту, API, GraphQL или WebSocket-серверу, запущенному у вас локально. Туннели работают на всех тарифах без ограничения по времени. На бесплатном тарифе доступны динамический адрес и один [бесплатный постоянный поддомен](#free-subdomain).

## Быстрый старт

```shell
tuna http 8080

```

В консоли появится публичный HTTPS-адрес вида `https://4l7mqf-212-49-103-2.ru.tuna.am` — запросы на него попадут в приложение на `localhost:8080`. Рядом будет ссылка на [инспектор запросов](https://tuna.am/docs/tunnels/http/inspect.md), где видно каждый запрос и ответ.

примечание

Все флаги с подсказками — в справке:

```shell
tuna http --help

```

У каждого флага есть [переменная окружения](https://tuna.am/docs/guides/environment-variables.md) — см. [справочник флагов](#flags).

## Готовые сценарии

Флаги свободно комбинируются. Несколько типовых задач:

| Задача                                                    | Команда                                                                                              |
| --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Показать сайт заказчику под паролем по постоянному адресу | `tuna http 3000 --subdomain=brave-otter-4821 --basic-auth="client:s3cret"`                           |
| Принимать вебхуки GitLab только с верной подписью         | `tuna http 8080 --subdomain=brave-otter-4821 --verify-webhook=gitlab --verify-webhook-secret=s3cret` |
| Открыть API фронтендеру на другом домене                  | `tuna http 8080 --cors --key-auth="dev-key"`                                                         |
| Проверить вёрстку с телефона                              | `tuna http 5173 --qr`                                                                                |
| Передать коллеге папку с файлами                          | `tuna http --file-server ./share --basic-auth="team:s3cret"`                                         |
| Открыть сервис, который проверяет заголовок `Host`        | `tuna http 11434 --request-header="host:localhost:11434"`                                            |
| Опубликовать собранный SPA (React, Vue)                   | `tuna http --file-server-spa ./dist`                                                                 |

В Docker, CI и systemd удобнее передавать те же настройки переменными окружения:

```shell
TUNA_TOKEN=tt_*** TUNA_SUBDOMAIN=brave-otter-4821 TUNA_INSPECT=false tuna http 8080

```

## Что можно опубликовать

Первый аргумент `tuna http` — адрес приложения, которое нужно открыть:

| Аргумент                          | Куда уйдёт запрос                                             |
| --------------------------------- | ------------------------------------------------------------- |
| `8080`                            | `http://localhost:8080`                                       |
| `localhost:8080`, `10.0.0.1:8080` | указанный хост и порт, в том числе другая машина в вашей сети |
| `https://localhost:443`           | приложение, которое само принимает HTTPS                      |
| `unix:///run/gunicorn.sock`       | приложение на unix-сокете                                     |

Вместо адреса можно раздать каталог встроенным сервером: [`--file-server`](#file-server), [`--file-server-spa`](#spa) или [`--webdav`](#webdav).

### Приложение на HTTPS

```shell
tuna http https://localhost:443

```

По умолчанию сертификат приложения не проверяется, подойдёт и самоподписанный. Включить проверку — `--tls-skip-verify=false`.

### Unix-сокет

Если приложение принимает HTTP на unix-сокете, как, например, gunicorn, укажите путь к сокету с префиксом `unix://`:

```shell
tuna http unix:///run/gunicorn.sock

```

[Инспектор](https://tuna.am/docs/tunnels/http/inspect.md), повтор запросов и [политики трафика](https://tuna.am/docs/traffic-policy.md) работают так же, как с портом. TLS к приложению за сокетом не поддерживается, флаг `--tls-skip-verify` к нему не применяется. Если приложение не отвечает, на странице ошибки 502 будет указан путь к сокету (`unix:///run/gunicorn.sock`), а не `localhost` с портом.

Требования к пути, права доступа и поведение при отсутствии сокета те же, что у [TCP-туннеля](https://tuna.am/docs/tunnels/tcp.md#unix-socket).

### Файловый сервер

Отдаёт файлы из каталога и показывает список файлов:

```shell
tuna http --file-server ./

```

### Одностраничное приложение (SPA)

Для собранных [одностраничных приложений](https://ru.wikipedia.org/wiki/%D0%9E%D0%B4%D0%BD%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5) (React, Vue, Angular): на любой несуществующий путь отдаётся `index.html`, поэтому работает клиентский роутинг.

```shell
tuna http --file-server-spa ./dist

```

### WebDAV

Каталог можно подключить как сетевой диск по протоколу WebDAV — с чтением и записью:

```shell
tuna http --webdav ./

```

warning

Файловый сервер и WebDAV открывают каталог всему интернету. Закройте доступ [паролем](#basic-auth) или [ключом](#key-auth).

## Адрес туннеля

| Адрес                                                | Как получить                 | Тариф                                |
| ---------------------------------------------------- | ---------------------------- | ------------------------------------ |
| Динамический, `4l7mqf-212-49-103-2.ru.tuna.am`       | без флагов                   | любой                                |
| Бесплатный постоянный, `brave-otter-4821.ru.tuna.am` | `--subdomain=<ваш-поддомен>` | любой, один на аккаунт               |
| Любой свободный поддомен, `billing.ru.tuna.am`       | `--subdomain=billing`        | [подписка](https://tuna.am/#pricing) |
| Собственный домен, `api.example.com`                 | `--domain=api.example.com`   | [подписка](https://tuna.am/#pricing) |

Защита от злоупотреблений

На бесплатном тарифе посетитель из браузера при первом заходе видит страницу-предупреждение о том, что сайт открыт через tuna. API-клиентов и вебхуки она не касается, а пропустить её можно заголовком `tuna-skip-browser-warning`. Зачем это нужно и как мы в целом боремся с фишингом и вредоносным контентом — на странице [Злоупотребления и жалобы](https://tuna.am/docs/abuse.md).

### Динамический адрес

Выдаётся автоматически при каждом запуске и меняется при переподключении. Адрес строится по схеме `<случайная-часть>-<ваш-IP>.<локация>.tuna.am`: IP-адрес источника виден в ссылке, так что анонимно разместить вредоносный контент не получится — подробнее в разделе [Анонимность](https://tuna.am/docs/abuse.md#%D0%B0%D0%BD%D0%BE%D0%BD%D0%B8%D0%BC%D0%BD%D0%BE%D1%81%D1%82%D1%8C). В постоянном поддомене IP-адреса нет. Подходит для разовой демонстрации или отладки. Если нужен адрес, который не изменится, — для вебхуков, колбэков платёжных систем, закладки в браузере — используйте постоянный поддомен.

### Бесплатный постоянный поддомен

У каждого пользователя есть один постоянный поддомен — бесплатно и на любом тарифе. Имя выдаётся случайно в формате `<прилагательное>-<существительное>-<4 цифры>`, например `brave-otter-4821`, и одинаково во всех локациях: `brave-otter-4821.ru.tuna.am` и `brave-otter-4821.nl.tuna.am`.

Свой поддомен можно посмотреть в [личном кабинете](https://my.tuna.am/domains): он отмечен меткой «Бесплатный», рядом готовая команда запуска. Также его подсказывает клиент при запуске туннеля без флагов:

```shell
Your permanent free domain: brave-otter-4821.ru.tuna.am. Use it with --subdomain=brave-otter-4821

```

По умолчанию туннель, как и раньше, получает динамический адрес — постоянный поддомен нужно указать явно:

```shell
tuna http 8080 --subdomain=brave-otter-4821

```

В другой локации:

```shell
tuna http 8080 --subdomain=brave-otter-4821 --location=nl

```

Или полным именем:

```shell
tuna http 8080 --domain=brave-otter-4821.ru.tuna.am

```

Нужен токен

Поддомен привязан к вашему аккаунту, поэтому клиент должен быть авторизован вашим токеном. Сохраните его один раз командой `tuna login` или `tuna config save-token <ТОКЕН>` (токен есть на [отдельной странице](https://my.tuna.am/token)), либо передавайте через переменную окружения `TUNA_TOKEN` — например, в Docker и CI. Без токена или с токеном другого аккаунта поддомен не заработает.

* Выбрать или сменить имя нельзя — поддомен с любым именем доступен по [подписке](https://tuna.am/#pricing).
* Удалить поддомен или передать его другому пользователю нельзя, `tuna domain clear` его пропускает.
* При смене тарифа поддомен сохраняется и не расходует лимит доменов подписки.
* Если вы зарегистрировались раньше, поддомен появится при первом открытии раздела [Домены](https://my.tuna.am/domains) или при первом запуске туннеля без флагов.

### Зарезервированный поддомен

По подписке можно занять любой свободный поддомен — после перезапуска туннеля адрес останется прежним:

```shell
tuna http 8080 --subdomain=billing

```

То же через [переменную окружения](https://tuna.am/docs/guides/environment-variables.md):

```shell
TUNA_SUBDOMAIN="billing" tuna http 8080

```

Поддомен резервируется автоматически при первом запуске с флагом. Все ваши домены — в [личном кабинете](https://my.tuna.am/domains).

Внимание

Ошибка `Domain already reserved` означает, что поддомен в этой локации уже занят другим пользователем — выберите другое имя.

### Собственный домен

Добавьте **свой домен** следуя инструкциям в [личном кабинете](https://my.tuna.am/domains), после проверки DNS и выпуска Let's Encrypt сертификата можно пользоваться:

```shell
tuna http 8080 --domain=my-api-project.example.com

```

Пошагово — в статье [Подключение своего домена](https://tuna.am/docs/tunnels/guides/connect-self-domain.md).

### Локация

Туннель поднимается в [локации](https://tuna.am/docs/tunnels/guides/locations.md) `ru`, если не указано иное. Выберите ближайшую к пользователям сайта:

```shell
tuna http 8080 --location=nl

```

### QR-код

Флаг `--qr` печатает в консоли QR-код со ссылкой на туннель — удобно, чтобы открыть сайт на телефоне.

```shell
tuna http 5173 --qr

```

![](/docs/img/examples/qr.png)

## Защита доступа

Туннель доступен всему интернету. Проверки ниже выполняются в клиенте tuna — запрос, который их не прошёл, до приложения не дойдёт.

| Флаг                                     | Кого пропускает                        |
| ---------------------------------------- | -------------------------------------- |
| [`--basic-auth`](#basic-auth)            | знающих логин и пароль                 |
| [`--key-auth`](#key-auth)                | запросы с ключом в заголовке `X-Token` |
| [`--cidr-allow`, `--cidr-deny`](#cidr)   | запросы из разрешённых подсетей        |
| [`--ua-allow`, `--ua-deny`](#user-agent) | запросы с подходящим `User-Agent`      |
| [`--verify-webhook`](#verify-webhook)    | вебхуки с верной подписью              |
| [`--rate-limit`](#rate-limit)            | не больше N запросов в секунду         |
| [`--https-redirect`](#https-redirect)    | только HTTPS, HTTP перенаправляется    |

Флаги со списками (`--basic-auth`, `--key-auth`, `--cidr-*`, `--ua-*`, `--*-header`) можно повторять. В переменных окружения значения перечисляются через запятую.

### Пароль (basic auth)

Браузер покажет окно ввода логина и пароля:

```shell
tuna http 8080 --basic-auth="login:password"

```

Несколько пользователей — повторите флаг:

```shell
tuna http 8080 --basic-auth="anna:pass1" --basic-auth="ivan:pass2"

```

Из скрипта:

```shell
curl -u login:password https://brave-otter-4821.ru.tuna.am/

```

### Ключ в заголовке (API token)

Для API и сервисов без браузера. Запрос пройдёт, только если в заголовке `X-Token` передан один из ключей:

```shell
tuna http 8080 --key-auth="my-secret-key"

```

```shell
curl -H "X-Token: my-secret-key" https://brave-otter-4821.ru.tuna.am/

```

### Доступ по подсетям IP

Белый список [подсетей](https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B4%D1%81%D0%B5%D1%82%D1%8C) в CIDR-формате — остальным доступ закрыт:

```shell
tuna http 8080 --cidr-allow="203.0.113.0/24" --cidr-allow="198.51.100.7/32"

```

Или, наоборот, закрыть доступ отдельным подсетям:

```shell
tuna http 8080 --cidr-deny="10.0.0.1/32"

```

### Доступ по User-Agent

Совпадение ищется по подстроке. Запретить `curl` и разрешить всех остальных:

```shell
tuna http 8080 --ua-deny=curl

```

Разрешить только Chrome:

```shell
tuna http 8080 --ua-allow=Chrome

```

примечание

`User-Agent` легко подделать — используйте этот фильтр против ботов и случайных сканеров, а не как защиту.

### Проверка подписи вебхуков

tuna пропустит только вебхуки, подписанные секретом, который вы задали у провайдера. Остальные запросы получат `401 Unauthorized`.

```shell
tuna http 8080 --verify-webhook=gitlab --verify-webhook-secret=1234

```

Поддерживаемые провайдеры: `gitlab`, `github`, `sentry`, `linear`. Для других сервисов и более сложной логики используйте [политики трафика](https://tuna.am/docs/traffic-policy.md).

### Ограничение частоты запросов (rate limit)

Защитит локальное приложение от перегрузки — не больше N запросов в секунду:

```shell
tuna http 8080 --rate-limit=2

```

При превышении лимита клиент получит `429 Too Many Requests`.

### Только HTTPS

Туннель по умолчанию отвечает и по HTTP, и по HTTPS. С флагом `--https-redirect` запросы по HTTP перенаправляются на HTTPS:

```shell
tuna http 8080 --https-redirect

```

## Заголовки и CORS

### Заголовки запроса

Добавить или заменить заголовок до того, как запрос попадёт в приложение. Частый случай — сервис принимает запросы только со «своим» `Host`, как Ollama или dev-серверы:

```shell
tuna http 11434 --request-header="host:localhost:11434"

```

### Заголовки ответа

Добавить или заменить заголовок в ответе клиенту:

```shell
tuna http 8080 --response-header="env:test" --response-header="x-robots-tag:noindex"

```

### CORS

Флаг `--cors` или переменная `TUNA_CORS=true` добавит CORS заголовки ко всем ответам. По умолчанию выставляются следующие заголовки:

```shell
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Accept, Accept-Language, Content-Language, Origin
Access-Control-Allow-Methods: GET, HEAD, POST
Access-Control-Allow-Origin: *

```

Их можно переопределить с помощью флага [`--response-header`](#response-header).

Запросы типа **OPTIONS** обрабатываются автоматически `200 OK`, не доходя до проксируемого сервера.

примечание

Если у вас возникают сложности в работе с CORS, рекомендуем ознакомиться с [данной статьёй](https://habr.com/ru/companies/macloud/articles/553826/). Так же рекомендуем [CORS Tester](https://corsfix.com/tools/cors-tester) для проверки ваших CORS заголовков.

## Другие возможности

### Политики трафика

Когда флагов не хватает — маршрутизация по путям, условия, свои ответы — опишите правила в файле [политик трафика](https://tuna.am/docs/traffic-policy.md). По умолчанию читается `.tuna.yml` из текущего каталога, другой путь — `--policy-file`. Флаг `--policy-url` загружает правила по [ссылке](https://tuna.am/docs/traffic-policy.md#policy-url). Ошибка в политике останавливает запуск туннеля, подробнее — в разделе [Ошибки политики](https://tuna.am/docs/traffic-policy.md#policy-errors).

### Инспектор запросов

Включён по умолчанию и доступен на `http://127.0.0.1:4040`. Инспектор хранит запросы в памяти, поэтому на серверах и встраиваемых устройствах его лучше выключать: `--inspect=false`. Подробнее — на странице [инспектора](https://tuna.am/docs/tunnels/http/inspect.md).

### Отчёты об ошибках

Флаг `--capture-key` встраивает в HTML-страницы виджет [отчётов](https://tuna.am/docs/reports.md): тестировщик отправляет баг со скриншотом, не меняя код сайта.

```shell
tuna http 3000 --capture-key=YOUR_CAPTURE_KEY

```

### Токен

Обычно токен сохраняется один раз командой `tuna login`. Указать другой токен можно флагом `--token` или переменной `TUNA_TOKEN`, переопределение идёт по [очерёдности конфигурации](https://tuna.am/docs/guides/config-ordering.md):

```shell
tuna http 8080 --token=tt_***

```

## Справочник флагов

| Флаг                      | Переменная окружения         | Описание                                                                   |
| ------------------------- | ---------------------------- | -------------------------------------------------------------------------- |
| `-s`, `--subdomain`       | `TUNA_SUBDOMAIN`             | [Поддомен](#address) в зоне tuna.am                                        |
| `-d`, `--domain`          | `TUNA_DOMAIN`                | [Полное имя домена](#custom-domain)                                        |
| `-l`, `--location`        | `TUNA_LOCATION`              | [Локация](#location): `ru` (по умолчанию), `nl`                            |
| `-f`, `--file-server`     | `TUNA_FILE_SERVER`           | [Раздать каталог](#file-server)                                            |
| `-F`, `--file-server-spa` | `TUNA_FILE_SERVER_SPA`       | [Раздать SPA](#spa)                                                        |
| `--webdav`                | `TUNA_WEBDAV`                | [WebDAV-сервер](#webdav) для каталога                                      |
| `--basic-auth`            | `TUNA_BASIC_AUTH`            | [Логин и пароль](#basic-auth), `login:password`                            |
| `--key-auth`              | `TUNA_KEY_AUTH`              | [Ключ](#key-auth) в заголовке `X-Token`                                    |
| `--cidr-allow`            | `TUNA_CIDR_ALLOW`            | [Разрешённые подсети](#cidr)                                               |
| `--cidr-deny`             | `TUNA_CIDR_DENY`             | [Запрещённые подсети](#cidr)                                               |
| `--ua-allow`              | `TUNA_UA_ALLOW`              | [Разрешённые User-Agent](#user-agent)                                      |
| `--ua-deny`               | `TUNA_UA_DENY`               | [Запрещённые User-Agent](#user-agent)                                      |
| `--verify-webhook`        | `TUNA_VERIFY_WEBHOOK`        | [Провайдер вебхуков](#verify-webhook)                                      |
| `--verify-webhook-secret` | `TUNA_VERIFY_WEBHOOK_SECRET` | Секрет подписи вебхуков                                                    |
| `--rate-limit`            | `TUNA_RATE_LIMIT`            | [Запросов в секунду](#rate-limit)                                          |
| `--https-redirect`        | `TUNA_HTTPS_REDIRECT`        | [Перенаправлять HTTP на HTTPS](#https-redirect)                            |
| `--request-header`        | `TUNA_REQUEST_HEADER`        | [Заголовок запроса](#request-header), `key:value`                          |
| `--response-header`       | `TUNA_RESPONSE_HEADER`       | [Заголовок ответа](#response-header), `key:value`                          |
| `--cors`                  | `TUNA_CORS`                  | [CORS-заголовки](#cors) и ответ на `OPTIONS`                               |
| `--policy-file`           | `TUNA_POLICY_FILE`           | [Файл политик](#traffic-policy), по умолчанию `.tuna.yml`                  |
| `--policy-url`            | `TUNA_POLICY_URL`            | [Ссылка на политики](#traffic-policy), только https                        |
| `--inspect`               | `TUNA_INSPECT`               | [Инспектор](#inspect), по умолчанию включён                                |
| `--tls-skip-verify`       | `TUNA_TLS_SKIP_VERIFY`       | [Не проверять сертификат](#https-upstream) приложения, по умолчанию `true` |
| `--qr`                    | `TUNA_QR_CODE`               | [QR-код](#qr) со ссылкой                                                   |
| `--capture-key`           | `TUNA_CAPTURE_KEY`           | [Виджет отчётов](#capture-key)                                             |
| `--token`                 | `TUNA_TOKEN`                 | [Токен](#token) авторизации                                                |

## Поведение

### Hop-by-hop

**НЕ** поддерживаются hop-by-hop заголовки за исключением заголовка `Connection: upgrade`, необходимого для работы Websocket соединений.

### WebSocket

Поддерживается протокол с двусторонним соединением между клиентом (например, браузером) и сервером, дополнительные настройки не требуются. Передаваемые данные **НЕ** будут отображены в [инспекторе](https://tuna.am/docs/tunnels/http/inspect.md).

### Server-sent events

Поддерживается технология отправки уведомлений от сервера к веб-браузеру, никакие дополнительные настройки не требуются. Передаваемые данные **НЕ** будут отображены в [инспекторе](https://tuna.am/docs/tunnels/http/inspect.md).

примечание

При включенном инспекторе важно, что бы клиент отправлял заголовок `Accept: text/event-stream`, иначе инспектор будет пытаться перехватить запрос и это вызовет задержки. Браузер передаёт этот заголовок по умолчанию.

### HTTP и HTTPS

При старте туннеля отображается ссылка на HTTPS, но обращаться можно и по HTTP. Запретить обращение по HTTP можно добавив флаг `--https-redirect`.

### Порядок обработки флагов и правил

Флаги влияющие на поведение обработки трафика всегда выполняются в определённой последовательности, вне зависимости от того в какой очерёдности они переданы в аргументах.

1. [https-redirect](#https-redirect)
2. [cors](#cors)
3. [cidr-allow](#cidr)
4. [cidr-deny](#cidr)
5. [ua-allow](#user-agent)
6. [ua-deny](#user-agent)
7. [rate-limit](#rate-limit)
8. [key-auth](#key-auth)
9. [basic-auth](#basic-auth)
10. [policy-file / policy-url](#traffic-policy)
11. [verify-webhook](#verify-webhook)
12. [request-header](#request-header)
13. [response-header](#response-header)

### Самостоятельное управление CORS заголовками

Если вы хотите управлять CORS заголовками самостоятельно на нижестоящем сервере и при этом используете авторизацию в tuna при помощи `--basic-auth` или `--key-auth`. То [**Preflight запросы**](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) - **не будут работать**, так как эти *OPTIONS* запросы не содержат `Authorization`, `X-Token` и любые другие специфичные заголовки.

Решение на выбор:

1. Не использовать авторизацию в tuna.
2. Управлять CORS заголовками в tuna, а не самостоятельно.
3. Обрабатывать `OPTIONS` и авторизацию отдельно при помощи более гибких [политик трафика](https://tuna.am/docs/traffic-policy.md).

❓ Как это выглядит на схеме

#### Случай 1

Вы управляете CORS на нижестоящем Caddy сервере и включаете авторизацию в tuna.

![Схема изобращающая Случай 1](/docs/img/tunnels/cors/case1.png)

#### Случай 2

Вы управляете CORS на нижестоящем Caddy сервере и **НЕ** включаете авторизацию в tuna.

![Схема изобращающая Случай 2](/docs/img/tunnels/cors/case2.png)

#### Случай 3

Вы управляете CORS и авторизацией в tuna.

![Схема изобращающая Случай 3](/docs/img/tunnels/cors/case3.png)
