REST
- REST(Representational State Ransfer)
- 분산 네트워크 프로그래밍의 아키텍처 스타일
REST 특성
- 클라이언트/서버
- 클라이언트와 서버가 서로 독립적으로 구분되어야 함
- 서로간의 의존성 때문에 확장에 문제가 없어야 함
- 상태 없음
- 클라이언트와 서버 간의 통신시 상태가 없어야 함
- 서버는 클라이언트의 상태를 기억 X
- Layered Architecture
- 다계층 형태로 레이어를 추가하거나 수정하거나 제거할 수 있어야 함
- 확장성이 있어야 함
- 캐시
- 서버의 응답들은 캐시를 가지고 있을 수 있고, 없을 수 있다.
- 캐시가 있을 경우 클라이언트가 캐시를 통해 응답을 재사용 할 수 있다.
- 서버 부하 낮추고 서버 성능 향상
- Code on demand
- 특정 시점에 서버가 특정 기능을 수행하는 스크립트 또는 플러그인을 클라이언트에 전달해서 해당 기능을 동작
- Example
- 애플릿, 자바스크립트, 플래시
- 통합 인터페이스
- 서버와 클라이언트 간의 상호 작용은 일관된 인터페이스들 위에서 이뤄져야 한다.
REST 인터페이스 규칙
- 리소스 식별
- 웹 안에서 서로 구분할 수 있는 개념
- URI와 같은 고유 식별자를 통해 표현
- 표현을 통한 리소스 처리
- 같은 데이터에 대해서 표현할 때 JSON, XML, HTML 페이지와 같이 다양한 콘텐츠 유형으로 표현할 수 있다.
- 형태가 바뀌더라도 데이터는 바뀌지 않는다.
- 자기 묘사 메시지
- HTTP 통신 할 때 HTTP header에 메타 데이터 정보를 추가해서 데이터에 대한 설명을 나타내는 정보를 담는다.
- HATEOAS - Hypermedia As The Engine Of Application State
- 어플리케이션의 상태에 대한 하이퍼미디어
- REST API를 개발할 때도 단순히 데이터만 전달하지 않고 링크 정보까지 포함해서 한다.
- 일반적으로 서버 개발의 경우 고가용성과 확정이 요구
- REST의 특성을 지켜 개발을 진행하면 더욱 확장성있는 서버 어플리케이션 개발 가능
- API
- 데이터와 기능의 집합
- API 이용자는 API가 제공하는 데이터와 특정 기능을 얻을 수 있다.
- REST API
- API의 구조가 REST 요건들에 적합한 경우를 일컫음
- 이때 상태를 RESTful 하다고 한다.
리소스
- Resource
- REST의 핵심 개념
- 점근할 수 있고 조작할 수 있는 모든 것
- 각각의 리소스들을 그룹화 할 수 있다.
- Ex) 사람, 할 일, 이미지, 비디오 등등 명사화 할 수 잇는 대부분의 것들
리소스 구분
- URI - Uniform Resource Identifier
- 리소스 구분을 위해 리소스 식별자를 사용
-
https://jpoub.com/posts
- 포스트 리소스의 집합
-
https://jpoub.com/posts/1
- 포스트 리소스의 집합 중 첫 번째 요소
-
https://jpoub.com/posts/1/comments
- 포스트 리소스의 집합 중 Servlet 요소와 연관 된 코멘트
-
https://jpoub.com/posts/1/comments/245
- 코멘트 집합의 245번째 요소
URI 템플릿
- 일관성 있는 구조화된 REST API 만들기 위해 사용
- PathVariable 사용
Representation
- RESTful 리소스들은 추상화되어 있음
- XML, HTML, JSON 등 특정 형식을 가리지 않음
- 클라이언트에게 전달하기 전에 데이터를 직렬화해서 그시점에 데이터의 상태에 대한 표현을 해주면 됨
- 대부분의 REST API에서는 JSON 포맷으로 응답하도록 개발함
- 내부적으로 Contents negotiation 과정을 거침
- header에 Accept 정보를 읽어 JSON 또는 XML로 표출
Accept: text/xml, application/xml, application/json
REST API 제작
- REST API를 클라이언트에게 제공하기 위해서 클라이언트가 접근할 수 있는 엔트리 포인트를 만들어야 함
- 이 작업을 위해 Spring MVC에 컨트롤러를 사용할 수 있다.
REST Controller 활용
- JSON으로 제공하는 API 제작
모델 클래스 추가
@ApiModel
public class Todo {
private long id;
private String title;
public Todo() {
}
public Todo(long id, String title) {
this.id = id;
this.title = title;
}
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
}
컨트롤러 클래스 추가
@RestController
@RequestMapping(value = "/basic")
public class BasicController {
private final AtomicLong counter = new AtomicLong();
@RequestMapping(value = "/todo")
public Todo basic(){
return new Todo(counter.incrementAndGet(),"라면사오기");
}
}
-
@RestController
- 기존 컨트롤러 어노테이션에서 ResponseBody 어노테이션을 추가한 것
- 별도로 @ResponseBody를 사용하지 않고 REST API를 만들 수 있따.
-
@ResponseBody
- 실행 결과에 대한 처리를 위한 어노테이션
- 실행결과는 View를 거치지 않고 HTTP Response Body에 직접 입력됨
-
java.util.concurrent.atomic.AtomicLong;
- 동시성 문제 처리를 위해서 추가된 패키지
-
AtomicLong
가 여기에 속함- 이를 사용하는 이유는 다음과 같다.
- 일반 Long 타입의 변수에 대해서 다른 쓰레드가 접근할 수 있다.
- 이를 방지하고자 Atomic을 사용한다.
- Long 타입의 변수에 대해서 Thread-safe를 보장함
- 일반 Long 타입의 변수에 대해서 다른 쓰레드가 접근할 수 있다.
- 이를 사용하는 이유는 다음과 같다.
REST API에서 HTTP Method 사용
- HTTP Method
- GET/POST/PUT과 같이 HTTP 프로토콜 사용 시의 호출 방식
컨트롤러 메서드에 POST 메서드 매핑
- REST API에서 POST 메서드는 새로운 리소스를 생성할 때 사용
@RequestMapping(value = "/todop", method = RequestMethod.POST)
public Todo postBasic(@RequestParam(value = "todoTitle") String todoTitle){
return new Todo(counter.incrementAndGet(), todoTitle);
}
-
@RequsetMapping
- 이 값을 키로 해서 클라이언트에게 파라미터 값을 전달 받을 수 있다.
응답 헤더 활용
- REST API에서 응답 헤더는 클라이언테에게 메타 정보로 활용될 수 있다.
- 통신 효율 향상
- 요청 결과에 대한 명확한 결과
-
ResponseEntity
- 응답 헤더에 대한 구현체
- HttpEntity를 상속받은 클래스
- Http 응답에 대한 상태값을 표현할 수 있음
@RequestMapping(value = "/todor", method = RequestMethod.POST)
public ResponseEntity<Todo> postBasicResponseEntity(@RequestParam(value = "todoTitle") String todoTitle){
return new ResponseEntity(new Todo(counter.incrementAndGet(), todoTitle), HttpStatus.CREATED);
}
URI 템플릿 활용
PathVariable 이용한 URL 표현
@RequestMapping(value = "/todos/{todoId}", method = RequestMethod.GET)
public Todo getPath(@PathVariable int todoId){
Todo todo1 = new Todo(1L, "문서쓰기");
Todo todo2 = new Todo(2L, "기획회의");
Todo todo3 = new Todo(3L, "운동");
Map<Integer,Todo> todoMap = new HashMap();
todoMap.put(1, todo1);
todoMap.put(2, todo2);
todoMap.put(3, todo3);
return todoMap.get(todoId);
}
- RequestMapping은 URI 템플릿을 사용 -> GET 메서드 사용
-
{todoId}
를 통해 값을 받아옴
HATEOAS를 이용한 자기주소정보 표현
- REST API의 문제 상황
- REST API를 이용하는 클라이언트들은 하나의 트랜잭션 안에서 여러 API를 사용하는 경우
- 기본이 되는 URI는 공통으로 사용하지만 하위 파라미터나 하위 URL에 대해서는 인지하지 못하는 경우
- 이유
- 전체 URI는 요청 파라미터에 따라 변경될 수 있기 때문
- HATEOAS를 이용해서 해당 결과를 얻을 수 있는 전체 URI를 반환할 수 있는 정보를 제공할 수 있음
- 클라이언트는 해당 정보를 통해 해당 결과를 얻을 수 있는 전체 URI 정보를 알 수 있음
HATEOAS를 이용한 URI 정보 표현
설정
- 라이브러리 추가
compile("org.springframework.boot:spring-boot-starter-hateoas")
public class TodoResource extends ResourceSupport {
private String title;
public TodoResource() {}
public TodoResource(String title) { this.title = title; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
}
@RequestMapping(value = "/todoh", method = RequestMethod.GET)
public ResponseEntity<TodoResource> geth(@RequestParam(value = "todoTitle") String todoTitle){
TodoResource todoResource = new TodoResource(todoTitle);
todoResource.add(linkTo(methodOn(BasicController.class).geth(todoTitle)).withSelfRel());
return new ResponseEntity(todoResource, HttpStatus.OK);
}
REST API 문서화
Spring과 swagger를 이용
- Spring API 문서 페이지를 자동 생성해서 지속적으로 동기화 할 수 있도록 함
swagger 설정 및 라이브러리 추가
- swagger
- swagger-UI
- API 정보를 JSON으로 매핑해서 문서로 생성
- swagger-editor
- swagger 문서를 작성
- 언어별 swagger SDK
- swagger-UI
- 의존성 추가
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.4.0' compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.4.0'
- SwaggerConfig 클래스 생성
@Configuration @EnableSwagger2 @Profile({"dev"}) public class SwaggerConfig { @Bean public UiConfiguration uiConfig() { return UiConfiguration.DEFAULT; } private ApiInfo metadata() { return new ApiInfoBuilder() .title("JPub Spring Boot") .description("Spring Boot Rest API") .version("1.0") .build(); } @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .groupName("basic") .select() .apis(RequestHandlerSelectors.any()) .paths(regex("/basic/.*")) .build() .apiInfo(metadata()); } }
- 기본 생성을 하면 Basic Controller 하나만 만들었지만 다른 것들도 추가로 표시됨
- 따라서, Docket의 설정을 변경해 basic으로 변경해 줌
- 컨트롤러의 기본 URI가 basic으로 시작하므로
- 따라서, Docket의 설정을 변경해 basic으로 변경해 줌
- 기본 생성을 하면 Basic Controller 하나만 만들었지만 다른 것들도 추가로 표시됨
REST 클라이언트 개발
- 업무별로 API를 만들어서 서로 API 간 통신을 통해 데이터를 주고 받는 경우가 많음
- API 서버를 만들기 위해서 다른 서버 API를 연동해야 하는 일도 있음
RestTemplate
- RestTemplate
- 스프링 MVC 라이브러리에 포함된 클래스
- 대게 클라이언트 라이브러리의 제공 형태는 get, post와 같은 HTTP 메서드에 대응되는 메서드들을 제공
- 다양한 메시지 컨버터를 내장
- JSON 응답을 Map 또는 모델 클래스로 간편하게 변환해서 사용
- Apache httpClient에 대한 의존성
- RestTemplate 의존성 추가
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.2'
RestTemplate 네트워크 속성 설정
- 이는 안해도 무관
- 안한다면, Timeout 설정에 따라 성능 이슈가 발생 할 수 있음
- HttpClient와 관련된 설정은 HttpComponentsClientHttpRequestFactory 클래스의 인스턴스를 이용함
- set 메서드를 이용해서 설정
public HttpComponentsClientHttpRequestFactory restTemplate() { HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setReadTimeout(READ_TIMEOUT); factory.setConnectTimeout(CONNECT_TIMEOUT); RestTemplate restTemplate = new RestTemplate(factory); return factory; }
- RequestConfig를 이용해서 설정 후 그 인스턴스를 통해 초기화
public HttpComponentsClientHttpRequestFactory restTemplate() { RequestConfig config = RequestConfig.custom() .setConnectTimeout(timeout) .setConnectionRequestTimeout(timeout) .setSocketTimeout(timeout) .build(); ClosealbeHttpClient httpClient = HttpClientBuilder .create() .setMaxConnTotal(MAX_CONN_TOTAL) .setMaxConnPerRoute(config) .build(); factory.setHttpClient(httpClient); RestTemplate restTemplate = new RestTemplate(factory); return new HttpComponentsClientHttpRequestFactory(httpClient); }
- set 메서드를 이용해서 설정
UriComponentsBuilder 활용
- UriComponentsBuilder
- Builder 방식으로 URI를 만드는데 필요한 정보들을 메서드를 이용해 만들 수 있으며 인코딩도 가능하다.
- RestTemplate을 이용해서 POST 방식으로 데이터를 요청할 때
- MultiValueMap을 사용해서 데이터를 전달해야 하지만 이를 이용하면 따로 만들지 않아도 됨
UriComponentsBuilder 생성
- 생성자의 접근 제한자가
protected
임- 직접 호출 불가
- 메서드 체이닝 방식으로 URI를 구성하는데 필요한 정보들을 추가
UriComponentsBuilder.newInstance().schem().method().method.
- http:, ftp:, https: 이와 같은 정보는 프로토콜을 의미함
- UriComponentsBuilder에서는 scheme 정보에 해당
UriComponentsBuilder로 파라미터 전달
UriComponentsBuilder.newInstance().schem("http")
.host("movie.naver.com")
.port(80)
.path("/movie/bi/mi/basic.nhn")
.queryParam("code", 146506)
.build()
.encode()
.toUri();
- 다수의 파라미터 일 경우
queryParam
를 추가해서 사용
UriComponent로 PathVariable이 포함된 URL 만들기
- API uri 자체가 파라미터가 되는 상황도 있음
- ex) http://booktest.com/book/1
- uri에 id 정보가 포함되는 경우
- ex) http://booktest.com/book/1
UriComponentsBuilder.newInstance().schem("http")
.host("test.book.com")
.port(80)
.path("/book/{bookId}")
.build().expand(bookId)
.encode()
.toUri();
- uri를 {}로 감싸고 expand 메서드를 추가함으로 PathVariable에 해당하는 변숫값 전달