Jeździłem Bentleyem… wśród odkurzaczy

Gościłem prezentację odkurzacza urządzenia do filtracji powietrza i sprzątania Rainbow. Nigdy nie byłem na prezentacji kosmicznych garnków ani cudownych kołder, więc byłem ciekawy jak to wygląda. Spodziewałem się, że będzie to trwać godzinę do półtorej. Tymczasem pranie/odkurzanie mózgu trwało… trzy godziny, a prowadzący powiedział, że to krótko, bo zazwyczaj trwa ok. pięciu. W tym czasie dowiedziałem się, że mokry kurz nie lata, że w kanapie mam dwa kilo złuszczonego naskórka, że śpiąc krócej wstaję świeższy niż po długim śnie, bo wdycham mniej roztoczy, a kilka wilgotnych szmatek symulujących moje płuca zostało zatkanych syfem wyrzucanym przez zwykłe odkurzacze. Efektem ubocznym prezentacji było odkurzenie połowy salonu i dwóch psich dywaników.

 Rainbow E2

Żarty na bok. Samo urządzenie sprawia dobre wrażenie. Przede wszystkim znakomicie robi to co ma robić odkurzacz czyli ssie jak filmografia Milli Jovovich. Przy tym jest cichszy niż mój w miarę nowy Samsung, żre mniej prądu, a wylanie wody z pojemnika jest łatwiejsze i bardziej higieniczne niż opróżnienie worka. Jest solidnie zbudowany, co daje nadzieję, że faktycznie będzie działać te 20 lat. Jednocześnie ma podobne gabaryty co typowy odkurzacz i jest od niego niewiele cięższy. Ponadto podoba mi się, że jest produkowany w USA, a nie Chinach. A to wszystko za jedyne 9000 zł.

No właśnie, taka cena czyni Rainbowa Bentleyem wśród odkurzaczy. Super samochód, super osiągi, ale płaci się przede wszystkim za elitarność i prestiż marki, który spływa na nabywcę. W wypadku samochodu to działa. Snobować się odkurzaczem? Nie.

PS. Jakby ktoś chciał sprawdzić to na własnej skórze to chętnie polecę was akwizytorowi, a ja dostanę nawilżacz i jonizator powietrza RainMate warty 700 zł!

Żegnaj T-Mobile

Przedwczoraj pożegnałem się z T-Mobile. Z jednej strony nic wielkiego, telefonia komórkowa jest teraz usługą tak podstawową i powszechną jak higiena jamy ustnej, a nie rozwodziłbym się nad zmianą pasty do zębów. Z drugiej strony abonament w Erze, a później T-Mobile, miałem od ponad 11 lat. To dłużej niż używam jakiejkolwiek marki pasty do zebów, to nawet dłużej niż jestem z moją dziewyczną, a obecnie żoną. ;-) Zapewne dlatego tak po ludzku trochę mi żal, że T-Mobile rozstał się ze mną bez mrugnięcia okiem, jakby te 11 lat z hakiem nic dla niego nie znaczyło. Ani przed końcem umowy ani po złożeniu wniosku o przeniesienie numeru nie skontaktował się ze mną i nie zaproponował przedłużenia umowy, o próbie przebicie oferty konkurencji nawet nie wspominając. Nie dostałem nawet SMS-a „szkoda, że odszedłeś, ale mam nadzieję, że wrócisz”. Żal mi, bo wydaje mi się, że byłem dobrym klientem, od ponad 11 lat płaciłem abonament, niby niezbyt wysoki, ale w dobie malejącej liczby abonentów i ostrej konkurencji w segmencie usług przedpłaconych ponoć każdy regularnie płacący łoś jest cenny.

Jednocześnie odczuwam ulgę, bo już od dłuższego czasu związek z T-Mobile mi się nie układał. Przy każdym przedłużeniu czułem się jak klient drugiej kategorii, bo pomimo wieloletniego stażu (a właściwie przez ten wieloletni staż) dawano mi gorsze warunki niż nowym klientom (tak, wiem, że tak działa dużo firm). Nie mogliśmy znaleźć porozumienia co do wzajemnych oczekiwań – ja miałem niewielkie potrzeby i chciałem niewiele płacić, T-Mobile konsekwentnie kasował tanie taryfy i usilnie chciał mi wcisnąć usługi, których nie potrzebuję i słono za nie kasować. W związku z tym szczególnie nie spodobało mi się skasowanie programu Era Premia, dzięki któremu „za darmo” mogłem nieco dopasować taryfę do moich potrzeb. Irytowało mnie poczucie humoru T-Mobile przejawiające się przykładowo w taryfie multimedialnej dla smartfonów z pakietem 100 MB transferu. Dużą kroplą w czarze goryczy była żenująca reklama „najlepsi przechodzą do T-Mobile”.

Zatem żegnaj T-Mobile, tak chyba będzie lepiej dla nas obojga.

Squid zmienia WSDL?

Z używaniem SOAP-a przez proxy w PHP bywają problemy, o czym już pisałem. Dzisiaj w podobnej sytuacji natknąłem się na inną zagwostkę. Podczas pisania integracji z usługą eraty.pl prosty kod z instrukcji:

