1
0
forked from nat/sludge

add logging, update readme

This commit is contained in:
gnat 2024-08-14 14:00:27 -07:00
parent 6175fafd20
commit bdc96b5700
10 changed files with 128 additions and 23 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
config.yaml
log
# ---> Python # ---> Python
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/

View File

@ -1,3 +1,63 @@
# Sludge: webthing for natalieee.net # sludge: webthing for natalieee.net
it rhymes with kludge. it rhymes with kludge.
## config
```yaml
ssl-key: ./key.pem
ssl-cert: ./cert.pem
http-port: 5000
https-port: # 5001
file-dir: 'site'
```
ssl-{cert,key} are self explanatory.
if the value of https? port is commented or not present, it will not run the https? server on that port.
file-dir is where the files for the site can be found.
the above config is the config for the local version of my website, which is used for testing.
it runs an http server on 5000, but no https server.
## serving files
- src/lib/router.py has the routing config for my website in it. it is easy to modify to your usecase.
- src/lib/patchers.py has the patching config for my website in it. it is also easy to modify.
## dynamic content
### embedded bash scripting
sludge has support for inline bash inside of html documents.<br>
ex:
```html
<p>lorem ipsum $[echo foo]</p>
```
would resolve to
```html
<p>lorem ipsum foo</p>
```
### variables in html
in addition to the above, you can have variables embedded in html.
```html
<p>lorem ipsum {aoeu}</p>
```
would normally resolve to
```html
<p>lorem ipsum {aoeu}</p>
```
however, if we assume that this text is in a file `foo.html`, then we can serve that file such that
```py
parse_file('./foo.html', dict(aoeu='foo'))
```
in which case it would resolve to
```html
<p>lorem ipsum foo</p>
```
<br>
in practice, this may be seen here:
```py
Route(
lambda path: os.path.isdir('.' + path.path),
[Method.GET],
lambda request, *_: Response(
ResponseCode.OK,
{'Content-Type': 'text/html'},
parse_file('./dir_index.html', dict(path='.' + request.path.path)).encode('utf-8')
)
)
```

View File

@ -45,8 +45,7 @@ def remove_html_tags(input_string: str) -> str:
def error_page(code: int) -> Response: def error_page(code: int) -> Response:
type = ResponseCode(code) type = ResponseCode(code)
print('error page called') return Response(
aoeu= Response(
type, type,
{'Content-Type': 'text/html'}, {'Content-Type': 'text/html'},
parse(f''' parse(f'''
@ -61,8 +60,6 @@ def error_page(code: int) -> Response:
</html> </html>
''').encode('utf-8') ''').encode('utf-8')
) )
print(aoeu)
return aoeu
def page(title, body): def page(title, body):
return parse(""" return parse("""

22
src/lib/logger.py Normal file
View File

@ -0,0 +1,22 @@
import logging
log = logging.getLogger()
log.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s | %(name)s | %(threadName)s | %(levelname)s: %(message)s')
file_logger = logging.FileHandler('log')
stream_logger = logging.StreamHandler()
file_logger.setLevel(logging.DEBUG)
stream_logger.setLevel(logging.INFO)
file_logger.setFormatter(formatter)
stream_logger.setFormatter(formatter)
log.addHandler(file_logger)
log.addHandler(stream_logger)
log.info('log initialized')

View File

@ -86,5 +86,10 @@ patchers: List[Patcher] = [
response.code, response.code,
response.headers, response.headers,
uwuify(response.body).encode('utf-8') uwuify(response.body).encode('utf-8')
) if 'text/html' in response.headers.values() and is_subdict({'uwu': 'true'}, request.path.params) else response ) if 'text/html' in response.headers.values() and is_subdict({'uwu': 'true'}, request.path.params) else response,
lambda response, request: Response(
response.code,
response.headers,
re.sub(r'sludge', lambda match: 'sludge' + ' (/&#x73;&#x6c;&#x28c;&#x64;&#x361;&#x292;/)' if random.randint(0, 5) < 1 else 'sludge', response.body.decode('utf-8')).encode('utf-8')
) if 'text/html' in response.headers.values() else response
] ]

View File

@ -5,6 +5,7 @@ from .path import Path
from .headers import Headers from .headers import Headers
from .body import Body from .body import Body
from .router import routes from .router import routes
from .logger import log
@dataclass @dataclass
class Request: class Request:
@ -43,12 +44,13 @@ class Request:
body_start = request_str.find('\r\n\r\n') + 4 body_start = request_str.find('\r\n\r\n') + 4
body = Body(request_bytes[body_start:], headers.get('Content-Type') or 'text/plain') body = Body(request_bytes[body_start:], headers.get('Content-Type') or 'text/plain')
log.info(f'received request for {path.path} from {headers.get('X-Real-IP')}')
return cls(method, path, version, headers, body) return cls(method, path, version, headers, body)
def match(self): def match(self):
for route in routes: for route in routes:
if route.matches(self): if route.matches(self):
print(route) log.debug(f'matched {self} with {route}')
return route return route
def __repr__(self): def __repr__(self):

