Давайте улучшим внешний вид домашней страницы нашего веб-приложения, добавив в проект несколько статических файлов, таких как CSS стили, изображения, а также добавим небольшой фрагмент JavaScript кода, который поможет нам выделить активный элемент навигации.
Содержание статьи
- Обработчик статических файлов http.FileServer
- Подключаем CSS и JS к шаблону
- Особенности обработчика статических файлов
- Производительность
- Обслуживание отдельных файлов
- Отключение просмотра файлов из директории
- Использование настраиваемой файловой системы
Все наши статические файлы будут сохранены в созданную папку ui/static
, скачиваем необходимые нам стили и изображения следующим образом:
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
1 2 3 4 |
$ cd $HOME/code/snippetbox $ wget https://golangs.org/wp-content/uploads/2021/01/golang-static-files.zip $ unzip -o golang-static-files.zip -d ./ui/static $ rm golang-static-files.zip |
Содержимое папки ui/static
будет выглядеть следующим образом:
Обработчик статических файлов http.FileServer
В Go есть модуль net/http
который поставляется вместе со встроенным обработчиком http.FileServer, который можно использовать для доступа к статическим файлам из определенной директории по HTTP протоколу. Давайте добавим в приложение новый маршрут, чтобы все HTTP-запросы, начинающиеся с "/static/"
, обрабатывались с помощью http.FileServer:
Метод | URL | Обработчик | Действие |
ANY | / | home | Отображение домашней страницы |
ANY | /snippet?id=1 | showSnippet | Отображение определенной заметки |
POST | /snippet/create | createSnippet | Создание новой заметки |
ANY | /static/ | http.FileServer | Обслуживание определенного статического файла |
Для инициализации обработчика http.FileServer
нужно использовать функцию http.FileServer() следующим образом:
1 |
fileServer := http.FileServer(http.Dir("./ui/static")) |
Когда данный обработчик получает HTTP-запрос, он удаляет ведущую косую черту из URL пути, а затем ищет в папке ./ui/static
соответствующий файл для его отправки пользователю.
Для того чтобы все сработало правильно, требуется удалить "/static"
из URL перед его отправкой в http.FileServer
. В противном случае, Go будет искать файла, которого не существует, и пользователь получит ошибку 404 page not found
. К счастью, в Go специально для этой задачи есть помощник http.StripPrefix().
Откройте файл main.go
и добавьте следующий код. В итоге файл должен выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package main import ( "log" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", home) mux.HandleFunc("/snippet", showSnippet) mux.HandleFunc("/snippet/create", createSnippet) // Инициализируем FileServer, он будет обрабатывать // HTTP-запросы к статическим файлам из папки "./ui/static". // Обратите внимание, что переданный в функцию http.Dir путь // является относительным корневой папке проекта fileServer := http.FileServer(http.Dir("./ui/static/")) // Используем функцию mux.Handle() для регистрации обработчика для // всех запросов, которые начинаются с "/static/". Мы убираем // префикс "/static" перед тем как запрос достигнет http.FileServer mux.Handle("/static/", http.StripPrefix("/static", fileServer)) log.Println("Запуск сервера на http://127.0.0.1:4000") err := http.ListenAndServe(":4000", mux) log.Fatal(err) } |
После завершения перезапустите приложение и откройте страницу http://127.0.0.1:4000/static/ в браузере. Вы должны увидеть список, состоящую из папок директории ui/static
.
Можете поэкспериментировать, зайдя в разные папки и посмотреть на файлы. К примеру, при переходе на http://127.0.0.1:4000/static/css/main.css вы увидите, что CSS файл открылся в браузере:
Подключаем CSS и JS к шаблону
Если HTTP-обработчик статических файлов работает правильно, то можно обновить файл ui/html/base.layout.tmpl
подключив к нему наши статические файлы, такие как CSS стили и JS, ну и про лого не забудем:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
{{define "base"}} <!doctype html> <html lang='en'> <head> <meta charset='utf-8'> <title>{{template "title" .}} - Snippetbox</title> <!-- Ссылка на CSS стили и иконку сайта --> <link rel='stylesheet' href='/static/css/main.css'> <link rel='shortcut icon' href='/static/img/favicon.ico' type='image/x-icon'> <!-- Подключаем новый шрифт для сайта от Google Fonts --> <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700'> </head> <body> <header> <h1><a href='/'>Хранилище Заметок</a></h1> </header> <nav> <a href='/'>Домашняя страница</a> </nav> <main> {{template "main" .}} </main> {{template "footer" .}} <!-- Подключаем JS чтобы сделать сайт более динамичным --> <script src="/static/js/main.js" type="text/javascript"></script> </body> </html> {{end}} |
Не забудьте сохранить изменения и затем перейти на страницу http://127.0.0.1:4000. Ваша домашняя страница теперь должна выглядеть следующим образом:
Особенности обработчика статических файлов
Обработчик статических файлов в Go обладает несколькими интересными особенностями, о которых стоит упомянуть:
- Он очищает все пути HTTP-запросов, запуская их через функцию path.Clean() перед тем как приступить к поиску запрашиваемого файла. Так удаляются потенциально опасные символы, такие как
.
и..
из URL, это поможет предотвратить атаки по обходу нижних уровней директорий. Эта функция особенно полезна, если вы используете статические файлы вместе с маршрутизатором, который не очищает URL пути в автоматическом порядке; - Сегментирование HTTP запросов полностью поддерживаются. Это необходимо, если ваше веб-приложение предоставляет пользователям возможность скачивания больших файлов или вы предоставляете видео поток и требуется поддерживать возобновляемые загрузки. Увидеть эту возможность в действии можно при помощи curl для сохранения картинки
logo.png
в несколько запросов по 100-199 байт каждый. К примеру:
123456789$ curl -i -H "Range: bytes=100-199" --output - http://localhost:4000/static/img/logo.pngHTTP/1.1 206 Partial ContentAccept-Ranges: bytesContent-Length: 100Content-Range: bytes 100-199/1075Content-Type: image/pngLast-Modified: Thu, 04 May 2017 13:07:52 GMTDate: Wed, 08 Aug 2018 16:21:16 GMT[binary data] - Такие заголовки как
Last-Modified
иIf-Modified-Since
тоже поддерживаются. Если файл не изменился с момента последнего запроса пользователя,http.FileServer
отправит код состояния304 Not Modified
вместо самого файла, так как сам файл уже есть у клиента в кэше браузера. Это помогает уменьшить задержку и расходы на обработку запроса для клиента и для сервера; - Заголовок
Content-Type
автоматически устанавливается в зависимости от расширения файла с помощью функцииmime.TypeByExtension()
. В зависимости от задачи, вы можете добавить свои собственные расширения и типы контента с помощью функцииmime.AddExtensionType()
.
Производительность
В приведенном выше коде мы настроили обработчик статических файлов таким образом, чтобы он обслуживал только файлы из папки ./ui/static
на вашем жестком диске.
Обратите внимание, что после запуска веб-приложения, обработчик http.FileServer
, возможно, не будет читать файлы напрямую с жесткого диска. Операционные системы на базе Windows и Unix кэшируют недавно использованные файлы в RAM, поэтому (по крайней мере, для часто использованных файлов) http.FileServer
будет доставать их из RAM, а не выполнять медленную обработку файлов с жесткого диска.
Обслуживание отдельных файлов
Иногда может потребоваться обслужить только один статический файл. Для этой задачи, функция http.ServeFile() используется следующим образом:
1 2 3 |
func downloadHandler(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./ui/static/file.zip") } |
Внимание: Обработчик http.ServeFile()
автоматически не очищает путь файла. Если вы указываете в данную функцию путь к файлу полученный от пользователя напрямую, во избежание атак обхода директории, перед использование этих данных, обязательно очистите их с помощью функции filepath.Clean()
. Иначе, пользователь сможет скачать различные файлы с сервера включая файл с настройками к базе данных.
Ограничение просмотра файлов из директории
Есть несколько способов отключения просмотра списка файлов из директории. Какой самый простой?
Добавьте пустой файл index.html
в ту директорию, где требуется отключить вывод списка файлов. Веб-сервер всегда ищет сперва файл index.html, и пользователь увидит пустую страницу с кодом состояния 200 OK
. Если вы хотите использоваться данный способ для всех директорий в ./ui/static
, можно задействовать следующую команду:
1 |
$ find ./ui/static -type d -exec touch {}/index.html \; |
Более сложным способом (однако он считается лучшим) является создание настраиваемой имплементации файловой системы http.FileSystem, с помощью которой будет возвращаться ошибка os.ErrNotExist
для любого HTTP запроса напрямую к папке.
Например, если пользователь попытается открыть в браузере ссылку http://127.0.0.1:4000/static/ в браузере, то он из соображений безопасности, не должен увидеть список файлов. Он должен получить ошибку 404 страница не найдена. Ниже вы увидите пример как это реализовать.
Использование настраиваемой файловой системы
Последним вариантом, который мы рассмотрим, будет использование настраиваемой файловой системы и ее последующая передача в http.FileServer
.
Обновляем наш main.go добавив новую структуру neuteredFileSystem
и метод Open
для неё.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package main import ( "log" "net/http" "path/filepath" ) func main() { mux := http.NewServeMux() fileServer := http.FileServer(neuteredFileSystem{http.Dir("./static")}) mux.Handle("/static", http.NotFoundHandler()) mux.Handle("/static/", http.StripPrefix("/static", fileServer)) err := http.ListenAndServe(":4000", mux) log.Fatal(err) } type neuteredFileSystem struct { fs http.FileSystem } func (nfs neuteredFileSystem) Open(path string) (http.File, error) { f, err := nfs.fs.Open(path) if err != nil { return nil, err } s, err := f.Stat() if s.IsDir() { index := filepath.Join(path, "index.html") if _, err := nfs.fs.Open(index); err != nil { closeErr := f.Close() if closeErr != nil { return nil, closeErr } return nil, err } } return f, nil } |
В данном коде мы создаем настраиваемый тип neuteredFileSystem
, который включает в себя http.FileSystem
. Затем мы создаем метод Open()
, который вызывается каждый раз, когда http.FileServer
получает запрос.
В методе Open()
мы открываем вызываемый путь. Используя метод IsDir()
мы проверим если вызываемый путь является папкой или нет. Если это папка, то с помощью метода Stat("index.html")
мы проверим если файл index.html
существует внутри данной папки.
Если файл index.html
не существует, то метод вернет ошибку os.ErrNotExist
(которая, в свою очередь, будет преобразована через http.FileServer
в ответ 404 страница не найдена
). Мы также вызываем метод Close()
для закрытия только, что открытого index.html файла, чтобы избежать утечки файлового дескриптора.
Во всех остальных случаях мы просто возвращаем файл и даем http.FileServer
сделать то, что он должен.
Попробуем открыть http://127.0.0.1:4000/static/, в примере выше есть скрин на котором виден список всех внутренних папок из ./static
.
Как это выглядит сейчас?
PS: Если у вас есть файл index.html
в папке /code/snippetbox/ui/static/
, то можете его удалить, иначе у вас будет белая страница.
Теперь все работает довольно неплохо:
- Все прямые HTTP запросы к папкам (в которых нет файла
index.html
) возвращают ответ404 Страница не найдена
, вместо показа всего списка из папок и файлов которые находятся в этой папке. Это отлично работает для запросов на конце с косой чертой, а также без нее; - Поведение
http.FileServer
по умолчанию никак не меняется, и файлыindex.html
отображаются в соответствии со стандартной документацией библиотеки.
Исходный код веб-приложения на Golang
Это было весьма весело! По традиции, ниже вы можете скачать готовый код который является актуальным на данный момент прочтения этой статьи.
Скачать: snippetbox-8.zip
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»