관리 메뉴

♠개발자의 작은 서재♠

RESTful API의 기본 개념과 Spring에서 REST API 만드는 방법 ( 2 ) 본문

Spring MVC/웹 애플리케이션 개발

RESTful API의 기본 개념과 Spring에서 REST API 만드는 방법 ( 2 )

♠디지털 모험일지♠ 2024. 10. 21. 19:24

11. REST API 테스트 방법

RESTful API가 올바르게 동작하는지 확인하려면 테스트가 필수입니다. Spring에서는 MockMvc를 사용해 단위 테스트를 쉽게 작성할 수 있습니다. MockMvc를 통해 API 호출과 응답을 시뮬레이션하여 API의 기능을 테스트할 수 있습니다.

예시: REST API 테스트 작성하기

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldReturnUserById() throws Exception {
        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("John"));
    }
}

위의 코드는 /users/1 엔드포인트에 대한 GET 요청을 테스트합니다. MockMvc를 사용해 API 호출을 시뮬레이션하고, 응답의 상태 코드와 JSON 응답의 특정 값을 검증할 수 있습니다.

12. RESTful API에 대한 문서화

API 문서는 개발자와 클라이언트에게 API 사용 방법을 안내하는 중요한 요소입니다. Spring에서는 Swagger 같은 도구를 사용하여 자동으로 REST API 문서를 생성할 수 있습니다. Swagger는 API의 엔드포인트, 요청 파라미터, 응답 형식 등을 명확하게 문서화해 줍니다.

예시: Swagger 적용하기

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.example"))
            .paths(PathSelectors.any())
            .build();
    }
}

위의 코드는 Spring 애플리케이션에서 Swagger를 활성화하고 API 문서를 자동 생성하도록 설정하는 방법을 보여줍니다. Swagger가 설정되면 /swagger-ui.html 경로에서 API 문서를 확인할 수 있습니다.

13. RESTful API에서 캐싱 적용

대규모 시스템에서 성능을 향상시키기 위해 API 응답에 캐싱을 적용할 수 있습니다. 캐싱을 통해 클라이언트가 자주 요청하는 데이터를 캐시에서 가져오고, 서버의 부하를 줄일 수 있습니다. Spring에서는 @Cacheable 어노테이션을 사용하여 간단히 캐싱을 적용할 수 있습니다.

예시: 캐싱 적용하기

import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Cacheable("users")
    @GetMapping("/users/{id}")
    public User getUserById(@PathVariable int id) {
        // 데이터베이스 조회 (예시)
        return findUserById(id);
    }

    private User findUserById(int id) {
        // 실제 사용자 조회 로직
        return new User(id, "User " + id);
    }
}

위의 코드는 @Cacheable 어노테이션을 사용하여 특정 사용자 데이터를 캐시에 저장하고, 이후 동일한 요청이 들어올 때 캐시에서 데이터를 반환하는 방식으로 성능을 최적화합니다.

14. RESTful API에서 파일 업로드와 다운로드 처리

RESTful API에서 파일 업로드와 다운로드를 처리하는 방법도 많이 사용됩니다. Spring은 파일 업로드를 처리하기 위해 MultipartFile 클래스를 제공하며, 파일 다운로드를 위해 적절한 헤더와 함께 파일을 응답으로 반환할 수 있습니다.

예시: 파일 업로드 구현

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@RestController
public class FileUploadController {

    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
        // 파일 저장 경로 설정
        String uploadDir = "/uploads/";
        File saveFile = new File(uploadDir + file.getOriginalFilename());

        // 파일 저장
        file.transferTo(saveFile);

        return "File uploaded successfully: " + file.getOriginalFilename();
    }
}

위의 코드는 사용자가 /upload 엔드포인트로 파일을 업로드할 때, 해당 파일을 서버에 저장하는 방식입니다. MultipartFile 객체를 사용해 파일을 쉽게 처리할 수 있으며, transferTo() 메서드를 통해 파일을 지정된 경로에 저장할 수 있습니다.

예시: 파일 다운로드 구현

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;

@RestController
public class FileDownloadController {

