Özel Yazı Tiplerinin Yüklenmesi

Özel yazı tipleriyle uğraşan biri, eğer ki performans ile birlikte UI elementlerinin görümüne çok önem veren bir sistemde çalışıyor ise yaptığı işi bir kaç kere daha gözden geçirmelidir. Ama ben bunu söyleyene kadar bile müşterilerinden geri dönüşler gelmiştir: ”Yazıların şekli birden değişiyor”, “Yazılar daha sonradan geliyor”, “Site çok yavaş açılıyor” gibi… Şimdi bu son yakarışı bir kenara bırakarak ilk ikisine çare bulalım. Tabii göreceksiniz bu kısmi bir çare olacak ve bazı şeylerden ödün vereceğiz.

Şimdi ”Yazıların şekli birden değişiyor” yani “yazı tipi değişmesi” diye tabir edeceğimiz olaya web camiasında FOUT (flash of unstyled text), “Yazılar daha sonradan geliyor” ’u da FOIT (Flash Of In­vis­i­ble Text) diye tabir ediyoruz.

Da daaa (sadece resimlere bakıp anlayanlar için):

FOUT yukarıda, FOIT aşağıdaki

 

Neyse ki kısmen de olsa bunu kontrol etmemizi sağlayan bir CSS özelliğimiz mevcut. Kısmen diyorum çünkü IE, Edge ve bazı mobil tarayıcılar bunu desteklemiyor. Eğer elini eteğinizi IE den çekmişseniz “font-display” ile buna bir çözüm bulabilirsiniz. Font-display özelliği temel olarak kullanılan özel yazı tipinin hangi koşulda(nasıl ve ne zaman) gösterileceğini kontrol etmenize yarar.

font-display özelliği destekleyen tarayıcılar

Font-display özelliği auto, swap, fallback, optional gibi değerler alır. Bizim FOUT ve FOIT den kurtulmamız için kullanacağımız değer ise “optional”. Bu arada ben font-display özelliği ile ilgili olarak detaya çok girmeyeceğim çünkü güzel bir kaynak var – font-display için daha detaylı bilgi almak için Fatih Hayrioğlu’nun güzel yazısına bakabilirsiniz- https://fatihhayrioglu.com/font-display-ozelligi/

Optional dedik ne yapar bu değer; tarayıcı çok kısa bir süre içinde özel yazı tipinin yüklenmesini bekler. Fakat bu süre içerisinde yüklenmez ise ikincil ya da varsayılan yazı tipi ne ise o yüklenir. Bu arada arka tarafta özel yazı tipi indirilmeye devam edilip tarayıcının memory cache’ine yazılır. Tarayıcıyı ikinci yenilemenizde bu kısa sürede yükleme gerçekleşeceği için(muhtemelen) tüm yazılar özel yazı tipiyle görünür. Dolayısıyla bu FOIT ve FOUT problemini ortadan kaldırır. Tabii burada tasarım ekibinizle ya da müşteriniz ile el sıkışmanız gerekiyor. Çünkü siteyi ilk kez ziyaret eden veya gizli pencerede ilk kez açan kullanıcı sitenizi ikincil yazı tipi ile görecektir. Ayrıca ikinci yazı tipinde, sitenin güzelce kontrol edilmesi gerekiyor, mazallah sitenin orası burası kayabilir.

