Wrażenia po wizycie na 4Developers

4 kwietnia odbyła się w Warszawie konferencja 4 Developers. Z racji że uczestniczyłem w niej w poprzednich latach postanowiłem że nie opuszczę jej także w tym roku.

Monika Konieczny – Gramification

Ciekawy temat, niezbyt ciekawe wykonanie. Wykład dotyczył kolejnej metody motywacji pracowników, w tym programistów. Metoda ta polega na wprowadzeniu elementów gry podczas realizacji projektu, tak aby pracownicy czerpali radość z pracy. Jako przykład podany zostało rozgrywanie jakiejś gry w określonych odstępach czasu przy czym warunki początkowe gracza zależą od jego tygodniowych wyników (np. liczba kulek do Paintboala rozdawanych w comiesięcznej rozgrywce zależy od części projektu jaki wykonał pracownik). Według prelentki powinno się jednak szybko z gier indywidualnych przechodzić do zespołowych. Ogólnie prezentację oceniam na dobry minus.

Greg Young – zapomnij o szczegółach liczy się efekt

O autorze tej prezentacji przeczytałem na blogu Sławka Sobótki. Początkowo sądziłem że Sławek przesadza z mówiąc że wysłuchanie Grega powoduje efekt "I know kung-fu". Jednak później się przekonałem, że ma rację. Prelekcja była skierowana głównie do programistów Java, dlatego skrytykował on na samym początku używanie potężnych Framewoków i bibliotek w projektach w których samo ich skonfigurowanie zajmuje więcej czasu niż wykonanie projektu, dodatkowo powodując całą masę dziwnych błędów. Jak to określił "Frameworks are DSLs to converting XML into exceptions". Przez dłuższy czas było precyzowane stwierdzenie że klasa rozwiązań powinna być dobrana do klasy problemu. Że zupełnie inne rozwiązania powinno się stosować pisząc pamiętnik dla młodszej siostry, a inne tworząc aplikację nadzorującą działanie układu chłodzącego rdzeń elektrowni atomowej. Greg poruszył też zagadnienie zaciągania "technologicznego długu" podczas pracy nad projektem. Zastosował on sformuowania "Dobry dług" i "Zły dług" zaczerpnięte z publikacji Roberta Kiyosaki. Jedna z wypowiedzi mówiła o zasadności (nie)stosowania testów jednostkowych w projektach które będą rozwijane nie dłużej niż 2-5 lat. Według programistów pisanie testów jest dobrym zachowaniem, jednak wg. kadry zarządzającej powoduje całą masę pracy, która kosztuje i zmniejsza dochodowość projektu. Greg bardzo trafniej porównał stosowanie Testów jednostkowych do wzięcia na kredyt Lamborgini. Dla osób dla których kluczowym elementem sukcesu jest wizerunek (np. gwiazdy sportu i filmowe, prezesi dużych przedsiębiorstw) jest to z pewnością inwestycja("Dobry dług"), jednak dla przeciętnego zjadacza chleba koszty kredytu i utrzymania samochodu tej klasy przewyższają zarówno potencjalny wzrost zarobków a nawet same zarobki("zły dług") i analogicznie stosowanie testów jednostkowych ma sens w systemie bankowym gdzie pomyłka w algorytmie naliczania odsetek może kosztować zarówno bank jak i producenta oprogramowania miliony złotych, jednak mija się z celem w aplikacji napisanej dla sklepu ze skarpetkami gdzie na upartego można w ciągu jednej nocy wprowadzić ponownie wszystkie wystawione i odebrane faktury z ostatniego miesiąca. Kolejną kwestią poruszoną przez Grega była kwestia wzorców projektowych i sensu ich stosowania. Skrytykował on podejście w którym na siłę próbuje się stosować wzorce projektowe (tylko po to by je stosować). Podkreślił jednak ich rolę w komunikacji między proramistami (podając jako przykład Fabrykę Fabryk Fabryk której opisanie bez znajomości wzorców zajęłoby więcej niż ten wpis na blogu).

Ciekawostką jest fakt iż Greg nie posiadał przygotowanej prezentacji także nawet nie włączył projektora.

Michał Gruchała – Architektura skalowalnych systemów Webowych

Prezentacja dotyczyła zagadnienia optymalizacji prostych serwisów poddawanych bardzo dużemu obciążeniu (rzędu kilku milionów wejść na godzinę). Z racji że nie zajmuje się tym zagadnieniem nie specjalnie słuchałem prelegenta.

Sławomir Sobótka – Nowe bardziej racjonalne podejście do warstw

Prezentacja Sławka dotyczyła CQRS (Command Query Responsibility Segregation) która jest rozwinięciem Domain Driven Design. Podejście to zaleca oddzielenie serwisu zwracającego dane od serwisu je zmieniającego, co w dużym uproszczeniu oznacza rezygnację ze zwracania jakiś wartości przez metody które dokonują jakichkolwiek zmian w składowanych danych, oraz rezygnację z modyfikowania danych w metodach dane zwracające. Sławek podobnie jak Greg zwracał uwagę na zastosowanie na dostosowanie rozwiązania do problemu oraz że Domain Driver Design ma zastosowanie głównie przy pisaniu złożonych aplikacji biznesowych, z resztą wyraźnie widać było że Sławek przesiąkł myślą techniczną Greg'a. Nie czuję się na siłach aby przekazać ideę całego wykładu ponieważ sam muszę ją zgłębić.

Stephan Hochdöerfer – DDD jeszcze raz ale inaczej

Podobnie jak pierwsza prelekcja Ciekawy temat nieciekawe wykonanie.

Greg Young – Uwolnij swoją domenę

Wykład ten można potraktować jako rozwinięcie wcześniejszego wykładu Grega i wykładu Sławka Sobótki. Podobnie jak w przykładzie prezentacji Sławka nie podejmuje się analizy tego wystąpienia.

Ogólnie konferencję oceniam bardzo dobrze, a napewno lepiej niż zeszłoroczną. Lepiej dobrano lokalizację (Hotel w Poznaniu śmierdział według mnie komuną, a budynek Wyższej Szkoły Menagerskiej w Warszawie nawet jeżeli nie był nowy to był bardzo dobrze odnowiony), do zalet należy też zaliczyć fakt iż w tym roku posiłek był wliczony w koszt konferencji, oraz że nie zabrakło przystawek. Do wad lokalizacji należy zaliczyć dużą odległość od centrum Warszawy oraz brak ścieżki .Net oraz przedstawicieli firm Adobe, Microsoft i Oracle (może i byli ale ich widać nie było).

Komunikacja PHP i C Sharp

Ostatnio musiałem napisać aplikację w której aplikacja .Net współpracuje z aplikacją napisaną w PHP za pośrednictwem gołego http.

Sprawa nie jest szczególnie złożona jednak jak się później okazało nie jest taka prosta jak myślałem. Fragmenty kodu zawarte w Blogu pochodzą z przykładowego programu dostępnego do pobrania na końcu postu.

To co było proste:

Komunikacja PHP z dowolną inną platformą odbywa się za pomocą protokołu HTTP który jest ogólnie dobrze opisany np. tutaj. Dodatkowo w większości platform istnieją dedykowane biblioteki obsługujące w mniejszym lub większym stopniu komunikację przez http, a biblioteka standardowa .Net nie jest pod tym względem wyjątkiem(tfu!).

Komunikację za pomocą http ułatwiają przynajmniej dwie klasy HttpWebRequest i WebClient. Klasa WebClient ma w moim mniemaniu znacznie bardziej intuicyjny interfejs i to jej będę się starał używać.

Wywołanie GET:

Wywołanie GET jest odwołaniem się do strony www równoważnym z wpisaniem adresu w przeglądarce internetowej, parametry do serwera wysyła się dopisując po adresie strony znak zapytania i wartości tych parametrów w formacie parametr1=wartosc1&parametr2=wartosc2&parametr3=wartosc3. Ograniczeniem tej metody jest fakt iż długość adresu strony wraz z wartościami parametrów nie może przekraczać 256znaków.

