EpicWEB.plwebdesign, programowanie, phat lewt!

o autorze / projekty / kontakt

Bezpieczeństwo / CSS / Epic Loot / Fsck Up / GFX / Google / Gry / Hosting / HTML / JS MySQL / OS / Other / PHP / Praca / Techblog / Web

Ostatni projekt: ddrpl.com

PHP - (r)ewolucja od Facebook'a?

Facebook to jeden z projektów, którego zwolennicy PHP używają często jako kontrargumentu, na to, że w PHP nie da się zrobić nic poważnego. Ci, którzy znają prawdę trochę lepiej wiedzą, że PHP to tylko jeden z wielu języków programowania, których FB używa do zasilania licznych front- i back-endów w swoim portalu.

Na blogu SDTimes pojawiła się informacja głosząca, iż Facebook zamierza przedstawić i upublicznić na zasadach Open Source swoją własną wersję runtime PHP - autor spekuluje jakoby programiści (a dokładniej jedna osoba zatrudniona przez FB) mieli przygotować własną wersję parsera, która zdecydowanie polepsza wydajność.

Aktualizując swój wpis, w oparciu o komentarze innych użytkowników, Alex wskazuje także, że nowe rozwiązanie to być może kompilator działający na zasadzie JIT (just-in-time compilation) - rozwiązanie które może zapewnić wzrost prędkości ale i bezpieczeństwo typów (type safe), które często wskazywane jest jako jedna z większych wad PHP.

Czymkolwiek nie okazało by się nowe rozwiązanie - miejmy nadzieję, że uda się je wprowadzić do PHP Core (co da większe szanse zaistnienia na popularnych platformach hostingowych) i nie ograniczy się do wprowadzenia nowych funkcji z prefixem fb_ ;-)

3 komentarze | PHP, Techblog 31 stycznia / 14:44:14
URL Trackback

CRON na Home.pl

Home.pl jest jednym z nielicznych, popularnych, usługodawców, którzy udostępniają usługę CRON. No, prawie - jest to ich implementacja, która polega na okresowym wywoływaniu plików PHP, Perla, Pythona lub CGI - dzięki temu możemy zaimplementować np. automatycznie rozsyłający się newsletter etc.

W swojej dokumentacji Home.pl stwierdza:

Środowisko uruchomieniowe skryptów jest identyczne ze środowiskiem, w jakim uruchamiane są skrypty na serwerach wirtualnych. W praktyce wywołanie takie niewiele różni się od zwykłego wywołania GET po protokole HTTP.

Dla mojej małej główki równa się to z czymś takim:

telnet mojadomena.pl 80
GET /cron-hourly.php HTTP/1.1
Host: mojadomena.pl
Connection: Close

Co się jednak okazuje? Panowie z Home poszli na łatwiznę i nawet o tym nie wspomnieli - otóż CRON po prostu skanuje FTP w poszukiwaniu odpowiednich plików i wywołuje je przez interpreter CLI.

Efekt? Zmienna $_SERVER['HTTP_HOST'] ma złą wartość - zamiast mojadomena.pl znajdziemy tam home.pl - i tak, dowiedziałem się o tym "the hard way" kiedy nagle dostałem maila, który zamiast z adresu info@mojadomena.pl przyszedł z info@home.pl

