Let's Go مبانی › سرو کردن فایل‌های استاتیک
قبلی · فهرست · بعدی
فصل ۲.۹.

سرو کردن فایل‌های استاتیک

حالا بیایید ظاهر و احساس صفحه اصلی را با اضافه کردن برخی فایل‌های CSS و تصویر استاتیک به پروژه خود بهبود دهیم، همراه با کمی JavaScript برای برجسته کردن آیتم ناوبری فعال.

اگر همراه هستید، می‌توانید فایل‌های لازم را بگیرید و آن‌ها را در پوشه ui/static که قبلاً ساختیم با دستورات زیر استخراج کنید:

$ cd $HOME/code/snippetbox
$ curl https://www.alexedwards.net/static/sb-v2.tar.gz | tar -xvz -C ./ui/static/

محتوای دایرکتوری ui/static شما اکنون باید شبیه به این باشد:

02.09-01.png

Handler http.FileServer

بسته net/http در Go با یک handler داخلی http.FileServer همراه است که می‌توانید از آن برای سرو کردن فایل‌ها از طریق HTTP از یک دایرکتوری خاص استفاده کنید. بیایید یک route جدید به برنامه خود اضافه کنیم تا همه درخواست‌های GET که با "/static/" شروع می‌شوند با استفاده از این handler شوند، مانند این:

Route pattern Handler Action
GET / home نمایش صفحه اصلی
GET /snippet/view/{id} snippetView نمایش یک تکه متن خاص
GET /snippet/create snippetCreate نمایش فرم برای ایجاد یک تکه متن جدید
POST /snippet/create snippetCreatePost ذخیره یک تکه متن جدید
GET /static/ http.FileServer سرو کردن یک فایل استاتیک خاص

برای ایجاد یک handler جدید http.FileServer، باید از تابع http.FileServer() مانند این استفاده کنیم:

fileServer := http.FileServer(http.Dir("./ui/static/"))

وقتی این handler درخواستی برای یک فایل دریافت می‌کند، اسلش ابتدایی را از مسیر URL درخواست حذف می‌کند و سپس دایرکتوری ./ui/static را برای فایل مربوطه جستجو می‌کند تا به کاربر ارسال کند.

پس، برای اینکه این به درستی کار کند، باید "/static" ابتدایی را از مسیر URL قبل از ارسال آن به http.FileServer حذف کنیم. در غیر این صورت به دنبال فایلی خواهد بود که وجود ندارد و کاربر پاسخ 404 page not found دریافت خواهد کرد. خوشبختانه Go یک helper http.StripPrefix() به طور خاص برای این کار شامل می‌کند.

فایل main.go خود را باز کنید و کد زیر را اضافه کنید، تا فایل به این صورت شود:

File: cmd/web/main.go
package main

import (
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()

    // Create a file server which serves files out of the "./ui/static" directory.
    // Note that the path given to the http.Dir function is relative to the project
    // directory root.
    fileServer := http.FileServer(http.Dir("./ui/static/"))

    // Use the mux.Handle() function to register the file server as the handler for
    // all URL paths that start with "/static/". For matching paths, we strip the
    // "/static" prefix before the request reaches the file server.
    mux.Handle("GET /static/", http.StripPrefix("/static", fileServer))

    // Register the other application routes as normal..
    mux.HandleFunc("GET /{$}", home)
    mux.HandleFunc("GET /snippet/view/{id}", snippetView)
    mux.HandleFunc("GET /snippet/create", snippetCreate)
    mux.HandleFunc("POST /snippet/create", snippetCreatePost)

    log.Print("starting server on :4000")
    
    err := http.ListenAndServe(":4000", mux)
    log.Fatal(err)
}

پس از تکمیل، برنامه را دوباره راه‌اندازی کنید و http://localhost:4000/static/ را در مرورگر خود باز کنید. باید یک لیست دایرکتوری قابل ناوبری از پوشه ui/static ببینید که شبیه به این است:

02.09-02.png