Metody tej nie powinno się także stosować na stronach w celu przekazywania danych wrażliwych (jak np. login i hasło użytkownika).

Do zbudowania łańcucha połączeniowego można użyć klasy UriBuilder, jest to klasa która na podstawie fragmentów adresu potrafi złożyć pełną ścieżkę do pliku/katalogu także na serwerze www.

Klasą która się przydaje jest także HttpUtility posiadająca metodę UrlEncode zwalniającą programistę z wstawiania odpowiednich znaków ucieczki w wysyłanym zapytaniu do serwera, niestety klasa ta znajduje się w przestrzeni nazw System.Web w bibliotece o tej samej nazwie, biblioteka ta zaś nie znajduje się w Wersji Client Profile platformy .Net. Tak więc jeżeli klasy tej chcemy użyć w projekcie Windows Forms lub WPF musimy dokonać następujących zmian w projekcie:

1) Zapisanie wszystkich niezapisanych plików.

2) W Solution Explorze klikamy prawym przyciskiem myszy na projekt i wybieramy Properties

3) W zakładce Application znajdujemy Combobox Target platform i wybieramy w nim .Net Framework 4.0

4) Zapisujemy wszysto i zamykamy właściwości projektu

5) Klikamy prawym przyciskiem w katalog refereces w projekcie i wybieramy AddReference

6) Przechodzimy na zakładkę .Net i wybieramy System.Web

7) Zatwierdzamy dialog i możemy używać klasy System.Web.HttpUtility

Najprostszy kod pobierający treść strony www może wyglądać tak.

WebClient client = new WebClient();
// Przy niektórych serwerach aplikacja powinna się przedstawić 
//client.Headers.Add("user-agent", "PHP and dotNet");

UriBuilder queryBuilder = new UriBuilder(tbServer.Text + "helloGet.php");
queryBuilder.Query = string.Format("name={0}", HttpUtility.UrlEncode(tbName.Text));

string result = client.DownloadString(queryBuilder.Uri);
MessageBox.Show(this, result);

plik helloGet.php zawiera chyba najprostrzą możliwą obsługę zapytania GET:

<?php
echo 'Witaj '.$_GET['name'];
?>

Pobranie plików z serwera:

Zadanie trochę mniej związane z PHP, aczkolwiek pojawiające się w pracy z http to pobranie pliku z serwera http. Tutaj klasa WebClient oferuje dwie możliwości.

Metoda DownloadFile podaje się adres pliku i nazwę pliku nic prostszego

using (SaveFileDialog sfd = new SaveFileDialog())
{
    sfd.Filter = "Pliki jpeg|*.jpg";
    if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        WebClient client = new WebClient();
        client.DownloadFile("http://www.programy.witraze-plocin.pl/wp-content/uploads/2010/04/cycki226jo5.jpg", sfd.FileName);
    }
}

Metoda DownloadData zwraca tablicę bajtów składających się na plik metoda może być przydatna gdy pobrane dane chcemy tylko obrobić bez zapisywania ich na dysku. Np. wyświetlić obrazek w pictureBoxie.

WebClient client = new WebClient();
byte[] buffer = client.DownloadData("http://www.programy.witraze-plocin.pl/wp-content/uploads/2010/04/cycki226jo5.jpg");

MemoryStream stream = new MemoryStream(buffer);

// Kwestie techniczne
if (pbCycki.Image != null)
{
    Image oldImage = pbCycki.Image;
    pbCycki.Image = null;
    oldImage.Dispose();
}

pbCycki.Image = Image.FromStream(stream);

Wadą tej metody jest spore zajęcie pamięci ponieważ cały plik jest wgrywany do RAMu, dodatkowo aplikacja musi pobrać cały plik i dopiero wtedy go obrobić. Przypadkiem gdy żadna z powyższych metod nie zastosowania jest pobranie dużego pliku z serwera i zapisanie go do strumienia nie będącego plikiem(np. wysłać portem COM do jakiegoś urządzenia). W tym przypadku należy wykorzystać metodę WebRequest.

private void FromHttpToStream(Stream outStream, string url)
{
    WebClient client = new WebClient();
    client.OpenRead(url).CopyTo(outStream);
}