Update - okazuje się, że problemem nie jest zła informacja w HTTP_HOST, ale jej .. brak. A w tym wypadku Home sam dodaje swoją domenę do adresu. Poniżej przykład wysyłanych informacji (wysłane print_r($_SERVER) na mój adres (zamieniłem login serwera na "LOGIN" w celu ochrony prywatności):

To: btm@anfo.pl
Subject: Cron na home
From: info@home.pl

Array
(
    [QUERY_STRING] => 
    [REQUEST_METHOD] => GET
    [REMOTE_HOST] => localhost
    [REMOTE_ADDR] => 127.0.0.1
    [DOCUMENT_ROOT] => /
    [SERVER_SOFTWARE] => IdeaCron
    [SERVER_PROTOCOL] => HTTP/1.0
    [GATEWAY_INTERFACE] => CGI/1.1
    [PATH] => /bin
    [TMP] => /tmp
    [TMPDIR] => /tmp
    [SERVER_ID] => LOGIN@home
    [SERVER_NAME] => LOGIN.home.pl
    [SERVER_ADMIN] => LOGIN@home.pl
    [SCRIPT_NAME] => /cron-5min.php
    [SCRIPT_FILENAME] => /cron-5min.php
    [REQUEST_URI] => /cron-5min.php
    [PATH_INFO] => /cron-5min.php
    [PATH_TRANSLATED] => /cron-5min.php
    [PHP_SELF] => /cron-5min.php
    [REQUEST_TIME] => 1253531102
    [argv] => Array
        (
        )

    [argc] => 0
)
13 komentarzy | CSS, Hosting, PHP, Techblog 21 września / 11:10:03
URL Trackback

include() i require() a open_basedir

Ostatnio spędziłem parę godzin szukając, dlaczego nie mogę includwać plików w swoich serwisach - okazało się, że winny jest open_basedir, który ustawiony jest dla konkretnej ścieżki, i include() (oraz inne funkcja otwierające pliki) tam właśnie zaczynają szukać plików, które mają wczytać - i nie przeszukują podkatalogów.

Popularne, i wszędzie sugerowane rozwiązanie:

<?
#a.php
include('podkatalog/b.php');

#podkatalog/b.php
include('plik.php');

Gdzie plik.php znajduje się w tym samym katalogu co a.php mimo, że wydaje się logiczne i w większości wypadków działa, może okazać się, że dzięki open_basedir przestało.

Rozwiązania?

a) wyłączyć dyrektywę open_basedir w php.ini (i masie podrzędnych, jeżeli masz dużo hostów virtualnych)
b) podawać ścieżki względne lub bezwzględne do dołączanych plików, np. tak:

<?
include('../plik.php');

# albo lepiej

include(dirname(__FILE__)  . '/../plik.php');
Dodaj komentarz | PHP 28 czerwca / 16:59:40
URL Trackback

Wycinanie znaków regionalnych oraz specjalnych ze zmiennej

Czas temu napisałem skrypt, który konwertował tytuły postów na linki SEO-friendly, czyli wywalał pliterki i zamieniał na bez-ogonkowe odpowiedniki, wywalał znaki specjalne etc. Niestety, poległ na czymś wklejonym z Worda. Nowa wersja jest nieco krótsza i wygląda tak:

It’s How Everyone Builds It zażółć gęślą jaźń !@#$%^&*()_+=-[]{}\\|;:\'<>?,./
<?
$string = iconv('UTF-8', 'UTF-8//IGNORE', $string);
$string = iconv('UTF-8', 'ASCII//TRANSLIT', $string);
$string = str_replace(' ', '-', trim(preg_replace('/([^a-zA-Z\s])/Us','', $string)));
echo $string;

W wyniku da nam ładne

Its-How-Everyone-Builds-It-zazolc-gesla-jazn-zazolc
Dodaj komentarz | PHP 28 czerwca / 16:56:07
URL Trackback

Fałszowanie mime-type przy wgrywaniu plików na serwer

Większość z programistów zdaje sobie sprawę z tego, jakim zagrożeniem może być błędnie napisany skrypt pozwalający na upload plików na serwer. Dlatego, większość ogranicza możliwość wgrywania plików przez odpowiednie filtrowanie na podstawie mime-type np. w taki sposób:

<?php
if($_FILES["type"] == "image/jpeg") {
// logika wgrywania dla plików JPEG
}
else {
echo "Nie można wgrać pliku takiego typu - tylko JPEG!";
}

Wszystko ładnie - wiemy, że wgrywany plik, jest de-facto plikiem jpeg.

Na pewno?

Otóż nie - dane, które znajdujemy w tablicy _FILES (name, size, type) są danymi, które podała nam przeglądarka podczas wysyłania pliku! Nie daje to nam w żaden sposób pewności, że złośliwy użytkownik nie zmienił swojej przeglądarki / nie użył innego programu który wysłał nam plik.php przy okazji dodając, że jest to format image/jpeg !

Jeżeli nie zabezpieczymy się przed tego typu zagraniami otwieramy serwer na wszelkiego rodzaju ataki. Zostają dwa rozwiązania, które najlepiej stosować jednocześnie

  1. sprawdź rozszerzenie pliku - rozszerzenie wyciągamy w następujący sposób:
    <?
    $ext = substr($file, strrpos($file,".") + 1);
    
  2. upewnij się, że plik rzeczywiście jest typu image/jpeg - np. przy użyciu funkcji getimagesize która jako wynik zwraca tablicę asocjacyjną - pod kluczem mime znajduje się typ pliku odczytany już na naszym serwerze
