Spring, MockMvc i @Scope("session")

To jeden z tych postów, które powstały, żeby nikt mi nie zarzucił, że blog nie żyje. Bo przecież postuję przynajmniej raz w roku! A poza tym może się komuś przyda.

Historia jakich wiele, stworzyliśmy sobie kontroler w Springu, a w tym kontrolerze jest użyty bean.

@RestController
@Scope(value="session")
public class SomeController {
@Autowired private StateBean state; @RequestMapping("/state") public String getState() { return state.name + " " + state.value; } }

Ale to nie jest taki zwykły bean. Ten nasz ma session scope. Dla niespringujących tłumaczę, beany z takim scope są powiązane z sesją użytkownika. To znaczy każdy użytkownik, który będzie korzystał z naszego serwisu będzie miał na wstępie przydzielony swój własny bean niczym opiekuna na wycieczce po Korei Północnej i przy każdej jego akcji będzie wykorzystywana ta sama instancja beana.

A tak wygląda kod tej nieszczęsnej fasolki:

@Component
@Scope(value="session")
public class StateBean {
public String name = "default"; public int value = 0; }

To teraz może testy (zaraz ktoś wyciągnie widły z napisem TDD, że testy powinny być najpierw, takim czytelnikom polecam czytać fragmenty posta w preferowanej przez siebie kolejności). Pierwsze podejście do przetestowania może wyglądać na przykład tak:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Autowired private WebApplicationContext context; @Autowired private StateBean state; @Test public void testState() throws Exception { state.name = "toggled"; state.value = 9; MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build(); MvcResult result = mvc.perform(MockMvcRequestBuilders.get("/state")).andReturn(); Assert.assertEquals("toggled 9", result.getResponse().getContentAsString()); } }

Wygląda logicznie? Wszczepiamy beana, coś w nim grzebiemy i liczymy, że Spring w swojej nieskończonej mądrości wszczepi tego samego beana w nasze zapytanie. Oczywiście gdyby tak było to nie miałbym po co pisać tego posta (co mogło by zagrozić utrzymaniu mojej aktywności minimum raz na rok).

Jedna z metod, która może człowiekowi (w tej roli ja) przyjść do głowy to stworzenie własnej sesji i wsadzenie do niej beana otrzymanego od Springa, a potem wrzucenie do zapytania. To wymaga sprawdzenia pod jakimi nazwami trzymane są beany w sesji. Ogólnie śliska sprawa, bo trochę za bardzo bazuje na wewnętrznej implementacji. Twórcy Springa mogą pewnego dnia zdecydować, że będą stosować inną konwencję w nazwach i będzie problem po podbiciu wersji.

To może zastanówmy się, skąd Spring w ogóle bierze tego beana. Okazuje się, że tworzy sobie dla każdego testu osobną sesję. I jest na tyle uprzejmy, że nam ją wszczepi jeśli go o to poprosimy. I tak oto działający kod prezentuje się tak:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Autowired private WebApplicationContext context; @Autowired private StateBean state; @Autowired private MockHttpSession session;
@Test public void testState() throws Exception { state.name = "toggled"; state.value = 9; MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build(); MvcResult result = mvc.perform(MockMvcRequestBuilders.get("/state").session(session)).andReturn(); Assert.assertEquals("toggled 9", result.getResponse().getContentAsString()); } }

I to wszystko bez czarów i bez hacków.

Jak połączyć Node'a z Pythonem

Dzisiaj będzie krótko i zwięźle. Grunt to żeby zostawić trochę wiedzy w cyberprzestrzenii.

Jeśli zamarzy wam się kiedyś uruchomić zewnętrzny proces wewnątrz Node.js i czytać z niego to pewnie po szybkim rozeznaniu skończycie mniej więcej z takim kodem:

var exec = require('child_process').exec; 
var child = exec('python sample.py');
child.stdout.on('data', function(data) {
console.log(data); }); child.stderr.on('data', function(data) {
console.log(data); });

