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

تجزیه داده‌های فرم (Parsing Form Data)

در این بخش، نحوه تجزیه و دسترسی (Parsing and Accessing) به داده‌های فرم (Form Data) ارسال شده در یک درخواست POST (POST Request) را بررسی می‌کنیم.

برای شروع، بیایید یک هندلر جدید (New Handler) به نام snippetCreatePost ایجاد کنیم که مسئول پردازش داده‌های فرم ارسالی است:

File: cmd/web/handlers.go
package main

import (
    "fmt"
    "net/http"
    "strconv"
)

func (app *application) snippetCreatePost(w http.ResponseWriter, r *http.Request) {
    // First we call r.ParseForm() which adds any data in POST request bodies
    // to the r.PostForm map. This also works in the same way for PUT and
    // PATCH requests. If there are any errors, we use our app.clientError
    // helper to send a 400 Bad Request response to the user.
    err := r.ParseForm()
    if err != nil {
        app.clientError(w, http.StatusBadRequest)
        return
    }

    // Use the r.PostForm.Get() method to retrieve the relevant data fields
    // from the r.PostForm map.
    title := r.PostForm.Get("title")
    content := r.PostForm.Get("content")
    expires := r.PostForm.Get("expires")

    // Convert the expires value from form to an integer.
    expiresInt, err := strconv.Atoi(expires)
    if err != nil {
        app.clientError(w, http.StatusBadRequest)
        return
    }

    // Pass the data to the SnippetModel.Insert() method, receiving the
    // ID of the new record back.
    id, err := app.snippets.Insert(title, content, expiresInt)
    if err != nil {
        app.serverError(w, r, err)
        return
    }

    // Redirect the user to the relevant page for the snippet.
    http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther)
}

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

07.02-01.png

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

07.02-02.png

اطلاعات تکمیلی

متد PostFormValue

پکیج net/http همچنین متد r.PostFormValue() را ارائه می‌دهد، که اساساً یک تابع میانبر است که r.ParseForm() را برای شما فراخوانی می‌کند و سپس مقدار فیلد مناسب را از r.PostForm بازیابی می‌کند.

توصیه می‌کنم از این میانبر استفاده نکنید زیرا هر خطایی که توسط r.ParseForm() برگردانده می‌شود را به صورت خاموش نادیده می‌گیرد. اگر از آن استفاده کنید، به این معنی است که مرحله تجزیه ممکن است با خطا مواجه شود و برای کاربران شکست بخورد، اما هیچ مکانیزم بازخوردی برای اطلاع دادن به آنها (یا شما) درباره مشکل وجود ندارد.

فیلدهای چند مقداری

در این حالت باید مستقیماً با نقشه r.PostForm کار کنید. نوع زیربنایی نقشه r.PostForm، url.Values است، که به نوبه خود نوع زیربنایی map[string][]string دارد. بنابراین، برای فیلدهایی با چندین مقدار می‌توانید روی نقشه زیربنایی به این صورت حلقه بزنید:

for i, item := range r.PostForm["items"] {
    fmt.Fprintf(w, "%d: Item %s\n", i, item)
}

محدود کردن اندازه فرم

به طور پیش‌فرض، فرم‌هایی که با متد POST ارسال می‌شوند محدودیت اندازه ۱۰ مگابایت داده دارند. استثنا این است که اگر فرم شما ویژگی enctype="multipart/form-data" داشته باشد و داده‌های چندبخشی ارسال کند، در این صورت هیچ محدودیت پیش‌فرضی وجود ندارد.

اگر می‌خواهید محدودیت ۱۰ مگابایت را تغییر دهید، می‌توانید از تابع http.MaxBytesReader() به این صورت استفاده کنید:

// Limit the request body size to 4096 bytes
r.Body = http.MaxBytesReader(w, r.Body, 4096)

err := r.ParseForm()
if err != nil {
    http.Error(w, "Bad Request", http.StatusBadRequest)
    return
}