Dodaj komentarz | PHP 28 czerwca / 16:46:53
URL Trackback

Optymalizacja wyszukiwania

Ostatnio przyszło mi pisać dwa serwisy od podstaw (znaczy: nie używając firmowych CMS-ów) więc miałem wolną rękę w kwestii zastosowanych skryptów, wzorców projektowych etc. Jako że strony docelowo mają mieć dużą odpowiedzialność postanowiłem przyłożyć się do optymalizacji.

Wprowadzenie

Najczęściej używaną funkcją, która może jednocześnie najbardziej obciążać bazę jest wyszukiwanie. Produkty wyszukiwane są pod kątem wielu parametrów przechowywanych w znormalizowanej bazie danych. Na pewno ułatwia to utrzymanie porządku, ale generuje też skomplikowane (zarówno pod wzg. długości jak i obciążenia bazy) zapytania SQL. Dlatego postanowiłem zastosować pewien prosty ale jakże pomocny trick.

Wyszukiwanie + zapis w bazie

Po wysłaniu przez danych użytkownika wyszukujemy wszystkie produkty spełniające kryteria wyszukiwania. W ten sposób w jednym zbiorze mamy wszystkie produkty (a prawdę mówiąc wystarczą nam ID produktów) oraz ich ilość. To wszystko (wraz z parametrami wyszukiwania o ile tego potrzebujemy) zapisujemy do bazy danych (np. stosując serializację) i zwracamy sobie ID wyszukiwania.

Kontynuacja wyszukiwania

Posiadając już ID elementów, które spełniają nasze kryteria wyszukiwania możemy w prosty sposób wyświetlić te, które nas interesują - do dzielenia na strony możemy użyć np. array_slice, następnie wybrać z bazy tylko te wyniki (w celu odpowiedniego posortowania można użyć funkcji FIELD opisywanej wcześnie).

Poza oczywistym zyskiem nie przeszukiwania n tabel za każdym razem kiedy osoba wyszukująca zmieni stronę możemy używać prostszych adresów URI - zamiast ?szukaj=KL5000&producent=asus&cena[od]=1000&cena[do]=3000&cpu=&gpu=&strona=5 możemy utworzyć adresy typu ?szukaj=12&strona=5

Dodaj komentarz | MySQL, PHP 28 czerwca / 16:36:24
URL Trackback

XCache - cachowanie zmiennych

W poprzednim wpisie poruszyłem temat wykorzystania XCache jako rozszerzenia do cachowania danych w PHP. Temat przechowywania wartości zmiennych był tam tylko lekko poruszony, jednak ostatnio sytuacja zmusiła mnie do ponownego rozpatrzenia sprawy.

Parę dni temu odbyły się polskie eliminacje do Eurowizji - jako, że strona eurowizja.com.pl, która utrzymywana jest na jednym z moich serwerów, w ciągu dwóch dni (dzień eliminacji i dzień po) wygenerowała 4 razy więcej ruchu niż w całym styczniu, serwer zaczął ledwo zipać. Postanowiłem zakasać rękawy i użyć cachowania zmiennych do przechowywania często pobieranych, ale nie często modyfikowanych informacji.

Konfiguracja PHP

Pierwsze co musimy zrobić, to zmodyfikować ponownie nasz pliki php.ini aktywując cachowanie zmiennych:

xcache.var_size = 4M
xcache.var_count = 1
xcache.var_slots = 8K
xcache.var_ttl = 3600
xcache.var_maxttl = 0
xcache.var_gc_interval = 300

Większość zmiennych konfiguracyjnych jest bardzo podobna do omawianych w poprzednim artykule, więc w razie wątpliwości odsyłam do owego wpisu. Pojawiają się właściwie dwie nowe:

xcache.var_maxttl - określa maksymalny czas życia zmiennej, jaki możemy ustawić podczas jej definiowania
xcache.var_gc_interval - określa częstotliwość (w sekundach) z jaką powinien być uruchamiany Garbage Collector, którego zadaniem jest usuwanie nie używanych danych z pamięci

Nowe funkcje z rozszerzenia XCache

Kiedy już mamy wszystko poprawnie skonfigurowane (i zresetowaliśmy serwer www!) możemy przystąpić do przerabiania naszej aplikacji. Przydadzą nam się teraz 4-ry funckje dodane przez XCache do PHP:

bool xcache_isset(string name)
bool xcache_unset(string name)
int xcache_inc(string name [, int value [, int ttl]])
int xcache_dec(string name [, int value [, int ttl]])

