Skip to main content
Dat 2. semester
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Request og response

Når en bruger arbejder med vores webapplikation, foregår der hele tiden en samtale mellem browseren og serveren.

Browseren sender en request. Serveren behandler requesten og sender et response tilbage. Det kaldes en request/response cycle.

Request Response Cycle

I vores applikation kan cyklussen for eksempel se sådan ud:

Browser
HTTP request
Javalin / Controller
Service
Mapper
Database
Service
Controller
Thymeleaf
HTTP response
Browser

Ikke alle requests går hele vejen til databasen. Hvis brugeren for eksempel bare skal have vist en statisk side, kan flowet være meget kortere.

Det vigtige er at forstå, hvor de forskellige ting sker.


Requesten

En HTTP-request sendes fra browseren til serveren.

En request indeholder blandt andet:

  • en HTTP-metode
  • en URL/path
  • headers
  • eventuelt data

Data, som browseren sender med requesten, kan blandt andet ligge i request body.

GET

GET bruges typisk, når browseren beder serveren om noget.

For eksempel:

GET /users

Browseren siger i praksis:

“Jeg vil gerne have /users.”

Query-parametre kan være en del af URL’en:

/users?id=42

I Javalin kan vi hente query-parametre:

String id = ctx.queryParam("id");

Flere query-parametre:

/users?id=42&sort=name

kan hentes sådan:

String id = ctx.queryParam("id");
String sort = ctx.queryParam("sort");

GET bruges typisk til at hente eller vise data.


POST

POST bruges typisk, når browseren sender data til serveren, for eksempel fra en formular.

En loginformular kan sende:

POST /login

med data som:

username=ada
password=secret

I Javalin kan form-data hentes sådan:

String username = ctx.formParam("username");
String password = ctx.formParam("password");

POST bruges ofte til handlinger som:

  • oprette en bruger
  • logge ind
  • oprette en task
  • sende en formular

PUT og PATCH

PUT og PATCH bruges typisk, når en eksisterende ressource skal opdateres.

De møder man især i API’er.

Eksempel:

PUT /users/42

betyder, at brugeren med ID 42 skal opdateres.

PATCH bruges typisk til en delvis opdatering.

I vores server-renderede applikation vil vi primært arbejde med GET og POST.


DELETE

DELETE bruges til at slette en ressource.

Eksempel:

DELETE /users/42

Igen er dette især relevant i API-baserede applikationer.

I vores server-renderede applikation kan en slettefunktion i stedet eksempelvis være lavet som en formular, der sender en POST-request.


At modtage requesten

Requesten bliver modtaget af webserveren.

I vores Javalin-applikation er Javalin baseret på Jetty, som håndterer den underliggende HTTP-kommunikation.

Javalin giver os adgang til den aktuelle request gennem ctx.

config.routes.get("/users", ctx -> {

    // ctx repræsenterer den aktuelle request/response

});

ctx er vores forbindelse til den aktuelle HTTP-request og response.

Vi kan blandt andet bruge ctx til at:

  • hente data fra requesten
  • hente headers
  • hente form-parametre
  • hente query-parametre
  • hente path-parametre
  • gemme data i sessionen
  • sende data til en template
  • sætte HTTP-status
  • sende en redirect
  • rendre en HTML-side

Headers

HTTP-requesten indeholder headers med information om requesten.

En header kan læses med:

String host = ctx.header("Host");

Der findes mange forskellige HTTP-headers.

De fleste af dem behøver vi ikke arbejde direkte med i vores almindelige applikationer.


At behandle requesten

Når Javalin har fundet den route, som passer til requesten, bliver route-handleren udført.

Et meget simpelt eksempel:

config.routes.get("/hello", ctx -> {
    ctx.result("Hej verden");
});

Her sker der ikke andet end:

GET /hello
route handler
"Hej verden"

I en rigtig applikation skal der ofte ske mere.

For eksempel ved login:

POST /login
hent username og password
UserService.login(...)
login lykkes?

Her kommer vores lagdeling ind.


Controlleren

Controlleren tager sig af kommunikationen mellem HTTP og resten af applikationen.

Controlleren kan for eksempel:

  1. hente data fra requesten
  2. kalde en service
  3. vælge hvad der skal ske bagefter
  4. sende et resultat tilbage til browseren

Eksempel:

public static void login(Context ctx, UserService userService) {

    String username = ctx.formParam("username");
    String password = ctx.formParam("password");

    User user = userService.login(username, password);

    if (user != null) {
        ctx.sessionAttribute("user", user);
        ctx.redirect("/tasks");
    } else {
        ctx.status(401);
        ctx.attribute("error", "Forkert brugernavn eller password");
        ctx.render("login.html");
    }
}

Controlleren ved altså noget om HTTP. Den ved for eksempel, hvad formParam, redirect, status og render betyder.


Servicen

Forretningslogikken skal ikke ligge i controlleren. Den placerer vi i en serviceklasse.

Eksempel:

public User login(String username, String password) {

    User user = userMapper.getUserByUsername(username);

    if (user != null &&
        user.getPassword().equals(password)) {

        return user;
    }

    return null;
}

Servicen arbejder med programmets regler.

Den skal ikke vide noget om:

  • HTML
  • Thymeleaf
  • browseren
  • HTTP-statuskoder
  • ctx

Det betyder, at den samme service kan bruges fra andre dele af programmet.


Mapperen

Hvis servicen skal hente eller gemme data i databasen, kalder den en mapper.

