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

سفارشی‌سازی پاسخ‌ها

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

$ 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

در این فصل به نحوه سفارشی‌سازی هدرهای پاسخ (response headers) که handlerهای شما ارسال می‌کنند می‌پردازیم، و همچنین به چند روش دیگر که می‌توانید پاسخ‌های متنی ساده به کاربران ارسال کنید نگاه می‌کنیم.

کدهای وضعیت HTTP

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

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..."))
}

...

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

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

سرور را دوباره راه‌اندازی کنید، سپس از 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...

ثابت‌های کد وضعیت

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

بیایید handler 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..."))
}

...

سفارشی‌سازی هدرها

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

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

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

نوشتن بدنه‌های پاسخ

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

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

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

اگر تازه با Go شروع کرده‌اید، مفهوم رابط‌ها (interfaces) می‌تواند کمی گیج‌کننده باشد و نمی‌خواهم الان خیلی روی آن تأکید کنم. اما در سطح عملی، به این معنی است که در هر تابعی که یک پارامتر 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")

بیایید از این استفاده کنیم و کد در handler snippetView خود را به‌روزرسانی کنیم تا از تابع fmt.Fprintf() استفاده کند. این به ما اجازه می‌دهد مقدار wildcard 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() content sniff می‌کند. اگر این تابع نتواند نوع محتوا را حدس بزند، Go به تنظیم هدر Content-Type: application/octet-stream به جای آن بازمی‌گردد.

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

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

دستکاری نقشه هدر

در این فصل از 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")

استانداردسازی هدر

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

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

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