Let's Go قالب‌های HTML پویا › مدیریت خطاهای زمان اجرا
قبلی · فهرست · بعدی
فصل ۵.۴.

مدیریت خطاهای زمان اجرا (Catching Runtime Errors)

به محض اینکه شروع به اضافه کردن رفتار پویا (Dynamic Behavior) به قالب‌های HTML می‌کنیم، خطر مواجهه با خطاهای زمان اجرا (Runtime Errors) وجود دارد.

بیایید یک خطای عمدی (Deliberate Error) به قالب view.tmpl اضافه کنیم و ببینیم چه اتفاقی می‌افتد:

File: ui/html/pages/view.tmpl
{{define "title"}}Snippet #{{.Snippet.ID}}{{end}}

{{define "main"}}
    {{with .Snippet}}
    <div class='snippet'>
        <div class='metadata'>
            <strong>{{.Title}}</strong>
            <span>#{{.ID}}</span>
        </div>
        {{len nil}} <!-- Deliberate error -->
        <pre><code>{{.Content}}</code></pre>
        <div class='metadata'>
            <time>Created: {{.Created}}</time>
            <time>Expires: {{.Expires}}</time>
        </div>
    </div>
    {{end}}
{{end}}

در کد بالا، خط {{len nil}} را اضافه کردیم که باید در زمان اجرا خطا ایجاد کند زیرا در Go مقدار nil طول ندارد.

حالا برنامه را اجرا کنید. خواهید دید که همه چیز همچنان به درستی کامپایل می‌شود:

$ go run ./cmd/web
time=2024-03-18T11:29:23.000+00:00 level=INFO msg="starting server" addr=:4000

اما اگر با استفاده از curl درخواستی به http://localhost:4000/snippet/view/1 ارسال کنید، پاسخی مشابه این دریافت خواهید کرد:

$ curl -i http://localhost:4000/snippet/view/1
HTTP/1.1 200 OK
Date: Wed, 18 Mar 2024 11:29:23 GMT
Content-Length: 734
Content-Type: text/html; charset=utf-8


<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>Snippet #1 - Snippetbox</title>
        <link rel='stylesheet' href='/static/css/main.css'>
        <link rel='shortcut icon' href='/static/img/favicon.ico' type='image/x-icon'>
        <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700'>
    </head>
    <body>
        <header>
            <h1><a href='/'>Snippetbox</a></h1>
        </header>
        
 <nav>
    <a href='/'>Home</a>
</nav>

        <main>
            
    
    <div class='snippet'>
        <div class='metadata'>
            <strong>An old silent pond</strong>
            <span>#1</span>
        </div>
        Internal Server Error

این وضعیت بسیار بد است. برنامه ما خطایی ایجاد کرده، اما به کاربر به اشتباه پاسخ 200 OK ارسال شده است. حتی بدتر از آن، کاربر یک صفحه HTML ناقص دریافت کرده است.

برای رفع این مشکل، باید رندر قالب را به یک فرآیند دو مرحله‌ای تبدیل کنیم. ابتدا باید یک رندر "آزمایشی" با نوشتن قالب در یک بافر انجام دهیم. اگر این کار با خطا مواجه شود، می‌توانیم پیام خطا را به کاربر ارسال کنیم. اما اگر موفقیت‌آمیز باشد، می‌توانیم محتوای بافر را به http.ResponseWriter خود بنویسیم.

بیایید تابع کمکی render() را به این روش به‌روزرسانی کنیم:

File: cmd/web/helpers.go
package main

import (
    "bytes" // New import
    "fmt"
    "net/http"
)

...

func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
    ts, ok := app.templateCache[page]
    if !ok {
        err := fmt.Errorf("the template %s does not exist", page)
        app.serverError(w, r, err)
        return
    }

    // Initialize a new buffer.
    buf := new(bytes.Buffer)

    // Write the template to the buffer, instead of straight to the
    // http.ResponseWriter. If there's an error, call our serverError() helper
    // and then return.
    err := ts.ExecuteTemplate(buf, "base", data)
    if err != nil {
        app.serverError(w, r, err)
        return
    }

    // If the template is written to the buffer without any errors, we are safe
    // to go ahead and write the HTTP status code to http.ResponseWriter.
    w.WriteHeader(status)

    // Write the contents of the buffer to the http.ResponseWriter. Note: this
    // is another time where we pass our http.ResponseWriter to a function that
    // takes an io.Writer.
    buf.WriteTo(w)
}

برنامه را مجدداً راه‌اندازی کنید و همان درخواست را دوباره امتحان کنید. حالا باید یک پیام خطای مناسب و پاسخ 500 Internal Server Error دریافت کنید.

$ curl -i http://localhost:4000/snippet/view/1
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Wed, 18 Mar 2024 11:29:23 GMT
Content-Length: 22

Internal Server Error

عالی است. این خیلی بهتر به نظر می‌رسد.

قبل از اینکه به فصل بعدی برویم، به فایل view.tmpl برگردید و خطای عمدی را حذف کنید:

File: ui/html/pages/view.tmpl
{{define "title"}}Snippet #{{.Snippet.ID}}{{end}}

{{define "main"}}
    {{with .Snippet}}
    <div class='snippet'>
        <div class='metadata'>
            <strong>{{.Title}}</strong>
            <span>#{{.ID}}</span>
        </div>
        <pre><code>{{.Content}}</code></pre>
        <div class='metadata'>
            <time>Created: {{.Created}}</time>
            <time>Expires: {{.Expires}}</time>
        </div>
    </div>
    {{end}}
{{end}}

واژه‌نامه اصطلاحات فنی

اصطلاح فارسی معادل انگلیسی توضیح
خطاهای زمان اجرا Runtime Errors خطاهایی که در حین اجرای برنامه رخ می‌دهند
رفتار پویا Dynamic Behavior عملکردی که در زمان اجرا تغییر می‌کند
خطای عمدی Deliberate Error خطایی که به قصد تست سیستم ایجاد می‌شود
مدیریت خطا Error Handling روش‌های کنترل و پاسخ به خطاها
پیام خطا Error Message توضیحی درباره خطای رخ داده
خطای قالب Template Error خطایی که در پردازش قالب رخ می‌دهد
تست خطا Error Testing بررسی واکنش سیستم به شرایط خطا
گزارش خطا Error Reporting نمایش و ثبت اطلاعات خطاها
بازیابی از خطا Error Recovery اقدامات لازم پس از رخداد خطا
پیشگیری از خطا Error Prevention روش‌های جلوگیری از وقوع خطا