Содержание


Язык программирования go

Часть 2. Разработка Web-приложений

Comments

Серия контента:

Этот контент является частью # из серии # статей: Язык программирования go

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Язык программирования go

Следите за выходом новых статей этой серии.

Вторая статья из данного цикла продолжает знакомить с языком программирования go. В этой статье освещается использование языка go в одном из самых перспективных направлений – Web-программировании. В качестве примера будет описана разработка простейшего Web-сервера, также в статье будут рассматриваться некоторые стандартные пакеты.

Огромное количество разработчиков занимается созданием приложений, основанных на возможностях, предлагаемых компьютерными сетями. К данному типу приложений относятся Web-приложения и клиент-серверные системы, а для их разработки было создано огромное количество всевозможных инструментов. Современные языки и технологии позволяют, используя стандартные и хорошо отлаженные средства, создавать приложения со сложной внутренней структурой. При этом получившаяся структура остается скрытой для программиста, и поэтому от разработчика не требуется высокой квалификации. Подобный подход не означает снижения требований к программистам, а, напротив, предполагает, что опытный специалист сможет создать еще более качественное приложение. На практике же это дает разработчикам шанс не увязнуть в сложной логике приложения. К таким средствам относится и язык go. Хотя за его основу и был взят язык С++, однако у языка go есть свои уникальные черты и возможности. Неполноценная реализация объектно-ориентированной парадигмы делает язык go «средним арифметическим» между языками С и С++. Очевидно создатели не разделяют чаяний индустрии относительно ОО подхода.

Создание Web-сервера

Язык go появился в эпоху Интернет, а в это время любой претендующий на место под солнцем язык программирования должен предоставлять богатые возможности для создания распределенных приложений. Эти средства представлены в go пакетами http, net и websocket, также имеются и пакеты для работы с html и xml. Наличие подобной функциональности говорит о серьезных намерениях языка закрепиться в качестве платформы для разработки корпоративных приложений. Однако отсутствие полноценной поддержки платформы MS Windows может помешать этому.

Web-версия самой известной программы на языке go будет выглядеть следующим образом.

Листинг 1. Простейшее Web-приложение
package main;

import (
"http"
"fmt"
)

func requestHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, world")
}

func main(){
	http.HandleFunc("/", requestHandler)
	http.ListenAndServe(":8080", nil)
}

Перед подробным анализом исходного кода приложения необходимо описать, как собрать и запустить приложение, а также принцип его работы. Для компиляции приведенный выше код необходимо сохранить в файле hello.go и выполнить две команды:

  8g hello.go
  8l hello.8.

После этого приложение надо запустить и проверить его работоспособность. Для этого потребуется открыть Web-браузер и ввести в его адресной строке URL: localhost:8080. Если все действия были выполнены успешно, то в окне браузера отобразится строка Hello, world. Если же строка не отобразилась, то следует проверить наличие брандмауэра и, если таковой имеется, то разрешить в нем активность на порту 8080. Также причиной ошибки может быть другое приложение, уже запущенное на этом порту.

После успешного запуска примитивного Web-сервера можно переходить к анализу его исходного кода. Функция main является точкой входа в программу, и в ней происходят вызовы двух функций из пакета http. Первый вызов устанавливает обработчик для подключившихся клиентов (функция requestHandler), а вторым вызовом на указанном порту запускается сервер, так что все запросы, поступающие на порт 8080, будут обрабатываться функцией requestHandler. Для этой функции также будет доступен контекст соответствующего запроса в виде объектов http.Request и http.ResponseWriter.

Создание файлового Web-сервера

В примере, представленном в листинге 2, приведен исходный код другого Web-сервера, обладающего большим набором функциональности. На данном варианте Web-сервера можно будет размещать любые файлы, например, HTML-страницы, графические изображения и архивы. Также в примере будет предусмотрена обработка 404-ой ошибки (файл не найден) и поддержка индексного файла. Однако, несмотря на все это, пример не предназначен для использования в «рабочей» среде и поэтому содержит ряд упрощений. Эти упрощения сделаны намеренно для обеспечения ясности кода и уменьшения его объема. Весь Web-сервер состоит из одного файла, приведенного в листинге 2.

