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:
- Servlet Filter-ek (leghamarabb, servlet szinten)
- DispatcherServlet (Front Controller)
- HandlerInterceptor.preHandle() (handler szinten)
- Controller metódus végrehajtás
- HandlerInterceptor.postHandle() (response előtt)
- HandlerInterceptor.afterCompletion() (response után)
- 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:
- URL paraméter:
/api/users?format=xml - Accept header:
Accept: application/xml - 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
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.
Hogyan konfigurálod a CORS-t Spring-ben? Globálisan:
WebMvcConfigurer.addCorsMappings(). Controller-szinten:@CrossOrigin. Filter-szinten:CorsFilterbean. Production-ben mindig explicit origin-okat adj meg.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ó.Hogyan kezeled a fájlfeltöltést?
@RequestPart MultipartFile file. Konfiguráció:spring.servlet.multipart.max-file-size. Spring Boot automatikusan regisztrálja aMultipartResolver-t.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.
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ó.
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