با این کد تنها ۴۰۹۶ بایت اول بدنه درخواست در طول r.ParseForm() خوانده خواهد شد. تلاش برای خواندن فراتر از این محدودیت باعث می‌شود MaxBytesReader یک خطا برگرداند، که متعاقباً توسط r.ParseForm() نمایش داده می‌شود.

علاوه بر این - اگر به محدودیت برخورد شود - MaxBytesReader یک پرچم روی http.ResponseWriter تنظیم می‌کند که به سرور دستور می‌دهد اتصال TCP زیربنایی را ببندد.

پارامترهای رشته پرس‌وجو

اگر فرمی دارید که داده‌ها را با استفاده از متد HTTP GET، به جای POST ارسال می‌کند، داده‌های فرم به عنوان پارامترهای رشته پرس‌وجو URL گنجانده خواهند شد. به عنوان مثال، اگر یک فرم HTML به این شکل داشته باشید:

<form action='/foo/bar' method='GET'>
    <input type='text' name='title'>
    <input type='text' name='content'>
    
    <input type='submit' value='Submit'>
</form>

هنگامی که فرم ارسال می‌شود، یک درخواست GET با URL به این شکل ارسال خواهد کرد: /foo/bar?title=value&content=value.

می‌توانید مقادیر پارامترهای رشته پرس‌وجو را در هندلرهای خود از طریق متد r.URL.Query().Get() بازیابی کنید. این همیشه یک مقدار رشته‌ای برای یک پارامتر برمی‌گرداند، یا رشته خالی "" اگر هیچ پارامتر منطبقی وجود نداشته باشد. به عنوان مثال:

func exampleHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Query().Get("title")
    content := r.URL.Query().Get("content")

    ...
}

نقشه r.Form

روش جایگزین برای دسترسی به پارامترهای رشته پرس‌وجو از طریق نقشه r.Form است. این مشابه نقشه r.PostForm است که در این فصل استفاده کردیم، با این تفاوت که شامل داده‌های فرم از هر بدنه درخواست POST و هر پارامتر رشته پرس‌وجو است.

فرض کنید کدی در هندلر خود به این شکل دارید:

err := r.ParseForm()
if err != nil {
    http.Error(w, "Bad Request", http.StatusBadRequest)
    return
}

title := r.Form.Get("title")

در این کد، خط r.Form.Get("title") مقدار title را از بدنه درخواست POST یا از یک پارامتر رشته پرس‌وجو با نام title برمی‌گرداند. در صورت تعارض، مقدار بدنه درخواست بر پارامتر رشته پرس‌وجو اولویت دارد.

استفاده از r.Form می‌تواند بسیار مفید باشد اگر می‌خواهید برنامه شما نسبت به نحوه ارسال مقادیر داده بی‌تفاوت باشد. اما خارج از آن سناریو، r.Form هیچ مزیتی ارائه نمی‌دهد و واضح‌تر و صریح‌تر است که داده‌ها را از بدنه درخواست POST از طریق r.PostForm یا از پارامترهای رشته پرس‌وجو از طریق r.URL.Query().Get() بخوانید.

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

اصطلاح فارسی معادل انگلیسی توضیح
تجزیه و دسترسی Parsing and Accessing خواندن و استخراج داده‌های فرم
داده‌های فرم Form Data اطلاعات وارد شده توسط کاربر در فرم
درخواست POST POST Request درخواست HTTP برای ارسال داده به سرور
هندلر جدید New Handler تابع جدید برای پردازش درخواست‌ها
تجزیه فرم Parse Form استخراج داده‌های فرم از درخواست
نقشه داده‌ها Data Map ساختار داده برای نگهداری مقادیر فرم
خطای کاربر Client Error خطای ناشی از درخواست نامعتبر
تبدیل داده Data Conversion تغییر نوع داده (مثلاً رشته به عدد)
درج رکورد Record Insertion افزودن داده جدید به پایگاه داده
تغییر مسیر Redirect هدایت کاربر به صفحه دیگر