آزادانه بازی کنید و از طریق لیست دایرکتوری مرور کنید تا فایل‌های فردی را مشاهده کنید. برای مثال، اگر به http://localhost:4000/static/css/main.css ناوبری کنید، باید فایل CSS در مرورگر شما مانند این ظاهر شود:

02.09-03.png

استفاده از فایل‌های استاتیک

با کار کردن صحیح file server، اکنون می‌توانیم فایل ui/html/base.tmpl را به‌روزرسانی کنیم تا از فایل‌های استاتیک استفاده کند:

File: ui/html/base.tmpl
{{define "base"}}
<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>{{template "title" .}} - Snippetbox</title>
         <!-- Link to the CSS stylesheet and favicon -->
        <link rel='stylesheet' href='/static/css/main.css'>
        <link rel='shortcut icon' href='/static/img/favicon.ico' type='image/x-icon'>
        <!-- Also link to some fonts hosted by Google -->
        <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700'>
    </head>
    <body>
        <header>
            <h1><a href='/'>Snippetbox</a></h1>
        </header>
        {{template "nav" .}}
        <main>
            {{template "main" .}}
        </main>
        <footer>Powered by <a href='https://golang.org/'>Go</a></footer>
         <!-- And include the JavaScript file -->
        <script src='/static/js/main.js' type='text/javascript'></script>
    </body>
</html>
{{end}}

مطمئن شوید که تغییرات را ذخیره می‌کنید، سپس سرور را دوباره راه‌اندازی کنید و http://localhost:4000 را بازدید کنید. صفحه اصلی شما اکنون باید شبیه به این باشد:

02.09-04.png

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

ویژگی‌ها و توابع file server

Handler http.FileServer در Go چند ویژگی واقعاً خوب دارد که ارزش ذکر دارد:

عملکرد

در این فصل file server را تنظیم کردیم تا فایل‌ها را از دایرکتوری ./ui/static روی هارد دیسک شما سرو کند.

اما مهم است توجه داشته باشید که http.FileServer احتمالاً این فایل‌ها را از دیسک نخواهد خواند وقتی برنامه در حال اجرا است. هم سیستم‌عامل‌های Windows و هم Unix-based فایل‌های اخیراً استفاده شده را در RAM cache می‌کنند، پس (حداقل برای فایل‌های مکرراً سرو شده) احتمال دارد که http.FileServer آن‌ها را از RAM سرو کند به جای انجام round-trip نسبتاً کند به هارد دیسک شما.

سرو کردن فایل‌های تکی

گاهی ممکن است بخواهید یک فایل تکی را از داخل یک handler سرو کنید. برای این کار تابع http.ServeFile() وجود دارد، که می‌توانید مانند این استفاده کنید:

func downloadHandler(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "./ui/static/file.zip")
}

غیرفعال کردن لیست‌های دایرکتوری

اگر می‌خواهید لیست‌های دایرکتوری را غیرفعال کنید، چند رویکرد مختلف وجود دارد که می‌توانید بگیرید.

ساده‌ترین راه؟ یک فایل خالی index.html به دایرکتوری خاصی که می‌خواهید لیست‌ها را برای آن غیرفعال کنید اضافه کنید. این سپس به جای لیست دایرکتوری سرو خواهد شد، و کاربر یک پاسخ 200 OK بدون body دریافت خواهد کرد. اگر می‌خواهید این را برای همه دایرکتوری‌های زیر ./ui/static انجام دهید می‌توانید از دستور استفاده کنید:

$ find ./ui/static -type d -exec touch {}/index.html \;

یک راه‌حل پیچیده‌تر (اما به طور قابل بحث بهتر) ایجاد یک پیاده‌سازی سفارشی از http.FileSystem است، و آن را طوری تنظیم کنید که یک خطای os.ErrNotExist برای هر دایرکتوری برگرداند. یک توضیح کامل و کد نمونه را می‌توانید در این پست وبلاگ پیدا کنید.