...

суббота, 24 августа 2013 г.

Написание своего Web-приложения на Go

Когда я задумался о написании Web приложения с использованием Go, я преследовал лишь желание попробовать нечто новое для себя. В последствии я понял, что Web оболочку можно использовать как кросплатформенную GUI библиотеку, чем и воспользовался в своем проекте[1].


Вступление



Отличием данного урока от остальных то, что я буду рассматривать только стандартные пакеты Go. Для моей задачи этого было более чем достаточно, да и разбираться в каких-то фреймворках (к примеру Revel[2]) у меня не было желания. Так-же я затрону тему запросов. Опять-же, четкого примера по загрузке файла на сервер в интернете, на данный момент, нет. Поэтому я решил написать статью, которая позволит создать скелет, для дальнейшей разработки.

В программе я не буду обрабатывать ошибки, по причине уменьшения объема кода. Так-же я не буду описывать тривиальные вещи, вроде очевидных аргументов функций (узнать подробности можно в официальной документации[3][4]). Некоторые моменты, для ясности, могут быть описаны не строго формальным языком, или не совсем верно.


Начало



Начнем с простой программы «Hello World». Для этого нам нужно описать функцию обработчик (handler):

func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "text/plain")
w.Write([]byte("Hello World!!!"))
}




Аргументами функции являются ResponseWriter и Request. ResponseWriter – это интерфейс, который используется для формирования HTTP ответа, Request – представляет из себя HTTP запрос полученный сервером или предназначенный для отправки клиенту. В данной функции, в заголовке устанавливаем тип содержимого «простой текст», а затем отправляем клиенту нашу строку.

Дальше нам нужно уже «привязать» данный обработчик к паттерну:



func main() {
http.HandleFunc("/", index)

http.ListenAndServe(":80", nil)
}


С помощью HandleFunc мы привязываем функцию обработчик к паттерну, а c с помощью ListenAndServe уже непосредственно запускаем сервер. Паттерн может быть любым: «/», «/page0», «/page0/page01». Но все что после паттерна, будет относится к нему. К примеру у нас зарегистрированы два паттерна: «/» и «/page0». Если в адресной строке написать «/page», то обращение будет к «/», а если «/page0/page», то к «/page0». Полностью код выглядит так:



package main

import (
"net/http"
)

func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "text/plain")
w.Write([]byte("Hello World!!!"))
}

func main() {
http.HandleFunc("/", index)

http.ListenAndServe(":80", nil)
}


Компилируем, запускаем (или сразу запускаем), в браузере переходим по адресу где запущен сервер (на локальной машине это localhost – 127.0.0.1) и видим нашу строку.


Используем шаблоны



«Вшитый» html-код в программу выглядит крайне уродливо и крайней неудобен для редактирования (особенно в больших проектах):

fmt.Fprintf(w, "<h1>Editing %s</h1>"+
"<form action=\"/save/%s\" method=\"POST\">"+
"<textarea name=\"body\">%s</textarea><br>"+
"<input type=\"submit\" value=\"Save\">"+
"</form>",
p.Title, p.Title, p.Body)


Поэтому мы воспользуемся пакетом template, для улучшения читаемости и простоты редактирования. Для начала напишем «костяк» нашей страницы (я буду ориентируясь на стандарт HTML5):



<!DOCTYPE html>

<html>
<head>
<meta charset="utf-8">
<title>{{.Title}}</title>
</head>
<body>
<p>{{.Msg}}</p>
</body>
</html>


Title и Msg в последствии превратятся в текст, но об этом чуть позже. Для начала нам нужно описать структуру данных, которую мы потом передадим функции:



type page struct {
Title string //имена переменных должны совпадать с тем, что мы написали выше!
Msg string //и переменные обязательно должны быть глобальными!
}


Теперь напишем функцию обработчик:



func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "text/html")

t, _ := template.ParseFiles(“index.html”)
t.Execute(w, &page{Title: "Just page", Msg: "Hello World"})
}


ParseFiles – загружает указанный файл (или файлы), Execute обрабатывает шаблон, подставляя данные, и пишет их в w.


Обработка событий



Осталось последнее, это сделать страницу с какой-нибудь кнопкой, загрузкой файла и т.п. В примере, сервер примет файл с изображением, конвертирует его в JPEG с качеством 0 (так будет ясно видно что все работает), и вернет обратно.

Изменим наш html-шаблон, добавив в него форму с методом POST:



<form target="_blank" action="/exec/" enctype="multipart/form-data" method="post">
<input type="file" name="imgfile" />
<input type="submit" name="button" value="Execute" />
</form>


Напишем функцию обработчик:



func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "text/html")

title := r.URL.Path[len("/"):]

if title != "exec/" {
t, _ := template.ParseFiles("index.html")
t.Execute(w, &page{Title: "Convert Image"})
} else {
imgfile, fhead, _ := r.FormFile("imgfile")

img, ext, _ := image.Decode(imgfile)

w.Header().Set("Content-type", "image/jpeg")
w.Header().Set("Content-Disposition", "filename=\"" + fhead.Filename + "." + ext + "\"")
jpeg.Encode(w, img, &jpeg.Options{0})
}
}


В переменной title будет то, что находится после паттерна. К примеру если адрес «http://localhost/qwe/», то в title будет «qwe/». Дальше мы смотрим, если мы не на «exec/», то загружаем, обрабатываем и выводим обычную страницу, если же мы на «exec/», то обрабатываем входной файл. FormFile — возвращает файл с указанным ключом (если таких форм несколько, то первый из них) и немного информации о файле. Мы устанавливаем тип контента (как «image/jpeg»), имя файла (сервер пошлет ответ браузеру с этим именем) и записываем изображение.


И еще один момент, если вы в своем шаблоне используете какой-нибудь локальный файл (например файл с изображением), то вам придется написать обработчик или использовать FileServer из пакета http. В первом случае у вас будет нечто подобное:



func logo(w http.ResponseWriter, r *http.Request) {
file, _ := ioutil.ReadFile("img/logo.png")
w.Write(file)
}

<...>
http.HandleFunc("/img/logo.png", logo)
<...>


А во втором все проще, т.к. не потребуется описывать каждый отдельный файл:



<...>
http.Handle("/img/", http.FileServer(http.Dir("img")))
<...>


Заключение



Таким образом, я показал насколько просто и понятно на Go можно написать свое Web-приложение. Преимущества Go, это простота самого языка, простота пакетов для работы с http-протоколом, возможности использования шаблонов, а так-же независимость кода, написанного на чистом Go, от архитектуры и операционной системы. Go, на данный момент, поддерживает: FreeBSD (x86, amd64, arm), NetBSD(x86, amd64), GNU/Linux (x86, amd64, arm), MacOS X (x86, amd64), Windows (x86, amd64).




[1] mapitemeditor.sourceforge.net/

[2] habrahabr.ru/post/162115/

[3] golang.org/doc/

[4] golang.org/pkg/

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


Комментариев нет:

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