Przy ich pomocy możemy odpowiednio zmodyfikować nasze skrypty tak, by dane, które są nam potrzebne ale trudne do zdobycia (wymagają dużego nakładu na SQL, znajdują się na innym serwerze etc.) były zawsze pod ręką.

Przykład użycia

Poniżej zamieszczam przykładowy kod, który pobiera dane z bazy SQL tylko wtedy, kiedy ich potrzebuje.

<?
// dołącz nagłówki, połączenia z bazą etc.
if(!xcache_isset('xcache_data')) { // 1
$data = pobierzBardzoWazneDaneZSQL();
xcache_set('xcache_data', $data, 3600); // 2
}
else $data = xcache_get('xcache_data'); // 3

Krótkie wyjaśnienie

  1. Sprawdzamy, czy XCache przechowuje jakieś zmienne w swojej prywatnej zmiennej xcache_data
  2. Zapisujemy nasze informacje w zmiennej xcache_data i zastrzegamy, że dane te mają być uważane za aktualne przez najbliższe 3600 sekund (godzinę)
  3. Jeżeli jednak dane są przechowywane w pamięci podręczne, pobieramy je z niej

Należy zwrócić uwagę na dwie rzeczy:

  1. Nazwa zmiennej, w jakiej my przechowujemy dane nie jest nazwą zmiennej, pod jaką są one przechowywane w xcache!
  2. Ponieważ możmy zapisać dane pod dowolną nazwą w xcache polecam stosowanie prefixów, tak by przypadkiem nie nadpisać sobie danych w innym ppliku / skrypcie. Ja osobiście używam prefixu będącego wynikiem md5() ze stałej __FILE__ co powoduje prawie pewną unikalność nazw zmiennych

Usuwanie danych z cache

W naszym skrypcie zdecydowaliśmy, że dane mają być przechowywane w xache przez minutę (patrz pkt. 1) - oc jeżeli dane zostaną przez nas umyślnie zmodyfikowane (np. aktualność zostanie zaktualizowana, ktoś zagłosuje w sondzie)? Do pomocy mamy dwa mechanizmy:

  1. Możemy zalogować się do XCache Admin (więcej na ten temat w poprzednim wpisie)
  2. Możemy użyć funkcji xcache_unset do usunięcia zmiennej z cache

Podsumowanie

Po zastosowaniu XCache na wspomnianej stronie Eurowizji stabilność serwera uległa zdecydowanemu polepszeniu - ilość wątków Apache i MySQL spadła o połowę, load wrócił do normy, a ja nie zobaczyłem już więcej wiadomości “strona nie działa!” od klienta :-)

Dodaj komentarz | PHP 28 czerwca / 16:32:40
URL Trackback

XCache - wprowadzenie do mechanizmu cache w PHP

Cache (pamięć podręczna) to mechanizm, w którym ostatnio pobierane dane dostępne ze źródła o wysokiej latencji i niższej przepustowości są przechowywane w pamięci o lepszych parametrach.

Wiedząc już czym będziemy się zajmować przejdźmy do możliwości.

XCache pozwala nam na cachowanie 2 typów danych:

  • zmienne oraz obiekty - w każdej chwili możemy zapisać oraz odczytać z cache wartość zmiennej czy też typ złożony (tablica, obiekt)
  • skompilowane pliki PHP - każdy plik PHP przed wykonaniem przechodzi przez fazę kompilacji, w któej zamieniany jest na kod maszynowy - XCache pozwala nam pa pominięcie tego kroku i przechowuje w pamięci już skompilowany kod

Instalacja na platformie Linux

Jako, że mój serwer korzysta z SuSE Linux skupię się na instalacji dla tego właśnie środowiska - instalacja na innych platformach powinna być bardzo podobna.

Proces instalacji zaczynamy od pobrania paczki bądź źródła. Zakładam, drogi czytelniku, że wiesz jak kompiluje się programy więc nie będę opisywał tego procesu ;-)

Kiedy już mamy pliki u siebie odnajdujemy plik xcache.so i kopiujemy go do folderu, zawierającego rozszerzenia dla PHP - u mnie jest to /usr/lib/php5/extensions/

Następnie, dodajemy zawartość pliku xcache.ini do naszego php.ini, można to robić komendą:

cat xcache.ini >> /sciezka/do/php.ini

