Skip to content
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

Open
9am opened this issue Sep 20, 2023 · 1 comment
Open

Dynamic Hits Counter with Deno #15

9am opened this issue Sep 20, 2023 · 1 comment
Assignees
Labels
api API deno Deno server Server

Comments

@9am
Copy link
Owner

9am commented Sep 20, 2023

How to build a dynamic image API that works as a hits counter powered by Deno Deploy and Deno KV
metronome

hits

@9am
Copy link
Owner Author

9am commented Sep 20, 2023

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.

deno-logo

The hits: n in the upper comment is what I come up with, check out the project page.


𐄡

The Idea

For the service, from the user side, it's pretty straightforward:

Embed a <img src="https://hits/api" /> into the web pages, and it'll show the hits number as a <img/> will do.

Wait... why do we need a <img> as a counter?

There are some situations in which we want to count and show the hits, but we can not include any javascript to send a request, like in Github README, issue, etc... And I'm using Github issue to serve my blog, well, maybe it's just me, but it's nice way to show me the number of reads.

To achieve that, we'll expose an HTTP API that returns an image with the hits in it. And also use the GET request as an increasing signal of the counter. The counter needs to be recorded under different keys of referer, which are the web pages that embed the <img/> so that we count hits for different resources.

idea


𐄡

The Troubles

1. Can't get the right Referer

We want to save the hits number in the key of different Referer so that we can give the right number when the image pops out.

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 a meta like this: <meta name="referrer" content="origin" />. The server will get the referer as https://a.com. Or even worse, with a setting of Referrer-Policy: no-referrer, there'll be no referer value at all.

The solution:

  1. For places we can embed the <img> like this, we add a referrerpolicy to override the default setting. no-referrer-when-downgrade means the referer will be ${origin}${path}${query} if the page protocol is not downgrade to HTTP.
    <img src="https://hits/api" referrerpolicy="no-referrer-when-downgrade" />
  2. 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
The 1 second in there works like a throttle 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:
  1. Keys are easy to understand.
  2. 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:
  1. A native sum mutation can be used to increase the counter.
  2. Take advantage of the native selector to filter and aggregate the result.
  3. 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.

hits

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.

Deno Deploy & KV Price


𐄡

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.

bitmap-font


𐄡

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 🕘

@9am 9am self-assigned this Sep 20, 2023
@9am 9am added deno Deno api API server Server labels Sep 20, 2023
@9am 9am changed the title Dynamic hits counter with Deno Dynamic Hits Counter with Deno Sep 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api API deno Deno server Server
Projects
None yet
Development

No branches or pull requests

1 participant