Wyślij zapytanie Dołącz do Sii

Standardem w tworzeniu systemów stają się mikroserwisy, w których działanie aplikacji jest często zależne od dostępności innych aplikacji. Nastręcza to problemów choćby z odtworzeniem dość specyficznych błędów w procesach, w których uczestniczą inne serwisy. Trudnością jest zasymulowanie zachowania systemu, gdy serwis za długo nie odpowiada lub połączenie zostało przerwane w trakcie. Podobnie jest z potrzebą sprawdzenia lokalnie działania całego procesu.

Mock-server – do czego służy

Tutaj z pomocą może nam przyjść mock-server. Jest to projekt, który umożliwia zasymulowanie działanie innego serwisu oraz łatwe konfigurowanie oczekiwanych odpowiedzi statycznych i dynamicznych, zależnych od danych, jakie przyjdą w żądaniu.

Mock-server wspiera:

  • protokoły http,
  • https,
  • połączenie websocket.

Przy połączeniu SSL domyślnie jest używany ich certyfikat. Oczywiście, można wgrać też własny, co zostanie wyjaśnione w dalszej części artykułu. Aplikacja udostępnia także skromny interfejs użytkownika, pod którym da się podejrzeć aktualną konfigurację i logi, a także po nich filtrować.

Ponadto, mock-server udostępnia API umożlwiające zarządzanie i konfigurację. Opisałem tu tylko część funkcjonalności – więcej znajdziecie w oficjalnej dokumentacji.

Podstawowe informacje dot. uruchomienia i integracji

Mock-server można uruchomić lub zintegrować na wiele sposobów:

  • Przy użyciu obrazu Dockera.
  • Z użyciem menadżera paczek helm w środowisku Kubernetesa.
  • Jako część procesu budowania w Maven i Grunt.
  • Bezpośrednio w aplikacji Java
    • w testach Junit4 wykorzystując adnotację @Rule,
    • w Junit5 z wykorzystaniem adnotacji @ExtendWith,
    • w Spring Test Execution Listener z adnotacją @MockServerTest.
  • Uruchomienie przez Node.js (npm).
  • Bezpośrednio z linii komend za pomocą
    • Homebrew,
    • uruchomienie pliku jar,
    • Mavena,
    • Dockera.

W artykule przedstawię jak:

  • uruchomić mock-server,
  • skonfigurować oczekiwane odpowiedzi,
  • użyć własnego certyfikatu,
  • wymusić mTls.

Uruchamiamy mock-server

Uruchomienie z linii komend z użyciem java jest moim zdaniem najszybszym i najprostszym sposobem. Wymagane jest tylko posiadanie zainstalowanej javy w wersji 8+ i pobranie wersji mock-servera z wbudowanym serwerem.

Dostępne wersje mock-servera z wbudowanym serwerem netty można znaleźć tutaj. Tylko wersje z suffixem shaded (starsze wersje mają suffix jar-with-dependecies) zawierają wszystkie niezbędne zależności potrzebne do uruchomienia.

Na potrzeby artykułu pobrałem plik mockserver-netty-5.12.0-shaded.jar. Po pobraniu pliku jar, w zależności od zainstalowanej wersji javy, należy wywołać jedną z poniższych komend w folderze w którym znajduje się nasza aplikacji.

  • Komenda dla javy 8
    java -jar mockserver-netty-5.12.0-shaded.jar -serverPort 1080,1081 -logLevel INFO
  • Komenda dla javy 9+
    java --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-exports=java.base/sun.security.util=ALL-UNNAMED -jar mockserver-netty-5.12.0-shaded.jar -serverPort 1080,1081 -logLevel INFO

Kod mock-servera w wersji 5.12.0 nie jest w pełni dostosowany do nowej javy 9+, dlatego uruchomienie go wymagało podania dwóch dodatkowych argumentów:

  • --add-exports=java.base/sun.security.x509=ALL-UNNAMED
  • --add-exports=java.base/sun.security.util=ALL-UNNAMED

Ponadto, zostały podane parametry stricte związane z samym mock-serverem