View File

@ -1,6 +1,7 @@
from socket import socket from socket import socket
from typing import Dict from typing import Dict
from .responsecodes import ResponseCode from .responsecodes import ResponseCode
from .logger import log
class Response: class Response:
def __init__(self, code: ResponseCode, headers: Dict[str, str], body: bytes): def __init__(self, code: ResponseCode, headers: Dict[str, str], body: bytes):
@ -16,6 +17,9 @@ class Response:
) )
def send(self, client: socket) -> None: def send(self, client: socket) -> None:
print(self) log.debug(f'sending {self} to {client}')
client.sendall(self.build_response()) client.sendall(self.build_response())
client.close() client.close()
def __repr__(self):
return f'Response(code={self.code}, headers={self.headers}, body={self.body[:256]})'

View File

@ -8,7 +8,9 @@ from .response import Response
from .responsecodes import ResponseCode from .responsecodes import ResponseCode
from .content import * from .content import *
from .patchers import patchers from .patchers import patchers
from .logger import log
import os import os
import traceback
@dataclass @dataclass
class Route: class Route:
@ -28,7 +30,7 @@ class Route:
return response return response
except Exception as e: except Exception as e:
print(e) log.error(tracebark.format_exc)
return error_page(500) return error_page(500)
def matches(self, request: 'Request') -> bool: def matches(self, request: 'Request') -> bool:
@ -46,7 +48,6 @@ routes = [
[ [
(lambda form_data: ( (lambda form_data: (
(lambda time: ( (lambda time: (
print('\n\nFORM DATA!!!!',form_data,request, '\n\n'),
f:=open(f'./files/posts-to-homepage/post_{time}.txt', 'w'), f:=open(f'./files/posts-to-homepage/post_{time}.txt', 'w'),
f.write(f"<i style='font-family: MapleMonoItalic'>{form_data['name']}</i>@{time}<br>{form_data['text']}<br><br>"), f.write(f"<i style='font-family: MapleMonoItalic'>{form_data['name']}</i>@{time}<br>{form_data['text']}<br><br>"),
f.close() f.close()
@ -60,7 +61,7 @@ routes = [
parse_file('./home.html').encode('utf-8') parse_file('./home.html').encode('utf-8')
][1] ][1]
)) ))
) if len(request.body.data) > 0 or request.method != Method.POST else error_page(ResponseCode.BAD_REQUEST) ) if len(request.body.data) > 0 or request.method != Method.POST else error_page(400)
), ),
Route( Route(
lambda path: os.path.isdir('.' + path.path), lambda path: os.path.isdir('.' + path.path),

View File

@ -1,21 +1,30 @@
import socket import socket
import threading import threading
import traceback
from typing import Callable from typing import Callable
from .logger import log
def serve(address: str, port: int, callback: Callable, wrapper: Callable[[socket.socket], socket.socket] = lambda s: s) -> None: def serve(address: str, port: int, callback: Callable, wrapper: Callable[[socket.socket], socket.socket] = lambda s: s) -> None:
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
while True: log.info(f'server started on {port}')
try: try:
server_socket.bind((address, port)) server_socket.bind((address, port))
server_socket.listen(1) server_socket.listen(1)
server_socket = wrapper(server_socket) server_socket = wrapper(server_socket)
while True: while True:
try:
conn, addr = server_socket.accept() conn, addr = server_socket.accept()
client_connection = threading.Thread(target=callback, args=(conn, addr)) client_connection = threading.Thread(target=callback, args=(conn, addr))
client_connection.start() client_connection.start()
except Exception as e: except Exception:
print(e) log.warn(traceback.format_exc())
finally: server_socket.close() except:
log.critical(traceback.format_exc())
finally:
server_socket.close()
log.info(f'server on {port} shut down')

View File

@ -1,4 +1,5 @@
from lib import Request, serve from lib import Request, serve
from lib.logger import log
from typing import Tuple from typing import Tuple
import threading import threading
import socket import socket
@ -16,7 +17,6 @@ def handle_client(client: socket.socket, addr: Tuple[str, int]) -> None:
while (data := client.recv(1024)): while (data := client.recv(1024)):
request += data request += data
print(len(data), data)
if len(data) < 1024: break if len(data) < 1024: break
@ -25,6 +25,8 @@ def handle_client(client: socket.socket, addr: Tuple[str, int]) -> None:
.execute(request, client, addr) \ .execute(request, client, addr) \
.send(client) .send(client)
log.info('destroy thread')
def main() -> None: def main() -> None:
http_thread = threading.Thread(name='http', target=serve, args=('0.0.0.0', config['http-port'], handle_client)) http_thread = threading.Thread(name='http', target=serve, args=('0.0.0.0', config['http-port'], handle_client))
https_thread = threading.Thread(name='https', target=serve, args=('0.0.0.0', config['https-port'], handle_client), kwargs=dict(wrapper=lambda socket: [ https_thread = threading.Thread(name='https', target=serve, args=('0.0.0.0', config['https-port'], handle_client), kwargs=dict(wrapper=lambda socket: [