Grupy w wyrażeniach regularnych

Kiedyś obiecałem sobie, że przynajmniej raz w roku będę coś tu wrzucał. Tak że teraz publikuję ten wpis jako pomnik mojej prokrastrynacji.

Każdy, kto miał styczność z wyrażeniami regularnymi wie, że temat jest delikatnie rzecz biorąc rozbudowany. Nie jestem ekspertem, ale szacuję, że jest około miliona klas znaków i praktycznie każdy dziwny symbol, jaki jest na klawiaturze ma jakieś swoje znaczenie w języku regexpów. Jakby tego było mało to każde narzędzie ma trochę inną składnię.

Niniejszy artykuł poruszy jedno niewielkie, nie wiem na ile znane (ciężko powiedzieć, ja kiedyś nie znałem) zagadnienie, mianowicie grupy dopasowań. Przyjmuję składnię regexpów w Java/JavaScript, więc polecam wciśnięcie F12 i zabawę w konsoli deweloperskiej w trakcie lektury.

Podstawowa składnia

Do zdefiniowania prostej grupy dopasowań nie trzeba robić nic więcej jak tylko postawić nawiasy w wyrażeniu regularnym.

/(a)bc/

Tyle. Jeśli teraz przepuścimy przez to wyrażenie jakiś pasujący ciąg znaków to wypluje nam ono dwie grupy, grupę 0, która jest całym dopasowaniem i grupę 1, która będzie po prostu zawierać literę a.

Nazwane grupy

Adresacja numerami jest ok, gdy mamy zdefiniowaną jedną, góra dwie grupy. Przy większej liczbie może ucierpieć czytelność, a i bez tego wyrażenia regularne są równie przyjemne do czytania, co książki Paulo Coelho. Żeby sobie ułatwić możemy ponazywać nasze grupy.

/(?<magicNumber>\d+)keyword/

Ok, tu jest tylko jedna grupa, ale nie sensu komplikować przykładu dla idei. W każdym razie teraz możemy się do tej naszej grupy odwoływać po nazwie magicNumber. W tym wypadku to będzie ciąg cyfr (czyli liczba, duh!).

Odwoływanie się do grupy

Co więcej do grupy można się odwołać też w samym wyrażeniu regularnym. Nie wiem po co ktoś miałby to robić, ale tak mi się to spodobało, że napiszę.

/(?<magicNumber>\d+)keyword\d*\k<magicNumber>\d*/

Trochę nam się to skomplikowało to może wytłumaczę, co tu się dzieje. Na początku szukamy tej naszej magicznej liczby, która znajduje się przed słowem keyword. Następnie spodziewamy się ciągu cyfr. Gdzieś wśród tych cyfr musi się znajdować ta magiczna liczba, którą sobie znaleźliśmy na początku, inaczej wyrażenie regularne niczego nie złapie.

Czyli ten ciąg się zmaczuje:
1234keyword865612346789

A ten już niezbyt:
1234keyword999999999999

IDE

Co najlepsze różnego rodzaje IDE i edytory, które pozwalają na Find/Replace po wyrażeniach regularnych, rozumieją też grupy dopasowań.

Załóżmy, że mamy taki kod:

obj.name = "asd";  
obj.age = 12;  
obj.gender = MALE;  

Chcemy zmienić obiekt na powiedzmy mapę. Nie możemy wtedy przypisywać pól, tylko musimy to robić przez metodę put. Jeśli mamy kilkadziesiąt takich przypadków to z ręki wyjdzie nam sporo klikania. Ale oczywiście można to łatwo rozwiązać używając grup!

Find: \.([a-z]+) = ([^;]+)
Replace: .put("$1", $2)

To działa przynajmniej w VSC i IntelliJ. O ile druga część nie jest wyrażeniem regularnym to jest jednak edytor robi z tym coś więcej i wstawia złapane grupy we wskazane miejsca.

No i to chyba tyle w temacie. Szczęśliwego nowego!

P. S. U mnie postanowieniem noworocznym jest ostylowanie jednolinijkowców na blogu. Chyba nie jest tak źle?

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.