Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Веб-программирование в Go

Часть 1

В go есть стандартный пакет net/http, который позволяет написать свой собственный веб-сервер. Начнем с написания простейшего примера: сервер будет обслуживать всего одну статическую страницу, на которой будет подсчитываться и выводиться счетчик ссылок:

 package main
 
 import (
     "fmt"
     "net/http"
 )
 
 
 type webCounter struct {
   count chan int
 }
 
 func NewCounter() *webCounter {
   counter := new(webCounter)
   counter.count = make(chan int, 1)
   go func() {
     for i:=1 ;; i++ { counter.count <- i }
   }()
   return counter
 }
 
 func (w *webCounter) ServeHTTP(r http.ResponseWriter, rq *http.Request) {
   if rq.URL.Path != "/" {
     r.WriteHeader(http.StatusNotFound)
   return
 }
 fmt.Fprintf(r, "You are visitor %d", <-w.count)
 }
 
 func main() {
   err := http.ListenAndServe(":8000", NewCounter())
   if err != nil {
     fmt.Printf("Server failed: ", err.Error())
   }
 }
Компилируем:
 go build
Запускаем откомпилированный бинарник, открываем броузер и набираем адрес:
   http://localhost:8000/
и обновляем несколько раз страницу. При каждом обновлении страницы будет срабатывать метод ServeHTTP(), и для обработки запроса каждый раз будет генериться goroutine. Все они будут обслуживаться одним и тем же каналом до окончания работы веб-сервера, поэтому счетчик не теряет свое значение.

Напишем веб-клиента, который будем делать запрос к нашему серверу и выводить содержимое заглавной страницы:


 package main
 
 import "fmt"
 import "net/http"
 import "os"
 import "io"
 
 
 func main() {
 	client := &http.Client{}
 	client.CheckRedirect =
 		func(req *http.Request, via []*http.Request) error {
 		fmt.Fprintf(os.Stderr, "Redirect: %v\n", req.URL);
 		return nil
 	}
 	var url string
 	url = "http://localhost:8000/"
 	page, err := client.Get(url)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
 		return
 	}
 	io.Copy(os.Stdout, page.Body)
 	page.Body.Close()
 }
Структура http.Client содержит метод Get(url), которая получает содержимое этой страницы.

Стандартный пакет "text/template" позволяет использовать технологию шаблонов при генерации HTML. В следующем примере имеется шаблон для главной страницы:

 <html>
 	<head>
 		<title>Go Web Counter</title>
 	</head>
 	<body>
 		<h1>A Simple Example</h1>
 		<p>You are visitor: {{.Counter}}</p>
 	</body>
 </html>
Соответственно меняется код самого сервера:

 package main
 import "fmt"
 import "net/http"
 import "text/template"
 
 type webCounter struct {
 	count chan int
 	template *template.Template
 }
 func NewCounter() *webCounter {
 	counter := new(webCounter)
 	counter.count = make(chan int, 1)
 	go func() {
 		for i:=1 ;; i++ { counter.count <- i }
 	}()
 	counter.template, _ = template.ParseFiles("counter.html")
 	return counter
 }
 func (w *webCounter) ServeHTTP(r http.ResponseWriter, rq *http.Request) {
 	if rq.URL.Path != "/" {
 		r.WriteHeader(http.StatusNotFound)
 		return
 	}
 	w.template.Execute(r, struct{Counter int}{<-w.count})
 }
 func main() {
 	err := http.ListenAndServe(":8000", NewCounter())
 	if err != nil {
 		fmt.Printf("Server failed: ", err.Error())
 	}
 }
Создается анонимная структура counter, хранящая счетчик. Она читает шаблон с диска и заполняет в нем {{.Counter}} значением счетчика.

