Compare commits
7 Commits
4d8ad30541
...
testing
Author | SHA1 | Date | |
---|---|---|---|
5527ef90e6 | |||
8c07d87efa | |||
1d0ef1535f | |||
0984e169eb | |||
055a3167e8 | |||
4ed6833a9e | |||
d68ca4e787 |
66
README.md
66
README.md
@@ -1,48 +1,82 @@
|
|||||||
## debweb
|
# debweb
|
||||||
|
|
||||||
**debweb** - простой webserver для дебилов (for me) на асинхронных сокетах
|
**debweb** - простой webserver для дебилов (for me) на асинхронных сокетах
|
||||||
|
|
||||||
## установка и настройка
|
# установка и настройка
|
||||||
|
|
||||||
debweb использует всего одну стороннюю библиотеку - aiofiles. ее можно установить с помощью
|
debweb использует всего одну стороннюю библиотеку - aiofiles. ее можно установить с помощью
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install aiofiles
|
pip install aiofiles
|
||||||
```
|
```
|
||||||
> [!IMPORTANT]
|
|
||||||
конфигурация сия шедевра происходит в файле `config.py`
|
конфигурация сия шедевра происходит в файле `config.py`
|
||||||
|
|
||||||
### основное
|
## основное
|
||||||
|
|
||||||
- `name` - название сервера, отображается в http заголовках
|
- `name` - название сервера, отображается в http заголовках
|
||||||
|
- `proxied` - указывает, находится ли сервер за прокси
|
||||||
|
|
||||||
### сеть
|
## сеть
|
||||||
|
|
||||||
- `addr` - адрес сервера
|
- `addr` - адрес сервера
|
||||||
- `port` - порт сервера
|
- `port` - порт сервера
|
||||||
|
|
||||||
### файлы и директории
|
## файлы и директории
|
||||||
|
|
||||||
- `log_file` - файл логов (по умолчанию вывод в консоль)
|
- `log_file` - файл логов (по умолчанию вывод в консоль)
|
||||||
- `preset_file` - файл пресета. обычный html документ. но в нем нужно указать одиночный тег `<FILES>` для отображения файлов в директории
|
- `preset_file` - файл пресета
|
||||||
- `directory` - рабочая директория **обязательно с / на конце!!!!**
|
- `directory` - рабочая директория
|
||||||
|
|
||||||
### буферы
|
## буферы
|
||||||
|
|
||||||
- `read_buffer` - буфер для запроса
|
- `read_buffer` - размер буфера для запросов
|
||||||
- `write_buffer` - размер буфера при отправке файлов
|
- `write_buffer` - размер буфера при отправке файлов
|
||||||
|
|
||||||
### логи
|
## логи
|
||||||
|
|
||||||
- `start_msg` - лог при старте сервера
|
- `start_msg` - лог при старте сервера
|
||||||
- `conn_msg` - лог при подключении
|
- `conn_msg` - лог при подключении
|
||||||
- `get_msg` - лог при GET запросе
|
- `get_msg` - лог при GET запросе
|
||||||
|
|
||||||
`<ADDR>` будет заменен на адрес клиента
|
## теги
|
||||||
|
|
||||||
`<FILE>` будет заменен на файл / директорию, к которой запрашивается доступ
|
- `<ADDR>` - адрес клиента
|
||||||
|
- `<FILE>` - файл / директория, к которой запрашивается доступ
|
||||||
|
- `<TIME>` - время, когда был выполнен запрос
|
||||||
|
|
||||||
### ошибки
|
## шаблоны
|
||||||
|
|
||||||
- `e404_file` - html файл, который будет отправлен при ошибке 404
|
- `file_entry` - шаблон для генерации строк файлов в листинге директории
|
||||||
- `e404_msg` - лог при ошибке 404
|
- `dir_entry` - шаблон для генерации строк каталогов в листинге директории
|
||||||
|
- `time_format` - формат времени для всего документа
|
||||||
|
|
||||||
|
### теги шаблонов
|
||||||
|
|
||||||
|
- `<NAME>` - название элемента
|
||||||
|
- `<REL_PATH>` - относительный путь элемента
|
||||||
|
- `<CDATE>` - дата создания элемент
|
||||||
|
- `<MDATE>` - дата модификации элемента
|
||||||
|
- `<SIZE_B>` - размер файла в байтах
|
||||||
|
- `<SIZE_KB>` - размер файла в килобайтах
|
||||||
|
- `<SIZE_MB>` - размер файла в мегабайтах
|
||||||
|
- все остальные html теги
|
||||||
|
|
||||||
|
## preset.html
|
||||||
|
|
||||||
|
обычный html документ, являющийся шаблоном для листинга каталога. если в директории будет находиться preset.html, сервер будет использовать именно его. в противном случае - тот, который указан в конфиге.
|
||||||
|
|
||||||
|
### теги пресета
|
||||||
|
|
||||||
|
- `<FILES>` - отображает все элементы директории
|
||||||
|
- `<FILE_COUNT>` - количество файлов
|
||||||
|
- `<DIR_COUNT>` - количество подкаталогов
|
||||||
|
- `<TOTAL_COUNT>` - общее количество элементов
|
||||||
|
- `<SERVER>` - название сервера
|
||||||
|
- `<LOAD_TIME>` - время обработки страницы
|
||||||
|
- `<SERVER_TIME>` - время на сервере
|
||||||
|
|
||||||
|
## ошибки
|
||||||
|
|
||||||
|
- `err_Files` - словарь с кодами ошибок и файлами, которые отправляются при этих ошибках
|
||||||
|
- `err_msgs` - словарь с кодами ошибок и логами, которые отправляются при этих ошибках
|
@@ -1,4 +1,4 @@
|
|||||||
name="debweb 1.1.3"
|
name="debweb 1.2.2"
|
||||||
proxied=False
|
proxied=False
|
||||||
|
|
||||||
addr="localhost"
|
addr="localhost"
|
||||||
@@ -15,6 +15,10 @@ start_msg="started at <ADDR>"
|
|||||||
conn_msg="conn from <ADDR>"
|
conn_msg="conn from <ADDR>"
|
||||||
get_msg="<ADDR> got <FILE>"
|
get_msg="<ADDR> got <FILE>"
|
||||||
|
|
||||||
|
file_entry = "<a href='/<REL_PATH>'><NAME></a> <SIZE_KB> <CDATE><br>\n"
|
||||||
|
dir_entry = "<a href='/<REL_PATH>'><NAME></a> <CDATE><br>\n"
|
||||||
|
time_format = "%a %b %e %H:%M:%S %Z %Y"
|
||||||
|
|
||||||
err_files = {
|
err_files = {
|
||||||
404: "html/404.html",
|
404: "html/404.html",
|
||||||
403: "html/403.html",
|
403: "html/403.html",
|
||||||
|
56
main.py
56
main.py
@@ -5,6 +5,8 @@ import datetime
|
|||||||
import aiofiles
|
import aiofiles
|
||||||
import asyncio
|
import asyncio
|
||||||
import config
|
import config
|
||||||
|
import utils
|
||||||
|
import time
|
||||||
import os
|
import os
|
||||||
|
|
||||||
STATUS = {
|
STATUS = {
|
||||||
@@ -15,12 +17,11 @@ STATUS = {
|
|||||||
418: "418 I'm a teapot"
|
418: "418 I'm a teapot"
|
||||||
}
|
}
|
||||||
|
|
||||||
# file_size = os.path.getsize(file_path)
|
|
||||||
|
|
||||||
class WebServer:
|
class WebServer:
|
||||||
async def log(self, text: str, addr: tuple=None, file: str=None) -> None:
|
async def log(self, text: str, addr: tuple=None, file: str=None) -> None:
|
||||||
text = text.replace("<ADDR>", f"{addr[0]}:{addr[1]}" if addr else "")
|
text = text.replace("<ADDR>", f"{addr[0]}:{addr[1]}" if addr else "")
|
||||||
text = text.replace("<FILE>", file if file else "")
|
text = text.replace("<FILE>", file if file else "")
|
||||||
|
text = text.replace("<TIME>", datetime.datetime.now().strftime(config.time_format))
|
||||||
|
|
||||||
if config.log_file:
|
if config.log_file:
|
||||||
async with aiofiles.open(config.log_file, mode="a") as file:
|
async with aiofiles.open(config.log_file, mode="a") as file:
|
||||||
@@ -62,9 +63,10 @@ class WebServer:
|
|||||||
async def handle(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
|
async def handle(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
|
||||||
addr = writer.get_extra_info("peername")
|
addr = writer.get_extra_info("peername")
|
||||||
await self.log(config.conn_msg, addr)
|
await self.log(config.conn_msg, addr)
|
||||||
|
conn_time = time.time()
|
||||||
|
|
||||||
rdata = await reader.read(config.read_buffer)
|
rdata = await reader.read(config.read_buffer)
|
||||||
data = rdata.decode()
|
data = rdata.decode()
|
||||||
|
|
||||||
if not data: return
|
if not data: return
|
||||||
|
|
||||||
real_addr = None
|
real_addr = None
|
||||||
@@ -90,7 +92,7 @@ class WebServer:
|
|||||||
await self.send_headers(writer, 418, file_size)
|
await self.send_headers(writer, 418, file_size)
|
||||||
await self.send_file(writer, config.err_files[418], file_size)
|
await self.send_file(writer, config.err_files[418], file_size)
|
||||||
|
|
||||||
await writer.close()
|
writer.close()
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -107,14 +109,19 @@ class WebServer:
|
|||||||
|
|
||||||
elif os.path.isdir(file_path):
|
elif os.path.isdir(file_path):
|
||||||
resp = ""
|
resp = ""
|
||||||
async with aiofiles.open(config.preset_file, "r", encoding="utf-8") as f:
|
if os.path.isfile(os.path.join(file_path, "preset.html")):
|
||||||
|
preset_file = os.path.join(file_path, "preset.html")
|
||||||
|
else:
|
||||||
|
preset_file = config.preset_file
|
||||||
|
async with aiofiles.open(preset_file, "r", encoding="utf-8") as f:
|
||||||
resp = await f.read()
|
resp = await f.read()
|
||||||
|
|
||||||
files = ""
|
files = ""
|
||||||
base_path = os.path.relpath(file_path, config.directory).replace('\\', '/')
|
base_path = os.path.relpath(file_path, config.directory).replace('\\', '/')
|
||||||
if base_path == '.':
|
if base_path == '.': base_path = ''
|
||||||
base_path = ''
|
|
||||||
|
|
||||||
|
file_count = 0
|
||||||
|
dir_count = 0
|
||||||
for item in sorted(os.listdir(file_path)):
|
for item in sorted(os.listdir(file_path)):
|
||||||
item_path = os.path.join(file_path, item)
|
item_path = os.path.join(file_path, item)
|
||||||
is_dir = os.path.isdir(item_path)
|
is_dir = os.path.isdir(item_path)
|
||||||
@@ -125,16 +132,29 @@ class WebServer:
|
|||||||
if is_dir:
|
if is_dir:
|
||||||
rel_path += "/"
|
rel_path += "/"
|
||||||
item += "/"
|
item += "/"
|
||||||
|
dir_count += 1
|
||||||
modify_time = os.path.getmtime(item_path)
|
else: file_count += 1
|
||||||
modify_datetime = datetime.datetime.fromtimestamp(modify_time)
|
|
||||||
formatted_time = modify_datetime.strftime("%d.%m.%Y %H:%M:%S")
|
|
||||||
|
|
||||||
rel_path_encoded = rel_path.replace(' ', '%20').replace('#', '%23')
|
rel_path_encoded = rel_path.replace(' ', '%20').replace('#', '%23')
|
||||||
|
|
||||||
files += f'<a class={"dir" if is_dir else "file"} href="/{rel_path_encoded}">{item}</a> | {formatted_time}<br>\n'
|
entry = config.dir_entry if is_dir else config.file_entry
|
||||||
|
entry = entry.replace("<NAME>", item)
|
||||||
|
entry = entry.replace("<REL_PATH>", rel_path_encoded)
|
||||||
|
entry = entry.replace("<CDATE>", utils.get_create_time(item_path, config.time_format))
|
||||||
|
entry = entry.replace("<MDATE>", utils.get_mod_time(item_path, config.time_format))
|
||||||
|
entry = entry.replace("<SIZE_B>", f"{os.path.getsize(item_path)}B")
|
||||||
|
entry = entry.replace("<SIZE_KB>", f"{format(os.path.getsize(item_path) / 1024, ".2f")}KB")
|
||||||
|
entry = entry.replace("<SIZE_MB>", f"{format(os.path.getsize(item_path) / 1024 ** 2, ".2f")}MB")
|
||||||
|
|
||||||
|
files += entry
|
||||||
|
|
||||||
resp = resp.replace("<FILES>", files)
|
resp = resp.replace("<FILES>", files)
|
||||||
|
resp = resp.replace("<FILE_COUNT>", str(file_count))
|
||||||
|
resp = resp.replace("<DIR_COUNT>", str(dir_count))
|
||||||
|
resp = resp.replace("<TOTAL_COUNT>", str(file_count + dir_count))
|
||||||
|
resp = resp.replace("<SERVER>", config.name)
|
||||||
|
resp = resp.replace("<LOAD_TIME>", format(time.time() - conn_time, ".3f"))
|
||||||
|
resp = resp.replace("<SERVER_TIME>", datetime.datetime.now().strftime(config.time_format))
|
||||||
resp = resp.encode()
|
resp = resp.encode()
|
||||||
|
|
||||||
await self.send_headers(writer, 200, len(resp))
|
await self.send_headers(writer, 200, len(resp))
|
||||||
@@ -144,10 +164,10 @@ class WebServer:
|
|||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
await self.log(self.e404_msg, addr, file_path)
|
await self.log(config.err_msgs[404], addr, file_path)
|
||||||
file_size = os.path.getsize(file_path)
|
file_size = os.path.getsize(config.err_files[404])
|
||||||
await self.send_headers(writer, 404, file_size)
|
await self.send_headers(writer, 404, file_size)
|
||||||
await self.send_file(writer, self._e404_file, file_size)
|
await self.send_file(writer, config.err_files[404], file_size)
|
||||||
|
|
||||||
writer.close()
|
writer.close()
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
@@ -173,13 +193,13 @@ class WebServer:
|
|||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
s = WebServer()
|
s = WebServer()
|
||||||
if not s.directory:
|
if not config.directory:
|
||||||
print("directory not set!")
|
print("directory not set!")
|
||||||
exit(1)
|
exit(1)
|
||||||
if not s.preset_file:
|
if not config.preset_file:
|
||||||
print("preset file not set!")
|
print("preset file not set!")
|
||||||
exit(1)
|
exit(1)
|
||||||
if not os.path.isfile(s.preset_file):
|
if not os.path.isfile(config.preset_file):
|
||||||
print("invalid preset file")
|
print("invalid preset file")
|
||||||
exit(1)
|
exit(1)
|
||||||
await s.start()
|
await s.start()
|
||||||
|
13
utils.py
Normal file
13
utils.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
|
def get_mod_time(path: str, format: str="%a %b %e %H:%M:%S %Z %Y") -> str:
|
||||||
|
modify_time = os.path.getmtime(path)
|
||||||
|
modify_datetime = datetime.datetime.fromtimestamp(modify_time)
|
||||||
|
return modify_datetime.strftime(format)
|
||||||
|
|
||||||
|
|
||||||
|
def get_create_time(path: str, format: str="%a %b %e %H:%M:%S %Z %Y") -> str:
|
||||||
|
create_time = os.path.getctime(path)
|
||||||
|
create_datetime = datetime.datetime.fromtimestamp(create_time)
|
||||||
|
return create_datetime.strftime(format)
|
Reference in New Issue
Block a user