본문 바로가기
Java, Kotlin, Spring/Spring, Spring Boot

Springboot - Exception, HATEOAS, CORS

by Wordbe 2021. 1. 19.
728x90

Springboot - Web MVC

 

 

ExceptionHandler

에러를 처리하는 클래스를 만들어보자. 스프링 MVC 에서 어노테이션 기반 에러처리이다.

/except 요청을 받을 시 예외객체를 리턴한다.

@Controller
public class ExceptionController {

    @GetMapping("/except")
    public String except() throws SampleException {
        throw new SampleException();
    }

    @ExceptionHandler(SampleException.class)
    public @ResponseBody AppError sampleError(SampleException e) {
        AppError appError = new AppError();
        appError.setMessage("error.app.key");
        appError.setReason("IDK IDK");
        return appError;
    }
}
public class SampleException extends Throwable {
}
@Getter @Setter
public class AppError {
    private String message;
    private String reason;
}

예외객체 (SampleException) 가 리턴 될 때, @ExceptionHandler 가 앱에러 객체를 호출하는 방식으로, 에러 페이지를 만든다.

따라서 /except 요청이 떨어지면, 브라우저에는 메소드에서 세팅된 AppError 객체가 떨어진다. (xml로 전송된다.)

 

예외처리를 전역적으로 처리하려면, 에러를 다루는 클래스를 따로 만들고 그 위에 @ControllerAdvice 어노테이션을 붙인다. 그리고 그 안에 에러핸들러 메소드를 구현한다.

 

 


 

스프링부트가 제공하는 예외 처리기는 @BasicErrorController 이다. HTML과 JSON 응답을 지원한다.

BasicErrorController 위에는 @RequestMapping("${server.error.path:${error.path:/error}}") 어노테이션이 붙어있다.

{error.path:/error} 는 error.path 에 리소스가 없으면 error 주소를 요청하는 표현식이다.

이것을 커스터마이징하려면 BasicErrorController를 상속받아 ErrorController를 구현하면 된다.

 

 


좀 더 손 쉬운 에러페이지를 만들어보자.

상태 코드값에 따라 여러 에러 페이지를 보여줄 수 있다.

resources/static/error 또는 resources/template/error 아래

404.html (404 에러시 반환할 페이지), 5xx.html(500번대 에러시 반환할 페이지) 를 만들어주면 된다.

 

/resources/static/error/404.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>404</h1>
</body>
</html>

동적인 에러 컨트롤 등 커스텀 에러를 리졸브하려면 ErrorViewResovler 를 상속받아 새로운 클래스를 만들면 된다.

 

 

 


Spring HATEOAS

HATEOAS, Hypermedia As The Engine Of Application State 란?

  • REST API 를 충족시키는 개념 중 하나로, 서버는 현재 리소스와 연관된 링크 정보를 함께 클라이언트에게 제공하고, 클라이언트는 연관된 링크 정보를 바탕으로 리소스에 접근한다는 원칙이다.
  • 연관된 링크정보란 Relation 과 Href(Hypertext Reference)를 말한다. 예를 들어, market 이라는 릴레이션이 있다면, 이 정보와 함께 market 관련 하이퍼링크를 리소스에 담아 전송하는 것을 말한다.

Springboot에서 이를 사용하려면 아래 의존성을 추가한다.

 

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

서비스와 서비스를 호출하는 컨트롤러를 만들어보자.

@Getter @Setter @ToString
@Service
public class BuyService {

    private String name;
    private int amt;
}
...
import org.springframework.hateoas.EntityModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

@RestController
public class BuyController {

    @GetMapping("/buy")
    public EntityModel<BuyService> buyService() {
        BuyService buyService = new BuyService();
        buyService.setName("Apple");
        buyService.setAmt(3);

        // link 추가 (HATEOAS를 위해)
        EntityModel<BuyService> buyServiceEntityModel = new EntityModel<>(buyService);
        buyServiceEntityModel.add(linkTo(methodOn(BuyController.class).buyService()).withSelfRel());

        return buyServiceEntityModel;
    }
}

 

그리고 테스트코드를 만들어보자.

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

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(BuyController.class)
class BuyControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    public void buy() throws Exception {
        mockMvc.perform(get("/buy"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$._links.self").exists());
    }
}

테스트 출력 결과를 보면, 응답객체 body 에 아래와 같이 name, amt 필드 뿐만 아니라

_links 필드가 들어온 것을 볼 수 있다.

Body = {"name":"Apple","amt":3,"_links":{"self":{"href":"http://localhost/buy"}}}

 

HATEOAS 의존성을 추가하면 스프링부트가 다양한 것을 제공해주는데, 그 중 ObjectMapper 와 LinkDiscovers 가 있다.

ObjectMapper 는 리소스 객체를 json으로 또는 json을 객체로 변환할 수 있는 매퍼이다. Autowired로 주입해서 사용할 수도 있고, 프로퍼티 파일에서 spring.jackson.* 로 설정해서 사용할 수 있다.

LinkDiscovers 는 xpath 를 확장해서 만든 유틸리티성 클라이언트 API 이다. REST API 호출 시 hateoas 지원하는(링크, 하이퍼링크정보 포함하는) 요청이라면, 링크 정보(_links의 self 정보)를 메소드를 통해 가져올 수 있는 API이다.

 

 


CORS

 

SOP와 CORS

웹 브라우저가 지원하는 기술이다.

SOP(Same Origin Policy) 는 같은 오리진에만 요청을 보낼 수 있다는 규약이다.

CORS(Cross Origin Resource Sharing) 는 서로 다른 오리진끼리 리소스를 공유할 수 있는 표준기법이다.

기본은 SOP가 적용 되어있다. 하나의 리소스는 하나의 오리진으로만 호출할 수 있다.

오리진이란 아래 3개를 조합한 주소이다.

  • URI 스키마 (http, https)
  • hostname (co.wordbe, localhost 등)
  • port (8080, 18080 등)

같은 출처(오리진)라는 것은 스키마, 호스트(포트까지) 가 같다는 것이다. http://localhost:8080http://localhost:18080 은 다른 출처다. 그래서 localhost:8080 출처는 localhost:8080/hello 에 접근가능하지만, localhost:18080은 그렇지 못하다. 이것이 작동할 수 있도록 CORS 를 활용해보자.

 

 

인텔리제이에서 프로젝트 2개를 만들어서 다른 포트로 실행시켜보자. (2개의 오리진을 만들자.)

localhost:8080

@RestController
public class BuyController {

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

어플리케이션을 실행시킨 뒤, localhost:8080/hello 에 접속한다.

 

localhost:18080

다른 프로젝트를 열고, 포트 설정부터 바꾸자.

application.properties

server.port=18080

resources/static/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
Index
<script src="/webjars/jquery/3.5.1/jquery.min.js"></script>
<script>
    $(function () {
        $.ajax("http://localhost:8080/hello")
            .done(function (msg) {
                alert(msg);
            })
            .fail(function () {
                alert("fail");
            })
    })
</script>
</body>
</html>

화면을 하나만들어서, jQuery를 이용해서 http://localhost:8080/hello 를 호출하면 결과값을 msg 파라미터로 받아서 알람창에 msg를 출력하게 하자.

이제 어플리케이션을 실행하면, 에러가 발생한다. SOP 위반이기 떄문이다.

localhost:8080 서버 코드로 가서 CORS 어노테이션을 달아주자.

@CrossOrigin(origins="http://localhost:18080")
@RestController
public class BuyController {

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

이제 18080 서버는 8080/hello 데이터를 받을 수 있게 된다.


스프링 MVC는 @CrossOrigin 으로 CORS 를 지원한다. @Controller@RequestMapping 에 어노테이션을 추가하여 사용 할 수 있다.

혹은 WebMvcConfigurer 를 구현하여 만든 클래스 위에 @CrossOrigin 어노테이션을 달아서 글로벌하게 사용할 수도 있다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:18080");
    }
}

 

 

 

 

 

 

728x90

'Java, Kotlin, Spring > Spring, Spring Boot' 카테고리의 다른 글

Spring Data - MySQL  (0) 2021.01.20
Spring Data - 인메모리 DB  (0) 2021.01.20
Springboot - 템플릿, HtmlUnit  (2) 2021.01.19
Springboot - Web MVC  (128) 2021.01.18
Springboot - Devtools, Live Reload  (4) 2021.01.17

댓글