$client = new \SoapClient('https://www.eraty.pl:8443/eRatyWSService/eRatyWS?wsdl');
$identyfikacja['login'] = 'ERatyTest';
$identyfikacja['haslo'] = 'ERatyTest!1';
$identyfikacja['numerSklepu'] = 13010005;
$identyfikacja['typProduktu'] = 0;
$identyfikacja['wariantSklepu'] = 1;
try
{
	$result = $client->pobierzLinieKredytowe($identyfikacja);
}
catch (\Exception $e)
{
	echo $e->faultstring;
}

rzucał mi wyjątkiem „Could not connect to host”. Oczywiście zarówno firewall i proxy zezwałały na dostęp do eraty.pl, a ten sam kod uruchomiony na innym serwerze działał bez zająknięcia. Nad znalezieniem rozwiązania trochę musiałem posiedzieć, aż w końcu w stack trace’ie zauważyłem, że metoda SoapClient::__doRequest() jako drugi parametr dostaje http://www.eraty.pl:8080/eRatyWSService/eRatyWS (hint: numer portu). Nie mam pojęcia skąd to się bierze, zakładam, że to coś linii PHP-Squid, ale z tą wiedzą rozwiązanie było proste. Wystarczyło wymusić poprawną lokalizację usługi przy tworzeniu obiektu SoapClient:

$client = new \SoapClient('https://www.eraty.pl:8443/eRatyWSService/eRatyWS?wsdl',
	array('location' => 'https://www.eraty.pl:8443/eRatyWSService/eRatyWS')
	);

Małe a cieszy: sp_executesql

Aplikacja, nad którą obecnie pracuję, dostaje od użytkowników zapytania SQL, które wykonuje na wielu bazach danych MS SQL. Zapytania mogą być najróżniejszego typu, od prostych SELECTów po złożone skrypty w Transact SQL-u. Wykonywanie takich złożonych zapytań przez PDO i odczytywanie wyników mogłoby być kłoptliwe, analiza zapytań i rozbijanie ich na pojedyncze kwerendy jeszcze bardziej. Bólu głowy mozna sobie oszczędzić w bardzo prosty sposób – z pomoca przychodzi sp_executesql. Nasze złożone zapytania możemy wykonać poprzez tę funkcję przerzucając rozwiązanie wspomnianych problemów na bazę danych.

$statement = $dbh->prepare("EXEC sp_executesql N" . $dbh->quote($query));
$statement->execute($params);
$rows = $statement->fetchAll();

Wywoływanie funkcji z nieznaną liczbą parametrów

Niektóre funkcje PHP mogą akceptować zmienną liczbę parametrów. To wygodne rozwiązanie, ale co zrobić jeśli nie mamy z góry ustalonej liczby parametrów, a funkcja nie ma swojego odpowiednika przyjmującego parametry w tablicy?

Załóżmy, że chcemy wyciągnąć wspólne wartości z kilku tablic. Można to zrobić używając funkcji array_intersect(). Jeżeli liczba tablic na wejściu nie jest znana możemy przetwarzanie je w pętli. Dużo prościej będzie jednak posłużyć się funkcją call_user_func_array().

$result = call_user_func_array('array_intersect', $arrayOfArrays);

W zmiennej $arrayOfArrays oczywiście znajdują się tablice, z których chcemy wyciągnąć wspólne wartości.

Wadą call_user_func_array() jest narzut czasowy, wywoływanie funkcji w ten sposób jest wyraźnie wolniejsze niż bezpośrednio. W krytycznej pętli chyba warto by się pokusić o switcha, który wywołuje funkcję z odpowiednią ilością argumentów.

PDO dblib a parametr charset

Dzisiaj zmagałem się z kodowaniem znaków w bazach danych Microsoft SQL. Sytuacja wyglądała następująco: PHP na Linuksie, aplikacja w UTF-8, na innym serwerze kilkadziesiąt baz danych Microsoft SQL, a żeby było zabawniej bazy używały kilku kodowań znaków.

Do baz łączyłem się przez PDO ze sterownikiem dblib. Pomysł z konwertowaniem kodowania w aplikacji szybko odrzuciłem, zamiast tego zainteresowałem się parametrem charset. W dokumentacji nie jest jasno opisany, ale łatwo można się domyślić, że informuje sterownik o oczekiwanym kodowaniu.

$dbh = new \PDO('dblib:charset=UTF-8;host=10.12.34.56;dbname=nazwa_bazy', $dbUser, $dbPassword);

Niestety to nie działało. Rozwiązaniem okazała się zmiana konfiguracji FreeTDS w pliku /etc/freetds/freetds.conf.

tds version = 7.0

Ta zmiana wymusza połaczenia z użyciem wersji 7.0 protokołu. Wedle dokumentacji dopiero w tej wersji działa parametr client charset.

Serwer na Debianie: nginx, PHP i bazy danych