Листинг 2. Более сложный вариант Web-сервера
package main;
import (
"http"
"os"
"mime"
"path"
)
/* функция для обработки подключившихся клиентов */
func requestHandler(w http.ResponseWriter, r *http.Request) {
	file := r.RawURL
	base := os.Getenv("HOME") + "/goserver/www"
	/* если отсутствует запрос к конкретному файлу – показать индексный файл */
	if file == "/" {
		file = "/index.html"
	}
/* если не удалось загрузить нужный файл – показать сообщение о 404-ой ошибке */
reqFile, err := os.Open(base + file, os.O_RDONLY, 0)
if err != nil {
file = "/404.html"
reqFile, err = os.Open(base + file, os.O_RDONLY, 0)
}
/* считать содержимое файла */
fi, err := reqFile.Stat()
contentType := mime.TypeByExtension(path.Ext(file));
var bytes = make([]uint8, fi.Size)
reqFile.Read(bytes)
/* отправить его клиенту */
w.SetHeader("Content-Type", contentType)
w.SetHeader("Server", "goserver/0.1")
w.Write(bytes)
}

func main(){
http.HandleFunc("/", requestHandler)
http.ListenAndServe(":8080", nil)
}

Для работы сервера в домашнем каталоге пользователя следует создать подкаталог goserver и поместить в него файл goserver.go, хотя это и не обязательное требование. Однако имя каталога, в который будет помещен корневой каталог Web-сервера, жестко прописано в коде - это папка $HOME/goserver/www. В каталог www можно сразу поместить файл 404.html, который будет показываться, когда сервер не сможет найти запрашиваемый файл. Это простая HTML-страница, содержимое которой может быть любым, но имя этого файла также жестко прописано в коде программы.

Функция main из листинга 2 является точкой входа в программу. В данной реализации этой функции также вызываются две функции из пакета http, назначение которых уже разбиралось при анализе листинга 1. Вся важная функциональность находится в функции-обработчике requestHandler, которая предназначена для обработки HTTP-запросов. Вторым входным параметром этой функции является переданный клиентом HTTP-запрос. В листинге 3 приведена структура из пакета http, используемая для описания HTTP-запроса.

Листинг 3. Структура, описывающая HTTP-запрос
type Request struct {
    Method     string // тип запроса - GET, POST или PUT
    RawURL     string // необработанное значение URL
    URL        *URL   // обработанное значение URL
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0
    Header Header
    Cookie []*Cookie
    Body io.ReadCloser
    ContentLength int64
    TransferEncoding []string
    Host string
    Referer string
    UserAgent string
    Form map[string][]string
    Trailer Header
}

Как видно, представленная в листинге 3 структура предоставляет доступ ко всем возможностям протокола HTTP, включая удобный доступ к полям формы или передаваемым параметрам запроса. Для передачи этих параметров используется хэш-таблица (ассоциативный массив). Тип данных Header, представляющий заголовки HTTP-запроса, также реализован в виде хэш-таблицы, только для него еще доступны некоторые методы. В примере с Web-сервером в листинге 2 используется только поле RawURL, содержащее URL запрашиваемого файла. На второй строке функции requestHandler при помощи метода Getenv из пакета os берется значение переменной окружения HOME и задается корневой каталог сервера. Пакет os отвечает за кросс-платформенное взаимодействие с функциями операционной системы, например, с файлами (функции Mkdirи Truncate) и переменными окружения (функция Getenv).

На следующей строке объявляется index-файл Web-сервера. Имя этого файла жестко прописано в коде, и хотя это не очень удачное решение с точки зрения дальнейшего сопровождения, оно позволяет несколько упростить код приложения.

После этого выполняется объявление и инициализация двух переменных. Оператор := сам определяет, какого типа выражение находится справа, и устанавливает соответствующий тип для переменной (или переменных), находящихся слева. Такой подход довольно удобен, но поначалу может сбить с толку. В этом же фрагменте демонстрируется, как в языке go выполняется обработка исключительных ситуаций.

  reqFile, err := os.Open(base + file, os.O_RDONLY, 0)

Кроме дескриптора файла на этой строке также объявляется переменная err, которой при возникновении исключительной ситуации будет присвоено определенное значение. В отличие от большинства языков программирования в языке go не разрешается объявлять неиспользуемые переменные, иначе возникает ошибка компиляции. Поэтому дальше объявленную переменную обязательно надо где-нибудь использовать. Если она принадлежит к типу Error, то можно просто проверить наличие ошибки. Возникает следующая ситуация: функция возвращает два значения, и программа обязана их принять и каким-то образом обработать. Получается, что язык go просто принуждает программистов создавать приложения с более тщательным подходом к обработке ошибок.

