Пример пакета сервера на Golang

от автора

В языке Go, по сути, есть две основных сущности: исполняемые файлы, и пакеты. В этой статье предлагаю рассмотреть вторую на небольшом примере.

Пакет — это библиотека функций и структур. По своему назначению она напоминает стандартные, всем хорошо известные, линкуемые библиотеки. Пакет в Go определяет область видимости. Если название переменных или структур начинается с маленькой буквы, то они локальные (область видимости пакета), если с большой, то экспортируемые. Локальные структуры и функции можно использовать только внутри пакета, глобальные внутри и вне пакета. Данную особенность легко понять на примере работы с пакетом json, входящей в состав стандартных библиотек языка.

Подобный код будет возвращать ошибку.

type Link struct { 	name  string 	url string 	title string 	class string }  links := make(map[string]Link) if err = json.Unmarshal(response, &links); err != nil {     return err } 


Дело в том, что, что мы используем функции из пакета json, передавая структуру c полями локальной видимости (в функции Unmarshal к полям структуры Link просто нет доступа).

Код пакета должен располагаться в соответствии с его именем, то есть если пакет называется ru/sezn/server, то файлы *.go будут находиться в папке server, которая будет подпапкой sezn и ru.

Рассмотрим пакет простого веб-сервера, который используется в наших веб приложениях: http://sezn.ru/ и http://hashcode.ru/

В языке Go существует своя собственная библиотека представляющая http-сервер. Поскольку большинство разработчиков имеют более одного сайта, использовать напрямую встроенный http-сервер не получится (в системе есть только один 80 порт). Для запуска мы будем использовать модуль fastCGI веб-сервера Apache2. Ниже приводится файл настройки виртуального хотса приложения.

<VirtualHost *:80>    ServerAdmin webmaster@hashcode.ru     DocumentRoot /path/to/bin/    ServerName sezn.ru     ErrorLog /var/log/apache2/sezn_error.log    LogLevel warn    CustomLog /var/log/apache2/sezn_warning.log combined     AddHandler fastcgi-script our_bin    FastCgiExternalServer /path/to/bin/our_bin -host 127.0.0.1:3489     RewriteEngine On    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f    RewriteRule ^(.*)$ /our_bin [QSA,L] </VirtualHost> 

Пакет сервера реализован в виде простой структуры с набором методов для обработки запросов файлов, регистрации вьюшек, проверки “капчи” и так далее. В основе системы роутинга мы используем библиотеку Gorilla (http://www.gorillatoolkit.org/). Для поддержки капчи была взята библиотека https://github.com/dchest/captcha.

Основная структура сервера:

type Server struct { 	Router    *mux.Router 	Transport string 	Addres    string 	MediaPath string 	CachedFiles map[string]*CachedFile 	fn404     func(w http.ResponseWriter, r *http.Request) } 

Для более быстрой работы мы используем кэширвоание всех файлов в папке с медией.

type CachedFile struct { 	FileName  string 	FullPath  string 	FilePath  string 	FileExt   string 	FileData  []byte } 

Сервер реализует следующий публичный интерфейс.

AddNamedHandler(pattern string, handler func(http.ResponseWriter, *http.Request), name string) AddHandler(pattern string, handler func(http.ResponseWriter, *http.Request)) Reverse(name string) *mux.Route Run() error SetRootMediaPath(path string) Set404ErrorHandler(fn func(w http.ResponseWriter, r *http.Request)) ServeHTTP(w http.ResponseWriter, r *http.Request) 

А таже интерфейс с областью видимости пакета.

cacheFiles() renderFile(w http.ResponseWriter, filename string) error 

Во время инициализации, мы лишь создаем основные структуры.

func InitServer(transport, addres string) *Server { 	server := &Server{Router: &mux.Router{}, Transport: transport, Addres: addres}	return server } 

Затем, по ходу инициализации модулей, основное приложение добавляет именованные/не именованные функции обработчики вызывая метод AddNamedHandler или AddHandler.

Код функции регистрации обработчика.

func (server *Server) AddNamedHandler(pattern string, handler func(http.ResponseWriter, *http.Request), name string) { 	server.Router.HandleFunc(pattern, handler).Name(name) } 

Для запуска сервера необходимо вызвать метод Run. В нем мы кэшируем медиа файлы (к вопросу о кэшировании http://meta.hashcode.ru/questions/1158/) и регистрируем обработчик fastCGI протокола.

func (server *Server) Run() error { 	server.cacheFiles() 	l, err := net.Listen(server.Transport, server.Addres) 	if err != nil { 		return err 	}  	fcgi.Serve(l, server.Router) 	return nil } 

До того, как будет вызван метод Run, необходимо установить обработчик, который будет вызваться если запрашиваемый файл не будет найден.

func (server *Server) Set404ErrorHandler(fn func(w http.ResponseWriter, r *http.Request)) { 	server.fn404 = fn 	server.Router.NotFoundHandler = server } 

Здесь есть тонкий момент, мы регистрируем обработчик ошибки 404, передавая библиотеке Gorilla наш сервер. Сервер реализует интерфейс обработки HTTP запросов. За это отвечает метод ServeHTTP.

func (server *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 	err := server.renderFile(w, r.URL.Path) 	if err != nil { 		w.Header().Set("Content-Type", "text/html; charset=utf-8") 		w.WriteHeader(http.StatusNotFound) 		server.fn404(w, r) 	} } 

Мы регистрируем обработчики всех вьюшек в системе роутинга библиотеки Gorilla. Если обработчик не найден, то мы пробуем найти файл, с таким именем. Если файла нет, то вызываем обработчик ошибки 404.

Метод считывания медиа файлов в память:

func (server *Server) cacheFiles() { 	server.CachedFiles = make(map[string]*CachedFile) 	dir, _ := filepath.Abs(server.MediaPath) 	  	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 		if info.IsDir() { 			return nil 		 		} 		file, err := ioutil.ReadFile(path) 		filePath := strings.Replace(path, dir, "", -1) 		server.CachedFiles[filePath] = &CachedFile { 			FileName: info.Name(), 			FullPath: path, 			FilePath: filePath, 			FileExt: filepath.Ext(path), 			FileData: file, 		} 		return nil 	}); } 

В момент запроса файла, нам необходимо лишь посмотреть вхождение соответствующего пути в словарь с файлами и сформировать ответ. Если файла с запрашиваем адресом не найдено, мы попробуем найти его на диске.

func (server *Server) renderFile(w http.ResponseWriter, filename string) error { 	var file []byte 	var ext string 	var err error  	 	if cachedFile, exist := server.CachedFiles[filename]; exist { 		file = cachedFile.FileData 		ext = cachedFile.FileExt 	} else { 		file, err = ioutil.ReadFile(server.MediaPath + filename) 		if err != nil { 			return err 		} 		ext = filepath.Ext(server.MediaPath + filename) 	} 	 	if ext != "" { 		w.Header().Set("Content-Type", mime.TypeByExtension(ext)) 	} 	if file != nil { 		w.Write(file)		 	}   	return nil } 

Имя “server” далеко не уникальное. Мы размещаем пакеты используя нотацию Java. Когда мы захотим экспортировать пакет, полное имя будет выглядеть как ru/sezn/server. Для создания пакета мы используем программу поставляемую с языком.

go get ru/sezn/server 

Вот в принципе и все. Буду рад ответить на вопросы в комментариях к посту или в соответствующей ветке на ХэшКоде (http://hashcode.ru/questions/tagged/go/).

P. S. Данный пакет переделывался в последний раз под Go RC1 и может не работать с более поздней/ранней версией языка.

ссылка на оригинал статьи http://habrahabr.ru/post/178539/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *