سرو کردن فایلهای استاتیک
حالا بیایید ظاهر و احساس صفحه اصلی را با اضافه کردن برخی فایلهای 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 شما اکنون باید شبیه به این باشد:
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 خود را باز کنید و کد زیر را اضافه کنید، تا فایل به این صورت شود:
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 ببینید که شبیه به این است:
آزادانه بازی کنید و از طریق لیست دایرکتوری مرور کنید تا فایلهای فردی را مشاهده کنید. برای مثال، اگر به http://localhost:4000/static/css/main.css ناوبری کنید، باید فایل CSS در مرورگر شما مانند این ظاهر شود:
استفاده از فایلهای استاتیک
با کار کردن صحیح file server، اکنون میتوانیم فایل 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 را بازدید کنید. صفحه اصلی شما اکنون باید شبیه به این باشد:
اطلاعات اضافی
ویژگیها و توابع file server
Handler http.FileServer در Go چند ویژگی واقعاً خوب دارد که ارزش ذکر دارد:
همه مسیرهای درخواست را با اجرای آنها از طریق تابع
path.Clean()قبل از جستجوی یک فایل پاکسازی میکند. این هر عنصر.و..را از مسیر URL حذف میکند، که به توقف حملات directory traversal کمک میکند. این ویژگی به خصوص مفید است اگر از fileserver در کنار یک router استفاده میکنید که به طور خودکار مسیرهای URL را پاکسازی نمیکند.درخواستهای Range به طور کامل پشتیبانی میشوند. این عالی است اگر برنامه شما فایلهای بزرگ را سرو میکند و میخواهید دانلودهای قابل از سرگیری را پشتیبانی کنید. میتوانید این قابلیت را در عمل ببینید اگر از curl برای درخواست بایتهای 100-199 از فایل
logo.pngاستفاده کنید، مانند این:$ curl -i -H "Range: bytes=100-199" --output - http://localhost:4000/static/img/logo.png HTTP/1.1 206 Partial Content Accept-Ranges: bytes Content-Length: 100 Content-Range: bytes 100-199/1075 Content-Type: image/png Last-Modified: Wed, 18 Mar 2024 11:29:23 GMT Date: Wed, 18 Mar 2024 11:29:23 GMT [binary data]
هدرهای
Last-ModifiedوIf-Modified-Sinceبه طور شفاف پشتیبانی میشوند. اگر یک فایل از آخرین باری که کاربر آن را درخواست کرده تغییر نکرده باشد، سپسhttp.FileServerیک کد وضعیت304 Not Modifiedبه جای خود فایل ارسال خواهد کرد. این به کاهش تأخیر و overhead پردازش برای هم client و هم server کمک میکند.Content-Typeبه طور خودکار از پسوند فایل با استفاده از تابعmime.TypeByExtension()تنظیم میشود. میتوانید پسوندها و انواع محتوای سفارشی خود را با استفاده از تابعmime.AddExtensionType()در صورت لزوم اضافه کنید.
عملکرد
در این فصل 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 برای هر دایرکتوری برگرداند. یک توضیح کامل و کد نمونه را میتوانید در این پست وبلاگ پیدا کنید.