Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
      Languages 
      Kernels 
      Packages 
      Books 
      Tests 
      OS 
      Forum 
      Математика 
NEWS
Последние статьи :
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
  SQL 30.07   
  Python 10.06   
 
TOP 20
 Intel 386...632 
 Trees...565 
 2.0-> Linux IP Networking...353 
 Part 4...313 
 OS ->Intel Manual 1...308 
 Python...307 
 Ethreal 2...304 
 Stein-MacEachern-> Час...304 
 Go Web ...303 
 William Gropp...302 
 Rodriguez 6...298 
 Keogh 1...286 
 Robert Love 4...282 
 Ethreal 3...278 
 Ethreal 1...278 
 Secure Programming for Li...276 
 Стивенс 5...273 
 Plusquellic 2...273 
 Си за 21 день...271 
 Lists...269 
 
  01.05.2020 : 2901126+ посещений 

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)
 }
Если теперь в каком-то хэндлере случится непредвиденная серверная ошибка, она будет обработана новым хэндлером и сервер не упадет, а будет работать дальше, только в логе появится сообщение.

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

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

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