Wywołanie POST:

Wysłanie zapytania POST do serwera http przy pomocy klasy HttpClient jest dosyć niskopoziomowe. Ponieważ należy wykonać następujące kroki:

1) Dodać do nagłówka wiadomości informację o rodzaju przesyłanej treści "content-type: application/x-www-form-urlencoded"

2) Sformatować treść wysyłanej wiadomości przy UrlEncode

3) Zakodować informację przy pomocy kodowania ASCII i wysłać na serwer.

4) Rozkodować przesłaną wiadomość przy pomocy kodowania użytego na stronie.

WebClient client = new WebClient();
// Przy niektórych serwerach aplikacja powinna się przedstawić 
//client.Headers.Add("user-agent", "PHP and dotNet");

client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");

byte[] buffer = Encoding.ASCII.GetBytes(string.Format("name={0}", HttpUtility.UrlEncode(tbName.Text)));
buffer = client.UploadData(tbServer.Text + "helloPost.php", "POST", buffer);
string result = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
MessageBox.Show(this, result);

Upload plików:

Upload plików też jest dość prosty klasa WebClient posiada metodę UploadFile, która jako parametry pobiera nazwę wysyłanego pliku i adres pliku do którego wysyłana jest zawartość pliku.

using (OpenFileDialog ofd = new OpenFileDialog())
{
    ofd.Filter = "Pliki jpeg|*jpg";
    if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        WebClient client = new WebClient();
        client.UploadFile(tbServer.Text + "uploadFile.php", ofd.FileName);
    }
}

Po stronie serwera wysyłany plik jest opisany w tabeli $_FILES w polu 'file' obsługa pliku. Przykładowa obsługa może wyglądać następująco:

<?php
$tempFile = $_FILES['file']['tmp_name'];
copy($tempFile, 'uploaded.jpg');
unlink($tempFile);
?>

Pliki z kodem przykładowego programu są do pobrania tutaj.

Google web elements

Przegladajac sieć natknąłem się na stronę Google Web page elements. Zachwyciła mnie łatwość ich zastosowania (wystarczy kilka linijek HTML + JS). Stwierdziłem że u siebie na stronie wykorzystam 2 z nich.

1) Translate pozwala skorzystać z serwisu Google Translate do przetłumaczenia zawartości strony. Sam na 100% nie będę nigdy podejmował prób przetłumaczenia materiałów tutaj prezentowanych, zwłaszcza że nie mam wejść z innych krajów niż Polska. A tłumaczenie zapewnione przez google może nie jest idealne ale wystarcza do zrozumienia sensu treści.

2) Virtual Keyboard pozwala na obsługę strony bez konieczności korzystania z fizycznej klawiatury (przy pomocy myszki lub ekranu dotykowego). Minusem tego gadżetu jest fakt że na Ubuntu pod Chrome rozwinięta klawiatura blokuje wpisywanie polskich znaków (pojawiają się ich polskie odpowiedniki) jednak zwinięcie klawiatury usuwa tą niedogodność.

Zastosowanie LambdaEqualityComparer

Jakiś czas temu Maciej Aniserowicz na swoim blogu opublikował kod klasy LambdaEqualityComarer

Bardzo często chcielibyśmy aby równość elementów jakiejś klasy zależała tylko od jakiegoś pola (najczęściej od ID), aby to osiągnąć należy nadpisać metody GetHashcode i Equals tej klasy jest to jednak praca dosyć żmudna i powtarzalna, dodatkowo okazuje się że kod ten należy często pisać ponieważ wiele klas wywołuje te metody w celu porównania elementów, przykładem takich klas są kontrolki WPF zawierające Listy jak np. ComboBox. Aby pracę tą trochę skrócić można użyć następującego fragmentu kodu:

    class User
    {
        /// <summary>
        /// Identyfikator
        /// </summary>
        public int ID { get; set; }

        // Inne pola

        private LambdaEqualityComparer<User, int> _comparer = new LambdaEqualityComparer<User, int>(u => u.ID);

        public override int GetHashCode()
        {
            return _comparer.GetHashCode(this);
        }

        public override bool Equals(object obj)
        {
            return _comparer.Equals(this, obj as User);
        }
    }

