-
Tremolo can be treated as a web framework?how to handle the request methods? |
Beta Was this translation helpful? Give feedback.
Replies: 20 comments 18 replies
-
Absolutely, Tremolo is a hybrid between ASGI server and web framework. If you need something simple to work, Tremolo can work just like Flask but without the need for Gunicorn for production. You only need this 1 library.
request.method Start with the basics. if you want to know. |
Beta Was this translation helpful? Give feedback.
-
Yeah.can you add a render to support the frontend templating? |
Beta Was this translation helpful? Give feedback.
-
Template engines tend to be too bloated to include in core. I think you can render your own with sync handlers as discussed in #283 (comment). |
Beta Was this translation helpful? Give feedback.
-
can just add a bridge to template engine,eg jinja2,not all of template engine |
Beta Was this translation helpful? Give feedback.
-
and,how to serve static files? |
Beta Was this translation helpful? Give feedback.
-
I'm not sure why a bridge is necessary since it's doable with just create your own helper function, or use Jinja directly.
await response.sendfile(path, content_type='text/html') If it's a single file. See: https://nggit.github.io/tremolo-docs/how-to/resumable-downloads.html If you want to create a custom handler that dinamically maps to your files see: #288 (comment) |
Beta Was this translation helpful? Give feedback.
-
too complex. |
Beta Was this translation helpful? Give feedback.
-
Yes. It looks complex, We don't hide the important details which make users aware of the security aspects. |
Beta Was this translation helpful? Give feedback.
-
the frontend templating and static files serving are usual work of a web framework, so |
Beta Was this translation helpful? Give feedback.
-
Will be added in the future if there is a lot of demand, or the best way is found. For now I think creating an extension like the following is not that complex: from functools import wraps
from jinja2 import Environment, FileSystemLoader
class TemplateExtension:
def __init__(self, app, searchpath, **kwargs):
self.env = Environment(loader=FileSystemLoader(searchpath, **kwargs))
app.template = self.template
def template(self, name):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
context = func(*args, **kwargs)
return self.env.get_template(name).render(context)
return wrapper
return decorator Usage: from tremolo import Application
app = Application()
# apply extension
TemplateExtension(app, 'templates')
@app.route('/hello')
@app.template('hello.html')
def hello_world(**server):
return {
'title': 'My Page',
'username': 'Alice',
'items': ['Milk', 'Bread', 'Eggs']
}
if __name__ == '__main__':
app.run('0.0.0.0', 8000, debug=True) templates/hello.html: <!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Welcome, {{ username }}!</h1>
<h2>Your Shopping List</h2>
<ul>
{% for item in items %}
<li>{{ loop.index }}. {{ item }}</li>
{% endfor %}
</ul>
</body>
</html>
|
Beta Was this translation helpful? Give feedback.
-
Good to templating! and,how about static files serving?mainly, the complex things are in serving static files in Tremolo |
Beta Was this translation helpful? Give feedback.
-
this method can work normally,but IDE will show a warn as follows: |
Beta Was this translation helpful? Give feedback.
-
Create the following middleware: import os
from urllib.parse import unquote_to_bytes
from tremolo.exceptions import Forbidden
class StaticMiddleware:
def __init__(self, app, **options):
self.app = app
self.document_root = os.path.abspath(
options.get('document_root', os.getcwd())
)
self.mime_types = {
# core documents
'.html': 'text/html; charset=utf-8',
'.htm': 'text/html; charset=utf-8',
'.xhtml': 'application/xhtml+xml',
# styles & scripts
'.css': 'text/css',
'.js': 'application/javascript',
'.mjs': 'application/javascript',
'.json': 'application/json',
# images
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.avif': 'image/avif',
# fonts
'.woff2': 'font/woff2',
'.woff': 'font/woff',
'.ttf': 'font/ttf',
'.otf': 'font/otf',
# audio/video
'.mp3': 'audio/mpeg',
'.wav': 'audio/wav',
'.mp4': 'video/mp4',
'.webm': 'video/webm',
# text/plain
'.txt': 'text/plain',
'.xml': 'application/xml',
# JSON-LD (SEO)
'.jsonld': 'application/ld+json',
# manifest
'.webmanifest': 'application/manifest+json',
}
self.app.add_middleware(
self._static_middleware, 'request', priority=9999
)
async def _static_middleware(self, request, response, **server):
path = unquote_to_bytes(request.path).decode('latin-1')
filepath = os.path.abspath(
os.path.join(self.document_root,
os.path.normpath(path.lstrip('/')))
)
if not filepath.startswith(self.document_root):
raise Forbidden('Path traversal is not allowed')
if '/.' in path and not path.startswith('/.well-known/'):
raise Forbidden('Access to dotfiles is prohibited')
dirname, basename = os.path.split(filepath)
ext = os.path.splitext(basename)[-1]
if ext != '' and os.path.isfile(filepath):
if ext not in self.mime_types:
raise Forbidden(f'Disallowed file extension: {ext}')
await response.sendfile(filepath,
content_type=self.mime_types[ext])
return True UsageSame as above example. app = Application()
# apply extension
TemplateExtension(app, 'templates')
# apply middleware
# `document_root` is local folder in your computer
StaticMiddleware(app, document_root='static') |
Beta Was this translation helpful? Give feedback.
-
if add middleware like you given,as follows: StaticMiddleware(app, document_root='static') the variables are(2 sub-dirname 'static') filepath='D:\\py\\tremolo\\work\\static\\static\\dist\\css\\adminlte.min.css'
path='/static/dist/css/adminlte.min.css' here the filename in frontend html file is in variable 'path' if modify it to StaticMiddleware(app) the output is OK |
Beta Was this translation helpful? Give feedback.
-
and your code got a alarm: http_server: INFO: middleware _static_middleware has exited with the connection possibly left open |
Beta Was this translation helpful? Give feedback.
-
and, because reailize serving static files via middleware,if at the same time,an on_requset handler exist,things become uncertain StaticMiddleware(app)
@app.on_request
async def before(**server):
print('Before request') got [2025-07-16 14:24:05] Tremolo worker (pid 11880) is started at 0.0.0.0 port 8000
Before request
[2025-07-16 14:24:09,923] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:24:09,929] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:24:09,933] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:24:09,936] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:24:09,938] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:24:09,941] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:24:09,945] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:24:09,948] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:24:09,950] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:24:09,989] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open if @app.on_request
async def before(**server):
print('Before request')
StaticMiddleware(app) got [2025-07-16 14:25:04] Tremolo worker (pid 18584) is started at 0.0.0.0 port 8000
Before request
Before request
Before request
Before request
[2025-07-16 14:25:09,791] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:25:09,797] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:25:09,800] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
Before request
Before request
Before request
Before request
Before request
[2025-07-16 14:25:09,802] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:25:09,804] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:25:09,807] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:25:09,811] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:25:09,812] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
[2025-07-16 14:25:09,816] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open
Before request
Before request
[2025-07-16 14:25:09,858] http_server: INFO: middleware _static_middleware has exited with the connection possibly left open |
Beta Was this translation helpful? Give feedback.
-
my html file include 9 static files,so the alarm 'http_server: INFO: middleware _static_middleware has exited with the connection possibly left open' appears more than 9 times <link rel="shortcut icon" href="/static/dist/img/paperairplane.ico" />
<!-- Font Awesome -->
<link rel="stylesheet" href="/static/plugins/fontawesome-free/css/all.min.css">
<!-- Theme style -->
<link rel="stylesheet" href="/static/dist/css/adminlte.min.css">
<!-- overlayScrollbars -->
<link rel="stylesheet" href="/static/plugins/overlayScrollbars/css/OverlayScrollbars.min.css">
<!-- jQuery -->
<script src="/static/plugins/jquery/jquery.min.js"></script>
<!-- AdminLTE App -->
<script src="/static/dist/js/adminlte.js"></script>
<link rel="stylesheet" href="/static/plugins/datatables-bs4/css/dataTables.bootstrap4.min.css">
<script src="/static/plugins/datatables/jquery.dataTables.min.js"></script>
<script src="/static/plugins/datatables-bs4/js/dataTables.bootstrap4.min.js"></script> |
Beta Was this translation helpful? Give feedback.
-
thanks. from functools import wraps
from jinja2 import Environment, FileSystemLoader
class TemplateExtension:
def __init__(self, searchpath, **kwargs):
self.env = Environment(loader=FileSystemLoader(searchpath), **kwargs)
self.env_async = Environment(loader=FileSystemLoader(searchpath), enable_async=True, **kwargs)
# app.template = self.template
def template(self, name):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
context = func(*args, **kwargs)
return self.env.get_template(name).render(context)
return wrapper
return decorator
def template_async(self, name):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
context = func(*args, **kwargs)
return await self.env_async.get_template(name).render_async(context)
return wrapper
return decorator
render=TemplateExtension('templates')
import os
from urllib.parse import unquote_to_bytes
from tremolo.exceptions import Forbidden
class StaticMiddleware:
def __init__(self, app, **options):
self.app = app
self.document_root = os.path.abspath(
options.get('document_root', os.getcwd())
)
self.mime_types = {
# core documents
'.html': 'text/html; charset=utf-8',
'.htm': 'text/html; charset=utf-8',
'.xhtml': 'application/xhtml+xml',
# styles & scripts
'.css': 'text/css',
'.js': 'application/javascript',
'.mjs': 'application/javascript',
'.json': 'application/json',
# images
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.avif': 'image/avif',
# fonts
'.woff2': 'font/woff2',
'.woff': 'font/woff',
'.ttf': 'font/ttf',
'.otf': 'font/otf',
# audio/video
'.mp3': 'audio/mpeg',
'.wav': 'audio/wav',
'.mp4': 'video/mp4',
'.webm': 'video/webm',
# text/plain
'.txt': 'text/plain',
'.xml': 'application/xml',
# JSON-LD (SEO)
'.jsonld': 'application/ld+json',
# manifest
'.webmanifest': 'application/manifest+json',
}
self.app.add_middleware(self._static_middleware, 'request',priority=9999)
async def _static_middleware(self, request, response, **server):
path = unquote_to_bytes(request.path).decode('latin-1')
filepath = os.path.abspath(
os.path.join(self.document_root,
os.path.normpath(path.lstrip('/')))
)
if not filepath.startswith(self.document_root):
raise Forbidden('Path traversal is not allowed')
if '/.' in path and not path.startswith('/.well-known/'):
raise Forbidden('Access to dotfiles is prohibited')
dirname, basename = os.path.split(filepath)
ext = os.path.splitext(basename)[-1]
if ext != '' and os.path.isfile(filepath):
if ext not in self.mime_types:
raise Forbidden(f'Disallowed file extension: {ext}')
await response.sendfile(filepath, content_type=self.mime_types[ext])
return True
return None |
Beta Was this translation helpful? Give feedback.
-
If a templating engine is integrated into tremolo, I'd suggest basing it on Mustache - this is the standard I've landed on after spending a long time looking around. I lean towards logic-less templates because it decouples view from language-specific syntax. It might be a good exercise to figure out how to build a stream-oriented template engine. I can probably give it a shot if there's interest |
Beta Was this translation helpful? Give feedback.
-
and Tremolo lacks the redirect response(status code 302,301,307) |
Beta Was this translation helpful? Give feedback.
Absolutely, Tremolo is a hybrid between ASGI server and web framework.
If you need something simple to work, Tremolo can work just like Flask but without the need for Gunicorn for production. You only need this 1 library.
Start with the basics. if you want to know.