비번 확인해서 삭제시키기
발단:
DELETE 요청 실패시 서버에서 반환한 errorhandler 안의 메세지를 프론트가 적절히 받지 못해 임의의 alert 내용을 띄우도록 했었는데, 이 사항을 뜯어본 과정을 기록해 보았다.
결론부터 말하면, Ajax의 error 옵션을 사용해서 추출해낼 수 있었다.
Ajax 삭제 요청시 “실패하면”의 조건을 달고 싶을 때 error 옵션을 쓴다. “성공하면”의 조건을 달 때 success 옵션을 쓰는 것과 같이. 아래는 템플릿 그대로 “실패하면” 콜백에게 인수 3개 쥐어주고 뭐가 나오는지 실험해 본 결과이다.
Ajax의 error 옵션 실험:
# app.py
@app.route("/api/comments/delete/<id>", methods=["DELETE"])
def delete_comment(id):
# 비밀번호가 맞지 않으면 비밀번호가 맞지 않다는 에러 메세지 반환
if 받아온 비번과 저장된 해시비번 값이 다르면:
return unauthorized()
else:
...
@app.errorhandler(401)
def unauthorized(error=None):
message = {
'status': 401,
'message': "Unauthorized: Password not matched",
}
resp = jsonify(message)
resp.status_code = 401
return resp
// index.html
$.ajax({
type: 'DELETE',
url: '/api/comments/delete/' + _id,
data: {pwd: password},
success: function (response) {
...
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(jqXHR) // # 인자1.
console.log(textStatus) // # 인자2.
console.log(errorThrown) // # 인자3.
alert("some error occurred")
}
})
결과 :
DELETE http://localhost:5000/api/comments/delete/638573724543ab31d4dc16f7 401 (UNAUTHORIZED)
> {readyState: 4, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …} // # 인자1. jqXHR
> error // # 인자2. textStatus
> UNAUTHORIZED // # 인자3. errorThrown
인자 1이 받는 jqXHR객체는 총체적인 정보를 가지고, 인자 2가 받는 textStatus라는 건 어디서 ‘error’라는 정체가 나왔는지 잘 모르겠다. 인자 3이 받는 errorThrown은 서버에서 정의한 unauthorized() 함수명이 그대로 쓰인 것 같다.
아래는 jqXHR 을 펼쳐본 결과이다:
{readyState: 4, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …}
- abort: ƒ (e)
- always: ƒ ()
- catch: ƒ (e)
- done: ƒ ()
- fail: ƒ ()
- getAllResponseHeaders: ƒ ()
- getResponseHeader: ƒ (e)
- overrideMimeType: ƒ (e)
- pipe: ƒ ()
- progress: ƒ ()
- promise: ƒ (e)
- readyState: 4
- responseJSON: {message: 'Unauthorized: Password not matched', status: 401}
- responseText: "{\"message\":\"Unauthorized: Password not matched\",\"status\":401}\n"
- setRequestHeader: ƒ (e,t)
- state: ƒ ()
- status: 401
- statusCode: ƒ (e)
- statusText: "UNAUTHORIZED"
- then: ƒ (t,n,r)
- [[Prototype]]: Object
흐음… 그래서 이 중에 내가 서버에서 지정한 에러 메세지 “Unauthorized: Password not matched”를 띄우고 싶다면 뭘 이용해야 하느냐. responseJSON 속성을 이용하는 게 제일 깔끔하겠다.
⇒ error: function(jqXHR) { alert(jqXHR['responseJSON']['message'] },
Ajax의 success 옵션 실험:
success 옵션도 인수를 3개 받는다던데 한 번 출력해보았다.
# app.py
@app.route("/api/comments/delete/<id>", methods=["DELETE"])
def delete_comment(id):
# 비밀번호가 맞지 않으면 비밀번호가 맞지 않다는 에러 메세지 반환
...
# 비밀번호가 맞으면 해당 id 코멘트 삭제
db.miniProject.delete_one({'_id': ObjectId(id)})
resp = jsonify("The comment was deleted successfully")
resp.status_code = 200
return resp
// index.html
$.ajax({
type: 'DELETE',
url: '/api/comments/delete/' + _id,
data: {pwd: password},
success: function (response, textStatus, jqXHR) {
console.log(response)
console.log(textStatus)
console.log(jqXHR)
},
error: function(jqXHR) {
...
}
})
결과 :
> The comment was deleted successfully
> success
> {readyState: 4, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …}
3번째 인자인 jqXHR을 펼쳐보면:
{readyState: 4, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …}
- abort: ƒ (e)
- always: ƒ ()
- catch: ƒ (e)
- done: ƒ ()
- fail: ƒ ()
- getAllResponseHeaders: ƒ ()
- getResponseHeader: ƒ (e)
- overrideMimeType: ƒ (e)
- pipe: ƒ ()
- progress: ƒ ()
- promise: ƒ (e)
- readyState: 4
- responseJSON: "The comment was deleted successfully"
- responseText: "\"The comment was deleted successfully\"\n"
- setRequestHeader: ƒ (e,t)
- state: ƒ ()
- status: 200 ⇒ error와 비교: 401
- statusCode: ƒ (e)
- statusText: "OK" ⇒ error와 비교: "UNAUTHORIZED"
- then: ƒ (t,n,r)
- [[Prototype]]: Object
error때와 거의 똑같은 포맷과 내용임을 알 수 있다.
3번째 인자인 jqXHR의 responseJSON을 이용해서 응답 메세지를 똑같이 출력할 수도 있겠으나, ‘3번째 인자’라는 점이 활용도가 떨어진다. 어차피 첫 인자 response를 명시하고서야 3번째 인수까지 갈 수 있을 테니(맞나?) 이 response를 출력하는 쪽으로 사용하는 게 편할 듯.
결론:
어쨌든 이로써 비교적 쉽게 비밀번호 확인 후 삭제하기 기능을 완성했다.
# app.py
from bson.objectid import ObjectId
from werkzeug.security import check_password_hash
# DELETE a comment
@app.route("/api/comments/delete/<id>", methods=["DELETE"])
def delete_comment(id):
# 비밀번호가 맞지 않으면 비밀번호가 맞지 않다는 에러 메세지 반환
_password_given = request.form['pwd']
_true_password = db.miniProject.find_one({'_id': ObjectId(id)})['pwd']
if not check_password_hash(_true_password, _password_given):
return unauthorized()
# 비밀번호가 맞으면 해당 id 코멘트 삭제
db.miniProject.delete_one({'_id': ObjectId(id)})
resp = jsonify("The comment was deleted successfully")
resp.status_code = 200
return resp
@app.errorhandler(401)
def unauthorized(error=None):
message = {
'status': 401,
'message': "Unauthorized: Password not matched",
}
resp = jsonify(message)
resp.status_code = 401
return resp
// index.html
// DELETE
function delete_comment(_id) {
let password = prompt("댓글 작성시 설정한 비밀번호를 입력해주세요")
if (!password) { // '', false, undefined, null, 0등인 경우
alert("비밀번호가 입력되지 않았습니다")
return
}
$.ajax({
type: 'DELETE',
url: '/api/comments/delete/' + _id,
data: {pwd: password},
success: function (response) {
alert(response)
window.location.reload()
},
error: function(jqXHR) {
alert(jqXHR['responseJSON']['message'])
}
})
}
참고로 이런 방법도 가능하다고 한다:
$.post('some.php', {name: 'John'})
.done(function(msg){ })
.fail(function(xhr, status, error) {
// error handling
});
이런 방식도:
$.ajax({
type: 'POST',
url: 'status.ajax.php',
data: {
deviceId: id
}
})
.done(
function (data) {
//your code
}
)
.fail(function (data) {
console.log( "Ajax failed: " + data['responseText'] );
})
추가할 것: 비밀번호 입력 시 **로 가려지게 만들기.
추가할 것2: 비밀번호 정보를 그냥 data로 보내도 되나? password 옵션은 이런 데 쓰는 건 아닌가? 프론트에서 해싱해서 보내야 하나? 그래야겠군. 근데 자바스크립트에게는… werkzeug.security가 없잖아… 그럼 파이썬에서 werkzeug.security이 하는 것과 똑같은 방식으로 해싱하지 않을 것 같고 그럼 어떡하지?
참고:
https://stackoverflow.com/questions/2833951/how-do-i-catch-an-ajax-query-post-error - ajax post error 잡기
Uploaded by N2T