Aby ten kod działał w 100% poprawnie należy jeszcze zmodyfikować kod Maćka tak aby uwzględniała fakt iż któryś z parametrów metody Equals może być nullem.

class LambdaEqualityComparer<T, TValue> : IEqualityComparer<T> 
        { 
            #region Private members 
            private Func<T, TValue> converter; 
            #endregion Private members 

            #region Constructors 
            public LambdaEqualityComparer(Func<T, TValue> converter) 
            { 
                this.converter = converter; 
            } 
            #endregion Constructors 

            #region IEqualityComparer<T> Members 
            public bool Equals(T x, T y) 
            { 
                if (x == null && y == null)
                {
                      return true;
                 } 
                  else if (x == null || y == null)
                 {
                         return false;
                 }
                  else
                  {
                         return converter(x).Equals(converter(y)); 
                  }
            }
            public int GetHashCode(T obj) 
            {
                if (obj == null)
               {
                       return 0;
                }
                else
                { 
                        return converter(obj).GetHashCode(); 
                 }
            }
            #endregion 
        } 

Na koniec dodam że implementację tą można rozbudować zgodnie z wzorcem dekoratora, co daje nam możliwość wykorzystania istniejących implementacji IEqualityComparer'a.

class LambdaEqualityComparer<T, TValue> : IEqualityComparer<T> 
        { 
            #region Private members 
            private Func<T, TValue> converter; 
            private IEqualityComparer<TValue> realComparer;
            #endregion Private members 

            #region Constructors 
           public LambdaEqualityComparer(Func<T, TValue> converter) : this(converter, null) { }

            public LambdaEqualityComparer(Func<T, TValue> converter, IEqualityComarer<TValue> realComparer) 
            { 
                this.converter = converter; 
                this.realComparer = realComparer; 
            } 
            #endregion Constructors 

            #region IEqualityComparer<T> Members 
            public bool Equals(T x, T y) 
            { 
                if (x == null && y == null)
                {
                      return true;
                 } 
                  else if (x == null || y == null)
                 {
                         return false;
                 }
                  else
                  {
                         if  (realComparer == null)
                         {
                                  return converter(x).Equals(converter(y)); 
                          }
                          else
                          {
                                   return realComparer.Equals(converter(x), converter(y));
                           }
                  }
            }
            public int GetHashCode(T obj) 
            {
                if (obj == null)
               {
                       return 0;
                }
                else
                {
                       if (realComparer == null)
                       { 
                              return converter(obj).GetHashCode(); 
                        }
                         else
                         {
                                  return realComparer.GetHashCode(converter(obj));
                          }
                 }
            }
            #endregion 
        } 

Niestandardowe generowanie kluczy głównych w NHibernate

Robiąc ostatnio mały projekt postanowiłem w końcu wykorzystać NHibernate, na temat tego ORM'a jest w necie masa informacji więc nie będę się na jego temat rozpisywał. Podobnie nie będę opisywał konfiguratora Fluent NHibernate, który jest znacznie bardziej intuicyjny niż tradycyjne pliki hbm (oparte na XML).

Problemem przed jakim stanąłem był fakt że moja aplikacja miała współpracować z bazą na której działała inna aplikacja, posiadająca dosyć charakterystyczną politykę generowania kluczy głównych. Która nijak nie pasowała do żadnej standardowej polityki zawartej standardowo w Bibliotece hibernate'a.

Na szczęście NHibernate jest przygotowany na taką ewentualność, jego API zawiera interfejs IIDentifierGenerator, który umożliwia pisanie własnych polityk generowania kluczy. Interfejs ten zawiera jedną metodę Generate.

public class MyGenerator:IIDentiferGenerator
{
    public object Generate(ISessionImplementor session, Object obj)
   {
        return Guid.NewGuid()
    }
}

