• Home
  • About
    • Zzu-h photo

      Zzu-h

      주니어 Android 개발자입니다.

    • Learn More
    • Email
    • Instagram
    • Tistory
    • Github
  • Posts
    • All Posts
    • All Tags
    • All Categories
  • Projects

REST API 서버 만들기

19 Jul 2021

Reading time ~7 minutes

REST

  • REST(Representational State Ransfer)
    • 분산 네트워크 프로그래밍의 아키텍처 스타일

REST 특성

  • 클라이언트/서버
    • 클라이언트와 서버가 서로 독립적으로 구분되어야 함
    • 서로간의 의존성 때문에 확장에 문제가 없어야 함
  • 상태 없음
    • 클라이언트와 서버 간의 통신시 상태가 없어야 함
    • 서버는 클라이언트의 상태를 기억 X
  • Layered Architecture
    • 다계층 형태로 레이어를 추가하거나 수정하거나 제거할 수 있어야 함
    • 확장성이 있어야 함
  • 캐시
    • 서버의 응답들은 캐시를 가지고 있을 수 있고, 없을 수 있다.
    • 캐시가 있을 경우 클라이언트가 캐시를 통해 응답을 재사용 할 수 있다.
      • 서버 부하 낮추고 서버 성능 향상
  • Code on demand
    • 특정 시점에 서버가 특정 기능을 수행하는 스크립트 또는 플러그인을 클라이언트에 전달해서 해당 기능을 동작
    • Example
      • 애플릿, 자바스크립트, 플래시
  • 통합 인터페이스
    • 서버와 클라이언트 간의 상호 작용은 일관된 인터페이스들 위에서 이뤄져야 한다.

REST 인터페이스 규칙

  1. 리소스 식별
    • 웹 안에서 서로 구분할 수 있는 개념
    • URI와 같은 고유 식별자를 통해 표현
  2. 표현을 통한 리소스 처리
    • 같은 데이터에 대해서 표현할 때 JSON, XML, HTML 페이지와 같이 다양한 콘텐츠 유형으로 표현할 수 있다.
    • 형태가 바뀌더라도 데이터는 바뀌지 않는다.
  3. 자기 묘사 메시지
    • HTTP 통신 할 때 HTTP header에 메타 데이터 정보를 추가해서 데이터에 대한 설명을 나타내는 정보를 담는다.
  4. 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를 보장함

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
  • 의존성 추가
      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으로 시작하므로

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);
        }
      

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 정보가 포함되는 경우
UriComponentsBuilder.newInstance().schem("http")
    .host("test.book.com")
    .port(80)
    .path("/book/{bookId}")
    .build().expand(bookId)
    .encode()
    .toUri();
  • uri를 {}로 감싸고 expand 메서드를 추가함으로 PathVariable에 해당하는 변숫값 전달


SpringSprng-bootRESTREST API Share Tweet +1