KakaoDevelopers, REST API 개발 가이드를 참고하여 개발중.
동적동의
API 사용시 사용자의 추가 동의가 필요한 경우 동적 동의가 필요합니다.
예를 들어 카카오톡 메시지 전송에 대한 동의를 하지 않은 사용자가 나에게 보내기
요청합니다.
curl -v -X POST 'https://kapi.kakao.com/v2/api/talk/memo/default/send' \
-H 'Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
-d 'template_object={...}'
[Response]
요청이 실패하면서 응답 바디에 JSON 객체로 아래 값을 포함합니다.
키 | 설명 |
---|---|
required_scopes | 현재 API를 사용하기 위해서 필요한 동의항목 |
allowed_scopes | 사용자가 동의한 동의항목 |
HTTP/1.1 403 Forbidden
{
"msg": "insufficient scopes.",
"code": -402,
"api_type": "TALK_MEMO_DEFAULT_SEND",
"required_scopes": [
"talk_message"
],
"allowed_scopes": [
"profile",
"account_email"
]
}
실습
개발 가이드에 따르면, 필요한 동의항목과 사용자가 동의한 항목이 응답에 나와야 한다.
내가 작성한 코드는 에러 처리가 되지 않아서, 응답 바디를 확인할 수 없었다.
public JsonNode sendMessage(String access_token) {
final String requestUrl = "https://kapi.kakao.com/v2/api/talk/memo/default/send";
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("title", "title");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Authorization", "Bearer " + access_token);
HttpEntity<String> request = new HttpEntity<>(httpHeaders);
JsonNode returnNode = null;
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(requestUrl, request, String.class);
logger.debug("## sendMessage : {}", response.getStatusCode());
logger.debug("## sendMessage : {}", response.getBody());
ObjectMapper mapper = new ObjectMapper();
try {
returnNode = mapper.readTree(String.valueOf(response.getBody()));
} catch (IOException e) {
e.printStackTrace();
}
return returnNode;
}
14:31:02.384 [ERROR] [http-nio-8080-exec-3] [o.a.c.c.C.[.[.[.[dispatcherServlet]] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException$Forbidden: 403 Forbidden] with root cause
org.springframework.web.client.HttpClientErrorException$Forbidden: 403 Forbidden
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:83)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:122)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:102)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:778)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:736)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:445)
at com.example.todolist.web.login.KakaoRestApi.sendMessage(KakaoRestApi.java:114)
at com.example.todolist.web.login.LoginController.kakaologinGet(LoginController.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Error Handing 처리
RestTemplateResponseErrorHandler 생성
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {
private static final Logger logger = getLogger(RestTemplateResponseErrorHandler.class);
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR
|| response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR ;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
logger.debug("## handleError : {}", response);
if (response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) {
} else if (response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {
if (response.getStatusCode() == HttpStatus.NOT_FOUND) {
//throw new NotFoundException();
}
}
}
}
RestTemplate 객체를 생성할 때, RestTemplateBuilder의 도움을 받아 ErrorHandler를 추가해서 생성했다.
public JsonNode sendMessage(String access_token) {
final String requestUrl = "https://kapi.kakao.com/v2/api/talk/memo/default/send";
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("title", "title");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Authorization", "Bearer " + access_token);
HttpEntity<String> request = new HttpEntity<>(httpHeaders);
JsonNode returnNode = null;
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
RestTemplate restTemplate = restTemplateBuilder.errorHandler(new RestTemplateResponseErrorHandler()).build();
ResponseEntity<String> response = restTemplate.postForEntity(requestUrl, request, String.class);
logger.debug("## sendMessage : {}", response.getStatusCode());
logger.debug("## sendMessage : {}", response.getBody());
ObjectMapper mapper = new ObjectMapper();
try {
returnNode = mapper.readTree(String.valueOf(response.getBody()));
} catch (IOException e) {
e.printStackTrace();
}
return returnNode;
}
## sendMessage : 403 FORBIDDEN
## sendMessage : {"msg":"insufficient scopes.","code":-402,"api_type":"TALK_MEMO_DEFAULT_SEND","required_scopes":["talk_message"],"allowed_scopes":["profile"]}
요청이 실패하였지만, 아까와는 달리, response body에 응답을 잘 볼 수 있었다.
느낀점
에러 핸들링은 필수구나…. 라는걸 느꼈음..
아직 개발을 시작한지 얼마 안되어서 정상적인 동작에 대해서만 적절한 처리를 하고,
에러나 예외 처리하는 것이 좀 미숙한데.. 좀 신경써서 해야겠다는 생각을 했음.
참고링크
Spring RestTemplate Error Handling