পাঠ ২২ · ৫৮-এর মধ্যে · মডিউল ৫
Home / Courses / Full-Stack Web Frameworks / এরর হ্যান্ডলিং

এরর হ্যান্ডলিং ও সেন্ট্রালাইজড এক্সসেপশন মিডলওয়্যার

Error handling and centralized exception middleware
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • কেন এরর হ্যান্ডলিং একটি সেন্ট্রাল মিডলওয়্যারে রাখা হয়, প্রতিটি হ্যান্ডলারে আলাদা try/except-এর বদলে
  • কাস্টম এক্সেপশন ক্লাস (NotFoundError, ValidationError) কীভাবে অর্থবহ সিগন্যাল বহন করে
  • একটি নির্দিষ্ট except ব্লক ক্রম কীভাবে সবচেয়ে নির্দিষ্ট এক্সেপশন থেকে সবচেয়ে সাধারণ এক্সেপশন পর্যন্ত সঠিকভাবে ম্যাচ করে
  • একই মিডলওয়্যার দিয়ে একাধিক আলাদা হ্যান্ডলারের এরর-আচরণ real কোডে যাচাই করা

১ · কেন এরর হ্যান্ডলিং সেন্ট্রালাইজড রাখা হয়

L19-এ দেখা গিয়েছিল একটি মিডলওয়্যার next_fn(request) কল করার আগে ও পরে কোড চালাতে পারে। এই একই ক্ষমতা ব্যবহার করে একটি মিডলওয়্যার next_fn(request)-কে একটি try ব্লকে মুড়ে রাখতে পারে — তাহলে চেইনের বাকি অংশে (আরও মিডলওয়্যার বা হ্যান্ডলারে) যেকোনো জায়গায় ছোঁড়া এক্সেপশন এই একটি জায়গাতেই ধরা পড়বে। প্রতিটি হ্যান্ডলারে আলাদা try/except লেখার দরকার নেই।

NotFoundError → 404
অনুরোধ করা রিসোর্স খুঁজে পাওয়া যায়নি।
ValidationError → 400
ক্লায়েন্টের পাঠানো ডেটা অবৈধ বা অসম্পূর্ণ।
অন্য যেকোনো Exception → 500
অপ্রত্যাশিত/অজানা সার্ভার-সাইড সমস্যা -- ক্লায়েন্টকে বিস্তারিত না জানিয়ে সাধারণ বার্তা দেওয়া হয়।

২ · সেন্ট্রালাইজড এরর মিডলওয়্যার — কোড দিয়ে

নিচের কোডে L19-এর build_chain() ফাংশনটি অপরিবর্তিত পুনরায় ব্যবহার করা হয়েছে। error_handling_middleware একটি একক মিডলওয়্যার হিসেবে চেইনে বসানো হয়েছে, যেটি next_fn(request)-কে try ব্লকে চালায় এবং তিন ধরনের এক্সেপশন আলাদাভাবে ধরে। চারটি ভিন্ন হ্যান্ডলার (প্রতিটি ইচ্ছাকৃতভাবে ভিন্ন কিছু করে) দিয়ে এই একই মিডলওয়্যার পরীক্ষা করা হয়েছে।

Python
# --- L19-এর চেইন-বিল্ডার -- অপরিবর্তিত পুনরায় ব্যবহৃত ---
def build_chain(middlewares, final_handler):
    def dispatch(index, request):
        if index == len(middlewares):
            return final_handler(request)
        current_mw = middlewares[index]
        def next_fn(req):
            return dispatch(index + 1, req)
        return current_mw(request, next_fn)
    return lambda request: dispatch(0, request)


# --- কাস্টম এক্সেপশন ক্লাস ---
class NotFoundError(Exception):
    pass


class ValidationError(Exception):
    pass


# --- সেন্ট্রালাইজড এরর-হ্যান্ডলিং মিডলওয়্যার ---
def error_handling_middleware(request, next_fn):
    try:
        return next_fn(request)
    except NotFoundError as e:
        print(f"[error-middleware] NotFoundError ধরা পড়েছে: {e}")
        return {"status": 404, "body": {"error": str(e)}}
    except ValidationError as e:
        print(f"[error-middleware] ValidationError ধরা পড়েছে: {e}")
        return {"status": 400, "body": {"error": str(e)}}
    except Exception as e:
        print(f"[error-middleware] অজানা এরর ধরা পড়েছে ({type(e).__name__}): {e}")
        return {"status": 500, "body": {"error": "Internal Server Error"}}