    @GetMapping("/download/{filename}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
        File file = new File("/uploads/" + filename);
        Resource resource = new FileSystemResource(file);

        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);

        return new ResponseEntity<>(resource, headers, HttpStatus.OK);
    }
}

위의 코드는 사용자가 /download/{filename} 엔드포인트로 파일을 요청할 때, 지정된 경로에 저장된 파일을 클라이언트에게 다운로드할 수 있도록 반환하는 방식입니다. HttpHeaders 객체를 사용하여 파일 다운로드를 위한 적절한 헤더(예: Content-Disposition)를 설정할 수 있습니다.

15. RESTful API에서 비동기 처리

Spring에서는 비동기 처리를 통해 RESTful API의 성능을 향상시킬 수 있습니다. 특히, 시간이 오래 걸리는 작업을 비동기적으로 처리하면, 클라이언트의 요청에 대한 응답 시간을 단축할 수 있습니다.

예시: 비동기 처리 구현

import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.CompletableFuture;

@RestController
public class AsyncController {

    @Async
    @GetMapping("/process")
    public CompletableFuture<String> process() {
        // 시간이 오래 걸리는 작업 시뮬레이션
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000); // 5초 지연
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Process completed";
        });
    }
}

위의 코드는 /process 엔드포인트에서 비동기 처리를 구현한 방식입니다. @Async 어노테이션을 사용해 메서드를 비동기적으로 처리하며, CompletableFuture를 반환하여 작업이 완료된 후 응답을 보냅니다. 비동기 처리 방식은 서버의 효율성을 높이고 클라이언트에게 빠른 응답을 제공하는 데 유용합니다.

16. RESTful API에서 필터와 인터셉터 활용

RESTful API에서 필터와 인터셉터는 요청과 응답의 흐름을 제어하고, 특정 로직을 실행하는 데 유용하게 사용됩니다.

  • 필터(Filter): 요청이 처리되기 전에, 또는 응답이 클라이언트로 전송되기 전에 실행됩니다.
  • 인터셉터(Interceptor): 컨트롤러로 요청이 전달되기 전에, 또는 응답이 생성되기 전에 실행됩니다.

예시: 인터셉터 활용

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class LoggingInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 요청 처리 전에 실행되는 로직
        System.out.println("Request URI: " + request.getRequestURI());
        return true; // 요청이 계속 진행되도록 허용
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 응답이 완료된 후 실행되는 로직
        System.out.println("Response Status: " + response.getStatus());
    }
}

위의 코드는 요청이 처리되기 전과 응답이 완료된 후에 로깅하는 인터셉터를 구현한 예시입니다. preHandle 메서드는 컨트롤러로 요청이 전달되기 전에 실행되며, afterCompletion 메서드는 응답이 완료된 후에 실행됩니다. 이를 통해 요청 및 응답 정보를 로그로 남길 수 있습니다.

17. RESTful API에서 예외 처리

Spring에서는 전역 예외 처리를 통해 API 요청 중 발생한 예외를 처리할 수 있습니다. 이를 통해 클라이언트에게 일관된 오류 메시지와 상태 코드를 반환할 수 있습니다.

예시: 전역 예외 처리

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGlobalException(Exception ex) {
        return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

위의 코드는 @ControllerAdvice를 사용하여 전역 예외 처리를 구현한 방식입니다. 특정 예외인 ResourceNotFoundException이 발생했을 때 404 상태 코드와 함께 오류 메시지를 반환하며, 그 외의 예외에 대해서는 500 상태 코드와 함께 일반적인 오류 메시지를 반환합니다. 이를 통해 각 예외 상황에 맞는 일관된 응답을 클라이언트에게 제공할 수 있습니다.

18. RESTful API에서 인증과 권한 처리

RESTful API에서 인증(Authentication)과 권한(Authorization)을 처리하기 위해 Spring Security를 사용할 수 있습니다. 일반적으로 JWT (JSON Web Token) 기반 인증이 많이 사용됩니다.

예시: Spring Security와 JWT 인증 적용

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AuthController {

    private final AuthenticationManager authenticationManager;

    public AuthController(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @PostMapping("/login")
    public String authenticateUser(@RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                loginRequest.getUsername(),
                loginRequest.getPassword()
            )
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);

        // JWT 생성 후 반환 (JWT 생성 로직 추가 필요)
        return "JWT Token";
    }
}

