„Why I wouldn’t use rails for new company”

Chociaż mój zawodowy świat to głównie JEE, to uważam, że warto choć trochę orientować się, co się dzieje w konkurencyjnych środowiskach. Jared to użytkownik Railsów niemal od momentu ich pojawienia się, a jego opinia jest tym bardziej cenna, że przedstawia spojrzenie doświadczonego dewelopera na stan tego środowiska na tle współczesnej konurencji. Warto też zapoznać się z komentarzami pod jego wpisem. Niektórzy, delikatnie mówiąc, się z nim nie zgadzają.

Java i problemy z połączeniem SSL

Niedawno napotkałem problem z podłączeniem serwera WildFly do serwera poczty przy użyciu połączenia szyfrowanego. Próba takiego połączenia kończyła się zgłoszeniem wyjątku:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Szybko okazało się, że źródłem problemu jest fakt, iż certyfikat zamieszczony na serwerze pocztowym jest niewiarygodny (self-signed). Mechanizmy bezpieczeństwa JRE nie są w stanie potwierdzić autentyczności certyfikatu i dlatego zgłaszają wyjątek.

Oczywiście da się ten problem obejść, choć jest to nieco uciążliwe. Żeby JRE uznało certyfikat za wiarygodny, trzeba go zaimportować do tzw. magazynu JRE. W tym celu najpierw trzeba pozyskać plik certyfikatu. W moim przypadku najprościej było go pobrać z programu pocztowego (Postbox, komercyjny klon Thunderbirda). Plik certyfikatu zapisałem na dysku w formacie DER, a następnie użyłem narzędzia keytool, standardowo rozprowadzanego w ramach pakietu JRE.

Oto przykładowe polecenie importujące plik certyfikat.cer znajdujący się w katalogu domowym użytkownika. Pierwszy przykład dotyczy systemu OS X. Należy je wykonać z uprawnieniami administratora.

keytool -import -alias myprivateroot \
    -keystore $(/usr/libexec/java_home)/bin\lib\security\cacerts \
    -file ~/certyfikat.cer

A tutaj wersja dla linuksa:

keytool -import -alias myprivateroot \
    -keystore /usr/java/default/jre/lib/security/cacerts \
    -file ~/certyfikat.cer

Po takim zabiegu następna próba połączenia z serwerem pocztowym po SSL powinna się udać bez problemu. Dodam jeszcze tylko, że powyższa recepta rozwiązuje nie tylko problem w przypadku połączeń z serwerem pocztowym. Zadziała również w przypadku serwera HTTP oraz innych połączeń SSL wykorzystujących certyfikaty, których autentyczności JRE nie jest w stanie zweryfikować.

Uciążliwość tego rozwiązania polega na tym, że po każdej aktualizacji JRE trzeba pamiętać o ponownym zaimportowaniu certyfikatu do magazynu.

JavaMail API i rozpoznawanie zwrotek

Wprowadzenie

Wysyłanie e-maili to funkcja, bez której większość współczesnych systemów informatycznych nie mogłoby się obejść. Chyba wszystkie liczące się środowiska programistyczne oferują mechanizmy ułatwiające realizację takiej funkcjonalności. W przypadku języka Java jest to specyfikacja JavaMail API (JSR 919). Nie będzie chyba zbytnią przesadą stwierdzenie, że w sieci można znaleźć tysiące artykułów pokazujących, jak wysyłać i odbierać wiadomości przy użyciu JavaMail, dzięki czemu implementacja tych funkcji we własnym systemie zwykle nie sprawia problemów. Istnieje jednak pewien aspekt, którego próżno szukać we wspomnianych artykułach i tego właśnie dotyczy niniejszy artykuł. Chodzi o metodę rozpoznawania faktu, czy wysłany mail doszedł, czy nie.

Wiem, wiem, nie ma niezawodnej metody realizacji takiego zadania. Nigdy nie będziemy mieć gwarancji, że wysłany przez nas e-mail doszedł do adresata, a tym bardziej że został przez niego przeczytany. W pewnych okolicznościach możemy mieć jednak pewność, że e-mail nie doszedł, a mianowicie wtedy, kiedy dostaniemy tak zwaną zwrotkę (bounce message), czyli automatyczną odpowiedź od serwera docelowego (lub czasami naszego) o niemożności dostarczenia przesyłki.

Jak rozpoznać zwrotkę?

Rozpoznanie takiej zwrotki bywa problematyczne, nie ma na to prostej metody. Serwery pocztowe stosują różne formaty przy wysyłaniu tego rodzaju komunikatów, tak więc analiza tytułu czy treści wiadomości będzie mało wiarygodna. Dodatkowo, jeżeli na dany adres oprócz zwrotek przychodzi dużo innych wiadomości, taka analiza może być bardzo kosztowna pod względem wydajności. Optymalnie byłoby, gdyby zwrotki przychodziły na specjalnie w tym celu wydzielony adres. Na szczęście jest na to sposób.

Jak wiadomo, każda wiadomość e-mail zawiera pola From: i To:. W pierwszym z nich znajduje się adres nadawcy, a w drugim – adres odbiorcy. Nie wszyscy jednak wiedzą, że w transmisji SMTP wykorzystywane jest jeszcze jedno pole (a w zasadzie polecenie) zawierające adres nadawcy, tzw. Envelope from. To pierwsze pole From: jest przeznaczone dla programów klienckich (czyli programów pocztowych na naszych komputerach), natomiast to drugie, Envelope From, jest używane w trakcie transmisji pomiędzy agentami pocztowymi, czyli serwerami i stanowi część protokołu SMTP1. To właśnie Envelope From mówi serwerowi, gdzie ma wysłać ewentualną zwrotkę. Zwykle oba pola From zawierają ten sam adres nadawcy, jednak nie musi tak być. Jeżeli w polu Envelope From ustawimy jakiś inny, specjalnie przygotowany adres, to tam właśnie będą wysyłane zwrotki. Jeżeli taki specjalny adres będziemy używać tylko w tym jednym celu, to będziemy mogli mieć niemal pewność, że wszystkie wiadomości, które tam dochodzą, to zwrotki.

Pole „Envelope From” w JavaMail API

Specyfikacja JavaMail API umożliwia programistom ustawienie pola Envelope From. Służy do tego metoda SMTPMessage.setEnvelopeFrom(String from). Klasa SMTPMessage jest implementacją klasy MimeMessage. Mimo że nie należy do oficjalnego API, z dokumentacji wynika, że można jej śmiało używać w miejsce standardowej MimeMessage.

Poniżej przykład zaczerpnięty z dokumentacji JavaMail API:

    Properties props = new Properties();
    props.put("mail.smtp.host", "my-mail-server");
    Session session = Session.getInstance(props, null);

    try {
        SMTPMessage msg = new SMTPMessage(session);
        msg.setEnvelopeFrom("return-address@example.com");
        msg.setFrom("me@example.com");
        msg.setRecipients(Message.RecipientType.TO,
                          "you@example.com");
        msg.setSubject("JavaMail hello world example");
        msg.setSentDate(new Date());
        msg.setText("Hello, world!\n");
        Transport.send(msg, "me@example.com", "my-password");
    } catch (MessagingException mex) {
        System.out.println("send failed, exception: " + mex);
    }

Dla kompletności informacji dodam, że istnieje drugi sposób ustawienia tej wartości, ale mniej elastyczny. Powyższy sposób pozwala ustawić inny adres dla każdej wysłanej wiadomości. Ten, który przedstawiłem niżej, ustawia ten sam adres dla wszystkich wiadomości wysłanych w ramach jednej sesji.

    Properties props = new Properties();
    props.put("mail.smtp.host", "my-mail-server");
    props.put("mail.smtp.from", "return-address@example.com");
    Session session = Session.getInstance(props, null);

    try {
        MimeMessage msg = new MimeMessage(session);
        msg.setFrom("me@example.com");
        msg.setRecipients(Message.RecipientType.TO,
                          "you@example.com");
        msg.setSubject("JavaMail hello world example");
        msg.setSentDate(new Date());
        msg.setText("Hello, world!\n");
        Transport.send(msg, "me@example.com", "my-password");
    } catch (MessagingException mex) {
        System.out.println("send failed, exception: " + mex);
    }

Finał

Problem rozpoznawania zwrotek mamy w ten sposób załatwiony. Pozostaje jeszcze jeden nietrywialny problem: dopasowania zwrotki do oryginalnej wiadomości. To jednak temat na inny artykuł.


  1. Jak już wspomniałem, jest to nie tyle pole, co polecenie protokołu SMTP. Serwer nadawcy wysyła je do serwera docelowego niedługo po nawiązaniu połączenia. Polecenie to wygląda tak:
    MAIL FROM: <return-address@example.com>
Workflow

Workflow – konwersja emaila na zdarzenie

Mam taki zwyczaj, że rachunki płacę na ostatnią chwilę (ale jeszcze w terminie). Wychodzę z założenia, że lepiej, gdy pieniądze leżą na moim koncie, niż na cudzym. Ponieważ faktury przychodzą najczęściej z kilkutygodniowym wyprzedzeniem, wyrobiłem sobie nawyk dodawania zdarzeń do kalendarza, w których zapisuję sobie komu, kiedy i ile mam zapłacić. Jako że są to faktury w formie elektronicznej, aż się prosi, żeby ten proces nieco zautomatyzować. Poniżej przedstawiam workflow, który sobie w tym celu obmyśliłem.

Workflow - ekran główny
Workflow – ekran główny

