Using CloudFlare Workers for Reverse Proxy

Warning
This article was last updated on 2023-01-15, the content may be out of date.

Introduction

In mainland China, Gravatar’s avatar service has always been unstable and unavailable. In addition to using some public mirror sites, we can also set up our own reverse proxy. However, if you want to set up your own reverse proxy, you need a server, which may incur additional costs. More importantly, a typical person’s server can only be located in one data center, resulting in large speed differences between regions, unlike Gravatar which has a global CDN network.

I hope that users worldwide can enjoy fast loading speeds. At the very least, the proxies used by users may also be distributed globally, right?

/en/gravatar-cloudflare-workers/network-map.svg
CloudFlare Network Map

CloudFlare Workers can directly process requests in their nearby data centers, which is much faster than using a random server.

Pricing

So, what is the pricing for Workers?

Free Plan Paid Plan - Bundled Paid Plan - Unbound
Requests 100,000 / day 10 million / month, +$0.50/million 1 million / month, + $0.15/million
Duration 10ms CPU time / invocation 50 ms CPU time / invocation 400,000 GB-s, + $12.50/million GB-s

The free plan is generally sufficient for most use cases.

You get 100,000 free requests per day, which is basically inexhaustible. The 10ms CPU time per invocation is also adequate, as our code likely only takes around 1ms to execute.

Even if you do need to pay, since we don’t require Workers KV, Queues, Durable Objects, or other products, and only need the number of requests, the Paid Plan - Unbound tier applies. 1 million requests cost a mere $0.15, equivalent to about one Chinese yuan, which is incredibly cheap.

Cost Calculation

Assuming each image is around 30KB, 1 million images would consume approximately 28.6GB of traffic. Considering that VPS providers may calculate traffic in both directions, it would be about 57.2GB.

The price of 57GB per yuan is considered average in the VPS market, not particularly cheap, especially when compared to unlimited traffic plans or Russian VPS offerings. However, when taking into account the quality of the network and the global distribution of data centers, CloudFlare’s offering is unbeatable.

CloudFlare’s speed is not something that cheap VPS plans can match. If you were to use a premium network like CN2, the price would definitely be much higher.

Workers JavaScript

The usage is very straightforward, essentially just JavaScript.

Let’s construct a simple example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
addEventListener(
    "fetch", event => {
        let url = new URL(event.request.url);
        url.hostname = "www.gravatar.com";
        url.protocol = "https";
        let request = new Request(url, event.request);
        event.respondWith(
            fetch(request)
        )
    }
)

In essence, the logic is to return the requested URL received via HTTPS, but change the hostname sent at the time of the request to www.gravatar.com.

Deployment

The deployment process is also very simple. Create a new Service in the CloudFlare Workers dashboard.

Copy the above code into it and click Deploy.

Custom Domains

By default, you will be assigned a workers.dev subdomain, which is perfectly fine to use. However, I prefer to set up my own custom domain.

Go to the Service settings, then to Triggers, click Add Custom Domains, and enter your desired domain name.

For example, if I choose gravatar.jamesflare.com, I would enter gravatar.jamesflare.com.

Testing

Let’s test it out and see if it works. Here, I’ll use my avatar URL for testing: /avatar/75cea16f157b9c5da5435379ab6cf294?s=32&d=.

Constructing the URL:

https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&d= https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&d=

As you can see, it works perfectly.

Usage in Hugo

This part is somewhat derivative. The process can be quite convoluted and may vary between different themes, so I want to focus on the thought process rather than providing a direct solution, as it may not be universally applicable.

I am using the FixIt theme, which is roughly equivalent to LoveIt.

Locating the Template File

After some searching, I found that the template responsible for rendering the author’s avatar in articles is located at /FixIt/layouts/partials/single/post-author.html.

The code is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{{- $params := .Scratch.Get "params" -}}

{{- $author := .Site.Author | merge (dict "name" "Anonymous" "link" (echoParam $params "authorlink") "email" (echoParam $params "authoremail")) -}}
{{- $avatar := .Site.Params.home.profile.avatarURL -}}
{{- if isset $params "author" | and (ne $params.author .Site.Author.name) -}}
  {{- $author = dict "name" $params.author | merge $author -}}
  {{- $author = dict "link" (echoParam $params "authorlink") | merge $author -}}
  {{- $author = dict "email" (echoParam $params "authoremail") | merge $author -}}
  {{- $avatar = "" -}}
{{- end -}}
{{- if (not $avatar | or $params.gravatarForce) | and $author.email -}}
  {{- $gravatar := .Site.Params.gravatar -}}
  {{- with $gravatar -}}
    {{- $avatar = printf "https://%v/avatar/%v?s=32&d=%v"
      (path.Clean .Host | default "www.gravatar.com")
      (md5 $author.email)
      (.Style | default "")
    -}}
  {{- end -}}
{{- end -}}
<span class="post-author">
  {{- $content := $author.name -}}
  {{- $icon := dict "Class" "fa-solid fa-user-circle" -}}
  {{- if $avatar -}}
    {{- $content = printf "%v&nbsp;%v" (dict "Src" $avatar "Class" "avatar" "Alt" $author.name | partial "plugin/image.html") $author.name -}}
    {{- $icon = "" -}}
  {{- end -}}
  {{- if $author.link -}}
    {{- $options := dict "Class" "author" "Destination" $author.link "Title" (T "single.author") "Rel" "author" "Icon" $icon "Content" $content -}}
    {{- partial "plugin/link.html" $options -}}
  {{- else -}}
    <span class="author">
      {{- with $icon -}}
        {{ . | partial "plugin/icon.html" }}
      {{ end -}}
      {{- $content | safeHTML -}}
    </span>
  {{- end -}}
</span>
{{- /* EOF */ -}}

Identifying the Relevant Code Section

The following code snippet is responsible for handling the avatar:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{{- if (not $avatar | or $params.gravatarForce) | and $author.email -}}
  {{- $gravatar := .Site.Params.gravatar -}}
  {{- with $gravatar -}}
    {{- $avatar = printf "https://%v/avatar/%v?s=32&d=%v"
      (path.Clean .Host | default "www.gravatar.com")
      (md5 $author.email)
      (.Style | default "")
    -}}
  {{- end -}}
{{- end -}}

It checks the value of the Host item under the gravatar sub-item of the params section in the configuration file.

If the Host item is empty, it defaults to www.gravatar.com.

There are two possible approaches: modifying the HTML template itself or modifying the value in the configuration file.

Configuring the .toml File

I opted for the second approach.

My configuration file is in the .toml format, so I’ll construct it as follows:

1
2
3
[params]
  [params.gravatar]
    host = "gravatar.jamesflare.com"

Previewing in the Browser

Regenerate the site. Here, I only need to preview it:

1
hugo server -D -e production --disableFastRender

Open the browser and navigate to http://localhost:1313/. Check the relevant part of the HTML source code:

1
2
data-src="https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&amp;d="
data-srcset="https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&amp;d=, https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&amp;d= 1.5x, https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&amp;d= 2x"

As you can see, the change has taken effect, which can also be verified using the Sources tab in the browser’s developer tools.

Side Note

As a side note, it turns out that the FixIt theme’s configuration file already included this option, and I was the clown for not noticing it earlier, despite searching extensively online. Here’s the relevant section:

1
2
3
4
5
[params]
  [params.gravatar]
    # Gravatar host, default: "www.gravatar.com"
    host = "www.gravatar.com" # ["cn.gravatar.com", "gravatar.loli.net", ...]
    style = "" # ["", "mp", "identicon", "monsterid", "wavatar", "retro", "blank", "robohash"]

Related Content

0%