Varnish web sitelerini hızlandırmak için kullanılan bir Reverse Proxy aracıdır. Ziyaretçilere asıl sunucu gibi gözüken bir ara katman vekilidir. Cache aldığı dosyaları bellek üzerinde tutabilir.
Django üzerinde geliştirdiğiniz bir proje zamanla alıp yürürse ve uygulama üzerinde tuttuğunuz Cache(Yani önbellek) zamanla işe yaramamaya başlarsa yapılandırmanız içerisine Varnish ekleyebilirsiniz.
Aşağıda bulunan yapılandırmayı kullanarak Django üzerinde statik dosyaların 1 yıl, sayfa ve diğer bileşenlerin ise 15 dakikalık önbelleklerini tutabilirsiniz.
Yapılandırmaya önbellek silme bileşenleri eklemediğimi özellikle hatırlatayım.
Django, formlarla çalışabilmemizi sağlayan kapsamlı bir takım enstrümanlar sunan özel bir yapıya sahiptir. Bu yapı özellikleri arasında, tek bir konumda form işlevselliği tanımlama, veri doğrulama ve Django modelleri ile entegrasyonlar yer alır, şimdi ufak bir örnek ile durumun nasıl işlediğine kısaca bakalım.
Form oluşturulması için django uygulamamız içerisinde forms.py adlı bir dosya oluşturuyoruz. İçerisini ise aşağıdaki gibi oluşturuyorum. Djangoda formların belirli bir dosya içerisinde bulunması gerekmediğinide hatırlatırım. Yani isterseniz forms.py oluşturmak yerine, uygulama dosyalarınızın içerisinde de barındırabilirsiniz. Örneğin: models.py veya views.py içerisinde formlar yer alabilirler.
from django import forms
class Contact(forms.Form):
name = forms.CharField(required=False)
email = forms.EmailField(label='Your email')
subject = forms.CharField(widget=forms.Textarea)
Burada en önemli nokta django’da formların forms.Form sınıfının bir alt sınıfı olarak alınması gerekmektedir. Bu yüzden otomatik olarak form yapısı içerisindeki bütün sınıflara eklenmesi gerekmektedir. Daha sonra, form sınıfının forms.CharField türünde ve form.EmailField türlerinden biri olan üç özellik olduğunu görebilirsiniz. Bu form alanı tanımları, belirli özelliklere karşılık gelir ve girdiyi kısıtlar.
Örneğin, forms.CharField girdinin bir dizi karakter ve form olması gerektiğini belirtir. forms.EmailField ise girdinin bir e-posta olması gerektiğini belirtir. Ayrıca, her bir form alanının, girdi türünü daha da kısıtlamak için özellikler (örn. required) içerdiğini görebilirsiniz. Şimdilik bu Django form alanı türleri hakkında yeterli bilgiyi size verecektir.
Formumuzu oluşturduktan hemen sonra, gelin bu oluşturduğumuz formu projemizin views.py dosyası içerisinde nasıl kullanılabileceğine bakalım.
from django.shortcuts import render
from .forms import Contact
def contact(request):
form = Contact()
return render(request,'about/contact.html',{'form': form})
Oluşturduğumuz views.py içerisindeki contact fonksiyonu, önce Contact form sınıfını bir önceki örneğimizden teslim alıp başlatır ve bunu form referansına atar. Bu form başvurusu daha sonra about/contact.html şablonunda kullanılabilir hale getirilecek bir argüman olarak iletilir.
Ardından, Django’da kullandığımız şablonunun içinde Django formunu normal bir değişken olarak verebilirsiniz. standart şablon sözdizimini {{ form.as_table }} olarak kullanırsanız. Çıktısı aşağıdaki gibi olacaktır.
Django formunun HTML etiketlerine nasıl çevrildiğini gördünüz! Django formunun her form alanı için uygun HTML <input> etiketlerini nasıl ürettiğine dikkat edin (ör. Forms.EmailField(label = ‘E-postanız’), istemci tarafını bu alanı doldurmaya zorlamak adına <label> içerisine required ekler ve HTML5 tipini type="email" olarak ayarlar. ve bir e-postanın doğrulanması da bu şekilde sağlanmış olur). Ayrıca, form alanında kullanılan bir takım alanların gerekliliklerini değiştirebilirsiniz. Bunu form yapısında devredışı bırakmak için required=False olarak düzenleyin.
Ayrıca söz diziminde kullandığımız as_table ile çıktının HTML5 tablo yapısında olmasını işlerimizi kolaylaştırdığı için istedik. İsterseniz dokümantasyon aracılığı ile daha sonradan kendi yapınıza uygun olanı seçip kullanabilirsiniz.
Tüm bunları görsel bir şekilde anlatmamız gerekirse aşağıdaki şablon sizin için yeterli olacaktır.
Django Form İş Akışı
Bu noktaya kadar anlattıklarım. Django form sınıfına dayalı boş bir form döndürdük ve teorik olarak devam ettik. Django formlarını işlevsel bir hale getirmek için eksik parçaları hadi gelin hep birlikte tamamlayalım. Eksik parçaları ekleyelim.
Şimdiye kadar bir Django form sınıfı tanımının hızlı bir şekilde bir HTML formuna dönüştürülebileceğini öğrendiniz. Ancak bu otomatik HTML olayı, Django form sınıfı tanımlarını kullanmanın sadece bir ufak bölümüdür. Ayrıca form değerlerini doğrulayabilir ve hataları son kullanıcıya daha hızlı gösterebilirsiniz istersenizde kendi yapınızı kurarak yolunuza devam edebilirsiniz.
Şimdi oluşturduğumuz formun tüm web formları için standart olan HTML <form> yapısını oluşturacak. method tanımını POST olarak kullandık. POST yöntemi, kullanıcı verilerini işleyen web formlarında standart bir uygulamadır. Alternatif bir yöntem olarakta GET kullanabilirsiniz. Ancak, kullanıcı tarafından sağlanan verileri aktarmak için kullanılabilecek bir seçenek değildir.(genellikle)
Action tanımlaması yapmadığımızı fark etmişsinizdir. Bu durumda istek bulunan mevcut sayfaya aktarılacaktır. {% csrf_token %} ise POST isteği yoluyla gönderildiği ve Django tarafından işlendiği durumlar için ayrılan özel bir etikettir. Django tarafından uygulanan standart bir güvenlik mekanizması olan Cross-Site Request Forgery(Siteler Arası İstek Sahtekarlığı) anlamına gelir.
CSRF’yi devre dışı bırakmak ve formlarda {% csrf_token%} Django etiketini dahil etmemek mümkün olsa da, CSRF bir güvenlik önlemi olarak çalıştığı için, {% csrf_token%} Django etiketini POST ile gelen tüm formlara eklemeye devam etmenizi öneririz.
{{form.as_p}} ise form yapısını oluştururken diğer elementlerin <p> ile sarılması ve çıktıların satır satır işlenmesidir. Şimdi herşey kısmen hazır olduğuna göre gelelim isteklerin işleneceği views.py düzenlemeye kodumuz aşağıdaki gibi olacak
from django.shortcuts import render
from django.http import HttpResponseRedirect
from .forms import ContactForm
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
return HttpResponseRedirect('/about/')
else:
form = ContactForm()
return render(request,'about/contact.html',{'form':form})
Yukarıdaki örneğimizde en önemli nokta isteklerin işlendiği bölüm olan if/else yapısıdır. Yapılacak işlemlerimizi buna göre basitçe belirleyebiliriz. POST isteği gelirse ayrı, GET isteği gelirse ayrı olarak işleme alınaktır. POST mantığına daha yakından bakalım. İstek türü POST ise, gelen kullanıcı verisi var demektir, bu yüzden gelen veriye request.POST referansı ile erişir ve Django formunu onunla başlatırız. Ancak, bireysel form alanlarına erişmenin veya parça parça atamaları yapmanın gerekmediğine dikkat edin.
Bu noktada, bir kullanıcının Django form alan tanımları ile ilgili geçerli bir veri sağlayıp sağlamadığını hala bilmiyoruz (örneğin, değerler metinse veya geçerli bir e-posta değilse). Bir formun verilerini doğrulamak için, bağlı form örneğinde is_valid() yöntemini kullanmanız gerekir. Eğer form.is_valid() True ise veriler işlenir ve sonraki işlem yapılır, return ile URL’ye yönlendirme ile son bulur. form.is_valid() öğesi false ise form verilerinin hataları olduğu anlamına gelir; ve istenen şekilde ister kullanıcıya mesaj ulaştırılır istersede form tekrar gösterilir.
CSRF veya siteler arası talep sahteciliği, siber suçluların kullanıcıları bir web uygulamasında istenmeyen eylemleri gerçekleştirmeye zorlamak için kullandıkları bir tekniktir. Kullanıcılar web formlarıyla etkileşimde bulunduklarında, sipariş vermekten (örn. Ürünler, para transferleri) verilerinin değiştirilmesine (ör. Ad, e-posta, adres) kadar değişen her türlü durum değiştiren görevleri yaparlar. Çoğu kullanıcı, bir HTTPS/SSL güvenli kilit sembolü gördüklerinden veya bir web formuyla etkileşimde bulunmadan önce kullanıcı adı ve parola kullandıkları için web formlarıyla etkileşimde bulunduklarında daha yüksek güvenlik duygusu hissetmeye eğilimlidirler.
Bir CSRF saldırısı, web uygulamasında sosyal mühendislik ve lax uygulama güvenliğine büyük ölçüde dayanır; bu nedenle, saldırı vektörü diğer güvenlik önlemlerine (örn. HTTPS/SSL, güçlü parola) bakılmaksızın çalışır.
Görebildiğiniz gibi, bir CSRF saldırısı gerçekleştirmek için, bir kullanıcının belirli bir sitede aktif bir oturuma sahip olması ve bir kullanıcının bir eylemi veya bir eylemi gerçekleştiren bir sayfayı tıklatması için kandırılması yeterlidir. Bu nedenle terimin adı: “Siteler arası”, istek orijinal siteden gelmiyor, çünkü bu siber suçlunun sahte bir isteğidir.
Web formları, bir kullanıcı için benzersiz olan ve bir oturum tanımlayıcısına benzer bir son kullanma süresine sahip , genellikle bir CSRF belirteci şeklinde adlandırılan benzersiz bir alan daha içermelidir.
Django, kodunuzu test etmek için oldukça basit bir web sunucusu ile gelir ve bu sunucu ile siteyi test aşamasında sorunsuz bir şekilde çalıştırıp kullanabiliriz, ancak uygulamanın geliştirilme aşaması tamamlandıktan sonra yayına alma sırasında bu web sunucunun kullanılması önerilmez.
Bu yüzden araya harici bir web sunucusu eklememiz gerekir. Bunun için biz makalemizde uWSGI kullanacağız. Diğer uygulamalara göre hız bakımından olumlu yönde bir artış söz konusu olacak. Ayrıca statik içerikleri de NGINX ile sunacağız.
Kullanacağımız yapı ise şu şekilde olacak
uWSGI’ye socket oluşturmasını sağlıyoruz ve protokol aracılığı ile NGINX kardeşimizin servis etmesini ve dış dünyaya açılmasını sağlıyoruz. Django 1.4 sürümünden sonra zaten wsgi modülü otomatik kendisi oluşturduğundan dolayı python tarafında yapacağımız bir şey kalmamış oluyor. Sadece modülün yerini bulmamız ve not almamız bizim için yeterli
Şimdi gerekli enstrümanların kurulumuna geçiyoruz. Debian 9 için aşağıdaki işlemleri yaparak bize lazım olan paketleri sisteme dahil ediyoruz.
apt install uwsgi nginx
uWSGI sisteme dahil edileceği sırada. Sistem için NGINX kurulumu da yapılacak isterseniz güncel sürüme yükseltebilirsiniz. Mevcut NGINX sürümü 1.10.3 kurulacak. NGINX kurulduktan sonra yapılandırma dosyamız içerisine aşağıdaki satırları ekliyoruz. Bu sayede dış dünya ile django uygulamamız iletişim kurmuş oluyor.
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8000;
}
Yaptıklarımız bunlarla da sınırlı kalmayacak. uWSGI üzerinde bir takım ayarlamalarda yapmamız gerekecek Uygulamamız için örnek oluşturması adına benim kullandığım uwsgi.ini yapılandırma dosyası aşağıdaki gibidir.
Şimdi bu işlemleri otomatize etmek ve birazda bizim işlerimizi kolaylaştırmak için supervisor kuralım ve üzerinde gereken yapılandırmayı yapalım. Bunun için sisteme aşağıdaki komut aracılığı ile enstrümanın kurulumunu tamamlayalım.
apt install supervisor
Django uygulamamız için gereken yapılandırmayı /etc/supervisor/conf.d/ içerisine yapacağız. Benim oluşturduğum ve kullanacağım yapılandırma şu şekilde
Yapılandırma dosyamızı supervisorümüze kabul ettirmemiz gerekiyor bunun için ise supervisorctl reread komutunu vermeniz yeterli bu sayede yapılandırma dosyalarını yenilemiş oluyoruz. Oluşturduğumuz yapılandırma dosyasındaki değişikliği tamamlamak için supervisorctl update komutu ile işlemlerimizi sonlandırıyoruz.
Günlerden bir gün django projesi ile baya içli dışlı olmuştuk ve recaptcha uygulaması gerekiyordu. Var olan uygulamaya da baktık kurması dert kurcalaması dert bizde düşündük zaten açık kaynak kodlu kodları kendi içimize alalım hem daha sonradan değişiklik yapılacaksa bize kod bakımından kolay olur.
İlk önce tabi düşündük sıfırdan yazalım diye ancak daha sonradan uğraşmak istemedik. Aşağıdakileri sırasıyla projenize dahil etmeniz yeterli.
fields.py
import os
import sys
import socket
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_text as smart_unicode
from StreamingTwitter.widgets import ReCaptcha
from StreamingTwitter.ReCaptcha import submit
TEST_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
TEST_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'
class ReCaptchaField(forms.CharField):
default_error_messages = {
'captcha_invalid': _('Incorrect, please try again.'),
'captcha_error': _('Error verifying input, please try again.'),
}
def __init__(self, public_key=None, private_key=None, use_ssl=None,
attrs=None, *args, **kwargs):
"""
ReCaptchaField can accepts attributes which is a dictionary of
attributes to be passed to the ReCaptcha widget class. The widget will
loop over any options added and create the RecaptchaOptions
JavaScript variables as specified in
https://developers.google.com/recaptcha/intro
"""
if attrs is None:
attrs = {}
public_key = public_key if public_key else
getattr(settings, 'RECAPTCHA_PUBLIC_KEY', TEST_PUBLIC_KEY)
self.private_key = private_key if private_key else
getattr(settings, 'RECAPTCHA_PRIVATE_KEY', TEST_PRIVATE_KEY)
self.use_ssl = use_ssl if use_ssl is not None else getattr(
settings, 'RECAPTCHA_USE_SSL', True)
self.widget = ReCaptcha(
public_key=public_key, use_ssl=self.use_ssl, attrs=attrs)
self.required = True
super(ReCaptchaField, self).__init__(*args, **kwargs)
def get_remote_ip(self):
f = sys._getframe()
while f:
if 'request' in f.f_locals:
request = f.f_locals['request']
if request:
remote_ip = request.META.get('REMOTE_ADDR', '')
forwarded_ip = request.META.get('HTTP_X_FORWARDED_FOR', '')
ip = remote_ip if not forwarded_ip else forwarded_ip
return ip
f = f.f_back
def clean(self, values):
super(ReCaptchaField, self).clean(values[1])
recaptcha_challenge_value = smart_unicode(values[0])
recaptcha_response_value = smart_unicode(values[1])
if os.environ.get('RECAPTCHA_TESTING', None) == 'True' and
recaptcha_response_value == 'PASSED':
return values[0]
if not self.required:
return
try:
check_captcha = submit(
recaptcha_challenge_value,
recaptcha_response_value, private_key=self.private_key,
remoteip=self.get_remote_ip(), use_ssl=self.use_ssl)
except socket.error: # Catch timeouts, etc
raise ValidationError(
self.error_messages['captcha_error']
)
if not check_captcha.is_valid:
raise ValidationError(
self.error_messages['captcha_invalid']
)
return values[0]
widgets.py
from StreamingTwitter.ReCaptcha import displayhtml
from django.utils.safestring import mark_safe
from django import forms
from django.conf import settings
TEST_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
class ReCaptcha(forms.widgets.Widget):
if getattr(settings, "NOCAPTCHA", False):
recaptcha_response_name = 'g-recaptcha-response'
recaptcha_challenge_name = 'g-recaptcha-response'
else:
recaptcha_challenge_name = 'recaptcha_challenge_field'
recaptcha_response_name = 'recaptcha_response_field'
def __init__(self, public_key=None, use_ssl=None, attrs=None, *args,
**kwargs):
self.public_key = public_key or getattr(settings, 'RECAPTCHA_PUBLIC_KEY', TEST_PUBLIC_KEY)
if attrs is None:
attrs = {}
self.use_ssl = use_ssl if use_ssl is not None else getattr(
settings, 'RECAPTCHA_USE_SSL', True)
self.js_attrs = attrs
super(ReCaptcha, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None):
return mark_safe(u'%s' % displayhtml(
self.public_key,
self.js_attrs, use_ssl=self.use_ssl))
def value_from_datadict(self, data, files, name):
return [
data.get(self.recaptcha_challenge_name, None),
data.get(self.recaptcha_response_name, None)
]
ReCaptcha.py
import json
from django.conf import settings
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
from urllib.parse import urlencode
from urllib.request import build_opener, ProxyHandler, Request, urlopen
DEFAULT_API_SSL_SERVER = "//www.google.com/recaptcha/api"
DEFAULT_API_SERVER = "//www.google.com/recaptcha/api"
DEFAULT_VERIFY_SERVER = "www.google.com"
DEFAULT_WIDGET_TEMPLATE = 'captcha/captcha.html'
API_SSL_SERVER = getattr(settings, "CAPTCHA_API_SSL_SERVER",
DEFAULT_API_SSL_SERVER)
API_SERVER = getattr(settings, "CAPTCHA_API_SERVER", DEFAULT_API_SERVER)
VERIFY_SERVER = getattr(settings, "CAPTCHA_VERIFY_SERVER",
DEFAULT_VERIFY_SERVER)
WIDGET_TEMPLATE = getattr(settings, "CAPTCHA_WIDGET_TEMPLATE",
DEFAULT_WIDGET_TEMPLATE)
def want_bytes(s, encoding='utf-8', errors='strict'):
if isinstance(s, str):
s = s.encode(encoding, errors)
return s
class RecaptchaResponse(object):
def __init__(self, is_valid, error_code=None):
self.is_valid = is_valid
self.error_code = error_code
def displayhtml(public_key,
attrs,
use_ssl=True,
error=None):
"""Gets the HTML to display for reCAPTCHA
public_key -- The public api key
use_ssl -- Should the request be sent over ssl? (deprecated)
error -- An error message to display (from RecaptchaResponse.error_code)"""
error_param = ''
if error:
error_param = '&error=%s' % error
if use_ssl:
server = API_SSL_SERVER
else:
server = API_SERVER
return render_to_string(
WIDGET_TEMPLATE,
{'api_server': server,
'public_key': public_key,
'error_param': error_param,
'options': mark_safe(json.dumps(attrs, indent=2)),
'options_dict': attrs,
})
def request(*args, **kwargs):
"""
Make a HTTP request with a proxy if configured.
"""
if getattr(settings, 'RECAPTCHA_PROXY', False):
proxy = ProxyHandler({
'http': settings.RECAPTCHA_PROXY,
'https': settings.RECAPTCHA_PROXY,
})
opener = build_opener(proxy)
return opener.open(*args, **kwargs)
else:
return urlopen(*args, **kwargs)
def submit(recaptcha_challenge_field,
recaptcha_response_field,
private_key,
remoteip,
use_ssl=False):
if not (recaptcha_response_field and recaptcha_challenge_field and
len(recaptcha_response_field) and len(recaptcha_challenge_field)):
return RecaptchaResponse(
is_valid=False,
error_code='incorrect-captcha-sol'
)
if getattr(settings, "NOCAPTCHA", False):
params = urlencode({
'secret': want_bytes(private_key),
'response': want_bytes(recaptcha_response_field),
'remoteip': want_bytes(remoteip),
})
else:
params = urlencode({
'privatekey': want_bytes(private_key),
'remoteip': want_bytes(remoteip),
'challenge': want_bytes(recaptcha_challenge_field),
'response': want_bytes(recaptcha_response_field),
})
params = params.encode('utf-8')
if use_ssl:
verify_url = 'https://%s/recaptcha/api/verify' % VERIFY_SERVER
else:
verify_url = 'http://%s/recaptcha/api/verify' % VERIFY_SERVER
if getattr(settings, "NOCAPTCHA", False):
verify_url = 'https://%s/recaptcha/api/siteverify' % VERIFY_SERVER
req = Request(
url=verify_url,
data=params,
headers={
'Content-type': 'application/x-www-form-urlencoded',
'User-agent': 'Stream reCAPTCHA Python'
}
)
httpresp = request(req)
if getattr(settings, "NOCAPTCHA", False):
data = json.loads(httpresp.read().decode('utf-8'))
return_code = data['success']
return_values = [return_code, None]
if return_code:
return_code = 'true'
else:
return_code = 'false'
else:
return_values = httpresp.read().decode('utf-8').splitlines()
return_code = return_values[0]
httpresp.close()
if (return_code == "true"):
return RecaptchaResponse(is_valid=True)
else:
return RecaptchaResponse(is_valid=False, error_code=return_values[1])
Ardından projenizde nerede kullanacaksanız. Direk olarak oraya aşağıdaki gibi bir ekleme yaparak html içerisine çağırmanız yeterli
views.py dosyanızın içerisine aşağıdaki satırları ekleyin.
re_captcha = {'captcha': FormWithCaptcha()}
HTML dosyanız içerisine de
{{ captcha }}
Tam olarak çalışmasını istiyorsak settings.py dosyasına şu kısımları da eklemeyi unutmayın
Recaptcha özelliği otomatik olarak gelecek ve kolaylıkla yönetebileceksiniz. Ayrıca bu eklenti ile birden fazla sayfada ReCaptcha özelliğini aktif edip kullanabilmenize imkan sağlar diğerleri gibi sadece tek sayfada kullanılabilir bir şekilde ayarlanmamıştır.
windows ve linux sistem yönetimi, network ve ağ güvenliği, siber güvenlik, yazılım ve gündemdeki diğer teknolojik konular hakkında blog yazıları