Parametr Opis
-serverPort <lista portów po przecinki> Można podać jeden lub więcej portów na jakich ma nasłuchiwać mock-server
-logLevel <poziom> Poziom logów, jakie będą pisane do konsoli. Można ustawić: INFO, WARN, DEBUG, TRACE
-Dmockserver.logLevel=OFF Wyłączenie pisania logów z klas mock-servera
-Droot.logLevel=OFF Całkowite wyłączenie logowania

Weryfikacja działania aplikacji

Najprostszym sposobem sprawdzenia czy nasza aplikacja działa, jest wejście na stronę, gdzie udostępniono dashboard mock-servera, w którym możemy podejrzeć logi, filtrować po nich, a także zobaczyć aktualną konfigurację odpowiedzi mock-servera.

Tworzenie statycznej oczekiwanej odpowiedzi

Domyślnie aplikacja zwraca 404 na każdy request. Odpowiedzi można skonfigurować przez api mock-servera. Opisy punktów krańcowych (endpointów) są dostępne do sprawdzenia. Można także wczytać odpowiedzi z pliku z użyciem statycznych odpowiedzi, wykorzystując OpenApi w wersji trzeciej lub Apache Velocity.

W artykule zaproponowałem inicjalizację odpowiedzi z pliku, wykorzystując składnie mock-servera do statycznych odpowiedzi. Należy stworzyć plik z rozszerzeniem json, w którym znajduje się lista ze statycznymi odpowiedziami. W moim przypadku jest to plik init.json.


[{
"httpRequest": {
"method":"GET",
"path": "/some/path"
},
"httpResponse": {
"headers": {
"content-type": ["applications/json"]
},
"body": {
"field":"value",
"field":"value2"
}
}]
}}]

Następnie, musimy uruchomić mock-server, dodając dodatkowe parametry:

  • -Dmockserver.initializationJsonPath=<ścieżka do pliku>
  • -Dmockserver.watchInitializationJson=true

Dmockserver.initializationJsonPath wskazuje na ścieżkę z plikiem konfiguracyjnym. Natomiast Dmockserver.watchInitializationJson=true co 5 sekund sprawdza, czy w pliku nie nastąpiły zmiany, dzięki czemu nie ma potrzeby restartowania aplikacji, gdy modyfikujemy oczekiwane odpowiedzi.

Poniżej znajdziecie pełne komendy. Należy podmienić <ścieżka do pliku konfiguracyjnego> na pełną lub relatywną ścieżkę do wcześniej utworzonego pliku.

  • Komenda dla javy 8
    java -Dmockserver.initializationJsonPath=init.json -Dmockserver.watchInitializationJson=true -jar mockserver-netty-5.12.0-shaded.jar -serverPort 1080,1081 -logLevel INFO
  • Komenda dla javy 9+
    java --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-exports=java.base/sun.security.util=ALL-UNNAMED -Dmockserver.initializationJsonPath=init.json -Dmockserver.watchInitializationJson=true -jar mockserver-netty-5.12.0-shaded.jar -serverPort 1080,1081 -logLevel INFO

Następnie wystarczy wykonać żądanie GET pod adres http://localhost:1080/some/path, a w odpowiedzi otrzymamy jsona :

{
„field”:”value”,
„field”:”value2”
}

Sekcje httpRequest i httpResponse

W przykładzie konfiguracji odpowiedzi widać dwie sekcje:

  • httpRequest,
  • httpResponse.

Sekcja httpRequest zawiera konfigurację, dla jakiego żądania ma być zwrócona odpowiedź z sekcji httpResponse. Można filtrować po ścieżce, metodzie, parametrach i wielu innych.

Aby dowiedzieć się więcej, polecam zajrzeć do dokumentacji w sekcji „Creating Expectations”.

W sekcji httpResponse jest definicja dokładnej odpowiedzi. Oprócz jej ciała można zdefiniować:

  • status odpowiedzi,
  • nagłówki,
  • ciasteczka,
  • a także symulować opóźnienia występujące w sieci.

Opis wszystkich możliwości znajdziecie na stronie mock-server.

Ważne! Należy pamiętać, że lista odpowiedzi jest analizowana od góry do dołu i brana jest pierwsza pasująca odpowiedź według kryteriów zdefiniowanych w sekcji httpRequest.

Konfiguracja w osobnym pliku

Przy większej ilości parametrów wygodniejsze będzie przeniesienie konfiguracji do osobnego pliku properties. W moim przypadku będzie to plik mock-server.properties. Poniżej znajdziecie jego treść.
Nie ma tam jedynie parametru -serverPort 1080,1081, który w dalszym ciągu musi być podany jako parametr uruchomieniowy.

mockserver.logLevel=INFO
mockserver.disableLogging=false
mockserver.disableSystemOut=false
mockserver.initializationJsonPath=init.json
mockserver.watchInitializationJson=true

Doszedł nam jeden nowy parametr:

  • -Dmockserver.propertyFile=<ścieżka do pliku konfiguracyjnego>

W folderze, z którego uruchamiamy aplikację, powinny znajdować się 3 pliki:

  1. mockserver-netty-5.12.0-shaded.jar
  2. properties
  3. json

Komenda do uruchomienia aplikacji wygląda następująco:

  • Komenda dla javy 8
    java -Dmockserver.propertyFile=mockserver.properties -jar mockserver-netty-5.12.0-shaded.jar -serverPort 1080,1081
  • Komenda dla javy 9+
    java --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-exports=java.base/sun.security.util=ALL-UNNAMED -Dmockserver.propertyFile=mockserver.properties -jar mockserver-netty-5.12.0-shaded.jar -serverPort 1080,1081

Jeśli nie zmodyfikowaliśmy init.json, to po uruchmonieniu aplikacji mock-server i wykonaniu żądania http://localhost:1080/some/path o typie GET, w odpowiedzi otrzymamy jsona o treści:

{
„field”:”value”,
„Field”:”value2”
}

Tworzenie dynamicznej odpowiedzi

Odpowiedzi dynamiczne można stworzyć na dwa sposoby. Jednym z nich jest wykorzystanie javascriptTemaplate, a drugim dodanie do ścieżki klasy pliku jar zawierającego klasę implementującą interfejs org.mockserver.mock.action.ExpectationResponseCallback.

W artykule przedstawię tylko drugą metodę. Związane jest to z usunięciem z javy 15+ silnika nashrom, w związku z czym pierwszy sposób nie zadziała już na javie wyższej niż 15.

Do stworzenia klasy wystarczy nam zwykły edytor tekstowy. Można też użyć IDE – w tym wypadku polecam dodać wcześniej użytą wersję aplikacji mock-server jako bibliotekę, aby w IDE działało automatyczne podpowiadanie składni. Aplikacja zostanie skompilowana i zbudowana przy użyciu javac, które jest dostępne wraz zainstalowaną javą.

W moim przypadku wykorzystem Inteljii Idea Community Edition. Tworzę przy jego użyciu pusty projekt, w nim folder libs i kolejno umieszczam w nim plik jar z mock-servera. U mnie jest to mockserver-netty-5.12.0-shaded.jar. Dodaję ten plik jako zewnętrzną bibliotekę projektu. Dostępny jest także poradnik jak to zrobić w Idea Community Edition.

W projekcie tworzymy klasę mockserver.response.SampleResponse implementującą interfejs org.mockserver.mock.action.ExpectationResponseCallback i metodę public HttpResponse handle(HttpRequest httpRequest). Klasa HttpResponse implementuje wzorzec budowniczy, dzięki czemu możemy szybko stworzyć oczekiwaną odpowiedź.

W przykładzie w odpowiedzi zwrócimy to samo, co zostało nam przekazane w żądaniu.


package mockserver.response;

import org.mockserver.mock.action.ExpectationResponseCallback;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;

public class SampleResponse implements ExpectationResponseCallback {

@Override
public HttpResponse handle(HttpRequest httpRequest) throws Exception {
return&amp;amp;nbsp; HttpResponse.&amp;amp;lt;em&amp;amp;gt;response&amp;amp;lt;/em&amp;amp;gt;()
.withBody(httpRequest.getBodyAsString())
.withStatusCode(200);
}

}

Stworzyłem także drugą klasę, która w odpowiedzi zwraca to samo tylko ze statusem 500.


package mockserver.response;

import org.mockserver.mock.action.ExpectationResponseCallback;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;

public class SampleResponse2 implements ExpectationResponseCallback {

@Override
public HttpResponse handle(HttpRequest httpRequest) throws Exception {
return&amp;amp;nbsp; HttpResponse.&amp;amp;lt;em&amp;amp;gt;response&amp;amp;lt;/em&amp;amp;gt;()
.withBody(httpRequest.getBodyAsString())
.withStatusCode(500);
}

}

Sama struktura projektu powinna wyglądać następująco:

Ryc. 1 - Mock-server – co to jest i dlaczego warto poznać go lepiej?

Następnym krokiem jest skompilowanie naszych dwóch klas i wygenerowanie paczki jar. Kod można skompilować wywołując komendę:

javac -cp .\lib\mockserver-netty-5.12.0-shaded.jar .\mockserver\response\*.java -d out

W rezultacie zostaną skompilowane wszystkie klasy w pakiecie mockserver.response, a wynik kompilacji znajdzie się w folderze out. Powinno wyglądać to następująco:

Ryc. 2 - Mock-server – co to jest i dlaczego warto poznać go lepiej?

Następnie wywołujemy polecenie, które spakuj to w plik jar. Należy to zrobić z poziomu projektu. Zależnie jakiej linii komend używacie, trzeba będzie wykonać jedno z poleceń:

  • Windows powershell
    cd out; jar -cf ../msLib.jar .; cd ..
  • Windows CMD
    cd out & jar -cf ../msLib.jar . & cd ..
  • Linux
    cd out && jar -cf ../msLib.jar . && cd ..

W rezultacie pojawi nam się plik msLib.jar. W lokalizacji, z której uruchamiamy mock-server, tworzy folder libs. Umieszczamy w nim nasz plik msLib.jar. Następnie, we wcześniej utworzonym pliku init.json, dodajemy nową konfigurację, gdzie oczekiwana odpowiedź będzie zwrócona przez nasze klasy. Plik init.json powinien wyglądać następująco:


[{
"httpRequest": {
"method":"GET",
"path":"/some/path"
},
"httpResponse": {
"headers": {
"content-type": ["applications/json"]
},
"body":{
"field":"value",
"field":"value2"
}
}
},
{
"httpRequest" : {
"path" : "/myResponse1"
},
"httpResponseClassCallback" : {
"callbackClass" : "mockserver.response.SampleResponse"
}
},
{
"httpRequest" : {
"path" : "/myResponse2"
},
"httpResponseClassCallback" : {
"callbackClass" : "mockserver.response.SampleResponse2"
}
}]

Jak widać, w przypadku, gdy odpowiedź ma być dynamicznie generowana przez klasę, sekcja httpResponse zmieniła się w sekcję httpResponseClassCallback z jednym polem callbackClass, gdzie podana jest klasa wraz z pakietem.

Folder z mock-server powinien mieć następującą zawartość:

Ryc. 3 - Mock-server – co to jest i dlaczego warto poznać go lepiej?

Plik mockserver.properties powinien zawierać następującą treść:

mockserver.logLevel=INFO
mockserver.disableLogging=false
mockserver.disableSystemOut=false
mockserver.initializationJsonPath=init.json
mockserver.watchInitializationJson=true

