클라이언트
가 보내는 HTTP 요청 메시지
의 첫 줄
에는 시작 라인인 요청 라인(request line)
이 있고, 그 밑으로 헤더(header)
, 본문(body)
가 있음.응답 메시지
의 첫 줄
에는 시작 라인인 상태 라인(status line)
이 있고, 그 밑에 헤더(header)
, 본문(body)
가 있음.100
~ 500
번대까지 5개의 그룹으로 나눠져있음.REST API
의 응답 표준으로 사용하는 JSON
은 키(key)
와 값(value)
의 쌍으로 된 속성으로 데이터를 표현함.JSON 데이터
나 배열
을 넣을 수도 있음.REST
HTTP URL
로 서버의 자원(resource)을 명시하고, HTTP 메서드
(POST, GET, PATCH/PUT, DELETE)로 해당 자원에 대해 CRUD
(생성, 조회, 수정, 삭제)하는 것.API
클라이언트
가 서버
의 자원을 요청할 수 있도록 서버에서 제공하는 인터페이스(Interface)
임.REST API
란 REST
기반으로 API
를 구현한 것.REST API
를 잘 구현하면 클라이언트
가 기기에 구애받지 않고 서버
의 자원을 이용할 수 있을 뿐만 아니라, 서버
가 클라이언트
의 요청에 체계적으로 대응할 수 있어서 서버 프로그램의 재사용성
과 확장성
이 향상됨.REST API
를 구현하려면 REST API의 주소
, 즉 URL
을 설계해야됨.GET
메서드./api/articles
: 전체 데이터 조회./api/articles/{id}
: 단일 데이터 조회.POST
메서드./api/articles
: 생성.PATCH
메서드./api/articles/{id}
: 내용 수정.DELETE
메서드./api/articles/{id}
: 삭제.URL 요청
을 받아 그 결과를 JSON
으로 변환해 줄 컨트롤러(controller)
가 필요함.REST API
로 요청
, 응답
을 주고 받을 때는 REST 컨트롤러(rest controller)
를 사용해야함.상태 코드
를 반환해주기 위해 ResponseEntity
클래스도 활용함.@RESTController
JSON
or 텍스트
같은 데이터를 반환함.@Controller
뷰(view) 페이지
를 반환함.좌 : RESTController
, 우 : Controller
@RestController
public class ArticleApiController {
@Autowired
private ArticleRepository articleRepository;
@GetMapping("/api/articles")
public List<Article> index() {
return articleRepository.findAll();
}
}
@RestController
@Autowired
DI
)@GetMapping("/api/articles")
"/api/articles"
주소로 오는 URL 요청을 받음.List<Article>
List<Article>
로 설정.return articleRepository.findAll();
repository
의 findAll
메서드를 통해 DB에 저장된 모든 데이터를 가져옴. @GetMapping("/api/articles/{id}")
public Article show(@PathVariable Long id) {
return articleRepository.findById(id).orElse(null);
}
@GetMapping("/api/articles/{id}")
"/api/articles/{id}"
주소로 오는 URL 요청을 받음.id
마다 URL 요청이 변경되므로 {id}
로 변수처리.public Article show(@PathVariable Long id)
Article
타입.id
로 검색하기 위해서 메서드의 매개변수로 값을 받아옴.{id}
와 매개변수 id
를 매핑 시켜줌.@PathVariable
를 이용해서.return articleRepository.findById(id).orElse(null);
findById
를 이용해서 id
로 엔티티
를 가져오거나, 데이터가 없을 경우 null
을 반환. @PostMapping("/api/articles")
public Article create(ArticleForm dto) {
Article article = dto.toEntity();
return articleRepository.save(article);
}
@PostMapping("/api/articles")
"/api/articles"
주소로 오는 URL 요청을 받음.public Article create(ArticleForm form)
Article
DTO
를 매개변수로 해서 값을 받아옴.Article article = form.toEntity();
DTO
-> Entity
변환.return articleRepository.save(article);
save()
메서드를 이용해서 DB에 저장하고 반환함.POST
로 데이터를 보내면 title
, content
에 null
이 나옴.@Controller
에서 데이터를 생성할 때는 메서드에 매개변수로 dto
를 받아오기만 하면 됐지만REST API
에서 데이터를 생성할 때는 JSON
데이터를 받아 와야 해서 매개변수
로 dto
를 썼다고 해서 받아올 수 있는 게 아님.이럴 때
매개변수 앞에 @RequestBody
어노테이션을 추가 해주면 됨.본문(body)
에 실어 보내는 데이터를 create()메서드의 매개변수
로 받아올 수 있음.@PatchMapping("/api/articles/{id}")
public ResponseEntity<Article> update(@PathVariable Long id, @RequestBody ArticleForm dto) {
Article article = dto.toEntity();
Article target = articleRepository.findById(id).orElse(null);
if (target == null || id != article.getId()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
Article updated = articleRepository.save(article);
return ResponseEntity.status(HttpStatus.OK).body(updated);
}
@PatchMapping("/api/articles/{id}")
@PatchMapping
으로 "/api/articles/{id}"
이 주소로 오는 URL 요청을 받음.@PathVariable Long id, @RequestBody ArticleForm dto
{id}
변수를 받아오기 위해서 @PathVariable
를 사용했고, 요청 메시지의 Body 데이터를 받아오기 위해서 @RequestBody
를 사용.if (target == null || id != article.getId())
존재하지 않는
article을 수정 요청한 경우, 수정 요청 id와 요청 본문의 id가 다를 경우
.예외처리
)return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
if문
이 참일 경우 잘못된 요청
이므로 ResponseEntity
의 상태(status)
에는 BAD_REQUEST(400)
, Body
에는 null
을 실어서 반환함.ResponseEntity<Article>
ResponseEntity<Article>
이렇게 설정해서 ResponseEntity
에 Article
을 담아서 반환 함.return ResponseEntity.status(HttpStatus.OK).body(updated);
updated
변수에 저장해서 ResponseEntity
에 담아서 응답으로 보냄.title
자체를 지우고 내용만 수정할 경우.
title
의 기존 값 자체가 없어져서 null
값이 되어버렸음. @PatchMapping("/api/articles/{id}")
public ResponseEntity<Article> update(@PathVariable Long id, @RequestBody ArticleForm dto) {
Article article = dto.toEntity();
log.info("id = {}, article = {}", id, article.toString());
Article target = articleRepository.findById(id).orElse(null);
if (target == null || id != article.getId()) {
log.info("잘못된 요청임 id = {}, article = {}", id, article.toString());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
target.patch(article); // 수정 된
Article updated = articleRepository.save(target); // 부분
return ResponseEntity.status(HttpStatus.OK).body(updated);
}
public void patch(Article article) { // 새로 만든 메서드.
if (article.title != null) {
this.title = article.title;
}
if (article.content != null) {
this.content = article.content;
}
}
patch()
메서드를 호출해서 수정할 내용이 있을 때만 동작하도록 구현.
@DeleteMapping("/api/articles/{id}")
public ResponseEntity<Article> delete(@PathVariable Long id) {
Article target = articleRepository.findById(id).orElse(null);
if (target == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
articleRepository.delete(target);
return ResponseEntity.status(HttpStatus.OK).build();
}
@DeleteMapping("/api/articles/{id}")
@DeleteMapping
으로 /api/articles/{id}
URL 요청을 받음.if (target == null)
articleRepository.delete(target);
return ResponseEntity.status(HttpStatus.OK).build();
.Body(null)
를 쓰거나 .build()
써도 결과는 같음.ResponseEntity
의 build()
메서드는 HTTP 응답의 Body가 없는
ResponseEntity 객체
를 생성함.ResponseEntity
REST Controller
의 반환형, 즉 REST API의 응답
을 위해 사용하는 클래스.HTTP 상태 코드
, 헤더
, 본문
을 실어서 보낼 수 있음.import java.net.URI;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
public class ResponseEntity<T> extends HttpEntity<T> {
private final Object status;
public ResponseEntity(HttpStatusCode status) {
this((Object)null, (MultiValueMap)null, (HttpStatusCode)status);
}
public ResponseEntity(@Nullable T body, HttpStatusCode status) {
this(body, (MultiValueMap)null, (HttpStatusCode)status);
}
public ResponseEntity(MultiValueMap<String, String> headers, HttpStatusCode status) {
this((Object)null, headers, (HttpStatusCode)status);
}
public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatusCode status) {
this(body, headers, (Object)status);
}
public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, int rawStatus) {
this(body, headers, (Object)rawStatus);
}
private ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, Object status) {
super(body, headers);
Assert.notNull(status, "HttpStatusCode must not be null");
this.status = status;
}
// 이하 코드 생략.
}
HttpStatus
열거형이름.상수 (형식)
import org.springframework.lang.Nullable;
public enum HttpStatus implements HttpStatusCode {
CONTINUE(100, HttpStatus.Series.INFORMATIONAL, "Continue"),
SWITCHING_PROTOCOLS(101, HttpStatus.Series.INFORMATIONAL, "Switching Protocols"),
PROCESSING(102, HttpStatus.Series.INFORMATIONAL, "Processing"),
EARLY_HINTS(103, HttpStatus.Series.INFORMATIONAL, "Early Hints"),
/** @deprecated */
@Deprecated(
since = "6.0.5"
)
CHECKPOINT(103, HttpStatus.Series.INFORMATIONAL, "Checkpoint"),
OK(200, HttpStatus.Series.SUCCESSFUL, "OK"),
CREATED(201, HttpStatus.Series.SUCCESSFUL, "Created"),
ACCEPTED(202, HttpStatus.Series.SUCCESSFUL, "Accepted"),
NON_AUTHORITATIVE_INFORMATION(203, HttpStatus.Series.SUCCESSFUL, "Non-Authoritative Information"),
// 이하 코드 생략.