2021-03-26-TIL
2021-03-26-TIL
AJAX로 답변 추가하고 삭제할 때 405 에러나는 문제
405는 HTTP 상태 코드 Method Not Allowed다. RFC 9110은 이 코드를 요청에 쓴 메서드를 서버가 알고는 있지만, 대상 리소스가 그 메서드를 지원하지 않을 때 쓴다고 정의한다. 그리고 405 응답에는 그 리소스가 지원하는 메서드 목록을 담은 Allow 헤더가 반드시 들어가야 한다. 즉 404처럼 주소가 틀린 것이 아니라, 주소는 맞는데 메서드가 맞지 않는다는 뜻이다.
그래서 405가 나면 먼저 두 가지를 맞춰 본다. 하나는 클라이언트가 실제로 보낸 메서드다. 브라우저 개발자 도구의 네트워크 탭에서 요청 메서드와 응답의 Allow 헤더를 확인할 수 있다. 다른 하나는 서버의 매핑이다. 예를 들어 서버는 @DeleteMapping으로 삭제를 받는데 클라이언트가 POST를 보내면 405가 된다.
HTML <form>으로 보낼 때는 한계가 하나 더 있다. HTML 표준에서 폼의 method 속성이 가질 수 있는 값은 get, post, dialog뿐이라, 폼만으로는 DELETE나 PUT 요청을 보낼 수 없다. 삭제를 DELETE로 받도록 설계했다면 fetch()나 XMLHttpRequest로 메서드를 직접 지정해 보내야 한다.
1
fetch(`/questions/${questionId}/answers/${answerId}`, { method: "DELETE" });
Java Invoke
링크한 문서는 TIBCO BusinessWorks라는 제품의 Java Invoke 기능 설명이다. 이런 기능의 바탕에는 Java 리플렉션의 Method.invoke()가 있다. Javadoc에 따르면 invoke(Object obj, Object... args)는 Method 객체가 나타내는 메서드를 지정한 객체에 대해 지정한 인자로 호출한다. 메서드 이름을 문자열로만 알고 있어도 실행 중에 찾아서 호출할 수 있다.
1
2
Method method = target.getClass().getMethod("hello", String.class);
Object result = method.invoke(target, "world"); // target.hello("world")
직렬화
직렬화는 객체를 저장하거나 전송할 수 있는 형태로 바꾸는 것이다. Java 기본 직렬화(Serializable)는 Java끼리만 읽을 수 있는 바이트 형식을 만들지만, JSON은 언어와 상관없이 읽을 수 있는 텍스트 형식이다. 그래서 브라우저와 서버가 데이터를 주고받는 AJAX에서는 Jackson 같은 라이브러리로 객체를 JSON으로 직렬화한다.
Bidirectional Relationship Support in JSON
질문과 답변처럼 서로를 참조하는 객체를 JSON으로 바꾸면, 질문이 답변을 담고 답변이 다시 질문을 담는 식으로 끝없이 이어진다. Jackson에서는 부모 쪽에 @JsonManagedReference, 자식 쪽에 @JsonBackReference를 붙여 자식에서 부모로 가는 참조를 직렬화에서 빼는 방법이 있다. 응답 전용 DTO를 따로 만들어 필요한 필드만 담는 것도 방법이다.
cglib
cglib(Code Generation Library)는 실행 중에 Java 바이트코드를 만들고 바꾸는 라이브러리다. 프로젝트 설명대로 AOP, 테스트, 데이터 접근 프레임워크가 동적 프록시 객체를 만들 때 쓴다. 스프링 AOP는 프록시 대상이 인터페이스를 하나라도 구현하면 JDK 동적 프록시를, 인터페이스가 없으면 cglib 프록시를 만든다. cglib 프록시는 대상 클래스를 상속해서 만들기 때문에, 스프링 문서도 지적하듯 final 메서드에는 부가 기능을 끼워 넣을 수 없다.
cglib 저장소의 README는 현재 이 프로젝트가 활발히 관리되지 않고 JDK 17 이상에서는 잘 동작하지 않을 수 있다며 ByteBuddy 같은 대안을 권한다. 스프링은 cglib을 spring-core 안에 재패키징해서 쓴다.
Event delegation
이벤트 위임은 자식 요소마다 리스너를 다는 대신 공통 부모에 리스너 하나를 다는 방식이다. 자식에서 일어난 이벤트가 부모로 전파되는 버블링 덕분에 가능하다. MDN은 많은 자식 요소 중 하나와 상호작용할 때 코드를 실행하고 싶으면 부모에 리스너를 두고 이벤트가 버블링되어 올라오게 하라고 설명한다. 어느 자식에서 일어났는지는 event.target으로 확인한다.
1
2
3
4
5
answerList.addEventListener("click", (event) => {
if (event.target.matches(".delete-button")) {
deleteAnswer(event.target.dataset.answerId);
}
});
AJAX로 답변을 추가하는 화면에서 이 방식이 특히 쓸모 있다. 새로 추가된 답변의 삭제 버튼에도 리스너를 따로 달 필요 없이, 이미 부모에 달아 둔 리스너가 처리한다.
댓글
아직 댓글이 없습니다