Привет Хабр! В этой статье я хочу рассказать, как легко написать полноценный HTTP сервер на Go, дабы у читателей не возникло проблем с поиском необходимого функционала в библиотеках. Использовать будем только функционал стандартных библиотек и оригинальный компилятор.
Сервер не будет обрабатывать множество ошибок — он создан исключительно для того, чтобы объяснить, как его вообще реализовать.
1. main()
Начнём с написания главной функции. Она будет предельно проста.
package main
// Импортируем всё, что нам может понадобиться
import (
"fmt"
"log"
"net/http"
"io/ioutil"
"os"
)
// Наша главная функция
func main() {
// При получени запроса к "/*", если не задано других обработчиков для данного
// запроса, вызываем функцию "handler".
http.HandleFunc("/", handler)
// Ну а как-же без этого?)
log.Println("Запускаемся. Слушаем порт 8080")
// Сканируем запросы к порту 8080. При наличии таковых - отвечаем так, как
// указано выше
http.ListenAndServe(":8080", nil)
}
2. handler()
Главная функция готова, но без обработчика она не будет работать.
// Обработчик запросов. Принимает поток вывода от пакета "net/http",
// а также информацию о запроса
func handler(iWrt http.ResponseWriter, iReq *http.Request) {
// Отправляет "Привет, мир" в ответ на запрос
fmt.Fprintln(iWrt, "Привет, мир")
}
На данном этапе сервер уже прекрасно запускается и отвечает на любой запрос фразой "Привет, мир"
3. Чтение файлов
Ни один полноценный веб-сервер не может обойтись без чтения файлов. И наш тоже.
// Считыватель файлов. Принимает имя файла и выдаёт его содержимое
func readFile(iFileName string) string {
// Считываем файл
lData, err := ioutil.ReadFile(iFileName)
var lOut string // Объявляем строчную переменную
// Если файл существует - записываем его в переменную lOut
if !os.IsNotExist(err) {
lOut = string(lData)
} else { // Иначе - отправляем стандартный текст
lOut = "404"
}
return lOut // Возвращаем полученную информацию
}
Теперь правим функцию handler(), дабы дать ей возможность считывать файлы
// Обработчик запросов. Принимает поток вывода от пакета "net/http",
// а также информацию о запроса
func handler(iWrt http.ResponseWriter, iReq *http.Request) {
var lGet = iReq.URL.Path[1:] // Упростим напсание кода
if lGet == "" || lGet == "/" { // Дабы разрешить "/" запросы
lGet = "index.html"
}
lData := readFile(lGet) // Считываем файл
// Отправляет файл в ответ на запрос
fmt.Fprintln(iWrt, lData)
}
4. И напоследок
Да, сервер не умеет обрабатывать ошибку 404 так, как положено.
Да, он не умеет много чего ещё.
И да, он не умеет использовать HTTPS, хотя небольшая правка функци main это способна исправить.
Однако он уже запускается и способен выдавать файлы из своей же папки. Мало того, он защищён — не позволит обращаться к файлам выше своей директории.
Написал я эту статью (повторяюсь) просто для того, чтобы тем, кто это будет читать, не пришлось разрывать документацию в поисках одной единственной функции, как это пришлось делать мне.
package main
import (
"fmt"
"log"
"io/ioutil"
"net/http"
"os"
)
// Наша главная функция
func main() {
// При получени запроса к "/*", если не задано других обработчиков для данного
// запроса, вызываем функцию "handler".
http.HandleFunc("/", handler)
// Ну а как-же без этого?)
log.Println("Запускаемся. Слушаем порт 8080")
// Сканируем запросы к порту 8080. При наличии таковых - отвечаем так, как
// указано выше
http.ListenAndServe(":8080", nil)
}
// Обработчик запросов. Принимает поток вывода от пакета "net/http",
// а также информацию о запроса
func handler(iWrt http.ResponseWriter, iReq *http.Request) {
var lGet = iReq.URL.Path[1:] // Упростим напсание кода
if lGet == "" || lGet == "/" { // Дабы разрешить "/" запросы
lGet = "index.html"
}
lData := readFile(lGet) // Считываем файл
// Отправляет файл в ответ на запрос
fmt.Fprintln(iWrt, lData)
}
func readFile(iFileName string) string {
// Считываем файл
lData, err := ioutil.ReadFile(iFileName)
var lOut string // Объявляем строчную переменную
// Если файл существует - записываем его в переменную lOut
if !os.IsNotExist(err) {
lOut = string(lData)
} else { // Иначе - отправляем стандартный текст
lOut = "404"
}
return lOut // Возвращаем полученную информацию
}
Комментарии (9)
ertaquo
28.08.2016 18:20+2На эту тему есть превосходная статья Writing Web Applications из официальной документации — ничего разрывать не надо, все описано в одном месте.
У вас, по сути, раскрыто все то же самое, что и в разделе «Introducing the net/http package (an interlude)» этой статьи, разве что добавлено чтение файла. Но позволю себе заметить несколько ошибок:
1. Ваш код уязвим и позволяет обратиться к любому файлу на сервере.
2. 404 отправляется как текст, а не как заголовок.
3. Вместо fmt лучше использовать log.
Короче говоря, ваша статья не учит ничему, а скорее дает вредные советы.handicraftsman
28.08.2016 19:15Это базовая версия и в ней сразу сказано, что она не создана для полноценной работы. И да, правки будут. Наверное, ими сейчас и займусь.
G-M-A-X
28.08.2016 19:11п. 3
> // Отправляет «Привет, мир» в ответ на запрос
fmt.Fprintln(iWrt, lData)
Там уже не «Привет, мир», а содержимое файла
п. 4
>Мало того, он защищён — не позволит обращаться к файлам выше своей директории.
А если придет запрос /../../../../еtc/passwords? Что-то не заметил у вас такой защиты.handicraftsman
28.08.2016 19:14- Спасибо, поправлю
- http-библиотека сокращает /../ в путях до просто /. Так-что выйдет просто /еtc/passwords и 404
dmx102
Жесть