Как упоминалось в описании Web-сервера, он способен обработать ситуацию, когда запрашиваемый файл отсутствует, показав вместо него HTML-страницу 404.html. Этот файл лежит в папке www, и его имя просто подставляется вместо запрашиваемого файла. Считается, что ошибка произошла, если значение переменной err отлично от nil. Но это самый простой способ обнаружения ошибок, так как переменная типа Error может принимать множество различных значений в зависимости от типа возникшей исключительной ситуации. Для переменных типа Error также можно вызывать функцию Stiring для получения текстового представления ошибки. В реальном приложении следовало бы отслеживать различные типы ошибок, и каким-то образом записывать информацию в журнальный файл.

Хотя в представленном примере журналирование и не поддерживается, но в стандартной библиотеке языка go имеется пакет log. C помощью этого пакета в приложении можно организовать несложную инфраструктуру для записи происходящих событий. Принцип работы подобного модуля будет следующим:

  1. создается объект типа Logger, через который выполняется настройка параметров системы журналирования: место для хранения файлов и конфигурация конкретного модуля для выполнения журналирования;
  2. далее в коде приложения события регистрируются путем вызова соответствующих методов: простое уведомление (метод Print) или ошибка (методы Fatal и Panic).

В типе File реализованы некоторые стандартные функции, использующиеся при работе с файлами, такие как чтение, запись, смена владельца и т.д. Здесь, также как и в других пакетах, можно заметить ориентацию go на платформу Unix. Это проявляется в наличии методов для выполнения операций, которые встречаются только на Unix-платформах, или в константах, которые также часто встречаются в Unix-системах, например, кодов ошибок.

Для корректной передачи запрашиваемого файла пользователю программе потребуется получить некоторую информацию об этом файле. Для этого используется функция Stat, возвращающая структуру типа FileInfo. Из этой структуры можно получить информацию о размере и о типе файла. Размер файла необходим для создания байтового массива, в который будет записан файл, и в этом случае считывание файла будет выполнено за один проход. Однако можно создать буфер, например, размером в 1024 байт и считывать файл порциями, и зачастую именно такой подход применяется в реальных приложениях, так как большие файлы очень быстро израсходуют память сервера.

Также потребуется информация о типе файла, а конкретно о его MIME-типе. Зная тип файла, Web-браузер пользователя сможет его корректно отобразить. Например, показать в окне, если это HTML-страница, или предложить сохранить на диск, если это архив. Эта информация будет передана в Web-браузер через заголовки HTTP-ответа. В объекте http.ResponseWriter для этого имеется метод SetHeader, в который передаются две строки, соответствующие имени HTTP-заголовка и его значению. Дополнительную информацию о различных типах HTTP-заголовков и их значениях можно получить в Wikipedia.

Для сборки представленного примера лучше воспользоваться специализированными инструментами. В листинге 4 приведен пример make-файла, который будет понятен всем С-программистам, работающим с Unix-платформой.

Листинг 4. Makefile для сборки приложения goserver
include $(GOROOT)/src/Make.inc
TARG=goserver
GOFILES=\
	goserver.go\
include $(GOROOT)/src/Make.cmd

Для корректной работы представленного сценария в системе должна быть определена переменная GOROOT. Файлы, подключаемые директивой include, позволяют с помощью нескольких строк кода создавать кросс-платформенный исполняемый код. При создании пакетов потребуется выполнить такие же действия, за исключением того, что необходимо будет подключить другой файл: include $(GOROOT)/src/Make.pkg.

Заключение

Несмотря на свою простоту, приложение, рассмотренное в этой статье, представляет собой достаточно серьезную программу, которая отличается от других Web-серверов, например, Apache и MS IIS только функциональными возможностями. При этом благодаря простоте программирования на языке go и предоставляемым им возможностям, представленный пример вполне можно довести до уровня, на котором приложение будет пригодно к коммерческому использованию. Также создатели языка go утверждают, что если переписать Web-сервер Apache на языке go, то исходного кода получится на порядок меньше, а производительность при этом практически не уменьшится.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=696645
ArticleTitle=Язык программирования go: Часть 2. Разработка Web-приложений
publish-date=06302011