Tabii tek probleminiz bu ise font-display size çözüm olacaktır. Ama varsayalım 1 KB’ın bile önemli olduğu, anlık çok yüksek trafiğin olduğu bir sistemde (bir web sitesinde) çalışıyorsunuz. O zaman en mantıklı çözümler özel yazı tiplerinden kaçınmak(olabildiğince azaltmak), bunları asenkron olarak sonradan yüklemek, preload ile çekmek vs olabilir (Detaylı bilgi için link bırakıyorum. https://github.com/zachleat/web-font-loading-recipes#recommended-methods.). Bu tekniklerden biri  de font-display ve lazy-load lu polyfill bir yöntem olan Ebay Yazı Tipi Yükleme Stratejisi  .

Tekniği kısaca özetlemek gerekirse; Font-display destekleyip desteklemediğine ya da  yazı tipinin daha önce yüklendiğini belirten localstorage’daki bir değere bakıyor. Eğer öyleyse html tag’ine özel yazı tipini kullan diyen bir class atıyor. Aksi takdirde footer’a yerleştirdikleri bir kod parçacığı ile ilgili yazı tipini asenkron olarak yüklüyor. İkinci açılışta ise textler özel yazı tipinde görünüyor. Tabii burada tasarım ekibi yazı tipi olarak, ikincil yazı tipiyle neredeyse aynı bir tip seçmişler. Sadece uzman gözler aradaki farkı anlayabilir belki.

Bu teknikte beğenmediğim birşey var. O da font-display destekliyorsa yazı tipinin ne kadar gösterilmeyecek olsa da ilk açılış anında indirilmeye başlıyor olmasıydı (ilk açılıştan bahsediyorum). Ben de bunun üzerine bu stratejiyle biraz oynayarak şu adımları izledim.

ADIM 1: Tüm CSS’lerdeki font-family özelliğini düzelt!

Bunu zaten biliyorsunuzdur; css içerisinde aktif olan biz stil özelliği içinde bir özel yazı tipi isteği varsa tarayıcı bunu gördüğü gibi yazı tipinin belirtilen url’den indirmeye başlar. Örnek vermek gerekirse:

HTML dosyanız

 
 
<html>
<!--....-->
<body>
<h1 class=”baslik”>Ben başlığım</h1>
</body>
</html>

 

Css dosyamız

 
 
  1. @font-face{
  2. font-family: 'Roboto'
  3. font-style: normal
  4. font-weight: 400
  5. src: url(/assets/fonts/Roboto.woff2) format('woff2')
  6. }
  7. body { font-family: ‘Roboto’,Arial,sans-serif}

 

Html sayfası render olup css özelliklerini görünce body elementinin Roboto yazı tipi ailesini kullandığını görecek ve bu yazı tipini indirecektir. Peki biz css’imizi bir anahtar gibi kullanacağımız bir selector eklesek ne olur? Bu anahtar yazı tipini kullan veya kullanma desin bize. Buyrun:

Css dosyamız bu hale dönüşüyor

 
 
@font-face{
font-family: 'Roboto'
font-style: normal
font-weight: 400
src: url(/assets/fonts/Roboto.woff2) format('woff2')
}
.font-loaded body { font-family: ‘Roboto’}
body { font-family:Arial,sans-serif}

 

Gördüğünüz gibi artık html elementine ekleyeceğimiz bir class sayesinde sitemizin kullandığı yazı tipini değiştireceğiz. Tabii bunu varolan sitede yapmak hele hele font-family özelliği body üzerinden değil de direk alt seçicilere verildiyse, vay halinize. Çok dikkatli bir şekilde yapmanız gerekiyor.

ADIM 2: LOCAL STORAGE’ı KONTROL EDEN SCRIPT!

Yine Ebay’in stratejisinde olduğu gibi head açılış tag’inden hemen sonra aşağıdaki script ile yazı tipinin memory cache de olup olmadığını(ki bunu localstorage’a eklediğimiz bir key ile kontrol ediyoruz) kontrol ediyoruz ve buna göre html tag’ine belirlediğimiz class’ı atıyoruz:

 
 
 (function() {
        var useCustomFont = localStorage && localStorage.getItem('custom-font');
        if (useCustomFont) {
            document.documentElement.classList.add('font-loaded');
       }
})();

 

ADIM 3: FOOTER’da ASENKRON YAZI TİPİNİ YÜKLE

Bu kısım biraz tricky çünkü yazı tipini indirmek için iki ayrı yöntem kullanıyor. Birisi native olarak FontFace (css font loading api) destekleyenler için diğeri desteklemeyip FontFaceObserver denen cross-browser çözüm sunan bir kütüphaneyi kullanıyor. Bu kütüphaneyi de asenkron olarak yüklüyor tabii ki. Bu kısım için ebay’in hazır script’ini kullanabilirsiniz, benim ki aşağıda:

 
 
  1. var fontFaceSet = document.fonts;
  2. var FONT_CLASS_NAME = 'font-roboto';
  3. var FONT_FACE_OBSERVER_LIB = 'https://www.sizinsiteniz.com/fontfaceobserver/fontfaceobserver.js';
  4. function lazyLoad(url, callback) {
  5. var scriptElem = document.createElement('script');
  6. scriptElem.type = 'application/javascript';
  7. scriptElem.async = true;
  8. scriptElem.onload = callback;
  9. scriptElem.src = url;
  10. var firstScript = document.getElementsByTagName('script')[0];
  11. firstScript.parentNode.insertBefore(scriptElem, firstScript);
  12. }
  13. function updateLocalStorage() {
  14. try {
  15. localStorage.setItem('custom-font', FONT_CLASS_NAME);
  16. } catch (ex) {
  17. // Either localStorage not present or quota has exceeded
  18. // Another reason Safari private mode
  19. // https://stackoverflow.com/questions/14555347/html5-localstorage-error-with-safari-quota-exceeded-err-dom-exception-22-an
  20. }
  21. }
  22. function isFontFaceSetCompatible() {
  23. var compatible = fontFaceSet && fontFaceSet.load;
  24. if (compatible && /Apple/.test(window.navigator.vendor)) {
  25. var match = /AppleWebKit\/([0-9]+)(?:\.([0-9]+))(?:\.([0-9]+))/.exec(window.navigator.userAgent);
  26. compatible = !(match && parseInt(match[1], 10) < 603);
  27. }
  28. return compatible;
  29. }
  30. function loadFont() {
  31. // check for fontfaceset else load polyfill before invoking fontloader
  32. if (isFontFaceSetCompatible()) {
  33. fontFaceSet.load('100 12px Roboto');
  34. fontFaceSet.load('300 12px Roboto');
  35. fontFaceSet.load('400 12px Roboto');
  36. fontFaceSet.ready.then(updateLocalStorage);
  37. } else {
  38. lazyLoad(FONT_FACE_OBSERVER_LIB, function() {
  39. var f = new FontFaceObserver('Roboto');
  40. Promise.all([
  41. new FontFaceObserver('100 12px Roboto').load(),
  42. new FontFaceObserver('300 12px Roboto').load(),
  43. new FontFaceObserver('400 12px Roboto').load()
  44. ]).then(updateLocalStorage);
  45. });
  46. }
  47. }
  48. function isFontLoaded() {
  49. return ((localStorage && localStorage.getItem('custom-font') === FONT_CLASS_NAME));
  50. }
  51. function init() {
  52. // Initialize font loader only if it is not loaded previously
  53. if (!isFontLoaded()) {
  54. window.addEventListener('load', function() {
  55. if (requestAnimationFrame) {
  56. requestAnimationFrame(loadFont);
  57. } else {
  58. loadFont();
  59. }
  60. });
  61. }
  62. }
  63. init();

 

Şimdi burada aklınıza şu soru gelebilir; nasıl oluyorda FontFace API font’un indireceği url’i biliyor. Cevap tabii ki css de tanımladığın @font-face özelliğinde saklı. FontFace direk oradaki bilgileri okuyup url’i rahatça bulup indiriyor.

Lafın kısası Ebay’den farklı olarak yazı tipinin font-display özelliğine bağlı olarak indirilmesini engelledim. Onun dışında birebir aynı taktik 🙂

Sonuç Olarak

Sonuç olarak web sitemizin ilk yüklenme anındaki yükü azaltmış olduk. Her ne kadar woff2 kullanıp maliyeti düşürmüş olsak da aynı yazı tipinin farklı versiyonlarını kullanıyorsak durum vahim olabilir. Tabii ki oturmuş uzun yollardan geçmiş sitelerde, onlarca CSS dosyalarında böyle bir değişikliği yapmak hem çok riskli hem de çok maliyetli olabiliyor. Ama sonuçta bunların hepsi performansı, performans da arama motoru sıralamalarını ve sıralama da kazancı etkilediği için bu gibi geliştirmeler görmezden gelinmemelidir. 

Esen kalın.

Kaynaklar:

https://www.ebayinc.com/stories/blogs/tech/ebays-font-loading-strategy/

https://github.com/eBay/ebay-font/tree/master/font/marketsans

https://fatihhayrioglu.com/font-display-ozelligi/

https://github.com/zachleat/web-font-loading-recipes

https://developers.google.com/web/updates/2016/02/font-display

https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API

Swift ile String – Hex – Byte Array Dönüşümleri

Yaklaşık 4 aydır swift ile ios uygulama geliştirmesi üzerine çalışıyorum. Front-end’den sonra çok istediğim ve bir türlü nasip olmayan mobile development’a geçiş yapmış bulunuyorum.
Bu süre zarfında çalıştığım projeyle alakalı olmak üzere çoğu kez string’lerin manipulasyon ile uğraştım. Aslında c tabanlı kütüphaneleri kullandığınız zaman bu dönüşümlere ihtiyacınız olabilir. Ayrıca bu tip kütüphaneleri kullanırken eğer çok “swifty” çözümler kullanmazsanız memory hatalarıyla karşı karşıya kalacaksınız. Bir de yazdığınız bu çözümleri swift’in extension yapısını kullanmak yapmak mimari yapınızı karmaşadan veya ek bir sınıftan kurtarmış olur.

Herhangi bir string’i hex string’e çevirmek için (bu arada hex’in ne olduğunu anlatmıyorum):

 
 
  1. extension String{
  2. func getHex() -> String {
  3. let temp = NSString(string:self)
  4. var hex = ""
  5. for i in 0..<temp.length{
  6. hex += String(temp.characterAtIndex(i),radix:16)
  7. }
  8. return hex
  9. }
  10. }
  11. "sercan".getHex() //çıktı: "73657263616e"

 

Hex string’i byte-array’e çevirmek için:

 
 
  1. extension String{
  2. func hexToByteArray() -> [UInt8] {
  3. let tempStr = NSString(string: self)
  4. let len = tempStr.length
  5. var arr = [UInt8]()
  6. for i in 0..<len where i%2 == 0{
  7. let x = tempStr.substringWithRange(NSRange(location: i, length: 2))
  8. let y = UInt8.init(x, radix: 16)
  9. arr.append(y!)
  10. }
  11. return arr
  12. }
  13. }
  14. "sercan".getHex().hexToByteArray() //çıktı: [115, 101, 114, 99, 97, 110]

 

Yine de belirtmekte fayda var UInt8 bizim diğer programlama dillerinde kullandığımız byte-array‘in swiftçesi

Bunun haricinde de NSData haline getirdiğimiz byte-array’i [bknz: NSData(bytes: <T,UnsafePointer>, length: <T,Int>)] hex’e çevirme olayımız var:

 
 
  1. extension NSData {
  2. func getHex() -> String? {
  3. var bytes = [UInt8](count: length, repeatedValue: 0)
  4. getBytes(&bytes, length: length)
  5. let hexString = NSMutableString()
  6. for byte in bytes {
  7. hexString.appendFormat("%02x", UInt(byte))
  8. }
  9. return String(hexString)
  10. }
  11. }
  12. var arr = "sercan".getHex().hexToByteArray()
  13. NSData(bytes: arr, length: arr.count).getHex() //çıktı: "73657263616e"

 

Çevirimler mantığı bildikten sonra çok kolay zaten.
Esen kalın

Swift ile Diffie-Helman Anahtar Değişimi (Openssl)

Diffie-Helman, açık bir ağ üzerinde ortak gizli bir anahtar üretmeye yarayan güvenli bir değişim methodudur. Bu yöntem kullanılarak üretilen bir simetrik anahtar bu açık ağda güvenli iletişimi sağlar. Anahtar simetrik olduğundan bir taraf göndereceği mesajı onunla gizlerken diğer tarafta onu aynı anahtarı kullanarak açabilir. Diffie-Helman’ı için verilen en iyi örnek renkler ile anlatılandır:

Aslında bu işlem matematiksel olarak logaritmik yöntemle yapılır. Her iki tarafta açık olan p ve g, bir de gizli a ve b – a bir tarafta, b diğer tarafta olacak şekilde – değerleri vardır vardır. Bu değerlerden bir A değeri hesaplanıp karşı tarafa gönderilir (g^a mod p = A). Karşı tarafta aynı işlemle bir B değeri hesaplayıp karşı tarafa gönderir. İki taraf da kendi gizli anahtarları ve karşı tarafın gönderdiği değerle (B^a mod p = s ve A^b mod p = s) ortak gizli s anahtarını elde ederler.

Bu şifreleme tekniğinin çözülememesi p’nin yeterince büyük olmasına bağlı. Bu sayılar için belirli standartlar mevcut hatta bunun için RFC dökümanları mevcut ( ben örnek olarak en son yayınlanan RFC 5114‘ü referans alacağım).

Olayın iOS swift tarafında da bir kaç kütüphane mevcut fakat p ve g değerlerini dışarıdan alan veya benim kullanacağım değerleri kullananı mevcut değildi. Bunun için OpenSSL kütüphanesini kullanan wrapper class yazdım. Ayrıca OpenSSL’in son versiyonunda benim kullanacağım değerler hazır bir fonksiyon ile verilmişti.

OpenSSL’in projeye dahil etmenin en güzel yolu CocoaPods:

 
 
  1. pod 'OpenSSL-iOS', '~> 1.0.204'

 

Kütüphane seçerken öyle rastgele seçmeyin direk openssl’in yayınladığını bulun ki yazdığım öyle olan.

Temel olarak OpenSSL kütüphanesinden 3 modul bize lazım olacak

 
 
  1. import OpenSSL.bn  //BIGNUM
  2. import OpenSSL.dh  //DIFFIE-HELMAN
  3. import OpenSSL.ossl_typ //tip atamaları

 

İlk olarak diffie-helman context’i mizi yaratalım. Ki bu bize public ve private key üretir:

 
 
  1. self.dh = DH_get_2048_256()  //bahsettiğim fonksiyon.
  2. DH_generate_key(dh)
  3. self.dhPubKey = dh.memory.pub_key
  4. self.dhPriKey = dh.memory.priv_key

 

Sıra hex string şeklinde gelen karşı tarafın public key’i ile ortak gizli anahtarı üretmeye geldi:

 
 
func getCommonKey(peerPubKey:String) throws-> [UInt8]{
        //Generating common key
        let peerPubStr = peerPubKey  //referans atıyacağımız için local değişkene atıyoruz
        var pubBIGNUM = BN_new()
        BN_hex2bn(&pubBIGNUM, peerPubStr) //hex'ten bignum'a dönüştürüyoruz
        
        var commonKeyArr = [UInt8](count: 256, repeatedValue: 0) //256 byte'lık ortak anahtarımız olacak onu oluşturuyoruz
        let status = DH_compute_key(&commonKeyArr, pubBIGNUM, self.dh) //Hesaplamayı yapıp array'e atıyoruz
        if(status < 0){
            throw DHError.DHGeneration("Calculation error for DH parameter, status: \(status.description)")
        }
        
//ortak anahtarımız byte-array şeklinde dönüyor
        return commonKeyArr 
    }

Burada yaptığımız temel olarak karşı tarafın ürettiği B değerini alıp kendi context’imize sokup ortak anahtarı bulmaktı.

Kodları daha düzenli bir şekilde bir cococapods haline getirdim. Oradan kendi projenize indirebilirsiniz:

 
 
  1. pod "GioSwKriptor"

 

Katkıda bulunmak istiyorsanız, github linki: GioSwKriptor (unit testi yazmayı unutmayın!)

Angular JS, Jasmine ve Karma ile Unit Test – Filter Testi

Merhaba,

Angular ile bir aplikasyon yapıyorsunuz ve filter(filtre) yazdınız. Hadi bakalım bunun unit testini yazın. Hemen şöyle tarih formatlayan bir filtremiz olsun:

 
 
  1. angular.module('moduleName',[]).filter('utcDate',function() {
  2.    return function(input){
  3.        if (input === null) {
  4.            return '';
  5.        }
  6.        return moment(moment.utc((new Date(input))).toDate()).format('DD.MM.YYYY HH:mm');
  7.    };
  8. });

Test yazarken tüm branch’lere (her if-else) girmemiz gerekecek dolayısıyla da burada bir null değerle bir de normal bir tarih değeriyle iki input verirsek tüm kodu cover etmiş oluruz.

 
 
  1. describe('Utc date filter test suite: ',function(){
  2.     var mockfilter;
  3.     beforeEach(function () {
  4.        module('moduleName');
  5.         inject(function (utcDateFilter) {
  6.             mockfilter = utcDateFilter;
  7.         });
  8.     });
  9.     it('Should convert properly', function () {
  10.         var d = "Fri Mar 11 2016 09:49:00 GMT+0200 (EET)",
  11.             exp = "11.03.2016 09:49";
  12.         expect(mockfilter(d)).toBe(exp);
  13.     });
  14.     it('Should return empty value if input is null', function () {
  15.        expect(mockfilter(null)).toBe('');
  16.     });
  17. });

 

Burada dikkat etmemiz gerekn “utcDate” diye yazdığımız filtreyi inject ederken “utcDateFilter” diye çağırmamız.

Kolay Gelsin

Angular JS, Jasmine ve Karma ile Unit Teste – Giriş

AngularJs yapısı gereği unit test’e uygun bir yapısı vardır. Bu uygunluğun nedeni de “Loose coupling” ve “Dependency Injection” felsefelerini benimsemiş olması. Yani içerdiği elemanların birbirine bağımlılığı yok.
Zaten angularjs ile test işlerine girmişseniz bu konulara aşinasınızdır daha fazla uzatmadan nasıl bir yol izleyeceğiz onu anlatayım. Tabii bazı varsayımlarım var. Birincisi modern bir web aplikasyonu yazdığınızı yani npm,bower gibi güçlü paket yöneticileri, grunt gibi bir task işleyicisi, karma gibi de bir test koşucunuz oldugunu ve ilk paragrafta bahsettiğim gibi DI bir mimariniz varsa (zaten olmasa yapmanız zor olacaktır işin içinden çıkamayacağınız bir durum alacaktır: aynen böyle olursunuz)

Malzemeler

  • Karma
  • Phantomjs (headless browser)
  • Cobertura (coverage için)
  • Bower
  • Npm
  • Jasmine

Tarif

Öncelikle daha önce de dediğim gibi bazı beklentilerim var. Projenizde npm ve bower paket yöneticisi olmalı. Bunlar projenizde eksik ise özellikle npm’i apt-get install … komutu  ile kolayca kurabiliriz. Dolayısıyla elimizde bir package.json dosyası yani projemize yüklediğimiz paketler olmalı. Örnek olarak şöyle birşey:

package.json
 
{
  "name": "xxxx",
  "version": "1.0.0",
  "description": "xxxxxx",
  "author": "sercan",
  "private": true,
  "devDependencies": {
    "grunt": "^0.4.5",
    "grunt-karma": "^0.12.1",
    "jasmine-core": "^2.3.4",
    "karma": "^0.13.14",
    "karma-chrome-launcher": "^0.2.1",
    "karma-coverage": "^0.5.3",
    "karma-firefox-launcher": "^0.1.7",
    "karma-html-reporter": "^0.2.7",
    "karma-jasmine": "^0.3.6",
    "karma-junit-reporter": "^0.3.8",
    "karma-phantomjs-launcher": "^0.2.1"
  }
}

 

Tabii test için ve lokalde kullanılacak paketler -dev parameteresiyle kurulmuştur. Buradaki bazı kütüphaneler olur da ihtiyacınız olur diye var (Ör: junit reporter, chrome-launcer vb).

İkinci paket yöneticimiz (yani bower) ile angular, bootstrap gibi kütüphaneleri kuruyoruz. Ekstra olarak yine bir development dependency olan angular-mocks kütüphanesini indiriyoruz. Bu bizim angular objelerini mock’lamak için yani sanal muadilini yaratmak için kullanacağımız kütüphane. Onu da bower install -dev  …  şeklinde kurabildiğinizi düşünüyorum.

Şimdi geldik karma’yı ayarlamaya. Anlatmaya direk dosyanın üzerinden devam ediyorum:

karma
 
  1. module.exports = function(config) {
  2.     config.set({
  3.         // base path
  4.         basePath: './',
  5.         // Kullanılacak framework, jasmine kullanıyoruz
  6.         frameworks: ['jasmine'],
  7.         files: [
  8.             'bower_components/angular/angular.js',
  9.             'bower_components/jquery/dist/jquery.min.js',
  10.             'bower_components/angular-route/angular-route.js',
  11.             'bower_components/angular-mocks/angular-mocks.js',
  12.             //...vs yüklenmesi gereken dosyalar
  13.         ],
  14.         // Plugin'ler
  15.         plugins: [
  16.        'phantomjs',
  17.        'karma-jasmine',
  18.        'karma-phantomjs-launcher',
  19.        'karma-coverage',
  20.        'karma-chrome-launcher',
  21.        'karma-firefox-launcher',
  22.        'karma-html-reporter',
  23.        'karma-junit-reporter'
  24.    ],
  25.         // Dışlanacak dosyalar, yani test'e dahil etmek istemediğimiz
  26.         exclude: [
  27.         ],
  28.         // Raporlama için kullanacağımız pluguin ler
  29.    reporters: ['progress', 'html', 'coverage', 'junit'],
  30.    // Junit için ayarlar
  31.    junitReporter: {
  32.       outputDir: 'xxx/junit', // output path
  33.       outputFile: 'junit-test-report.xml', //dosya ismi, path ile birleşecek
  34.       suite: '', //çıkan xml de suite için olan attribute'u ayarlar
  35.       useBrowserName: false // adından anlaşılıyor
  36.    },
  37.    // Kod coverage için cobertura kullanıyoruz, onun ayarları
  38.    coverageReporter: {
  39.       reporters: [
  40.           {
  41.          type : 'cobertura',
  42.          dir : 'xxx/coverage'
  43.           },
  44.           {
  45.          type : 'html',
  46.                    dir : 'xxx/coverage'
  47.           }
  48.       ]
  49.    },
  50.    // Karma html reporter ayarları
  51.    htmlReporter: {
  52.       outputDir: 'xxx/karma_html',
  53.       templatePath: null,
  54.       focusOnFailures: true,
  55.       namedFiles: false,
  56.       pageTitle: null,
  57.       urlFriendlyName: false,
  58.       reportName: ''
  59.    },
  60.         // web server port
  61.         port: 9876,
  62.         // renklendirme
  63.         colors: true,
  64.         // logging leveli
  65.         logLevel: config.LOG_INFO,
  66.         // dosyalar değiştiğinde çalışsın mı
  67.         autoWatch: true,
  68.         // Hangi browser'da test yapalım
  69.         browsers: ['PhantomJS'],
  70.         // Timeout zamanı
  71.         captureTimeout: 60000,
  72.         // Tek kosumluk test ayarı
  73.         singleRun: false
  74.     });
  75. };

 

zaten gördüğünüz üzere bu karma.conf.js dosyası bir node js modülü. İçinde de belirli ayarlar var.

Detaylı bilgiye buradan ulaşabilirsiniz: https://karma-runner.github.io/0.13/config/configuration-file.html

Şimdi basit bir test yazmaya hazırız fakat öncelikle örnek bir kodumuz olmalı. Biz öncelikle en basitinden başlayalım: controller testi

loginController.js adında bir kontrolumuz olsun:

loginController.js
 
  1. angular.module('module_name',[]).controller('loginController', ['$scope', function ($scope) {
  2.         $scope.validForm = true;
  3.         $scope.User = {Username: '', Password: ''};
  4.         $scope.submitButtonText = "Sign In";
  5.         $scope.alerts = [];
  6.         $scope.loading= false;
  7.         $scope.closeAlert = function (index) {
  8.             $scope.alerts.splice(index, 1);
  9.         };
  10.     }]);

Beginner’lar için açıklıyayım module_name adlı modulumuzun altında loginController adlı bir controller’ımız var ve $scope’a bağımlı. İçinde closeAlert diye bir method var. Diğer satırlarda bazı değişkenler var.

Şimdi jasmine framework ile test yazacağız. Ama ondan önce isterseniz şu linke bir bakın: http://jasmine.github.io/2.4/introduction.html

Genel bir jasmine test template’i şu şekildedir:

 
 
  1. describe("Test spec tanımı. Ör: login controller testleri", function() {
  2.   beforeEach(function() {
  3.      //kod
  4.   });
  5.   afterEach(function() {
  6.     //kod
  7.   });
  8.   it("Test tanımı", function() {
  9.     //assertion
  10.   });
  11.   it("Another test", function() {
  12.     //assertion
  13.   });
  14. });

 

Biz de bu yapı da birşey yapacağız ama işin içinde bir trick var. O da modülü mock’lamak olacaktır ve controller’dan başka bir scope’da yeni bir muadil yaratmak olacaktır. Tabii ki angular-mock kutuphanesini kullanarak. Ondan sonra da testleri yazacağız.

 
 
  1. describe('Login testing suite', function () {
  2.     var mockLoginController, scope, apiFactoryMock;
  3.     //Burada Inject olaylarını gerçekleştiriyoruz
  4.     beforeEach(function () {
  5.         //Modulu mock'layıp yaratıyoruz
  6.         angular.mock.module('module_name');
  7.         //controller'ı inject edip global bir değişkene atıyoruz
  8.         angular.mock.inject(function ($controller, $rootScope) {
  9.             //Yeni scope yaratılıyor
  10.             scope = $rootScope.$new();
  11.             //Controller yaratılıyor
  12.             mockLoginController = $controller('loginController', {$scope: scope});
  13.         });
  14.     });
  15.     it('Should "validForm" parameter be defined', function () {
  16.         expect(scope.validForm).toBeDefined();
  17.     });
  18.     it('Should "User" model be defined', function () {
  19.         expect(scope.User).toBeDefined();
  20.     });
  21.     it('Should close alert func. works properly', function () {
  22.         scope.alert=[1,2,3,4];
  23.         mockLoginController.closeAlert(2);
  24.         expect(scope.alert.size).toBe(3);
  25.     });
  26. //Şeklinde testlerimiz sürer gider
  27. });

Bundan sonraki anlatımlarda direk çözüme yönelik testler yazacağız. Ör: filter,directive, timeout func. vb.

Esen kalın.

Nginx ile Reverse Proxy

Bu yazıda kısaca nginx server’da proxy ayarlarının nasıl yapılacağına değineceğim. Kurulum olayını direk apt-get ile kolayca yapıyoruz o kısmı atlıyorum. Ayrıca nginx ayarları da nginx.conf dosyasında tanımlanıyor. Bu ayarlar da şimdilik bizi aşıyor biz asıl konumuza odaklanıyoruz.

Proxy, bildiğiniz gibi internet erişiminde kullanılan ara sunucuya denir. Türkçe meali vekil sunucudur.

Ne yapar?
Kullanıcı/tarayıcı isteği yapar, tabii bu istek direk vekil sunucumuza gelir. İstekte “ben şunu şurdan istiyorum” denir. Vekil sunucu ilgili adrese bağlanır ve datayı çekip, response oluşturur ve kullanıcıya döndürür.

Nasıl?
Nginx’in kurulduğu yerde (root dosyasında) sites-available klasörü içerisinde default adında bir config dosyası vardır. Eğer ellemediyseniz orada default ayarlar bulursunuz haliyle. Tabii ben buna şatdadanak “default” isminde dedim diye illaki “default” olacak diye birşey yok, nginx.conf dosyasında sonralara doğru bakarsanız “include..” ile başlayan satırlarda tanımlanmıştır.

Dosyayı açında json tadında bir tanımlama ile karşılaşılıyor:

 
 
  1. server {
  2.     listen 80;
  3.     root /xxx/web-root;
  4.     index index.html index.htm;
  5.     server_name xxx.com;
  6.     <strong>location /api/v1/ {
  7.         proxy_pass http://xx.xxx.xxx.xx:8090/;
  8.     }</strong>
  9. }

 

Burada ki önemli kısım “location” ile başlayan bir kısım. Biz burada aslında http://ngnixroot/api/v1 adresine bir request yapıldığında bunu direk olarak http://xx.xxx.xxx.xx:8090/ bu adrese yönleniriyor (xx ler ip blockları,kafanız karışmasın herhangi bir domain de olabilir).

Ne zaman ?

Web olaylarında genellikle ajax request’lerinde “cross origin domain policy” hatası alırız (aslında bu bir hata değil de genellikle böyle diyoruz. “Abiii cross origin domain yedim :P”). Velhasılkelam başka bir domain altında çalışan bir API’miz var ve buna başka domain’de olan bir web arayüzünden  request atmaya çalışıyoruz. Direk jsonp vs kullanmazsak veya ajax request’imiz arasına kendi yaptığımız php/.net ile yazdığımız bir filter koymazsak dediğim hatayı alırız.

Tam bu anda eğer bu web sitemiz nginx’in altında çalışıyorsa buna bi reverse proxy çakarız ve API’yı sanki lokalimizde bir path’de çalılıyormuşcasına gösterebiliriz. Ve alavere dalavereye de fazlaca girmemiş oluruz.

Kolay Gelsin

“Yaşamak bir ağaç gibi tek ve hür ve bir orman gibi kardeşçesine”
Nazım Hikmet

WordPress saat ve tarih formatlama

WordPress’de tarih ve zaman formatlama için kullanılan iki method vardır ve bunlar parametre olarak “format string”‘i alır: the_date() ve the_time(). Bu iki methodun döndürdüğü değer bakımından bir farklılığı var. O da the_date() methodu sadece tarih bilgisini zaman(time) bilgisinden hariç getiriyor. the_time() ise the_date()’in yaptığının aynısını ve daha fazlasını yapabiliyor. O yüzden benim önerim the_time() methodunun kullanılması yönünde.
Aslında bilmeniz gereken “format string”‘ini nasıl yazmanız gerektiği. Ben genel olarak bazı değerleri vereceğim ama tüm değerlere buradan ulaşabilirsiniz.
Örneğin şöyle bir formatlama yapmak istiyorsanız:
// 24 September 2015, Friday
Şöyle bir string yazıp the_time() methodunu çağırmalısınız:

 the_time('j F Y, l');

Burada:

j: Günün 0 ön eki almamış hali
F: Ayın text hali
Y: Yılın 4 basamaklı hali
l: Haftanın gününü belirtmekte

Ayrıca bu örneklere de bakarak neler yapabileceğinizi görebilirsiniz:

F j, Y g:i a - November 6, 2010 12:50 am
F j, Y - November 6, 2010
F, Y - November, 2010
g:i a - 12:50 am
g:i:s a - 12:50:48 am
l, F jS, Y - Saturday, November 6th, 2010
M j, Y @ G:i - Nov 6, 2010 @ 0:50
Y/m/d \a\t g:i A - 2010/11/06 at 12:50 AM
Y/m/d \a\t g:ia - 2010/11/06 at 12:50am
Y/m/d g:i:s A - 2010/11/06 12:50:48 AM
Y/m/d - 2010/11/06

Bu örneklerde de birşey dikkatinizi çekmiş olabilir: \a\t. Backsplash ile literal yazımlar yapabiliyoruz. Bunin haricinde eklenecek önemli bir nokta da tarihlerin localize edilmesi yani çevrilmesi. Bunun için de date_i18n() methodunu kullanıyoruz.
Bu konuyla ilgili yazacaklarım bu kadar ama sorularınız olursa mail atmaktan çekinmeyin.

İyi günler.

Kaynak: http://codex.wordpress.org/Formatting_Date_and_Time

Java’da Json / Pojo Object İşlemleri (Jackson Kütüphanesi)

Merhaba gencolar,

Hani okuduysanız geçenlerde web’in hayatımızın vazgeçilmezi oldugunu ve tüm projelerimizde web’e bulaştığımızı söylemiştik. Bu abi de bizim rest alışverişlerimizden sonra obje çevirimleri için kullandığımız kütüphane.

Üç değişik kullanım methodu var: Streaming API, Tree, POJO (Mapping). Bu ilk ikisine hiç girmeden direk üçüncü yani POJO/Json Mapping kullanımından yürüyeceğiz. Neden mi? çünkü böyle daha kolay. Ama yine de bir göz atın kendi kararınızı verin.

Maven repository linki:

http://mvnrepository.com/artifact/com.fasterxml.jackson.core

Tabii eğer jersey ile bir kullanım mevcut ise:

http://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-json-jackson/2.22.2

Şöyle bir json objemiz gelsin:

 
 
  1. { name :'sercan', age:29}

 

Ona karşılık şöyle bir pojo objemiz olsun:

 
 
  1. public class Person{
  2.     @JsonProperty("name")
  3.     private String name;
  4.     @JsonProperty("age")
  5.     private int age;
  6.    //..
  7.    //getters & setters
  8. }

 

Not: JsonProperty data donerken/gelirken ki değerleri. Örneğin pojo’daki değişken ismi ile değil de farklı bir şekilde dönmek istiyoruz. O zaman bunları kullanırız. JPA’deki column name gibi aslında.

ve bu şekilde json pojo’ya dönüşür:

 
 
  1. String jsonStr = "{ name:'sercan',age:29}";
  2. ObjectMapper mapper = new ObjectMapper();
  3. Person p = mapper.readValue(jsonStr, Person.class);

 

ve de(cuppala) şu şekilde de pojo json string’e çevrilir:

 
 
  1. Person p = new Person();
  2. p.setName("alican");
  3. p.setAge(12);
  4. ObjectMapper mapper = new ObjectMapper();
  5. String jsonStr = mapper.writeValueAsString(p);

 

Olay bu kadar. Genellikle hele de jersey ile api bağlantıları vs kuruyorsanız jackson kütüphanesi çok işinize yarayacak.

Ayrıca kaynak linkine bir göz atmanızı öneririm:
http://www.mkyong.com/java/how-to-convert-java-object-to-from-json-jackson/

Apache HttpClient ile Post ve Get Örneği

Merhaba,

Web artık hayatımızın kaçınılmazı oldu. Internet of things ile birlikte internete bağlı olmayan “kalem” bile kalmayacak gelecekte (tabii görürmüyüz bilemiyore!). Velhasıl kelam herhangi bir projemizin web’e bağlılığı açık bir ihtiyaç.

Apache HttpClient kütüphanesi de bu bağımlılığı gideren (yani gerekli lokasyonlara çağrı atan alan) java’daki ağabey.

Son versionu için repository’ler burada: http://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient/4.5.1

Maven:

 
 
  1. <dependency>
  2. <groupId>org.apache.httpcomponents</groupId>
  3. <artifactId>httpclient</artifactId>
  4. <version>4.5.1</version>
  5. </dependency>

 

Gradle:

 
 
  1. 'org.apache.httpcomponents:httpclient:4.5.1'

 

Neyse lafı fazla uzatmadan POST örneği ile başlayalım (Gerekli açıklamaları comment-line da yaptım):

 
 
  1. String url= "http://xxx.com";
  2. List<NameValuePair> params = ....;
  3. //Http'i client'ı olusturuyoruz
  4. HttpClient client = HttpClientBuilder.create().build();
  5. //HttpPost instance'ı alıyoruz
  6. //Zaten GET için de HttpGet alacağız
  7. //Hatta diğer request tipler için de ayrı sınıflar HttpXXX mevcut
  8. HttpPost post = new HttpPost(url);
  9. //Optiyonel olarak header ekliyoruz
  10. post.setHeader("User-Agent", "Mozilla/5.0");
  11. //Yine datamız var ekliyoruz, tabi bunun için datayı nasıl yollaycaksak header'a
  12. //o content type'ı ekliyoruz:
  13. post.setHeader("Content-Type", "application/x-www-form-urlencoded");
  14. post.setEntity(new UrlEncodedFormEntity(params));
  15. //Request'i execute ediyoruz
  16. HttpResponse response = client.execute(post);

 

Execution sonunda data HttpResponse tipinde gelir:
response.getEntity().getContent() deyince de inputstream döner burdan sonra ne yapacağınız size kalmış. Burada data’nın gönderme ihtiyacına göre değişecek kısım header’da content type ve data’nın wrap’lenmesi. Örneğin Json gönderdiğimizi varsayalım (burada json işlemleri için Jackson kütüphanesini kullanıyorum):

 
 
  1. //Header'da content type set edilir
  2. httpClient.addHeader("Content-Type", "application/json");
  3. //Datanın çevirimi için object mapper (jackson'dan)
  4. //String'e çeviriyoruz
  5. ObjectMapper mapper  = new ObjectMapper();
  6. StringEntity paramsInStringJson = new StringEntity(mapper.writeValueAsString(params));
  7. post.setEntity(paramsInStringJson);
  8. //Bu kadar

 

GET için de HttpPost yerine dediğim gibi HttpGet sınıfından instance alacaksınız

 
 
  1. HttpGet request = new HttpGet(url);
  2. //Yine header ile ilgili işlemler yapılabilir...
  3. //Ve request yapılır
  4. HttpResponse response = client.execute(request);

Hadi kolay gelsin

Ref: http://www.mkyong.com/java/apache-httpclient-examples/

Merhaba Gençler!

Web üzerinde çalışmayı iyi kötü bir 8 sene oldu sanırsam. Hayat her anlamda zorluğunu eminimki tüm programcılara da kod üzerinde gösteriyordur. Ben de bazen saçma sapan konularda bazen imkansızları arayışta ve bazen de çok komplike şeyler yapmaya çalışırken türlü türlü problemleri çözmeye uğraşıyorum. Uğraşıyorum, çözüyorum da bunlar bazen bookmark’ların ötesine gidemiyor. Ve zamanla unutulup gidiyor… Ayrıca bazen de insanın bildiğini saklamaması gerekiyor. Ki zaten bilgi paylaştıkça çoğalan bir şey. Dolayısıyla da sitemin 3. arayüzünü yaparken blog kurma ihtiyacını duydum. Sitem tamamiyle bitmedi hatta şuan yüzde 20’lerde blog kısmı da dahil ama yapmak uzun zaman alacağından bunun benim yazılarımın önüne geçmesini istemedim temasının herşeyiyle bana ait olduğu sistemimi kurdum.

Bundan sonra ufak tefek yazılarımla karşınızda olacağım.

Esen kalın