Let's Go مبانی › سفارشی‌سازی پاسخ‌ها
قبلی · فهرست · بعدی
فصل 2.6.

سفارشی‌سازی پاسخ‌ها (Customizing Responses)

به طور پیش‌فرض، هر پاسخی که هندلرهای شما ارسال می‌کنند دارای کد وضعیت HTTP (HTTP Status Code) 200 OK است (که به کاربر نشان می‌دهد درخواست او دریافت و با موفقیت پردازش شده است)، به علاوه سه هدر سیستم‌تولید (System-generated) خودکار: یک هدر Date، و Content-Length و Content-Type بدنه پاسخ (Response Body).

$ curl -i localhost:4000/
HTTP/1.1 200 OK
Date: Wed, 18 Mar 2024 11:29:23 GMT
Content-Length: 21
Content-Type: text/plain; charset=utf-8

Hello from Snippetbox

در این فصل به چگونگی سفارشی‌سازی هدرهای پاسخ که هندلرهای شما ارسال می‌کنند می‌پردازیم و همچنین به چند روش دیگر برای ارسال پاسخ‌های متنی ساده به کاربران نگاه می‌کنیم.

کدهای وضعیت HTTP (HTTP Status Codes)

ابتدا، بیایید هندلر snippetCreatePost خود را به‌روزرسانی کنیم تا به جای 200 OK کد وضعیت 201 Created ارسال کند. برای انجام این کار، می‌توانید از متد w.WriteHeader() در هندلرهای خود به این صورت استفاده کنید:

File: main.go
package main

...

func snippetCreatePost(w http.ResponseWriter, r *http.Request) {
    // Use the w.WriteHeader() method to send a 201 status code.
    w.WriteHeader(201)

    // Then w.Write() method to write the response body as normal.
    w.Write([]byte("Save a new snippet..."))
}

...

(بله، این کمی احمقانه است زیرا هندلر هنوز چیزی ایجاد نمی‌کند! اما به خوبی الگوی تنظیم یک کد وضعیت سفارشی را نشان می‌دهد.)

اگرچه این تغییر به نظر ساده می‌آید، اما چند نکته ظریف وجود دارد که باید توضیح دهم:

سرور را مجدداً راه‌اندازی کنید، سپس از curl برای ارسال یک درخواست POST به http://localhost:4000/snippet/create استفاده کنید. باید ببینید که پاسخ HTTP اکنون دارای یک کد وضعیت 201 Created مشابه این است:

$ curl -i -d "" http://localhost:4000/snippet/create
HTTP/1.1 201 Created
Date: Wed, 18 Mar 2024 11:29:23 GMT
Content-Length: 21
Content-Type: text/plain; charset=utf-8

Save a new snippet...

ثابت‌های کد وضعیت (Status Code Constants)

پکیج net/http ثابت‌هایی برای کدهای وضعیت HTTP فراهم می‌کند که می‌توانیم به جای نوشتن شماره کد وضعیت خودمان از آن‌ها استفاده کنیم. استفاده از این ثابت‌ها یک عمل خوب است زیرا به جلوگیری از اشتباهات ناشی از تایپ اشتباه کمک می‌کند و همچنین می‌تواند کد شما را واضح‌تر و خودمستند کند — به‌ویژه هنگام کار با کدهای وضعیت کمتر استفاده‌شده.

بیایید هندلر snippetCreatePost خود را به‌روزرسانی کنیم تا از ثابت http.StatusCreated به جای عدد 201 استفاده کند، به این صورت:

File: main.go
package main

...

func snippetCreatePost(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusCreated)

    w.Write([]byte("Save a new snippet..."))
}

...

سفارشی‌سازی هدرها (Customizing Headers)

