0

Flask: dừng request đúng chuẩn bằng abort() — 404/403 kèm trang lỗi tùy chỉnh trong 5 phút

Khi người dùng mở một bài viết không tồn tại, hoặc vào trang quản trị khi chưa có quyền, view phải dừng ngay và trả đúng mã lỗi — không phải tự render template lỗi rồi trả về với status 200. Flask có sẵn abort(): nó ném một ngoại lệ HTTP, dừng view ngay tại chỗ, và Flask tự trả đúng status code kèm trang lỗi. Kết hợp với errorhandler() là bạn có trang lỗi đẹp, đúng chuẩn cho toàn bộ ứng dụng.

Vấn đề

Cách hay gặp nhất là tự trả chuỗi lỗi kèm mã trạng thái: return "Không tìm thấy bài viết", 404. Cách này chạy được, nhưng mỗi view phải lặp lại cùng một mẫu, và rất dễ quên mã trạng thái — chỉ cần viết thiếu , 404 là trang lỗi trả về 200, Google sẽ index cả trang "không tìm thấy", còn client hay API thì không biết request đã thất bại.

# Cách kém: dễ quên mã lỗi, mỗi view lặp lại một kiểu
@app.get("/bai-viet/<int:post_id>")
def xem_bai_viet(post_id):
    post = Post.query.get(post_id)
    if post is None:
        return "Không tìm thấy bài viết", 404  # quên ", 404" là thành 200
    return render_template("post.html", post=post)

Giải pháp

abort() nhận mã lỗi HTTP và dừng view ngay lập tức — mọi dòng code phía sau không chạy nữa. Muốn nhắn thêm lý do thì truyền description, Flask sẽ đưa nó vào trang lỗi mặc định.

from flask import Flask, abort, render_template

app = Flask(__name__)

@app.get("/bai-viet/<int:post_id>")
def xem_bai_viet(post_id):
    post = Post.query.get(post_id)
    if post is None:
        abort(404, description=f"Không có bài viết #{post_id}")
    if post.is_private and not current_user.is_admin:
        abort(403, description="Bài viết này chỉ dành cho quản trị viên")
    return render_template("post.html", post=post)

Trang lỗi mặc định của Flask khá thô, nên đăng ký errorhandler() một lần cho toàn app để render template đẹp của riêng bạn — đối tượng lỗi có sẵn code và description để hiển thị:

@app.errorhandler(404)
def page_not_found(error):
    return render_template("404.html", message=error.description), 404

@app.errorhandler(403)
def forbidden(error):
    return render_template("403.html", message=error.description), 403

Một lưu ý quan trọng: abort() chỉ có ý nghĩa trong request context — trong view, trong before_request, hoặc trong hàm helper được view gọi. Ngoài request, ví dụ trong tác vụ nền hay Flask shell, nó vẫn ném ra ngoại lệ HTTP nhưng không có request nào để Flask chuyển thành response, nên nó chỉ là một exception chưa được xử lý.

Áp dụng vào dự án của bạn

Quy tắc chọn mã nhanh: không tìm thấy tài nguyên thì abort(404), không có quyền truy cập thì abort(403), dữ liệu đầu vào sai định dạng thì abort(400). Viết description ngắn gọn, hướng về người dùng ("Không có bài viết #12") thay vì lộ chi tiết nội bộ. Và đăng ký errorhandler() cho 404, 403 ngay từ đầu dự án — một lần duy nhất, mọi abort() trong app đều được hưởng trang lỗi thống nhất.

Câu hỏi thường gặp

abort() khác gì return ("…", 404)?

abort() ném ngoại lệ nên view dừng ngay tại chỗ, code phía sau không chạy. return kèm tuple chỉ trả response sau khi view chạy hết — nếu quên mã trạng thái thì response thành 200, và bạn phải lặp lại mẫu này ở mọi view.

API thì xử lý lỗi thế nào?

Đăng ký errorhandler() trả JSON thay vì HTML, ví dụ {"error": error.description} kèm error.code. Client nhận đúng mã lỗi và thông báo, thay vì phải parse trang HTML.

Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.


Bài đầy đủ bản tiếng Anh: Flask: Stop Requests the Standard Way with abort() — 404/403 with Custom Error Pages in 5 Minutes


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.