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

  • Tutorial
Когда я задумался о написании 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.Fprint(w, "<form target=\"_blank\" action=\"/exec/\" enctype=\"multipart/form-data\" method=\"post\">"+
	"<input type=\"file\" name=\"imgfile\" />"+
	"<input type=\"submit\" name=\"button\" value=\"Execute\" />"+
	"</form>")


Поэтому мы воспользуемся пакетом 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»), имя файла (сервер пошлет ответ браузеру с этим именем) и записываем изображение.

Полный текст примера
index.html
<!DOCTYPE html>

<html>
    <head>
        <meta charset="utf-8">
        <title>{{.Title}}</title>
    </head>
    <body>
        <form target="_blank" action="/exec/" enctype="multipart/form-data" method="post">
			<input type="file" name="imgfile" />
			<input type="submit" name="button" value="Execute" />
		</form>
    </body>
</html>


lesson.go
package main

import (
	"net/http"
	"html/template"

	"image"
	"image/jpeg"
	_"image/png"
	_"image/gif"
)

type page struct {
	Title string
	Msg string
}

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})
    }
}

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

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




И еще один момент, если вы в своем шаблоне используете какой-нибудь локальный файл (например файл с изображением), то вам придется написать обработчик или использовать 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.StripPrefix("/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
Поделиться публикацией

Похожие публикации

Комментарии 22
    0
    Есть вот такой тутор описывающий разработку веб-приложенией на go golang.org/doc/articles/wiki/
      –3
      поспешил
        +1
        Так я с неё и начинал.
        А вообще, я даже больше скажу, есть перевод (с дополнениями) этой статьи habrahabr.ru/post/122095/
        Но там, например, нет примера по загрузке файла на сервер (а найти его было сложно, а по докам непонятно), да и примеры там слишком уж мудреные. Лично я, когда я только начал, несколько дней перечитывал эту статью и мучил людей на #go-nuts, прежде чем понял что по чем.
          +1
          а за пример с файлом двойное спасибо! :)
        +1
        > t, _ := template.ParseFiles(“index.html”)

        Тут стоит или проверить ошибку, или использовать template.Must. Еще стоило бы упомянуть mux, наверно, он крайне полезен.
          +1
          Я в самом начале статьи написал о том, что я не буду проверять ошибки, чтобы уменьшить количество кода в статье. Если бы я проверял каждую функцию (а в своих программах я это делаю), то было бы слишком много однотипного текста, вроде:
          if err != nil {
          	log.Println(err)
          	return
          }
          

          Там-же я написал про сторонние пакеты.

          Что по поводу template.Must, в моих программах я не использую os.Exit(), os.Panic(), а только return, в противном случае не сработает defer и многие открытые потоки не закроются корректно.
            +3
            При смерти приложения ОС почистит за вас ресурсы, которых в примере толком и нет. А Must не требует полновесной проверки ошибок.
              0
              Ладно, не будет разводить дискуссию, скажу только, что чисто гипотетически, OS может и не почистить, файлы все-равно запишутся с ошибкой (flush/sync могут не сработать), и я предпочитаю те куски кода которые можно написать одинакового, так и писать. Зачем разводить в одном месте log.Fatal, в другом template.Must и т.д.?
          +1
          А есть какой-нибудь MVC-фреймворк для Go? Вроде django что-нибудь?
            +1
            Достаточно прочитать статью чтобы получить этот ответ.
            habrahabr.ru/post/162115/
              0
              Это я видел. Может быть что-то еще?
                0
                Поищите в интернете по подобному запросу: «go language mvc», найдете еще несколько.
              +1
              Наиболее популярные фреймворки — это Revel и Beego. Ничего типа Django и Rails под Go нет и вряд ли будет.
                0
                Почему не будет?
                  0
                  Каждой задаче — свой инструмент. Go и не предназначался для full stack веб приложений. Он прекрасно подходит для веб-сервисов или консольных утилиток. Во всяком случае такое мнение витает в сообществе.

                  Полнофункциональный веб сайт прекрасно пишется на Rails/Django/etc, а бэкенды к нему на всяких Erlang/Go/etc.
                    0
                    Я думаю, это вопрос восприятия. И слабого продвижения Го со стороны Гугла (не факт, что они сами на нем много пишут, как бы вообще не свернули). На Го вроде много разного и пишут, но ничего серьезного я пока не видел. Больше модули, утилиты и все такое.

                    Про разработку сайтов на python я лично узнал на примере Джанги, а на Руби именно с использованием rails. Альтернативы, конечно есть, но эти 2 являются основными для этих языков. Вероятно, именно появление чего-то подобного для Го и хорошее средство для написания именно сайтов основной массе программистов. Сам язык является отличным компромисом между python/ruby и С. Но еще не набрал ту критическую массу популярности, чтобы на него начали портировать крупные фреймворки.

                    Конечно, большинству программистов сайтов такой язык не нужен. Для них он будет слишком сложным из-за типизации и памяти, а максимальная скорость и дешевая многопоточность вроде как и не нужна. Дорога ему в крупные компании с очень высоконагруженными сайтами, где он, возможно, потеснит python и С (один язык для всего это тоже, кстати, плюс). Этим корпорациям, конечно, никакие аналоги Джанги не нужны, но став популярным там, он начнет приживаться и в компаниях поменьше и тогда, возможно, кто-то из них и оформит свои наработки в виде полноценного фреймворка.

                    Но это вопрос пока спорный, сейчас Го еще нужно отлаживать и отлаживать. Большинство моих запросов у Гуглу по поводу ошибок ведут на форумы с долгими разбирательствами и предложением запостить багрепорт, а не решениями. И с качеством модулей пока беда и много альтернатив, которые делают одно и тоже но с разным количеством косяков, прям phpclasses какой-то. Надеюсь, со временем будет что-то типа CPAN, куда будут включаться только проверенные, работающие и полнофункциональные модули без дублей.
              +2
              //и переменные обязательно должны быть глобальными!

              Не глобальными, а публичными.
                0
                Спасибо за указание на ошибку.
                P.S. Пишите такое личным сообщением, а то я исправлю и ваш пост будет бессмысленным.
                  0
                  Тогда уж не публичными, а экспортируемыми ;)
                  +1
                  Вы могли бы использовать вот эту простую библиотеку github.com/hoisie/web
                  в ней очень мало всего скрыто и используются почти прямые вызов в стандартную библиотеку. Как минимум можно было бы на неё посмотреть.

                  В целом статья интересная для начинающих.
                    0
                    В Go есть достаточно всего (опять-же лично для меня и моих задач), а смысл в этом пакете я вижу только в более простой работе с cookie.
                    –1
                    Я правильно понимаю, что по данному тесту _http://forum.dlang.org/thread/jzsvmdtfwjutqtnrcrdh@forum.dlang.org#post-mailman.247.1386169361.3242.digitalmars-d:40puremagic.com

                    D + Vibed со значительным отрывом обходит Go?

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое