Голанг: HTTP и сети

Руководство для начинающих по обслуживанию файлов с использованием HTTP-серверов в Go

В этой статье мы собираемся реализовать некоторые из основных API-интерфейсов модуля http для обслуживания статического контента в Интернете.

В предыдущем руководстве мы говорили о базовой реализации HTTP-сервера в Go и о том, как работают маршруты. Если вы не читали его, я бы порекомендовал просмотреть его, прежде чем продолжить эту статью.



Создание простого« Hello World! HTTP-сервер в Go
В этой статье мы узнаем, как запустить простой HTTP-сервер, который отвечает текстом Hello World! Для всех… medium.com »



В Интернете мы обслуживаем не только обработанный HTML, но и файлы, такие как CSS, JavaScript, PDF и т. Д. В большинстве случаев эти файлы обслуживаются как есть с диска, но иногда их необходимо обработать перед доставкой. клиенту.

В некоторых случаях мы обслуживаем содержимое каталога и позволяем пользователю решать, какие файлы ему нужны. В этой статье мы рассмотрим некоторые сценарии использования и при этом реализуем некоторые меры безопасности.

FileServer функция

Функция http.FileServer() обеспечивает возможность обслуживания всего каталога файловой системы с помощью индексов. Давайте посмотрим на его синтаксис.

func FileServer(root FileSystem) Handler

Как видите, он возвращает объект Handler, что делает его идеальным кандидатом для использования в качестве handler в функции ListenAndServe или Handle. Он принимает аргумент типа интерфейса FileSystem.

type FileSystem interface {
    Open(name string) (http.File, error)
}

Аргумент root представляет каталог файловой системы, из которого будет обслуживаться контент. К счастью, нам не нужно создавать собственную реализацию интерфейса FileSystem.

http.Dir тип

Тип Dir может выглядеть как функция, но это псевдоним типа данных string и реализует метод Open, определенный интерфейсом FileSystem. Мы можем вызывать тип Dir как функцию, которая представляет собой не что иное, как синтаксис преобразования типов.

Строковое значение, передаваемое синтаксису преобразования типов, является относительным или абсолютным каталогом в собственной файловой системе.

var fs FileSystem = http.Dir("/tmp")

💡 Тип http.Dir не исключает . файлы с префиксом, такие как .git или .htpasswd, поэтому будьте осторожны при обслуживании. В противном случае вы можете создать свой собственный FileSystem интерфейс реализации. Если мы передадим пустую строку каталога “”, Go будет использовать os.Executable() в качестве каталога по умолчанию для обслуживания файлов.

Обслуживание каталога

Я создал простую временную папку в моем пользовательском каталоге, чтобы продемонстрировать этот пример. Это выглядит следующим образом.

/Users/Uday.Hiwarale/tmp
├── .htpasswd
├── files
|  └── test.pdf
├── main.js
├── page.html
└── style.css

Давайте напишем простой HTTP-сервер Go для обслуживания /Users/Uday.Hiwarale/tmp каталога с использованием http.FileServer обработчика и http.Dir типа.

Из приведенного выше примера мы видим, что мы не реализовали никаких маршрутов, и весь трафик проходит через fs мультиплексор запросов. Но если запустить это приложение и открыть URL-адрес по умолчанию в браузере, все будет довольно просто.

Если путь URL-адреса запроса - /, то FileServer возвращает список файлов и папок из каталога root. Если путь URL-адреса запроса совпадает с путем к файлу или папке в каталоге root, то возвращается этот файл или папка.

💡 Если вам интересно, как браузер узнал, что это файл PDF, вам следует проверить заголовок ответа Content-Type. FileServer имеет функцию установки соответствующего заголовка Content-Type в зависимости от типа файла.

Если путь URL-адреса запроса не соответствует файлу или папке, содержащимся в каталоге root, возвращается сообщение 404 страница не найдена со статусом HTTP 404.

Индексы обработки

Если у вас есть опыт работы с сервером Apache или Nginx, то вы знаете, что такое индексы. индекс - это список файлов и папок, содержащихся в каталоге. Если каталог не содержит index.html файл, эти серверы берут на себя ответственность за возврат HTML со списком этих файлов и папок.

Именно этим и занимается наш файловый сервер Go. Однако, вообще говоря, вы не хотите, чтобы пользователь видел содержимое каталога. В этом случае вы должны взять на себя ответственность за возврат HTML с индексом каталога.

Мы можем сделать это, поместив index.html в каждый каталог, который может возвращать индекс. Давайте переименуем файл page.html в index.html, содержащийся в каталоге root, обслуживаемом нашим HTTP-сервером.

Теперь, если вы получите доступ к корневому URL-адресу, наш файловый сервер вместо этого вернет index.html. Таким образом, мы отключили доступ к индексу каталога root. Однако внутренние каталоги, такие как files/, могут по-прежнему возвращать индекс, если они не содержат index.html файл.

Из приведенного выше примера, поскольку index.html - это файл в каталоге root, мы должны иметь доступ к нему по пути /index.html URL. Однако Go перенаправит любой URL-адрес, заканчивающийся на /index.html, на путь ./ (текущий каталог файла) с кодом состояния 301 Moved Permanantly.

Раздача файлов по маршруту

В предыдущей статье мы узнали, как создавать маршруты и как обрабатывать ответы для отдельных маршрутов. В типичном сценарии использования нам нужен конкретный маршрут для обработки части обслуживания статических файлов. Давайте создадим /static маршрут для обслуживания содержимого каталога /tmp.

Теперь, когда наша программа настроена, мы можем запустить сервер и использовать /static маршрут для доступа к файлам из каталога /tmp, как обычно.

Из приведенного выше результата мы не получаем желаемого результата. По какой-то причине HTTP-сервер Go не может перенаправить URL-адрес нужному обработчику, и запрос перехватывается обработчиком /.

