ردیابی خطاهای زمان اجرا
به محض اینکه شروع به افزودن رفتار پویا به قالبهای HTML خود میکنیم، خطر مواجهه با خطاهای زمان اجرا (runtime errors) وجود دارد.
بیایید یک خطای عمدی به قالب 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}} را اضافه کردهایم که باید در زمان اجرا (runtime) یک خطا ایجاد کند زیرا در 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() را برای استفاده از این رویکرد بهروزرسانی کنیم:
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 برگردید و خطای عمدی را حذف کنید:
{{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}}