Way to add redirects
It'd be nice if there was an easy way to add redirects for Jetforce when in static file serving mode. There could be something like a redirects.jetforce file at the root, that isn't served but instead consulted for redirect info. Ideally it wouldn't require a server restart when adding a new redirect, so maybe it could reload that file every 5 minutes or something.
Another way to do this could be to have a special .redir file type, that just contains the new URL. So I can create a file called example.gmi.redir, with the content of the file being just the line: /path/to/redirect/file.gmi. And when someone requests example.gmi, Jetforce sees the redirect file, and returns an absolute URL redirect: gemini://myhost.com/path/to/redirect/file.gmi.
Do either of those options sound good to you?
Hey, thanks for the suggestions.
It sounds like you are going down a path of something like an Apache .htaccess file. I don't think there is anything fundamentally wrong with this pattern, but it does open the door to a lot of incidental complexity. How do you specify temporary vs. permanent redirects? What if you want to redirect based on a regular expression pattern? How about external URLs? Can you use the same "configuration" file to add authentication to individual directories? etc.
I suggest that a good way to add something like redirects would be to extend the built-in server with your own custom endpoints. From my point of view, I like the simplicity of not having to decide what features should or should not be allowed by configuration files. Adding virtual hosts is another example along the same lines. I have tried to develop the internal library to make this as straightforward as possible. Admittedly the documentation is not there yet.
#!/usr/local/env python3
"""
This example shows how you can extend the jetforce static directory server to
support advanced behavior like custom redirects and directory authentication.
"""
from jetforce import GeminiServer, StaticDirectoryApplication, Response, Status
app = StaticDirectoryApplication("/var/gemini")
# Example of registering a custom file extension
app.mimetypes.add_type("text/gemini", ".gemlog")
@app.route("/old/(?P<route>.*)")
def redirect_old_regex(request, route):
"""
Redirect any request that starts with "/old/..." to "/new/...".
"""
return Response(Status.REDIRECT_PERMANENT, f"/new/{route}")
@app.route("/custom-cgi")
def custom_cgi(request):
"""
Invoke a CGI script from anywhere in the filesystem.
"""
return app.run_cgi_script("/opt/custom-cgi.sh", request.environ)
@app.route("/auth/.*")
def authenticated(request):
"""
Require a TLS client certificate to access files in the /auth directory.
"""
if request.environ.get('TLS_CLIENT_HASH'):
return app.serve_static_file(request)
else:
return Response(Status.CLIENT_CERTIFICATE_REQUIRED, "Need certificate")
if __name__ == "__main__":
server = GeminiServer(app, host="127.0.0.1", hostname="localhost")
server.run()
I think the usecase of what I proposed is very simple and somewhat common, when people move files they will often want to provide redirects.
How do you specify temporary vs. permanent redirects? What if you want to redirect based on a regular expression pattern?
Questions like this seem clearly out of scope to me, at which point you should definitely be bringing in the Python library like you mention. But using the lib for just several single file redirects would be cumbersome and a lot of work I think. It makes sense in your example above, where there are specific dirs that need to be redirected, as well as a regex and some authentication. But what if there's just a few files?
#!/usr/local/env python3
"""
This example shows how you can extend the jetforce static directory server to
support advanced behavior like custom redirects and directory authentication.
"""
from jetforce import GeminiServer, StaticDirectoryApplication, Response, Status
app = StaticDirectoryApplication("/var/gemini")
# Example of registering a custom file extension
app.mimetypes.add_type("text/gemini", ".gemlog")
@app.route("/path/to/file.gmi)")
def redirect_file_1(request, route):
return Response(Status.REDIRECT_PERMANENT, "/path/to/newfile.gmi")
@app.route("/path/to/other/file.gmi)")
def redirect_file_2(request, route):
return Response(Status.REDIRECT_PERMANENT, "/path/to/other/newfile.gmi")
@app.route("/somedir/myfiles/test.gmi)")
def redirect_file_3(request, route):
return Response(Status.REDIRECT_PERMANENT, "/path/to/my/cool_file.gmi")
@app.route("/path/to/this_file.gmi)")
def redirect_file_4(request, route):
return Response(Status.REDIRECT_PERMANENT, "/path/to/newest_file.gmi")
# Continues, etc..
if __name__ == "__main__":
server = GeminiServer(app, host="127.0.0.1", hostname="localhost")
server.run()
This feels like a lot of extra lines just for redirection, which is why I brought this up. Especially if you're not a Python programmer, or a dev at all, this might be inaccessible. Obviously there are cleverer ways to do this, like with a dictionary, but again it's not very obvious or accessible.
How about external URLs?
That seems possible in both of the potential methods I described, by writing a URL with a scheme in it, like https://example.com/.
Can you use the same "configuration" file to add authentication to individual directories?
I think for either method, that would definitely be a separate thing, not part of this. I think there's a much lower usecase for that, at the moment anyway. In the future people may want to easily protect their files, and I think having a way to do that would be great, but for now I think demand is low.
Thanks for taking the time to think about this!
Solderpunk has sort of added this feature to Molly Brown, see his post about it: gemini://gemini.circumlunar.space/~solderpunk/cornedbeef/extensive-molly-brown-updates.gmi (Portal). There's also more documentation on the Molly Brown README.
I think the usecase of what I proposed is very simple and somewhat common, when people move files they will often want to provide redirects.
I'll take your word for it, but I've never come across a situation where I wanted to redirect only a single file instead of a directory or a regular expression.
This feels like a lot of extra lines just for redirection, which is why I brought this up. Especially if you're not a Python programmer, or a dev at all, this might be inaccessible. Obviously there are cleverer ways to do this, like with a dictionary, but again it's not very obvious or accessible.
I like the approach of adding code examples for common patterns to make it more obvious. I would also be open to adding helper methods to make specifying redirects more straightforward, e.g.
#!/usr/local/env python3
from jetforce import GeminiServer, StaticDirectoryApplication, Response, Status
temporary_redirects = {
"/path/to/file.gmi": "/path/to/newfile.gmi",
"/path/to/other/file.gmi": "/path/to/other/newfile.gmi"
}
app = StaticDirectoryApplication(
"/var/gemini".
temporary_redirects=temporary_redirects
)
...
For what it's worth, defining configuration through code is a fairly common pattern for web frameworks (Django, RoR). I consider jetforce to be closer is spirit to these libraries than out-of-the-box servers like apache/nginx. At the end of the day, yeah it will probably turn off some people who really don't want to touch code. It's not for everyone (although I will contend that python is simple enough that anyone could modify something like the above example). I would rather build something that I enjoy than try to appeal to the widest possible audience.
Solderpunk has sort of added this feature to Molly Brown, see his post about it: gemini://gemini.circumlunar.space/~solderpunk/cornedbeef/extensive-molly-brown-updates.gmi (Portal). There's also more documentation on the Molly Brown README.
That's awesome, the .molly files are exactly what I was thinking about 😄
Maybe it's not as common as I thought, lol. I understand your position on this, thanks for responding.