Pierwszy parametr zapewnia dostęp do obiektów związanych z konfiguracją połączenia z bazą danych oraz stanem tego połączenia, drugim parametrem jest obiekt dla którego generowany jest klucz. Przykład powyżej dla każdego obiektu zwróci wartość statycznej metody NewGuid która nadaje się na klucz główny ponieważ z definicji jest to Global Unique Identifier. Jednak generowanie nowego Guida nie jest niczym nadzwyczajnym po za tym biblioteka NHibernate'a już ją zawiera.

Częstym sposobem generowania kluczy głównych jest procedura składowana (lub fragment zapytania SQL) która zwraca najniższą nieużywaną wartość którą aplikacja może wykorzystać jako klucz dla danej tabeli.

Aby uruchomić takie zapytanie pod kontrolą NHibernate należy wykorzystać właściwości Connection i Batcher parametru session przekazanego do metody generate.

public class MyGenerator:IIDentiferGenerator
{
    public object Generate(ISessionImplementor session, Object obj)
   {
        using(IDbCommand command = session.Connection.GenerateCommand())
        {
            command.CommandText = "exec GetNextId()";
            using(IDataReader  reader = session.Batcher.ExecuteReader())
            {
                if (reader.Read())
                {
                     return reader.GetInt(0);
                }
                else
                {
                      throw new Exception ("Nie da się wygenerować klucza.");
                }
            }
        }
    }
}

Klasa przedstawiona powyżej jedną poważną wadę: jest bardzo sztywna tj. trudno byłoby ją wykorzystać do generowania klucza dla dowolnej encji. Informację na temat tabeli w której należy sprawdzać "Zajętość" danego identyfikatora można uzyskać tylko przez weryfikację typu parametru obj metody Generate. Wykorzystanie tego faktu sprawiłoby to że programista musiałby dla każdego obsługiwanego typu dopisywać kilka linijek kodu w tej metodzie co generowałoby masę powtarzalnego kodu.

Znacznie lepszym rozwiązaniem jest Implementacja Interfejsu IConfigurable zawierającego metodę Configure która przyjmuje jako parametry następujące wartości:

type – typ dla jakiego jest konfigurowana klasa

params – Słownik klucz <=> wartość przechowujący dodatkowe dane konfiguracyjne.

d – informacja na temat silnika bazodanowego przechowującego dane.

Parametrem nas interesującym jest params zawiera on dane na temat tabeli w której zapisywany jest dany typ oraz kolumny będącej kluczem głównym. Nie jestem pewien czy dla każdego silnika bazodanowego nazwy kluczy są takie same dlatego nie podaję tutaj konkretnego fragmentu kodu.

Pozostaje jeszcze tylko kwestia jak powiedzieć NHibernate'owi żeby dla danej klasy używał naszego generatora. We FluentHibernate wymaga to wpisania jednej linijki w klasie Mapującej:

Id(obj => obj.ID).GeneratedBy().Custom<MyGenerator>()

Edit: 02-10-2010

Podczas prac w NHibernate zauważyłem że zachowuje się on dosyć specyficznie podczas zapisu więcej niż jednego obiektu jedego typu w ciągu jednej tranzakcji. Otóż najpierw generuje on klucze główne dla wszystkich nowych obiektów, a następnie dokonuje ich zapisu. Jeżeli teraz oprzemy nasz mechanizm zapisu na zapytaniu do bazy to musimy się liczyć z faktem iż za każdym razem generowanie klucza będzie się odbywać na niezmienionej bazie. Więc zapytanie wywołane za każdym razem zwróci tą samą wartość. Należy to uwzględnić w kodzie, najczęściej przez duplikację części logiki zapytania, co jest oczywistą wadą tego rozwiązania, zaletą jego zaś jest mniejsza ilość zapytań co zazwyczaj poprawia aplikację pod względem wydajnosciowym.

PHP Cast patern

Podczas prac w PHP(z którego na szczęście rzadko korzystam) bardzo wkurza mnie fakt iż nie jestem w stanie zdefiniować (przy pomocy PHP Doca) iż jakaś funkcja zwraca tablicę elementów określonego typu. Przez co chcąc prze-iterować po wyniku takiej funkcji mam lekko utrudniony dostęp do pól i metod danej klasy (uzupełnianie kodu Eclipse nie radzi sobie z taką sytuacją zupełnie). Dlatego jakiś czas temu wpadłem na pomysł dodawania do każdej (a właściwie prawie każdej) nowej klasy statycznej metody Cast:

