В языке 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 и может не работать с более поздней/ранней версией языка.
Автор: DevExpert