- Şu tarihte
Cloudflare Workers ile URL Kısaltma Servisi
Cloudflare ile tamamiyle serverless bir şekilde URL kısaltma servisi yapalım.
Öncelikle bazı teorik bilgileri vermem gerekiyor gibi hissediyorum, başlayalım:
Serverless nedir?
Tıpkı bulut servislerinde olduğu gibi başkasının bilgisayarını kullandığımız bir tabir. Şöyle ki, gezdiğiniz ve kullandığınız web-mobil uygulamalar aslında daima açık bir makinede çalışıyor (yani bir başkasının bilgisayarında) böylelikle hem kendi bilgisayarlarımızı kapatabiliyoruz hem de ufak uygulamalar için dahi yeni donanımlar alma ihtiyacımız olmuyor. Bu noktada bulut sağlayıcıları bizlere makinelerini kiralama imkanı sunuyor. Bunlara çoğu yerde virtual machines, virtual dedicated servers, virtual private servers gibi adlar takılmış olabilir, bunlar birbirine çok benzer manalarda kullanılmaktadır, işin özü anlattığım gibi kiralamadan ibaret.
Bu noktada serverless dediğimiz şey ise bu kiralamanın daha da sanallaşması manasına gelmektedir. Öyle ki, bahsi geçen makinelerin kurulum, bakım gibi işlerini dahi serverless sağlayıcıları üstlenmektedir. Burada bize kalan yalnızca bu platformların kabul ettiği dillerde ve runtimelarda yazılımlar geliştirmek ve limitlerine/sınırlarına saygı duymaktır.
Cloudflare nedir?
Öncelikle Cloudflare bir şirket. Dünya çapında DNS (Domain Name Server), WAF (Web Application Firewall), CDN (Content Delivery Network) ve bir çok alanda hizmet vermekte. Kendileri bir problem yaşadığında bunu tüm dünya hissetmekte çünkü üzerinden ciddi manada trafik geçmektedir. Bunun yanı sıra geliştiricilere ciddi manada servislerini ücretsiz bir şekilde sunmaktadır. Bu yazının konusu olan serverless hizmetlerini de belirli limitlere kadar ücretsiz olarak kullandırmaktadır.
URL Kısaltma Servisi
Bit.ly benzeri sadece yeni kayıt oluşturma ve bu kayıttan kısaltılan adrese yönlenecek bir servis yapalım, ne lazım?:
- API
- Veritabanı
- Platform
Bu noktada ihtiyaçlarımızın hepsine Cloudflare hem de ücretsiz bir şekilde cevap verebilmektedir.
API için Pages/Workers
, Veritabanı için D1
(SQLite). Şu anlık direkt olarak Workers servisini kullanamıyoruz çünkü D1 için Pages uygulamalarına izin var. Burada bir uyarı da D1 servisi şu tarihte Alpha
[^1] sürümünde.
Diagram
Yapacağımız şey böyle bir akıştan geçecek:
Kullanıcı Cloudflare'in vereceği .pages.dev
uzantılı adresimize veya bizim vereceğimiz bir domaine istek atacak ve cevap olarak bir şeyler alacak. Burada Cloudflare'in de
desteklediği JavaScript ile NodeJS
üzerinden wrangler
CLI aracını kullanarak geliştirme yapacağız. wrangler
Cloudflare'in resmi aracı, NodeJS'in yüklü ve bir IDE'nin (Text editörü de olur)
yüklü olması gerekiyor. Teoriye başlayalım.
wrangler'i indirmek için (NodeJS 16.13.0
ve sonrası bir sürüme ihtiyacınız var):
npm install wrangler
Oluşturacağımız projede hono
adlı bir framework kullanacağız yine bir Cloudflare çalışanı tarafından açık kaynak olarak geliştirilen edge
uygulamalar için uygun bir seçenek.
Halihazırda bir Cloudflare hesabınız olduğunu varsayarak wrangler login
komutu ile Cloudflare ile wrangler'i yetkilendirelim. Bu adımı tamamladıktan sonra npm create hono@latest my-app
komutu ile my-app
isimli yeni bir hono projesi oluşturabilirsiniz. Tabi ki my-app
ismini değiştirebilirsiniz. Komutu uyguladığınızda aşağıdaki gibi bir soru soracaktır, burada cloudflare-pages
'i
seçmelisiniz:
Bu adımdan sonra hazır bir projemiz olacak, proje dizinine girerek editörümüzü açalım.
Proje yapısı:
.
├── README.md
├── functions
│ └── api
│ └── [[route]].ts
├── package-lock.json
├── package.json
├── pages
│ └── index.html
└── tsconfig.json
Buna benzer bir şekilde olacak, burada görebileceğiniz üzere pages
ve functions
dizinleri bulunmakta. Bizim bir önyüzümüz olmayacağından pages ile işimiz olmayacak lakin silmemize de gerek yok.
functions
dizini altında api ve onun altında bir TypeScript
dosyası var. Bu projede TypeScript kullanılmakta, TypeScript ise JavaScript'in tip belirtimlerini şart koşan lehçesi diyebiliriz. Günün
sonunda TypeScript kodu JavaScript koduna dönüştürülmektedir.
URL kısaltma servisi için iki tane şeye ihtiyacımız var, biri kısaltılacak URL'in kaydedilmesi ve bir yerde tutulması, diğeri ise bu URL'e yönlenecek bir link oluşturmak.
Yani google.com
adresini alıp bir yere yazacağız ve karşılığında bir değer üreteceğiz bu değerle de yine bize gelindiğinde google.com
'a yönlendirme yapacağız. Öyle ise iki servis yazmalıyız.
Servislerden biri POST isteği olmalı ve bu servis ilgili adresi alıp veritabanına yazmalı, bunu yaparken de bir değer üretmeli ve bu URL'le kaydetmeli.
google.com
'a karşılık askd2hiw
gibi bir değer üretmeliyiz ki uzun URL'imizi kısaltmış olalım. google.com
örneği burada kısa gelmiş olabilir ama aşağıdaki gibi bir URL'le ne yapacağız?
https://www.google.com.tr/maps/place/Sazova+Bilim+Park%C4%B1/@39.7663693,30.4733069,17z/data=!3m1!4b1!4m6!3m5!1s0x14cc16f6ad649637:0x3fdb331065245db6!8m2!3d39.7663693!4d30.4758818!16s%2Fg%2F11c542sqh5?entry=ttu
Google'ın kendi URL kısaltma servisi ile bunu https://goo.gl/maps/Evbd5q66e6uSQg4v5
adresine çevirmiş. Bizde buna benzer bir şey yapacağız.
Diğer servisimiz ise bu kısalttığımız adrese yönlenecek olan servis olmalı. Tıpkı Google'ın kısalttığı gibi örnek olarak Evbd5q66e6uSQg4v5
ifadesi ile gelinince Google Maps'teki ilgili lokasyonun açılması
gibi bir yönlendirme yapacağız. Bu noktada da Evbd5q66e6uSQg4v5
ifadesini veritabanında bulmalı ve bu ifadeye karşılık gelen URL'i bulup HTTP 302 ile yönlendirme yapıp kullanıcıya cevap dönmeliyiz.
Sözde kodu (pseudocode) yazmış olduk aslında. Yukarıdaki diagrama bakıldığında Worker servisimiz veritabanı ile konuşacak ve hem yazıp hem okuma yapacak.
Servisleri kısaca tanıtmak gerekirse, Workers
servisi Cloudflare'in serverless fonksiyon hosting (FaaS - Function as a Service) hizmetidir. D1
ise yine Cloudflare'in SQLite
adlı açık kaynak SQL veritabanıdır. Pages
ise önyüz hosting servisidir. Bu yapı ile sadece kod yazıp servisimizi canlıya alabiliyoruz herhangi bir sunucu kurulumu ve kontrolüne gerek yok. Sadece bir kaç ayar yapmamız gerekiyor
Cloudflare arayüzünden o kadar.
Öncelikle veritabanımızı oluşturmamız gerekiyor, bunun için D1 servisine girmeliyiz. Workers & Pages
menüsü altında D1 seçeneği üzerinden ulaşabilirsiniz. D1'e girip Create database
seçeneğinden Dashboard
seçeneği üzerinden aşağıdaki gibi bir veritabanı oluşturabilirsiniz:
Evet bir veritabanımız oldu. Hemen içine girip yine elle bir tablo oluşturalım, tablomuzun ismi urls
olabilir. Her tablonun bir PK'i (Primary Key) olmak zorundadır. Genelde id
gibi bir integer
sütunu PK olarak seçilir. id, url, short
adında üç tane sütun oluşturalım. url
tahmin edeceğiniz gibi uzun hali, short
ise kısaltılmış versiyonu. Tablomuzda tamam ise yazacağımız uygulamamızın bu veritabanına erişebilmesi için yetkilendirme yapmamız gerekmekte. Lakin bundan önce lokalimizdeki uygulamamızı Cloudflare'de oluşturmamız gerekli, bunun için proje dizinimizde:
npm run deploy
Komutunu uygulayalım, Cloudflare'de uygulamamız oluşturulacak ve wrangler
CLI'ı oluşturduğu projenin halka açık adresini verecek. Komutun çıktısı buna benzer bir şey olmalı:
Cloudflare arayüzünde de projemizin oluştuğunu görebilirsiniz. Bu uygulamanın ayarlarına girelim Settings başlığı altından Functions
ayarlarına girelim. D1 veritabanımızı yetkilendirmek için
biraz aşağıda D1 database bindings
kısmından bir değişken adıyla veritabanımızı eşleştirelim. Ben DB
ifadesini uygun gördüm çünkü kodda da bunu kullanacağız ve anlaşılırlık önemli.
Artık uygulamamız D1 veritabanı ile konuşabilecek. Peki veritabanı bağlantısını nasıl kuracağız? Genellikle veritabanları ile uygulamalar TCP üzerinden birbirleri ile konuşurlar fakat serverless platformlarda uygulamalarımız devamlı ayakta olmayabileceği için TCP bağlantılarının devamlılığı ve sağlığı konusunda problemler mevcuttur, Cloudflare'in bu konuda bir çalışması var fakat ne kadar efektif olduğu yönünde derinlemesine bir araştırma şart. Bundan dolayı serverless veritabanları HTTP driverları üzerinden kullanılırlar. Bir çoğunun TCP ile de bağlanabilirlikleri mevcuttur fakat D1 için bu söz konusu değil. (Yine CF blogundan şöyle bir yazı mevcut)
Gelelim kodumuza, [[route]].ts
dosyasını açıp baktığımızda hazırda bir hello endpointi görüyoruz. c
objesi yani context içinde hem Dashboard'da oluşturduğumuz variable'ları hem de isteğin contextine ulaşabiliriz. Endpointin sonunda dikkat ederseniz c.json
return edilmiş. Burada client'a cevabı json şeklinde döneceğimizi belirtiyoruz. Şimdi hello'yu silip kendi endpointlerimizi yazalım.
URL Kısaltan Servis
Bunun için POST /create
şeklinde bir endpoint yapalım.
Kısaltacağımız URL'i client bize JSON olarak body'den iletse ve bizde bunu valide ederek veritabanımıza yazıp kısa versiyonunu da client'a dönsek. Evet bunu yapalım:
app.post('/create', async (c) => {
const body = await c.req.json() // Context'in içinden request'in json body'sini alalım
if (!body) {
c.status(400)
return c.json({
code: 400,
message: 'Missing body',
})
}
const { url } = body // JSON body içinde gelen url değerini alalım
if (!url || url === '') {
c.status(400)
return c.json({
code: 400,
message: 'Missing body',
})
}
// Bir kontrol: Daha önce aynı URL'i kısaltmış olabilir miyiz?
const find = await c.env.DB.prepare(
`
select id from urls where url = '${url}';
`
).raw()
const isExist = find[0]
if (isExist) {
c.status(409)
return c.json({
status: 409,
message: 'Already created',
})
}
// URL'in kısa versiyonu için nanoid kullanıyoruz, siz başka bir şey tercih edebilirsiniz
const nanoid = customAlphabet('1234567890abcdef', 6) // 6 karakterlik random bir değer üretelim
const short = nanoid()
// URL'imizi veritabanına yazalım
await c.env.DB.prepare(
`
insert into urls (url, short) values ('${url}', '${short}');
`
).raw()
return c.json({
url,
short, // Kullanıcıya kısa versiyonu dönelim
})
})
Yukarıda görüldüğü üzere bir POST endpointi yazdık, bazı kontroller yaptık ve bu kontroller sonucuna göre kullanıcıya cevap döndük. Şayet isterseniz try-catch
bloğu içinde yazıp
exception kontrolü de yapabilirsiniz. Bu yazıda kodu olabildiğince basit tutuyorum. Dikkat ederseniz veritabanı işlemleri context
içindeki env
objesindeki DB'den yapılıyor, bu DB objesi de
bizim Cloudflare Dashboard üzerinde binding ettiğimiz isim. raw
methodu yaptığımız sorgu ile ilgili başka metadata'larda döndüğü için dizinin 0. indexli elemanını aldık.
URL Yönlendiren Servis
Gelelim oluşturduğumuz kısa URL'i asıl adrese yönlendirecek servise. Bunun da URL üzerinden gelinmesi gerekiyor. Yani blabla.com/:id şeklinde gelinmesi gerekiyor ki kullanıcı rahatlıkla asıl adrese gidebilsin.
app.get(':id', async (c) => {
const short = c.req.param('id') // Context içinden parametredeki değeri alıyoruz
if (!short) {
c.status(400)
return c.json({
code: 400,
message: 'Missing ID parameter',
})
}
const results = await c.env.DB.prepare(
`
select url from urls where short = '${short}';
`
).raw()
const url = results[0]
if (!url) {
// Eğer veritabanında kayıt yoksa kullanıcıya 404 dönelim
c.status(404)
return c.json({
status: 404,
message: 'Not found',
})
}
return c.redirect(url, 302) // HTTP 302 ile redirect ediyoruz ve son..
})
Bu endpointi de yazdıktan sonra deploy komutu ile Cloudflare'e yeni sürümü gönderelim.
Şimdi uygulamamıza ilk isteğimizi atalım, https://cloudflare.com
için bir kısaltma yapalım, örnek cURL isteğini aşağıya bırakıyorum:
curl --request POST \
--url https://your-app.pages.dev/api/create \
--header 'Content-Type: application/json' \
--data '{
"url": "https://cloudflare.com"
}'
Cevap olarak ise şöyle bir şey almalısınız:
{
"url": "https://cloudflare.com",
"short": "0eb13f"
}
short değeri bizim kısaltılmış adresimiz olacak şimdi de kısalttığımız adrese erişelim:
curl --request GET \
--url https://your-app.pages.dev/api/0eb13f
Adrese gittiğimizde Cloudflare anasayfasını görüyor olmalıyız. Tabi ki bu çokta kısa bir link olmadı fakat mantığı aslında bu kadar basit. Böylelikle herhangi bir sunucu masrafı olmadan
bir uygulamayı canlıya almış olduk, dilerseniz Custom Domain
kısmından kendi alan adınız üzerinden bu uygulamayı servis edebilirsiniz. Ücretsiz özelliklerin sınırlı olduğunu unutmayın.
Sınırları ve platformun kısıtlarını görmek için Cloudflare'in dokümantasyonunu inceleyebilirsiniz.
[^1]: Yazılım yaşam döngüsünde bir adım. Bu adım yazılımın henüz test ve geliştirme aşamasında olduğunu ifade etmektedir. Kısaca bu yazılımı canlı ortamlarda kullanmayınız demektedir.