شما همچنین می‌توانید هدرهای HTTP ارسال‌شده به کاربر را با تغییر نقشه هدر پاسخ (Response Header Map) سفارشی کنید. احتمالاً رایج‌ترین کاری که می‌خواهید انجام دهید این است که یک هدر اضافی به نقشه اضافه کنید، که می‌توانید با استفاده از متد w.Header().Add() این کار را انجام دهید.

برای نشان دادن این موضوع، بیایید یک هدر Server: Go به پاسخی که هندلر home ما ارسال می‌کند اضافه کنیم. اگر در حال دنبال کردن هستید، کد هندلر را به این صورت به‌روزرسانی کنید:

File: main.go
package main

...

func home(w http.ResponseWriter, r *http.Request) {
    // Use the Header().Add() method to add a 'Server: Go' header to the
    // response header map. The first parameter is the header name, and
    // the second parameter is the header value.
    w.Header().Add("Server", "Go")

    w.Write([]byte("Hello from Snippetbox"))
}

...

بیایید این را امتحان کنیم و با استفاده از curl یک درخواست دیگر به http://localhost:4000/ ارسال کنیم. این بار باید ببینید که پاسخ اکنون شامل یک هدر جدید Server: Go است، به این صورت:

$ curl -i http://localhost:4000
HTTP/1.1 200 OK
Server: Go
Date: Wed, 18 Mar 2024 11:29:23 GMT
Content-Length: 21
Content-Type: text/plain; charset=utf-8

Hello from Snippetbox

نوشتن بدنه‌های پاسخ (Writing Response Bodies)

تا کنون در این کتاب از w.Write() برای ارسال بدنه‌های پاسخ HTTP خاص به کاربر استفاده کرده‌ایم. و در حالی که این ساده‌ترین و اساسی‌ترین راه برای ارسال یک پاسخ است، در عمل بسیار رایج‌تر است که مقدار http.ResponseWriter خود را به یک تابع دیگر که پاسخ را برای شما می‌نویسد، ارسال کنید.

در واقع، توابع زیادی وجود دارند که می‌توانید برای نوشتن یک پاسخ استفاده کنید!

نکته کلیدی که باید درک کنید این است… زیرا مقدار http.ResponseWriter در هندلرهای شما یک متد Write() دارد، آن io.Writer را برآورده می‌کند.

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

این به این معناست که می‌توانید از توابع کتابخانه استاندارد مانند io.WriteString() و خانواده fmt.Fprint*() (همه آن‌ها یک پارامتر io.Writer می‌پذیرند) برای نوشتن بدنه‌های پاسخ متنی ساده نیز استفاده کنید.

// Instead of this...
w.Write([]byte("Hello world"))

// You can do this...
io.WriteString(w, "Hello world")
fmt.Fprint(w, "Hello world")

بیایید از این استفاده کنیم و کد در هندلر snippetView خود را به‌روزرسانی کنیم تا از تابع fmt.Fprintf() استفاده کند. این به ما اجازه می‌دهد تا مقدار id وایلدکارد را در پیام بدنه پاسخ خود جای‌گذاری کنیم و پاسخ را در یک خط بنویسیم، به این صورت:

File: main.go
package main

...

func snippetView(w http.ResponseWriter, r *http.Request) {
    id, err := strconv.Atoi(r.PathValue("id"))
    if err != nil || id < 1 {
        http.NotFound(w, r)
        return
    }

    fmt.Fprintf(w, "Display a specific snippet with ID %d...", id)
}

...

اطلاعات اضافی

تشخیص محتوا (Content Sniffing)

برای تنظیم خودکار هدر Content-Type، Go محتوای بدنه پاسخ را تشخیص می‌دهد با استفاده از تابع http.DetectContentType(). اگر این تابع نتواند نوع محتوا را حدس بزند، Go به جای آن هدر Content-Type: application/octet-stream را تنظیم می‌کند.

