воскресенье, 12 июня 2011 г.

Web Application with Go


Некоторое время назад столкнулся с очень интересным языком программирования, носящим гордое название Go)
Оказывается, он существует уже достаточно давно и количество проектов в code.google, написанных на этом языке уже не так мало (http://code.google.com/hosting/search?q=label:Go). Для справки, wikipedia сообщает, что
"На данный момент его поддержка осуществляется для операционных систем FreeBSD, Linux, Mac OS X и частично Windows" (линуксоиды, радуемся!)). 
Насколько я понял, причиной создания Go явилась Идея - попытка сделать нечто такое, что работало бы чрезвычайно быстро, но при этом радовало простотой разработки. Т.е. совместить C/C++ и python)
Т.к. язык разрабатывается компанией Google и поддерживает много интересные вещей (например, сборку мусора или обобщенного программирования), то я просто не мог обойти его своим вниманием.

Пару слов об установке на linux

  • для установки потребуются следующие пакеты:
    $ sudo apt-get install python-setuptools python-dev mercurial bison gcc libc6-dev ed make
  • добавить переменные окружения (директории, конечно, можете выбрать любые)
    export GOROOT=$HOME/go
    export GOARCH=386
    export GOOS=linux
    export GOBIN=$HOME/bin
    export PATH=$GOBIN:$PATH
  • затем добавить эти строки в .bashr и не забыть создать нужные директории
    $ mkdir ~/bin  
    $ chmod 755 ~/bin
  • далее надо клонировать репозиторий Go
    $ hg clone -r release https://go.googlecode.com/hg/ $GOROOT  
    и начать установку, выполнив скрипт .all.bash
    $ cd $GOROOT/src 
    $ ./all.bash
Для разработки можно использовать Go перспективу для eclipse (хотя она мне почему-то не впечатлила), либо взять плагин для gedit (http://go-lang.cat-v.org/text-editors/gedit/) или другого удобного редактора.

Начинаем кодить
Ну теперь стоит прикоснуться к Go и что-нибудь написать) Hello World никому не интересны, поэтому я решил поглядеть на пример написания простенького веб-приложения, которое будет отображать wiki страницу. Итак...
Для начала создадим файл wiki.go, который будет содержать код приложения, и постепенно наполним его смыслом)


package main

import (
"http"
"io/ioutil"
"os"
"regexp"
"template"
)

Конечно, package) здесь говорим, что создаем пакет main и импортируем другие необходимые для работы пакеты. 
  • regexp - для работы с регулярными выражениями
  • template - для работы с шаблонами (далее скажу подробнее)
  • os - для работы со стандартными потоками Stdin,Stdout и ошибками
type Page struct {
  Title string
  Body  []byte 
}
Создаем структуру данных, которая будет описывать веб страницу. Скажу сразу, в Go редко используются точки с запятой, поэтому нужно отбросить внезапно появившееся желание перевести строку следующим образом
func someFunc()
  {
  }
ибо это вызвет негодования компилятора. Как точку с запятой Go трактует конец не пустой строки (при определенных условиях).
 Ну и скорее всего такой способ объявления не порадует C-программистов - создатели Go решили сделать объявление переменных более приближенным к человеческому языку, и теперь это выглядит так: "var A int", т.е. переменная такая-то такого-то типа. Но можно и не заморачитаться с объявлением, написав нечто подобное "p, err := loadPage(title)". Здесь loadPage возвращает два значения, которые принимают переменные p и err.

func (p *Page) save() os.Error {
 filename := p.Title + ".txt"
 return ioutil.WriteFile(filename, p.Body, 0600)
}

func loadPage(title string) (*Page, os.Error) {
 filename := title + ".txt"
 body, err := ioutil.ReadFile(filename)
 if err != nil {
  return nil, err
 }
 return &Page{Title: title, Body: body}, nil
}
Что можно сделать со страницей? Правильно, сохранить в виде файла либо загрузить) Причем сохранение мы описываем прямо на типе Page, указывая перед функцией receiver - (p *Page). Ключевое слово class в Go отсутствует, вместо этого для любого именнованого типа (включая структуры и базовые типы вроде int) можно определить методы работы с ним. Это, конечно, может показаться неожиданным - да что там, это чертовски непривычно и любопытно) Именно поэтому в слайдах документации перед описанием объектно-ориентированности в Go появляется призыв отбросить предубеждения) Для полноты описания, скажу что loadPage возвращает два значения - os.Error отвечает за ошибки, которые могли произойти в процессе загрузки файла. Дальше начинается самое интересное - обработка запросов, приходящих от клиента. Все обработчики будут строиться по определенному принципу
 func someHandler(w http.ResponseWriter, r *http.Request, title string) {
  p, err := loadPage(title)
  if err != nil {
   //some error handler
   return
  }
  renderTemplate(w, "someTemplate", p)
 }