Jeśli to będzie proces działający długotrwale to czeka was spore rozczarowanie. Żadne dane nie spłyną do standardowego wyjścia dopóki się nie zakończy. Co ciekawe to dotyczy tylko Pythona. Pozostałe programy nie mają z tym problemu. Oczywiście skoro problem jest specyficzny dla Pythona to tam też jest rozwiązanie. Wystarczy użyć dodatkowej opcji:

python -u sample.py

Nie rozgryzłem dlaczego np. terminal nie ma z tym problemu, ale dzisiaj nie mam już do tego głowy.

Migracja

Jak wiadomo Jogger zakończył działalność i trzeba było się przenieść na inny system.

Blog wygląda podobnie do tego starego, ale pod spodem jest Ghost. Do tego komentarze na Disqusie.

Nie wiem po co to piszę, i tak pewnie nikt tego nie czyta.

Przejście na HTTPS

Miałem napisać coś o programowaniu, ale przy okazji tego grzebania tu i tam zrobiłem sobie HTTPS na stronach (oprócz bloga, bo się nie da albo ja nie umiem). No, a że lepiej sobie taką świeżo nabytą wiedzę zapisywać (mam taką jedną notkę do której czasem wracam, bo nie osiągnąłem jeszcze takiego poziomu, żeby te dziwne rzeczy wpisywać z głowy) to więc tworzę ten wpis.

Po pierwsze - certyfikacja

Jak rasowy cebulak wybrałem stronę, gdzie certyfikaty SSL dają za darmo czyli StartSSL. Tu jest kilka pułapek. Przede wszystkim po rejestracji i potwierdzeniu konta logowanie odbywa się przy pomocy certyfikatu. Po prostu jakimś magicznym sposobem strona ładuje certyfikat do przeglądarki, który potem służy nam do logowania (pierwszy raz widziałem, żeby jakaś strona coś takiego robiła). Jak certyfikat nam się zgubi to trzeba pisać do supportu i tracić czas. Zdecydowanie mniej wygodne rozwiązanie niż kliknięcie "Przypomnij hasło". Druga pułapka to, że jeśli pociągniemy cały proces przez tę stronę jesteśmy zmuszeni do ustawienia hasła dla certyfikatu. Teraz przy każdym restarcie nginxa muszę wpisywać hasło (i to kilka razy! bo tak!). I tu dochodzimy do kolejnej pułapki. Certyfikaty są darmowe, ale jeśli coś spieprzymy trzeba wyłożyć $25 na anulowanie certyfikatu. A, zapomniałbym, na jedną subdomenę możemy dostać tylko jeden certyfikat (przynajmniej dopóki nie płacimy).

Weryfikacja

Zanim zaczniemy tworzyć certyfikaty należy zweryfikować domenę (nie mylić z weryfikacją konta przy rejestracji). Problem jest tu taki, że system sam wybiera nam kilka możliwych maili pod które gotów jest wysłać kod weryfikacyjny. Na przykład próbując zweryfikować domenę example.com dostałem takie propozycje:

postmaster@example.com
hostmaster@example.com
webmaster@example.com

Czyli nie pozostaje nic innego jak postawić serwer pocztowy (pamiętaj o wpisie MX w DNS!) i/lub stworzyć odpowiednie konto.

Klucz prywatny

Z wyżej podanych powodów zalecam samodzielne wygenerowanie sobie klucza prywatnego i CSR (Certificate Signing Request). W sumie certyfikat też można sobie samemu wygenerować, ale potem przeglądarki będą krzyczeć, że strona wygląda podejrzanie, więc taki certyfikat nadaje się jedynie do testów albo wewnętrznych zastosowań (np. intranet). Polecenie do wygenerowania klucza prywatnego wygląda tak:

$ openssl genrsa -out klucz_prywatny.key 2048

Po wykonaniu zawartość pliku klucz_prywany.key będzie wyglądać mniej więcej tak:

-----BEGIN RSA PRIVATE KEY-----
[dużo dziwnych literek]
-----END RSA PRIVATE KEY-----

Teraz możemy wygenerować CSR:

$ openssl req -new -sha256 -key klucz_prywatny.key -out zapytanie.csr