Co spowoduje dopisanie danych na koniec. W tym momencie możemy już zrestartować nasz serwer i cieszyć się z nowego rozszerzenia. Proponuję jednak doczytać kolejne akapity i skonfigurować XCache wg. naszych potrzeb.

Cachowanie zmiennych

Do pierwszego zastosowania służą nam głównie 2 funkcje:

bool xcache_set(string name, mixed value [, int ttl]) - pozwala na zapisanie zdefiniowanej zmiennej pod nazwą name nadając jej wartość value, dodatkowo, możemy podać 3-ci parametr ttl określający ilość sekund, po których wartość powinna zostać usunięta z cache

mixed xcache_get(string name) - funkcja zwracająca dane uprzednio zapisane w cache

Dodatkowo możemy korzystać jeszcze z funkcji nie wymagających dokładniejszego wyjaśnienia czyli:

bool xcache_isset(string name)
bool xcache_unset(string name)
int xcache_inc(string name [, int value [, int ttl]])
int xcache_dec(string name [, int value [, int ttl]])

Dwie ostatnie funkcje odpowiednio zwiększają oraz zmniejszają wartość zapisanej zmiennej.

Cachowanie plików PHP

Do drugiego zastosowania nie są potrzebne nam praktycznie żadne funkcje. Po zainstalowaniu i skonfigurowaniu (uwaga - domyślnie cachowanie tego typu jest wyłączone!) rozszerzenie samo zadba o wypełnianie oraz usuwanie cache w razie potrzeby. Sposób działania możemy modyfikować poprzez następujące dyrektywy konfiguracyjne:

xcache.size - określa całkowitą ilość pamięci, którą przeznaczamy na cache
xcache.count - ile oddzielnych jednostek / wątków cache utworzyć (autorzy sugerują, by liczba ta była równa ilości procesorów/rdzeni)
xcache.slots - minimalna wielkość slotu przeznaczona na jeden plik - każdy plik może zajmować więcej niż jeden slot, ale żaden nie może zająć mniej
xcache.ttl - ilość sekund, przez ile dany plik może być maksymalnie przechowywany w cache

U mnie wartości te wyglądają następująco:

xcache.size = 64M
xcache.count = 4
xcache.slots = 8K
xcache.ttl = 3600

Średnie wykorzystanie wynosi 60-70%, a ilość zapchań (clogs - są to sytuacje, w których następuje żądanie pliku, który powinien już znaleźć się w cache, ale wciąż trwa jego kompilacja) nie przekracza 10 na jednostkę (czyli około 0.1%)

Administracja - XCache Admin

W folderze z XCache znajdziemy też katalog admin zawierający prosty panel administracyjny pozwalający na przeglądanie statystyk, nazw przetrzymywanych plików oraz zmiennych (w wypadku tych ostatnich - także na ich modyfikację!) Zanim skorzystamy z panelu administracyjnego, musimy zmienić jeszcze kilka rzeczy w php.ini, a mianowicie:

[xcache.admin]
xcache.admin.enable_auth = On
xcache.admin.user = “login_administratora”
xcache.admin.pass = “tu_wstaw_wynik_md5(twoje_haslo)”

Po takim zabiegu musimy jeszcze tylko zresetować serwer.

Kilka słów na zakończenie

Pliki cachowane są do momentu osiągnięcia lokalnego bądź też globalnego ttl lub do chwili, gdy nastąpi jakakolwiek zmiana w ich źródle.

Należy zwrócić uwagę, że wszelkie zmienne cachowane są globalnie. Co to znaczy? Każda zcachowana zmienna dostępna jest dla dowolnego wątku PHP - oznacza to, że jeżeli użytkownik A zapisze jakąś zmienną do cache, użytkownik B może ją bez problemu odczytać (dotyczy to tylko zmiennych! pliki nie są cachowane w ten sposób) używając xcache_get! Dodatkowo, jeżeli dwa różne skrypty (lub nawet dwa takie same skrypty - np. gotowe fora internetowe) używają cache będą one dzielić wszystkie dane! By uniknąć tego typu działania sugeruję użyć prostego wrappera do funkcji, który przez zapisaniem lub odczytaniem zmiennej o danej nazwie odpowiednio ją zmodyfikuje - np.:

<?
function myCache_set($name, $value, $ttl = null) {
$salt = "moj_prywatny_skrypt_";
return xcache_set($salt.$name, $value, $ttl);
}

Dodaj komentarz | PHP 28 czerwca / 16:28:31
URL Trackback