На вход, естесственно, принимается writer для ответа, сам http запрос и название запрошенной страницы. Затем нужная страница загружается по названию и происходит проверка выполнения загрузки. Отображением занимается функция renderTemplate, которая отображает шаблон страницы, заполненный нужными данными.
 func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
 p, err := loadPage(title)
 if err != nil {
  http.Redirect(w, r, "/edit/"+title, http.StatusFound)
  return
 }
 renderTemplate(w, "view", p)
 }

 func editHandler(w http.ResponseWriter, r *http.Request, title string) {
  p, err := loadPage(title)
  if err != nil {
   p = &Page{Title: title}
  }
  renderTemplate(w, "edit", p)
 }

 func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
  body := r.FormValue("body")
  p := &Page{Title: title, Body: []byte(body)}
  err := p.save()
  if err != nil {
   http.Error(w, err.String(), http.StatusInternalServerError)
   return
  }
  http.Redirect(w, r, "/view/"+title, http.StatusFound)
 }
Т.к. это все-таки wiki, страницу можно просмотреть, отредактировать и сохранить измененный вариант. Здесь также присутствуют вызовы методов из пакета http - http.Error (отправляет клиенту сообщение об ошибке) и http.Redirect, которая запрашивает сохраненную страницу на просмотр и приводит к выполнению обработчика viewHandler. Теперь настало время renderTemplate)
 //создаем список, содержащий *Template, ассоциируемый с ключами типа string
 //make - аналог new в cpp, но совершающий еще некоторые действия, помимо выделения памяти
 var templates = make(map[string]*template.Template) 
               
 func init() { //инициализируем список двумя значениями
  for _, tmpl := range []string{"edit", "view"} {
   templates[tmpl] = template.MustParseFile(tmpl+".html", nil)
  }
 }

 
 func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
  //шаблон заполняется нужными значениями из p и передается в ResponseWriter  
  err := templates[tmpl].Execute(w, p)
  if err != nil {
   http.Error(w, err.String(), http.StatusInternalServerError)
  }
 }
а шаблон выглядит примерно так:
 <h1>Editing {Title}</h1>

 <form action="/save/{Title}" method="POST">
 <div><textarea name="body" rows="20" cols="80">{Body|html}</textarea></div>
 <div><input type="submit" value="Save"></div>
 </form>
Т.е. просто туда, где надо, подставляем значения, которые будут получены из объекта Page в будущем. Теперь немного валидации. Все, что передал пользовател, надо проверять - это аксиома)
 const lenPath = len("/view/")

 var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")

 func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
   title := r.URL.Path[lenPath:]
   if !titleValidator.MatchString(title) {
    http.NotFound(w, r)
    return
   }
   fn(w, r, title)
  }
 }
На счет регулярок здесь все просто, а вот функция makeHandler требует пристального внимания. Она будет использоваться для создания обработчиков (возвращает http.HandlerFunc) и принимает на вход переменную типа функции с нужными параметрами. По сути, это обертка для наших обработчиков, которая помимо основных действий (представленных вызовом fn(w,r,title)) еще и выполняет проверку запрошенного пользователем заголовка. Ну и, наконец, добрались до финала
 func main() {
  http.HandleFunc("/view/", makeHandler(viewHandler))
  http.HandleFunc("/edit/", makeHandler(editHandler))
  http.HandleFunc("/save/", makeHandler(saveHandler))
  http.ListenAndServe(":8080", nil)
 }
Здесь мы просто указываем, какой обработчик будет отвечать за ту или иную страницу. Затем начинаем слушать 8080 порт на localhost'е. Теперь надо все это безобразие собрать и запустить
 $ 8g wiki.go
 $ 8l -o wiki wiki.8
 $ ./wiki
Отмечу, что компилятор 8g (для архитектуры 386) в использовании во многом напоминает обычный gcc. Ну вот и все, мой рассказ окончен. Боюсь, что получилось несколько сумбурно, но я старался заинтересовать) Вот несколько полезных ссылок:

      0коммент.:

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