Şu tarihte

Cloudflare Workers ile URL Kısaltma Servisi

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:

URL Kısaltma Servisi Mimari Diagramı

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:

Hono - Cloudflare Pages

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:

Cloudflare D1 Veritabanı Oluşturma

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ı:

Wrangler CLI ile Cloudflare Deploy

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.

Cloudflare Pages - D1 Yetkilendirme

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.