|
1
|
1 # d2o
|
|
|
2
|
|
|
3 Минималистичный веб-сервер на Go с конфигурацией в формате ICF.
|
|
|
4
|
|
|
5 ## Структура проекта
|
|
|
6
|
|
|
7 ```
|
|
|
8 d2o/
|
|
|
9 go.mod
|
|
|
10 cmd/d2o/
|
|
|
11 main.go — точка входа, HTTP-обработчик, сборка листеров
|
|
|
12 icf/
|
|
|
13 icf.go — парсер формата ICF (переиспользуемая библиотека)
|
|
|
14 fcgi/
|
|
|
15 fcgi.go — минимальный FastCGI-клиент
|
|
|
16 ```
|
|
|
17
|
|
|
18 ## Сборка и запуск
|
|
|
19
|
|
|
20 ```sh
|
|
|
21 go build -o d2o ./cmd/d2o/
|
|
|
22 ./d2o # читает /etc/d2obase
|
|
|
23 ./d2o /path/to/config # альтернативный путь
|
|
|
24 ```
|
|
|
25
|
|
|
26 ---
|
|
|
27
|
|
|
28 ## Формат конфигурации ICF
|
|
|
29
|
|
|
30 **ICF (Inherited Configuration Format)** — текстовый формат правил вида «паттерн → директивы».
|
|
|
31 Вдохновлён синтаксисом bash и базами данных CoreDNS.
|
|
|
32
|
|
|
33 ### Основные правила синтаксиса
|
|
|
34
|
|
|
35 ```
|
|
|
36 ; Это комментарий
|
|
|
37
|
|
|
38 ; Переменная (без пробелов вокруг =)
|
|
|
39 KEY=value
|
|
|
40
|
|
|
41 ; Абстрактный блок (миксин)
|
|
|
42 @name
|
|
|
43 |> directive arg1 arg2
|
|
|
44
|
|
|
45 ; Конкретный блок с наследованием миксина
|
|
|
46 block.id @mixin
|
|
|
47 |> directive arg1 arg2
|
|
|
48
|
|
|
49 ; Блок с именованным capture-группой
|
|
|
50 <sub>.example.com
|
|
|
51 |> root /srv/$sub
|
|
|
52 ```
|
|
|
53
|
|
|
54 ### Переменные
|
|
|
55
|
|
|
56 Объявляются как `KEY=value` на уровне файла. Подставляются через `$KEY` в аргументах директив.
|
|
|
57
|
|
|
58 ```
|
|
|
59 WWW=/srv/www
|
|
|
60 CERT=/etc/acme/example.com
|
|
|
61
|
|
|
62 example.com
|
|
|
63 |> root $WWW/root
|
|
|
64 |> tls $CERT.{crt,key}
|
|
|
65 ```
|
|
|
66
|
|
|
67 ### Brace expansion
|
|
|
68
|
|
|
69 Аргумент `prefix.{a,b,c}` раскрывается в три отдельных аргумента: `prefix.a`, `prefix.b`, `prefix.c`.
|
|
|
70 Порядок важен — используется для `tls`, где первый аргумент сертификат, второй ключ.
|
|
|
71
|
|
|
72 ```
|
|
|
73 |> tls /etc/acme/example.com.{crt,key}
|
|
|
74 ; эквивалентно:
|
|
|
75 |> tls /etc/acme/example.com.crt /etc/acme/example.com.key
|
|
|
76 ```
|
|
|
77
|
|
|
78 ### Capture-группы
|
|
|
79
|
|
|
80 В идентификаторе блока `<name>` захватывает любую подстроку до следующего литерала.
|
|
|
81 `<_>` — анонимный wildcard, ничего не сохраняет.
|
|
|
82
|
|
|
83 ```
|
|
|
84 <sub>.example.com
|
|
|
85 |> root /srv/$sub ; $sub подставляется из захваченного значения
|
|
|
86
|
|
|
87 <_>.static.example.com
|
|
|
88 |> root /srv/static ; совпадает с чем угодно, capture не нужен
|
|
|
89 ```
|
|
|
90
|
|
|
91 Capture жадный слева: `<sub>.example.com` на запрос `a.b.example.com` даст `sub=a.b`.
|
|
|
92
|
|
|
93 ### Кавычки в аргументах
|
|
|
94
|
|
|
95 Аргументы с пробелами берутся в двойные кавычки:
|
|
|
96
|
|
|
97 ```
|
|
|
98 |> fcgi unix:/run/php-fpm.sock "*.php"
|
|
|
99 ```
|
|
|
100
|
|
|
101 ### Абстрактные блоки и наследование
|
|
|
102
|
|
|
103 `@name` задаёт набор директив без привязки к паттерну.
|
|
|
104 Конкретный блок наследует их через `block.id @name` — директивы миксина идут первыми, директивы блока их перекрывают.
|
|
|
105
|
|
|
106 ```
|
|
|
107 @base
|
|
|
108 |> port 80
|
|
|
109 |> port+tls 443
|
|
|
110
|
|
|
111 example.com @base
|
|
|
112 |> root /srv/www ; наследует port 80 и port+tls 443
|
|
|
113
|
|
|
114 other.com @base
|
|
|
115 |> root /srv/other ; то же самое
|
|
|
116 ```
|
|
|
117
|
|
|
118 ### Матчинг блоков
|
|
|
119
|
|
|
120 - Домен матчится точно (с учётом capture-групп).
|
|
|
121 - Путь матчится как префикс: блок `example.com/api` сработает на `/api/users`.
|
|
|
122 - При нескольких совпадениях выигрывает наиболее специфичный (больше совпавших литеральных символов).
|
|
|
123 - Сначала проверяется `host/path`, потом `host`.
|
|
|
124
|
|
|
125 ```
|
|
|
126 example.com
|
|
|
127 |> root /srv/www
|
|
|
128
|
|
|
129 example.com/api
|
|
|
130 |> rprx 127.0.0.1:8080 ; перекрывает блок выше для /api/*
|
|
|
131 ```
|
|
|
132
|
|
|
133 ---
|
|
|
134
|
|
|
135 ## Конфигурация d2o
|
|
|
136
|
|
|
137 Файл по умолчанию: `/etc/d2obase`
|
|
|
138
|
|
|
139 ### @d2o — настройки сервера
|
|
|
140
|
|
|
141 ```
|
|
|
142 @d2o
|
|
|
143 |> threads 512 ; GOMAXPROCS
|
|
|
144 ```
|
|
|
145
|
|
|
146 | Директива | Аргументы | Описание |
|
|
|
147 |------------|-----------|----------|
|
|
|
148 | `threads` | N | Количество потоков ОС (GOMAXPROCS) |
|
|
|
149
|
|
|
150 ### port — HTTP-листенер
|
|
|
151
|
|
|
152 ```
|
|
|
153 |> port 80
|
|
|
154 ```
|
|
|
155
|
|
|
156 | # | Тип | Описание |
|
|
|
157 |---|-------|--------------|
|
|
|
158 | 1 | `int` | Номер порта |
|
|
|
159
|
|
|
160 ### port+tls — HTTPS-листенер
|
|
|
161
|
|
|
162 ```
|
|
|
163 |> port+tls 443
|
|
|
164 ; сертификат берётся из директивы tls того же блока
|
|
|
165 ```
|
|
|
166
|
|
|
167 Знак `+` в имени директивы — обычный символ, не оператор.
|
|
|
168
|
|
|
169 | # | Тип | Описание |
|
|
|
170 |---|--------|-----------------------------------------------|
|
|
|
171 | 1 | `int` | Номер порта |
|
|
|
172 | 2 | `path` | Путь к сертификату (необязателен, если есть `tls`) |
|
|
|
173 | 3 | `path` | Путь к ключу (необязателен, если есть `tls`) |
|
|
|
174
|
|
|
175 ### tls — пути к сертификату и ключу
|
|
|
176
|
|
|
177 ```
|
|
|
178 |> tls /etc/acme/example.com.{crt,key}
|
|
|
179 ```
|
|
|
180
|
|
|
181 Используется как источник сертификата для `port+tls` в том же блоке, если пути не указаны явно.
|
|
|
182
|
|
|
183 | # | Тип | Описание |
|
|
|
184 |---|--------|-------------------|
|
|
|
185 | 1 | `path` | Путь к сертификату |
|
|
|
186 | 2 | `path` | Путь к ключу |
|
|
|
187
|
|
|
188 ### root — отдача статики
|
|
|
189
|
|
|
190 ```
|
|
|
191 |> root /srv/www/example.com
|
|
|
192 |> root /srv/www/example.com hide ; листинг запрещён (по умолчанию)
|
|
|
193 |> root /srv/www/example.com show ; листинг разрешён, index-файл index.html
|
|
|
194 |> root /srv/www/example.com show index.php index.html ; свои index-файлы
|
|
|
195 ```
|
|
|
196
|
|
|
197 | # | Тип | Описание |
|
|
|
198 |---|--------|---------------------------------|
|
|
|
199 | 1 | `path` | Корневая директория |
|
|
|
200 | 2 | `show\|hide` | Режим директорий (по умолчанию `hide`) |
|
|
|
201 | 3–14 | `filename` | Index-файлы (только с `show`), проверяются по порядку |
|
|
|
202
|
|
|
203 При `show`: сначала ищутся index-файлы по списку, если ни один не найден — отдаётся листинг директории.
|
|
|
204 При `hide` или без аргумента: запрос к директории возвращает 403.
|
|
|
205
|
|
|
206 ### fcgi — FastCGI
|
|
|
207
|
|
|
208 ```
|
|
|
209 |> fcgi unix:/run/php-fpm.sock
|
|
|
210 |> fcgi unix:/run/php-fpm.sock "*.php" ; только .php файлы
|
|
|
211 |> fcgi 127.0.0.1:9000 "*.php"
|
|
|
212 ```
|
|
|
213
|
|
|
214 | # | Тип | Описание |
|
|
|
215 |---|---------|----------------------------------------------------|
|
|
|
216 | 1 | `addr` | Адрес сокета: `unix:/path` или `host:port` |
|
|
|
217 | 2 | `glob` | Паттерн файлов (по умолчанию `*`, все запросы) |
|
|
|
218
|
|
|
219 Если glob не совпадает, запрос падает в `root` (если задан).
|
|
|
220
|
|
|
221 ### rprx — обратный прокси
|
|
|
222
|
|
|
223 ```
|
|
|
224 |> rprx 127.0.0.1:3000
|
|
|
225 |> rprx http://127.0.0.1:3000
|
|
|
226 ```
|
|
|
227
|
|
|
228 | # | Тип | Описание |
|
|
|
229 |---|-------|---------------------------------|
|
|
|
230 | 1 | `url` | Адрес backend (схема необязательна, по умолчанию `http://`) |
|
|
|
231
|
|
|
232 ### Приоритет директив
|
|
|
233
|
|
|
234 При одновременном наличии нескольких директив порядок обработки: **rprx > fcgi > root**.
|
|
|
235
|
|
|
236 ---
|
|
|
237
|
|
|
238 ## Пример полного конфига
|
|
|
239
|
|
|
240 ```
|
|
|
241 ; /etc/d2obase
|
|
|
242
|
|
|
243 ACME=/etc/acme/qwaderton.org
|
|
|
244 WWW=/srv/www/qwaderton.org
|
|
|
245
|
|
|
246 @d2o
|
|
|
247 |> threads 512
|
|
|
248
|
|
|
249 @ports
|
|
|
250 |> tls $ACME.{crt,key}
|
|
|
251 |> port 80
|
|
|
252 |> port+tls 443
|
|
|
253
|
|
|
254 qwaderton.org @ports
|
|
|
255 |> root $WWW/root show index.php index.html
|
|
|
256
|
|
|
257 qwaderton.org/webfeather @ports
|
|
|
258 |> root $WWW/root/webfeather
|
|
|
259 |> fcgi unix:/run/php-fpm.sock *.php
|
|
|
260
|
|
|
261 <sub>.qwaderton.org @ports
|
|
|
262 |> root $WWW/$sub
|
|
|
263 ```
|
|
|
264
|
|
|
265 ---
|
|
|
266
|
|
|
267 ## Известные ограничения
|
|
|
268
|
|
|
269 - **Один миксин на блок.** Множественное наследование не поддерживается.
|
|
|
270 - **Brace expansion без вложенности.** `{a,{b,c}}` не работает.
|
|
|
271 - **FastCGI — один запрос на соединение.** Keep-alive с FPM не реализован.
|
|
|
272 - **Нет HTTP→HTTPS редиректа** из коробки — нужно реализовывать отдельным блоком на порту 80.
|
|
|
273 - **Нет hot reload** конфига — требуется перезапуск процесса. |