New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic Hits Counter with Deno #15
Comments
Deno has been there for a while, it supports pretty much everything a Node.js developer needs: testing, linting, formatting, TypeScript, and even JSX(without installing a ton of packages). It's backward compatible with Node.js and npm. So if you work heavily on Node.js, it is worth a try to switch to Deno. But what I love most is Deno Deploy. It's super easy to build a serverless application, and the deployment is lightning-fast. It's a perfect platform to try some ideas or an MVP. I've been invited to try Deno KV Beta in Deno Deploy recently (It's in open beta now), so I got this idea to build a generated image API that works as a hits counter. I'll not bother to show you how to set up the project and Deno Deploy, cuz it's pretty easy thanks to the thorough Documatation and the well-tuned UX. In this article, I'll talk about some trouble I encountered.
𐄡
The IdeaFor the service, from the user side, it's pretty straightforward:
Wait... why do we need a
|
key(referer) | value |
---|---|
https://a.com/x.html |
3 |
https://b.com |
4 |
https://c.com?query=search |
5 |
... | ... |
Lucky for us, the information can be found in HTTP headers
. Origin, path, and query string will be included. The problem is that which one of them are included is depended on the referrer policy. Every page can have its own referer policy
settings in different ways.
For example, if a page
https://a.com/x.html
has ameta
like this:<meta name="referrer" content="origin" />
. The server will get the referer ashttps://a.com
. Or even worse, with a setting ofReferrer-Policy: no-referrer
, there'll be no referer value at all.
The solution:
- For places we can embed the
<img>
like this, we add areferrerpolicy
to override the default setting.no-referrer-when-downgrade
means the referer will be${origin}${path}${query}
if the page protocol is notdowngrade
to HTTP.
<img src="https://hits/api" referrerpolicy="no-referrer-when-downgrade" />
- For markdown, we have no choice but to add an explicit param
referer
for the API.
![alt](https://hits/api?referer={value})
𐄡
2. CDN or Proxy server cache image request
For a normal page, the API works fine. But in the GitHub markdown, the counter just doesn't update. Because GitHub uses a camo
proxy server for the image embedded in the markdown content.
A
![](imglink)
turns into<img src="https://camo.githubusercontent.com/{imgid}" data-canonical-src="imglink" />
.
So the next time we open the page with our counter, the proxy server returns the cached image, and the counter failed to increase cuz no request was received at all.
The solution:
A Cache-Control
response header was my first thought, but adding a Cache-Control: no-cache
didn't work. I started to look for solutions from other projects that may encounter the same issue. Thanks to github-readme-stats, I finally took their way to bust the cache:
Cache-Control: max-age=1, s-maxage=1
The1
second in there works like athrottle
so that we can take advantage of the cache.
𐄡
3. The design of KEYs
I have plans to save more information about the hits, like date, geo, browser, device, etc. So I can think of 2 ways of organizing the data.
Option 1:
key(referer) | value |
---|---|
a.com |
{ total: 3, 2023-09-01: 3, 2023-09-02: 1, geo-cn: 4, geo-us: 1 ...} |
... | ... |
Pros:
- Keys are easy to understand.
- No need to query DB multiple times for each type of data.
Option 2:
key(referer) | key(total) | key(date) | key(geo) | value |
---|---|---|---|---|
a.com |
total |
- | - | 5 |
a.com |
- | 2023-09-01 |
- | 4 |
a.com |
- | 2023-09-02 |
- | 1 |
a.com |
- | - | us |
3 |
a.com |
- | - | cn |
2 |
... | ... | ... | ... | ... |
Pros:
- A native
sum
mutation can be used to increase the counter. - Take advantage of the native
selector
to filter and aggregate the result. - Only a small amount of selected data will be returned to the server side, no need to filter the result again. e.g. find hits for the last 7 days:
db.list({ prefix: [referer, 'date'], start: '2023-09-13' })
I took the 2nd solution for now until I become more experienced with KV DBs.
A trend for the last 7 days
𐄡
4. The access right control
There is a request limitation on Deno Deploy and Deno KV, and I'm not ready to provide the API as a public service. So to prevent unknown request or even attack, an ALLOW_REFERER
env was there to filter the input. This can not prevent a vicious attack, but enough for an MVP.
Maybe someday in the future, I'll make it a free service.
𐄡
5. An eye-catching renderer
Well, it's not a trouble for me. I've been exploring a bitmap font display recently. It's fun to apply it here. I'll explain the details in another article.
𐄡
Closing thoughts
For the last few months, the fear of uncertainty has overwhelmed me. New things keep coming into my life, some are good, some are not. It's hard to find time to explore new things and write. But strangely, I found the peace coming back the moment I started writing. I'm thankful for the things I love and the people who love me. At the end of the day, that's what keeps me going!
@9am 🕘
The text was updated successfully, but these errors were encountered: