Spring Boot: study.diary : ajax 를 이용한 Rest API 호출로 view 와 data 분리 에서 최종적으로 만든 코드는 Rest API 를 호출할 때 application/x-www-form-urlencoded 형식의 content-type 으로 전송하는 예제였어.
application/x-www-form-urlencoded 형식의 데이터는 _csrf=26906007-6d4e-4470-b76f-189dc827fa2d&_method=PUT&diary_date=2024-03-05&diary_content=4444 와 같이 key=value&key=value 형식으로 데이터가 이루어졌어.
반면에 application/json 형식의 데이터는 {_csrf: ‘49543209-c640-4c87-9c04-6e70334c5150’, _method: ‘PUT’, diary_date: ‘2024-03-05’, diary_content: 4444} 와 같이 이루어지지.
Rest API 를 사용할 때는 보통 input, output 데이터의 형식을 json 으로 사용하고 있어. 간혹 xml 형식의 데이터를 사용하는 경우도 있지만, json 형식의 데이터를 사용하는 것이 정답은 아니지만, 대세인 것은 분명해.
그래서 이번 포스트에서는 json 형식의 데이터를 입출력 데이터로 사용하기 위해서 어떻게 변경해야 하는가를 알아보려고 해.
일단, 전송하는 클라이언트 측의 자바 스크립트를 변경해야겠지?
<script src="https://code.jquery.com/jquery-3.6.0.min.js" charset="UTF-8"></script>
<script th:inline="javascript">
function convertUrlEncodedToJson(str) {
let jsonOutput = "";
let formRequest = decodeURIComponent(str).split("&");
formRequest.forEach(function(keyValuePair) {
let nameAndValue = keyValuePair.split('=');
jsonOutput += '"' + nameAndValue[0] + '"' + ':' + '"' +
nameAndValue[1]
.replace(new RegExp(String.fromCharCode(13), 'g'), '\\r')
.replace(new RegExp(String.fromCharCode(10), 'g'), '\\n')
.replace(/\\n/g, "\\n")
.replace(/\\'/g, "\\'")
.replace(/\\"/g, '\\"')
.replace(/\\&/g, "\\&")
.replace(/\\r/g, "\\r")
.replace(/\\t/g, "\\t")
.replace(/\\b/g, "\\b")
.replace(/\\f/g, "\\f")
+ '"' + ',';
});
return '{' + jsonOutput.substring(0, jsonOutput.length -1) + '}';
}
function modifyDiary() {
let editForm = $("#editForm");
let formData = new FormData(editForm.get(0));// get Form element
let diary_date = formData.get("diary_date");
let diary_content = formData.get("diary_content");
// 입력값 유효성 확인
if (!diary_date || !diary_content) {
alert("빈값일 수 없습니다.");
return;
}
let diaryId = /*[[${diary.id}]]*/"";
let url = `/diary/${diaryId}`;
let data = editForm.serialize();
console.log("data = " + data);
data = convertUrlEncodedToJson(data);
console.log(data);
data = JSON.parse(data);
console.log(data);
//////////////////////////////////////////
$.ajax({
url: url,
type: 'PUT',
data: JSON.stringify(data),
contentType: 'application/json',
error: function(xhr, status, error) {
alert("error");
},
success: function() {
alert("succeeded");
window.location.href = '/';
}
});
}
</script>
application/x-www-form-urlencoded 형식의 데이터를 application/json 형식의 데이터로 변환시키기 위해서 convertUrlEncodedToJson 함수를 사용하고 있어. 이 함수가 리턴하는 값은 문자열 값인데, JSON.parse 함수로 JSON 오브젝트로 변경시키고 있지.
modifyDiary 함수에서 콘솔에 찍고 있는 로그는 다음과 같아.
JSON.parse 함수로 리턴받은 data 를 JSON.stringify 함수를 이용해서 문자열화시켜서 전달하는데, 이 문자열의 포맷이 json 형식이라는 것을 알려주기 위해서 반드시 contentType: ‘application/json’ 을 명시해 주어야 해.
만약 contentType: ‘application/json’ 을 명시하지 않으면 디폴트값인 application/x-www-form-urlencoded 로 인식하게 될거야.
어쨌든, 전송하는 클라이언트측에서는 json 방식의 데이터를 전송하는 것으로 수정되었는데, 이제 이 데이터를 받아서 처리할 서버쪽에서 json 형식의 데이터를 받아들이도록 수정해야겠지.
@PutMapping(value = "/diary/{id}")
//public void UpdateDiary(@PathVariable("id") Integer id, String diary_date, String diary_content) {
public void UpdateDiary(@PathVariable("id") Integer id, @RequestBody Map<String, Object> map) {
System.out.println("id=" + id);
System.out.println(map);
//diaryService.UpdateDiary(id, diary_date, diary_content);
}
기존의 String 형 파라미터로는 json 형식의 데이터를 처리할 수가 없기 때문에 json 형식의 데이터를 컨버팅해서 담을 Map<String, Object> 형식의 파라미터를 추가했어. 그런데, 이 데이터는 필수적으로 받아야 하기 때문에 @RequestBody 애노테이션을 붙여줬어.
swagger UI 에서 확인해보면
필수 입력값(required) 으로 application/json 형식의 데이터가 표시되고 있지.
이제 전송하는 측과 수신받는 측의 코드가 모두 수정되었어. 그런데 이 상태로 프로그램을 실행시켜보면 클라이언트 콘솔에 403 Forbidden 에러가 기록되지.
이 오류는 json 형식의 데이터 전송과는 무관한 오류이고, csrf 토큰 사용과 관련된 오류야. 만약 csrf 토큰을 사용하지 않겠다는 설정을 하게 되면 나타날리 없어. 이 오류는 editdiary.html 파일에 아래와 같은 코드를 작성함으로써 해결할 수가 있어.
우선 <head></head> 태그 사이에 아래 코드를 추가해줘.
<meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>
그리고 modifyDiary 함수 안에 아래 코드를 추가해줘.
<script src="https://code.jquery.com/jquery-3.6.0.min.js" charset="UTF-8"></script>
<script th:inline="javascript">
function modifyDiary() {
...
let token = $("meta[name='_csrf']").attr("content");
let header = $("meta[name='_csrf_header']").attr("content");
$.ajax({
url: url,
type: 'PUT',
data: JSON.stringify(data),
contentType: 'application/json',
beforeSend: function(xhr){
xhr.setRequestHeader(header, token);
},
error: function(xhr, status, error) {
alert("error");
},
success: function() {
alert("succeeded");
window.location.href = '/';
}
});
}
</script>
이렇게 수정한 후에 프로그램을 실행시켜서 일기수정 폼의 소스를 보면 <head></head> 태그 사이에서 아래와 같은 코드를 볼 수 있을거야.
<meta id="_csrf" name="_csrf" content="b30d6bdc-38d6-4a9b-8691-9610c81b1cb8"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta id="_csrf_header" name="_csrf_header" content="X-XSRF-TOKEN"/>
이 데이터가 있어야만 클라이언트에서 서버에 대한 요청이 공격이 아닌 요청으로 허용이 될 수 있는거야.
이제 프로그램을 실행한 후에 서버쪽 로그를 확인해볼께.
이번에는 swagger UI 를 이용해서 json 형식의 데이터를 전송해볼께.
요청이 성공했다는 응답을 받았어.
그럼 마지막으로 UpdateDiary 함수에서 DiaryService 의 UpdateDiary 함수를 호출하는 코드를 작성하면서 마무리할께.
@PutMapping(value = "/diary/{id}")
public void UpdateDiary(@PathVariable("id") Integer id, @RequestBody Map<String, Object> map) {
System.out.println("id=" + id);
System.out.println(map);
diaryService.UpdateDiary(id, map.get("diary_date").toString(), map.get("diary_content").toString());
}