# --- চারটি ভিন্ন সিমুলেটেড হ্যান্ডলার -- ইচ্ছাকৃতভাবে চার রকম আচরণ ---
def handler_ok(request):
    return {"status": 200, "body": {"message": "সব ঠিক আছে"}}


def handler_not_found(request):
    raise NotFoundError("ইউজার আইডি ৯৯ পাওয়া যায়নি")


def handler_invalid_input(request):
    raise ValidationError("ইমেইল ফিল্ড আবশ্যক")


def handler_crash(request):
    raise ZeroDivisionError("অপ্রত্যাশিত বিভাজন এরর")  # সাধারণ Exception-এর একটি উপশ্রেণি


test_cases = [
    ("সঠিক রিকোয়েস্ট", handler_ok),
    ("404 কেস -- NotFoundError", handler_not_found),
    ("400 কেস -- ValidationError", handler_invalid_input),
    ("500 কেস -- অজানা এক্সেপশন", handler_crash),
]

results = []
for label, handler in test_cases:
    chain = build_chain([error_handling_middleware], handler)
    print(f"\n=== {label} ===")
    response = chain({"method": "GET", "path": "/test"})
    print("ফলাফল:", response)
    results.append((label, response["status"], response["body"]))

print("\n--- সারাংশ ---")
for label, status, body in results:
    print(f"{label:28s} -> status={status}, body={body}")

    
handler_crash একটি ZeroDivisionError ছোঁড়ে — যেটি NotFoundError বা ValidationError কোনোটিই নয়, তাই প্রথম দুটি except ব্লক তা ধরতে পারে না। এটি সবশেষে except Exception as e:-এ গিয়ে ধরা পড়ে (কারণ ZeroDivisionError পাইথনের বিল্ট-ইন এক্সেপশন হায়ারার্কিতে Exception-এর একটি উপশ্রেণি), ফলে সঠিকভাবে 500 রিটার্ন হয় — এটিই দেখায় "ক্যাচ-অল" শাখাটি সত্যিই যেকোনো অপ্রত্যাশিত এক্সেপশনের জন্য একটি নিরাপদ ফলব্যাক হিসেবে কাজ করে।

৩ · except ব্লকের ক্রম কেন গুরুত্বপূর্ণ

পাইথনে except ব্লকগুলো উপর থেকে নিচে পরীক্ষা করা হয়, প্রথম যেটি মেলে সেটিই চলে। যদি except Exception as e: ব্লকটি সবার আগে রাখা হতো, তাহলে সেটিই সব এক্সেপশন ধরে ফেলত — NotFoundError ও ValidationError-ও এতে আটকে যেত এবং সবসময় 500 রিটার্ন হতো, কখনো সঠিক 404/400 নয়। তাই সবচেয়ে নির্দিষ্ট এক্সেপশন টাইপগুলো আগে, সবচেয়ে সাধারণ (Exception) সবার শেষে রাখা আবশ্যক।

মূল কথা · Key takeaway

একটি সেন্ট্রালাইজড এরর-হ্যান্ডলিং মিডলওয়্যার চেইনের বাকি অংশকে একটি try/except-এ মুড়ে রাখে, এবং নির্দিষ্ট এক্সেপশন টাইপকে নির্দিষ্ট HTTP স্ট্যাটাস কোড ও রেসপন্স বডিতে ম্যাপ করে — সবচেয়ে নির্দিষ্ট টাইপ আগে, সবচেয়ে সাধারণ Exception সবার শেষে। এর ফলে প্রতিটি পৃথক হ্যান্ডলারকে নিজে থেকে এরর-ফরম্যাটিং লজিক পুনরাবৃত্তি করতে হয় না — শুধু প্রাসঙ্গিক এক্সেপশনটি ছোঁড়াই যথেষ্ট।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ 500 কেসের রেসপন্স বডিতে str(e) (আসল এরর মেসেজ) না দিয়ে সাধারণ "Internal Server Error" কেন দেওয়া হয়েছে, অথচ 404/400-এ আসল মেসেজ দেওয়া হয়েছে?

404 (NotFoundError) ও 400 (ValidationError) হলো প্রত্যাশিত ব্যবসায়িক-লজিক এরর -- এগুলোর মেসেজ ক্লায়েন্টের জন্য দরকারি ও নিরাপদ ("ইমেইল ফিল্ড আবশ্যক" জানলে ক্লায়েন্ট ফিক্স করতে পারে)। কিন্তু একটি ZeroDivisionError-এর মতো অপ্রত্যাশিত এক্সেপশন প্রায়ই সার্ভারের অভ্যন্তরীণ ইমপ্লিমেন্টেশন বিবরণ (ফাইল পাথ, ভ্যারিয়েবল নাম) ফাঁস করে দিতে পারে -- তাই ক্লায়েন্টকে একটি সাধারণ, নিরাপদ বার্তা দেওয়া হয়, আসল বিস্তারিত শুধু সার্ভার-সাইড লগে (উপরের কোডে print()-এ) রাখা হয়।

