💾 Archived View for mozz.us › journal › 2020-06-18.gmi captured on 2023-05-24 at 17:44:48. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2020-09-24)
-=-=-=-=-=-=-
Published 2020-06-18
This is a followup to my previous article about adding favicons to gemini
[2020-06-03] Adding Emoji Favicons to Gemini
Since then, some folks have tried out the proposal and sent me feedback that I think is worth sharing publicly.
This is by far the most common criticism that I've seen so far. Unicode has over a million characters to chose from. Why restrict favicons to only emojis? Why shouldn't other unicode symbols be allowed too?
I picked emojis simply because they're a well-defined list of character sequences, maintained by the Unicode Consortium. This makes it trivial for a client to implement an allow-list of valid favicons.
https://unicode.org/emoji/charts/full-emoji-list.html
I'm not against allowing monograms or other non-emoji symbols, in fact I would like to include them. But I can't figure out a good way to express this in a specification. I definitely don't want to accept arbitrary unicode sequences of any length. And I don't want to limit to a single unicode codepoint either (that would exclude diacritical marks and other joiners). I've seen folks mention a "glyph", but what exactly *is* a glyph? The best information that I can gather is that it appears to be font-specific and not well defined. What I really want to say is something like "a printable unicode sequence that occupies 1-2 horizontal blocks of space and 1 vertical block of space. Is there a term for that?
In other words... unicode confuses me and I'm open to better ideas.
Another point that was brought to my attention is that making the favicon request *before* the first request for content will double the round-trip time. This could add a noticeable delay if you're requesting content from a server somewhere on the other side of the world.
Of course, this in true. In the RFC, I added a couple of examples that showed making the requests serially (first request the the favicon, then request the content). I had my own web proxy's implementation in mind when I wrote this. Because portal.mozz.us generates a cooked HTML page, I wanted to wait for the favicon to download before I could render the page. But there's no reason that the two requests *need* to be made in a particular order. It's not *disallowed* by the RFC as long as the client doesn't preemptively load the favicon. With a GUI framework, a client might use an event loop to fire off both requests concurrently and then render the favicon separately from the rest of the content.
I will likely update the RFC to call this out and say something like... "In order to minimize the effects of latency, clients are encouraged to make the favicon request concurrently with the main gemini request".
I also updated my implementation on portal.mozz.us to spin off the favicon request in a separate thread. If the thread responds before the main content request, cool, I will include it in the response. Otherwise, I will return the page without the favicon, but still cache the favicon to use in any future responses. I think this is a good compromise and it wasn't as hard to write as I thought it would be.
So far, a handful of people have been kind enough to humor me and add a favicon.txt file to their server. Thanks!
gemini://acidic.website/favicon.txt
20 text/plain 🈴
gemini://alexschroeder.ch/favicon.txt
20 text/plain; charset=UTF-8 🐝
gemini://astrobotany.mozz.us/favicon.txt
20 text/plain 🌻
gemini://carcosa.net/favicon.txt
20 text/plain ♏
gemini://cosmic.voyage/favicon.txt
20 text/plain 🚀
(not displayed by portal.mozz.us because it's not an emoji)
20 text/plain ⑨
gemini://gemini.conman.org/favicon.txt
20 text/plain; charset=UTF-8 👽
gemini://makeworld.gq/favicon.txt
20 text/plain 🔗
20 text/plain 🐟
gemini://tilde.black/favicon.txt
20 text/plain 🕳
For anyone interested, here's the code that I use to fetch favicons in my HTTP proxy server. It's very poorly written and even more poorly documented.
import shelve import threading import emoji class Favicon: """ Usage: favicon = Favicon(host, port).load() """ # DOES_NOT_EXIST means that we tried to request the favicon but the server # either failed or returned an invalid response. Compared to "None" which # means that we don't know if the server has a favicon or not. DOES_NOT_EXIST = -1 database = "/tmp/favicon_cache" expiration = 60 * 60 # cache expiration, seconds lock = threading.Lock() def __init__(self, host, port): self.host = host self.port = port self.key = host if self.port: self.key += f":{port}" def load(self): value, is_expired = self._load_cache() if value is None or is_expired: thread = threading.Thread(target=self.refresh) thread.start() if value == self.DOES_NOT_EXIST: return None else: return value def refresh(self): """ Attempt to fetch the remote favicon and cache the result. Use a lock to prevent two threads from refreshing at the same time and making duplicate requests to the server. """ has_lock = self.lock.acquire(blocking=False) if has_lock: try: value = self._fetch_favicon() self._save_cache(value) finally: self.lock.release() def _fetch_favicon(self): if self.port: url = f"gemini://{self.host}:{self.port}/favicon.txt" else: url = f"gemini://{self.host}/favicon.txt" try: with gemini_request(self.host, self.port or 1965, url) as response: if response.status == '20' and response.meta.startswith("text/plain"): text = response.body.read().decode(response.charset) text = text.strip() if text in emoji.UNICODE_EMOJI: return text else: return self.DOES_NOT_EXIST except Exception: return self.DOES_NOT_EXIST def _load_cache(self): with shelve.open(self.database) as db: if self.key in db: ttl, value = db[self.key] if time.time() - ttl < self.expiration: return value, False else: return value, True return None, False def _save_cache(self, value): with shelve.open(self.database) as db: db[self.key] = (time.time(), value)