Konsola poprosi nas o wypełnienia kilku mało znaczących danych (kraj, miasto, organizacja). Ważne są dwa pola. Po pierwsze w Common Name (CN) musisz wpisać nazwę swojej domeny. Po drugie kiedy zapyta się o hasło możesz spokojnie ominąć ten krok wciskająć ENTER (chyba że masz paranoję albo dobre powody, żeby używać dodatkowych zabezpieczeń). Plik CSR powinien zawierać podobnie enigmatyczny ciąg znaków co klucz prywatny z tym, że będzie się zaczynać linijką -----BEGIN CERTIFICATE REQUEST-----. CSR możemy też wygenerować przy pomocy jakiegoś generatora online (zainteresowanych odsyłam do Google), ale wiadomo, że własna konsola jest bardziej tró.

Certyfikat

Pozostaje teraz rozpocząć proces tworzenia certyfikatu (pamiętaj, że każda subdomena wymaga osobnego). Wystarczy wypełnić odpowiednie pola i wkleić treść pliku CSR. Jeśli wszystko pójdzie dobrze to dostaniemy pole tekstowe z treścią certyfikatu (zaczyna się od -----BEGIN CERTIFICATE-----) i kopiujemy go do nowego pliku (wygląda na to, że konwencja zaleca nadanie rozszerzenia .pem).

Konfiguracja serwera

Zakładam, że masz już skonfigurowany i działający serwer Nginx (sorry, z Apachem mi jakoś nie po drodze). W moim wypadku wystarczyło dorzucenie trzech linijek do pliku konfiguracyjnego:

listen               443 ssl;
ssl_certificate      /ścieżka/do/certyfikat.pem;
ssl_certificate_key  /ścieżka/do/klucz_prywatny.key;

I to wszystko... prawie. Po takiej operacji stronka działała poprawnie na moim normalnym kompie, ale na lapku przeglądarka już miała fochy (znaczy mówiła, że jakiś certyfikat jest, ale nie może go zweryfikować). Nie wiem do końca na jakiej zasadzie to działa, ale po kilku godzinach jedna z przeglądarek zaczęła nagle uznawać certyfikat, ale na innej dalej był problem. Okazało się, że potrzeba jeszcze jednego elementu...

Łańcuch zaufania

Oprócz własnego certyfikatu potrzebujemy jeszcze certyfikatów instytucji, które potwierdzą, że nasz jest ok. Na StartSSL te certyfikaty są podpisane jako Root CA i Intermediate Server CA. Po pobraniu należy je dokleić do naszego certyfikatu (nie wiem na ile kolejność ma znaczenie, ale wszystko tutoriale umieszczają te certyfikaty od najmniej zaufanego do najbardziej zaufanego czyli najpierw nasz, a potem kolejno Intermediate i Root).

Jak widać dorobienie sobie kłódki przy adresie nie jest takie trudne. Jeśli jeszcze nie słyszeliście to Google odejmuje punkty za serwowanie stron w zwykłym HTTP. Generalnie, żeby wszystko było tip-top trzeba jeszcze ustawić przekierowanie z HTTP z HTTPS i wypucować stronkę, żeby nie występowało tzw. mixed-content (kiedy dokument z HTTPS zawiera obrazki albo skrypty z adresu HTTP, niektóre przeglądarki to blokują), ale to już pozostawiam czytelnikowi.

Projektowo

Doprowadziłem dzisiaj Parowca do działania (po dość długiej przerwie). Dalej nie wiem dlaczego zdychał, w każdym razie teraz pilnuje go Supervisord, więc jak mu się padnie to powinien zaraz wstać.

Poza tym pracuję nad certyfikatem SSL. W końcu HTTPS jest teraz w modzie. W ciągu paru następnych dni planuję spłodzić jakąś poważniejszą notkę (to znaczy coś konkretnie o programowaniu, a nie jakieś wpisy, że zmieniłem kolory na blogu). No i startuję już jakiś poważniejszy projekt.

Projekty
Poważniejszy niż przynajmniej 90 innych projektów w tym katalogu...

To chyba na tyle. Czas spać (i czytać) :)