Cały misterny mechanizm opiera się na genialnej aplikacji Workflow. Jest to bardzo pomysłowy program dla systemu iOS, pozwalający w naprawdę łatwy sposób konstruować akcje automatyzujące często wykonywane czynności. Można przetwarzać tekst, generować PDF-y, obrabiać obrazki, sterować muzyką, wysyłać wiadomości, zarządzać kalendarzem i robić naprawdę wiele, wiele innych rzeczy. Gotowe akcje możemy w postaci ikonki umieścić na pulpicie albo dodać do listy rozszerzeń akcji (action extensions) i wywoływać z innych aplikacji. Workflow współpracuje z wieloma aplikacjami niezależnych deweloperów, w tym Dropbox, Evernote, Fantastical, Overcast Day One, Editorial, że wymienię tylko kilka. W bibliotece Workflow znajduje się sporo akcji przygotowanych przez autorów programu i gotowych do wykorzystania.

Mój schemat postępowania jest następujący i, jak widać, niezbyt skomplikowany.

  1. W programie pocztowym, z wiadomości z fakturą do zapłaty wyciągam informacje o terminie płatności oraz numerze faktury.
  2. W kalendarzu dodaję zdarzenie, w którym opisuję ile, kiedy i za co mam zapłacić.

Na starcie pojawił się pewien problem. Okazało się, że systemowa aplikacja Mail z jakichś powodów nie obsługuje rozszerzeń akcji. Sprawdziłem jeszcze dwa inne programy pocztowe, Mailbox oraz Microsoft Outlook, i w obu była ta sama sytuacja: brak obsługi rozszerzeń akcji. Ostatecznie zdecydowałem się  na niezbyt eleganckie obejście: skopiowanie zawartości maila do schowka, a następnie uruchomienie akcji ręcznie. Żeby nie szukać akcji po pulpitach, można użyć Spotlighta. Mam nadzieję, że doczekamy się obsługi rozszerzeń akcji w Mailu i takie zabiegi nie będą w przyszłości konieczne.

Workflow - screenshot 2

Uporawszy się z problemem przystąpiłem do konstruowania akcji w aplikacji Workflow. Krok pierwszy to pobranie terminu płatności. W tym celu użyłem operacji Get clipboard. Pobiera ona dane ze schowka, a następnie przekazuje je następnej operacji, którą w moim przypadku jest wyszukanie tekstu przy użyciu wyrażeń regularnych (Match Text). W treści maila szukam daty (czyli tekstu pasującego do szablonu 0000-00-00). W moim przypadku zostały odnalezione dwie daty (data wystawienia faktury oraz termin płatności). Interesująca jest tylko ta druga. Żeby ją wybrać i zapamiętać, użyłem kolejno operacji Get item from list oraz Set Variable. Zmienną nazwałem TerminPlatnosci. W ten sposób zrealizowałem pierwszy, najtrudniejszy krok: wydobyłem z treści wiadomości a następnie zapamiętałem interesującą mnie informację, tutaj: termin płatności.

Workflow - screenshot 3

Następne kroki są bardzo podobne do pierwszego, więc nie ma sensu ich szczegółowo opisywać. Sprowadzają się do wykonania sekwencji operacji: 1. pobierz tekst ze schowka, 2. odnajdź tekst pasujący do zadanego ciągu znaków i 3. zapamiętaj go w zmiennej. W ten sposób odczytałem kwotę należności oraz numer faktury.

W ostatnim kroku wszystkie zapamiętane w zmiennych informacje połączyłem i dodałem do kalendarza. Najpierw skonstruowałem tytuł zdarzenia. Ponieważ do obsługi kalendarza używam aplikacji Fantastical 2, miałem nieco ułatwione zadanie. Wykorzystałem wygodną cechę tej aplikacji: jeżeli w tytule zdarzenia znajdzie ona zaszytą datę, to automatycznie używa jej jako daty samego zdarzenia. Żeby skonstruować taki tytuł użyłem operacji Text, której wynik przekazałem do operacji Add Event via Fantastical. Dodatkowo, w polu Notes wpisałem informację o kwocie oraz numerze faktury. Użyłem do tego zmiennych utworzonych w poprzednich krokach.

Workflow - screenshot 3

I to wszystko. Akcja gotowa. Możemy ją dodać do któregoś pulpitu, żeby łatwiej było ją uruchamiać. Jeżeli tak zrobimy, będzie ją można łatwo odnaleźć przy użyciu funkcji Spotlight. Użycie akcji sprowadza się do wykonania następujących czynności: w programie pocztowym wybieram interesującą mnie wiadomość z fakturą, zaznaczam jej treść (przytrzymać palcem, a następnie wybrać zaznacz wszystko) i kopiuję do schowka. Teraz trzeba wyjść z aplikacji Mail i uruchomić naszą akcję. Po jej zakończeniu na ekranie ukaże się okno aplikacji Fantastical 2 z prośbą o potwierdzenie dodania zdarzenia do kalendarza.

Jak widać, zbudowanie własnej akcji nie jest wcale trudne. Potrafi nie tylko przynieść sporo satysfakcji, ale później znacznie ułatwia codzienne czynności.