প্র ০২ যদি handler_not_found নিজেই একটি try/except দিয়ে NotFoundError ধরে ফেলত এবং শুধু None রিটার্ন করত, তাহলে error_handling_middleware-এ কী ঘটত?

তাহলে এক্সেপশনটি হ্যান্ডলারের ভেতরেই ধরা পড়ে যেত এবং কখনো error_handling_middleware পর্যন্ত "প্রোপাগেট" (propagate) হতো না -- ফলে মিডলওয়্যারের except NotFoundError ব্লক কখনো চলত না, এবং chain() শুধু None রিটার্ন করত (404 স্ট্যাটাস কোড নয়)। এটি দেখায় কেন কাস্টম এক্সেপশন হ্যান্ডলারের নিজের ভেতরে ধরে না রেখে সেন্ট্রাল মিডলওয়্যার পর্যন্ত "উঠতে" (propagate) দেওয়া জরুরি।

প্র ০৩ results লিস্টে চারটি এন্ট্রি জমা করে শেষে একসাথে "সারাংশ" প্রিন্ট করার সুবিধা কী?

প্রতিটি টেস্ট কেসের ফলাফল আলাদাভাবে (মাঝেমধ্যে ছড়িয়ে) প্রিন্ট হওয়ার পাশাপাশি, শেষে একটি একক টেবিলে সবগুলো status/body পাশাপাশি দেখা গেলে চোখে সহজে ধরা পড়ে চারটি ভিন্ন এক্সেপশন সত্যিই চারটি ভিন্ন স্ট্যাটাস কোডে (200/404/400/500) ম্যাপ হয়েছে কি না -- এটি hand-tracing ও যাচাইয়ের কাজটি সহজ করে, বিশেষত যখন টেস্ট কেসের সংখ্যা বাড়ে।

অনুশীলন

  1. চিন্তা করুন: যদি ভবিষ্যতে একটি নতুন এক্সেপশন টাইপ PermissionError যোগ করে সেটিকে 403 স্ট্যাটাস কোডে ম্যাপ করতে হয়, error_handling_middleware-এ ঠিক কোথায় নতুন except ব্লকটি বসাবেন -- সবার উপরে, মাঝে, নাকি except Exception-এর ঠিক আগে? কেন?

    except Exception as e:-এর ঠিক আগে বসানো উচিত (তবে NotFoundError/ ValidationError-এর সাথে যেকোনো ক্রমে, যেহেতু এই তিনটি একে অপরের থেকে সম্পূর্ণ স্বতন্ত্র/unrelated ক্লাস)। মূল নিয়ম শুধু এটাই -- যেকোনো নির্দিষ্ট (specific) এক্সেপশন টাইপকে অবশ্যই সবচেয়ে সাধারণ except Exception-এর আগে রাখতে হবে, নয়তো সেটি কখনো নিজে থেকে ম্যাচ হওয়ার সুযোগ পাবে না -- সাধারণ ব্লকটিই সবসময় আগে ধরে ফেলবে।

  2. পরীক্ষা করুন: উপরের কোড সেলে একটি নতুন হ্যান্ডলার handler_permission_denied(request) লিখুন যা raise PermissionError("এই রিসোর্স দেখার অনুমতি নেই") করে (নতুন ক্লাস ডিফাইন করার দরকার নেই -- PermissionError পাইথনের বিল্ট-ইন এক্সেপশন)। এটিকে test_cases-এ যোগ করুন এবং Run চেপে দেখুন এটি কোন স্ট্যাটাস কোডে যায়।

    যেহেতু error_handling_middleware-এ PermissionError-এর জন্য কোনো নির্দিষ্ট except ব্লক নেই, এটি NotFoundError ও ValidationError উভয়কেই মিস করে সবশেষে except Exception as e:-এ গিয়ে ধরা পড়বে -- অর্থাৎ এটি ভুলভাবে 500 রিটার্ন করবে, 403 নয়। এই ফলাফলটিই দেখায় কেন উপরের প্রথম অনুশীলনে বর্ণিত নতুন except PermissionError ব্লক যোগ করা জরুরি, যদি সত্যিই 403 আচরণ চাই।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
ব্যাক-এন্ড ফ্রেমওয়ার্কে ডিপেন্ডেন্সি ইনজেকশন