В следующем примере показано, как работают вложенные шаблоны. Имеется базовый шаблон base.html, в который могут быть вложены два других - index.html либо about.html. Исходники можно найти на гитхабе. В одном каталоге с кодом сервера нужно создать подкаталог templates, куда положить 3 шаблона - base.html, index.html, about.html. Код сервера:


 package main
 
 import (
 	"fmt"
 	"html/template"
 	"io"
 	"log"
 	"net/http"
 	"time"
 )
 
 const STATIC_URL string = "/static/"
 const STATIC_ROOT string = "static/"
 
 type Context struct {
 	Title  string
 	Static string
 }
 
 func Home(w http.ResponseWriter, req *http.Request) {
 	context := Context{Title: "Welcome!"}
 	render(w, "index", context)
 }
 
 func About(w http.ResponseWriter, req *http.Request) {
 	context := Context{Title: "About"}
 	render(w, "about", context)
 }
 
 func render(w http.ResponseWriter, tmpl string, context Context) {
 	context.Static = STATIC_URL
 	tmpl_list := []string{"templates/base.html",
 		fmt.Sprintf("templates/%s.html", tmpl)}
 	t, err := template.ParseFiles(tmpl_list...)
 	if err != nil {
 		log.Print("template parsing error: ", err)
 	}
 	err = t.Execute(w, context)
 	if err != nil {
 		log.Print("template executing error: ", err)
 	}
 }
 
 func StaticHandler(w http.ResponseWriter, req *http.Request) {
 	static_file := req.URL.Path[len(STATIC_URL):]
 	if len(static_file) != 0 {
 		f, err := http.Dir(STATIC_ROOT).Open(static_file)
 		if err == nil {
 			content := io.ReadSeeker(f)
 			http.ServeContent(w, req, static_file, time.Now(), content)
 			return
 		}
 	}
 	http.NotFound(w, req)
 }
 
 func main() {
 	http.HandleFunc("/", Home)
 	http.HandleFunc("/about/", About)
 	http.HandleFunc(STATIC_URL, StaticHandler)
 	err := http.ListenAndServe(":8000", nil)
 	if err != nil {
 		log.Fatal("ListenAndServe: ", err)
 	}
 }
Любой веб-сервер должен уметь заполнять при пост-бэке формы ее заполненные вручную поля. В go для этого можно использовать словари:

 package main
 
 import (
 	"html/template"
 	"net/http"
 )
 
 var templateString = `
 
 <html>
 <body>
 {{ if .name }}
 <p>Your name: {{ .name }}</p>
 {{ end }}
 <form action="/" method="POST">
 <input type="text" name="name" value="{{ .name }}">
 <input type="submit" value="Send">
 </form>
 </body>
 </html>
 `
 var templ = template.Must(template.New("t1").Parse(templateString))
 
 func myFunc(w http.ResponseWriter, r *http.Request) {
 	context := make(map[string]string)
 	if r.Method == "POST" {
 		context["name"] = r.FormValue("name")
 	}
 	templ.Execute(w, context)
 }
 
 func main() {
 	myHandler := http.HandlerFunc(myFunc)
 	http.ListenAndServe(":8000", myHandler)
 }
Если нам нужно вывести контент в формате XML:

 package main
 
 import (
   "encoding/xml"
   "net/http"
 )
 
 type Profile struct {
   Name    string
   Hobbies []string `xml:"Hobbies>Hobby"`
 }
 
 func main() {
   http.HandleFunc("/", foo)
   http.ListenAndServe(":3000", nil)
 }
 
 func foo(w http.ResponseWriter, r *http.Request) {
   profile := Profile{"Alex", []string{"snowboarding", "programming"}}
 
   x, err := xml.MarshalIndent(profile, "", "  ")
   if err != nil {
     http.Error(w, err.Error(), http.StatusInternalServerError)
     return
   }
 
   w.Header().Set("Content-Type", "application/xml")
   w.Write(x)
 }