Przedstawiam mój przepis szybkie postawienie serwera, na którym działa nginx, PHP i bazy danych. Ja z powodzeniem używam takiej konfiguracji na maszynie deweloperskiej, ale można jej też użyć jako punktu wyjścia do konfiguracji serwera produkcyjnego, jednak w tym wypadku radzę uważnie przeczytać zastrzeżenia na końcu.

W przepisie używam Debiana Squeeze, ale po dodaniu sudo przed większością poleceń będzie go można zastosować do Ubuntu. ;-)

Opisywana konfiguracja spełnia następujące założenia:

  • Na serwerze mają działać nginx, PHP, MySQL, PostgreSQL, MongoDB i memcached.
  • Oprogramowanie powinno być w możliwie aktualnych stabilnych wersjach.
  • Skrypty PHP mają działać z prawami użytkownika.
  • Serwer powinien mieć możliwość uruchamiania skryptów PHP zabezpieczonych ionCube’em.

Czytaj dalej „Serwer na Debianie: nginx, PHP i bazy danych”

Błąd w ADOdb CacheFlush()

Przy okazji przeprowadzki anime.com.pl na nowy serwer zaktualizowałem wykorzystywane biblioteki, w tym ADOdb. W trakcie testów odkryłem, że coś jest nie tak z obsługą cache, a konkretnie czyszczenie zapamiętanych wyników przez CacheFlush() nie działało. Po analizie kodu ADOdb odkryłem, że pierwszą rzeczą, którą robi CacheFlush() jest sprawdzenie czy $ADODB_CACHE jest ustawione:

global $ADODB_CACHE_DIR, $ADODB_CACHE;	
if (empty($ADODB_CACHE)) return false;

Problem polega na tym, że $ADODB_CACHE jest inicjalizowane tylko w metodzie CacheExecute() czyli innymi słowy, żeby skorzystać z CacheFlush() trzeba najpierw wywołać CacheExecute(). Moim zdaniem to błąd (wcześniej chyba tak nie było), zgłosiłem go i mam nadzieję, że zostanie poprawiony. W międzyczasie pozostaje poprawienie ADOdb na własną rękę lub wywoływanie CacheExecute() przed CacheFlush().

su: Uwierzytelnienie nie powiodło się

Na jednym VPS-ie z Debianem 6 nie mogłem przejść na konto roota korzystając z polecenia su. Próba jego wykonania kończyła się tak:

joe@dejiko:~$ su -
Hasło:
su: Uwierzytelnienie nie powiodło się

W /var/log/auth.log pojawiały się takie wpisy:

Feb 21 10:48:30 dejiko unix_chkpwd[4123]: check pass; user unknown
Feb 21 10:48:30 dejiko unix_chkpwd[4123]: password check failed for user (root)
Feb 21 10:48:30 dejiko su[4122]: pam_unix(su:auth): authentication failure; logname=joe uid=1000 euid=1000 tty=/dev/pts/1 ruser=joe rhost=  user=root

Początkowo myślałem, że problem tkwi w tym, że użytkownik joe nie należy do grupy sudoers, ale ten trop okazał się mylny. W wyniku śledztwa okazało się, że winne są uprawnienia pliku /bin/su. Na działającym systemie wyglądały one tak:

-rwsr-xr-x 1 root root 34024 2011-02-15  /bin/su

zaś na problematycznej maszynie tak:

-rwxr-xr-x 1 root root 34024 2011-02-15  /bin/su

Innymi słowy brakowało bitu „set UID”, więc problem rozwiązałem poleceniem:

chmod u+s /bin/su

Na inkryminowanym systemie ten sam problem dotyczył polecenia passwd, objawy były następujące:

joe@dejiko:~$ passwd
Zmienianie hasła dla joe.
(obecne) hasło UNIX:
passwd: Błąd podczas modyfikowania tokenu uwierzytelniania
passwd: password unchanged

Tu również pomógł chmod.

Lighttpd na kilku portach

Ostatnio przenosiłem serwer Icecast2 na osobną maszynkę i, co ważniejsze, na osobną domenę. Ponieważ trochę słuchaczy miało zapamiętane URL-e do radia chciałem ich bezboleśnie przekierować na nowy adres. W tym celu przygotowałem taką regułkę w Lighttpd:

$SERVER["socket"] == ":8000" {
	url.redirect = ("^/(.*)" => "http://stream.anime.com.pl:8000/$1")
}

Po zatrzymaniu Icecasta i zrestartowaniu Lighttpd wszystkie żądania HTTP wysyłane na port 8000 (na którm działał Icecast) są przekierowywane na nowy serwer. Dzięki temu słuchacze wciąż mogą używać starych URL-i.

Jednocześnie ten przykład pokazuje jak skłonić Lighty do nasłuchiwania na kilku portach jednocześnie. Można to wykorzystać np. po to, żeby uruchomić testową wersję serwisu na porcie 81 obok wersji produkcyjnej działającej na domyślnym porcie 80.

PS. Icecast2 ma funkcję przenoszenia słuchaczy, ale z tego co zrozumiałem działa ona tylko w obrębie jednego serwera. Czyli można ich przenieść na inny strumień na tym samym serwerze, ale na inny serwer już nie.