Controller
Service
Mapper
Database

Mapperen tager sig af SQL og JDBC.

Eksempel:

public User getUserByUsername(String username) {

    String sql = """
            SELECT username, password
            FROM users
            WHERE username = ?
            """;

    // JDBC-kode

}

Controlleren behøver ikke vide, hvordan brugeren blev hentet.

Den ved bare, at den kan spørge servicen:

User user = userService.login(username, password);

At forberede response

Når requesten er behandlet, skal serveren sende et response tilbage til browseren.

Et response kan indeholde:

  • en HTTP-statuskode
  • headers
  • en response body

I vores server-renderede applikation er response body ofte HTML.

Response body indeholder det indhold, som serveren sender tilbage til klienten. I Javalin kan vi for eksempel sætte response body med ctx.result().


Sende tekst

Den enkleste response er:

ctx.result("Hej verden");

Her er “Hej verden” en streng, som sendes tilbage som response body. Når vi bruger ctx.result() til at sende tekst tilbage, sætter Javalin Content-Type til text/html i headeren, så browseren fortolker response body som HTML.

Browseren modtager teksten:

Hej verden

Sætte statuskode

Vi kan sætte HTTP-statuskoden:

ctx.status(404);

eller kombinere status og tekst:

ctx.status(404).result("Brugeren blev ikke fundet");

Eksempler på statuskoder, som er gode at kende:

StatusBetydning
200OK
201Created
400Bad Request
401Unauthorized
403Forbidden
404Not Found
500Internal Server Error

Vi behøver ikke kunne alle HTTP-statuskoder udenad.

Det vigtigste er at forstå, at statuskoden fortæller klienten, hvordan requesten gik.


Redirect

Serveren kan bede browseren om at gå til en anden URL:

ctx.redirect("/tasks");

Et typisk flow efter login er:

POST /login
login lykkes
ctx.redirect("/tasks")
GET /tasks

Browseren foretager derefter en ny request til /tasks.


Thymeleaf

Når response skal være en HTML-side, bruger vi Thymeleaf.

Før vi renderer siden, kan controlleren lægge data i request scope:

ctx.attribute("user", user);
ctx.attribute("tasks", tasks);

Derefter renderer vi templaten:

ctx.render("tasks.html");

Thymeleaf bruger dataene til at generere den HTML, som sendes til browseren.

I templaten kan vi for eksempel skrive:

<h1 th:text="${user.username}">
    Brugernavn
</h1>

Og:

<ul>
    <li th:each="task : ${tasks}"
        th:text="${task.title}">
        Task
    </li>
</ul>

Browseren modtager almindelig HTML.

Den kender ikke noget til Thymeleaf.


Session

Nogle data skal kunne bruges på tværs af flere requests.

Et eksempel er den bruger, der er logget ind.

Vi kan gemme brugeren i sessionen:

ctx.sessionAttribute("user", user);

På en senere request kan vi hente brugeren:

User user = ctx.sessionAttribute("user");

Thymeleaf kan også læse sessionens attributter:

<p th:text="${session.user.username}">
    Brugernavn
</p>

Sessionen gør det derfor muligt at huske oplysninger mellem requests.


Et komplet eksempel

Lad os følge et login fra start til slut.

Browseren viser loginformularen:

GET /login

Javalin finder login-routen:

config.routes.get("/login", ctx -> {
    ctx.render("login.html");
});

Browseren viser formularen.

Brugeren udfylder:

username = ada
password = secret

og sender formularen:

POST /login

Controlleren henter værdierne:

String username = ctx.formParam("username");
String password = ctx.formParam("password");

Controlleren kalder servicen:

User user = userService.login(username, password);

Servicen kalder mapperen:

UserService
UserMapper
PostgreSQL

Mapperen finder brugeren.

Servicen kontrollerer loginoplysningerne og returnerer en User.

Controlleren gemmer brugeren i sessionen:

ctx.sessionAttribute("user", user);

Derefter redirecter controlleren:

ctx.redirect("/tasks");

Browseren laver nu en ny request:

GET /tasks

Controlleren henter brugeren fra sessionen:

User user = ctx.sessionAttribute("user");

Controlleren henter brugerens tasks gennem servicen:

List<Task> tasks = taskService.getTasksForUser(user);

Data sendes til Thymeleaf:

ctx.attribute("user", user);
ctx.attribute("tasks", tasks);
ctx.render("tasks.html");

Thymeleaf genererer HTML.

HTML’en sendes tilbage til browseren.

Hele forløbet:

Browser
   │ POST /login
Javalin
UserController
UserService
UserMapper
PostgreSQL
UserMapper
UserService
UserController
Thymeleaf
HTML response
Browser

Det er request/response-cyklussen i vores webapplikation.


Hvem har ansvaret?

Det kan være nyttigt at tænke på lagene sådan:

DelAnsvar
BrowserSender requests og viser responses
JavalinModtager HTTP-requests og kobler dem til routes
ControllerHåndterer HTTP og koordinerer requesten
ServiceIndeholder forretningslogik
MapperKommunikerer med databasen
PostgreSQLGemmer data
ThymeleafGenererer HTML

Det vigtigste er ikke at huske alle klasserne.

Det vigtigste er at kunne følge data gennem systemet:

Request
Controller
Service
Mapper
Database

Response
Controller
Service
Thymeleaf

Når du arbejder med en fejl, kan du derfor spørge:

Hvor i request/response-cyklussen går det galt?