Это происходит потому, что http.Handle по сравнению с http.HandleFunc для успешной работы требуется завершающий /. Если мы изменим /static путь маршрута на /static/, он должен заработать.

// handle `/static/` route using a Handler
http.Handle( "/static/", fs )

Из приведенного выше результата мы видим, что обработчик fs файлового сервера вызывает, но не может вернуть файл. Если вы попробуете любую комбинацию URL-адресов, результаты будут такими же. Так что же вызывает эту проблему?

Проблема возникает из-за URL-адреса запроса. http.FileServer обслуживает файл, просматривая URL-адрес запроса. Поскольку наш URL-адрес запроса - /static/files/test.pdf, он попытается найти файл в root каталоге с путем /tmp/static/files/test.pdf, а его не существует.

Каким-то образом нам нужно удалить часть /static из URL-адреса и вызвать обработчик fs для обслуживания файла. Это именно то, что делает http.StripPrefix.

func StripPrefix(prefix string, h Handler) Handler

Давайте изменим наш предыдущий пример и реализуем метод StripPrefix.

// handle `/static/` route using a Handler
http.Handle( "/static/", http.StripPrefix( "/static", fs ) )

ServeFile функция

Пакет http предоставляет ServeFile функцию для обслуживания файла на диске, используя его путь к файлу. Эта функция имеет следующий синтаксис.

func ServeFile(w http.ResponseWriter, r *http.Request, name string)

Нам известны первые два аргумента. Аргумент name - это путь к файлу на диске. Во всех случаях рекомендуется абсолютный путь, но это относительный путь, тогда все становится немного странно.

Давайте создадим простой HTTP-сервер для обслуживания некоторых файлов с помощью функции ServeFile. Мы можем повторно использовать наш предыдущий каталог tmp/ для этого варианта использования.

В программе, приведенной выше, мы используем /pdf путь маршрута для получения test.pdf файла, и он отлично работает.

Однако у вышеуказанной программы есть некоторые проблемы. Прежде всего, путь к каталогу root, сохраненный в TmpDir, разделяется /. Это не зависит от платформы, и другие платформы, такие как Windows, могут использовать \ в качестве разделителя.

Чтобы преобразовать путь, независимый от платформы, в путь, зависящий от платформы, можно использовать path/filepath package. Функция FromSlash заменяет / допустимым разделителем, например / в системах Unix и \ в случае Windows.

var TmpDir = filepath.FromSlash("/Users/Uday.Hiwarale/tmp/")

Во-вторых, мы присоединяемся к файлу /files/test.pdf с помощью оператора +, и он также содержит /. Мы можем использовать функцию filepath.Join(), чтобы объединить два пути вместе с некоторой очисткой (удаляет лишние косые черты).

filepath.Join( TmpDir, "/files/test.pdf" )

Измерения безопасности

Пока name аргумент функции ServeFile очищен должным образом, все в порядке. Однако, если в эту функцию передается необработанный пользовательский ввод с URL-адреса, это может привести к массовым нарушениям безопасности.

Например, если URL-адрес запроса содержит элемент .. path, который в общих чертах переводится как перемещение вверх по каталогу, мы предоставляем доступ к каталогу, который может содержать конфиденциальные файлы.

Чтобы решить эту проблему, мы можем либо отклонить запросы, содержащие .. элементов, либо удалить небезопасные элементы пути перед обслуживанием файла. Однако функция ServeFile имеет встроенный механизм для работы с этой ситуацией, и в этом случае она отклоняет запрос.

Обработка index.html

Как мы видели в FileServer уроке, index.html, содержащийся в каталоге, действует как индекс каталога. Но когда мы используем /index.html в URL-адресе запроса, он перенаправляется на ближайший / URL-путь, а затем обслуживает его.

То же самое происходит с функцией ServeFile. Если URL-адрес запроса содержит /index.html, он будет перенаправлен на ./ путь (относительный каталог).

В программе выше мы обслуживаем файл index.html из маршрута /index.html. Мы также добавили / путь для обработки любых откатов.

Как видите, мы были перенаправлены на путь ./, который равен / для index.html, и получили ответ по умолчанию - маршрут /.

Чтобы избежать такой ситуации, мы можем либо прекратить использование /index.html, что является общепринятым в Интернете, либо мы можем использовать другие механизмы для обслуживания содержимого файла, такие как функция http.ServeContent.

Обработка относительных путей

Если вы используете функцию ServeFile и аргумент name является абсолютным путем, вам не о чем беспокоиться о доставке файла. Но использование относительного пути к файлу может привести к некоторым проблемам.

Мы должны учитывать тот факт, что Go - это компилируемый язык. Из нескольких программных файлов, расположенных в разных местах, мы можем создать двоичный исполняемый файл. Любые относительные пути к файлам, используемые в этих файлах, не имеют значения во время выполнения.

os.Executable функция

В большинстве случаев мы должны стараться максимально использовать абсолютный путь к файлу. Но в некоторых случаях, когда важен относительный путь к файлу, мы должны использовать функцию os.Executable, которая возвращает абсолютный путь к запущенному исполняемому файлу.

Если вы отправляете .exe файл своей программы и пользователям было дано указание поместить этот файл в каталог, содержащий некоторые статические файлы, от которых зависит ваша программа, то функция os.Executable() пригодится для получения каталога запущенного исполняемого файла.

exePath, err := os.Executable()   => /usr/share/program.exe
exeDir := filepath.Dir(exePath) => /usr/share/

os.Getwd функция

В некоторых случаях вам понадобится каталог терминала, на котором выполнялась программа Go или исполняемый файл. В Bash вы могли использовать команду pwd. os.Getwd() возвращает текущий рабочий каталог.