Пакет — это библиотека функций и структур. По своему назначению она напоминает стандартные, всем хорошо известные, линкуемые библиотеки. Пакет в 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/
Добавить комментарий