Uniform (유니폼 인터페이스)
항상 같은형식, 같은 방법으로 정보를 요청/수정한다.
자원 조회: GET /users/1
자원 생성: POST /users
자원 수정: PUT /users/1
자원 삭제: DELETE /users/1
Stateless (무상태성)
세션/쿠키정보를 별도로 저장하지않아 요청만을 단순히 처리한다.
Cacheable (캐시 가능)
HTTP 헤더값을 이용하여 리소스가 변경되었는지 확인한다.
JAVA에서는 먼저 SpringBoot를 사용하여 RESTful API를 만들고, Last-Modified 헤더와 E-Tag를 활용해 구현한다.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant;
@RestController
@SpringBootApplication
public class CacheExampleApplication {
private final String cachedData = "{\"id\": 1, \"name\": \"John Doe\", \"email\": \"johndoe@example.com\"}";
private long lastModifiedTime = Instant.now().toEpochMilli();
private final String eTag = "\"1\""; // 리소스의 고유 식별자 (E-Tag)
@GetMapping("/user")
public ResponseEntity<String> getUser(@RequestHeader(value = "If-None-Match", defaultValue = "") String ifNoneMatch,
@RequestHeader(value = "If-Modified-Since", defaultValue = "") String ifModifiedSince) {
// E-Tag 확인
if (eTag.equals(ifNoneMatch)) {
// 수정되지 않았음을 나타내는 응답 (Not Modified)
return ResponseEntity.status(304).build();
}
// Last-Modified 헤더 확인
long ifModifiedSinceTime = Instant.parse(ifModifiedSince).toEpochMilli();
if (ifModifiedSinceTime >= lastModifiedTime) {
// 수정되지 않았음을 나타내는 응답 (Not Modified)
return ResponseEntity.status(304).build();
}
// 리소스가 수정되었음을 나타내는 응답을 전송하고, 업데이트된 Last-Modified와 E-Tag 헤더를 함께 포함
return ResponseEntity.ok()
.header("Last-Modified", Instant.ofEpochMilli(lastModifiedTime).toString())
.header("E-Tag", eTag)
.body(cachedData);
}
public static void main(String[] args) {
SpringApplication.run(CacheExampleApplication.class, args);
}
}
Self-descriptiveness (자체 표현 구조)
RESTFUL API 메세지만 봐도 쉽게 이해할 수 있는 구조이다.
{
"id": 1,
"username": "john_doe",
"email": "johndoe@example.com",
"age": 30,
"is_active": true
}
Client-Server 구조
REST서버는 API를 제공, 클라이언트는 세션/로그인 정보로 각각의 역할이 확실히 구분된다.
구조적인 유연성을 확보하는 방법 가능
계층형구조 보안, 로드밸런싱, 암호화 계층을 추가해 구조상 유연성을 둘수있다.
<dependencies>
<!-- Spring Boot Starter Web for RESTful API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security for security features -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Cloud Starter Netflix Ribbon for load balancing -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!-- Jasypt for encryption -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
</dependency>
</dependencies>
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@RibbonClient(name = "example-service", configuration = RibbonConfiguration.class)
public class RestfulServiceApplication {
public static void main(String[] args) {
SpringApplication.run(RestfulServiceApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
class RibbonConfiguration {
// Ribbon 구성이 필요한 경우 여기에 추가
}
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/public/**").permitAll() // /public/** 경로는 인증 없이 접근 가능
.anyRequest().authenticated()
.and()
.httpBasic(); // 기본 HTTP 인증 사용
}
}
@RestController
class ApiController {
@Value("${secret.message}")
private String secretMessage;
private final RestTemplate restTemplate;
public ApiController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@GetMapping("/public/resource")
public String publicResource() {
return "이것은 공개 리소스입니다.";
}
@GetMapping("/secure/resource")
public String secureResource() {
return "이것은 보안 리소스입니다. 비밀 메시지: " + secretMessage;
}
@GetMapping("/load-balanced/resource")
public String loadBalancedResource() {
// 로드 밸런싱된 방식으로 다른 서비스에 접근
String response = restTemplate.getForObject("http://example-service/resource", String.class);
return "다른 서비스로부터의 응답: " + response;
}
}
네트워크 기반의 중간매체
Proxy, Gateway는 클라이언트와 서버 사이에 위치하여 요청과 응답을 중개/가공함
<dependencies>
<!-- Spring Boot Starter Web for RESTful API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Starter Netflix Zuul for API Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
//api gateway의 메인 애플리케이션 클래스를 만든다.
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
'이론' 카테고리의 다른 글
표준프레임워크 퍼스트북: 1. 표준프레임워크소개 (0) | 2023.10.26 |
---|---|
HTTP 응답상태코드 (0) | 2023.10.23 |
REST API URI의 6가지 규칙 (0) | 2023.10.23 |
REST의 구성: 자원, 행위, 표현 (0) | 2023.10.23 |
REST와 REST API, 그리고 RESTful (0) | 2023.10.23 |