위의 코드는 사용자가 /login 엔드포인트로 로그인 요청을 보낼 때, 인증이 성공하면 JWT 토큰을 생성하고 반환하는 방식으로 동작합니다. AuthenticationManager를 사용해 사용자의 인증 정보를 확인하고, 인증이 완료되면 JWT 토큰을 반환하여 클라이언트가 이를 이용해 이후의 요청에 인증 정보를 제공할 수 있도록 합니다.

19. RESTful API에서 로깅 및 모니터링

RESTful API의 안정성과 가용성을 높이기 위해 로깅과 모니터링은 필수입니다. Spring Boot는 기본적으로 SLF4JLogback을 지원하여 손쉽게 로깅을 추가할 수 있습니다. 또한, Actuator를 통해 API의 헬스 체크와 메트릭스를 제공할 수 있습니다.

예시: 로깅 적용하기

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoggingController {

    private static final Logger logger = LoggerFactory.getLogger(LoggingController.class);

    @GetMapping("/logs")
    public String logExample() {
        logger.info("GET /logs request received");
        return "Logging example";
    }
}

위의 코드는 /logs 엔드포인트에 대한 요청이 들어올 때, 로그를 기록하는 간단한 예시입니다. Logger 객체를 사용해 로깅할 수 있으며, 다양한 레벨(info, error, debug)을 통해 로그의 중요도에 따라 기록할 수 있습니다.

예시: Actuator 적용하기

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class CustomHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        // 커스텀 헬스 체크 로직
        boolean isServiceUp = checkServiceHealth();
        if (isServiceUp) {
            return Health.up().build();
        } else {
            return Health.down().withDetail("Error", "Service is down").build();
        }
    }

    private boolean checkServiceHealth() {
        // 서비스 상태 체크 로직 (예: DB 연결 상태 등)
        return true;
    }
}

위의 코드는 Spring Actuator를 사용해 커스텀 헬스 체크를 구현한 예시입니다. HealthIndicator 인터페이스를 구현하여 서비스의 상태를 점검하고, 그 결과를 Health.up() 또는 Health.down()으로 반환할 수 있습니다. Actuator는 /actuator/health 엔드포인트에서 애플리케이션의 상태를 확인할 수 있게 해주며, 이를 통해 서버의 상태 모니터링이 가능합니다.

20. RESTful API에서 Rate Limiting 적용

API 서버에 대한 과도한 요청을 방지하기 위해 Rate Limiting(요청 제한)을 적용할 수 있습니다. Spring에서는 이를 위해 Bucket4j 또는 Spring Cloud Gateway 등의 라이브러리를 사용할 수 있습니다.

예시: Bucket4j를 사용한 Rate Limiting 구현

import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Refill;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;

@RestController
public class RateLimitController {

    private final Bucket bucket;

    public RateLimitController() {
        Bandwidth limit = Bandwidth.classic(10, Refill.greedy(10, Duration.ofMinutes(1)));
        this.bucket = Bucket4j.builder().addLimit(limit).build();
    }

    @GetMapping("/limited")
    public String limitedEndpoint() {
        if (bucket.tryConsume(1)) {
            return "Request successful";
        } else {
            return "Too many requests, please try again later";
        }
    }
}

위의 코드는 Bucket4j 라이브러리를 사용하여 API의 Rate Limiting(요청 제한)을 구현한 예시입니다. Bucket 객체는 일정 시간 내에 허용된 요청량을 추적하며, 한도가 초과되면 요청을 거부하고 "Too many requests" 메시지를 반환합니다. 위 코드에서는 1분에 10개의 요청만 허용하며, 초과 시에는 요청이 차단됩니다.

이와 같은 Rate Limiting을 통해 서버에 과도한 부하를 방지하고, 공정한 리소스 배분을 보장할 수 있습니다.

Comments