스파르타코딩클럽/[내일배움캠프] 프로젝트와 트러블 슈팅

비번 확인해서 삭제시키기 + 실패시 에러 메세지 띄우기 ✔️

깊은바다거북 2022. 12. 2. 21:00

비번 확인해서 삭제시키기

발단:

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: ƒ, …}
    1. abort: ƒ (e)
    1. always: ƒ ()
    1. catch: ƒ (e)
    1. done: ƒ ()
    1. fail: ƒ ()
    1. getAllResponseHeaders: ƒ ()
    1. getResponseHeader: ƒ (e)
    1. overrideMimeType: ƒ (e)
    1. pipe: ƒ ()
    1. progress: ƒ ()
    1. promise: ƒ (e)
    1. readyState: 4
    1. responseJSON: {message: 'Unauthorized: Password not matched', status: 401}
    1. responseText: "{\"message\":\"Unauthorized: Password not matched\",\"status\":401}\n"
    1. setRequestHeader: ƒ (e,t)
    1. state: ƒ ()
    1. status: 401
    1. statusCode: ƒ (e)
    1. statusText: "UNAUTHORIZED"
    1. then: ƒ (t,n,r)
    1. [[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: ƒ, …}
    1. abort: ƒ (e)
    1. always: ƒ ()
    1. catch: ƒ (e)
    1. done: ƒ ()
    1. fail: ƒ ()
    1. getAllResponseHeaders: ƒ ()
    1. getResponseHeader: ƒ (e)
    1. overrideMimeType: ƒ (e)
    1. pipe: ƒ ()
    1. progress: ƒ ()
    1. promise: ƒ (e)
    1. readyState: 4
    1. responseJSON: "The comment was deleted successfully"
    1. responseText: "\"The comment was deleted successfully\"\n"
    1. setRequestHeader: ƒ (e,t)
    1. state: ƒ ()
    1. status: 200 ⇒ error와 비교: 401
    1. statusCode: ƒ (e)
    1. statusText: "OK" ⇒ error와 비교: "UNAUTHORIZED"
    1. then: ƒ (t,n,r)
    1. [[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