Webbarkitekturens utveckling: PHP-nostalgi och CORS-mardrömmen

”På den tiden räckte det med en enda fil…”

PHP som jag arbetade med under studietiden var verkligen bekvämt. I en enda index.php-fil kunde man lägga in HTML-taggar, öppna <?php ?> mitt i och även skriva kod för databasfrågor där. Det fanns ingen tydlig gräns mellan frontend och backend. När man hade deklarerat en variabel kunde man använda den var som helst i HTML-koden, och fel på grund av datakommunikation var nästan aldrig ett problem.

Men när jag i arbetslivet gick över till en stack med Vue.js i frontend och Spring Boot i backend började helvetet.

”Jag har ju startat Spring-servern lokalt, Vue-skärmen är också uppe, så varför kommer inte datan fram?”

Konsolen i Chrome DevTools var täckt av knallröda felmeddelanden.

Access to XMLHttpRequest at ... has been blocked by CORS policy

Vad är egentligen det här ”CORS” som stoppar min data? Och varför övergav vi den bekväma gamla metoden där en enda fil räckte, bara för att medvetet dela upp frontend och backend?

Förr bodde de under samma tak. Nu bor de i olika hus och måste passera en kontroll.

Vem ska laga maten? (SSR vs CSR)

För att förstå den här förändringen måste man först förstå vem som lagar ”rätten” som kallas webbsida, alltså HTML. Det är precis där skillnaden mellan SSR (Server Side Rendering) och CSR (Client Side Rendering) ligger.

1. SSR: en färdig lunchlåda (PHP, JSP)

  • Upplägg: Servern (köket) lagar ris, tillbehör och soppa och packar allt snyggt i en lunchlåda (HTML) innan den skickas iväg.
  • Klienten (webbläsaren): behöver bara lyfta på locket och visa innehållet.
  • Fördelar: Den första sidan laddas snabbt. Sökmotorer (SEO) har lätt att läsa innehållet.
  • Nackdelar: Även om man bara vill byta ut ett enda tillbehör måste man ta emot hela lunchlådan på nytt. (Skärmen blinkar)

2. CSR: en hemlevererad matkasse (React, Vue)

  • Upplägg: Servern lagar ingen mat. Den skickar bara råvarorna (JSON-data) och receptet (JavaScript).
  • Klienten (webbläsaren): tar emot råvarorna och steker och fräser dem själv på den egna datorn (i webbläsaren) för att bygga upp skärmen.
  • Fördelar: Eftersom man kan byta ut just de tillbehör som behövs blir övergångarna mellan vyer väldigt mjuka (som i en app). Belastningen på servern minskar.
  • Nackdelar: Den första laddningen går långsamt, eftersom receptet (JS-filerna) först måste laddas ner.

[Tip] CSR och SPA är inte exakt samma sak

Begreppen används ofta som om de vore samma sak, men strikt sett är de inte det.

  • CSR (Client Side Rendering): Det handlar om ”metod” (How). Det syftar på det sätt som webbläsaren ritar upp skärmen på.
  • SPA (Single Page Application): Det handlar om ”form” (What). Det syftar på en applikation som bara består av en enda sida.

Vanligtvis använder vi CSR för att bygga en SPA. Med andra ord: eftersom vi vill undvika blinkningar från full sidomladdning genom att använda en enda sida (SPA), väljer vi ett upplägg där webbläsaren själv ritar om de delar som behövs (CSR).

Skälet till att vi använder Vue.js eller React är användarupplevelsen. För att en webbplats ska kännas lika smidig som en mobilapp flyttade vi själva ”matlagningen” från servern till webbläsaren.

CORS, vakten som stoppar objudna gäster

När frontend (Vue) och backend (Spring) delas upp får vi i praktiken ”två hus”.

  • Frontendens hus: localhost:8080 (Vue-server)
  • Backendens hus: localhost:3000 (Spring-server)

Det är här problemet börjar. Av säkerhetsskäl följer webbläsaren en grundregel: ”lita inte lättvindigt på resurser som kommer från en annan Origin.” Det är SOP, alltså Same Origin Policy.

