Создаем структуру данных, которая будет описывать веб страницу. Скажу сразу, в 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.
Ну вот и все, мой рассказ окончен. Боюсь, что получилось несколько сумбурно, но я старался заинтересовать)
Вот несколько полезных ссылок:
 |
|