class Moja {

	/**
	 * 
	 * Pseudorzutowanie na typ
	 * @param mixed $item
	 * @return Moja
	 */
	public static function Cast($item) {
		return $item;
	}

	public function JakasFunkcja() {
		;
	}	
}

Teraz mogę sobie w kodzie spokojnie zwracać wyniku typu tablicowego i iterować po nim:

pictures/Code.jpg

W tej chwili jedyną wadą tego rozwiązania jaka mi przychodzi do głowy jest dodatkowa ilość generowanego kodu.

Nowa wersja bloga

Korzystając z okazji przymusowego nieróbstwa związanego ze zwolnieniem lekarskim postanowiłem dokończyć pracę nad Blogiem.

A że nie chciało mi się bawić z instalacją Flex Builder'a na Ubuntu przepisałem całość na "gołego" PHP'a.

W czasie prac nad nowym wyglądem postanowiłem zastąpić swoją bardzo niedoskonałą Captchę zabawką ze stajni Googla noszącym nazwę "ReCaptchia". Jest to rozwiązanie darmowe, bardzo skuteczne(większość udanych masowych łamań tego systemu opiera się na pracy Hindusów) , posiada wsparcie dla osób niewidomych, a na dodatek (jak mi się zdawało) jest łatwe w instalacji na każdej stronie.

O ile z wyświetleniem dialogu nie było większych problemów:

echo recaptcha_get_html($publicKey);

O tyle weryfikacja poprawności wprowadzonego obrazka okazała się znacznie trudniejsza ponieważ standardowe wywołanie:

$resp = recaptcha_check_answer ($privateKey,
                                $_SERVER["REMOTE_ADDR"],
                                $_POST["recaptcha_challenge_field"],
                                $_POST["recaptcha_response_field"]);
if($resp->is_valid)
{
    //TODO: Obsługa zapisu
} else {
    // TODO: Wyświetlić komunikat o błędzie
}

Sprawiała że moja strona kończyła się komunikatem 'Could not open socket'.

Po dłuższym śledztwie okazało się że komunikat ten pojawia się wielu użytkownikom a błąd z nim związany jest nierozwiązany od dwóch lat link.

Jest on bardzo często spowodowany blokowaniem przez firmy hostujące połączeń wychodzących na port 80. Ponieważ walka z OVH o otwarcie portu wydała mi się nie do wygrania (Błąd fsockopen Connection refused jest dość częsty na stronach hostowanych przez tą firmę – wystarczy wpisać ten komunikat w google) postanowiłem poszukać obejścia, którym okazały się być serwery Proxy, które przekierowują połączenie z jednego portu na inny port innego serwera.

Recaptchia nie wspiera Proxy na szczęście biblioteka PHP która zapewnia dostęp do Recaptchia jest w pełni Open Source'owa więc otworzyłem ją sobie i zmieniłem metodę _recaptcha_http_post dodając jej parametry $proxyHost = null i $proxyPort = null do których można przekazać adres i port serwera proxy. Oczywiście musiałem zmodyfikować też ciało funkcji dodając kod modyfikujący treść zapytania HTTP, oraz musiałem zmodyfikować funkcję recaptcha_check_answer tak aby mogła przekazać informacje na temat proxy do ww. metody.

Ostatecznie kod sprawdzający poprawność tekstu z obrazka wygląda następująco:

$resp = recaptcha_check_answer ($privateKey,
                                $_SERVER["REMOTE_ADDR"],
                                $_POST["recaptcha_challenge_field"],
                                $_POST["recaptcha_response_field"],
                                array(),
                                '190.90.128.233', #adres serwera proxy
                                8080);

Moje zmiany w bibliotece są wysłane do googla jako załącznik do Zadania nr 80 w projekcie Recaptcha mam nadzieję że zostaną włączone do oficjalnej wersji tej biblioteki.