A Step Further, Short Domains Configurable Without Servers
Introduction:
020 Short Link, a simple and free short link platform implemented using Cloudflare Worker
Deployment Method:
Step One: Create a Namespace
Go to the Workers & Pages
section under the KV
project
Create a namespace by clicking Create a namespace
Remember the name of this space you've created, you'll need it later
Step Two: Create a Worker
Go to the Workers & Pages
section under the Overview
project
Click Create application
Click Create Worker
Click deploy
Step Three: Configure Worker
Click Configure Worker
Go to the Settings
section under the Variables
project
Bind KV Namespace
Enter LINKS
for Variable name
, and fill in the namespace name you just created for KV namespace
Modify the domain in Triggers
Like this:
Click Quick edit
at the top of the page
Enter the following content
// project name, which determines which project the html is fetched from.
const github_repo = typeof(GITHUB_REPO)!="undefined" ? GITHUB_REPO
: 'AoEiuV020/Url-Shorten-Worker'
// Project version, cdn will have a cache, so you need to specify the version when there is an update.
const github_version = typeof(GITHUB_VERSION)!="undefined" ? GITHUB_VERSION
: '@main'
// password, ignoring whitelist and timeout settings if the password is correct, and supporting customized short links.
const password = typeof(PASSWORD)!="undefined" ? PASSWORD
: 'AoEiuV020 yes'
// Short chain timeout, in milliseconds, integer multiplication supported, 0 means no timeout set.
const shorten_timeout = typeof(SHORTEN_TIMEOUT)!="undefined" ? SHORTEN_TIMEOUT.split("*").reduce((a,b)=>parseInt(a)*parseInt(b),1)
: (1000 * 60 * 10)
// The length of the default short chain key, which is automatically extended when duplicates are encountered.
const default_len = typeof(DEFAULT_LEN)!="undefined" ? parseInt(DEFAULT_LEN)
: 6
// is true to enable demo, otherwise no password and non-whitelisted requests are not entertained, yes to allow visitors to try and expire after timeout.
const demo_mode = typeof(DEMO_MODE)!="undefined" ? DEMO_MODE === 'true'
: true
// for true to automatically delete timed out demo short link records, otherwise just mark them as expired so that the history can be queried in the backend.
const remove_completely = typeof(REMOVE_COMPLETELY)!="undefined" ? REMOVE_COMPLETELY === 'true'
: true
// Whitelisted domains ignore timeouts, json array format, just write the top level domain, automatically pass the top level domain and all second level domains
const white_list = JSON.parse(typeof(WHITE_LIST)!="undefined" ? WHITE_LIST
: `[
"aoeiuv020.com",
"aoeiuv020.cn",
"aoeiuv020.cc",
"020.name"
]`)
//Displaying this no-abuse notice on a web page when demo mode is on doesn't need to make it clear when it expires.
const demo_notice = typeof(DEMO_NOTICE)!="undefined" ? DEMO_NOTICE
: `Note: In order to prevent the example service from being abused, all links generated by the demo site may expire at any time, if you need to use it for a long time, please build it by yourself.`
//console.log(`${github_repo}, ${github_version}, ${password}, ${shorten_timeout}, ${demo_mode}, ${white_list}, ${demo_notice}`)
const html404 = `<!DOCTYPE html>
<body>
<h1>404 Not Found.</h1>
<p>The url you visit is not found.</p>
</body>`
async function randomString(len) {
let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****Confusing characters are removed by default oOLl,9gq,Vv,Uu,I1****/
let maxPos = $chars.length;
let result = '';
for (i = 0; i < len; i++) {
result += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return result;
}
async function checkURL(url){
let str=url;
let Expression=/^http(s)?:\/\/(.*@)?([\w-]+\.)*[\w-]+([_\-.,~!*:#()\w\/?%&=]*)?$/;
let objExp=new RegExp(Expression);
if(objExp.test(str)==true){
if (str[0] == 'h')
return true;
else
return false;
}else{
return false;
}
}
// Check if the domain name is in the whitelist, the parameter contains only the domain name part
async function checkWhite(host){
return white_list.some((h) => host == h || host.endsWith('.'+h))
}
async function md5(message) {
const msgUint8 = new TextEncoder().encode(message) // encode as (utf-8) Uint8Array
const hashBuffer = await crypto.subtle.digest('MD5', msgUint8) // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string
return hashHex
}
async function checkHash(url, hash) {
if (!hash) {
return false
}
return (await md5(url+password)) == hash
}
async function save_url(url, key, admin, len) {
len = len || default_len;
// If the password is correct and the key is specified, the old value is overwritten
const override = admin && key
if (!override) {
// Ignore the specified key if the password is incorrect
key = await randomString(len)
}
const is_exists = await load_url(key)
console.log("key exists " + key + " " + is_exists)
if (override || !is_exists) {
var mode = 3
if (admin) {
mode = 0
}
let value = `${mode};${Date.now()};${url}`
if (remove_completely && mode != 0 && !await checkWhite(new URL(url).host)) {
// Use expirationTtl to implement automatic deletion of expired records, below 60 seconds will report an error.
let ttl = Math.max(60, shorten_timeout / 1000)
console.log("key auto remove: " + key + ", " + ttl + "s")
return await LINKS.put(key, value, {expirationTtl: ttl}),key
} else {
return await LINKS.put(key, value),key
}
} else {
return await save_url(url, key, admin, len + 1)
}
}
async function load_url(key) {
const value = await LINKS.get(key)
if (!value) {
return null
}
const list = value.split(';')
console.log("value split " + list)
var url
if (list.length == 1) {
// The old data jumped forward normally for the time being
url = list[0]
} else {
url = list[2]
const mode = parseInt(list[0])
const create_time = parseInt(list[1])
if (mode != 0 && shorten_timeout > 0
&& Date.now() - create_time > shorten_timeout) {
const host = new URL(url).host
if (await checkWhite(host)) {
console.log('white list')
} else {
// Timeouts and failures to find are handled in the same way
console.log("shorten timeout")
return null
}
}
}
return url
}
async function handleRequest(request) {
console.log(request)
if (request.method === "POST") {
let req=await request.json()
console.log("url " + req["url"])
let admin = await checkHash(req["url"], req["hash"])
console.log("admin " + admin)
if(!await checkURL(req["url"]) || (!admin && !demo_mode && !await checkWhite(new URL(req["url"]).host))){
// In non-demo mode, non-whitelisted addresses are treated as if they were not legitimate
return new Response(`{"status":500,"key":": Error: Url illegal."}`, {
headers: {
"content-type": "text/html;charset=UTF-8",
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Methods": "POST",
},
})}
let stat,random_key=await save_url(req["url"], req["key"], admin)
console.log("stat " + stat)
if (typeof(stat) == "undefined"){
return new Response(`{"status":200,"key":"/`+random_key+`"}`, {
headers: {
"content-type": "text/html;charset=UTF-8",
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Methods": "POST",
},
})
}else{
return new Response(`{"status":200,"key":": Error:Reach the KV write limitation."}`, {
headers: {
"content-type": "text/html;charset=UTF-8",
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Methods": "POST",
},
})}
}else if(request.method === "OPTIONS"){
return new Response(``, {
headers: {
"content-type": "text/html;charset=UTF-8",
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Methods": "POST",
},
})
}
const requestURL = new URL(request.url)
const path = requestURL.pathname.split("/")[1]
console.log(path)
if(!path){
const html= await fetch(`https://cdn.jsdelivr.net/gh/${github_repo}${github_version}/index.html`)
const text = (await html.text())
.replaceAll("###GITHUB_REPO###", github_repo)
.replaceAll("###GITHUB_VERSION###", github_version)
.replaceAll("###DEMO_NOTICE###", demo_notice)
return new Response(text, {
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
}
const url = await load_url(path)
if (!url) {
// 404, not found or timeout
console.log('not found')
return new Response(html404, {
headers: {
"content-type": "text/html;charset=UTF-8",
},
status: 404
})
}
return Response.redirect(url, 302)
}
addEventListener("fetch", async event => {
event.respondWith(handleRequest(event.request))
})
You can modify the following environment variables, where the Key is the corresponding uppercase:
- Adjust Timeout Settings
Short links generated in demonstration mode are inaccessible after timeout, and timeout settings are invalid if the whitelist or password is correct. Modify the variable shorten_timeout at the beginning of the script, in milliseconds, 0 means no timeout setting.
- Adjust Whitelist
Domain names in the whitelist have short links that ignore timeouts. Modify the variable white_list at the beginning of the script. It's a JSON array, write the top-level domain, and automatically pass the top-level domain and all second-level domains.
- Turn Off Demonstration Mode
Only when the demonstration mode is enabled are visitors allowed to add non-whitelist addresses without a password. The short link will expire after a timeout. Modify the variable demo_mode at the beginning of the script, true to enable demonstration, false to deny requests without a password and non-whitelisted.
- Automatically Delete Demonstration Records
Whether to automatically delete the expired short link records in demonstration mode. Modify the variable remove_completely at the beginning of the script, true to automatically delete expired demonstration short link records, otherwise just mark them as expired to query the history in the backend.
- Change Password
There's a hidden input box on the web page to enter a password. If the password is correct, it ignores the whitelist and timeout settings and supports custom short links. Modify the variable password at the beginning of the script. This private information is recommended to be configured directly in the environment variables.
- Modify Short Link Length
Short link length is the randomly generated key, which is the length of the short link's path part. If the length is not enough, it may cause duplication, and it will be automatically extended if duplication occurs. Modify the variable default_len at the beginning of the script.
Related Links:
GitHub address: https://github.com/AoEiuV020/Url-Shorten-Worker
Demo: https://020.name
Original link:http://enblog.fuyiran.link/Technology/29.html
Copyright: All posts on this blog, unless otherwise stated, are published using theCC BY-NC-SA 4.0 license agreement. Please indicate the source for reprinting Fu Speaking (enblog.fuyiran.link)