threaded HTTP server, rate limiting, remove slow-web rhetoric
This commit is contained in:
parent
63610145ee
commit
304f1eedf9
6 changed files with 59 additions and 19 deletions
4
app.py
4
app.py
|
|
@ -4,7 +4,7 @@ import time
|
|||
import threading
|
||||
import argparse
|
||||
import RNS
|
||||
from http.server import HTTPServer
|
||||
from http.server import HTTPServer, ThreadingHTTPServer
|
||||
|
||||
from db import init_db, get_setting, set_setting
|
||||
from handlers import dispatch_request
|
||||
|
|
@ -101,7 +101,7 @@ def start_gateway(reticulum, bind_host="127.0.0.1"):
|
|||
GatewayState.reticulum = reticulum
|
||||
GatewayState.local_dispatch = dispatch_request
|
||||
HTTPServer.allow_reuse_address = True
|
||||
server = HTTPServer((bind_host, gateway.GATEWAY_PORT), GatewayHandler)
|
||||
server = ThreadingHTTPServer((bind_host, gateway.GATEWAY_PORT), GatewayHandler)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
|
||||
|
|
|
|||
25
gateway.py
25
gateway.py
|
|
@ -2,8 +2,9 @@ import re
|
|||
import sys
|
||||
import time
|
||||
import threading
|
||||
import collections
|
||||
import RNS
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from http.server import HTTPServer, ThreadingHTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
APP_NAME = "tinyweb"
|
||||
|
|
@ -11,6 +12,10 @@ ASPECTS = ["server"]
|
|||
GATEWAY_PORT = 8080
|
||||
REQUEST_TIMEOUT = 60
|
||||
MAX_BODY_SIZE = 16 * 1024 * 1024 # 16 MiB — covers /import and every other form
|
||||
RATE_LIMIT_WINDOW = 60
|
||||
RATE_LIMIT_MAX = 30
|
||||
_rate_tracker = collections.defaultdict(list)
|
||||
_rate_lock = threading.Lock()
|
||||
|
||||
|
||||
class GatewayState:
|
||||
|
|
@ -67,12 +72,28 @@ def ensure_link():
|
|||
|
||||
class GatewayHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def _check_rate_limit(self):
|
||||
client = self.client_address[0]
|
||||
now = time.time()
|
||||
with _rate_lock:
|
||||
times = _rate_tracker[client]
|
||||
cutoff = now - RATE_LIMIT_WINDOW
|
||||
while times and times[0] < cutoff:
|
||||
times.pop(0)
|
||||
if len(times) >= RATE_LIMIT_MAX:
|
||||
return False
|
||||
times.append(now)
|
||||
return True
|
||||
|
||||
def _forward(self, method):
|
||||
parsed = urlparse(self.path)
|
||||
query = parse_qs(parsed.query)
|
||||
|
||||
body = {}
|
||||
if method == "POST":
|
||||
if not self._check_rate_limit():
|
||||
self.send_error(429, "Too many requests — slow down.")
|
||||
return
|
||||
try:
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
except ValueError:
|
||||
|
|
@ -187,7 +208,7 @@ def main():
|
|||
|
||||
print(f"Gateway listening on http://localhost:{GATEWAY_PORT}")
|
||||
print(f"Open http://localhost:{GATEWAY_PORT} in your browser")
|
||||
HTTPServer(("127.0.0.1", GATEWAY_PORT), GatewayHandler).serve_forever()
|
||||
ThreadingHTTPServer(("127.0.0.1", GATEWAY_PORT), GatewayHandler).serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
19
handlers.py
19
handlers.py
|
|
@ -1043,9 +1043,10 @@ def handle_about():
|
|||
|
||||
return _respond(
|
||||
f'<h1>{esc(name)}</h1>'
|
||||
f'<p>A personal search engine, built for the slow web.</p>'
|
||||
f'<p>TinyWeb is about taking back the internet. No algorithms, no ads, no tracking. '
|
||||
f'Just human-curated pages shared freely across a mesh network.</p>'
|
||||
f'<p>A personal, decentralized search engine.</p>'
|
||||
f'<p>You save pages you find. They are stored locally and shared over a mesh network '
|
||||
f'so other people can find them too.</p>'
|
||||
f'<p>Search results come from your index and the indexes of people you are connected to.</p>'
|
||||
f'<ul>'
|
||||
f'<li><b>{page_count}</b> page(s) indexed</li>'
|
||||
f'<li><b>{tag_count}</b> tag(s)</li>'
|
||||
|
|
@ -1069,18 +1070,6 @@ def handle_about():
|
|||
f'The <a href="/export">export</a> page gives you a JSON dump of pages only — '
|
||||
f'it does not preserve your identity or subscription state, so it is a migration aid, '
|
||||
f'not a substitute for a full backup.</p>'
|
||||
f'<h2>what is the slow web?</h2>'
|
||||
f'<p>The slow web is a movement for intentionality over speed, '
|
||||
f'human curation over algorithmic feeds, privacy over surveillance, '
|
||||
f'and community over corporations. Every page in this index was saved by a person '
|
||||
f'because they found it valuable — not because an algorithm told them to click.</p>'
|
||||
f'<h2>how it works</h2>'
|
||||
f'<ul>'
|
||||
f'<li>Save pages you find valuable with the bookmarklet or /add</li>'
|
||||
f'<li>Search your personal index — queries never leave your machine</li>'
|
||||
f'<li>Subscribe to friends over Reticulum — encrypted, decentralized, works without the internet</li>'
|
||||
f'<li>Tag and organize your collection into curated lists</li>'
|
||||
f'</ul>'
|
||||
f'<p><a href="/">search</a> | <a href="/pages">browse</a> | <a href="/tags">tags</a></p>'
|
||||
)
|
||||
|
||||
|
|
|
|||
12
templates.py
12
templates.py
|
|
@ -12,6 +12,14 @@ FORUM_CSS = """
|
|||
.forum-form { max-width: 500px; }
|
||||
.forum-form input:not([type=checkbox]):not([type=radio]), .forum-form textarea { width: 100%; box-sizing: border-box; }
|
||||
.forum-form textarea { resize: vertical; }
|
||||
.forum-form input, .forum-form textarea, .forum-form button { margin-bottom: 8px; }
|
||||
.forum-form small { display: block; margin-bottom: 6px; }
|
||||
.forum-form label { display: block; margin-bottom: 6px; }
|
||||
.forum-form + .forum-form { margin-top: 1rem; }
|
||||
.forum-form + .section-title { margin-top: 1rem; }
|
||||
.section-desc + .forum-form { margin-top: 0.8rem; }
|
||||
ul + .forum-form { margin-top: 1rem; }
|
||||
.checkbox-label { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
||||
.forum-toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
||||
.forum-toolbar form { flex: 1; min-width: 160px; margin: 0; }
|
||||
.forum-toolbar input[name=q] { width: 100%; box-sizing: border-box; }
|
||||
|
|
@ -20,6 +28,10 @@ FORUM_CSS = """
|
|||
.forum-status { margin: 0 0 0.8rem 0; }
|
||||
.forum-status span { margin-right: 0.5rem; }
|
||||
.forum-nav { margin: 0.5rem 0; }
|
||||
.section { margin: 1.5rem 0; }
|
||||
.section-title { font-weight: 600; margin-bottom: 0.3rem; }
|
||||
.section-desc { font-size: 0.85rem; margin-bottom: 0.5rem; }
|
||||
.section ul { margin: 0.3rem 0; }
|
||||
</style>"""
|
||||
|
||||
DEFAULT_TEMPLATE = "<html>\n<head>\n<meta name=\"referrer\" content=\"no-referrer\">\n<meta http-equiv=\"x-dns-prefetch-control\" content=\"off\">\n" + FORUM_CSS + "</head>\n<body>\n{{content}}\n</body>\n</html>"
|
||||
|
|
|
|||
|
|
@ -348,6 +348,9 @@
|
|||
.forum-list .thread-title { font-size: 1.05rem; margin-bottom: 0.1rem; }
|
||||
.forum-list .thread-meta { font-size: 0.8rem; opacity: 0.7; }
|
||||
.forum-list .thread-badge { font-size: 0.78rem; opacity: 0.6; }
|
||||
.forum-form input, .forum-form textarea, .forum-form button { margin-bottom: 8px; }
|
||||
.forum-form small { display: block; margin-bottom: 6px; }
|
||||
.forum-form label { display: block; margin-bottom: 6px; }
|
||||
.post { margin-bottom: 1rem; padding-left: 1rem; border-left: 1px solid rgba(40, 70, 65, 0.3); }
|
||||
.post-meta { font-size: 0.82rem; opacity: 0.7; }
|
||||
.section { margin: 1.5rem 0; }
|
||||
|
|
|
|||
|
|
@ -223,6 +223,21 @@
|
|||
background: #f5f5f5;
|
||||
}
|
||||
a.forum-action-inline { text-transform: none; font-size: 13px; padding: 2px 6px; border: none; }
|
||||
.section { margin: 1.5rem 0; }
|
||||
.section-title { font-weight: 600; margin-bottom: 0.3rem; }
|
||||
.section-desc { font-size: 0.85rem; color: #999; margin-bottom: 0.5rem; }
|
||||
.section ul { margin: 0.3rem 0; }
|
||||
.forum-form input, .forum-form textarea, .forum-form button { margin-bottom: 8px; }
|
||||
.forum-form small { display: block; margin-bottom: 6px; }
|
||||
.forum-form label { display: block; margin-bottom: 6px; }
|
||||
.forum-form + .forum-form { margin-top: 1rem; }
|
||||
.forum-form + .section-title { margin-top: 1rem; }
|
||||
.section-desc + .forum-form { margin-top: 0.8rem; }
|
||||
ul + .forum-form { margin-top: 1rem; }
|
||||
.checkbox-label { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
||||
.forum-status { font-size: 0.82rem; color: #999; margin: 0 0 0.8rem 0; }
|
||||
.forum-status span { margin-right: 1.2rem; }
|
||||
.forum-nav { margin: 1rem 0; }
|
||||
hr { border: none; border-top: 1px solid #eee; margin: 16px 0; }
|
||||
|
||||
small {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue