~~NOCACHE~~ ~~REVEAL theme=simple&disableLayout=0&transition=none&controls=1&show_progress_bar=1&build_all_lists=0&show_image_borders=0&horizontal_slide_level=2&enlarge_vertical_slide_headers=0&show_slide_details=1&open_in_new_window=1&size=1024x768~~ ======= Javalin - Lekki Framework Webowy dla Javy ====== ===== Co to jest Javalin? ===== * **Javalin** to lekki framework do tworzenia aplikacji webowych i REST API w Javie * Oparty na serwerze **Jetty** (serwer HTTP i servletów) * Przeznaczony dla programistów, którzy chcą szybko tworzyć aplikacje bez dużej ilości boilerplate'u * Idealne do: * REST API * Mikroserwisów * Prototypów * Małych i średnich aplikacji webowych * "Czysty" kod Java, bez adnotacji i refleksji * Następca Spark Java, ale z nowoczesnym designem i lepszą wydajnością * Obecna wersja: **7.x** (znaczące zmiany od wersji 6.x) * W Javalin 7 trasy definiujemy przez ''config.routes'' podczas ''Javalin.create(...)'' * https://javalin.io/ ===== Dodanie Javalin do projektu Maven ===== Plik ''pom.xml'': io.javalin javalin 7.2.2 io.javalin javalin-bundle 7.2.2 ===== Zawartość i zależności javalin-bundle ===== **javalin-bundle** to "all-in-one" pakiet zawierający: * ''javalin-core'' - główny framework * ''jetty-server'' - serwer HTTP * ''jetty-servlet'' - obsługa servletów * ''jackson'' - serializacja JSON * ''slf4j-api'' - API loggowania **Zaleta:** Nie musisz ręcznie dodawać zależności, wszystko jest już skonfigurowane i kompatybilne [[https://javalin.io/download#javalin-modules|Inne moduły]]: ''javalin-rendering-*'', ''javalin-ssl'', ''javalin-micrometer'' ===== Uruchomienie serwera ===== import io.javalin.Javalin; public class HelloWorldApp { public static void main(String[] args) { Javalin app = Javalin.create(config -> { config.routes.get("/", ctx -> { ctx.result("Hello World!"); }); }); // Uruchom na porcie 8080 app.start(8080); } } ===== Uchwyty ===== * Podstawowe typy: * akcje przed: ''before'', ''beforeMatched'' * punktów końcowych ''endpoints'' : ''get'', ''post'', ''put'', ''delete'', ''patch'' * akcje po: ''after'', ''afterMatched'' * Ścieżka: * ''/'', ''/hello-world'', ''/hello/{name}'', ''/user/*'' * Handler: ''ctx -> { ... }'' - lambda z obiektem ''Context'' * Pobieranie danych z żądania i wysyłanie odpowiedzi przez ''Context'' * Uchwyty do tych samych scieżek wykonywane w kolejności definicji config.routes.get("/", ctx -> { ctx.result("Hello World!"); }); ===== Routing: Metody HTTP ===== Javalin obsługuje wszystkie standardowe metody HTTP: * ''GET'' - pobranie danych * ''POST'' - utworzenie zasobu * ''PUT'' - aktualizacja zasobu * ''DELETE'' - usunięcie zasobu * ''PATCH'' - częściowa aktualizacja ===== Składnia tras ===== Javalin app = Javalin.create(config -> { // GET - pobierz dane config.routes.get("/api/users", ctx -> { ctx.result("List of users"); }); // POST - utwórz zasób config.routes.post("/api/users", ctx -> { ctx.status(201); // Created ctx.result("User created"); }); // PUT - aktualizuj config.routes.put("/api/users/{id}", ctx -> { String id = ctx.pathParam("id"); ctx.result("Updated user " + id); }); // DELETE - usuń config.routes.delete("/api/users/{id}", ctx -> { String id = ctx.pathParam("id"); ctx.result("Deleted user " + id); }); }).start(8080); ===== Priorytety tras ===== * Trasy są przetwarzane w kolejności zdefiniowania * Bardziej konkretne trasy powinny być zdefiniowane przed ogólnymi * Wildcard ''*'' dopasowuje dowolny tekst Javalin app = Javalin.create(config -> { config.routes.get("/admin/users", ctx -> ctx.result("Admin users")); // Konkretna config.routes.get("/admin/*", ctx -> ctx.result("Admin section")); // Ogólna config.routes.get("/*", ctx -> ctx.result("Everything else")); // Najogólniejsza }); ===== Obiekt Context? ===== * ''Context'' to centralny obiekt w Javalin reprezentujący: * **Żądanie HTTP** od klienta (request) * **Odpowiedź HTTP** dla klienta (response) * Każdy handler otrzymuje obiekt ''Context'' jako argument i poprzez niego: * Pobiera dane z żądania * Wysyła dane w odpowiedzi * Handler nic nie zwraca, wszystko idzie przez ''Context'' Javalin app = Javalin.create(config -> { config.routes.get("/example", ctx -> { String query = ctx.queryParam("q"); String path = ctx.path(); ctx.status(200); ctx.result("Response, query=" + query + ", path=" + path); }); }); ===== Kluczowe metody Context ===== * ''path()'' - ścieżka żądania * ''method()'' - metoda HTTP * ''pathParam(key)'' - parametr z ścieżki (np. ''/users/{id}'') * ''queryParam(key)'' - parametr z query string (''/search?q=java'') * ''body()'' - ciało żądania * ''result(String)'' - wysłanie odpowiedzi * ''json(Object)'' - wysłanie JSON * ''status(int)'' - kod statusu HTTP * Zobacz: [[https://javalin.io/documentation#context|Dokumentacja Context]] ===== Parametry w ścieżce ===== Javalin app = Javalin.create(config -> { // Definicja: /users/{id}/posts/{postId} config.routes.get("/users/{id}/posts/{postId}", ctx -> { String userId = ctx.pathParam("id"); // Z walidacją typu int postId = ctx.pathParamAsClass("postId", Integer.class).get(); ctx.result("User: " + userId + ", Post: " + postId); }); }).start(8080); // GET /users/123/posts/456 -> User: 123, Post: 456 // GET /users/123/posts/abc -> "Serwer error" (NumberFormatException) ===== Query Parameters ===== Parametry po znaku ''?'' w URL: Javalin app = Javalin.create(config -> { config.routes.get("/search", ctx -> { String query = ctx.queryParam("q"); int limitInt = ctx.queryParamAsClass("limit", Integer.class).getOrDefault(42); ctx.result("Szukam: " + query + " (limit: " + limitInt + ")"); }); }); // GET /search?q=java&limit=5 // Odpowiedź: Szukam: java (limit: 5) Wiele wartości tego samego parametru: // GET /filter?tag=java&tag=web&tag=api List tags = ctx.queryParams("tag"); // tags = ["java", "web", "api"] ===== Body - Ciało żądania ===== Dane przesłane w ciele żądania (zazwyczaj POST/PUT): Javalin app = Javalin.create(config -> { config.routes.post("/echo", ctx -> { String body = ctx.body(); ctx.result("Echo: " + body); }); }); // CMD: curl -X POST http://localhost:8080/echo -d "hello" // POST /echo // Body: hello // Odpowiedź: Echo: hello ===== Nagłówki żądania ===== Javalin app = Javalin.create(config -> { config.routes.get("/headers", ctx -> { String userAgent = ctx.header("User-Agent"); String contentType = ctx.header("Content-Type"); ctx.result("User-Agent: " + userAgent); Map headers = ctx.headerMap(); }); }); ===== Kody statusu HTTP ====== Javalin app = Javalin.create(config -> { config.routes.post("/create", ctx -> { // Utworzenie zasobu ctx.status(201); // Created ctx.result("Zasób utworzony"); }); config.routes.get("/notfound", ctx -> { ctx.status(404); // Not Found ctx.result("Nie znaleziono"); }); }); // Powszechne kody: // 200 - OK (GET, POST, PUT zwrócił dane) // 201 - Created (POST/PUT utworzył zasób) // 204 - No Content (DELETE, operacja OK bez zawartości) // 400 - Bad Request (błąd w żądaniu klienta) // 401 - Unauthorized (brak autoryzacji) // 404 - Not Found (nie znaleziono) // 500 - Internal Server Error (błąd serwera) ===== Wysłanie JSON ===== // Record - idealny do przesyłania JSON record User(int id, String name, String email) {} Javalin app = Javalin.create(config -> { config.routes.get("/user", ctx -> { User user = new User(1, "Alice", "alice@example.com"); ctx.json(user); // Automatyczna konwersja do JSON }); }); // Odpowiedź: {"id":1,"name":"Alice","email":"alice@example.com"} ===== Rekordy w Java 16+ ===== Rekordy to specjalne klasy wprowadzone w Javie 16, idealne do przechowywania danych: * Automatycznie generują konstruktor, gettery, ''equals()'', ''hashCode()'' i ''toString()'' * niemutowalne (immutable) - wartości nie mogą być zmieniane po utworzeniu record Product(int id, String name, double price) {} Product p = new Product(1, "Laptop", 999.99); System.out.println(p.name()); ===== Serialiacja i deserializacja JSON ===== Rekordy to idealne do przesyłania danych w JSON: record User(int id, String name, String email) {} record Product(int id, String title, double price) {} Javalin app = Javalin.create(config -> { // Wysłanie config.routes.get("/user", ctx -> { User user = new User(1, "Alice", "alice@example.com"); ctx.json(user); // Konwersja Record -> JSON }); // Otrzymanie config.routes.post("/user", ctx -> { User user = ctx.bodyAsClass(User.class); // Konwersja JSON -> Record System.out.println("User: " + user.name()); ctx.json(user); }); }); ===== Przechwytywanie wyjątków ===== config.routes.exception(ValidationException.class, (e, ctx) -> { ctx.status(400); ctx.result("Liczba musi byc liczba calkowita\n" + e.getMessage()); }); config.routes.exception(RuntimeException.class, (e, ctx) -> { ctx.status(500); ctx.result("Blad serwera: " + e.getMessage()); }); config.routes.get("/boom", ctx -> { throw new RuntimeException("DEMO: kontrolowany wyjątek"); }); ===== Error Handlers ===== Obsługa błędów HTTP na poziomie aplikacji: // Wszystkie 404 config.routes.error(404, ctx -> { ctx.result("Strona nie znaleziona"); }); // Wszystkie 500 config.routes.error(500, ctx -> { ctx.result("Błąd serwera"); }); ===== Serwowanie HTML/CSS/JavaScript ==== Javalin app = Javalin.create(config -> { config.staticFiles.add("/public"); // Folder z plikami }); app.start(8080); // Pliki z src/main/resources/public/: // - /index.html → http://localhost:8080/index.html // - /css/style.css → http://localhost:8080/css/style.css // - /js/app.js → http://localhost:8080/js/app.js ===== Struktura projektu ===== src/main/resources/ └── public/ ├── index.html ├── css/ │ └── style.css └── js/ └── app.js ===== Przykład index.html ===== Moja aplikacja

Witaj w Javalin

Ćwiczenie: [[https://javalin.io/tutorials/html-forms-example|Przykład formularza HTML z Javalin]] ===== Before Handler ===== Wykonywane przed obsługą żądania // Globalny middleware Javalin app = Javalin.create(config -> { config.routes.before(ctx -> { System.out.println(">>> " + ctx.method() + " " + ctx.path()); // Logging, walidacja, autoryzacja }); // Przed dopasowaniem trasy config.routes.beforeMatched("/api/*", ctx -> { String token = ctx.header("Authorization"); if (token == null) { ctx.status(401).result("Brak tokenu"); } }); }); ===== After Handler ====== Wykonywane po obsłudze żądania: Javalin app = Javalin.create(config -> { config.routes.after(ctx -> { // run after all requests }); config.routes.after("/path/*", ctx -> { // runs after request to /path/* }); config.routes.afterMatched( // runs after all matched requests (including static files) ); }); ===== Resource-Oriented Design ==== REST API powinno być zorientowane na **zasoby** (rzeczy), nie na **akcje** (czasowniki): * Złe: ''/api/getUser'', ''/api/createProduct'' * Dobre: ''/api/users'', ''/api/products'' ===== Prawidłowe użycie metod HTTP ===== ^ Metoda ^ Akcja ^ Przykład ^ | GET | Pobranie | GET /api/users (pobranie listy) | | POST | Utworzenie | POST /api/users (utwórz nowego) | | PUT | Aktualizacja | PUT /api/users/1 (aktualizuj) | | DELETE | Usunięcie | DELETE /api/users/1 (usuń) | ===== apiBuilder ===== config.routes.apiBuilder(() -> { path("/users", () -> { get(UserController::getAllUsers); post(UserController::createUser); path("/{id}", () -> { get(UserController::getUser); }); ws("/events", UserController::webSocketEvents); }); }); ===== Role ===== Interfejs ''RouteRole'' do definiowania ról użytkowników: enum Role implements RouteRole { ANYONE, USER_READ, USER_WRITE } Przypisanie ról do endpointów: config.routes.get("/public", ctx -> ctx.result("Hello public"), Role.ANYONE); config.routes.get("/private", ctx -> ctx.result("Hello private"), Role.USER_READ); Pobieranie ról z kontekstu: config.routes.before("/private/*", ctx -> { Set roles = ctx.roles(); if (!roles.contains(Role.USER_READ)) { ctx.status(403).result("Forbidden"); } }); ====== Materiały ====== * Dokumentacja Javalin 7: https://javalin.io/documentation * Tutorial: https://javalin.io/tutorials * Przykłady na GitHub: https://github.com/tipsy/javalin-examples