В следующем примере показано, как записать и прочитать куку:

 package main
 
 import (
 	"fmt"
 	"strconv"
 	"log"
 	"net/http"
 )
 
 func SetMyCookie(response http.ResponseWriter){
 	cookie := http.Cookie{Name: "testcookiename", Value:"testcookievalue"}
 	http.SetCookie(response, &cookie)
 }
 
 func rootHandler(response http.ResponseWriter, request *http.Request){
 
 	SetMyCookie(response)
 	response.Header().Set("Content-type", "text/plain")
 	fmt.Fprint(response,  "FooWebHandler says ... \n")
 	fmt.Fprintf(response, " request.Method     '%v'\n", request.Method)
 	fmt.Fprintf(response, " request.RequestURI '%v'\n", request.RequestURI)
 	fmt.Fprintf(response, " request.URL.Path   '%v'\n", request.URL.Path)
 	fmt.Fprintf(response, " request.Form       '%v'\n", request.Form)
 	fmt.Fprintf(response, " request.Cookies()  '%v'\n", request.Cookies())
 }
 
 
 func main(){
 	port := 8000
 	portstring := strconv.Itoa(port)
 
 	mux := http.NewServeMux()
 	mux.Handle("/", http.HandlerFunc( rootHandler ))
 
 	log.Print("Listening on port " + portstring + " ... ")
 	err := http.ListenAndServe(":" + portstring, mux)
 	if err != nil {
 		log.Fatal("ListenAndServe error: ", err)
 	}
 }
Сделать upload файла:

 package main
 
 import (
 	"fmt"
 	"html/template"
 	"io/ioutil"
 	"net/http"
 	"os"
 )
 
 var size int64 = 5 * 1024 * 1024
 var html = template.Must(template.New("html").Parse(`
 <html>
 	<head>
 		<meta charset="UTF-8"/>
 		<title>Golang File Upload</title>
 	</head>
 	<body>
 		<form action="/upload" method="POST" enctype="multipart/form-data">
 			<label for="file">File: </label>
 			<input name="file" type="file"></input>
 			<button type="submit">upload</button>
 		</form>
 	</body>
 </html>
 `))
 
 func root(w http.ResponseWriter, r *http.Request) {
 	err := html.Execute(w, nil)
 	if err != nil {
 		fmt.Print(err)
 	}
 }
 
 func upload(w http.ResponseWriter, r *http.Request) {
 	var path string
 	if err := r.ParseMultipartForm(size); err != nil {
 		fmt.Println(err)
 		http.Error(w, err.Error(), http.StatusForbidden)
 	}
 
 	for _, fileHeaders := range r.MultipartForm.File {
 		for _, fileHeader := range fileHeaders {
 			file, _ := fileHeader.Open()
 			path = fmt.Sprintf("%s", fileHeader.Filename)
 			buf, _ := ioutil.ReadAll(file)
 			ioutil.WriteFile(path, buf, os.ModePerm)
 		}
 	}
 	fmt.Printf("File \"%v\" uploaded\n", path)
 }
 
 func main() {
 	http.HandleFunc("/upload", upload)
 	http.HandleFunc("/", root)
 	fmt.Print(http.ListenAndServe(":8000", nil))
 }
В следующем примере дана реализация аутентификации на основе сессии с использованием куки. Сервер будет обслуживать две страницы - корневую по умолчанию, и вторую - internal - куда пользователь попадает после того, как набирает логин и пароль. Для этого примера нужно на гитхабе забрать тулкит под названием горилла. Из этого тулкита понадобится два пакета - mux и securecookie. Их можно установить с помощью команд:
   go get github.com/gorilla/mux
   go get github.com/gorilla/securecookie