Ostatnim etapem jest uruchomienie aplikacji tak, aby wczytała do swojej ścieżki uruchomieniowej pliki z lokalizacji libs/*. Aby to osiągnąć, trzeba uruchomić mock-server, używając komendy -cp zamiast -jar. Komenda wygląda następująco.

  • Java 8
    java -Dmockserver.propertyFile=mockserver.properties -cp "mockserver-netty-5.12.0-shaded.jar;libs/*" org.mockserver.cli.Main -serverPort 1080,1081
  • Java 9+
    java --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-exports=java.base/sun.security.util=ALL-UNNAMED -Dmockserver.propertyFile=mockserver.properties -cp "mockserver-netty-5.12.0-shaded.jar;libs/*" org.mockserver.cli.Main -serverPort 1080,1081

Po uruchomieniu i wykonaniu zapytania POST pod adres otrzymamy w odpowiedzi kod 200 lub 500, a w ciele żądania to, co wysłaliśmy.

Ryc. 4 - Mock-server – co to jest i dlaczego warto poznać go lepiej?

Ryc. 5 - Mock-server – co to jest i dlaczego warto poznać go lepiej?

Mock-server – własny certyfikat i włączenie mTLS

Komunikacja szyfrowana jest w dzisiejszych czasach standardem i mock-server także ją wspiera. Domyślnie mock-server przedstawia się certyfikatem. Lepszym rozwiązaniem jest jednak podanie własnego certyfikatu. W artykule przedstawię, jak wygenerować własne CA, certyfikat i jak go podpisać naszym CA. Pokażę także, jak przygotować już istniejący certyfikat tak, żeby obsłużył go mock-server.

Generowanie własnego certyfikatu

Na potrzeby lokalnych testów stworzymy własne CA, a następnie certyfikat, który zostanie nim podpisany. Wszystko z użyciem openssl (na systemie Windows polecam wykorzystać do tego zadania WSL).

W pierwszym kroku w wybranej lokalizacji stworzymy certyfikat CA z użyciem poniższych komend.

openssl genrsa -des3 -out rootCA.key 4096
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt

Następnie tworzymy folder i generujemy certyfikaty dla mock-servera.

mkdir mockserver
openssl genrsa -out mockserver/mockserver.key 2048
openssl req -new -sha256 -key mockserver/mockserver.key -subj "/C=PL/ST=CA/O=MyOrgMock, Inc./CN=localhost" -out mockserver/mockserver.csr
openssl x509 -req -in mockserver/mockserver.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out mockserver/mockserver.crt -days 500 -sha256

Na nieszczęście mock-server nie jest w stanie obsłużyć naszego certyfikatu i klucza w domyślnym formacie. Musimy więc skonwertować certyfikat do formatu PEM, a klucz prywatny do PKCS#8. Można to zrobić poniższymi komendami:

openssl pkcs8 -topk8 -inform PEM -in mockserver/mockserver.key -out mockserver/mockserver.pksc8.pem -nocrypt
openssl x509 -in mockserver/mockserver.crt -out mockserver/mockserver.crt.pem -outform PEM

Ostatnim etapem jest skonwertowanie naszego certyfikatu rootCa.crt, którym podpisaliśmy certyfikat dla mock-servera, do formatu PEM. Dzięki temu mock-server będzie akceptował wszystkie certyfikaty, które są nim podpisane.

Można to zrobić poniższą komendą:

openssl x509 -in rootCA.crt -out rootCA.pem -outform PEM

Konwersja istniejącego certyfikatu

Może się zdarzyć, że posiadamy własny CA lub dostajemy gotowe certyfikaty do aplikacji, która będzie działać na hoście. W tym wypadku wystarczy przekonwertować certyfikat do formatu PEM i dodatkowo wygenerowany klucz skonwertować do PKCS#8. Poniżej przykład konwersji certyfikatu w formacie p12.

openssl pkcs12 -in path.p12 -out newfile.crt.pem -clcerts -nokeys
openssl pkcs12 -in path.p12 -out newfile.key.pem -nocerts -nodes
openssl pkcs8 -topk8 -inform PEM -in newfile.key.pem -out newfile.key.pkcs8.pem -nocrypt

Trzeba także dodać CA, którym zostały podpisane nasze certyfikaty. W tym wypadku wystarczy go tylko skonwertować do formatu PEM. Poniżej przykład konwersji z formatu .cer do .pem

openssl x509 -inform der -in certificate.cer -out certificate.pem

Konfiguracje mtls

Gdy już posiadamy certyfikaty w poprawnych formatach, umieszczamy je w folderze config, który należy utworzyć w tej samej lokalizacji, co naszą aplikację mock-servera. Będą to następujące pliki:

rootCA.pem
mockserver/mockserver.crt.pem
mockserver/mockserver.pksc8.pem

Do pliku mockserver.properties należy dodać dodatkowe parametry, z zachowaniem tych, które zostały dołączone we wcześniejszej części artykułu.

#Wymuszenie mutualTLS
mockserver.tlsMutualAuthenticationRequired=false
#Certyfikat root
mockserver.certificateAuthorityCertificate=config/rootCA.pem
#Certyfikat dla mock-server’a
mockserver.privateKeyPath=config/private_key_PKCS_8.pem
mockserver.x509CertificatePath=config/localhost.pem

Parametr mockserver.tlsMutualAuthenticationRequired wymusza mutual TLS. Po jego ustawieniu na true nie będziemy mieli możliwości łączyć się z mock-serverem po porcie http, ani po porcie https z poziomu przeglądarki.

Folder z naszą aplikacją powinien wyglądać następująco:

Ryc. 6 - Mock-server – co to jest i dlaczego warto poznać go lepiej?

Aplikację uruchamiamy tak jak we wcześniejszej części artykułu z użyciem komendy:

  • Dla javy 8
    java -Dmockserver.propertyFile=mockserver.properties -jar mockserver-netty-5.12.0-shaded.jar -serverPort 1080
  • Dla javy 9+
    java --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-exports=java.base/sun.security.util=ALL-UNNAMED -Dmockserver.propertyFile=mockserver.properties -jar mockserver-netty-5.12.0-shaded.jar -serverPort 1080

Istnieje możliwość sprawdzenia, czy mock-server przedstawia się teraz naszym certyfikatem.

Ryc. 7 - Mock-server – co to jest i dlaczego warto poznać go lepiej?

Kolejną rzeczą jest sprawdzenie czy mutualTLS działa poprawnie. Po zmianie parametru mockserver.tlsMutualAuthenticationRequired na true i restarcie aplikacji nie będziemy mieli możliwości wejścia na wcześniejszy adres, ponieważ nasza przeglądarka nie przedstawia się żadnym certyfikatem, a każde żądanie z niepoprawnym certyfikatem będzie odrzucane.

Do przetestowania działania mTls wykorzystam postmana. Na potrzeby testów wygenerowałem nowy certyfikat analogicznie jak dla mock-servera i podpisałem go stworzonym lokalnie CA. Można także wykorzystać certyfikat wygenerowany dla mock-servera.

Aby skonfigurować aplikację postman, należy wejść w: File -> settings -> Certificates, a następnie włączyć parametr CACertificates i podać tam nasz plik rootCA, zaś w sekcji Client certificates dodać nowy certyfikat. Należy wskazać plik CRT i klucz prywatny, a host ustawić na adres, na którym działa mock-server.

W moim przypadku wygląda to następująco:

Ryc. 8 - Mock-server – co to jest i dlaczego warto poznać go lepiej?

Po wykonaniu żądania na nieskonfigurowany endpoint dostaniemy domyślną odpowiedź 404.

Podsumowanie

Mock-server daje duże możliwości stworzenia całego systemu umożliwiając lokalne sprawdzenie działania procesów, jak i możliwość stworzenie testów integracyjnych z uwzględnieniem brzegowych przypadków. W prosty sposób można także wymusić mTls, dzięki czemu jeszcze dokładniej możemy odwzorować system produkcyjny lokalnie.

***

Jeśli chcesz rozpocząć swoją przygodę z Dockerem lub interesują Cię informacje dotyczące wprowadzenia do Kubernetes, zachęcamy do przeczytania innych artykułów naszych ekspertów.

Ocena:
Autor
Avatar
Janusz Groszewski

Software Developer w Sii. Główne technologie z jakimi pracuje to: Java, Spring i SQL. W wolnym czasie gra w planszówki, trenuje na siłowni, a także dokształca się z inwestowania na giełdzie.

Twój adres e-mail nie zostanie opublikowany.

Może Cię również zainteresować

Pokaż więcej postów

Bądź na bieżąco

Zapisz się do naszego newslettera i otrzymuj najświeższe informacje ze świata Sii.

Otrzymaj ofertę

Jeśli chcesz dowiedzieć się więcej na temat oferty Sii, skontaktuj się z nami.

Wyślij zapytanie Wyślij zapytanie

Get an offer

Natalia Competency Center Director

Dołącz do Sii

Znajdź idealną pracę – zapoznaj się z naszą ofertą rekrutacyjną i aplikuj.

APLIKUJ APLIKUJ

Join Sii

Paweł Process Owner

ZATWIERDŹ

This content is available only in one language version.
You will be redirected to home page.

Are you sure you want to leave this page?