Создание веб-сервера в Go
Библиотека Go делает написание веб-сервера, который отвечает на запросы клиентов, простым и легким.
Сейчас мы создадим и опишем минимальный сервер, который возвращает компонент пути из URL, использованного для обращения к серверу.
Иначе говоря, если запрос имеет вид http://localhost:8000/hello, то ответ будет выглядеть как URL.Path = "/hello"
.
Иными словами, мы напишем минимальный echo-сервер
.
// минимальный "echo"-сервер. package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", handler) // каждый запрос вызывает обработчик log.Fatal(http.ListenAndServe("localhost:8000", nil)) } // Обработчик возвращает компонент пути из URL запроса func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) }
Программа содержит буквально несколько строк
, потому что библиотечные функции выполняют большую часть работы.
Функция main связывает функцию-обработчик с входящим URL, который начинается с /,
и запускает сервер, прослушивающий порт 8000 в ожидании входящих запросов.
Запрос представлен структурой типа http.Request
, которая содержит ряд связанных полей,
одно из которых представляет собой URL входящего запроса.
Полученный запрос передается функции-обработчику, которая извлекает компонент пути (/hello)
из URL запроса и отправляет его обратно в качестве ответа с помощью fmt.Fpnintf.
Давайте запустим наш сервер в фоновом режиме.
В Mac OS X или Linux
добавьте к команде амперсанд (&);
$ go run server.go &
в Microsoft Windows
необходимо запустить команду без амперсанда в отдельном окне.
Затем осуществим клиентский запрос из командной строки:
$ go build fetch $ ./fetch http://localhost:8000 URL.Path = "/" $ ./fetch http://localhost:8000/help URL.Path = "/help"
Расширять возможности сервера довольно легко. Одним полезным расширением является конкретный URL, который возвращает некоторое состояние. Например, этот вариант нашего минисервера делает то же, что и предыдущий, но при этом еще и подсчитывает количество запросов; запрос к URL /count возвращает это количество, за исключением самого запроса /count:
// минимальный "echo"-сервер со счетчиком запросов, package main import ( "fmt" "log" "net/http" "sync" ) var mu sync.Mutex var count int func main() { http.HandleFunc("/", handler) http.HandleFunc("/count", counter) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } // Обработчик, возвращающий компонент пути запрашиваемого URL. func handler(w http.ResponseWriter, r *http.Request) { mu.Lock() count++ mu.Unlock() fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) } // Счетчик, возвращающий количество сделанных запросов, func counter(w http.ResponseWriter, r *http.Request) { mu.Lock() fmt.Fprintf(w, "Count %d\n", count) mu.Unlock() }
У сервера имеется два обработчика, и запрашиваемый URL определяет, какой из них будет вызван:
- запрос /count вызывает counter,
- а все прочие — handler.
Сервер запускает обработчик для каждого входящего запроса в отдельной go-подпрограмме(горутине
),
так что несколько запросов могут обрабатываться одновременно.
Однако если два параллельных запроса попытаются обновить счетчик count в один и тот же момент времени,
он может быть увеличен не согласованно;
в такой программе может возникнуть серьезная ошибка под названием состояние гонки (race condition
).
Чтобы избежать этой проблемы, нужно гарантировать, что доступ к переменной получает не более одной go-подпрограммы одновременно.
Для этого каждый доступ к переменной должен быть окружен вызовами mu.Lock()
и mu.Unlock()
.