после чего дать ссылку на установленные компоненты в виде

 import (
 	"github.com/gorilla/mux"
 	"github.com//gorilla/securecookie"
Можно пойти другим путем: в локальном каталоге, в котором будет лежать текст, приведенный ниже, создать каталог gorilla и скопировать туда два подкаталога mux и securecookie вместе с файлами.
В примере регистрируются 4 хэндлера. Для логина и лог-аута разрешен пост. Функция loginHandler читает логин и пароль, которые приходят из формы. Имя пишется в сессию и происходит редирект на internal страницу. Если логин и пароль пусты, возвращаемся на главную. logoutHandle удаляет сессию и редиректит на главную. setSession ложит логин и пароль в сессию, зашифрованное значение сессии сохраняется в куку. getUserName читает куку. В примере используется т.н. client-side сессия. Другой вариант хранения сессии - в базе.

 package main
 
 import (
 	"fmt"
 	"./gorilla/mux"
 	"./gorilla/securecookie"
 	"net/http"
 )
 
 // cookie handling
 
 var cookieHandler = securecookie.New(
 	securecookie.GenerateRandomKey(64),
 	securecookie.GenerateRandomKey(32))
 
 func getUserName(request *http.Request) (userName string) {
 	if cookie, err := request.Cookie("session"); err == nil {
 		cookieValue := make(map[string]string)
 		if err = cookieHandler.Decode("session", cookie.Value, &cookieValue); err == nil {
 			userName = cookieValue["name"]
 		}
 	}
 	return userName
 }
 
 func setSession(userName string, response http.ResponseWriter) {
 	value := map[string]string{
 		"name": userName,
 	}
 	if encoded, err := cookieHandler.Encode("session", value); err == nil {
 		cookie := &http.Cookie{
 			Name:  "session",
 			Value: encoded,
 			Path:  "/",
 		}
 		http.SetCookie(response, cookie)
 	}
 }
 
 func clearSession(response http.ResponseWriter) {
 	cookie := &http.Cookie{
 		Name:   "session",
 		Value:  "",
 		Path:   "/",
 		MaxAge: -1,
 	}
 	http.SetCookie(response, cookie)
 }
 
 // login handler
 
 func loginHandler(response http.ResponseWriter, request *http.Request) {
 	name := request.FormValue("name")
 	pass := request.FormValue("password")
 	redirectTarget := "/"
 	if name != "" && pass != "" {
 		// .. check credentials ..
 		setSession(name, response)
 		redirectTarget = "/internal"
 	}
 	http.Redirect(response, request, redirectTarget, 302)
 }
 
 // logout handler
 
 func logoutHandler(response http.ResponseWriter, request *http.Request) {
 	clearSession(response)
 	http.Redirect(response, request, "/", 302)
 }
 
 // index page
 
 const indexPage = `
 <h1>Login</h1>
 <form method="post" action="/login">
     <label for="name">User name</label>
     <input type="text" id="name" name="name">
     <label for="password">Password</label>
     <input type="password" id="password" name="password">
     <button type="submit">Login</button>
 </form>
 `
 
 func indexPageHandler(response http.ResponseWriter, request *http.Request) {
 	fmt.Fprintf(response, indexPage)
 }
 
 // internal page
 
 const internalPage = `
 <h1>Internal</h1>
 <hr>
 <small>User: %s</small>
 <form method="post" action="/logout">
     <button type="submit">Logout</button>
 </form>
 `
 
 func internalPageHandler(response http.ResponseWriter, request *http.Request) {
 	userName := getUserName(request)
 	if userName != "" {
 		fmt.Fprintf(response, internalPage, userName)
 	} else {
 		http.Redirect(response, request, "/", 302)
 	}
 }
 
 // server main method
 
 var router = mux.NewRouter()
 
 func main() {
 
 	router.HandleFunc("/", indexPageHandler)
 	router.HandleFunc("/internal", internalPageHandler)
 
 	router.HandleFunc("/login", loginHandler).Methods("POST")
 	router.HandleFunc("/logout", logoutHandler).Methods("POST")
 
 	http.Handle("/", router)
 	http.ListenAndServe(":8000", nil)
 }
В следующем примере мы напишем веб-сервис и клиента к нему. Материал взят отсюда.
Прежде всего нам понадобится пакет net/http, поскольку наш сервис будет работать по протоколу http. Также нам понадобится пакет мартини. Сервис будет выполнять основные прототипы функций для интерфейса гостевой книги: он позволяет добавлять записи в гостевую книгу и читать их после добавления. Записи хранятся в памяти. Запись в гостевой книге представлена структурой:

 type GuestBookEntry struct {
         Id      int
         Email   string
         Title   string
         Content string
 }
Сама гостевая книга представлена структурой, реализованной в форме коллекции:

 type GuestBook struct {
         guestBookData []*GuestBookEntry
 }
Добавление записи в гостевую книгу:

 func (g *GuestBook) AddEntry(email, title, content string) int
Прочитать запись:

 func (g *GuestBook) GetEntry(id int) (*GuestBookEntry, error)
Как вы уже знаете, в go методу можно передать в качестве параметра интерфейс. В этом интерфейсе можно реализовать целый список других методов и произвольный набор типов. Реализация интерфейса WebService, в котором определены 4 метода, в частности методы для добавления, чтения и удаления записи в гостевой книге:

 type WebService interface {
         GetPath() string
  
         // если параметр отсутсвует, удяляются все записи
         WebDelete(params martini.Params) (int, string)
  
         // реализация http-метода GET 
         // если параметр отсутствует, возвращаются все записи
         WebGet(params martini.Params) (int, string)
  
         // реализация http-метода POST method. 
         WebPost(params martini.Params, req *http.Request) (int, string)
 }
Функция, регистрирующая вебсервис, в котором инициализируются эти 4 метода:

   func RegisterWebService(webService WebService, classicMartini *martini.ClassicMartini)
Реализация http.Post:

 func (g *GuestBook) WebPost(params martini.Params,
         req *http.Request) (int, string) {
         defer req.Body.Close()
  
         // читаем тело запроса
         requestBody, err := ioutil.ReadAll(req.Body)
         if err != nil {
                 return http.StatusInternalServerError, “internal error”
         }
  
         if len(params) != 0 {
                 return http.StatusMethodNotAllowed, “method not allowed”
         }
  
         // расшифровываем данные от клиента
         var guestBookEntry GuestBookEntry
         err = json.Unmarshal(requestBody, &guestBookEntry)
         if err != nil {
                 return http.StatusBadRequest, “invalid JSON data”
         }
  
         // добавляем запись
         g.AddEntry(guestBookEntry.Email, guestBookEntry.Title,
                 guestBookEntry.Content)
  
         return http.StatusOK, “new entry created”
 }
Стандартный пакет encoding/json переводит присланные данные из формата json в структуры go.
Код главной функции на сервере:

 func main() {
 	martiniClassic := martini.Classic()
 	guestBook := guestbook.NewGuestBook()
 	guestbook.RegisterWebService(guestBook, martiniClassic)
 	martiniClassic.Run()
 }
Здесь мы создаем 2 обьекта - мартини и гостевую книгу - и передаем эти обьекты в качестве параметров для регистрации сервиса. Исходные коды примера лежат тут . Распаковав архив, в корневом каталоге собираем сервер и клиента:
 go build server.go
 go build client.go
Запускаем сервер:
  server
 
Открываем второй терминал и запускаем клиента со следующими параметрами, выполняя пост:
 client --request_url=http://127.0.0.1:8000/guestbook --request_method=post \
        --request_data='{"Id":0,"Email":"my-email@blablabla.com"}'
Читаем добавленную запись:
 client --request_url=http://127.0.0.1:8000/guestbook/0 --request_method=get
Ее также можно посмотреть в броузере:
 http://localhost:8000/guestbook/0

Часть 2

Рассмотрим простейшее веб-приложение:

 package main
     
     import (
         "fmt"
         "net/http"
     )
     
     func handler(w http.ResponseWriter, r *http.Request) {
         fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
     }
     
     func main() {
         http.HandleFunc("/", handler)
         http.ListenAndServe(":8000", nil)
     }
Функция handler импортирует из базового пакета http два обьекта - http.ResponseWriter и указатель на http.Request. Функция http.HandleFunc будет перенаправлять все запросы для корня на хэндлер. Затем мы включаем прослушивание порта 8000, не используя хэндлер. Системный пакет net/http написан все на том же go. Если глянуть на его исходники, то мы увидим, что ResponseWriter - это интерфейс, в котором определены 3 функии:

     type ResponseWriter interface {
             // заголовок возвращает коллекцию типа map
             Header() Header
     
             // пишет данные для клиента после вызова WriteHeader()
             Write([]byte) (int, error)
     
             // отсылает клиенту заголовок вместе с кодом статуса
             WriteHeader(int)
     }
Хэндлер можно было бы расшифровать и переписать на более низком уровне так:

 func handler(w http.ResponseWriter, r *http.Request) {
         w.Header().Set("Content-Type", "application/json; charset=utf-8") 
 
         myItems := []string{"item1", "item2", "item3"}
         a, _ := json.Marshal(myItems)
 
         w.Write(a)
         return
     }

Иногда возникает необходимость после того, как респонс отдан клиенту, дописать в конец этого респонса что-то еще. Для этого нужно вызвать функцию Write() этого самого респонса. Нужно создать структуру, в которой будет одно поле - хэндлер, создать ссылку на обьект этой структуры, присвоив ее хэндлеру нужное значение, и эту ссылку передать в нужный обработчик:

  package main
 
 import (
 	"net/http"
 )
 
 type AppendMiddleware struct {
 	handler http.Handler
 }
 
 func (a *AppendMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	a.handler.ServeHTTP(w, r)
 	w.Write([]byte(""))
 }
 
 func rootHandler(w http.ResponseWriter, r *http.Request) {
 	w.Write([]byte("Success!"))
 }
 
 func aboutHandler(w http.ResponseWriter, r *http.Request) {
 	w.Write([]byte("About !"))
 }
 
 func main() {
 
 	rmd := &AppendMiddleware{http.HandlerFunc(rootHandler)}
 	amd := &AppendMiddleware{http.HandlerFunc(aboutHandler)}
 
         http.Handle("/", rmd)
         http.Handle("/about/", amd)
 	http.ListenAndServe(":8000", nil)
 }
А что делать, если нужно что-то добавить не в конец, а в начало респонса ? Нам нужен буфер респонса, который можно модифицировать и потом передать в хэндлер. Функция ResponseRecorder хранит этот буфер, и кроме этого хранит заголовки:

 package main
 
 import (
 	"net/http"
 	"net/http/httptest"
 	"strconv"
 )
 
 type ModifierMiddleware struct {
 	handler http.Handler
 }
 
 func (m *ModifierMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	rec := httptest.NewRecorder()
 	// подменяем респонс на ResponseRecorder
 	m.handler.ServeHTTP(rec, r)
 
 	// копируем хидер респонса
 	for k, v := range rec.Header() {
 		w.Header()[k] = v
 	}
 	// добавляем свой собственный хидер
 	w.Header().Set("X-We-Modified-This", "Yup")
 	// status code
 	w.WriteHeader(418)
         // вставляем в начало респонса нужный текст
         data := []byte("Middleware says hello again. ")
 
         // у заголовка изменился Content-Length
         // нужно его пересчитать
         clen, _ := strconv.Atoi(r.Header.Get("Content-Length"))
         clen += len(data)
         r.Header.Set("Content-Length", strconv.Itoa(clen))
 
         // пишем то, что хотели добавить в начало
         w.Write(data)
 	// пишем все остальное
 	w.Write(rec.Body.Bytes())
 }
 
 func myHandler(w http.ResponseWriter, r *http.Request) {
 	w.Write([]byte("Success!"))
 }
 
 func main() {
 	mid := &ModifierMiddleware{http.HandlerFunc(myHandler)}
 
 	println("Listening on port 8000")
 	http.ListenAndServe(":8000", mid)
 }
Обьект http.Request представляет из себя структуру, в которой содержатся параметры клиентского запроса, данные и т.д:

    type Request struct {
             Method string
             URL *url.URL
             Proto      string // "HTTP/1.0"
             ProtoMajor int    // 1
             ProtoMinor int    // 0
             Header Header
             Body io.ReadCloser
             ContentLength int64
             TransferEncoding []string
             Close bool
             Host string
             Form url.Values
             PostForm url.Values
             MultipartForm *multipart.Form
             Trailer Header
             RemoteAddr string
             RequestURI string
             TLS *tls.ConnectionState
     }
Системная функция http.HandleFunc регистрирует хэндлер:

    func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
         DefaultServeMux.HandleFunc(pattern, handler)
     }