Tänk dig det här. Jag är inloggad på Naver, och från en webbplats skapad av en hacker skickas i smyg en begäran till Naver-servern som säger: ”ge mig användarens personuppgifter.” Om webbläsaren inte stoppade det, skulle all min information ligga helt öppen.

Därför blockerar webbläsaren som standard begäranden när origin, alltså domän eller port, är annorlunda. Men för utvecklingens skull har vi medvetet delat upp portarna. Vi vill bara använda vår egen data, och ändå säger webbläsaren, som en vakt: ”Din port stämmer inte. Tillträde förbjudet.” Det är den verkliga innebörden av ett CORS-fel, alltså Cross-Origin Resource Sharing.

[Code Verification] Utfärda ett passerkort

Lösningen på det här problemet är egentligen enkel. Servern, alltså backend, behöver bara skriva ett passerkort till webbläsaren, vakten, som säger: ”Den här personen är en gäst som jag själv har bjudit in. Släpp in honom.”

I Spring Boot kan det här passerkortet utfärdas med en enda konfigurationsfil.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 1. For alla API-vagar
                .allowedOrigins("http://localhost:8080") // 2. Tillat forfragningar fran denna adress
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 3. Tillat dessa typer av forfragningar
                .allowCredentials(true); // 4. Cookies och autentiseringsuppgifter tillats
    }
}

Analys:

  1. allowedOrigins: Det här är det viktigaste. Om du anger frontend-serverns adress (localhost:8080) här, sätter Spring-servern en stämpel i svarsheadern, Access-Control-Allow-Origin: http://localhost:8080, innan svaret skickas.
  2. Webbläsaren kontrollerar stämpeln, tänker ”Aha, serverns ägare har gett sitt tillstånd” och lämnar över datan till frontend.

Det viktiga är att man inte av bekvämlighet öppnar allt med allowedOrigins("*"). Det vore som att lämna ytterdörren vidöppen med en skylt där det står: ”Välkomna, tjuvar.”

Praktiskt råd: när ska man använda vad?

Betyder det då att CSR, alltså Vue eller React, alltid är det rätta svaret? Nej. I praktiken måste valet göras utifrån syftet.

  1. Adminsidor (Admin), dashboards:
    • Rekommendation: CSR (Vue, React)
    • Varför: Det är mycket datainteraktion, och sidorna behöver inte synas i sökmotorer. Snabb, app-liknande respons är viktig.
  2. Nyhetssajter, bloggar, startsidor för webbutiker:
    • Rekommendation: SSR (Next.js, Nuxt.js)
    • Varför: Sidorna måste hittas bra i Google-sökningar (SEO), och när en kund kommer in ska innehållet visas direkt i stället för en vit skärm.
  3. Små hobbyprojekt:
    • Rekommendation: Thymeleaf (SSR inbyggt i Spring Boot)
    • Varför: När du utvecklar ensam finns det ingen anledning att slita med att dela upp frontend och backend och konfigurera CORS. Att utveckla allt i ett, som på PHP-tiden, ger bäst produktivitet.

Avslutning: att ta sig över gränsen

Den gamla ”stor familj under samma tak”-modellen från PHP-tiden var bekväm, men ju mer allt växte i komplexitet, desto svårare blev det att hantera. Dagens modell med ”två separerade familjer”, frontend och backend, är mer besvärlig på grund av kommunikation och CORS, men den gör det också möjligt för varje sida att fokusera på sin egen roll.

Nästa gång du stöter på ett CORS-fel, försök därför att inte bara bli irriterad. Webbläsaren försöker inte jävlas med dig. Den genomför bara en noggrann kontroll för att skydda ditt hus, alltså din server.

Nu är vägen mellan frontend och backend öppen. Datan kan färdas fram och tillbaka. Men var lagras den egentligen på serversidan? Förr kunde jag bara skicka iväg en fråga och hämta det jag behövde. I Spring känns det däremot, på grund av saker som JPA och Entity, nästan som om man inte längre får skriva SQL direkt.

Nästa gång ska vi titta på hur den ”skolboksmässiga” modellen för databasdesign, normalisering, i praktiken kan bli ett hinder, och varför JPA så gärna försöker dölja SQL för oss.

Lämna en kommentar