تابع http.DetectContentType() به‌طور کلی به خوبی کار می‌کند، اما یک مشکل رایج برای توسعه‌دهندگان وب این است که نمی‌تواند JSON را از متن ساده تشخیص دهد. بنابراین، به‌طور پیش‌فرض، پاسخ‌های JSON با هدر Content-Type: text/plain; charset=utf-8 ارسال می‌شوند. می‌توانید با تنظیم دستی هدر صحیح در هندلر خود از این اتفاق جلوگیری کنید، به این صورت:

w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"name":"letsgofa"}`))

دستکاری نقشه هدر (Manipulating the Header Map)

در این فصل از w.Header().Add() برای افزودن یک هدر جدید به نقشه هدر پاسخ استفاده کردیم. اما همچنین متدهای Set()، Del()، Get() و Values() وجود دارند که می‌توانید برای دستکاری و خواندن از نقشه هدر استفاده کنید.

// Set a new cache-control header. If an existing "Cache-Control" header exists
// it will be overwritten.
w.Header().Set("Cache-Control", "public, max-age=31536000")

// In contrast, the Add() method appends a new "Cache-Control" header and can
// be called multiple times.
w.Header().Add("Cache-Control", "public")
w.Header().Add("Cache-Control", "max-age=31536000")

// Delete all values for the "Cache-Control" header.
w.Header().Del("Cache-Control")

// Retrieve the first value for the "Cache-Control" header.
w.Header().Get("Cache-Control")

// Retrieve a slice of all values for the "Cache-Control" header.
w.Header().Values("Cache-Control")

قانونی‌سازی هدر (Header Canonicalization)

هنگامی که از متدهای Set()، Add()، Del()، Get() و Values() بر روی نقشه هدر استفاده می‌کنید، نام هدر همیشه با استفاده از تابع textproto.CanonicalMIMEHeaderKey() قانونی‌سازی می‌شود. این تابع اولین حرف و هر حرفی که بعد از یک خط تیره می‌آید را به حروف بزرگ تبدیل می‌کند و بقیه حروف را به حروف کوچک تبدیل می‌کند. این به این معناست که هنگام فراخوانی این متدها، نام هدر غیر حساس به حروف بزرگ و کوچک است.

اگر نیاز دارید که از این رفتار قانونی‌سازی جلوگیری کنید، می‌توانید نقشه هدر زیرین را به‌طور مستقیم ویرایش کنید. این نقشه در پشت صحنه نوع map[string][]string دارد. برای مثال:

w.Header()["X-XSS-Protection"] = []string{"1; mode=block"}

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

اصطلاح فارسی معادل انگلیسی توضیح
سفارشی‌سازی پاسخ‌ها Customizing Responses تنظیم و تغییر جنبه‌های مختلف پاسخ HTTP مانند هدرها، کد وضعیت و بدنه پاسخ
کد وضعیت HTTP HTTP Status Code عددی سه رقمی که نتیجه پردازش درخواست HTTP را نشان می‌دهد
هدر سیستم‌تولید System-generated Header هدرهایی که به طور خودکار توسط سرور HTTP تولید می‌شوند
بدنه پاسخ Response Body محتوای اصلی که در پاسخ HTTP به کاربر ارسال می‌شود
نقشه هدر پاسخ Response Header Map مجموعه‌ای از جفت‌های کلید-مقدار که هدرهای HTTP را در پاسخ نگهداری می‌کند
تشخیص محتوا Content Sniffing فرآیند تشخیص خودکار نوع محتوا با بررسی داده‌های واقعی
قانونی‌سازی هدر Header Canonicalization فرآیند استانداردسازی نام‌های هدر به یک فرمت یکسان
نویسنده Writer اینترفیسی در Go که قابلیت نوشتن داده را فراهم می‌کند
ثابت‌های کد وضعیت Status Code Constants مقادیر ثابت تعریف شده در Go برای کدهای وضعیت HTTP
دستکاری نقشه هدر Header Map Manipulation عملیات افزودن، حذف یا تغییر هدرهای HTTP در پاسخ