본문 바로가기

Computer Science

REST API / Spring hateoas

반응형

REST API

REST API를 공부하면서 이응준님의 "그런 REST API로 괜찮은가"를 참조했다.

https://tv.naver.com/v/2292653

 

Day1, 2-2. 그런 REST API로 괜찮은가

NAVER Engineering

tv.naver.com

REST API란 지금까지 client가 api요청을 보내면 그 값을 JSON, XML로 반환해주는 api정도?? 로만 인식하고 있었다.
영상을 들어보니 REST API라는 말이 맞을 수도 있고 틀릴 수도 있다는데 REST API를 만든 로이필딩씨께서는 self-descriptive messages, HATEOAS를 충족하지 못하면 REST API가 아니라고 한다..?

REST API이기 위한 몇가지 조건이 있다. 

 

Uniform Interface의 제약조건

identification of resources

resource가 uri로 식별되면 된다. (ex : api/users/name 이런식으로 uri상에서 정보가 나타야한다는것 같다.)

 

manipulation of resources through representations

representation전송을 통해서 resource를 조작해야 한다. (GET, POST, UPDATE ...)

ex) POST 요청인데 데이터를 삭제하는 요청은 없어야 한다.

 

self-descriptive messages

메시지는 스스로를 설명해야한다.

GET / HTTP/1.1

- 이 HTTP 요청 메시지는 뭔가 빠져있어서 self-descriptive하지 못하다.

GET / HTTP/1.1
Host : www.example.org

- 목적지를 추가하면 이제 self-descriptive 하다라고 말할 수 있다.

HTTP/1.1 200 OK
[ {"op" : "remove", "path" : "a/b/c/"} ]

- self-descriptive하지 못하다. 어떻게 해석해야할지 모르기 때문이다.

HTTP/1.1 200 OK
Content-Type : application/json
[ {"op" : "remove", "path" : "a/b/c/"} ]

- 대괄호, 중괄호의 의미가 뭐인지 이해할 수 있기 때문에 파싱이가능해지고 문법을 해석할 수 있게 된다. 하지만 이것만으로는 부족한데 여기서 의미하는 "op"와 "path"의 의미를 모르기 때문이다.

HTTP/1.1 200 OK
Content-Type : application/json-patch+json
[ {"op" : "remove", "path" : "a/b/c/"} ]

- json patch + json이라는 미디어 타입으로 정의되어 있는 메시지 이기 때문에 json patch라는 명세를 찾아가서 이것을 이해한다음에 메시지를 해석하면 올바르게 이 메시지의 의미를 해석할 수 있음
- 메시지 내용으로 온전히 해석이 가능해야한다.

 

HATEOAS(hypermedia as the engine of applicatoin state)

- HATEOAS : 애플리케이션의 상태는 Hyperlink를 이용해 전이되어야 한다.

HTTP/1.1 200 OK
Content-Type : text/html

<html>
    <head></head>
    <body><a href="/test">test</a></body>
</html>

- html 같은 경우는 a link 로 연결 되어 있기 때문에 HATEOAS를 만족한다.

 

HTTP/1.1 200 OK
Content-Type : application.json
Link : </articles/1>; rel="previous",
             </articles/3>; rel="next";
{
    "title" : "The second article",
    "contents" : "blah blah ..."
}

- json 같은 경우에도 LINK를 이용해서 URI 정보를 인지할 수 있다.

 

Spring HATEOAS란?

  • Hypermedia As The Engine Of Application State를 구현하기 위해 편리한 기능들을 제공해주는 tool(라이브러리)이다.
  • Hypermedia As The Engine Of Application State는 Rest API를 만들 때, 서버가 리소스에 대한 정보를 제공할 때,
  • 그 리소스와 연관이 되어있는 링크 정보들까지 같이 제공하고,
  • 클라이언트는 제공이 된 연관된 링크 정보를 바탕으로 리소스에 접근한다.

대표적인 기능

  1. 링크 만드는 기능
  2. 리소스 만드는 기능
  3. 링크 찾아주는 기능

의존성

		<dependency>
			<groupId>org.springframework.hateoas</groupId>
			<artifactId>spring-hateoas</artifactId>
			<version>0.25.1.RELEASE</version>
		</dependency>

링크

{
  "content":"Hello, World!",
  "_links":{
    "self":{
      "href":"http://localhost:8080/greeting?name=World"
    }
  }
}

다음과 같이 어떤 특정 콘텐츠안에 _links라는 항목안에 여러 링크들을 자동적으로 추가해준다.

 

Controller

	@GetMapping
	public ResponseEntity searchUser(){
		URI createUri = linkTo(methodOn(UserController.class).searchUser()).toUri();
		
		List<User> users = userService.selectAll();
		
		UserResource userResource = new UserResource(users);
		userResource.add(linkTo(UserController.class).withSelfRel());
		userResource.add(linkTo(UserController.class).withRel("user-update"));
		return ResponseEntity.created(createUri).body(userResource);
	}

TestController

	@Test
	@TestDescription("전체 유저를 조회하는 테스트")
	public void searchUsers() throws Exception {
		
		MockMvc.perform(get("/api/users/")
				.contentType(MediaType.APPLICATION_JSON_UTF8)
				.accept(MediaTypes.HAL_JSON))
		.andDo(print())
		.andExpect(status().isCreated());
	}

실행결과

이런식으로 REST API를 나타낼수 있다.

 

Errors -> Json 변환 안되는 이슈

잘못된 입력값에서 대해서 Errors를 처리할 수 있다. 하지만 body안에 아무처리 없이 넣어주면 errors는 json으로 반환되지 않는다.

 

	@PostMapping 
	public ResponseEntity createUser(@RequestBody @Valid UserDto userDto,Errors errors){
		if(errors.hasErrors()) {
			return ResponseEntity.badRequest().body(errors);
		}
		
		User user = modelMapper.map(userDto, User.class);
		URI createUri = linkTo(UserController.class).toUri();
		return ResponseEntity.created(createUri).body(user);
	}

 

Spring 에서는 JsonCompenent를 제공해준다. 다음과 같이 JsonSerializer를 직접 상속받아서 반환할 json을 하나식 처리해주면 된다.

@JsonComponent
public class ErrorsSerializer extends JsonSerializer<Errors> {

	@Override
	public void serialize(Errors errors, JsonGenerator gen, SerializerProvider serializers) throws IOException {
		gen.writeStartArray();
		errors.getFieldErrors().stream().forEach(e -> {
			try {
				gen.writeStartObject();
				gen.writeStringField("field", e.getField());
				gen.writeStringField("objectName", e.getObjectName());
				gen.writeStringField("code", e.getCode());
				gen.writeStringField("defaultMessage", e.getDefaultMessage());
				Object rejectedValue = e.getRejectedValue();
				if (rejectedValue != null) {
					gen.writeStringField("rejectedValue", rejectedValue.toString());
				}
				gen.writeEndObject();
			} catch (IOException e1) {
				e1.printStackTrace();

			}
		});
		errors.getGlobalErrors().stream().forEach(e -> {
			try {
				gen.writeStartObject();
				gen.writeStringField("objectName", e.getObjectName());
				gen.writeStringField("code", e.getCode());
				gen.writeStringField("defaultMessage", e.getDefaultMessage());
				gen.writeEndObject();
			} catch (IOException e1) {
				e1.printStackTrace();

			}
		});
		gen.writeEndArray();
	}

}

 

반응형

'Computer Science' 카테고리의 다른 글

Socket 정리  (0) 2020.10.04
클라이언트 token 보안 전략  (0) 2020.10.04
CSRF, CORS 개념  (0) 2020.10.04
Mybatis VS Hibernate  (0) 2020.10.04
git flow, git hub flow , git lab flow  (0) 2020.10.04