Középhaladó Olvasási idő: ~9 perc

HTTP kezelés

request/response lifecycle, interceptors, filters, CORS, content negotiation

HTTP kezelés

A Spring HTTP kezelés lefedi a teljes request/response életciklust: filter-ek, interceptor-ok, CORS, content negotiation, fájlfeltöltés, cache, és a hibakezelés mechanizmusait.


1. Definíció

A HTTP kezelés a Spring webes rétegének azon komponenseit fogja össze, amelyek a request és response feldolgozás teljes életciklusát irányítják — a nyers HTTP kérés beérkezésétől a végleges válasz kiadásáig. Ez magában foglalja a Servlet Filter-eket, HandlerInterceptor-okat, CORS konfigurációt, content negotiation-t, fájlfeltöltést és a HTTP cache kezelést.

A Spring Boot automatikusan konfigurálja az alapvető HTTP komponenseket, de a finomhangolás megértése kulcsfontosságú production környezetben és interjúkon.

Client → Servlet Filter Chain → DispatcherServlet
    → HandlerInterceptor → Controller → Response
        (CORS, Content-Type, Cache headers)

2. Alapfogalmak

HTTP request/response életciklus

A teljes feldolgozási sorrend:

  1. Servlet Filter-ek (leghamarabb, servlet szinten)
  2. DispatcherServlet (Front Controller)
  3. HandlerInterceptor.preHandle() (handler szinten)
  4. Controller metódus végrehajtás
  5. HandlerInterceptor.postHandle() (response előtt)
  6. HandlerInterceptor.afterCompletion() (response után)
  7. Servlet Filter-ek (response úton vissza)

Servlet Filter

A Filter a legalsó szintű HTTP feldolgozó — a DispatcherServlet előtt és után fut:

@Component
@Order(1)
public class RequestLoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        long start = System.currentTimeMillis();

        chain.doFilter(request, response); // továbbengedés

        long duration = System.currentTimeMillis() - start;
        log.info("{} {} - {}ms",
                req.getMethod(), req.getRequestURI(), duration);
    }
}

HandlerInterceptor

Spring-szintű interceptor, eléri a Spring context-et:

@Component
public class AuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {
        String token = request.getHeader("Authorization");
        if (token == null) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false; // megállítja a kérést
        }
        return true; // továbbengedés
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) {
        // Response előtt, controller után
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) {
        // Response után (cleanup, logging)
    }
}

Regisztráció:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/public/**");
    }
}

Filter vs Interceptor összehasonlítás

Szempont Servlet Filter HandlerInterceptor
Szint Servlet (legalsó) Spring Handler
Spring context Nem garantált Elérhető
Lifecycle doFilter() preHandle/postHandle/afterCompletion
Regisztráció @Component / FilterRegistrationBean WebMvcConfigurer
Sorrend @Order / FilterRegistrationBean.setOrder() InterceptorRegistry.order()
Alkalmazás Security, encoding, compression Auth, logging, tenancy

3. Gyakorlati használat

CORS konfiguráció

A Cross-Origin Resource Sharing (CORS) szabályozza, mely domain-ekről érhetők el az API-k:

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://app.example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600); // preflight cache 1 óra
    }
}

Controller-szintű CORS:

@CrossOrigin(origins = "https://app.example.com")
@RestController
@RequestMapping("/api/users")
public class UserController { ... }

// Vagy metódus szinten
@CrossOrigin(maxAge = 3600)
@GetMapping("/{id}")
public UserDto findById(@PathVariable Long id) { ... }

Content negotiation

A content negotiation határozza meg a request/response formátumot:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(
            ContentNegotiationConfigurer configurer) {
        configurer
                .defaultContentType(MediaType.APPLICATION_JSON)
                .favorParameter(true)
                .parameterName("format")
                .mediaType("json", MediaType.APPLICATION_JSON)
                .mediaType("xml", MediaType.APPLICATION_XML);
    }
}

Stratégiák prioritás sorrendben:

  1. URL paraméter: /api/users?format=xml
  2. Accept header: Accept: application/xml
  3. URL kiterjesztés: /api/users.xml (nem ajánlott, deprecated)

Fájlfeltöltés (Multipart)

@RestController
@RequestMapping("/api/files")
public class FileController {

    @PostMapping("/upload")
    public ResponseEntity<String> upload(
            @RequestPart("file") MultipartFile file,
            @RequestPart("metadata") FileMetadata metadata) {

        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body("File is empty");
        }

        String filename = StringUtils.cleanPath(file.getOriginalFilename());
        storageService.store(file, filename);

        return ResponseEntity.ok("Uploaded: " + filename);
    }

    @PostMapping("/upload-multiple")
    public ResponseEntity<String> uploadMultiple(
            @RequestPart("files") List<MultipartFile> files) {
        files.forEach(f -> storageService.store(f, f.getOriginalFilename()));
        return ResponseEntity.ok("Uploaded " + files.size() + " files");
    }
}

Konfiguráció (application.properties):

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=50MB
spring.servlet.multipart.file-size-threshold=2KB

4. Kód példák

HTTP cache kezelés

@GetMapping("/{id}")
public ResponseEntity<ProductDto> findById(@PathVariable Long id) {
    ProductDto product = productService.findById(id);
    return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS)
                    .cachePublic()
                    .noTransform())
            .eTag(product.version().toString())
            .body(product);
}

// Conditional GET (If-None-Match)
@GetMapping("/{id}")
public ResponseEntity<ProductDto> findById(
        @PathVariable Long id,
        WebRequest webRequest) {
    ProductDto product = productService.findById(id);
    String etag = product.version().toString();

    if (webRequest.checkNotModified(etag)) {
        return null; // 304 Not Modified
    }

    return ResponseEntity.ok()
            .eTag(etag)
            .body(product);
}

Egyedi FilterRegistrationBean

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<RequestLoggingFilter> loggingFilter() {
        FilterRegistrationBean<RequestLoggingFilter> bean =
                new FilterRegistrationBean<>();
        bean.setFilter(new RequestLoggingFilter());
        bean.addUrlPatterns("/api/*");
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        bean.setName("requestLoggingFilter");
        return bean;
    }
}

Request/Response wrapper

public class CachingRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] body;

    public CachingRequestWrapper(HttpServletRequest request)
            throws IOException {
        super(request);
        this.body = request.getInputStream().readAllBytes();
    }

    @Override
    public ServletInputStream getInputStream() {
        return new DelegatingServletInputStream(
                new ByteArrayInputStream(body));
    }

    public String getBody() {
        return new String(body, StandardCharsets.UTF_8);
    }
}

ResponseBodyAdvice (response módosítás)

@RestControllerAdvice
public class ResponseWrapper implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType,
                            Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> converterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
        return new ApiResponse<>(true, body, null);
    }
}

5. Trade-offok

Filter vs Interceptor vs AOP

Szempont Servlet Filter HandlerInterceptor AOP (@Aspect)
Szint Servlet Spring Handler Metódus
Hozzáférés Request/Response Request/Response + Handler Metódus args + return
Használat Security, encoding Auth, logging, timing Tranzakció, audit
Sorrend Leghamarabb Filter után Controller-ben
Teszt MockFilterChain MockMvc Unit test

CORS: Global vs Controller-szintű

Megközelítés Előny Hátrány
WebMvcConfigurer Egy helyen, áttekinthető Nem rugalmas per-endpoint
@CrossOrigin Endpoint-szintű kontroll Szétszórt konfiguráció
CorsFilter Legkorábbi, Filter szintű Kézi implementáció

Cache stratégiák

Stratégia Használat
Cache-Control: max-age Statikus tartalom, ritkán változó adat
ETag + If-None-Match Dinamikus tartalom, 304 Not Modified
Last-Modified Fájl-alapú tartalom
no-cache Mindig revalidáció szükséges
no-store Érzékeny adat, tilos cache-elni

6. Gyakori hibák

❌ CORS konfiguráció hiányzik

Access to XMLHttpRequest at 'http://api.example.com'
from origin 'http://app.example.com' has been blocked by CORS policy

Mindig konfiguráld a CORS-t, ha a frontend és backend különböző domain-en van!

❌ Filter sorrend nem megfelelő

// ROSSZ: security filter a logging után fut
@Order(1)  // LoggingFilter
@Order(2)  // SecurityFilter → túl késő!

// JÓ: security filter előbb
@Order(1)  // SecurityFilter
@Order(2)  // LoggingFilter

❌ Multipart limit túl kicsi

# Alapértelmezett 1MB — nagy fájlnál "MaxUploadSizeExceededException"
spring.servlet.multipart.max-file-size=1MB

# Production:
spring.servlet.multipart.max-file-size=50MB
spring.servlet.multipart.max-request-size=100MB

❌ Cache header hiányzik

Cache-Control nélkül a böngésző saját heurisztikát használ — nem determinisztikus! Mindig adj meg explicit cache header-eket.

❌ Content-Type mismatch

// ROSSZ: kliens JSON-t küld, de nincs @RequestBody
@PostMapping
public void create(CreateUserRequest req) { ... } // form data binding!

// JÓ:
@PostMapping
public void create(@RequestBody CreateUserRequest req) { ... } // JSON

7. Mélyebb összefüggések

OncePerRequestFilter

A OncePerRequestFilter garantálja, hogy a filter kérésenként pontosan egyszer fut — fontos forward/include esetén:

@Component
public class JwtFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                     HttpServletResponse response,
                                     FilterChain filterChain)
            throws ServletException, IOException {

        String token = extractToken(request);
        if (token != null && jwtService.isValid(token)) {
            SecurityContextHolder.getContext()
                    .setAuthentication(jwtService.getAuth(token));
        }
        filterChain.doFilter(request, response);
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        return request.getServletPath().startsWith("/public");
    }
}

RequestContextHolder

A RequestContextHolder statikus hozzáférést biztosít az aktuális request-hez bármely Spring bean-ből:

public class AuditService {

    public String getCurrentClientIp() {
        ServletRequestAttributes attrs =
                (ServletRequestAttributes) RequestContextHolder
                        .currentRequestAttributes();
        return attrs.getRequest().getRemoteAddr();
    }
}

Figyelem: csak szinkron (Spring MVC) környezetben működik. WebFlux-ban ServerWebExchange-et kell használni.

WebMvcConfigurer testreszabási pontok

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) { }

    @Override
    public void addCorsMappings(CorsRegistry registry) { }

    @Override
    public void configureContentNegotiation(
            ContentNegotiationConfigurer configurer) { }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) { }

    @Override
    public void configureMessageConverters(
            List<HttpMessageConverter<?>> converters) { }

    @Override
    public void addArgumentResolvers(
            List<HandlerMethodArgumentResolver> resolvers) { }

    @Override
    public void addReturnValueHandlers(
            List<HandlerMethodReturnValueHandler> handlers) { }
}

Streaming response (nagy fájlok)

@GetMapping("/download/{id}")
public ResponseEntity<StreamingResponseBody> download(
        @PathVariable Long id) {
    return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"export.csv\"")
            .body(outputStream -> {
                try (var cursor = dataService.streamResults(id)) {
                    while (cursor.hasNext()) {
                        outputStream.write(cursor.next().getBytes());
                        outputStream.flush();
                    }
                }
            });
}

8. Interjúkérdések

  1. Mi a különbség a Servlet Filter és a HandlerInterceptor között? Filter: servlet szinten, leghamarabb fut, doFilter(). Interceptor: Spring handler szinten, preHandle/postHandle/afterCompletion, eléri a Spring context-et.

  2. Hogyan konfigurálod a CORS-t Spring-ben? Globálisan: WebMvcConfigurer.addCorsMappings(). Controller-szinten: @CrossOrigin. Filter-szinten: CorsFilter bean. Production-ben mindig explicit origin-okat adj meg.

  3. Mi a content negotiation? Az Accept header (és opcionálisan URL paraméter) alapján a Spring kiválasztja a megfelelő HttpMessageConverter-t (JSON, XML). A configureContentNegotiation() metódusban testreszabható.

  4. Hogyan kezeled a fájlfeltöltést? @RequestPart MultipartFile file. Konfiguráció: spring.servlet.multipart.max-file-size. Spring Boot automatikusan regisztrálja a MultipartResolver-t.

  5. Mi a Cache-Control és ETag? Cache-Control: utasítás a böngészőnek/proxy-nak (max-age, no-cache). ETag: tartalom hash, Conditional GET-tel 304 Not Modified.

  6. Mi a OncePerRequestFilter? Garantálja, hogy forward/include esetén is pontosan egyszer fut kérésenként. Tipikus használat: JWT token validáció.

  7. Hogyan tesztelned a filter-eket és interceptor-okat? MockMvc-vel integrációs teszt: mockMvc.perform(get("/api/...")).andExpect(...). Filter: MockFilterChain. Interceptor: MockHttpServletRequest + közvetlen metódus hívás.


9. Szószedet

Fogalom Jelentés
Servlet Filter Legalsó szintű HTTP feldolgozó (doFilter)
HandlerInterceptor Spring handler szintű pre/post processing
CORS Cross-Origin Resource Sharing, domain-közi hozzáférés szabályozás
Content negotiation Accept/Content-Type alapú formátum választás
MultipartFile Feltöltött fájl wrapper objektum
Cache-Control HTTP cache header (max-age, no-cache, no-store)
ETag Entity Tag, tartalom hash a conditional GET-hez
OncePerRequestFilter Pontosan egyszer fut kérésenként (forward-safe)
RequestContextHolder Statikus hozzáférés az aktuális request-hez
WebMvcConfigurer Központi testreszabási interfész (interceptor, CORS, converter)
FilterRegistrationBean Filter regisztráció Spring Boot-ban (URL pattern, sorrend)
StreamingResponseBody Nagy fájlok streaming letöltése

10. Gyorsreferencia

REQUEST LIFECYCLE:
  Client → Servlet Filter → DispatcherServlet
    → Interceptor.preHandle() → Controller
    → Interceptor.postHandle() → Response
    → Interceptor.afterCompletion()

FILTER:
  implements Filter / OncePerRequestFilter
  @Component @Order(1)
  FilterRegistrationBean (URL pattern, sorrend)

INTERCEPTOR:
  implements HandlerInterceptor
  preHandle() / postHandle() / afterCompletion()
  WebMvcConfigurer.addInterceptors()

CORS:
  WebMvcConfigurer.addCorsMappings()    Globális
  @CrossOrigin                          Controller/metódus
  CorsFilter bean                       Filter szintű

CONTENT NEGOTIATION:
  Accept header               Kliens preferencia
  Content-Type header          Request body formátum
  produces/consumes attribútum Controller szintű

FÁJLFELTÖLTÉS:
  @RequestPart MultipartFile
  spring.servlet.multipart.max-file-size=10MB
  spring.servlet.multipart.max-request-size=50MB

CACHE:
  CacheControl.maxAge(1, HOURS)  Cache időtartam
  .eTag(version)                 Conditional GET
  304 Not Modified               Nem változott tartalom

🎮 Játékok

10 kérdés