ServeMux - это http-мультиплексор или переключатель. Он берет урл из каждого запроса и вызывает тот хэндлер, который ему соответствует. Урл может быть простым и составным.

Исходный код функции http.ListenAndServe - она устанавливает tcp-коннект с keep-alive таймаутом, создавая для каждого входящего коннекта новую goroutine:

     func ListenAndServe(addr string, handler Handler) error {
         server := &Server{Addr: addr, Handler: handler}
         return server.ListenAndServe()
     }
     
    func (srv *Server) ListenAndServe() error {
         addr := srv.Addr
         if addr == "" {
             addr = ":http"
         }
         ln, err := net.Listen("tcp", addr)
         if err != nil {
             return err
         }
         return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
     }
     
    func (srv *Server) Serve(l net.Listener) error {
         defer l.Close()
         var tempDelay time.Duration // how long to sleep on accept failure
         for {
             rw, e := l.Accept()
             if e != nil {
                 if ne, ok := e.(net.Error); ok && ne.Temporary() {
                     if tempDelay == 0 {
                         tempDelay = 5 * time.Millisecond
                     } else {
                         tempDelay *= 2
                     }
                     if max := 1 * time.Second; tempDelay > max {
                         tempDelay = max
                     }
                     srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                     time.Sleep(tempDelay)
                     continue
                 }
                 return e
             }
             tempDelay = 0
             c, err := srv.newConn(rw)
             if err != nil {
                 continue
             }
             c.setState(c.rwc, StateNew) // before Serve can return
             go c.serve()
         }
     }
Пакет http имеет несколько встроенных хэндлеров, таких, как FileServer, NotFoundHandler, RedirectHandler. Последний можно использовать для редиректа - в следующем примере мы создаем два новых обьекта - мультиплексор и редирект-хэндлер, регистрируем мультиплексор и передаем его в качестве параметра - в результате при загрузке корневой страницы сразу произойдет редирект:

   mux := http.NewServeMux()
 
   rh := http.RedirectHandler("http://example.org", 307)
   mux.Handle("/", rh)
 
   log.Println("Listening...")
   http.ListenAndServe(":3000", mux)
В следующем примере мы рассмотрим, как делать вложенные хэндлеры. Пусть у нас имеются два хэндлера, в каждом засекается время его выполнения, после чего это время логируется:

 func aboutHandler(w http.ResponseWriter, r *http.Request) {
   t1 := time.Now()
   fmt.Fprintf(w, "You are on the about page.")
   t2 := time.Now()
   log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
 }
 
 func indexHandler(w http.ResponseWriter, r *http.Request) {
   t1 := time.Now()
   fmt.Fprintf(w, "Welcome!")
   t2 := time.Now()
   log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
 }
 
 func main() {
   http.HandleFunc("/about", aboutHandler)
   http.HandleFunc("/", indexHandler)
   http.ListenAndServe(":8080", nil)
 }
Здесь мы видим повторное использование кода, от которого надо избавиться. Надо написать хэндлер, который будет в качестве параметра принимать другой хэндлер, чтобы это выглядело как-то так:

 loggingHandler(indexHandler)
Для функции логирования мы создадим отдельный хэндлер, который в качестве параметра принимает другой хэндлер:

 func loggingHandler(next http.Handler) http.Handler {
   fn := func(w http.ResponseWriter, r *http.Request) {
     t1 := time.Now()
     next.ServeHTTP(w, r)
     t2 := time.Now()
     log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
   }
 
   return http.HandlerFunc(fn)
 }
Окончательно программа выглядит так:

 package main
 
 import (
   
   "net/http"
   "time"
   "log"
   "fmt"
   
 )  
 
 type Constructor func(http.Handler) http.Handler
 
 // Chain - immutable коллекция хэндлеров
 type Chain struct {
 	constructors []Constructor
 }
 
 // создает коллекцию Chain
 func New(constructors ...Constructor) Chain {
 	c := Chain{}
 	c.constructors = append(c.constructors, constructors...)
 
 	return c
 }
 
 // функция возвращает из коллекции нужный хэндлер
 // берет в качестве параметра хэндлер
 // может вызываться несколько раз подряд, 
 // т.е. уровень вложенности хэндлеров может быть больше двух
 func (c Chain) Then(h http.Handler) http.Handler {
 	var final http.Handler
 	if h != nil {
 		final = h
 	} else {
 		final = http.DefaultServeMux
 	}
 
 	for i := len(c.constructors) - 1; i >= 0; i-- {
 		final = c.constructors[i](final)
 	}
 
 	return final
 }
 
 // эта функция является оберткой для предыдущей функции 
 // берет в качестве параметра хэндлер-функцию
 func (c Chain) ThenFunc(fn http.HandlerFunc) http.Handler {
 	if fn == nil {
 		return c.Then(nil)
 	}
 	return c.Then(http.HandlerFunc(fn))
 }
 
 
 func loggingHandler(next http.Handler) http.Handler {
   fn := func(w http.ResponseWriter, r *http.Request) {
     t1 := time.Now()
     next.ServeHTTP(w, r)
     t2 := time.Now()
     log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
   }
 
   return http.HandlerFunc(fn)
 }
 
 func aboutHandler(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintf(w, "You are on the about page.")
 }
 
 func indexHandler(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintf(w, "Welcome!")
 }
 
 func main() {
   commonHandlers := New(loggingHandler)
   http.Handle("/about/", commonHandlers.ThenFunc(aboutHandler))
   http.Handle("/", commonHandlers.ThenFunc(indexHandler))
   http.ListenAndServe(":8000", nil)
 }
Загружаем веб-сервер и смотрим лог, при этом все логируется:
 2015/01/06 20:13:37 [GET] "/" 39.091µs
 2015/01/06 20:13:48 [GET] "/about/" 9.115µs
Добавим в этот пример еще один обработчик верхнего уровня, который будет обрабатывать ошибки хэндлеров и поддерживать сервер на плаву:

 func recoverHandler(next http.Handler) http.Handler {
     fn := func(w http.ResponseWriter, r *http.Request) {
     defer func() {
       if err := recover(); err != nil {
         log.Printf("panic: %+v", err)
         http.Error(w, http.StatusText(500), 500)
       }
     }()
 
     next.ServeHTTP(w, r)
   }
 
   return http.HandlerFunc(fn)
 }
Главная функция будет выглядеть так:

 func main() {
   commonHandlers := New(loggingHandler, recoverHandler)
   http.Handle("/about/", commonHandlers.ThenFunc(aboutHandler))
   http.Handle("/", commonHandlers.ThenFunc(indexHandler))
   http.ListenAndServe(":8000", nil)
 }
Если теперь в каком-то хэндлере случится непредвиденная серверная ошибка, она будет обработана новым хэндлером и сервер не упадет, а будет работать дальше, только в логе появится сообщение.

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье