Создание веб-сервера в 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().